var fs = require('fs')
var path = require('path')
var pull = require('pull-stream')
var cat = require('pull-cat')
var paramap = require('pull-paramap')
var h = require('hyperscript')
var marked = require('ssb-marked')
var emojis = require('emoji-named-characters')
var qs = require('querystring')
var u = require('./util')
var multicb = require('multicb')
var RenderMsg = require('./render-msg')
module.exports = Render
function MdRenderer(render) {
marked.Renderer.call(this, {})
this.render = render
}
MdRenderer.prototype = new marked.Renderer()
MdRenderer.prototype.urltransform = function (href) {
return this.render.toUrl(href)
}
MdRenderer.prototype.image = function (ref, title, text) {
var href = this.render.imageUrl(ref)
var name = text || title
if (name) href += '?name=' + encodeURIComponent(name)
return h('img', {
src: href,
alt: this.render.getImageAlt(ref, text),
title: title || undefined
}).outerHTML
}
MdRenderer.prototype.link = function(href, title, text) {
href = this.urltransform(href)
var name = href && /^\/(&|%26)/.test(href) && (title || text)
return '' + text + ''
};
function lexerRenderEmoji(emoji) {
var el = this.renderer.render.emoji(emoji)
return el && el.outerHTML || el
}
function Render(app, opts) {
this.app = app
this.opts = opts
this.markedOpts = {
gfm: true,
mentions: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false,
emoji: lexerRenderEmoji,
renderer: new MdRenderer(this),
}
}
Render.prototype.emoji = function (emoji) {
var name = ':' + emoji + ':'
return emoji in emojis ?
h('img.ssb-emoji', {
src: this.opts.emoji_base + emoji + '.png',
alt: name,
height: 17,
title: name,
}) : name
}
/* disabled until it can be done safely without breaking html
function fixSymbols(str) {
// Dillo doesn't do fallback fonts, so specifically render fancy characters
// with Symbola
return str.replace(/[^\u0000-\u00ff]+/, function ($0) {
return '' + $0 + ''
})
}
*/
Render.prototype.markdown = function (text, mentions) {
if (!text) return ''
var mentionsObj = this._mentions = {}
var mentionsByLink = this._mentionsByLink = {}
if (Array.isArray(mentions)) mentions.forEach(function (link) {
if (!link) return
else if (link.name)
mentionsObj['@' + link.name] = link.link
else if (link.host === 'http://localhost:7777')
mentionsObj[link.href] = link.link
if (link.link)
mentionsByLink[link.link] = link
})
var out = marked(String(text), this.markedOpts)
delete this._mentions
delete this._mentionsByLink
return out //fixSymbols(out)
}
Render.prototype.imageUrl = function (ref) {
var m = /^blobstore:(.*)/.exec(ref)
if (m) ref = m[1]
return this.opts.img_base + ref
}
Render.prototype.getImageAlt = function (id, fallback) {
var link = this._mentionsByLink[id]
if (!link) return fallback
var name = link.name || fallback
return name
+ (link.size != null ? ' (' + this.formatSize(link.size) + ')' : '')
}
Render.prototype.formatSize = function (size) {
if (size < 1024) return size + ' B'
size /= 1024
if (size < 1024) return size.toFixed(2) + ' KB'
size /= 1024
return size.toFixed(2) + ' MB'
}
Render.prototype.toUrl = function (href) {
if (!href) return href
var mentions = this._mentions
if (mentions && href in this._mentions) href = this._mentions[href]
if (/^ssb:\/\//.test(href)) href = href.substr(6)
switch (href[0]) {
case '%':
if (!u.isRef(href)) return false
return this.opts.base + encodeURIComponent(href)
case '@':
if (!u.isRef(href)) return false
return this.opts.base + href
case '&':
if (!u.isRef(href)) return false
return this.opts.blob_base + href
case '#': return this.opts.base + 'channel/' +
encodeURIComponent(href.substr(1))
case '/': return this.opts.base + href.substr(1)
case '?': return this.opts.base + 'search?q=' + encodeURIComponent(href)
}
var m = /^blobstore:(.*)/.exec(href)
if (m) return this.opts.blob_base + m[1]
if (/^javascript:/.test(href)) return false
return href
}
Render.prototype.lockIcon = function () {
return this.emoji('lock')
}
Render.prototype.avatarImage = function (link, cb) {
var self = this
if (!link) return cb(), ''
if (typeof link === 'string') link = {link: link}
var img = h('img.ssb-avatar-image', {
width: 72,
alt: ' '
})
if (link.image) gotAbout(null, link)
else self.app.getAbout(link.link, gotAbout)
function gotAbout(err, about) {
if (err) return cb(err)
if (!about.image) img.src = self.toUrl('/static/fallback.png')
else img.src = self.imageUrl(about.image)
cb()
}
return img
}
Render.prototype.prepareLink = function (link, cb) {
if (typeof link === 'string') link = {link: link}
if (link.name || !link.link) cb(null, link)
else this.app.getAbout(link.link, function (err, about) {
if (err) return cb(null, link)
link.name = about.name || (link.link.substr(0, 8) + '…')
if (link.name && link.name[0] === link.link[0]) {
link.name = link.name.substr(1)
}
cb(null, link)
})
}
Render.prototype.prepareLinks = function (links, cb) {
var self = this
if (!links) return cb()
var done = multicb({pluck: 1})
if (Array.isArray(links)) links.forEach(function (link) {
self.prepareLink(link, done())
})
done(cb)
}
Render.prototype.idLink = function (link, cb) {
var self = this
if (!link) return cb(), ''
var a = h('a', ' ')
self.prepareLink(link, function (err, link) {
if (err) return cb(err)
a.href = self.toUrl(link.link)
var sigil = link.link && link.link[0] || '@'
a.childNodes[0].textContent = sigil + link.name
cb()
})
return a
}
Render.prototype.privateLine = function (recps, cb) {
var done = multicb({pluck: 1, spread: true})
var self = this
var el = h('div.recps',
self.lockIcon(),
Array.isArray(recps)
? recps.map(function (recp) {
return [' ', self.idLink(recp, done())]
}) : '')
done(cb)
return el
}
Render.prototype.msgLink = function (msg, cb) {
var self = this
var el = h('span')
var a = h('a', {href: self.toUrl(msg.key)}, msg.key)
self.app.unboxMsg(msg, function (err, msg) {
if (err) return el.appendChild(u.renderError(err)), cb()
var renderMsg = new RenderMsg(self, self.app, msg, {wrap: false})
renderMsg.title(function (err, title) {
if (err) return el.appendChild(u.renderError(err)), cb()
a.childNodes[0].textContent = title
cb()
})
})
return a
}
Render.prototype.renderMsg = function (msg, opts, cb) {
new RenderMsg(this, this.app, msg, opts).message(cb)
}
Render.prototype.renderFeeds = function (opts) {
var self = this
return paramap(function (msg, cb) {
self.renderMsg(msg, opts, cb)
}, 4)
}
Render.prototype.getName = function (id, cb) {
// TODO: consolidate the get name/link functions
var self = this
switch (id && id[0]) {
case '%':
return self.app.getMsgDecrypted(id, function (err, msg) {
if (err && err.name == 'NotFoundError')
return cb(null, String(id).substring(0, 8) + '…(missing)')
if (err) return fallback()
new RenderMsg(self, self.app, msg, {wrap: false}).title(cb)
})
case '@': // fallthrough
case '&':
return self.app.getAbout(id, function (err, about) {
if (err || !about || !about.name) return fallback()
cb(null, about.name)
})
default:
return cb(null, String(id))
}
function fallback() {
cb(null, String(id).substr(0, 8) + '…')
}
}
Render.prototype.getNameLink = function (id, cb) {
var self = this
self.getName(id, function (err, name) {
if (err) return cb(err)
cb(null, h('a', {href: self.toUrl(id)}, name))
})
}