diff options
Diffstat (limited to 'lib/render.js')
-rw-r--r-- | lib/render.js | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/lib/render.js b/lib/render.js new file mode 100644 index 0000000..135f3b3 --- /dev/null +++ b/lib/render.js @@ -0,0 +1,203 @@ +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 (href, title, text) { + return h('img', { + src: this.render.imageUrl(href), + alt: text, + title: title || undefined + }).outerHTML +} + +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, + title: name, + height: 16, + width: 16 + }) : name +} + +Render.prototype.markdown = function (text, mentions) { + var mentionsObj = this._mentions = {} + if (Array.isArray(mentions)) mentions.forEach(function (link) { + if (link && link.name) mentionsObj['@' + link.name] = link.link + }) + var out = marked((text || '').toString(), this.markedOpts) + delete this._mentions + return out +} + +Render.prototype.imageUrl = function (ref) { + return this.opts.img_base + ref +} + +Render.prototype.toUrl = function (href) { + if (!href) return href + var mentions = this._mentions + if (mentions && href in this._mentions) href = this._mentions[href] + switch (href[0]) { + case '%': return this.opts.base + encodeURIComponent(href) + case '@': + if (!u.isRef(href)) return false + return this.opts.base + href + case '&': return this.opts.blob_base + href + case '#': return this.opts.base + encodeURIComponent(href) + case '/': return this.opts.base + href.substr(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', { + alt: link.link + }) + 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(err) + link.name = about.name + 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) + a.childNodes[0].textContent = '@' + 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.publish = function (content, cb) { + var self = this + + var el = h('div') + self.app.publish(content, function (err, msg) { + if (err) return el.appendChild(u.renderError(err)), cb() + self.app.unboxMsg(msg, function (err, msg) { + if (err) return el.appendChild(u.renderError(err)), cb() + self.renderMsg(msg, false, function (err, msgEl) { + if (err) msgEl = [ + h('a', {href: self.toUrl(msg.key)}, msg.key), + u.renderError(err)] + el.appendChild(h('div', + 'published:', + h('table.ssb-msgs', msgEl) + )) + cb() + }) + }) + }) + return el +} + +Render.prototype.renderMsg = function (msg, raw, cb) { + new RenderMsg(this, this.app, msg).message(raw, cb) +} + +Render.prototype.renderFeeds = function (raw) { + var self = this + return paramap(function (msg, cb) { + self.renderMsg(msg, raw, cb) + }, 4) +} |