git ssb

16+

cel / patchfoo



Tree: 49d72723ed74ac04099a46e2aae2ef68432ba210

Files: 49d72723ed74ac04099a46e2aae2ef68432ba210 / lib / render-msg.js

24183 bytesRaw
1var h = require('hyperscript')
2var htime = require('human-time')
3var multicb = require('multicb')
4var u = require('./util')
5var mdInline = require('./markdown-inline')
6
7module.exports = RenderMsg
8
9function 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
23RenderMsg.prototype.toUrl = function (href) {
24 return this.render.toUrl(href)
25}
26
27RenderMsg.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
35function token() {
36 return '__' + Math.random().toString(36).substr(2) + '__'
37}
38
39RenderMsg.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
93RenderMsg.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
123RenderMsg.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
146RenderMsg.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
159RenderMsg.prototype.sync = function (cb) {
160 cb(null, h('tr.msg-row', h('td', {colspan: 3},
161 h('hr')
162 )))
163}
164
165RenderMsg.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
174RenderMsg.prototype.recpsIds = function () {
175 return this.value.private
176 ? u.toArray(this.c.recps).map(u.linkDest)
177 : []
178}
179
180RenderMsg.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
192RenderMsg.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
231RenderMsg.prototype.encrypted = function (cb) {
232 this.wrapMini(this.render.lockIcon(), cb)
233}
234
235RenderMsg.prototype.markdown = function (cb) {
236 return this.render.markdown(this.c.text, this.c.mentions)
237}
238
239RenderMsg.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
261RenderMsg.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
274RenderMsg.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
283RenderMsg.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
300function truncate(str, len) {
301 str = String(str)
302 return str.length > len ? str.substr(0, len) + '...' : str
303}
304
305function title(str) {
306 return truncate(mdInline(str), 72)
307}
308
309RenderMsg.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
334RenderMsg.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
340RenderMsg.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
350RenderMsg.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
363function dateTime(d) {
364 var date = new Date(d.epoch)
365 return date.toUTCString()
366 // d.bias
367 // d.epoch
368}
369
370RenderMsg.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
412RenderMsg.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
435RenderMsg.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
446RenderMsg.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
454RenderMsg.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
470RenderMsg.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
508RenderMsg.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
516RenderMsg.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
532RenderMsg.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
543RenderMsg.prototype.issueEdit = function (cb) {
544 this.wrap('', cb)
545}
546
547RenderMsg.prototype.object = function (cb) {
548 this.wrap(h('pre', this.linkify(JSON.stringify(this.c, 0, 2))), cb)
549}
550
551RenderMsg.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
560RenderMsg.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
604RenderMsg.prototype.missing = function (cb) {
605 this.wrapMini(h('code', 'MISSING'), cb)
606}
607
608RenderMsg.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
640RenderMsg.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
663RenderMsg.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
674function formatDuration(s) {
675 return Math.floor(s / 60) + ':' + ('0' + s % 60).substr(-2)
676}
677
678RenderMsg.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
701RenderMsg.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
728RenderMsg.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
753RenderMsg.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
767RenderMsg.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
779RenderMsg.prototype.mutualAccount = function (cb) {
780 return this.object(cb)
781}
782
783RenderMsg.prototype.gathering = function (cb) {
784 this.wrapMini('gathering', cb)
785}
786
787function unwrapP(html) {
788 return String(html).replace(/^<p>(.*)<\/p>\s*$/, function ($0, $1) {
789 return $1
790 })
791}
792
793RenderMsg.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