git ssb

16+

cel / patchfoo



Tree: 09cf04663fe2a772d5a06bbcf86b9014dc4f5228

Files: 09cf04663fe2a772d5a06bbcf86b9014dc4f5228 / lib / render-msg.js

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

Built with git-ssb-web