Files: 227a1624073790b244e01bf9e1efc9429a6e0feb / lib / util.js
6313 bytesRaw
1 | var pull = require('pull-stream') |
2 | var h = require('pull-hyperscript') |
3 | var paramap = require('pull-paramap') |
4 | var Highlight = require('highlight.js') |
5 | var removeMd = require('remove-markdown') |
6 | var u = exports |
7 | |
8 | u.imgMimes = { |
9 | png: 'image/png', |
10 | jpeg: 'image/jpeg', |
11 | jpg: 'image/jpeg', |
12 | gif: 'image/gif', |
13 | tif: 'image/tiff', |
14 | svg: 'image/svg+xml', |
15 | xcf: 'image/x-xcf', |
16 | ico: 'image/x-icon', |
17 | bmp: 'image/bmp' |
18 | } |
19 | |
20 | u.getExtension = function(filename) { |
21 | var m = /\.([^.]+)$/.exec(filename) |
22 | return m && m[1] |
23 | } |
24 | |
25 | u.readNext = function (fn) { |
26 | var next |
27 | return function (end, cb) { |
28 | if (next) return next(end, cb) |
29 | fn(function (err, _next) { |
30 | if (err) return cb(err) |
31 | next = _next |
32 | next(null, cb) |
33 | }) |
34 | } |
35 | } |
36 | |
37 | u.readOnce = function (fn) { |
38 | var ended |
39 | return function (end, cb) { |
40 | fn(function (err, data) { |
41 | if (err || ended) return cb(err || ended) |
42 | ended = true |
43 | cb(null, data) |
44 | }) |
45 | } |
46 | } |
47 | |
48 | u.when = function (bool, elFunction) { |
49 | return bool |
50 | ? elFunction() |
51 | : pull.once('') |
52 | } |
53 | |
54 | u.sourceMap = function (source, fn) { |
55 | return pull( |
56 | source, |
57 | pull.filter(Boolean), |
58 | pull.map(fn) |
59 | ) |
60 | } |
61 | |
62 | u.paraSourceMap = function paraSourceMap (source, fn) { |
63 | return pull( |
64 | pull.values(source), |
65 | paramap(fn, 8) |
66 | ) |
67 | } |
68 | |
69 | u.escape = function (str) { |
70 | return String(str) |
71 | .replace(/&/g, '&') |
72 | .replace(/</g, '<') |
73 | .replace(/>/g, '>') |
74 | .replace(/"/g, '"') |
75 | } |
76 | |
77 | u.encodeLink = function (url) { |
78 | if (!Array.isArray(url)) url = [url] |
79 | return '/' + url.map(encodeURIComponent).join('/') |
80 | } |
81 | |
82 | u.link = function (parts, text, raw, props) { |
83 | if (text == null) text = parts[parts.length-1] |
84 | if (!raw) text = u.escape(text) |
85 | return '<a href="' + u.encodeLink(parts) + '"' + |
86 | (props ? ' ' + props : '') + |
87 | '>' + text + '</a>' |
88 | } |
89 | |
90 | u.timestamp = function (time, req) { |
91 | time = Number(time) |
92 | var d = new Date(time) |
93 | return '<span title="' + time + '">' + |
94 | d.toLocaleString(req._locale) + '</span>' |
95 | } |
96 | |
97 | u.nav = function (links, page, after) { |
98 | return h('nav', [ |
99 | u.sourceMap(pull.values(links), link => { |
100 | return h('a', { |
101 | href: (typeof link[0] == 'string') ? link[0] : u.encodeLink(link[0]), |
102 | class: (link[2] == page) ? 'active' : '' |
103 | }, |
104 | link[1]) |
105 | }), |
106 | pull.once(after || '') |
107 | ]) |
108 | } |
109 | |
110 | u.hiddenInputs = function (values) { |
111 | return Object.keys(values).map(function (key) { |
112 | return '<input type="hidden"' + |
113 | ' name="' + u.escape(key) + '"' + |
114 | ' value="' + u.escape(values[key]) + '"/>' |
115 | }).join('') |
116 | } |
117 | |
118 | u.highlight = function(code, lang) { |
119 | if (!lang && /^#!\/bin\/[^\/]*sh$/m.test(code)) lang = 'sh' |
120 | if (!lang || code.length > 100000) return u.escape(code) |
121 | // auto highlighting removed because it was causing highlight.js to hang |
122 | try { |
123 | return Highlight.highlight(lang, code).value |
124 | } catch(e) { |
125 | if (!/^Unknown language:/.test(e.message)) console.error('highlight:', e) |
126 | return u.escape(code) |
127 | } |
128 | } |
129 | |
130 | u.pre = function (text) { |
131 | return '<pre>' + u.escape(text) + '</pre>' |
132 | } |
133 | |
134 | u.json = function (obj) { |
135 | return u.linkify(u.pre(JSON.stringify(obj, null, 2))) |
136 | } |
137 | |
138 | u.linkify = function (text) { |
139 | // regex is from ssb-ref |
140 | return text.replace(/(@|%|&|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+/g, function (str) { |
141 | return '<a href="/' + str + '">' + str + '</a>' |
142 | }) |
143 | } |
144 | |
145 | u.readObjectString = function (obj, cb) { |
146 | pull(obj.read, pull.collect(function (err, bufs) { |
147 | if (err) return cb(err) |
148 | cb(null, Buffer.concat(bufs, obj.length).toString('utf8')) |
149 | })) |
150 | } |
151 | |
152 | u.pullReverse = function () { |
153 | return function (read) { |
154 | return u.readNext(function (cb) { |
155 | pull(read, pull.collect(function (err, items) { |
156 | cb(err, items && pull.values(items.reverse())) |
157 | })) |
158 | }) |
159 | } |
160 | } |
161 | |
162 | function compareMsgs(a, b) { |
163 | return (a.value.timestamp - b.value.timestamp) || (a.key - b.key) |
164 | } |
165 | |
166 | u.pullSort = function (comparator, descending) { |
167 | return function (read) { |
168 | return u.readNext(function (cb) { |
169 | pull(read, pull.collect(function (err, items) { |
170 | if (err) return cb(err) |
171 | items.sort(comparator) |
172 | if (descending) items.reverse() |
173 | cb(null, pull.values(items)) |
174 | })) |
175 | }) |
176 | } |
177 | } |
178 | |
179 | u.sortMsgs = function (descending) { |
180 | return u.pullSort(compareMsgs, descending) |
181 | } |
182 | |
183 | u.truncate = function (str, len) { |
184 | str = String(str) |
185 | return str.length < len ? str : str.substr(0, len) + '…' |
186 | } |
187 | |
188 | u.messageTitle = function (msg) { |
189 | var c = msg.value.content |
190 | return u.truncate(c.title || c.text || msg.key, 40) |
191 | } |
192 | |
193 | u.ifModifiedSince = function (req, lastMod) { |
194 | var ifModSince = req.headers['if-modified-since'] |
195 | if (!ifModSince) return false |
196 | var d = new Date(ifModSince) |
197 | return d && Math.floor(d/1000) >= Math.floor(lastMod/1000) |
198 | } |
199 | |
200 | u.decryptMessage = function (sbot, msg, cb) { |
201 | var c = msg && msg.value && msg.value.content |
202 | if (sbot.private && c && typeof c === 'string' && c.slice(-4) === '.box') { |
203 | sbot.private.unbox(msg.value.content, function (err, content) { |
204 | if (err || !content) return cb(null, msg) // leave message encrypted |
205 | var msg1 = {} |
206 | for (var k in msg) msg1[k] = msg[k] |
207 | msg1.value = {} |
208 | msg1.private = true |
209 | for (var j in msg.value) msg1.value[j] = msg.value[j] |
210 | msg1.value.private = true |
211 | msg1.value.content = content |
212 | if (!content.recps) { |
213 | sbot.whoami(function (err, feed) { |
214 | if (err) return cb(err) |
215 | content.recps = [msg1.value.author] |
216 | if (feed.id !== msg1.value.author) content.recps.push(feed.id) |
217 | cb(null, msg1) |
218 | }) |
219 | } else cb(null, msg1) |
220 | }) |
221 | } else cb(null, msg) |
222 | } |
223 | |
224 | u.isMessageReadable = function (msg) { |
225 | return msg && msg.value && msg.value.content |
226 | && typeof msg.value.content === 'object' |
227 | } |
228 | |
229 | u.readableMessages = function () { |
230 | return pull.filter(u.isMessageReadable) |
231 | } |
232 | |
233 | u.decryptMessages = function (sbot) { |
234 | return paramap(function (msg, cb) { |
235 | u.decryptMessage(sbot, msg, cb) |
236 | }, 4) |
237 | } |
238 | |
239 | u.formatMarkdownTitle = function (title) { |
240 | return u.escape(removeMd(title)) |
241 | } |
242 | |
243 | u.privateIcon = function (req) { |
244 | return `<img src="/emoji/lock.png" height="16" width="16" alt="${req._t('repo.Private')}" title="${req._t('repo.RepoIsPrivate')}">` |
245 | } |
246 | |
247 | u.msgIdToDomId = function (id) { |
248 | return 'msg-' + id.replace(/^%/, '') |
249 | .replace(/\+/g, '-') |
250 | .replace(/\//g, '_') |
251 | .replace(/=/g, '') |
252 | } |
253 |
Built with git-ssb-web