Files: 7cbc5c587105a7add08d250458303c47bcd0e57a / lib / render.js
6414 bytesRaw
1 | var fs = require('fs') |
2 | var path = require('path') |
3 | var pull = require('pull-stream') |
4 | var cat = require('pull-cat') |
5 | var paramap = require('pull-paramap') |
6 | var h = require('hyperscript') |
7 | var marked = require('ssb-marked') |
8 | var emojis = require('emoji-named-characters') |
9 | var qs = require('querystring') |
10 | var u = require('./util') |
11 | var multicb = require('multicb') |
12 | var RenderMsg = require('./render-msg') |
13 | |
14 | module.exports = Render |
15 | |
16 | function MdRenderer(render) { |
17 | marked.Renderer.call(this, {}) |
18 | this.render = render |
19 | } |
20 | MdRenderer.prototype = new marked.Renderer() |
21 | |
22 | MdRenderer.prototype.urltransform = function (href) { |
23 | return this.render.toUrl(href) |
24 | } |
25 | |
26 | MdRenderer.prototype.image = function (href, title, text) { |
27 | href = this.render.imageUrl(href) |
28 | var name = text || title |
29 | if (name) href += '?name=' + encodeURIComponent(name) |
30 | return h('img', { |
31 | src: href, |
32 | alt: text, |
33 | title: title || undefined |
34 | }).outerHTML |
35 | } |
36 | |
37 | MdRenderer.prototype.link = function(href, title, text) { |
38 | href = this.urltransform(href) |
39 | var name = href && /^\/(&|%26)/.test(href) && (title || text) |
40 | return '<a' |
41 | + (href !== false |
42 | ? ' href="' + href + '"' |
43 | : ' class="bad"') |
44 | + (title ? ' title="' + title + '"' : '') |
45 | + (name ? ' download="' + encodeURIComponent(name) + '"' : '') |
46 | + '>' + text + '</a>' |
47 | }; |
48 | |
49 | |
50 | function lexerRenderEmoji(emoji) { |
51 | var el = this.renderer.render.emoji(emoji) |
52 | return el && el.outerHTML || el |
53 | } |
54 | |
55 | function Render(app, opts) { |
56 | this.app = app |
57 | this.opts = opts |
58 | |
59 | this.markedOpts = { |
60 | gfm: true, |
61 | mentions: true, |
62 | tables: true, |
63 | breaks: true, |
64 | pedantic: false, |
65 | sanitize: true, |
66 | smartLists: true, |
67 | smartypants: false, |
68 | emoji: lexerRenderEmoji, |
69 | renderer: new MdRenderer(this), |
70 | } |
71 | } |
72 | |
73 | Render.prototype.emoji = function (emoji) { |
74 | var name = ':' + emoji + ':' |
75 | return emoji in emojis ? |
76 | h('img.ssb-emoji', { |
77 | src: this.opts.emoji_base + emoji + '.png', |
78 | alt: name, |
79 | title: name, |
80 | }) : name |
81 | } |
82 | |
83 | /* disabled until it can be done safely without breaking html |
84 | function fixSymbols(str) { |
85 | // Dillo doesn't do fallback fonts, so specifically render fancy characters |
86 | // with Symbola |
87 | return str.replace(/[^\u0000-\u00ff]+/, function ($0) { |
88 | return '<span class="symbol">' + $0 + '</span>' |
89 | }) |
90 | } |
91 | */ |
92 | |
93 | Render.prototype.markdown = function (text, mentions) { |
94 | if (!text) return '' |
95 | var mentionsObj = this._mentions = {} |
96 | if (Array.isArray(mentions)) mentions.forEach(function (link) { |
97 | if (!link) return |
98 | else if (link.name) |
99 | mentionsObj['@' + link.name] = link.link |
100 | else if (link.host === 'http://localhost:7777') |
101 | mentionsObj[link.href] = link.link |
102 | }) |
103 | var out = marked(String(text), this.markedOpts) |
104 | delete this._mentions |
105 | return out //fixSymbols(out) |
106 | } |
107 | |
108 | Render.prototype.imageUrl = function (ref) { |
109 | var m = /^blobstore:(.*)/.exec(ref) |
110 | if (m) ref = m[1] |
111 | return this.opts.img_base + ref |
112 | } |
113 | |
114 | Render.prototype.toUrl = function (href) { |
115 | if (!href) return href |
116 | var mentions = this._mentions |
117 | if (mentions && href in this._mentions) href = this._mentions[href] |
118 | if (/^ssb:\/\//.test(href)) href = href.substr(6) |
119 | switch (href[0]) { |
120 | case '%': |
121 | if (!u.isRef(href)) return false |
122 | return this.opts.base + encodeURIComponent(href) |
123 | case '@': |
124 | if (!u.isRef(href)) return false |
125 | return this.opts.base + href |
126 | case '&': |
127 | if (!u.isRef(href)) return false |
128 | return this.opts.blob_base + href |
129 | case '#': return this.opts.base + encodeURIComponent(href) |
130 | case '/': return this.opts.base + href.substr(1) |
131 | case '?': return this.opts.base + 'search?q=' + encodeURIComponent(href) |
132 | } |
133 | var m = /^blobstore:(.*)/.exec(href) |
134 | if (m) return this.opts.blob_base + m[1] |
135 | if (/^javascript:/.test(href)) return false |
136 | return href |
137 | } |
138 | |
139 | Render.prototype.lockIcon = function () { |
140 | return this.emoji('lock') |
141 | } |
142 | |
143 | Render.prototype.avatarImage = function (link, cb) { |
144 | var self = this |
145 | if (!link) return cb(), '' |
146 | if (typeof link === 'string') link = {link: link} |
147 | var img = h('img.ssb-avatar-image', { |
148 | alt: ' ' |
149 | }) |
150 | if (link.image) gotAbout(null, link) |
151 | else self.app.getAbout(link.link, gotAbout) |
152 | function gotAbout(err, about) { |
153 | if (err) return cb(err) |
154 | if (!about.image) img.src = self.toUrl('/static/fallback.png') |
155 | else img.src = self.imageUrl(about.image) |
156 | cb() |
157 | } |
158 | return img |
159 | } |
160 | |
161 | Render.prototype.prepareLink = function (link, cb) { |
162 | if (typeof link === 'string') link = {link: link} |
163 | if (link.name || !link.link) cb(null, link) |
164 | else this.app.getAbout(link.link, function (err, about) { |
165 | if (err) return cb(err) |
166 | link.name = about.name || (link.link.substr(0, 8) + '…') |
167 | if (link.name && link.name[0] === link.link[0]) { |
168 | link.name = link.name.substr(1) |
169 | } |
170 | cb(null, link) |
171 | }) |
172 | } |
173 | |
174 | Render.prototype.prepareLinks = function (links, cb) { |
175 | var self = this |
176 | if (!links) return cb() |
177 | var done = multicb({pluck: 1}) |
178 | if (Array.isArray(links)) links.forEach(function (link) { |
179 | self.prepareLink(link, done()) |
180 | }) |
181 | done(cb) |
182 | } |
183 | |
184 | Render.prototype.idLink = function (link, cb) { |
185 | var self = this |
186 | if (!link) return cb(), '' |
187 | var a = h('a', ' ') |
188 | self.prepareLink(link, function (err, link) { |
189 | if (err) return cb(err) |
190 | a.href = self.toUrl(link.link) |
191 | a.childNodes[0].textContent = '@' + link.name |
192 | cb() |
193 | }) |
194 | return a |
195 | } |
196 | |
197 | Render.prototype.privateLine = function (recps, cb) { |
198 | var done = multicb({pluck: 1, spread: true}) |
199 | var self = this |
200 | var el = h('div.recps', |
201 | self.lockIcon(), |
202 | Array.isArray(recps) |
203 | ? recps.map(function (recp) { |
204 | return [' ', self.idLink(recp, done())] |
205 | }) : '') |
206 | done(cb) |
207 | return el |
208 | } |
209 | |
210 | Render.prototype.msgLink = function (msg, cb) { |
211 | var self = this |
212 | var el = h('span') |
213 | var a = h('a', {href: self.toUrl(msg.key)}, msg.key) |
214 | self.app.unboxMsg(msg, function (err, msg) { |
215 | if (err) return el.appendChild(u.renderError(err)), cb() |
216 | var renderMsg = new RenderMsg(self, self.app, msg, {wrap: false}) |
217 | renderMsg.title(function (err, title) { |
218 | if (err) return el.appendChild(u.renderError(err)), cb() |
219 | a.childNodes[0].textContent = title |
220 | cb() |
221 | }) |
222 | }) |
223 | return a |
224 | } |
225 | |
226 | Render.prototype.renderMsg = function (msg, raw, cb) { |
227 | new RenderMsg(this, this.app, msg).message(raw, cb) |
228 | } |
229 | |
230 | Render.prototype.renderFeeds = function (raw) { |
231 | var self = this |
232 | return paramap(function (msg, cb) { |
233 | self.renderMsg(msg, raw, cb) |
234 | }, 4) |
235 | } |
236 |
Built with git-ssb-web