Files: 9c6467e267ec5e13ac657da60215077fc7b42510 / book / html / layout / detail.js
6469 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 | ratingTypeInput.addEventListener('suggestselect', ev => { |
70 | value.set(ev.detail.value) |
71 | }) |
72 | |
73 | return when(isEditing, suggestWrapper, |
74 | h('span.text', { innerHTML: computed(value, simpleMarkdown) })) |
75 | } |
76 | |
77 | function simpleEdit(isEditing, name, value) { |
78 | return h('div', { classList: when(computed([value, isEditing], (v, e) => { return v || e }), |
79 | '-expanded', '-contracted') }, |
80 | [h('span', name + ':'), |
81 | when(isEditing, |
82 | h('input', {'ev-input': e => value.set(e.target.value), value: value }), |
83 | h('span', value))]) |
84 | |
85 | } |
86 | |
87 | function textEdit(isEditing, name, value) { |
88 | const markdown = api.message.html.markdown |
89 | const input = h('textarea', {'ev-input': e => value.set(e.target.value), value: value }) |
90 | |
91 | return h('div', { classList: when(computed([value, isEditing], (v, e) => { return v || e }), |
92 | '-expanded', '-contracted') }, |
93 | [h('div', name + ':'), |
94 | when(isEditing, input, computed(value, markdown))]) |
95 | } |
96 | |
97 | function bookLayout (msg, opts) { |
98 | if (!(opts.layout === undefined || opts.layout === 'detail')) return |
99 | |
100 | const { obs, isEditing, isCard } = opts |
101 | |
102 | const { title, authors, description, images } = api.book.html |
103 | |
104 | let isEditingSubjective = Value(false) |
105 | let originalSubjective = {} |
106 | let originalBook = {} |
107 | let reviews = [] |
108 | |
109 | return [h('Message -book-detail', [ |
110 | title({ title: obs.title, msg, isEditing, onUpdate: obs.title.set }), |
111 | authors({authors: obs.authors, isEditing, onUpdate: obs.authors.set}), |
112 | h('section.content', [ |
113 | images({images: obs.images, isEditing, onUpdate: obs.images.add }), |
114 | h('section.description', |
115 | description({description: obs.description, isEditing, onUpdate: obs.description.set})), |
116 | ]), |
117 | h('section.actions', [ |
118 | h('button.edit', { 'ev-click': () => { |
119 | if (isEditing()) { // cancel |
120 | Object.keys(originalBook).forEach((v) => { |
121 | obs[v].set(originalBook[v]) |
122 | }) |
123 | } else |
124 | originalBook = JSON.parse(JSON.stringify(obs())) |
125 | |
126 | isEditing.set(!isEditing()) |
127 | } }, |
128 | when(isEditing, 'Cancel', 'Edit book')), |
129 | when(isEditing, h('button', {'ev-click': () => saveBook(obs)}, 'Update book')) |
130 | ]), |
131 | h('section.subjective', [ |
132 | computed(obs.subjective, subjectives => { |
133 | let i = 0; |
134 | Object.keys(subjectives).forEach(user => { |
135 | if (i++ < reviews.length) return |
136 | let subjective = obs.subjective.get(user) |
137 | let isMe = Value(api.keys.sync.id() == user) |
138 | let isOwnEditingSubj = computed([isEditingSubjective, isMe], |
139 | (e, me) => { return e && me }) |
140 | reviews.push([ |
141 | h('section', [api.about.html.image(user), |
142 | when(computed([subjective.rating, isEditingSubjective], |
143 | (v, e) => { return v || e }), |
144 | h('span.text', [api.about.obs.name(user), ' rated '])), |
145 | ratingEdit(isOwnEditingSubj, subjective.rating), |
146 | ratingTypeEdit(isOwnEditingSubj, subjective.ratingType)]), |
147 | simpleEdit(isOwnEditingSubj, 'Shelve', subjective.shelve), |
148 | simpleEdit(isOwnEditingSubj, 'Genre', subjective.genre), |
149 | textEdit(isOwnEditingSubj, 'Review', subjective.review) |
150 | ]) |
151 | }) |
152 | |
153 | return reviews |
154 | }) |
155 | ]), |
156 | h('section.actions', [ |
157 | h('button.subjective', { |
158 | 'ev-click': () => { |
159 | if (isEditingSubjective()) { // cancel |
160 | let subj = obs.subjective.get(api.keys.sync.id()) |
161 | Object.keys(originalSubjective).forEach((v) => { |
162 | subj[v].set(originalSubjective[v]) |
163 | }) |
164 | } else |
165 | originalSubjective = JSON.parse(JSON.stringify(obs.subjective.get(api.keys.sync.id())())) |
166 | |
167 | isEditingSubjective.set(!isEditingSubjective()) |
168 | } |
169 | }, |
170 | when(isEditingSubjective, 'Cancel', 'Edit rating')), |
171 | when(isEditingSubjective, h('button', { 'ev-click': () => saveSubjective(obs) }, 'Update rating')) |
172 | ]), |
173 | ])] |
174 | |
175 | function saveBook(obs) { |
176 | obs.amend() |
177 | |
178 | isEditing.set(false) |
179 | } |
180 | |
181 | function saveSubjective(obs) { |
182 | obs.updateSubjective() |
183 | |
184 | isEditingSubjective.set(false) |
185 | } |
186 | } |
187 | } |
188 |
Built with git-ssb-web