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