git ssb

16+

cel / patchfoo



Tree: 0f2d21603df91e7285c4a50da9d9dadf95f600b6

Files: 0f2d21603df91e7285c4a50da9d9dadf95f600b6 / lib / render-msg.js

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

Built with git-ssb-web