git ssb

16+

cel / patchfoo



Tree: 7c57c1321fc73354ed03fe8fae1263bf5acfe329

Files: 7c57c1321fc73354ed03fe8fae1263bf5acfe329 / lib / render-msg.js

43690 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 return this.render.linkify(text)
29}
30
31function token() {
32 return '__' + Math.random().toString(36).substr(2) + '__'
33}
34
35RenderMsg.prototype.raw = function (cb) {
36 // linkify various things in the JSON. TODO: abstract this better
37
38 // clone the message for linkifying
39 var m = {}, k
40 for (k in this.msg) m[k] = this.msg[k]
41 m.value = {}
42 for (k in this.msg.value) m.value[k] = this.msg.value[k]
43 var tokens = {}
44
45 // link to feed starting from this message
46 if (m.value.sequence) {
47 var tok = token()
48 tokens[tok] = h('a', {href:
49 this.toUrl(m.value.author + '?gt=' + (m.value.sequence-1))},
50 m.value.sequence)
51 m.value.sequence = tok
52 }
53
54 if (typeof m.value.content === 'object' && m.value.content != null) {
55 var c = m.value.content = {}
56 for (k in this.c) c[k] = this.c[k]
57
58 // link to messages of same type
59 tok = token()
60 tokens[tok] = h('a', {href: this.toUrl('/type/' + c.type)}, c.type)
61 c.type = tok
62
63 // link to channel
64 if (c.channel) {
65 tok = token()
66 tokens[tok] = h('a', {href: this.toUrl('#' + c.channel)}, c.channel)
67 c.channel = tok
68 }
69
70 // link to hashtags
71 // TODO: recurse
72 for (var k in c) {
73 if (!c[k] || c[k][0] !== '#') continue
74 tok = token()
75 tokens[tok] = h('a', {href: this.toUrl(c[k])}, c[k])
76 c[k] = tok
77 }
78 }
79
80 // link refs
81 var els = this.linkify(JSON.stringify(m, 0, 2))
82
83 // stitch it all together
84 for (var i = 0; i < els.length; i++) {
85 if (typeof els[i] === 'string') {
86 for (var tok in tokens) {
87 if (els[i].indexOf(tok) !== -1) {
88 var parts = els[i].split(tok)
89 els.splice(i, 1, parts[0], tokens[tok], parts[1])
90 continue
91 }
92 }
93 }
94 }
95 this.wrap(h('pre', els), cb)
96}
97
98RenderMsg.prototype.wrap = function (content, cb) {
99 if (!this.shouldWrap) return cb(null, content)
100 var date = new Date(this.msg.value.timestamp)
101 var self = this
102 var channel = this.c.channel ? '#' + this.c.channel : ''
103 var done = multicb({pluck: 1, spread: true})
104 done()(null, [h('tr.msg-row',
105 h('td.msg-left', {rowspan: 2},
106 h('div', this.render.avatarImage(this.msg.value.author, done())),
107 h('div', this.render.idLink(this.msg.value.author, done())),
108 this.recpsLine(done())
109 ),
110 h('td.msg-main',
111 h('div.msg-header',
112 h('a.ssb-timestamp', {
113 title: date.toLocaleString(),
114 href: this.msg.key ? this.toUrl(this.msg.key) : undefined
115 }, htime(date)), ' ',
116 h('code', h('a.ssb-id',
117 {href: this.toUrl(this.msg.key)}, this.msg.key)),
118 channel ? [' ', h('a', {href: this.toUrl(channel)}, channel)] : '')),
119 h('td.msg-right', this.actions())
120 ), h('tr',
121 h('td.msg-content', {colspan: 2},
122 this.issues(done()),
123 content)
124 )])
125 done(cb)
126}
127
128RenderMsg.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.actions())
147 ))
148 done(cb)
149}
150
151RenderMsg.prototype.actions = function () {
152 return this.msg.key ?
153 h('form', {method: 'post', action: ''},
154 this.msg.rel ? [this.msg.rel, ' '] : '',
155 this.opts.withGt && this.msg.timestamp ? [
156 h('a', {href: '?gt=' + this.msg.timestamp}, '↓'), ' '] : '',
157 this.c.type === 'gathering' ? [
158 h('a', {href: this.render.toUrl('/about/' + encodeURIComponent(this.msg.key))}, 'about'), ' '] : '',
159 /^(ssb_)?chess_/.test(this.c.type) ? [
160 h('a', {href: this.toUrl(this.msg.key) + '?full',
161 title: 'view full game board'}, 'full'), ' '] : '',
162 typeof this.c.text === 'string' ? [
163 h('a', {href: this.toUrl(this.msg.key) + '?raw=md',
164 title: 'view markdown source'}, 'md'), ' '] : '',
165 h('a', {href: this.toUrl(this.msg.key) + '?raw',
166 title: 'view raw message'}, 'raw'), ' ',
167 this.buttonsCommon(),
168 this.c.type === 'gathering' ? [this.attendButton(), ' '] : '',
169 this.voteButton('dig')
170 ) : [
171 this.msg.rel ? [this.msg.rel, ' '] : ''
172 ]
173}
174
175RenderMsg.prototype.sync = function (cb) {
176 cb(null, h('tr.msg-row', h('td', {colspan: 3},
177 h('hr')
178 )))
179}
180
181RenderMsg.prototype.recpsLine = function (cb) {
182 if (!this.value.private) return cb(), ''
183 var author = this.value.author
184 var recpsNotSelf = u.toArray(this.c.recps).filter(function (link) {
185 return u.linkDest(link) !== author
186 })
187 return this.render.privateLine(recpsNotSelf, cb)
188}
189
190RenderMsg.prototype.recpsIds = function () {
191 return this.value.private
192 ? u.toArray(this.c.recps).map(u.linkDest)
193 : []
194}
195
196RenderMsg.prototype.buttonsCommon = function () {
197 var chan = this.msg.value.content.channel
198 var recps = this.recpsIds()
199 return [
200 chan ? h('input', {type: 'hidden', name: 'channel', value: chan}) : '',
201 h('input', {type: 'hidden', name: 'link', value: this.msg.key}),
202 h('input', {type: 'hidden', name: 'recps', value: recps.join(',')})
203 ]
204}
205
206RenderMsg.prototype.voteButton = function (expression) {
207 var chan = this.msg.value.content.channel
208 return [
209 h('input', {type: 'hidden', name: 'vote_value', value: 1}),
210 h('input', {type: 'hidden', name: 'vote_expression', value: expression}),
211 h('input', {type: 'submit', name: 'action_vote', value: expression})]
212}
213
214RenderMsg.prototype.attendButton = function () {
215 var chan = this.msg.value.content.channel
216 return [
217 h('input', {type: 'submit', name: 'action_attend', value: 'attend'})
218 ]
219}
220
221RenderMsg.prototype.message = function (cb) {
222 if (this.opts.raw) return this.raw(cb)
223 if (this.msg.sync) return this.sync(cb)
224 if (typeof this.c === 'string') return this.encrypted(cb)
225 if (this.isMissing) return this.missing(cb)
226 switch (this.c.type) {
227 case 'post': return this.post(cb)
228 case 'ferment/like':
229 case 'robeson/like':
230 case 'vote': return this.vote(cb)
231 case 'about': return this.about(cb)
232 case 'contact': return this.contact(cb)
233 case 'pub': return this.pub(cb)
234 case 'channel': return this.channel(cb)
235 case 'git-repo': return this.gitRepo(cb)
236 case 'git-update': return this.gitUpdate(cb)
237 case 'pull-request': return this.gitPullRequest(cb)
238 case 'issue': return this.issue(cb)
239 case 'issue-edit': return this.issueEdit(cb)
240 case 'music-release-cc': return this.musicRelease(cb)
241 case 'ssb-dns': return this.dns(cb)
242 case 'gathering': return this.gathering(cb)
243 case 'micro': return this.micro(cb)
244 case 'ferment/audio':
245 case 'robeson/audio':
246 return this.audio(cb)
247 case 'ferment/repost':
248 case 'robeson/repost':
249 return this.repost(cb)
250 case 'ferment/update':
251 case 'robeson/update':
252 return this.update(cb)
253 case 'chess_invite':
254 case 'ssb_chess_invite':
255 return this.chessInvite(cb)
256 case 'chess_invite_accept':
257 case 'ssb_chess_invite_accept':
258 return this.chessInviteAccept(cb)
259 case 'chess_move':
260 case 'ssb_chess_move':
261 return this.chessMove(cb)
262 case 'chess_game_end':
263 case 'ssb_chess_game_end':
264 return this.chessGameEnd(cb)
265 case 'chess_chat':
266 return this.chessChat(cb)
267 case 'wifi-network': return this.wifiNetwork(cb)
268 case 'mutual/credit': return this.mutualCredit(cb)
269 case 'mutual/account': return this.mutualAccount(cb)
270 case 'npm-publish': return this.npmPublish(cb)
271 case 'npm-packages': return this.npmPackages(cb)
272 case 'npm-prebuilds': return this.npmPrebuilds(cb)
273 case 'acme-challenges-http-01': return this.acmeChallengesHttp01(cb)
274 case 'bookclub': return this.bookclub(cb)
275 case 'macaco_maluco-sombrio-wall': return this.sombrioWall(cb)
276 case 'macaco_maluco-sombrio-tombstone': return this.sombrioTombstone(cb)
277 case 'macaco_maluco-sombrio-score': return this.sombrioScore(cb)
278 case 'blog': return this.blog(cb)
279 default: return this.object(cb)
280 }
281}
282
283RenderMsg.prototype.encrypted = function (cb) {
284 this.wrapMini(this.render.lockIcon(), cb)
285}
286
287RenderMsg.prototype.markdown = function (cb) {
288 if (this.opts.markdownSource)
289 return this.markdownSource(this.c.text, this.c.mentions)
290 return this.render.markdown(this.c.text, this.c.mentions)
291}
292
293RenderMsg.prototype.markdownSource = function (text, mentions) {
294 return h('div',
295 h('pre', String(text)),
296 mentions ? [
297 h('div', h('em', 'mentions:')),
298 this.valueTable(mentions, 2, function () {})
299 ] : ''
300 ).innerHTML
301}
302
303RenderMsg.prototype.post = function (cb) {
304 var self = this
305 var done = multicb({pluck: 1, spread: true})
306 if (self.c.root === self.c.branch) done()()
307 else self.link(self.c.root, done())
308 self.links(self.c.branch, done())
309 self.links(self.c.fork, done())
310 done(function (err, rootLink, branchLinks, forkLinks) {
311 if (err) return self.wrap(u.renderError(err), cb)
312 self.wrap(h('div.ssb-post',
313 rootLink ? h('div', h('small', h('span.symbol', '→'), ' ', rootLink)) : '',
314 branchLinks.map(function (a, i) {
315 return h('div', h('small', h('span.symbol', '  ↳'), ' ', a))
316 }),
317 forkLinks.map(function (a, i) {
318 return h('div', h('small', h('span.symbol', '⑂'), ' ', a))
319 }),
320 h('div.ssb-post-text', {innerHTML: self.markdown()})
321 ), cb)
322 })
323}
324
325RenderMsg.prototype.vote = function (cb) {
326 var self = this
327 var v = self.c.vote || self.c.like || {}
328 self.link(v, function (err, a) {
329 if (err) return cb(err)
330 self.wrapMini([
331 v.value > 0 ? 'dug' : v.value < 0 ? 'downvoted' : 'undug',
332 ' ', a,
333 v.reason ? [' as ', h('q', v.reason)] : ''
334 ], cb)
335 })
336}
337
338RenderMsg.prototype.getName = function (id, cb) {
339 switch (id && id[0]) {
340 case '%': return this.getMsgName(id, cb)
341 case '@': // fallthrough
342 case '&': return this.getAboutName(id, cb)
343 default: return cb(null, String(id))
344 }
345}
346
347RenderMsg.prototype.getMsgName = function (id, cb) {
348 var self = this
349 self.app.getMsg(id, function (err, msg) {
350 if (err && err.name == 'NotFoundError')
351 cb(null, id.substring(0, 10)+'...(missing)')
352 else if (err) cb(err)
353 // preserve security: only decrypt the linked message if we decrypted
354 // this message
355 else if (self.msg.value.private) self.app.unboxMsg(msg, gotMsg)
356 else gotMsg(null, msg)
357 })
358 function gotMsg(err, msg) {
359 if (err) return cb(err)
360 new RenderMsg(self.render, self.app, msg, {wrap: false}).title(cb)
361 }
362}
363
364function truncate(str, len) {
365 str = String(str)
366 return str.length > len ? str.substr(0, len) + '...' : str
367}
368
369function title(str) {
370 return truncate(mdInline(str), 72)
371}
372
373RenderMsg.prototype.title = function (cb) {
374 var self = this
375 self.app.filterMsg(self.msg, self.opts, function (err, show) {
376 if (err) return cb(err)
377 if (show) self.title1(cb)
378 else cb(null, '[…]')
379 })
380}
381
382RenderMsg.prototype.title1 = function (cb) {
383 var self = this
384 if (!self.c || typeof self.c !== 'object') {
385 cb(null, self.msg.key)
386 } else if (typeof self.c.text === 'string') {
387 if (self.c.type === 'post')
388 cb(null, title(self.c.text))
389 else
390 cb(null, '%' + self.c.type + ': ' + (self.c.title || title(self.c.text)))
391 } else {
392 if (self.c.type === 'ssb-dns')
393 cb(null, self.c.record && JSON.stringify(self.c.record.data) || self.msg.key)
394 else if (self.c.type === 'npm-publish')
395 self.npmPublishTitle(cb)
396 else if (self.c.type === 'chess_chat')
397 cb(null, title(self.c.msg))
398 else if (self.c.type === 'chess_invite')
399 self.chessInviteTitle(cb)
400 else if (self.c.type === 'bookclub')
401 self.bookclubTitle(cb)
402 else
403 self.app.getAbout(self.msg.key, function (err, about) {
404 if (err) return cb(err)
405 var name = about.name || about.title
406 || (about.description && mdInline(about.description))
407 if (name) return cb(null, truncate(name, 72))
408 self.message(function (err, el) {
409 if (err) return cb(err)
410 cb(null, '%' + title(h('div', el).textContent))
411 })
412 })
413 }
414}
415
416RenderMsg.prototype.getAboutName = function (id, cb) {
417 this.app.getAbout(id, function (err, about) {
418 cb(err, about && about.name || (String(id).substr(0, 8) + '…'))
419 })
420}
421
422RenderMsg.prototype.link = function (link, cb) {
423 var self = this
424 var ref = u.linkDest(link)
425 if (!ref) return cb(null, '')
426 self.getName(ref, function (err, name) {
427 if (err) name = truncate(ref, 10)
428 cb(null, h('a', {href: self.toUrl(ref)}, name))
429 })
430}
431
432RenderMsg.prototype.link1 = function (link, cb) {
433 var self = this
434 var ref = u.linkDest(link)
435 if (!ref) return cb(), ''
436 var a = h('a', {href: self.toUrl(ref)}, ref)
437 self.getName(ref, function (err, name) {
438 if (err) name = ref
439 a.childNodes[0].textContent = name
440 cb()
441 })
442 return a
443}
444
445RenderMsg.prototype.links = function (links, cb) {
446 var self = this
447 var done = multicb({pluck: 1})
448 u.toArray(links).forEach(function (link) {
449 self.link(link, done())
450 })
451 done(cb)
452}
453
454function dateTime(d) {
455 var date = new Date(d.epoch)
456 return date.toString()
457 // d.bias
458 // d.epoch
459}
460
461// TODO: make more DRY
462var knownAboutProps = {
463 type: true,
464 root: true,
465 about: true,
466 attendee: true,
467 about: true,
468 image: true,
469 description: true,
470 name: true,
471 title: true,
472 attendee: true,
473 startDateTime: true,
474 endDateTime: true,
475 location: true,
476 /*
477 rating: true,
478 ratingType: true,
479 */
480}
481
482RenderMsg.prototype.about = function (cb) {
483 var keys = Object.keys(this.c).sort().join()
484 var isSelf = this.c.about === this.msg.value.author
485
486 if (keys === 'about,name,type') {
487 return this.wrapMini([
488 isSelf ?
489 'self-identifies as ' :
490 ['identifies ', h('a', {href: this.toUrl(this.c.about)}, truncate(this.c.about, 10)), ' as '],
491 h('ins', this.c.name)
492 ], cb)
493 }
494
495 var done = multicb({pluck: 1, spread: true})
496 var elCb = done()
497
498 var isAttendingMsg = u.linkDest(this.c.attendee) === this.msg.value.author
499 && keys === 'about,attendee,type'
500 if (isAttendingMsg) {
501 var attending = !this.c.attendee.remove
502 this.wrapMini([
503 attending ? ' is attending' : ' is not attending', ' ',
504 this.link1(this.c.about, done())
505 ], elCb)
506 return done(cb)
507 }
508
509 var extras
510 for (var k in this.c) {
511 if (this.c[k] !== null && this.c[k] !== '' && !knownAboutProps[k]) {
512 if (!extras) extras = {}
513 extras[k] = this.c[k]
514 }
515 }
516
517 var img = u.linkDest(this.c.image)
518 // if there is a description, it is likely to be multi-line
519 var hasDescription = this.c.description != null
520 // if this about message gives the thing a name, show its id
521 var showComputedName = !isSelf && !this.c.name
522
523 this.wrap([
524 this.c.root ? h('div',
525 h('small', '> ', this.link1(this.c.root, done()))
526 ) : '',
527 isSelf ? 'self-describes as ' : [
528 'describes ',
529 !this.c.about ? ''
530 : showComputedName ? this.link1(this.c.about, done())
531 : h('a', {href: this.toUrl(this.c.about)}, truncate(this.c.about, 10)),
532 ' as '
533 ],
534 this.c.name ? [h('ins', this.c.name), ' '] : '',
535 this.c.description ? h('div',
536 {innerHTML: this.render.markdown(this.c.description)}) : '',
537 this.c.title ? h('h3', this.c.title) : '',
538 this.c.attendee ? h('div',
539 this.link1(this.c.attendee.link, done()),
540 this.c.attendee.remove ? ' is not attending' : ' is attending'
541 ) : '',
542 this.c.startDateTime ? h('div',
543 'starting at ', dateTime(this.c.startDateTime)) : '',
544 this.c.endDateTime ? h('div',
545 'ending at ', dateTime(this.c.endDateTime)) : '',
546 this.c.location ? h('div', 'at ', this.c.location) : '',
547 img ? h('a', {href: this.toUrl(img)},
548 h('img.ssb-avatar-image', {
549 src: this.render.imageUrl(img),
550 alt: ' ',
551 })) : '',
552 /*
553 this.c.rating != null ? this.aboutRating() : '',
554 */
555 extras ? this.valueTable(extras, 1, done())
556 : ''
557 ], elCb)
558 done(cb)
559}
560
561/*
562 * disabled until it's clearer how to do this -cel
563RenderMsg.prototype.aboutRating = function (cb) {
564 var rating = Number(this.c.rating)
565 var type = this.c.ratingType || '★'
566 var text = rating + ' ' + type
567 if (isNaN(rating)) return 'rating: ' + text
568 if (rating > 5) rating = 5
569 var el = h('div', {title: text})
570 for (var i = 0; i < rating; i++) {
571 el.appendChild(h('span',
572 {innerHTML: unwrapP(this.render.markdown(type) + ' ')}
573 ))
574 }
575 return el
576}
577*/
578
579RenderMsg.prototype.contact = function (cb) {
580 var self = this
581 self.link(self.c.contact, function (err, a) {
582 if (err) return cb(err)
583 if (!a) a = "?"
584 self.wrapMini([
585 self.c.following && self.c.autofollow ? 'follows pub' :
586 self.c.following && self.c.pub ? 'autofollows' :
587 self.c.following ? 'follows' :
588 self.c.blocking ? 'blocks' :
589 self.c.flagged ? 'flagged' :
590 self.c.following === false ? 'unfollows' :
591 self.c.blocking === false ? 'unblocks' : '',
592 self.c.flagged === false ? 'unflagged' :
593 ' ', a,
594 self.c.note ? [
595 ' from ',
596 h('code', self.c.note)
597 ] : '',
598 self.c.reason ? [' because ', h('q', self.c.reason)] : ''
599 ], cb)
600 })
601}
602
603RenderMsg.prototype.pub = function (cb) {
604 var self = this
605 var addr = self.c.address || {}
606 self.link(addr.key, function (err, pubLink) {
607 if (err) return cb(err)
608 self.wrapMini([
609 'connects to ', pubLink, ' at ',
610 h('code', addr.host + ':' + addr.port)], cb)
611 })
612}
613
614RenderMsg.prototype.channel = function (cb) {
615 var chan = '#' + this.c.channel
616 this.wrapMini([
617 this.c.subscribed ? 'subscribes to ' :
618 this.c.subscribed === false ? 'unsubscribes from ' : '',
619 h('a', {href: this.toUrl(chan)}, chan)], cb)
620}
621
622RenderMsg.prototype.gitRepo = function (cb) {
623 var self = this
624 var id = self.msg.key
625 var name = self.c.name
626 var upstream = self.c.upstream
627 self.link(upstream, function (err, upstreamA) {
628 if (err) upstreamA = ('a', {href: self.toUrl(upstream)}, String(name))
629 self.wrapMini([
630 upstream ? ['forked ', upstreamA, ': '] : '',
631 'git clone ',
632 h('code', h('small', 'ssb://' + id)),
633 name ? [' ', h('a', {href: self.toUrl(id)}, String(name))] : ''
634 ], cb)
635 })
636}
637
638RenderMsg.prototype.gitUpdate = function (cb) {
639 var self = this
640 // h('a', {href: self.toUrl(self.c.repo)}, 'ssb://' + self.c.repo),
641 var size = [].concat(self.c.packs, self.c.indexes)
642 .map(function (o) { return o && o.size })
643 .reduce(function (total, s) { return total + s })
644
645 var done = multicb({pluck: 1, spread: true})
646 self.link(self.c.repo, done())
647 self.render.npmPackageMentions(self.c.mentions, done())
648 self.render.npmPrebuildMentions(self.c.mentions, done())
649 done(function (err, a, pkgMentionsEl, prebuildMentionsEl) {
650 if (err) return cb(err)
651 self.wrap(h('div.ssb-git-update',
652 'git push ', a, ' ',
653 !isNaN(size) ? [self.render.formatSize(size), ' '] : '',
654 self.c.refs ? h('ul', Object.keys(self.c.refs).map(function (ref) {
655 var id = self.c.refs[ref]
656 var type = /^refs\/tags/.test(ref) ? 'tag' : 'commit'
657 var path = id && ('/git/' + type + '/' + encodeURIComponent(id)
658 + '?msg=' + encodeURIComponent(self.msg.key))
659 return h('li',
660 ref.replace(/^refs\/(heads|tags)\//, ''), ': ',
661 id ? h('a', {href: self.render.toUrl(path)}, h('code', id))
662 : h('em', 'deleted'))
663 })) : '',
664 Array.isArray(self.c.commits) ?
665 h('ul', self.c.commits.map(function (commit) {
666 var path = '/git/commit/' + encodeURIComponent(commit.sha1)
667 + '?msg=' + encodeURIComponent(self.msg.key)
668 return h('li', h('a', {href: self.render.toUrl(path)},
669 h('code', String(commit.sha1).substr(0, 8))), ' ',
670 self.linkify(String(commit.title)),
671 self.render.gitCommitBody(commit.body)
672 )
673 })) : '',
674 Array.isArray(self.c.tags) ?
675 h('ul', self.c.tags.map(function (tag) {
676 var path = '/git/tag/' + encodeURIComponent(tag.sha1)
677 + '?msg=' + encodeURIComponent(self.msg.key)
678 return h('li',
679 h('a', {href: self.render.toUrl(path)},
680 h('code', String(tag.sha1).substr(0, 8))), ' ',
681 'tagged ', String(tag.type), ' ',
682 h('code', String(tag.object).substr(0, 8)), ' ',
683 String(tag.tag),
684 tag.title ? [': ', self.linkify(String(tag.title).trim()), ' '] : '',
685 tag.body ? self.render.gitCommitBody(tag.body) : ''
686 )
687 })) : '',
688 self.c.commits_more ? h('div',
689 '+ ' + self.c.commits_more + ' more commits') : '',
690 self.c.tags_more ? h('div',
691 '+ ' + self.c.tags_more + ' more tags') : '',
692 pkgMentionsEl,
693 prebuildMentionsEl
694 ), cb)
695 })
696}
697
698RenderMsg.prototype.gitPullRequest = function (cb) {
699 var self = this
700 var done = multicb({pluck: 1, spread: true})
701 self.link(self.c.repo, done())
702 self.link(self.c.head_repo, done())
703 done(function (err, baseRepoLink, headRepoLink) {
704 if (err) return cb(err)
705 self.wrap(h('div.ssb-pull-request',
706 'pull request ',
707 'to ', baseRepoLink, ':', self.c.branch, ' ',
708 'from ', headRepoLink, ':', self.c.head_branch,
709 self.c.title ? h('h4', self.c.title) : '',
710 h('div', {innerHTML: self.markdown()})), cb)
711 })
712}
713
714RenderMsg.prototype.issue = function (cb) {
715 var self = this
716 self.link(self.c.project, function (err, projectLink) {
717 if (err) return cb(err)
718 self.wrap(h('div.ssb-issue',
719 'issue on ', projectLink,
720 self.c.title ? h('h4', self.c.title) : '',
721 h('div', {innerHTML: self.markdown()})), cb)
722 })
723}
724
725RenderMsg.prototype.issueEdit = function (cb) {
726 this.wrap('', cb)
727}
728
729RenderMsg.prototype.object = function (cb) {
730 var done = multicb({pluck: 1, spread: true})
731 var elCb = done()
732 this.wrap([
733 this.valueTable(this.c, 1, done()),
734 ], elCb)
735 done(cb)
736}
737
738RenderMsg.prototype.valueTable = function (val, depth, cb) {
739 var isContent = depth === 1
740 var self = this
741 switch (typeof val) {
742 case 'object':
743 if (val === null) return cb(), ''
744 var done = multicb({pluck: 1, spread: true})
745 var el = Array.isArray(val)
746 ? h('ul', val.map(function (item) {
747 return h('li', self.valueTable(item, depth + 1, done()))
748 }))
749 : h('table.ssb-object', Object.keys(val).map(function (key) {
750 if (key === 'text') {
751 return h('tr',
752 h('td', h('strong', 'text')),
753 h('td', h('div', {
754 innerHTML: self.render.markdown(val.text, val.mentions)
755 }))
756 )
757 } else if (isContent && key === 'type') {
758 // TODO: also link to images by type, using links2
759 var type = val.type
760 return h('tr',
761 h('td', h('strong', 'type')),
762 h('td', h('a', {href: self.toUrl('/type/' + type)}, type))
763 )
764 }
765 return h('tr',
766 h('td', h('strong', key)),
767 h('td', self.valueTable(val[key], depth + 1, done()))
768 )
769 }))
770 done(cb)
771 return el
772 case 'string':
773 if (val[0] === '#') return cb(null, h('a', {href: self.toUrl('/channel/' + val.substr(1))}, val))
774 if (u.isRef(val)) return self.link1(val, cb)
775 return cb(), self.linkify(val)
776 case 'boolean':
777 return cb(), h('input', {
778 type: 'checkbox', disabled: 'disabled', checked: val
779 })
780 default:
781 return cb(), String(val)
782 }
783}
784
785RenderMsg.prototype.missing = function (cb) {
786 this.wrapMini(h('code', 'MISSING'), cb)
787}
788
789RenderMsg.prototype.issues = function (cb) {
790 var self = this
791 var done = multicb({pluck: 1, spread: true})
792 var issues = u.toArray(self.c.issues)
793 if (self.c.type === 'issue-edit' && self.c.issue) {
794 issues.push({
795 link: self.c.issue,
796 title: self.c.title,
797 open: self.c.open,
798 })
799 }
800 var els = issues.map(function (issue) {
801 var commit = issue.object || issue.label ? [
802 issue.object ? h('code', issue.object) : '', ' ',
803 issue.label ? h('q', issue.label) : ''] : ''
804 if (issue.merged === true)
805 return h('div',
806 'merged ', self.link1(issue, done()))
807 if (issue.open === false)
808 return h('div',
809 'closed ', self.link1(issue, done()))
810 if (issue.open === true)
811 return h('div',
812 'reopened ', self.link1(issue, done()))
813 if (typeof issue.title === 'string')
814 return h('div',
815 'renamed ', self.link1(issue, done()), ' to ', h('ins', issue.title))
816 })
817 done(cb)
818 return els.length > 0 ? [els, h('br')] : ''
819}
820
821RenderMsg.prototype.repost = function (cb) {
822 var self = this
823 var id = u.linkDest(self.c.repost)
824 self.app.getMsg(id, function (err, msg) {
825 if (err && err.name == 'NotFoundError')
826 gotMsg(null, id.substring(0, 10)+'...(missing)')
827 else if (err) gotMsg(err)
828 else if (self.msg.value.private) self.app.unboxMsg(msg, gotMsg)
829 else gotMsg(null, msg)
830 })
831 function gotMsg(err, msg) {
832 if (err) return cb(err)
833 var renderMsg = new RenderMsg(self.render, self.app, msg, {wrap: false})
834 renderMsg.message(function (err, msgEl) {
835 self.wrapMini(['reposted ',
836 h('code.ssb-id',
837 h('a', {href: self.render.toUrl(id)}, id)),
838 h('div', err ? u.renderError(err) : msgEl || '')
839 ], cb)
840 })
841 }
842}
843
844RenderMsg.prototype.update = function (cb) {
845 var id = String(this.c.update)
846 this.wrapMini([
847 h('div', 'updated ', h('code.ssb-id',
848 h('a', {href: this.render.toUrl(id)}, id))),
849 this.c.title ? h('h4.msg-title', this.c.title) : '',
850 this.c.description ? h('div',
851 {innerHTML: this.render.markdown(this.c.description)}) : ''
852 ], cb)
853}
854
855function formatDuration(s) {
856 return Math.floor(s / 60) + ':' + ('0' + s % 60).substr(-2)
857}
858
859RenderMsg.prototype.audio = function (cb) {
860 // fileName, fallbackFileName, overview
861 this.wrap(h('table', h('tr',
862 h('td',
863 this.c.artworkSrc
864 ? h('a', {href: this.render.toUrl(this.c.artworkSrc)}, h('img', {
865 src: this.render.imageUrl(this.c.artworkSrc),
866 alt: ' ',
867 width: 72,
868 height: 72,
869 }))
870 : ''),
871 h('td',
872 h('a', {href: this.render.toUrl(this.c.audioSrc)}, this.c.title),
873 isFinite(this.c.duration)
874 ? ' (' + formatDuration(this.c.duration) + ')'
875 : '',
876 this.c.description
877 ? h('p', {innerHTML: this.render.markdown(this.c.description)})
878 : ''
879 ))), cb)
880}
881
882RenderMsg.prototype.musicRelease = function (cb) {
883 var self = this
884 this.wrap([
885 h('table', h('tr',
886 h('td',
887 this.c.cover
888 ? h('a', {href: this.render.imageUrl(this.c.cover)}, h('img', {
889 src: this.render.imageUrl(this.c.cover),
890 alt: ' ',
891 width: 72,
892 height: 72,
893 }))
894 : ''),
895 h('td',
896 h('h4.msg-title', this.c.title),
897 this.c.text
898 ? h('div', {innerHTML: this.render.markdown(this.c.text)})
899 : ''
900 )
901 )),
902 h('ul', u.toArray(this.c.tracks).filter(Boolean).map(function (track) {
903 return h('li',
904 h('a', {href: self.render.toUrl(track.link)}, track.fname))
905 }))
906 ], cb)
907}
908
909RenderMsg.prototype.dns = function (cb) {
910 var self = this
911 var record = self.c.record || {}
912 var done = multicb({pluck: 1, spread: true})
913 var elCb = done()
914 self.wrap([
915 h('div',
916 h('p',
917 h('ins', {title: 'name'}, record.name), ' ',
918 h('span', {title: 'ttl'}, record.ttl), ' ',
919 h('span', {title: 'class'}, record.class), ' ',
920 h('span', {title: 'type'}, record.type)
921 ),
922 h('pre', {title: 'data'},
923 JSON.stringify(record.data || record.value, null, 2)),
924 !self.c.branch ? null : h('div',
925 'replaces: ', u.toArray(self.c.branch).map(function (id, i) {
926 return [self.link1(id, done()), i === 0 ? ', ' : '']
927 })
928 )
929 )
930 ], elCb)
931 done(cb)
932}
933
934RenderMsg.prototype.wifiNetwork = function (cb) {
935 var net = this.c.network || {}
936 this.wrap([
937 h('div', 'wifi network'),
938 h('table',
939 Object.keys(net).map(function (key) {
940 return h('tr',
941 h('td', key),
942 h('td', h('pre', JSON.stringify(net[key]))))
943 })
944 ),
945 ], cb)
946}
947
948RenderMsg.prototype.mutualCredit = function (cb) {
949 var self = this
950 var currency = String(self.c.currency)
951 self.link(self.c.account, function (err, a) {
952 if (err) return cb(err)
953 self.wrapMini([
954 'credits ', a || '?', ' ',
955 h('code', self.c.amount), ' ',
956 currency[0] === '#'
957 ? h('a', {href: self.toUrl(currency)}, currency)
958 : h('ins', currency),
959 self.c.memo ? [' for ', h('q', self.c.memo)] : ''
960 ], cb)
961 })
962}
963
964RenderMsg.prototype.mutualAccount = function (cb) {
965 return this.object(cb)
966}
967
968RenderMsg.prototype.gathering = function (cb) {
969 this.wrapMini('gathering', cb)
970}
971
972function unwrapP(html) {
973 return String(html).replace(/^<p>(.*)<\/p>\s*$/, function ($0, $1) {
974 return $1
975 })
976}
977
978RenderMsg.prototype.micro = function (cb) {
979 var el = h('span', {innerHTML: unwrapP(this.markdown())})
980 this.wrapMini(el, cb)
981}
982
983function hJoin(els, seperator, lastSeparator) {
984 return els.map(function (el, i) {
985 return [i === 0 ? '' : i === els.length-1 ? lastSeparator : seperator, el]
986 })
987}
988
989function asNpmReadme(readme) {
990 if (!readme || readme === 'ERROR: No README data found!') return
991 return u.ifString(readme)
992}
993
994function singleValue(obj) {
995 if (!obj || typeof obj !== 'object') return obj
996 var keys = Object.keys(obj)
997 if (keys.length === 1) return obj[keys[0]]
998}
999
1000function ifDifferent(obj, value) {
1001 if (singleValue(obj) !== value) return obj
1002}
1003
1004RenderMsg.prototype.npmPublish = function (cb) {
1005 var self = this
1006 var render = self.render
1007 var pkg = self.c.meta || {}
1008 var pkgReadme = asNpmReadme(pkg.readme)
1009 var pkgDescription = u.ifString(pkg.description)
1010
1011 var versions = Object.keys(pkg.versions || {})
1012 var singleVersion = versions.length === 1 ? versions[0] : null
1013 var singleRelease = singleVersion && pkg.versions[singleVersion]
1014 var singleReadme = singleRelease && asNpmReadme(singleRelease.readme)
1015
1016 var distTags = pkg['dist-tags'] || {}
1017 var distTagged = {}
1018 for (var distTag in distTags)
1019 if (distTag !== 'latest')
1020 distTagged[distTags[distTag]] = distTag
1021
1022 self.links(self.c.previousPublish, function (err, prevLinks) {
1023 if (err) return cb(err)
1024 self.wrap([
1025 h('div',
1026 'published ',
1027 h('u', pkg.name), ' ',
1028 hJoin(versions.map(function (version) {
1029 var distTag = distTagged[version]
1030 return [h('b', version), distTag ? [' (', h('i', distTag), ')'] : '']
1031 }), ', ')
1032 ),
1033 pkgDescription ? h('div',
1034 // TODO: make mdInline use custom emojis
1035 h('q', {innerHTML: unwrapP(render.markdown(pkgDescription))})) : '',
1036 prevLinks.length ? h('div', 'previous: ', prevLinks) : '',
1037 pkgReadme && pkgReadme !== singleReadme ?
1038 h('blockquote', {innerHTML: render.markdown(pkgReadme)}) : '',
1039 versions.map(function (version, i) {
1040 var release = pkg.versions[version] || {}
1041 var license = u.ifString(release.license)
1042 var author = ifDifferent(release.author, self.msg.value.author)
1043 var description = u.ifString(release.description)
1044 var readme = asNpmReadme(release.readme)
1045 var keywords = u.toArray(release.keywords).map(u.ifString)
1046 var dist = release.dist || {}
1047 var size = u.ifNumber(dist.size)
1048 return [
1049 h > 0 ? h('br') : '',
1050 version !== singleVersion ? h('div', 'version: ', version) : '',
1051 author ? h('div', 'author: ', render.npmAuthorLink(author)) : '',
1052 license ? h('div', 'license: ', h('code', license)) : '',
1053 keywords.length ? h('div', 'keywords: ', keywords.join(', ')) : '',
1054 size ? h('div', 'size: ', render.formatSize(size)) : '',
1055 description && description !== pkgDescription ?
1056 h('div', h('q', {innerHTML: render.markdown(description)})) : '',
1057 readme ? h('blockquote', {innerHTML: render.markdown(readme)}) : ''
1058 ]
1059 })
1060 ], cb)
1061 })
1062}
1063
1064RenderMsg.prototype.npmPackages = function (cb) {
1065 var self = this
1066 var done = multicb({pluck: 1, spread: true})
1067 var elCb = done()
1068 function renderIdLink(id) {
1069 return [h('a', {href: self.toUrl(id)}, truncate(id, 8)), ' ']
1070 }
1071 self.render.npmPackageMentions(self.c.mentions, function (err, el) {
1072 if (err) return cb(err)
1073 var dependencyLinks = u.toArray(self.c.dependencyBranch)
1074 var versionLinks = u.toArray(self.c.versionBranch)
1075 self.wrap(h('div', [
1076 el,
1077 dependencyLinks.length ? h('div',
1078 'dependencies via: ', dependencyLinks.map(renderIdLink)
1079 ) : '',
1080 versionLinks.length ? h('div',
1081 'previous versions: ', versionLinks.map(renderIdLink)
1082 ) : ''
1083 ]), elCb)
1084 return done(cb)
1085 })
1086}
1087
1088RenderMsg.prototype.npmPrebuilds = function (cb) {
1089 var self = this
1090 self.render.npmPrebuildMentions(self.c.mentions, function (err, el) {
1091 if (err) return cb(err)
1092 self.wrap(el, cb)
1093 })
1094}
1095
1096RenderMsg.prototype.npmPublishTitle = function (cb) {
1097 var pkg = this.c.meta || {}
1098 var name = pkg.name || pkg._id || '?'
1099
1100 var taggedVersions = {}
1101 for (var version in pkg.versions || {})
1102 taggedVersions[version] = []
1103
1104 var distTags = pkg['dist-tags'] || {}
1105 for (var distTag in distTags) {
1106 if (distTag === 'latest') continue
1107 var version = distTags[distTag] || '?'
1108 var tags = taggedVersions[version] || (taggedVersions[version] = [])
1109 tags.push(distTag)
1110 }
1111
1112 cb(null, name + '@' + Object.keys(taggedVersions).map(function (version) {
1113 var tags = taggedVersions[version]
1114 return (tags.length ? tags.join(',') + ':' : '') + version
1115 }).join(','))
1116}
1117
1118function expandDigitToSpaces(n) {
1119 return ' '.substr(-n)
1120}
1121
1122function parseFenRank (line) {
1123 return line.replace(/\d/g, expandDigitToSpaces).split('')
1124}
1125
1126function parseChess(fen) {
1127 var fields = String(fen).split(/\s+/)
1128 var ranks = fields[0].split('/')
1129 var f2 = fields[2] || ''
1130 return {
1131 board: ranks.map(parseFenRank),
1132 /*
1133 nextMove: fields[1] === 'b' ? 'black'
1134 : fields[1] === 'w' ? 'white' : 'unknown',
1135 castling: f2 === '-' ? {} : {
1136 w: {
1137 k: 0 < f2.indexOf('K'),
1138 q: 0 < f2.indexOf('Q'),
1139 },
1140 b: {
1141 k: 0 < f2.indexOf('k'),
1142 q: 0 < f2.indexOf('q'),
1143 }
1144 },
1145 enpassantTarget: fields[3] === '-' ? null : fields[3],
1146 halfmoves: Number(fields[4]),
1147 fullmoves: Number(fields[5]),
1148 */
1149 }
1150}
1151
1152var chessSymbols = {
1153 ' ': [' ', ''],
1154 P: ['♙', 'white', 'pawn'],
1155 N: ['♘', 'white', 'knight'],
1156 B: ['♗', 'white', 'bishop'],
1157 R: ['♖', 'white', 'rook'],
1158 Q: ['♕', 'white', 'queen'],
1159 K: ['♔', 'white', 'king'],
1160 p: ['♟', 'black', 'pawn'],
1161 n: ['♞', 'black', 'knight'],
1162 b: ['♝', 'black', 'bishop'],
1163 r: ['♜', 'black', 'rook'],
1164 q: ['♛', 'black', 'queen'],
1165 k: ['♚', 'black', 'king'],
1166}
1167
1168function chessPieceName(c) {
1169 return chessSymbols[c] && chessSymbols[c][2] || '?'
1170}
1171
1172function renderChessSymbol(c, loc) {
1173 var info = chessSymbols[c] || ['?', '', 'unknown']
1174 return h('span.symbol', {
1175 title: info[1] + ' ' + info[2] + (loc ? ' at ' + loc : '')
1176 }, info[0])
1177}
1178
1179function chessLocToIdxs(loc) {
1180 var m = /^([a-h])([1-8])$/.exec(loc)
1181 if (m) return [8 - m[2], m[1].charCodeAt(0) - 97]
1182}
1183
1184function lookupPiece(board, loc) {
1185 var idxs = chessLocToIdxs(loc)
1186 return idxs && board[idxs[0]] && board[idxs[0]][idxs[1]]
1187}
1188
1189function chessIdxsToLoc(i, j) {
1190 return 'abcdefgh'[j] + (8-i)
1191}
1192
1193RenderMsg.prototype.chessBoard = function (board) {
1194 if (!board) return ''
1195 return h('table.chess-board',
1196 board.map(function (rank, i) {
1197 return h('tr', rank.map(function (piece, j) {
1198 var dark = (i ^ j) & 1
1199 return h('td', {
1200 class: 'chess-square chess-square-' + (dark ? 'dark' : 'light'),
1201 }, renderChessSymbol(piece, chessIdxsToLoc(i, j)))
1202 }))
1203 })
1204 )
1205}
1206
1207RenderMsg.prototype.chessMove = function (cb) {
1208 var self = this
1209 var c = self.c
1210 var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
1211 var game = parseChess(fen)
1212 var piece = game && lookupPiece(game.board, c.dest)
1213 self.link(self.c.root, function (err, rootLink) {
1214 if (err) return cb(err)
1215 self.wrap([
1216 h('div', h('small', '> ', rootLink)),
1217 h('p',
1218 // 'player ', (c.ply || ''), ' ',
1219 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
1220 'from ', c.orig, ' ',
1221 'to ', c.dest
1222 ),
1223 self.chessBoard(game.board)
1224 ], cb)
1225 })
1226}
1227
1228RenderMsg.prototype.chessInvite = function (cb) {
1229 var self = this
1230 var myColor = self.c.myColor
1231 self.link(self.c.inviting, function (err, link) {
1232 if (err) return cb(err)
1233 self.wrap([
1234 'invites ', link, ' to play chess',
1235 // myColor ? h('p', 'my color is ' + myColor) : ''
1236 ], cb)
1237 })
1238}
1239
1240RenderMsg.prototype.chessInviteTitle = function (cb) {
1241 var self = this
1242 var done = multicb({pluck: 1, spread: true})
1243 self.getName(self.c.inviting, done())
1244 self.getName(self.msg.value.author, done())
1245 done(function (err, inviteeLink, inviterLink) {
1246 if (err) return cb(err)
1247 self.wrap([
1248 'chess: ', inviterLink, ' vs. ', inviteeLink
1249 ], cb)
1250 })
1251}
1252
1253RenderMsg.prototype.chessInviteAccept = function (cb) {
1254 var self = this
1255 self.link(self.c.root, function (err, rootLink) {
1256 if (err) return cb(err)
1257 self.wrap([
1258 h('div', h('small', '> ', rootLink)),
1259 h('p', 'accepts invitation to play chess')
1260 ], cb)
1261 })
1262}
1263
1264RenderMsg.prototype.chessGameEnd = function (cb) {
1265 var self = this
1266 var c = self.c
1267 if (c.status === 'resigned') return self.link(self.c.root, function (err, rootLink) {
1268 if (err) return cb(err)
1269 self.wrap([
1270 h('div', h('small', '> ', rootLink)),
1271 h('p', h('strong', 'resigned'))
1272 ], cb)
1273 })
1274
1275 var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
1276 var game = parseChess(fen)
1277 var piece = game && lookupPiece(game.board, c.dest)
1278 var done = multicb({pluck: 1, spread: true})
1279 self.link(self.c.root, done())
1280 self.link(self.c.winner, done())
1281 done(function (err, rootLink, winnerLink) {
1282 if (err) return cb(err)
1283 self.wrap([
1284 h('div', h('small', '> ', rootLink)),
1285 h('p',
1286 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
1287 'from ', c.orig, ' ',
1288 'to ', c.dest
1289 ),
1290 h('p',
1291 h('strong', self.c.status), '. winner: ', h('strong', winnerLink)),
1292 self.chessBoard(game.board)
1293 ], cb)
1294 })
1295}
1296
1297RenderMsg.prototype.chessChat = function (cb) {
1298 var self = this
1299 self.link(self.c.root, function (err, rootLink) {
1300 if (err) return cb(err)
1301 self.wrap([
1302 h('div', h('small', '> ', rootLink)),
1303 h('p', self.c.msg)
1304 ], cb)
1305 })
1306}
1307
1308RenderMsg.prototype.chessMove = function (cb) {
1309 if (this.opts.full) return this.chessMoveFull(cb)
1310 return this.chessMoveMini(cb)
1311}
1312
1313RenderMsg.prototype.chessMoveFull = function (cb) {
1314 var self = this
1315 var c = self.c
1316 var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
1317 var game = parseChess(fen)
1318 var piece = game && lookupPiece(game.board, c.dest)
1319 self.link(self.c.root, function (err, rootLink) {
1320 if (err) return cb(err)
1321 self.wrap([
1322 h('div', h('small', '> ', rootLink)),
1323 h('p',
1324 // 'player ', (c.ply || ''), ' ',
1325 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
1326 'from ', c.orig, ' ',
1327 'to ', c.dest
1328 ),
1329 self.chessBoard(game.board)
1330 ], cb)
1331 })
1332}
1333
1334RenderMsg.prototype.chessMoveMini = function (cb) {
1335 var self = this
1336 var c = self.c
1337 var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
1338 var game = parseChess(fen)
1339 var piece = game && lookupPiece(game.board, c.dest)
1340 self.link(self.c.root, function (err, rootLink) {
1341 if (err) return cb(err)
1342 self.wrapMini([
1343 'moved ', chessPieceName(piece), ' ',
1344 'to ', c.dest
1345 ], cb)
1346 })
1347}
1348
1349RenderMsg.prototype.acmeChallengesHttp01 = function (cb) {
1350 var self = this
1351 self.wrapMini(h('span',
1352 'serves ',
1353 hJoin(u.toArray(self.c.challenges).map(function (challenge) {
1354 return h('a', {
1355 href: 'http://' + challenge.domain +
1356 '/.well-known/acme-challenge/' + challenge.token,
1357 title: challenge.keyAuthorization,
1358 }, challenge.domain)
1359 }), ', ', ', and ')
1360 ), cb)
1361}
1362
1363RenderMsg.prototype.bookclub = function (cb) {
1364 var self = this
1365 var props = self.c.common || self.c
1366 var images = u.toLinkArray(props.image || props.images)
1367 self.wrap(h('table', h('tr',
1368 h('td',
1369 images.map(function (image) {
1370 return h('a', {href: self.render.toUrl(image.link)}, h('img', {
1371 src: self.render.imageUrl(image.link),
1372 alt: image.name || ' ',
1373 width: 180,
1374 }))
1375 })),
1376 h('td',
1377 h('h4', props.title),
1378 props.authors ?
1379 h('p', h('em', props.authors))
1380 : '',
1381 props.description
1382 ? h('div', {innerHTML: self.render.markdown(props.description)})
1383 : ''
1384 )
1385 )), cb)
1386}
1387
1388RenderMsg.prototype.bookclubTitle = function (cb) {
1389 var props = this.c.common || this.c
1390 cb(null, props.title || 'book')
1391}
1392
1393RenderMsg.prototype.sombrioPosition = function () {
1394 return h('span', '[' + this.c.position + ']')
1395}
1396
1397RenderMsg.prototype.sombrioWall = function (cb) {
1398 var self = this
1399 self.wrapMini(h('span',
1400 self.sombrioPosition(),
1401 ' wall'
1402 ), cb)
1403}
1404
1405RenderMsg.prototype.sombrioTombstone = function (cb) {
1406 var self = this
1407 self.wrapMini(h('span',
1408 self.sombrioPosition(),
1409 ' tombstone'
1410 ), cb)
1411}
1412
1413RenderMsg.prototype.sombrioScore = function (cb) {
1414 var self = this
1415 self.wrapMini(h('span',
1416 'scored ',
1417 h('ins', self.c.score)
1418 ), cb)
1419}
1420
1421RenderMsg.prototype.blog = function (cb) {
1422 var self = this
1423 var blogId = u.linkDest(self.c.blog)
1424 var imgId = u.linkDest(self.c.thumbnail)
1425 var imgLink = imgId ? u.toLinkArray(self.c.mentions).filter(function (link) {
1426 return link.link === imgId
1427 })[0] || u.toLink(self.c.thumbnail) : null
1428 self.wrapMini(h('table', h('tr',
1429 h('td',
1430 imgId ? h('img', {
1431 src: self.render.imageUrl(imgId),
1432 alt: (imgLink.name || '')
1433 + (imgLink.size != null ? ' (' + self.render.formatSize(imgLink.size) + ')' : ''),
1434 width: 180,
1435 }) : 'blog'),
1436 h('td',
1437 blogId ? h('h3', h('a', {href: self.render.toUrl('/markdown/' + blogId)},
1438 self.c.title || self.msg.key)) : '',
1439 self.c.summary || '')
1440 )), cb)
1441}
1442

Built with git-ssb-web