Files: 3a98bfe282f62b3b4dbf11b0dbe2d7443d39dd48 / book / html / layout / detail.js
5642 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, when, computed, Value } = require('mutant') |
3 | var htmlEscape = require('html-escape') |
4 | const addSuggest = require('suggest-box') |
5 | |
6 | exports.needs = nest({ |
7 | 'book.obs.book': 'first', |
8 | 'about.html.image': 'first', |
9 | 'about.obs.name': 'first', |
10 | 'keys.sync.id': 'first', |
11 | 'emoji.async.suggest': 'first', |
12 | 'emoji.sync.url': 'first', |
13 | 'message.html': { |
14 | 'markdown': 'first' |
15 | }, |
16 | 'book.html': { |
17 | 'title': 'first', |
18 | 'authors': 'first', |
19 | 'description': 'first', |
20 | 'images': 'first' |
21 | } |
22 | }) |
23 | |
24 | exports.gives = nest('book.html.layout') |
25 | |
26 | exports.create = (api) => { |
27 | return nest('book.html.layout', bookLayout) |
28 | |
29 | function renderEmoji (emoji, url) { |
30 | if (!url) return ':' + emoji + ':' |
31 | return ` |
32 | <img |
33 | src="${htmlEscape(url)}" |
34 | alt=":${htmlEscape(emoji)}:" |
35 | title=":${htmlEscape(emoji)}:" |
36 | class="emoji" |
37 | > |
38 | ` |
39 | } |
40 | |
41 | function simpleMarkdown(text) { |
42 | if (text.startsWith(':')) |
43 | return renderEmoji(text, api.emoji.sync.url(text.match(/:([^:]*)/)[1])) |
44 | else |
45 | return text |
46 | } |
47 | |
48 | function ratingEdit(isEditing, value) { |
49 | return when(isEditing, |
50 | h('input', {'ev-input': e => value.set(e.target.value), value: value, |
51 | placeholder: 'your rating' }), |
52 | h('span.text', { innerHTML: computed(value, simpleMarkdown) })) |
53 | } |
54 | |
55 | function ratingTypeEdit(isEditing, value) { |
56 | let getEmojiSuggestions = api.emoji.async.suggest() |
57 | |
58 | let ratingTypeInput = h('input', {'ev-input': e => value.set(e.target.value), |
59 | value: value, placeholder: 'rating type' }) |
60 | |
61 | let suggestWrapper = h('span.ratingType', ratingTypeInput) |
62 | |
63 | addSuggest(ratingTypeInput, (inputText, cb) => { |
64 | if (inputText[0] === ':') { |
65 | cb(null, getEmojiSuggestions(inputText.slice(1))) |
66 | } |
67 | }, {cls: 'PatchSuggest'}) |
68 | |
69 | return when(isEditing, suggestWrapper, |
70 | h('span.text', { innerHTML: computed(value, simpleMarkdown) })) |
71 | } |
72 | |
73 | function simpleEdit(isEditing, name, value) { |
74 | return h('div', { classList: when(computed([value, isEditing], (v, e) => { return v || e }), |
75 | '-expanded', '-contracted') }, |
76 | [h('span', name + ':'), |
77 | when(isEditing, |
78 | h('input', {'ev-input': e => value.set(e.target.value), value: value }), |
79 | h('span', value))]) |
80 | |
81 | } |
82 | |
83 | function textEdit(isEditing, name, value) { |
84 | const markdown = api.message.html.markdown |
85 | const input = h('textarea', {'ev-input': e => value.set(e.target.value), value: value }) |
86 | |
87 | return h('div', { classList: when(computed([value, isEditing], (v, e) => { return v || e }), |
88 | '-expanded', '-contracted') }, |
89 | [h('div', name + ':'), |
90 | when(isEditing, input, computed(value, markdown))]) |
91 | } |
92 | |
93 | function bookLayout (msg, opts) { |
94 | if (!(opts.layout === undefined || opts.layout === 'detail')) return |
95 | |
96 | const { obs, isEditing, isCard } = opts |
97 | |
98 | const { title, authors, description, images } = api.book.html |
99 | |
100 | let isEditingSubjective = Value(false) |
101 | let reviews = [] |
102 | |
103 | return [h('Message -book-detail', [ |
104 | title({ title: obs.title, msg, isEditing, onUpdate: obs.title.set }), |
105 | authors({authors: obs.authors, isEditing, onUpdate: obs.authors.set}), |
106 | h('section.content', [ |
107 | images({images: obs.images, isEditing, onUpdate: obs.images.add }), |
108 | h('section.description', |
109 | description({description: obs.description, isEditing, onUpdate: obs.description.set})), |
110 | ]), |
111 | h('section.actions', [ |
112 | h('button.edit', { 'ev-click': () => isEditing.set(!isEditing()) }, |
113 | when(isEditing, 'Cancel', 'Edit book')), |
114 | when(isEditing, h('button', {'ev-click': () => saveBook(obs)}, 'Update book')) |
115 | ]), |
116 | h('section.subjective', [ |
117 | computed(obs.subjective, subjectives => { |
118 | let i = 0; |
119 | Object.keys(subjectives).forEach(user => { |
120 | if (i++ < reviews.length) return |
121 | let subjective = obs.subjective.get(user) |
122 | let isMe = Value(api.keys.sync.id() == user) |
123 | let isOwnEditingSubj = computed([isEditingSubjective, isMe], |
124 | (e, me) => { return e && me }) |
125 | reviews.push([ |
126 | h('section', [api.about.html.image(user), |
127 | when(computed([subjective.rating, isEditingSubjective], |
128 | (v, e) => { return v || e }), |
129 | h('span.text', [api.about.obs.name(user), ' rated '])), |
130 | ratingEdit(isOwnEditingSubj, subjective.rating), |
131 | ratingTypeEdit(isOwnEditingSubj, subjective.ratingType)]), |
132 | simpleEdit(isOwnEditingSubj, 'Shelve', subjective.shelve), |
133 | simpleEdit(isOwnEditingSubj, 'Genre', subjective.genre), |
134 | textEdit(isOwnEditingSubj, 'Review', subjective.review) |
135 | ]) |
136 | }) |
137 | |
138 | return reviews |
139 | }) |
140 | ]), |
141 | h('section.actions', [ |
142 | h('button.subjective', { 'ev-click': () => isEditingSubjective.set(!isEditingSubjective()) }, |
143 | when(isEditingSubjective, 'Cancel', 'Edit rating')), |
144 | when(isEditingSubjective, h('button', { 'ev-click': () => saveSubjective(obs) }, 'Update rating')) |
145 | ]), |
146 | ])] |
147 | |
148 | function saveBook(obs) { |
149 | obs.amend() |
150 | |
151 | isEditing.set(false) |
152 | } |
153 | |
154 | function saveSubjective(obs) { |
155 | obs.updateSubjective() |
156 | |
157 | isEditingSubjective.set(false) |
158 | } |
159 | } |
160 | } |
161 |
Built with git-ssb-web