git ssb

16+

cel / patchfoo



Tree: 6c1319aa3220157ed76d0c897ae085605a9d65c2

Files: 6c1319aa3220157ed76d0c897ae085605a9d65c2 / lib / render-msg.js

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

Built with git-ssb-web