git ssb

16+

cel / patchfoo



Tree: d185716fb11842271c37ff499725b2b66ba2a5e3

Files: d185716fb11842271c37ff499725b2b66ba2a5e3 / lib / render.js

25214 bytesRaw
1var fs = require('fs')
2var path = require('path')
3var pull = require('pull-stream')
4var cat = require('pull-cat')
5var paramap = require('pull-paramap')
6var h = require('hyperscript')
7var ph = require('pull-hyperscript')
8var marked = require('../vendor/ssb-marked')
9var qs = require('querystring')
10var u = require('./util')
11var multicb = require('multicb')
12var RenderMsg = require('./render-msg')
13var Highlight = require('highlight.js')
14var md = require('ssb-markdown')
15var Diff = require('diff')
16var lru = require('hashlru')
17
18var maxWordLength = 80
19var truncateWordsRe = new RegExp('[^\\s<>&"]{' + (maxWordLength+1) + ',}')
20function truncateWord(word) {
21 var len = maxWordLength - 5 // "…[##]"
22 var truncatedLength = word.length - len
23 return word.substr(0, len) + '…<sub>[' + truncatedLength + ']</sub>'
24}
25function truncateWords(text) {
26 // truncate long words to avoid horizontal scrollbar
27 return text.replace(truncateWordsRe, truncateWord)
28}
29
30module.exports = Render
31
32function MdRenderer(render) {
33 marked.Renderer.call(this, {})
34 this.render = render
35}
36MdRenderer.prototype = new marked.Renderer()
37
38MdRenderer.prototype.urltransform = function (href) {
39 return this.render.toUrl(href)
40}
41
42MdRenderer.prototype.image = function (ref, title, text) {
43 if (ref[0] !== '&') return this.link(ref, title, text)
44 var alt = this.render.getImageAlt(ref, text)
45 if (alt.length > 60) alt = alt.substr(0, 60) + '…'
46 return (/^video:/.test(text) ? h('video', {
47 controls: 'controls',
48 src: this.render.toUrl(ref),
49 title: u.unescapeHTML(title) || undefined
50 }, '\n' + alt) : /^audio:/.test(text) ? h('audio', {
51 controls: 'controls',
52 src: this.render.toUrl(ref),
53 title: u.unescapeHTML(title) || undefined
54 }, '\n' + alt) : h('img', {
55 src: this.render.imageUrl(ref),
56 alt: alt,
57 title: u.unescapeHTML(title) || undefined
58 })).outerHTML
59}
60
61MdRenderer.prototype.link = function (ref, title, text) {
62 var href = this.urltransform(ref)
63 var name = href && /^\/(&|%26)/.test(href) && (title || text)
64 if (name) {
65 href += (/\?/.test(href) ? '&' : '?') +
66 'filename=' + encodeURIComponent(name)
67 }
68 if (u.isRef(ref)) {
69 var myName = this.render.app.getNameSync(ref)
70 if (myName) title = title ? title + ' (' + myName + ')' : myName
71 }
72 var hrefToken = href !== false ? u.token() : undefined
73 var a = h('a', {
74 class: href === false ? 'bad' : undefined,
75 href: href !== false ? hrefToken : undefined,
76 title: title || undefined
77 })
78 text = truncateWords(text)
79 // text is already html-escaped
80 a.innerHTML = text
81 var html = a.outerHTML
82 // href is already html-escaped
83 if (hrefToken) html = html.replace(hrefToken, href)
84
85 var link = this.render._mentionsByLink[ref]
86 if (link && link.type === 'text/x-markdown') {
87 html += h('sup', ' [', h('a', {
88 href: this.render.toUrl('/markdown/' + ref),
89 title: 'view rendered markdown'
90 }, 'md'), ']').outerHTML
91 }
92
93 return html
94}
95
96MdRenderer.prototype.mention = function (preceding, id) {
97 var href = this.urltransform(id)
98 var myName = this.render.app.getNameSync(id)
99 var html = (preceding||'') + h('a', {
100 class: href === false ? 'bad' : undefined,
101 href: href !== false ? href : undefined,
102 title: myName || undefined,
103 }, id.length > 50 ? id.slice(0, 8) + '…' : id).outerHTML
104
105 var link = this.render._mentionsByLink[id]
106 if (link && link.type === 'text/x-markdown') {
107 html += h('sup', ' [', h('a', {
108 href: this.render.toUrl('/markdown/' + id),
109 title: 'view rendered markdown'
110 }, 'md'), ']').outerHTML
111 }
112
113 return html
114}
115
116MdRenderer.prototype.code = function (code, lang, escaped) {
117 if (this.render.opts.codeInTextareas) {
118 return h('div', h('textarea', {
119 cols: 80,
120 rows: u.rows(code),
121 style: 'font-family: monospace',
122 innerHTML: escaped ? code : u.escapeHTML(code)
123 })).outerHTML
124 } else {
125 return marked.Renderer.prototype.code.call(this, code, lang, escaped)
126 }
127}
128
129function lexerRenderEmoji(emoji) {
130 var el = this.renderer.render.emoji(emoji)
131 return el && el.outerHTML || el
132}
133
134function Render(app, opts) {
135 this.app = app
136 this.opts = opts
137
138 this.markedOpts = {
139 gfm: true,
140 mentions: true,
141 tables: true,
142 breaks: true,
143 pedantic: false,
144 sanitize: true,
145 smartLists: true,
146 smartypants: false,
147 emoji: lexerRenderEmoji,
148 renderer: new MdRenderer(this),
149 highlight: this.highlight.bind(this),
150 }
151}
152
153Render.prototype.emoji = function (emoji) {
154 var name = ':' + emoji + ':'
155 var link = this._mentions && this._mentions[name]
156 if (link && link.link) {
157 this.app.reverseEmojiNameCache.set(emoji, link.link)
158 return h('img.ssb-emoji', {
159 src: this.opts.img_base + link.link,
160 alt: name
161 + (link.size != null ? ' (' + this.formatSize(link.size) + ')' : ''),
162 height: 17,
163 title: name,
164 })
165 }
166 return name
167}
168
169/* disabled until it can be done safely without breaking html
170function fixSymbols(str) {
171 // Dillo doesn't do fallback fonts, so specifically render fancy characters
172 // with Symbola
173 return str.replace(/[^\u0000-\u00ff]+/, function ($0) {
174 return '<span class="symbol">' + $0 + '</span>'
175 })
176}
177*/
178
179Render.prototype.symbolify = function (text) {
180 var arr = text.split(u.symbolRegex)
181 for (var i = 1; i < arr.length; i += 2) {
182 arr[i] = h('span.symbol', arr[i])
183 }
184 return arr
185}
186
187Render.prototype.markdown = function (text, mentions, opts) {
188 if (!text) return ''
189 var ssbcMd = opts && opts.ssbcMd
190 var mentionsObj = this._mentions = {}
191 var mentionsByLink = this._mentionsByLink = {}
192 if (Array.isArray(mentions)) mentions.forEach(function (link) {
193 if (!link) return
194 else if (link.emoji)
195 mentionsObj[':' + link.name + ':'] = link
196 else if (link.name && link.link && link.link[0] === '@')
197 mentionsObj['@' + link.name] = link.link
198 else if (link.host === 'http://localhost:7777')
199 mentionsObj[link.href] = link.link
200 if (link.link)
201 mentionsByLink[link.link +
202 (link.query && typeof link.query.unbox === 'string' ?
203 '?unbox=' + link.query.unbox.replace(/\s/g, '+') : '') +
204 (link.key ? '#' + link.key : '')] = link
205 })
206 var self = this
207 var out = ssbcMd ? md.block(String(text), {
208 toUrl: function (ref) { return self.toUrl(ref) },
209 imageLink: function (ref) { return self.imageUrl(ref) }
210 }) : marked(u.toString(text), this.markedOpts)
211 delete this._mentions
212 delete this._mentionsByLink
213 return out //fixSymbols(out)
214}
215
216Render.prototype.imageUrl = function (ref) {
217 var m = /^blobstore:(.*)/.exec(ref)
218 if (m) ref = m[1]
219 ref = String(ref).replace(/#/, '%23')
220 return this.opts.img_base + ref
221}
222
223Render.prototype.getImageAlt = function (id, fallback) {
224 var link = this._mentionsByLink[id]
225 if (!link) return fallback
226 var name = String(u.unescapeHTML(link.name || fallback))
227 .replace(/^(video|audio):/, '')
228 return name
229 + (link.type && !/\.\S+$/.test(name) ? ' [' + link.type + ']' : '')
230 + (link.size != null ? ' (' + this.formatSize(link.size) + ')' : '')
231}
232
233Render.prototype.formatSize = function (size) {
234 if (size < 1024) return size + ' B'
235 size /= 1024
236 if (size < 1024) return size.toFixed(2) + ' KB'
237 size /= 1024
238 return size.toFixed(2) + ' MB'
239}
240
241Render.prototype.linkify = function (text, opts) {
242 var ellipsis = opts && opts.ellipsis
243 var arr = text.split(u.ssbRefEncRegex)
244 for (var i = 1; i < arr.length; i += 2) {
245 var id = arr[i]
246 var text = ellipsis && id.length > 8 ? id.substr(0, 8) + '…' : id
247 arr[i] = h('a', {href: this.toUrlEnc(id)}, text)
248 }
249 return arr
250}
251
252Render.prototype.toUrlEnc = function (href) {
253 var url = this.toUrl(href)
254 if (url) return url
255 try { href = decodeURIComponent(href) }
256 catch (e) { return false }
257 return this.toUrl(href)
258}
259
260Render.prototype.toUrl = function (href) {
261 if (!href) return href
262 var mentions = this._mentions
263 if (mentions && href in this._mentions) href = this._mentions[href]
264 if (/^ssb:/.test(href)) href = u.translateFromURI(href) || href
265 if (/^ssb-blob:\/\//.test(href)) {
266 return this.opts.base + 'zip/' + href.substr(11)
267 }
268 switch (href[0]) {
269 case '%':
270 var parts = href.split('?')
271 var hash = parts.shift()
272 var query = parts.join('?')
273 if (!u.isRef(hash)) return false
274 return this.opts.base +
275 (this.opts.encode_msgids ? encodeURIComponent(hash) : hash)
276 + (query ? '?' + query : '')
277 case '@':
278 if (!u.isRef(href.replace(/\?.*/, ''))) return false
279 return this.opts.base + href
280 case '&':
281 var parts = href.split('#')
282 var hash = parts.shift()
283 var key = parts.shift()
284 var fragment = parts.join('#')
285 parts = hash.split('?')
286 hash = parts.shift()
287 query = parts.join('?')
288 if (!u.isRef(hash)) return false
289 return this.opts.blob_base + hash
290 + (query ? '?' + query : '')
291 + (key ? encodeURIComponent('#' + key) : '')
292 + (fragment ? '#' + fragment : '')
293 case '#': return this.opts.base + 'channel/' +
294 encodeURIComponent(href.substr(1).toLowerCase())
295 case '/': return this.opts.base + href.substr(1)
296 case '?': return this.opts.base + 'search?q=' + encodeURIComponent(href)
297 }
298 var m = /^blobstore:(.*)/.exec(href)
299 if (m) return this.opts.blob_base + m[1]
300 if (/^javascript:/.test(href)) return false
301 if (!/^[a-z0-9]*:/.test(href)) return 'http://' + href
302 return href
303}
304
305Render.prototype.lockIcon = function () {
306 return h('span.symbol', '🔒')
307}
308
309Render.prototype.avatarImage = function (link, cb) {
310 var self = this
311 if (!link) return cb(), ''
312 if (typeof link === 'string') link = {link: link}
313 var img = h('img.ssb-avatar-image', {
314 width: 72,
315 alt: ' '
316 })
317 if (link.image) gotAbout(null, link)
318 else self.app.getAbout(link.link, gotAbout)
319 function gotAbout(err, about) {
320 if (err) console.trace(err)
321 img.src = about && about.image ? self.imageUrl(about.image)
322 : self.toUrl('/static/fallback.png')
323 cb()
324 }
325 return img
326}
327
328const errors = lru(100)
329
330function logUniqueError(err) {
331 // Avoid logging the same error many times.
332 if (errors.get(err.stack)) return
333 errors.set(err.stack, true)
334 console.error(err)
335}
336
337Render.prototype.prepareLink = function (link, cb) {
338 if (typeof link === 'string') link = {link: link}
339 if (link.name || !link.link) cb(null, link)
340 else this.app.getAbout(link.link, function (err, about) {
341 // Don't let this function fail, instead, log the error once.
342 if (err) logUniqueError(err)
343 link.name = about && (about.name || about.title) || (link.link.substr(0, 8) + '…')
344 if (link.name && link.name[0] === link.link[0]) {
345 link.name = link.name.substr(1)
346 }
347 cb(null, link)
348 })
349}
350
351Render.prototype.prepareLinks = function (links, cb) {
352 var self = this
353 if (!links) return cb()
354 var done = multicb({pluck: 1})
355 if (Array.isArray(links)) links.forEach(function (link) {
356 self.prepareLink(link, done())
357 })
358 done(cb)
359}
360
361Render.prototype.idLink = function (link, cb) {
362 var self = this
363 if (!link) return cb(), ''
364 var a = h('a', ' ')
365 self.prepareLink(link, function (err, link) {
366 if (err) return cb(err)
367 a.href = self.toUrl(link.link)
368 var sigil = link.link && link.link[0] || '@'
369 var name = link.name || String(link.link).substr(1, 8) + '…'
370 a.childNodes[0].textContent = sigil + name
371 cb()
372 })
373 return a
374}
375
376// %NM8tXGBBDKKcpRbbyd/5uN1p/2OtBMFDylLMDPGoq8Q=.sha256
377var idRegex = /^[A-Za-z0-9._\-+=/]*[A-Za-z0-9_\-+=/]$/
378
379Render.prototype.idLinkCopyable = function (link, cb) {
380 var self = this
381 if (!self.app.copyableIds) return idLink(link, cb)
382 if (!link) return cb(), ''
383 var a = h('a', ' ')
384 self.prepareLink(link, function (err, link) {
385 if (err) return cb(err)
386 a.href = self.toUrl(link.link)
387 var name = link.name || String(link.link).substr(1, 8) + '…'
388 if (idRegex.test(name)) a.childNodes[0].textContent = '@' + name
389 else {
390 var name = link.name.startsWith('~') ? link.name : '@' + link.name
391 a.className = 'id-copyable-link'
392 a.innerHTML = h('span', [
393 h('span.id-deemphasize', '['),
394 h('span.id-name', name),
395 h('span.id-deemphasize', ']'
396 + '(', h('span.id-inner', link.link), ')'),
397 ]).innerHTML
398 }
399 cb()
400 })
401 return a
402}
403
404Render.prototype.privateLine = function (opts, cb) {
405 var recps = opts.recps
406 var isAuthorRecp = opts.isAuthorRecp
407 var done = multicb({pluck: 1, spread: true})
408 var self = this
409 var el = h('div.recps',
410 opts.noLockIcon ? '' : self.lockIcon(),
411 !isAuthorRecp ? [
412 h('span', {title: 'Author is not a recipient'}, '[!]'), ' '
413 ] : '',
414 Array.isArray(recps)
415 ? recps.map(function (recp) {
416 return [' ', self.idLink(recp, done())]
417 }) : '')
418 done(cb)
419 return el
420}
421
422Render.prototype.msgIdLink = function (id, cb) {
423 var self = this
424 var el = h('span')
425 var a = h('a', {href: self.toUrl(id)}, id)
426 self.app.getMsgDecrypted(id, function (err, msg) {
427 if (err) return el.appendChild(u.renderError(err)), cb()
428 var renderMsg = new RenderMsg(self, self.app, msg, {wrap: false, serve: self.serve})
429 renderMsg.title(function (err, title) {
430 if (err) return el.appendChild(u.renderError(err)), cb()
431 a.childNodes[0].textContent = title
432 cb()
433 })
434 })
435 return a
436}
437
438Render.prototype.phMsgLink = function (msg) {
439 var self = this
440 return u.readNext(function (cb) {
441 self.app.unboxMsg(msg, function (err, msg) {
442 if (err) return cb(err)
443 var renderMsg = new RenderMsg(self, self.app, msg, {wrap: false, serve: self.serve})
444 renderMsg.title(function (err, title) {
445 if (err) return cb(err)
446 cb(null, ph('a', {href: self.toUrl(msg.key)}, u.escapeHTML(title)))
447 })
448 })
449 })
450}
451
452Render.prototype.renderMsg = function (msg, opts, cb) {
453 var self = this
454 self.app.filterMsg(msg, opts, function (err, show) {
455 if (err) return cb(err)
456 if (show) new RenderMsg(self, self.app, msg, opts).message(cb)
457 else cb(null, '')
458 })
459}
460
461Render.prototype.renderFeeds = function (opts) {
462 var self = this
463 var limit = Number(opts.limit)
464 return pull(
465 paramap(function (msg, cb) {
466 self.renderMsg(msg, opts, cb)
467 }, 4),
468 pull.filter(Boolean),
469 limit && pull.take(limit)
470 )
471}
472
473Render.prototype.gitCommitBody = function (body) {
474 if (!body) return ''
475 var isMarkdown = !/^# Conflicts:$/m.test(body)
476 return isMarkdown
477 ? h('div', {innerHTML: this.markdown('\n' + body)})
478 : h('pre', this.linkify('\n' + body))
479}
480
481Render.prototype.getName = function (id, cb) {
482 // TODO: consolidate the get name/link functions
483 var self = this
484 switch (id && id[0]) {
485 case '%':
486 return self.app.getMsgDecrypted(id, function (err, msg) {
487 if (err && err.name == 'NotFoundError')
488 return cb(null, String(id).substring(0, 8) + '…(missing)')
489 if (err) return fallback()
490 new RenderMsg(self, self.app, msg, {wrap: false}).title(cb)
491 })
492 case '@': // fallthrough
493 case '&':
494 return self.app.getAbout(id, function (err, about) {
495 if (err || !about || !about.name) return fallback()
496 cb(null, about.name)
497 })
498 default:
499 return cb(null, String(id))
500 }
501 function fallback() {
502 cb(null, String(id).substr(0, 8) + '…')
503 }
504}
505
506Render.prototype.getNameLink = function (id, opts, cb) {
507 if (!cb && typeof opts === 'function') cb = opts, opts = null
508 if (typeof id === 'object' && id !== null && typeof id.link === 'string') {
509 var link = id
510 id = link.link
511 if (typeof link.name === 'string') {
512 return cb(null, h('a', {href: self.toUrl(id)},
513 u.truncate(link.name, length)))
514 }
515 }
516 var length = opts && opts.length || Infinity
517 var self = this
518 self.getName(id, function (err, name) {
519 if (err) return cb(err)
520 cb(null, h('a', {href: self.toUrl(id)}, u.truncate(name, length)))
521 })
522}
523
524Render.prototype.npmAuthorLink = function (author) {
525 if (!author) return
526 var url = u.ifString(author.url)
527 var email = u.ifString(author.email)
528 var name = u.ifString(author.name)
529 var title
530 if (!url && u.isRef(name)) url = name, name = null
531 if (!url && !email) return name || JSON.stringify(author)
532 if (!url && email) url = 'mailto:' + email, email = null
533 if (!name && email) name = email, email = null
534 var feed = u.isRef(url) && url[0] === '@' && url
535 if (feed && name) title = this.app.getNameSync(feed)
536 if (feed && name && name[0] != '@') name = '@' + name
537 if (feed && !name) name = this.app.getNameSync(feed) // TODO: async
538 if (url && !name) name = url
539 var secondaryLink = email && h('a', {href: this.toUrl('mailto:' + email)}, email)
540 return [
541 h('a', {href: this.toUrl(url), title: title}, name),
542 secondaryLink ? [' (', secondaryLink, ')'] : ''
543 ]
544}
545
546// auto-highlight is slow
547var useAutoHighlight = false
548
549Render.prototype.highlight = function (code, lang) {
550 if (code.length > 100000) return u.escapeHTML(code)
551 if (!lang && /^#!\/bin\/[^\/]*sh$/m.test(code)) lang = 'sh'
552 try {
553 return lang
554 ? Highlight.highlight(lang, code).value
555 : useAutoHighlight
556 ? Highlight.highlightAuto(code).value
557 : u.escapeHTML(code)
558 } catch(e) {
559 if (!/^Unknown language/.test(e.message)) console.trace(e)
560 return u.escapeHTML(code)
561 }
562}
563
564Render.prototype.npmPackageMentions = function (links, cb) {
565 var self = this
566 var pkgLinks = u.toArray(links).filter(function (link) {
567 return /^npm:/.test(link.name)
568 })
569 if (pkgLinks.length === 0) return cb(null, '')
570 var done = multicb({pluck: 1})
571 pkgLinks.forEach(function (link) {
572 self.npmPackageMention(link, {}, done())
573 })
574 done(function (err, mentionEls) {
575 cb(null, h('table',
576 h('thead', h('tr',
577 h('th', 'package'),
578 h('th', 'version'),
579 h('th', 'tag'),
580 h('th', 'size'),
581 h('th', 'tarball'),
582 h('th', 'readme')
583 )),
584 h('tbody', mentionEls)
585 ))
586 })
587}
588
589Render.prototype.npmPrebuildMentions = function (links, cb) {
590 var self = this
591 var prebuildLinks = u.toArray(links).filter(function (link) {
592 return /^prebuild:/.test(link.name)
593 })
594 if (prebuildLinks.length === 0) return cb(null, '')
595 var done = multicb({pluck: 1})
596 prebuildLinks.forEach(function (link) {
597 self.npmPrebuildMention(link, {}, done())
598 })
599 done(function (err, mentionEls) {
600 cb(null, h('table',
601 h('thead', h('tr',
602 h('th', 'name'),
603 h('th', 'version'),
604 h('th', 'runtime'),
605 h('th', 'abi'),
606 h('th', 'platform+libc'),
607 h('th', 'arch'),
608 h('th', 'size'),
609 h('th', 'tarball')
610 )),
611 h('tbody', mentionEls)
612 ))
613 })
614}
615
616Render.prototype.npmPackageMention = function (link, opts, cb) {
617 var nameRegex = /'prebuild:{name}-v{version}-{runtime}-v{abi}-{platform}{libc}-{arch}.tar.gz'/
618 var parts = String(link.name).replace(/\.tgz$/, '').split(':')
619 var name = parts[1]
620 var version = parts[2]
621 var distTag = parts[3]
622 var self = this
623 var done = multicb({pluck: 1, spread: true})
624 var base = '/npm/' + (opts.author ? u.escapeId(link.author) + '/' : '')
625 var pathWithAuthor = opts.withAuthor ? '/npm/' +
626 u.escapeId(link.author) +
627 (opts.name ? '/' + opts.name +
628 (opts.version ? '/' + opts.version +
629 (opts.distTag ? '/' + opts.distTag + '/' : '') : '') : '') : ''
630 self.app.getAbout(link.author, done())
631 self.app.getBlobState(link.link, done())
632 done(function (err, about, blobState) {
633 if (err) return cb(err)
634 cb(null, h('tr', [
635 opts.withAuthor ? h('td', h('a', {
636 href: self.toUrl(pathWithAuthor),
637 title: 'publisher'
638 }, about.name || u.truncate(link.author, 8)), ' ') : '',
639 h('td', h('a', {
640 href: self.toUrl(base + name),
641 title: 'package name'
642 }, name), ' '),
643 h('td', version ? [h('a', {
644 href: self.toUrl(base + name + '/' + version),
645 title: 'package version'
646 }, version), ' '] : ''),
647 h('td', distTag ? [h('a', {
648 href: self.toUrl(base + name + '//' + distTag),
649 title: 'dist-tag'
650 }, distTag), ' '] : ''),
651 h('td', {align: 'right'}, link.size != null ? [h('span', {
652 title: 'tarball size'
653 }, self.formatSize(link.size)), ' '] : ''),
654 h('td', typeof link.link === 'string' ? h('code', h('a', {
655 href: self.toUrl('/links/' + link.link),
656 title: 'package tarball'
657 }, link.link.substr(0, 8) + '…')) : ''),
658 h('td',
659 blobState === 'wanted' ?
660 'fetching...'
661 : blobState ? h('a', {
662 href: self.toUrl('/npm-readme/' + encodeURIComponent(link.link)),
663 title: 'package contents'
664 }, 'readme')
665 : self.blobFetchForm(link.link))
666 ]))
667 })
668}
669
670Render.prototype.blobFetchForm = function (id) {
671 return h('form', {action: '', method: 'post'},
672 h('input', {type: 'hidden', name: 'action', value: 'want-blobs'}),
673 h('input', {type: 'hidden', name: 'async_want', value: '1'}),
674 h('input', {type: 'hidden', name: 'blob_ids', value: id}),
675 h('input', {type: 'submit', value: 'fetch'})
676 )
677}
678
679Render.prototype.npmPrebuildNameRegex = /^prebuild:(.*?)-v([0-9]+\.[0-9]+.*?)-(.*?)-v(.*?)-(.*?)-(.*?)\.tar\.gz$/
680
681Render.prototype.npmPrebuildMention = function (link, opts, cb) {
682 var m = this.npmPrebuildNameRegex.exec(link.name)
683 if (!m) return cb(null, '')
684 var name = m[1], version = m[2], runtime = m[3],
685 abi = m[4], platformlibc = m[5], arch = m[6]
686 var self = this
687 var done = multicb({pluck: 1, spread: true})
688 var base = '/npm-prebuilds/' + (opts.author ? u.escapeId(link.author) + '/' : '')
689 self.app.getAbout(link.author, done())
690 self.app.getBlobState(link.link, done())
691 done(function (err, about, blobState) {
692 if (err) return cb(err)
693 cb(null, h('tr', [
694 opts.withAuthor ? h('td', h('a', {
695 href: self.toUrl(link.author)
696 }, about.name || u.truncate(link.author, 8)), ' ') : '',
697 h('td', h('a', {
698 href: self.toUrl(base + name)
699 }, name), ' '),
700 h('td', h('a', {
701 href: self.toUrl('/npm/' + name + '/' + version)
702 }, version), ' '),
703 h('td', runtime, ' '),
704 h('td', abi, ' '),
705 h('td', platformlibc, ' '),
706 h('td', arch, ' '),
707 h('td', {align: 'right'}, link.size != null ? [
708 self.formatSize(link.size), ' '
709 ] : ''),
710 h('td', typeof link.link === 'string' ? h('code', h('a', {
711 href: self.toUrl('/links/' + link.link)
712 }, link.link.substr(0, 8) + '…')) : ''),
713 h('td',
714 blobState === 'wanted' ?
715 'fetching...'
716 : blobState ? ''
717 : self.blobFetchForm(link.link))
718 ]))
719 })
720}
721
722Render.prototype.friendsList = function (prefix) {
723 prefix = prefix || '/'
724 var self = this
725 return pull(
726 paramap(function (item, cb) {
727 if (typeof item === 'string') item = {feed: item}
728 var id = item.feed
729 self.app.getAbout(item.feed, function (err, about) {
730 var name = about && about.name || id.substr(0, 8) + '…'
731 cb(null, [
732 h('a', {href: self.toUrl(prefix + id)}, name),
733 item.msg ? h('a', {href: self.toUrl(item.msg.key)}, '₁') : '',
734 item.msg2 ? h('a', {href: self.toUrl(item.msg2.key)}, '₂') : ''
735 ])
736 })
737 }, 8),
738 function (read) {
739 var count = 0
740 var ended
741 return function (abort, cb) {
742 if (ended) return cb(ended)
743 read(abort, function (end, el) {
744 if (end === true) {
745 ended = true
746 cb(null, '(' + count + ')')
747 } else {
748 count++
749 cb(end, u.toHTML(el) + ' ')
750 }
751 })
752 }
753 }
754 )
755}
756
757Render.prototype.textEditDiffTable = function (oldMsg, newMsg) {
758 var oldC = oldMsg && oldMsg.value.content || {}
759 var newC = newMsg && newMsg.value.content || {}
760 var oldText = String(oldC.text || oldC.description || '')
761 var newText = String(newC.text || newC.description || '')
762 var diff = Diff.structuredPatch('', '', oldText, newText)
763 var self = this
764 // note: this structure is duplicated in lib/render.js
765 return h('table', {class: 'diff-table'}, [
766 diff.hunks.map(function (hunk) {
767 var oldLine = hunk.oldStart
768 var newLine = hunk.newStart
769 return [
770 h('tr', [
771 h('td', {colspan: 2}),
772 h('td', {colspan: 2}, h('pre',
773 '@@ -' + oldLine + ',' + hunk.oldLines + ' ' +
774 '+' + newLine + ',' + hunk.newLines + ' @@'))
775 ]),
776 hunk.lines.map(function (line) {
777 var s = line[0]
778 if (s == '\\') return
779 var lineNums = [s == '+' ? '' : oldLine++, s == '-' ? '' : newLine++]
780 return [
781 h('tr', {
782 class: s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : ''
783 }, [
784 lineNums.map(function (num, i) {
785 return h('td', String(num))
786 }),
787 h('td', {class: 'diff-sigil'}, h('code', s)),
788 h('td', {class: 'diff-line'}, {innerHTML:
789 u.unwrapP(self.markdown(line.substr(1),
790 s == '-' ? oldC.mentions : newC.mentions))
791 })
792 ])
793 ]
794 })
795 ]
796 })
797 ])
798}
799
800

Built with git-ssb-web