Files: fd2459df8b46e1e0fe589fc20f3d16020399d671 / book / html / layout / detail.js
6598 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, when, computed, Value, map } = require('mutant') |
3 | const addSuggest = require('suggest-box') |
4 | |
5 | exports.needs = nest({ |
6 | 'book.obs.book': 'first', |
7 | 'about.html.image': 'first', |
8 | 'about.obs.name': 'first', |
9 | 'keys.sync.id': 'first', |
10 | 'emoji.async.suggest': 'first', |
11 | 'message.html': { |
12 | markdown: 'first' |
13 | }, |
14 | 'book.html': { |
15 | title: 'first', |
16 | authors: 'first', |
17 | series: 'first', |
18 | description: 'first', |
19 | images: 'first', |
20 | simpleEmoji: '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 ratingEdit(isEditing, value) { |
30 | return when(isEditing, |
31 | h('input', { |
32 | 'ev-input': e => value.set(e.target.value), |
33 | value, |
34 | placeholder: 'your rating' |
35 | }), |
36 | h('span.text', { innerHTML: computed(value, api.book.html.simpleEmoji) }) |
37 | ) |
38 | } |
39 | |
40 | function ratingTypeEdit(isEditing, value) { |
41 | let getEmojiSuggestions = api.emoji.async.suggest() |
42 | |
43 | let ratingTypeInput = h('input', {'ev-input': e => value.set(e.target.value), |
44 | value: value, placeholder: 'rating type' }) |
45 | |
46 | let suggestWrapper = h('span.ratingType', ratingTypeInput) |
47 | |
48 | addSuggest(ratingTypeInput, (inputText, cb) => { |
49 | if (inputText[0] === ':') { |
50 | cb(null, getEmojiSuggestions(inputText.slice(1))) |
51 | } |
52 | }, {cls: 'PatchSuggest'}) |
53 | |
54 | ratingTypeInput.addEventListener('suggestselect', ev => { |
55 | value.set(ev.detail.value) |
56 | }) |
57 | |
58 | return when(isEditing, suggestWrapper, |
59 | h('span.text', { innerHTML: computed(value, api.book.html.simpleEmoji) })) |
60 | } |
61 | |
62 | function simpleEdit(isEditing, name, value) { |
63 | const classList = computed([value, isEditing], (v, e) => { |
64 | return v || e |
65 | ? '-expanded' |
66 | : '-contracted' |
67 | }) |
68 | |
69 | return h('div', { classList }, [ |
70 | h('span', name + ':'), |
71 | when(isEditing, |
72 | h('input', {'ev-input': e => value.set(e.target.value), value }), |
73 | h('span', value) |
74 | ) |
75 | ]) |
76 | } |
77 | |
78 | function textEdit(isEditing, name, value) { |
79 | const classList = computed([value, isEditing], (v, e) => { |
80 | return v || e |
81 | ? '-expanded' |
82 | : '-contracted' |
83 | }) |
84 | |
85 | return h('div', { classList }, [ |
86 | h('div', name + ':'), |
87 | when(isEditing, |
88 | h('textarea', {'ev-input': e => value.set(e.target.value), value }), |
89 | computed(value, api.message.html.markdown) |
90 | ) |
91 | ]) |
92 | } |
93 | |
94 | function saveSubjective(obs, isEditingSubjective) { |
95 | obs.updateSubjective() |
96 | isEditingSubjective.set(false) |
97 | } |
98 | |
99 | function handleSubjective(user, i, obs) { |
100 | let originalSubjective = {} |
101 | let isEditingSubjective = Value(false) |
102 | let subjective = obs.subjective.get(user) |
103 | let isMe = Value(api.keys.sync.id() == user) |
104 | let isOwnEditingSubj = computed([isEditingSubjective, isMe], |
105 | (e, me) => { return e && me }) |
106 | let showRating = computed([subjective.rating, isEditingSubjective, isMe], |
107 | (v, e, me) => { return v || (e && me) }) |
108 | |
109 | function editRatingClick() { |
110 | if (isEditingSubjective()) { // cancel |
111 | if (obs.subjective.has(api.keys.sync.id())) { |
112 | let subj = obs.subjective.get(api.keys.sync.id()) |
113 | Object.keys(originalSubjective).forEach((v) => { |
114 | subj[v].set(originalSubjective[v]) |
115 | }) |
116 | } |
117 | } else { |
118 | if (obs.subjective.has(api.keys.sync.id())) |
119 | originalSubjective = JSON.parse(JSON.stringify(obs.subjective.get(api.keys.sync.id())())) |
120 | } |
121 | |
122 | isEditingSubjective.set(!isEditingSubjective()) |
123 | } |
124 | |
125 | return [ |
126 | h('section', |
127 | [api.about.html.image(user), |
128 | h('span.text', [api.about.obs.name(user), when(showRating, ' rated ')]), |
129 | ratingEdit(isOwnEditingSubj, subjective.rating), |
130 | ratingTypeEdit(isOwnEditingSubj, subjective.ratingType)]), |
131 | simpleEdit(isOwnEditingSubj, 'Shelve', subjective.shelve), |
132 | simpleEdit(isOwnEditingSubj, 'Genre', subjective.genre), |
133 | textEdit(isOwnEditingSubj, 'Review', subjective.review), |
134 | h('section.comments', map(subjective.comments, com => { |
135 | return h('div', |
136 | [api.about.html.image(com.author), |
137 | // FIXME: time, in general like cards |
138 | h('span.text', computed(com.content.text, api.message.html.markdown))]) |
139 | })), |
140 | h('section.actions', |
141 | when(isMe, [ |
142 | h('button.subjective', { 'ev-click': editRatingClick }, |
143 | when(isEditingSubjective, 'Cancel', 'Edit my rating')), |
144 | when(isEditingSubjective, |
145 | h('button', { 'ev-click': () => saveSubjective(obs, isEditingSubjective) }, |
146 | 'Update rating')) |
147 | ])) |
148 | ] |
149 | } |
150 | |
151 | function bookLayout(msg, opts) { |
152 | const { layout, obs, isEditing, isCard } = opts |
153 | |
154 | if (layout !== undefined && layout !== 'detail') return |
155 | |
156 | const { title, authors, description, |
157 | series, seriesNo, images } = api.book.html |
158 | |
159 | let originalBook = {} |
160 | let reviews = [] |
161 | |
162 | function editClick() { |
163 | if (isEditing()) { // cancel |
164 | Object.keys(originalBook).forEach((v) => { |
165 | obs[v].set(originalBook[v]) |
166 | }) |
167 | } else |
168 | originalBook = JSON.parse(JSON.stringify(obs())) |
169 | |
170 | isEditing.set(!isEditing()) |
171 | } |
172 | |
173 | return [h('Message -book-detail', [ |
174 | title({ title: obs.title, msg, isEditing, onUpdate: obs.title.set }), |
175 | series({ series: obs.series, seriesNo: obs.seriesNo, msg, isEditing, |
176 | onUpdate: obs.series.set, onUpdateNo: obs.seriesNo.set }), |
177 | authors({authors: obs.authors, isEditing, onUpdate: obs.authors.set}), |
178 | h('section.content', [ |
179 | images({images: obs.images, isEditing, onUpdate: obs.images.add }), |
180 | h('section.description', |
181 | description({description: obs.description, isEditing, onUpdate: obs.description.set})), |
182 | ]), |
183 | h('section.actions', [ |
184 | h('button.edit', { 'ev-click': editClick }, |
185 | when(isEditing, 'Cancel', 'Edit book')), |
186 | when(isEditing, h('button', {'ev-click': () => saveBook(obs)}, 'Update book')) |
187 | ]), |
188 | h('section.subjective', [ |
189 | computed(obs.subjective, subjectives => { |
190 | let i = 0; |
191 | Object.keys(subjectives).forEach(user => { |
192 | if (i++ < reviews.length) return |
193 | reviews.push(handleSubjective(user, i, obs)) |
194 | }) |
195 | |
196 | return reviews |
197 | }) |
198 | ]) |
199 | ])] |
200 | |
201 | function saveBook(obs) { |
202 | obs.amend() |
203 | |
204 | isEditing.set(false) |
205 | } |
206 | } |
207 | } |
208 |
Built with git-ssb-web