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