Files: 49d72723ed74ac04099a46e2aae2ef68432ba210 / lib / render-msg.js
24183 bytesRaw
1 | var h = require('hyperscript') |
2 | var htime = require('human-time') |
3 | var multicb = require('multicb') |
4 | var u = require('./util') |
5 | var mdInline = require('./markdown-inline') |
6 | |
7 | module.exports = RenderMsg |
8 | |
9 | function RenderMsg(render, app, msg, opts) { |
10 | this.render = render |
11 | this.app = app |
12 | this.msg = msg |
13 | this.value = msg && msg.value || {} |
14 | var content = this.value.content |
15 | this.c = content || {} |
16 | this.isMissing = !content |
17 | |
18 | if (typeof opts === 'boolean') opts = {raw: opts} |
19 | this.opts = opts || {} |
20 | this.shouldWrap = this.opts.wrap !== false |
21 | } |
22 | |
23 | RenderMsg.prototype.toUrl = function (href) { |
24 | return this.render.toUrl(href) |
25 | } |
26 | |
27 | RenderMsg.prototype.linkify = function (text) { |
28 | var arr = text.split(u.ssbRefRegex) |
29 | for (var i = 1; i < arr.length; i += 2) { |
30 | arr[i] = h('a', {href: this.toUrl(arr[i])}, arr[i]) |
31 | } |
32 | return arr |
33 | } |
34 | |
35 | function token() { |
36 | return '__' + Math.random().toString(36).substr(2) + '__' |
37 | } |
38 | |
39 | RenderMsg.prototype.raw = function (cb) { |
40 | // linkify various things in the JSON. TODO: abstract this better |
41 | |
42 | // clone the message for linkifying |
43 | var m = {}, k |
44 | for (k in this.msg) m[k] = this.msg[k] |
45 | m.value = {} |
46 | for (k in this.msg.value) m.value[k] = this.msg.value[k] |
47 | var tokens = {} |
48 | |
49 | // link to feed starting from this message |
50 | if (m.value.sequence) { |
51 | var tok = token() |
52 | tokens[tok] = h('a', {href: |
53 | this.toUrl(m.value.author + '?gt=' + (m.value.sequence-1))}, |
54 | m.value.sequence) |
55 | m.value.sequence = tok |
56 | } |
57 | |
58 | if (typeof m.value.content === 'object' && m.value.content != null) { |
59 | var c = m.value.content = {} |
60 | for (k in this.c) c[k] = this.c[k] |
61 | |
62 | // link to messages of same type |
63 | tok = token() |
64 | tokens[tok] = h('a', {href: this.toUrl('/type/' + c.type)}, c.type) |
65 | c.type = tok |
66 | |
67 | // link to channel |
68 | if (c.channel) { |
69 | tok = token() |
70 | tokens[tok] = h('a', {href: this.toUrl('#' + c.channel)}, c.channel) |
71 | c.channel = tok |
72 | } |
73 | } |
74 | |
75 | // link refs |
76 | var els = this.linkify(JSON.stringify(m, 0, 2)) |
77 | |
78 | // stitch it all together |
79 | for (var i = 0; i < els.length; i++) { |
80 | if (typeof els[i] === 'string') { |
81 | for (var tok in tokens) { |
82 | if (els[i].indexOf(tok) !== -1) { |
83 | var parts = els[i].split(tok) |
84 | els.splice(i, 1, parts[0], tokens[tok], parts[1]) |
85 | continue |
86 | } |
87 | } |
88 | } |
89 | } |
90 | this.wrap(h('pre', els), cb) |
91 | } |
92 | |
93 | RenderMsg.prototype.wrap = function (content, cb) { |
94 | if (!this.shouldWrap) return cb(null, content) |
95 | var date = new Date(this.msg.value.timestamp) |
96 | var self = this |
97 | var channel = this.c.channel ? '#' + this.c.channel : '' |
98 | var done = multicb({pluck: 1, spread: true}) |
99 | done()(null, [h('tr.msg-row', |
100 | h('td.msg-left', {rowspan: 2}, |
101 | h('div', this.render.avatarImage(this.msg.value.author, done())), |
102 | h('div', this.render.idLink(this.msg.value.author, done())), |
103 | this.recpsLine(done()) |
104 | ), |
105 | h('td.msg-main', |
106 | h('div.msg-header', |
107 | h('a.ssb-timestamp', { |
108 | title: date.toLocaleString(), |
109 | href: this.msg.key ? this.toUrl(this.msg.key) : undefined |
110 | }, htime(date)), ' ', |
111 | h('code', h('a.ssb-id', |
112 | {href: this.toUrl(this.msg.key)}, this.msg.key)), |
113 | channel ? [' ', h('a', {href: this.toUrl(channel)}, channel)] : '')), |
114 | h('td.msg-right', this.actions()) |
115 | ), h('tr', |
116 | h('td.msg-content', {colspan: 2}, |
117 | this.issues(done()), |
118 | content) |
119 | )]) |
120 | done(cb) |
121 | } |
122 | |
123 | RenderMsg.prototype.wrapMini = function (content, cb) { |
124 | if (!this.shouldWrap) return cb(null, content) |
125 | var date = new Date(this.value.timestamp) |
126 | var self = this |
127 | var channel = this.c.channel ? '#' + this.c.channel : '' |
128 | var done = multicb({pluck: 1, spread: true}) |
129 | done()(null, h('tr.msg-row', |
130 | h('td.msg-left', |
131 | this.render.idLink(this.value.author, done()), ' ', |
132 | this.recpsLine(done()), |
133 | channel ? [h('a', {href: this.toUrl(channel)}, channel), ' '] : ''), |
134 | h('td.msg-main', |
135 | h('a.ssb-timestamp', { |
136 | title: date.toLocaleString(), |
137 | href: this.msg.key ? this.toUrl(this.msg.key) : undefined |
138 | }, htime(date)), ' ', |
139 | this.issues(done()), |
140 | content), |
141 | h('td.msg-right', this.actions()) |
142 | )) |
143 | done(cb) |
144 | } |
145 | |
146 | RenderMsg.prototype.actions = function () { |
147 | return this.msg.key ? |
148 | h('form', {method: 'post', action: ''}, |
149 | this.msg.rel ? [this.msg.rel, ' '] : '', |
150 | this.opts.withGt && this.msg.timestamp ? [ |
151 | h('a', {href: '?gt=' + this.msg.timestamp}, '↓'), ' '] : '', |
152 | h('a', {href: this.toUrl(this.msg.key) + '?raw'}, 'raw'), ' ', |
153 | this.voteFormInner('dig') |
154 | ) : [ |
155 | this.msg.rel ? [this.msg.rel, ' '] : '' |
156 | ] |
157 | } |
158 | |
159 | RenderMsg.prototype.sync = function (cb) { |
160 | cb(null, h('tr.msg-row', h('td', {colspan: 3}, |
161 | h('hr') |
162 | ))) |
163 | } |
164 | |
165 | RenderMsg.prototype.recpsLine = function (cb) { |
166 | if (!this.value.private) return cb(), '' |
167 | var author = this.value.author |
168 | var recpsNotSelf = u.toArray(this.c.recps).filter(function (link) { |
169 | return u.linkDest(link) !== author |
170 | }) |
171 | return this.render.privateLine(recpsNotSelf, cb) |
172 | } |
173 | |
174 | RenderMsg.prototype.recpsIds = function () { |
175 | return this.value.private |
176 | ? u.toArray(this.c.recps).map(u.linkDest) |
177 | : [] |
178 | } |
179 | |
180 | RenderMsg.prototype.voteFormInner = function (expression) { |
181 | var chan = this.msg.value.content.channel |
182 | return [ |
183 | h('input', {type: 'hidden', name: 'action', value: 'vote'}), |
184 | h('input', {type: 'hidden', name: 'recps', |
185 | value: this.recpsIds().join(',')}), |
186 | chan ? h('input', {type: 'hidden', name: 'channel', value: chan}) : '', |
187 | h('input', {type: 'hidden', name: 'link', value: this.msg.key}), |
188 | h('input', {type: 'hidden', name: 'value', value: 1}), |
189 | h('input', {type: 'submit', name: 'expression', value: expression})] |
190 | } |
191 | |
192 | RenderMsg.prototype.message = function (cb) { |
193 | if (this.opts.raw) return this.raw(cb) |
194 | if (this.msg.sync) return this.sync(cb) |
195 | if (typeof this.c === 'string') return this.encrypted(cb) |
196 | if (this.isMissing) return this.missing(cb) |
197 | switch (this.c.type) { |
198 | case 'post': return this.post(cb) |
199 | case 'ferment/like': |
200 | case 'robeson/like': |
201 | case 'vote': return this.vote(cb) |
202 | case 'about': return this.about(cb) |
203 | case 'contact': return this.contact(cb) |
204 | case 'pub': return this.pub(cb) |
205 | case 'channel': return this.channel(cb) |
206 | case 'git-repo': return this.gitRepo(cb) |
207 | case 'git-update': return this.gitUpdate(cb) |
208 | case 'pull-request': return this.gitPullRequest(cb) |
209 | case 'issue': return this.issue(cb) |
210 | case 'issue-edit': return this.issueEdit(cb) |
211 | case 'music-release-cc': return this.musicRelease(cb) |
212 | case 'ssb-dns': return this.dns(cb) |
213 | case 'gathering': return this.gathering(cb) |
214 | case 'micro': return this.micro(cb) |
215 | case 'ferment/audio': |
216 | case 'robeson/audio': |
217 | return this.audio(cb) |
218 | case 'ferment/repost': |
219 | case 'robeson/repost': |
220 | return this.repost(cb) |
221 | case 'ferment/update': |
222 | case 'robeson/update': |
223 | return this.update(cb) |
224 | case 'wifi-network': return this.wifiNetwork(cb) |
225 | case 'mutual/credit': return this.mutualCredit(cb) |
226 | case 'mutual/account': return this.mutualAccount(cb) |
227 | default: return this.object(cb) |
228 | } |
229 | } |
230 | |
231 | RenderMsg.prototype.encrypted = function (cb) { |
232 | this.wrapMini(this.render.lockIcon(), cb) |
233 | } |
234 | |
235 | RenderMsg.prototype.markdown = function (cb) { |
236 | return this.render.markdown(this.c.text, this.c.mentions) |
237 | } |
238 | |
239 | RenderMsg.prototype.post = function (cb) { |
240 | var self = this |
241 | var done = multicb({pluck: 1, spread: true}) |
242 | var branchDone = multicb({pluck: 1}) |
243 | u.toArray(self.c.branch).forEach(function (branch) { |
244 | self.link(branch, branchDone()) |
245 | }) |
246 | if (self.c.root === self.c.branch) done()() |
247 | else self.link(self.c.root, done()) |
248 | branchDone(done()) |
249 | done(function (err, rootLink, branchLinks) { |
250 | if (err) return self.wrap(u.renderError(err), cb) |
251 | self.wrap(h('div.ssb-post', |
252 | rootLink ? h('div', h('small', '>> ', rootLink)) : '', |
253 | branchLinks.map(function (a, i) { |
254 | return h('div', h('small', '> ', a)) |
255 | }), |
256 | h('div.ssb-post-text', {innerHTML: self.markdown()}) |
257 | ), cb) |
258 | }) |
259 | } |
260 | |
261 | RenderMsg.prototype.vote = function (cb) { |
262 | var self = this |
263 | var v = self.c.vote || self.c.like || {} |
264 | self.link(v, function (err, a) { |
265 | if (err) return cb(err) |
266 | self.wrapMini([ |
267 | v.value > 0 ? 'dug' : v.value < 0 ? 'downvoted' : 'undug', |
268 | ' ', a, |
269 | v.reason ? [' as ', h('q', v.reason)] : '' |
270 | ], cb) |
271 | }) |
272 | } |
273 | |
274 | RenderMsg.prototype.getName = function (id, cb) { |
275 | switch (id && id[0]) { |
276 | case '%': return this.getMsgName(id, cb) |
277 | case '@': // fallthrough |
278 | case '&': return this.getAboutName(id, cb) |
279 | default: return cb(null, String(id)) |
280 | } |
281 | } |
282 | |
283 | RenderMsg.prototype.getMsgName = function (id, cb) { |
284 | var self = this |
285 | self.app.getMsg(id, function (err, msg) { |
286 | if (err && err.name == 'NotFoundError') |
287 | cb(null, id.substring(0, 10)+'...(missing)') |
288 | else if (err) cb(err) |
289 | // preserve security: only decrypt the linked message if we decrypted |
290 | // this message |
291 | else if (self.msg.value.private) self.app.unboxMsg(msg, gotMsg) |
292 | else gotMsg(null, msg) |
293 | }) |
294 | function gotMsg(err, msg) { |
295 | if (err) return cb(err) |
296 | new RenderMsg(self.render, self.app, msg, {wrap: false}).title(cb) |
297 | } |
298 | } |
299 | |
300 | function truncate(str, len) { |
301 | str = String(str) |
302 | return str.length > len ? str.substr(0, len) + '...' : str |
303 | } |
304 | |
305 | function title(str) { |
306 | return truncate(mdInline(str), 72) |
307 | } |
308 | |
309 | RenderMsg.prototype.title = function (cb) { |
310 | var self = this |
311 | if (!self.c || typeof self.c !== 'object') { |
312 | cb(null, self.msg.key) |
313 | } else if (typeof self.c.text === 'string') { |
314 | if (self.c.type === 'post') |
315 | cb(null, title(self.c.text)) |
316 | else |
317 | cb(null, '%' + self.c.type + ': ' + (self.c.title || title(self.c.text))) |
318 | } else { |
319 | if (self.c.type === 'ssb-dns') |
320 | cb(null, self.c.record && JSON.stringify(self.c.record.data) || self.msg.key) |
321 | else |
322 | self.app.getAbout(self.msg.key, function (err, about) { |
323 | if (err) return cb(err) |
324 | var name = about.name || about.title || about.description |
325 | if (name) return cb(null, name) |
326 | self.message(function (err, el) { |
327 | if (err) return cb(err) |
328 | cb(null, '%' + title(h('div', el).textContent)) |
329 | }) |
330 | }) |
331 | } |
332 | } |
333 | |
334 | RenderMsg.prototype.getAboutName = function (id, cb) { |
335 | this.app.getAbout(id, function (err, about) { |
336 | cb(err, about && about.name || (String(id).substr(0, 8) + '…')) |
337 | }) |
338 | } |
339 | |
340 | RenderMsg.prototype.link = function (link, cb) { |
341 | var self = this |
342 | var ref = u.linkDest(link) |
343 | if (!ref) return cb(null, '') |
344 | self.getName(ref, function (err, name) { |
345 | if (err) return cb(err) |
346 | cb(null, h('a', {href: self.toUrl(ref)}, name)) |
347 | }) |
348 | } |
349 | |
350 | RenderMsg.prototype.link1 = function (link, cb) { |
351 | var self = this |
352 | var ref = u.linkDest(link) |
353 | if (!ref) return cb(), '' |
354 | var a = h('a', {href: self.toUrl(ref)}, ref) |
355 | self.getName(ref, function (err, name) { |
356 | if (err) name = ref |
357 | a.childNodes[0].textContent = name |
358 | cb() |
359 | }) |
360 | return a |
361 | } |
362 | |
363 | function dateTime(d) { |
364 | var date = new Date(d.epoch) |
365 | return date.toUTCString() |
366 | // d.bias |
367 | // d.epoch |
368 | } |
369 | |
370 | RenderMsg.prototype.about = function (cb) { |
371 | var img = u.linkDest(this.c.image) |
372 | var done = multicb({pluck: 1, spread: true}) |
373 | var elCb = done() |
374 | // if there is a description, it is likely to be multi-line |
375 | var hasDescription = this.c.description != null |
376 | var wrap = hasDescription ? this.wrap : this.wrapMini |
377 | var isSelf = this.c.about === this.msg.value.author |
378 | // if this about message gives the thing a name, show its id |
379 | var showComputedName = !isSelf && !this.c.name |
380 | |
381 | wrap.call(this, [ |
382 | isSelf |
383 | ? hasDescription ? 'self-describes' : 'self-identifies' |
384 | : [hasDescription ? 'describes' : 'identifies', ' ', |
385 | !this.c.about ? '?' |
386 | : showComputedName ? this.link1(this.c.about, done()) |
387 | : h('a', {href: this.toUrl(this.c.about)}, truncate(this.c.about, 10)) |
388 | ], |
389 | ' as ', |
390 | this.c.name ? [h('ins', this.c.name), ' '] : '', |
391 | this.c.description ? h('div', |
392 | {innerHTML: this.render.markdown(this.c.description)}) : '', |
393 | this.c.title ? h('h3', this.c.title) : '', |
394 | this.c.attendee ? h('div', |
395 | this.link1(this.c.attendee.link, done()), |
396 | this.c.attendee.remove ? ' is not attending' : ' is attending' |
397 | ) : '', |
398 | this.c.startDateTime ? h('div', |
399 | 'starting at ', dateTime(this.c.startDateTime)) : '', |
400 | this.c.endDateTime ? h('div', |
401 | 'ending at ', dateTime(this.c.endDateTime)) : '', |
402 | this.c.location ? h('div', 'at ', this.c.location) : '', |
403 | img ? h('a', {href: this.toUrl(img)}, |
404 | h('img.ssb-avatar-image', { |
405 | src: this.render.imageUrl(img), |
406 | alt: ' ', |
407 | })) : '' |
408 | ], elCb) |
409 | done(cb) |
410 | } |
411 | |
412 | RenderMsg.prototype.contact = function (cb) { |
413 | var self = this |
414 | self.link(self.c.contact, function (err, a) { |
415 | if (err) return cb(err) |
416 | if (!a) a = "?" |
417 | self.wrapMini([ |
418 | self.c.following && self.c.autofollow ? 'follows pub' : |
419 | self.c.following && self.c.pub ? 'autofollows' : |
420 | self.c.following ? 'follows' : |
421 | self.c.blocking ? 'blocks' : |
422 | self.c.flagged ? 'flagged' : |
423 | self.c.following === false ? 'unfollows' : |
424 | self.c.blocking === false ? 'unblocks' : '', |
425 | self.c.flagged === false ? 'unflagged' : |
426 | ' ', a, |
427 | self.c.note ? [ |
428 | ' from ', |
429 | h('code', self.c.note) |
430 | ] : '', |
431 | ], cb) |
432 | }) |
433 | } |
434 | |
435 | RenderMsg.prototype.pub = function (cb) { |
436 | var self = this |
437 | var addr = self.c.address || {} |
438 | self.link(addr.key, function (err, pubLink) { |
439 | if (err) return cb(err) |
440 | self.wrapMini([ |
441 | 'connects to ', pubLink, ' at ', |
442 | h('code', addr.host + ':' + addr.port)], cb) |
443 | }) |
444 | } |
445 | |
446 | RenderMsg.prototype.channel = function (cb) { |
447 | var chan = '#' + this.c.channel |
448 | this.wrapMini([ |
449 | this.c.subscribed ? 'subscribes to ' : |
450 | this.c.subscribed === false ? 'unsubscribes from ' : '', |
451 | h('a', {href: this.toUrl(chan)}, chan)], cb) |
452 | } |
453 | |
454 | RenderMsg.prototype.gitRepo = function (cb) { |
455 | var self = this |
456 | var id = self.msg.key |
457 | var name = self.c.name |
458 | var upstream = self.c.upstream |
459 | self.link(upstream, function (err, upstreamA) { |
460 | if (err) upstreamA = ('a', {href: self.toUrl(upstream)}, String(name)) |
461 | self.wrapMini([ |
462 | upstream ? ['forked ', upstreamA, ': '] : '', |
463 | 'git clone ', |
464 | h('code', h('small', 'ssb://' + id)), |
465 | name ? [' ', h('a', {href: self.toUrl(id)}, String(name))] : '' |
466 | ], cb) |
467 | }) |
468 | } |
469 | |
470 | RenderMsg.prototype.gitUpdate = function (cb) { |
471 | var self = this |
472 | // h('a', {href: self.toUrl(self.c.repo)}, 'ssb://' + self.c.repo), |
473 | var size = [].concat(self.c.packs, self.c.indexes) |
474 | .map(function (o) { return o && o.size }) |
475 | .reduce(function (total, s) { return total + s }) |
476 | self.link(self.c.repo, function (err, a) { |
477 | if (err) return cb(err) |
478 | self.wrap(h('div.ssb-git-update', |
479 | 'git push ', a, ' ', |
480 | !isNaN(size) ? [self.render.formatSize(size), ' '] : '', |
481 | self.c.refs ? h('ul', Object.keys(self.c.refs).map(function (ref) { |
482 | var id = self.c.refs[ref] |
483 | return h('li', |
484 | ref.replace(/^refs\/(heads|tags)\//, ''), ': ', |
485 | id ? h('code', id) : h('em', 'deleted')) |
486 | })) : '', |
487 | Array.isArray(self.c.commits) ? |
488 | h('ul', self.c.commits.map(function (commit) { |
489 | return h('li', |
490 | h('code', String(commit.sha1).substr(0, 8)), ' ', |
491 | self.linkify(String(commit.title)), |
492 | self.gitCommitBody(commit.body) |
493 | ) |
494 | })) : '', |
495 | Array.isArray(self.c.tags) ? |
496 | h('ul', self.c.tags.map(function (tag) { |
497 | return h('li', |
498 | h('code', String(tag.sha1).substr(0, 8)), ' ', |
499 | 'tagged ', String(tag.type), ' ', |
500 | h('code', String(tag.object).substr(0, 8)), ' ', |
501 | String(tag.tag) |
502 | ) |
503 | })) : '' |
504 | ), cb) |
505 | }) |
506 | } |
507 | |
508 | RenderMsg.prototype.gitCommitBody = function (body) { |
509 | if (!body) return '' |
510 | var isMarkdown = !/^# Conflicts:$/m.test(body) |
511 | return isMarkdown |
512 | ? h('div', {innerHTML: this.render.markdown('\n' + body)}) |
513 | : h('pre', this.linkify('\n' + body)) |
514 | } |
515 | |
516 | RenderMsg.prototype.gitPullRequest = function (cb) { |
517 | var self = this |
518 | var done = multicb({pluck: 1, spread: true}) |
519 | self.link(self.c.repo, done()) |
520 | self.link(self.c.head_repo, done()) |
521 | done(function (err, baseRepoLink, headRepoLink) { |
522 | if (err) return cb(err) |
523 | self.wrap(h('div.ssb-pull-request', |
524 | 'pull request ', |
525 | 'to ', baseRepoLink, ':', self.c.branch, ' ', |
526 | 'from ', headRepoLink, ':', self.c.head_branch, |
527 | self.c.title ? h('h4', self.c.title) : '', |
528 | h('div', {innerHTML: self.markdown()})), cb) |
529 | }) |
530 | } |
531 | |
532 | RenderMsg.prototype.issue = function (cb) { |
533 | var self = this |
534 | self.link(self.c.project, function (err, projectLink) { |
535 | if (err) return cb(err) |
536 | self.wrap(h('div.ssb-issue', |
537 | 'issue on ', projectLink, |
538 | self.c.title ? h('h4', self.c.title) : '', |
539 | h('div', {innerHTML: self.markdown()})), cb) |
540 | }) |
541 | } |
542 | |
543 | RenderMsg.prototype.issueEdit = function (cb) { |
544 | this.wrap('', cb) |
545 | } |
546 | |
547 | RenderMsg.prototype.object = function (cb) { |
548 | this.wrap(h('pre', this.linkify(JSON.stringify(this.c, 0, 2))), cb) |
549 | } |
550 | |
551 | RenderMsg.prototype.object = function (cb) { |
552 | var done = multicb({pluck: 1, spread: true}) |
553 | var elCb = done() |
554 | this.wrap([ |
555 | this.valueTable(this.c, done()), |
556 | ], elCb) |
557 | done(cb) |
558 | } |
559 | |
560 | RenderMsg.prototype.valueTable = function (val, cb) { |
561 | var self = this |
562 | switch (typeof val) { |
563 | case 'object': |
564 | if (val === null) return cb(), '' |
565 | var done = multicb({pluck: 1, spread: true}) |
566 | var el = Array.isArray(val) |
567 | ? h('ul', val.map(function (item) { |
568 | return h('li', self.valueTable(item, done())) |
569 | })) |
570 | : h('table.ssb-object', Object.keys(val).map(function (key) { |
571 | if (key === 'text') { |
572 | return h('tr', |
573 | h('td', h('strong', 'text')), |
574 | h('td', h('div', { |
575 | innerHTML: self.render.markdown(val.text, val.mentions) |
576 | })) |
577 | ) |
578 | } else if (key === 'type') { |
579 | var type = val.type |
580 | return h('tr', |
581 | h('td', h('strong', 'type')), |
582 | h('td', h('a', {href: self.toUrl('/type/' + type)}, type)) |
583 | ) |
584 | } |
585 | return h('tr', |
586 | h('td', h('strong', key)), |
587 | h('td', self.valueTable(val[key], done())) |
588 | ) |
589 | })) |
590 | done(cb) |
591 | return el |
592 | case 'string': |
593 | if (u.isRef(val)) return self.link1(val, cb) |
594 | return cb(), self.linkify(val) |
595 | case 'boolean': |
596 | return cb(), h('input', { |
597 | type: 'checkbox', disabled: 'disabled', checked: val |
598 | }) |
599 | default: |
600 | return cb(), String(val) |
601 | } |
602 | } |
603 | |
604 | RenderMsg.prototype.missing = function (cb) { |
605 | this.wrapMini(h('code', 'MISSING'), cb) |
606 | } |
607 | |
608 | RenderMsg.prototype.issues = function (cb) { |
609 | var self = this |
610 | var done = multicb({pluck: 1, spread: true}) |
611 | var issues = u.toArray(self.c.issues) |
612 | if (self.c.type === 'issue-edit' && self.c.issue) { |
613 | issues.push({ |
614 | link: self.c.issue, |
615 | title: self.c.title, |
616 | open: self.c.open, |
617 | }) |
618 | } |
619 | var els = issues.map(function (issue) { |
620 | var commit = issue.object || issue.label ? [ |
621 | issue.object ? h('code', issue.object) : '', ' ', |
622 | issue.label ? h('q', issue.label) : ''] : '' |
623 | if (issue.merged === true) |
624 | return h('div', |
625 | 'merged ', self.link1(issue, done())) |
626 | if (issue.open === false) |
627 | return h('div', |
628 | 'closed ', self.link1(issue, done())) |
629 | if (issue.open === true) |
630 | return h('div', |
631 | 'reopened ', self.link1(issue, done())) |
632 | if (typeof issue.title === 'string') |
633 | return h('div', |
634 | 'renamed ', self.link1(issue, done()), ' to ', h('ins', issue.title)) |
635 | }) |
636 | done(cb) |
637 | return els.length > 0 ? [els, h('br')] : '' |
638 | } |
639 | |
640 | RenderMsg.prototype.repost = function (cb) { |
641 | var self = this |
642 | var id = u.linkDest(self.c.repost) |
643 | self.app.getMsg(id, function (err, msg) { |
644 | if (err && err.name == 'NotFoundError') |
645 | gotMsg(null, id.substring(0, 10)+'...(missing)') |
646 | else if (err) gotMsg(err) |
647 | else if (self.msg.value.private) self.app.unboxMsg(msg, gotMsg) |
648 | else gotMsg(null, msg) |
649 | }) |
650 | function gotMsg(err, msg) { |
651 | if (err) return cb(err) |
652 | var renderMsg = new RenderMsg(self.render, self.app, msg, {wrap: false}) |
653 | renderMsg.message(function (err, msgEl) { |
654 | self.wrapMini(['reposted ', |
655 | h('code.ssb-id', |
656 | h('a', {href: self.render.toUrl(id)}, id)), |
657 | h('div', err ? u.renderError(err) : msgEl || '') |
658 | ], cb) |
659 | }) |
660 | } |
661 | } |
662 | |
663 | RenderMsg.prototype.update = function (cb) { |
664 | var id = String(this.c.update) |
665 | this.wrapMini([ |
666 | h('div', 'updated ', h('code.ssb-id', |
667 | h('a', {href: this.render.toUrl(id)}, id))), |
668 | this.c.title ? h('h4.msg-title', this.c.title) : '', |
669 | this.c.description ? h('div', |
670 | {innerHTML: this.render.markdown(this.c.description)}) : '' |
671 | ], cb) |
672 | } |
673 | |
674 | function formatDuration(s) { |
675 | return Math.floor(s / 60) + ':' + ('0' + s % 60).substr(-2) |
676 | } |
677 | |
678 | RenderMsg.prototype.audio = function (cb) { |
679 | // fileName, fallbackFileName, overview |
680 | this.wrap(h('table', h('tr', |
681 | h('td', |
682 | this.c.artworkSrc |
683 | ? h('a', {href: this.render.toUrl(this.c.artworkSrc)}, h('img', { |
684 | src: this.render.imageUrl(this.c.artworkSrc), |
685 | alt: ' ', |
686 | width: 72, |
687 | height: 72, |
688 | })) |
689 | : ''), |
690 | h('td', |
691 | h('a', {href: this.render.toUrl(this.c.audioSrc)}, this.c.title), |
692 | isFinite(this.c.duration) |
693 | ? ' (' + formatDuration(this.c.duration) + ')' |
694 | : '', |
695 | this.c.description |
696 | ? h('p', {innerHTML: this.render.markdown(this.c.description)}) |
697 | : '' |
698 | ))), cb) |
699 | } |
700 | |
701 | RenderMsg.prototype.musicRelease = function (cb) { |
702 | var self = this |
703 | this.wrap([ |
704 | h('table', h('tr', |
705 | h('td', |
706 | this.c.cover |
707 | ? h('a', {href: this.render.imageUrl(this.c.cover)}, h('img', { |
708 | src: this.render.imageUrl(this.c.cover), |
709 | alt: ' ', |
710 | width: 72, |
711 | height: 72, |
712 | })) |
713 | : ''), |
714 | h('td', |
715 | h('h4.msg-title', this.c.title), |
716 | this.c.text |
717 | ? h('div', {innerHTML: this.render.markdown(this.c.text)}) |
718 | : '' |
719 | ) |
720 | )), |
721 | h('ul', u.toArray(this.c.tracks).filter(Boolean).map(function (track) { |
722 | return h('li', |
723 | h('a', {href: self.render.toUrl(track.link)}, track.fname)) |
724 | })) |
725 | ], cb) |
726 | } |
727 | |
728 | RenderMsg.prototype.dns = function (cb) { |
729 | var self = this |
730 | var record = self.c.record || {} |
731 | var done = multicb({pluck: 1, spread: true}) |
732 | var elCb = done() |
733 | self.wrap([ |
734 | h('div', |
735 | h('p', |
736 | h('ins', {title: 'name'}, record.name), ' ', |
737 | h('span', {title: 'ttl'}, record.ttl), ' ', |
738 | h('span', {title: 'class'}, record.class), ' ', |
739 | h('span', {title: 'type'}, record.type) |
740 | ), |
741 | h('pre', {title: 'data'}, |
742 | JSON.stringify(record.data || record.value, null, 2)), |
743 | !self.c.branch ? null : h('div', |
744 | 'replaces: ', u.toArray(self.c.branch).map(function (id, i) { |
745 | return [self.link1(id, done()), i === 0 ? ', ' : ''] |
746 | }) |
747 | ) |
748 | ) |
749 | ], elCb) |
750 | done(cb) |
751 | } |
752 | |
753 | RenderMsg.prototype.wifiNetwork = function (cb) { |
754 | var net = this.c.network || {} |
755 | this.wrap([ |
756 | h('div', 'wifi network'), |
757 | h('table', |
758 | Object.keys(net).map(function (key) { |
759 | return h('tr', |
760 | h('td', key), |
761 | h('td', h('pre', JSON.stringify(net[key])))) |
762 | }) |
763 | ), |
764 | ], cb) |
765 | } |
766 | |
767 | RenderMsg.prototype.mutualCredit = function (cb) { |
768 | var self = this |
769 | self.link(self.c.account, function (err, a) { |
770 | if (err) return cb(err) |
771 | self.wrapMini([ |
772 | 'credits ', a || '?', ' ', |
773 | self.c.amount, ' ', self.c.currency, |
774 | self.c.memo ? [' for ', h('q', self.c.memo)] : '' |
775 | ], cb) |
776 | }) |
777 | } |
778 | |
779 | RenderMsg.prototype.mutualAccount = function (cb) { |
780 | return this.object(cb) |
781 | } |
782 | |
783 | RenderMsg.prototype.gathering = function (cb) { |
784 | this.wrapMini('gathering', cb) |
785 | } |
786 | |
787 | function unwrapP(html) { |
788 | return String(html).replace(/^<p>(.*)<\/p>\s*$/, function ($0, $1) { |
789 | return $1 |
790 | }) |
791 | } |
792 | |
793 | RenderMsg.prototype.micro = function (cb) { |
794 | var el = h('span', {innerHTML: unwrapP(this.markdown())}) |
795 | this.wrapMini(el, cb) |
796 | } |
797 |
Built with git-ssb-web