Files: feb9d3ba55bb5896bdc42c4579acc0780735f4dc / util.js
6165 bytesRaw
1 | var URL = require('url') |
2 | var QS = require('qs') |
3 | var hyperscript = require('hyperscript') |
4 | var cpara = require('cont').para |
5 | var pull = require('pull-stream') |
6 | var paramap = require('pull-paramap') |
7 | var nested = require('libnested') |
8 | var renderer = require('ssb-markdown') |
9 | var ref = require('ssb-ref') |
10 | var htmlEscape = require('html-escape') |
11 | function isFunction (f) { |
12 | return 'function' === typeof f |
13 | } |
14 | |
15 | var isArray = Array.isArray |
16 | |
17 | function isEmpty (e) { |
18 | for(var k in e) return false |
19 | return true |
20 | } |
21 | |
22 | function isString (s) { |
23 | return 'string' === typeof s |
24 | } |
25 | |
26 | function cleanOpts (opts) { |
27 | return (function copy (opts) { |
28 | var o = undefined |
29 | if(opts && 'object' === typeof opts) |
30 | for(var k in opts) { |
31 | if('undefined' !== typeof opts[k]) { |
32 | var v = copy(opts[k]) |
33 | if('undefined' !== typeof v) { |
34 | o = o || {} |
35 | o[k] = v |
36 | } |
37 | } |
38 | } |
39 | else |
40 | return opts //a string, or other value |
41 | return o |
42 | })(opts) |
43 | } |
44 | |
45 | function encodeOpts (opts) { |
46 | opts = cleanOpts(opts) |
47 | return opts ? '?'+QS.stringify(opts) : '' |
48 | } |
49 | |
50 | exports.cleanOpts = cleanOpts |
51 | |
52 | exports.toUrl = function toUrl(path, opts) { |
53 | return '/'+( |
54 | Array.isArray(path) ? path.join('/') : ''+path |
55 | ) + encodeOpts(opts) |
56 | } |
57 | exports.h = function () { |
58 | return [].slice.call(arguments) |
59 | } |
60 | |
61 | function toCont (f) { |
62 | if(f.length === 1) return function (cb) { |
63 | f(function (err, hs) { |
64 | exports.toHTML(hs)(cb) |
65 | }) |
66 | } |
67 | else if(f.length === 2) |
68 | return function (cb) { |
69 | pull( |
70 | f, |
71 | paramap(function (e, cb) { |
72 | exports.toHTML(e)(cb) |
73 | }, 32), |
74 | pull.collect(cb) |
75 | ) |
76 | } |
77 | } |
78 | |
79 | function flatten (a) { |
80 | var _a = [] |
81 | for(var i = 0; i < a.length; i++) |
82 | if(isArray(a[i]) && !isString(a[i][0])) |
83 | _a = _a.concat(flatten(a[i])) |
84 | else |
85 | _a.push(a[i]) |
86 | return _a |
87 | } |
88 | |
89 | |
90 | //even better would be streaming html, |
91 | //not just into arrays. |
92 | var k = 0 |
93 | exports.toHTML = function toHTML (hs) { |
94 | return function (cb) { |
95 | if(!isFunction(cb)) throw new Error('cb must be a function, was:'+cb) |
96 | var called = false |
97 | var C = ( |
98 | isFunction(hs) ? toCont(hs) |
99 | : isArray(hs) ? cpara(hs.map(toHTML)) |
100 | : function (cb) { |
101 | if(!called) { |
102 | called = true |
103 | cb(null, hs) |
104 | } |
105 | else |
106 | throw new Error('called twice') |
107 | } |
108 | ) |
109 | |
110 | C(function (err, val) { |
111 | if(err) cb(err) |
112 | else if(isArray(val) && isString(val[0])) { |
113 | cb(null, hyperscript.apply(null, flatten(val))) |
114 | } else |
115 | cb(null, val) |
116 | }) |
117 | } |
118 | } |
119 | |
120 | exports.createHiddenInputs = function createHiddenInputs (meta, _path) { |
121 | _path = _path ? [].concat(_path) : [] |
122 | var hidden = [] |
123 | nested.each(meta, function (value, path) { |
124 | if(value !== undefined) |
125 | hidden.push(['input', { |
126 | name: _path.concat(path).map(function (e) { return '['+e+']' }).join(''), |
127 | value: value, |
128 | type: 'hidden' |
129 | }]) |
130 | }, true) |
131 | return hidden |
132 | } |
133 | |
134 | var cacheId = exports.cacheId = function (id) { |
135 | if('string' === typeof id) |
136 | return '_'+Buffer.from(id.substring(1, 12), 'base64').toString('hex') |
137 | else |
138 | return 'R'+id.lte+'-'+(id.lte - id.gte) |
139 | } |
140 | exports.cacheTag = function (url, id, time) { |
141 | if(time) |
142 | return ['link', { |
143 | rel: 'partial-refresh', href: url, id: cacheId(id), 'data-cache': ''+time |
144 | }] |
145 | } |
146 | |
147 | |
148 | function renderEmoji (emoji, url) { |
149 | if (!url) return ':' + emoji + ':' |
150 | return ` |
151 | <img |
152 | src="${htmlEscape(url)}" |
153 | alt=":${htmlEscape(emoji)}:" |
154 | title=":${htmlEscape(emoji)}:" |
155 | class="emoji" |
156 | > |
157 | ` |
158 | } |
159 | |
160 | //copied from patchwork |
161 | exports.markdown = function markdown (content) { |
162 | if (typeof content === 'string') { content = { text: content } } |
163 | var mentions = {} |
164 | var typeLookup = {} |
165 | var emojiMentions = {} |
166 | if (Array.isArray(content.mentions)) { |
167 | content.mentions.forEach(function (link) { |
168 | if (link && link.link && link.type) { |
169 | typeLookup[link.link] = link.type |
170 | } |
171 | if (link && link.name && link.link) { |
172 | if (link.emoji) { |
173 | // handle custom emoji |
174 | emojiMentions[link.name] = link.link |
175 | } else { |
176 | // handle old-style patchwork v2 mentions (deprecated) |
177 | mentions['@' + link.name] = link.link |
178 | } |
179 | } |
180 | }) |
181 | } |
182 | |
183 | var blobsUrl = '/blobs/get/' |
184 | var emojiUrl = '/img/emoji/' |
185 | |
186 | function id2Url (id) { |
187 | return ( |
188 | ref.isMsg(id) ? '/thread?id='+encodeURIComponent(id) |
189 | : ref.isBlobLink(id) ? blobsUrl+encodeURIComponent(id) |
190 | : ref.isFeed(id) ? '/public?author='+encodeURIComponent(id) |
191 | : id[0] == '#' ? '/public?channel='+id.substring(1) |
192 | : id |
193 | ) |
194 | } |
195 | |
196 | return ['div.Markdown', { |
197 | innerHTML: renderer.block(content.text, { |
198 | emoji: (emoji) => { |
199 | var url = emojiMentions[emoji] |
200 | ? blobsUrl + emojiMentions[emoji] |
201 | : emojiUrl + emoji + '.png' |
202 | return renderEmoji(emoji, url) |
203 | }, |
204 | toUrl: (id) => { |
205 | var link = ref.parseLink(id) |
206 | if (link && ref.isBlob(link.link)) { |
207 | var url = blobsUrl+link.link |
208 | var query = {} |
209 | if (link.query && link.query.unbox) query['unbox'] = link.query.unbox |
210 | if (typeLookup[link.link]) query['contentType'] = typeLookup[link.link] |
211 | return url + '?' + QS.stringify(query) |
212 | } else if (link || id.startsWith('#') || id.startsWith('?')) { |
213 | return id2Url(id) |
214 | } else if (mentions[id]) { |
215 | // handle old-style patchwork v2 mentions (deprecated) |
216 | return id2Url(mentions[id]) |
217 | } |
218 | return false |
219 | }, |
220 | imageLink: function (id) { |
221 | return id2Url(id) |
222 | } |
223 | }) |
224 | }] |
225 | } |
226 | |
227 | exports.createRenderer = function (render) { |
228 | return function (sbot) { |
229 | return function (opts, apply) { |
230 | if(opts.id && ref.isMsgLink(opts.id)) |
231 | return function (cb) { |
232 | sbot.get({id:opts.id, private: true}, function (err, msg) { |
233 | if(err) return cb(err) |
234 | var data = {key: opts.id, value: msg, timestamp: msg.timestamp || Date.now() } |
235 | cb(null, render(data, apply)) |
236 | }) |
237 | } |
238 | else if(opts.key && opts.value) |
239 | return render(opts, apply) |
240 | } |
241 | } |
242 | } |
243 | |
244 | |
245 | |
246 | |
247 | |
248 | |
249 | |
250 | |
251 | |
252 | |
253 | |
254 |
Built with git-ssb-web