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