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