git ssb

9+

cel / ssb-viewer



Commit 02bfe74c0273e98741997b1d9d8c28a583dffda2

Merge styling changes from Andre Staltz

Anders Rune Jensen committed on 5/11/2017, 7:34:42 PM
Parent: 58e51b98743793a144acbba06d639beb926b51db

Files changed

index.jschanged
render.jsadded
index.jsView
@@ -2,60 +2,33 @@
22 var http = require('http')
33 var qs = require('querystring')
44 var path = require('path')
55 var crypto = require('crypto')
6-var cat = require('pull-cat')
76 var pull = require('pull-stream')
87 var paramap = require('pull-paramap')
9-var marked = require('ssb-marked')
108 var sort = require('ssb-sort')
119 var toPull = require('stream-to-pull-stream')
1210 var memo = require('asyncmemo')
1311 var lru = require('lrucache')
14-var htime = require('human-time')
15-var emojis = require('emoji-named-characters')
1612 var serveEmoji = require('emoji-server')()
13 +var {
14 + MdRenderer,
15 + renderEmoji,
16 + formatMsgs,
17 + wrapPage,
18 + renderThread
19 +} = require('./render');
1720
18-var emojiDir = path.join(require.resolve('emoji-named-characters'), '../pngs')
1921 var appHash = hash([fs.readFileSync(__filename)])
2022
2123 var urlIdRegex = /^(?:\/(([%&@]|%25|%26|%40)(?:[A-Za-z0-9\/+]|%2[Ff]|%2[Bb]){43}(?:=|%3[Dd])\.(?:sha256|ed25519))(?:\.([^?]*))?|(\/.*?))(?:\?(.*))?$/
2224
23-function MdRenderer(opts) {
24- marked.Renderer.call(this, {})
25- this.opts = opts
25 +function hash(arr) {
26 + return arr.reduce(function (hash, item) {
27 + return hash.update(String(item))
28 + }, crypto.createHash('sha256')).digest('base64')
2629 }
27-MdRenderer.prototype = new marked.Renderer()
2830
29-MdRenderer.prototype.urltransform = function (href) {
30- if (!href) return false
31- switch (href[0]) {
32- case '#': return '/channel/' + href.slice(1)
33- case '%': return this.opts.msg_base + encodeURIComponent(href)
34- case '@': return this.opts.feed_base + encodeURIComponent(href)
35- case '&': return this.opts.blob_base + encodeURIComponent(href)
36- }
37- if (href.indexOf('javascript:') === 0) return false
38- return href
39-}
40-
41-MdRenderer.prototype.image = function (href, title, text) {
42- return '<img src="' + this.opts.img_base + escape(href) + '"'
43- + ' alt="' + text + '"'
44- + (title ? ' title="' + title + '"' : '')
45- + (this.options.xhtml ? '/>' : '>')
46-}
47-
48-function renderEmoji(emoji) {
49- var opts = this.renderer.opts
50- return emoji in emojis ?
51- '<img src="' + opts.emoji_base + escape(emoji) + '.png"'
52- + ' alt=":' + escape(emoji) + ':"'
53- + ' title=":' + escape(emoji) + ':"'
54- + ' class="ssb-emoji" height="16" width="16">'
55- : ':' + emoji + ':'
56-}
57-
5831 exports.name = 'viewer'
5932 exports.manifest = {}
6033 exports.version = require('./package').version
6134
@@ -65,8 +38,9 @@
6538 var host = conf.host || config.host || '::'
6639
6740 var base = conf.base || '/'
6841 var defaultOpts = {
42 + base: base,
6943 msg_base: conf.msg_base || base,
7044 feed_base: conf.feed_base || base,
7145 blob_base: conf.blob_base || base,
7246 img_base: conf.img_base || base,
@@ -383,16 +357,8 @@
383357 cb(null, {key: id, value: value})
384358 })
385359 }
386360
387-function escape(str) {
388- return String(str)
389- .replace(/&/g, '&amp;')
390- .replace(/</g, '&lt;')
391- .replace(/>/g, '&gt;')
392- .replace(/"/g, '&quot;')
393-}
394-
395361 function respond(res, status, message) {
396362 res.writeHead(status)
397363 res.end(message)
398364 }
@@ -460,179 +426,4 @@
460426 read(abort, cb)
461427 }
462428 }
463429 }
464-
465-function formatMsgs(id, ext, opts) {
466- switch (ext || 'html') {
467- case 'html': return pull(renderThread(opts), wrapPage(id))
468- case 'js': return pull(renderThread(opts), wrapJSEmbed(opts))
469- case 'json': return wrapJSON()
470- default: return null
471- }
472-}
473-
474-function wrap(before, after) {
475- return function (read) {
476- return cat([pull.once(before), read, pull.once(after)])
477- }
478-}
479-
480-function renderThread(opts) {
481- return pull(
482- pull.map(renderMsg.bind(this, opts)),
483- wrap('<div class="ssb-thread">', '</div>')
484- )
485-}
486-
487-function wrapPage(id) {
488- return wrap('<!doctype html><html><head>'
489- + '<meta charset=utf-8>'
490- + '<title>' + id + ' | ssb-viewer</title>'
491- + '<meta name=viewport content="width=device-width,initial-scale=1">'
492- + '<link rel=stylesheet href="/static/base.css">'
493- + '<link rel=stylesheet href="/static/nicer.css">'
494- + '</head><body>',
495- '</body></html>'
496- )
497-}
498-
499-function wrapJSON() {
500- var first = true
501- return pull(
502- pull.map(JSON.stringify),
503- join(','),
504- wrap('[', ']')
505- )
506-}
507-
508-function wrapJSEmbed(opts) {
509- return pull(
510- wrap('<link rel=stylesheet href="' + opts.base + 'static/base.css">', ''),
511- pull.map(docWrite),
512- opts.base_token && rewriteBase(new RegExp(opts.base_token, 'g'))
513- )
514-}
515-
516-
517-function rewriteBase(token) {
518- // detect the origin of the script and rewrite the js/html to use it
519- return pull(
520- replace(token, '" + SSB_VIEWER_ORIGIN + "/'),
521- wrap('var SSB_VIEWER_ORIGIN = (function () {'
522- + 'var scripts = document.getElementsByTagName("script")\n'
523- + 'var script = scripts[scripts.length-1]\n'
524- + 'if (!script) return location.origin\n'
525- + 'return script.src.replace(/\\/%.*$/, "")\n'
526- + '}())\n', '')
527- )
528-}
529-
530-function join(delim) {
531- var first = true
532- return pull.map(function (val) {
533- if (!first) return delim + String(val)
534- first = false
535- return val
536- })
537-}
538-
539-function replace(re, rep) {
540- return pull.map(function (val) {
541- return String(val).replace(re, rep)
542- })
543-}
544-
545-function docWrite(str) {
546- return 'document.write(' + JSON.stringify(str) + ')\n'
547-}
548-
549-function hash(arr) {
550- return arr.reduce(function (hash, item) {
551- return hash.update(String(item))
552- }, crypto.createHash('sha256')).digest('base64')
553-}
554-
555-function renderMsg(opts, msg) {
556- var c = msg.value.content || {}
557- var name = encodeURIComponent(msg.key)
558- return '<div class="ssb-message" id="' + name + '">'
559- + '<img class="ssb-avatar-image" alt=""'
560- + ' src="' + opts.img_base + escape(msg.author.image) + '"'
561- + ' height="32" width="32">'
562- + '<a class="ssb-avatar-name"'
563- + ' href="/' + escape(msg.value.author) + '"'
564- + '>' + msg.author.name + '</a>'
565- + msgTimestamp(msg, name)
566- + render(opts, c)
567- + '</div>'
568-}
569-
570-function msgTimestamp(msg, name) {
571- var date = new Date(msg.value.timestamp)
572- return '<time class="ssb-timestamp" datetime="' + date.toISOString() + '">'
573- + '<a href="#' + name + '">'
574- + formatDate(date) + '</a></time>'
575-}
576-
577-function formatDate(date) {
578- // return date.toISOString().replace('T', ' ')
579- return htime(date)
580-}
581-
582-function render(opts, c)
583-{
584- if (c.type === 'post') {
585- var channel = c.channel ? ' in <a href="/channel/' + c.channel + '">#' + c.channel + '</a>' : ''
586- return channel + renderPost(opts, c)
587- } else if (c.type == 'vote' && c.vote.expression == 'Dig') {
588- var channel = c.channel ? ' in <a href="/channel/' + c.channel + '">#' + c.channel + '</a>' : ''
589- var linkedText = 'this'
590- if (typeof c.vote.linkedText != 'undefined')
591- linkedText = c.vote.linkedText.substring(0, 75)
592- return ' dug ' + '<a href="/' + c.vote.link + '">' + linkedText + '</a>' + channel
593- }
594- else if (c.type == 'vote') {
595- var linkedText = 'this'
596- if (typeof c.vote.linkedText != 'undefined')
597- linkedText = c.vote.linkedText.substring(0, 75)
598- return ' voted <a href="/' + c.vote.link + '">' + linkedText + '</a>'
599- }
600- else if (c.type == 'contact' && c.following) {
601- var name = c.contact
602- if (typeof c.contactAbout != 'undefined')
603- name = c.contactAbout.name
604- return ' followed <a href="/' + c.contact + '">' + name + "</a>"
605- }
606- else if (c.type == 'contact' && !c.following) {
607- var name = c.contact
608- if (typeof c.contactAbout != 'undefined')
609- name = c.contactAbout.name
610- return ' unfollowed <a href="/' + c.contact + '">' + name + "</a>"
611- }
612- else if (typeof c == 'string')
613- return ' wrote something private '
614- else if (c.type == 'about')
615- return ' changed something in about'
616- else if (c.type == 'issue')
617- return ' created an issue'
618- else if (c.type == 'git-update')
619- return ' did a git update'
620- else if (c.type == 'ssb-dns')
621- return ' updated dns'
622- else if (c.type == 'pub')
623- return ' connected to a pub'
624- else if (c.type == 'channel' && c.subscribed)
625- return ' subscribed to channel <a href="/channel/' + c.channel + '">#' + c.channel + "</a>"
626- else if (c.type == 'channel' && !c.subscribed)
627- return ' unsubscribed from channel <a href="/channel/' + c.channel + '">#' + c.channel + "</a>"
628- else
629- return renderDefault(c)
630-}
631-
632-function renderPost(opts, c) {
633- return '<div class="ssb-post">' + marked(c.text, opts.marked) + '</div>'
634-}
635-
636-function renderDefault(c) {
637- return '<pre>' + JSON.stringify(c, 0, 2) + '</pre>'
638-}
render.jsView
@@ -1,0 +1,439 @@
1 +var path = require('path');
2 +var pull = require("pull-stream");
3 +var marked = require("ssb-marked");
4 +var htime = require("human-time");
5 +var emojis = require("emoji-named-characters");
6 +var cat = require("pull-cat");
7 +
8 +var emojiDir = path.join(require.resolve("emoji-named-characters"), "../pngs");
9 +
10 +exports.wrapPage = wrapPage;
11 +exports.MdRenderer = MdRenderer;
12 +exports.renderEmoji = renderEmoji;
13 +exports.formatMsgs = formatMsgs;
14 +exports.renderThread = renderThread;
15 +
16 +function MdRenderer(opts) {
17 + marked.Renderer.call(this, {});
18 + this.opts = opts;
19 +}
20 +
21 +MdRenderer.prototype = new marked.Renderer();
22 +
23 +MdRenderer.prototype.urltransform = function(href) {
24 + if (!href) return false;
25 + switch (href[0]) {
26 + case "#":
27 + return this.opts.base + "channel/" + href.slice(1);
28 + case "%":
29 + return this.opts.msg_base + encodeURIComponent(href);
30 + case "@":
31 + return this.opts.feed_base + encodeURIComponent(href);
32 + case "&":
33 + return this.opts.blob_base + encodeURIComponent(href);
34 + }
35 + if (href.indexOf("javascript:") === 0) return false;
36 + return href;
37 +};
38 +
39 +MdRenderer.prototype.image = function(href, title, text) {
40 + return (
41 + '<img src="' +
42 + this.opts.img_base +
43 + escape(href) +
44 + '"' +
45 + ' alt="' +
46 + text +
47 + '"' +
48 + (title ? ' title="' + title + '"' : "") +
49 + (this.options.xhtml ? "/>" : ">")
50 + );
51 +};
52 +
53 +function renderEmoji(emoji) {
54 + var opts = this.renderer.opts;
55 + return emoji in emojis
56 + ? '<img src="' +
57 + opts.emoji_base +
58 + escape(emoji) +
59 + '.png"' +
60 + ' alt=":' +
61 + escape(emoji) +
62 + ':"' +
63 + ' title=":' +
64 + escape(emoji) +
65 + ':"' +
66 + ' class="ssb-emoji" height="16" width="16">'
67 + : ":" + emoji + ":";
68 +}
69 +
70 +function escape(str) {
71 + return String(str)
72 + .replace(/&/g, "&amp;")
73 + .replace(/</g, "&lt;")
74 + .replace(/>/g, "&gt;")
75 + .replace(/"/g, "&quot;");
76 +}
77 +
78 +function formatMsgs(id, ext, opts) {
79 + switch (ext || "html") {
80 + case "html":
81 + return pull(renderThread(opts), wrapPage(id));
82 + case "js":
83 + return pull(renderThread(opts), wrapJSEmbed(opts));
84 + case "json":
85 + return wrapJSON();
86 + default:
87 + return null;
88 + }
89 +}
90 +
91 +function wrap(before, after) {
92 + return function(read) {
93 + return cat([pull.once(before), read, pull.once(after)]);
94 + };
95 +}
96 +
97 +function renderThread(opts) {
98 + return pull(
99 + pull.map(renderMsg.bind(this, opts)),
100 + wrap(
101 + '<span class="top-tip">You are reading content from ' +
102 + '<a href="https://www.scuttlebutt.nz">Scuttlebutt</a>' +
103 + '</span>' +
104 + '<main>',
105 +
106 + '</main>' +
107 + '<a class="call-to-action" href="https://www.scuttlebutt.nz">' +
108 + 'Join Scuttlebutt now' +
109 + '</a>'
110 + )
111 + );
112 +}
113 +
114 +function wrapPage(id) {
115 + return wrap(
116 + "<!doctype html><html><head>" +
117 + "<meta charset=utf-8>" +
118 + "<title>" +
119 + id + " | ssb-viewer" +
120 + "</title>" +
121 + '<meta name=viewport content="width=device-width,initial-scale=1">' +
122 + styles +
123 + "</head><body>",
124 + "</body></html>"
125 + );
126 +}
127 +
128 +var styles = `
129 + <style>
130 + html { background-color: #f1f3f5; }
131 + body {
132 + color: #212529;
133 + font-family: "Helvetica Neue", "Calibri Light", Roboto, sans-serif;
134 + -webkit-font-smoothing: antialiased;
135 + -moz-osx-font-smoothing: grayscale;
136 + letter-spacing: 0.02em;
137 + padding-top: 30px;
138 + padding-bottom: 50px;
139 + }
140 + a { color: #364fc7; }
141 +
142 + .top-tip, .top-tip a {
143 + color: #868e96;
144 + }
145 + .top-tip {
146 + text-align: center;
147 + display: block;
148 + margin-bottom: 10px;
149 + font-size: 14px;
150 + }
151 + main { margin: 0 auto; max-width: 40rem; }
152 + main article:first-child { border-radius: 3px 3px 0 0; }
153 + main article:last-child { border-radius: 0 0 3px 3px; }
154 + article {
155 + background-color: white;
156 + padding: 20px;
157 + box-shadow: 0 1px 3px #949494;
158 + position: relative;
159 + }
160 + .top-right { position: absolute; top: 20px; right: 20px; }
161 + article > header { margin-bottom: 20px; }
162 + article > header > figure {
163 + margin: 0; display: flex;
164 + }
165 + article > header > figure > img {
166 + border-radius: 2px; margin-right: 10px;
167 + }
168 + article > header > figure > figcaption {
169 + display: flex; flex-direction: column; justify-content: space-around;
170 + }
171 + .ssb-avatar-name { font-size: 1.2em; font-weight: bold; }
172 + time a { color: #868e96; }
173 + .ssb-avatar-name, time a {
174 + text-decoration: none;
175 + }
176 + .ssb-avatar-name:hover, time:hover a {
177 + text-decoration: underline;
178 + }
179 + section p { line-height: 1.45em; }
180 + section p img {
181 + max-width: 100%;
182 + max-height: 50vh;
183 + margin: 0 auto;
184 + }
185 + .status {
186 + font-style: italic;
187 + }
188 +
189 + code {
190 + display: inline;
191 + padding: 2px 5px;
192 + font-weight: 600;
193 + background-color: #e9ecef;
194 + border-radius: 3px;
195 + color: #495057;
196 + }
197 + blockquote {
198 + padding-left: 1.2em;
199 + margin: 0;
200 + color: #868e96;
201 + border-left: 5px solid #ced4da;
202 + }
203 + pre {
204 + background-color: #212529;
205 + color: #ced4da;
206 + font-weight: bold;
207 + padding: 5px;
208 + border-radius: 3px;
209 + position: relative;
210 + }
211 + pre::before {
212 + content: "METADATA";
213 + position: absolute;
214 + top: -7px;
215 + left: 0px;
216 + background-color: #212529;
217 + padding: 2px 4px 0;
218 + border-radius: 2px;
219 + font-family: "Helvetica Neue", "Calibri Light", Roboto, sans-serif;
220 + font-size: 9px;
221 + }
222 + .call-to-action {
223 + display: block;
224 + margin: 0 auto;
225 + width: 13em;
226 + text-align: center;
227 + text-decoration: none;
228 + margin-top: 20px;
229 + margin-bottom: 60px;
230 + background-color: #5c7cfa;
231 + padding: 15px 0;
232 + color: #edf2ff;
233 + border-radius: 3px;
234 + border-bottom: 3px solid #3b5bdb;
235 + }
236 + .call-to-action:hover {
237 + background-color: #748ffc;
238 + border-bottom: 3px solid #4c6ef5;
239 + }
240 + </style>
241 +`;
242 +
243 +function wrapJSON() {
244 + var first = true;
245 + return pull(pull.map(JSON.stringify), join(","), wrap("[", "]"));
246 +}
247 +
248 +function wrapJSEmbed(opts) {
249 + return pull(
250 + wrap('<link rel=stylesheet href="' + opts.base + 'static/base.css">', ""),
251 + pull.map(docWrite),
252 + opts.base_token && rewriteBase(new RegExp(opts.base_token, "g"))
253 + );
254 +}
255 +
256 +function rewriteBase(token) {
257 + // detect the origin of the script and rewrite the js/html to use it
258 + return pull(
259 + replace(token, '" + SSB_VIEWER_ORIGIN + "/'),
260 + wrap(
261 + "var SSB_VIEWER_ORIGIN = (function () {" +
262 + 'var scripts = document.getElementsByTagName("script")\n' +
263 + "var script = scripts[scripts.length-1]\n" +
264 + "if (!script) return location.origin\n" +
265 + 'return script.src.replace(/\\/%.*$/, "")\n' +
266 + "}())\n",
267 + ""
268 + )
269 + );
270 +}
271 +
272 +function join(delim) {
273 + var first = true;
274 + return pull.map(function(val) {
275 + if (!first) return delim + String(val);
276 + first = false;
277 + return val;
278 + });
279 +}
280 +
281 +function replace(re, rep) {
282 + return pull.map(function(val) {
283 + return String(val).replace(re, rep);
284 + });
285 +}
286 +
287 +function docWrite(str) {
288 + return "document.write(" + JSON.stringify(str) + ")\n";
289 +}
290 +
291 +function renderMsg(opts, msg) {
292 + var c = msg.value.content || {};
293 + var name = encodeURIComponent(msg.key);
294 + return (
295 + '<article id="' +
296 + name +
297 + '">' +
298 + '<header>' +
299 + '<figure>' +
300 + '<img alt="" ' +
301 + 'src="' + opts.img_base + escape(msg.author.image) + '" ' +
302 + 'height="50" width="50">' +
303 + '<figcaption>' +
304 + '<a class="ssb-avatar-name"' +
305 + ' href="' + opts.base +
306 + escape(msg.value.author) +
307 + '"' +
308 + ">" + msg.author.name + "</a>" +
309 + msgTimestamp(msg, name) +
310 + '</figcaption>' +
311 + '</figure>' +
312 + '</header>' +
313 + render(opts, c) +
314 + "</article>"
315 + );
316 +}
317 +
318 +function msgTimestamp(msg, name) {
319 + var date = new Date(msg.value.timestamp);
320 + var isoStr = date.toISOString();
321 + return (
322 + '<time class="ssb-timestamp" datetime="' + isoStr + '">' +
323 + '<a ' +
324 + 'href="#' + name + '" ' +
325 + 'title="' + isoStr + '" ' +
326 + '>' + formatDate(date) + '</a>' +
327 + '</time>'
328 + );
329 +}
330 +
331 +function formatDate(date) {
332 + // return date.toISOString().replace('T', ' ')
333 + return htime(date);
334 +}
335 +
336 +function render(opts, c) {
337 + var base = opts.base;
338 + if (c.type === "post") {
339 + var channel = c.channel
340 + ? '<div class="top-right"><a href="' + base + 'channel/' + c.channel + '">#' + c.channel + "</a></div>"
341 + : "";
342 + return channel + renderPost(opts, c);
343 + } else if (c.type == "vote" && c.vote.expression == "Dig") {
344 + var channel = c.channel
345 + ? ' in <a href="' + base + 'channel/' + c.channel + '">#' + c.channel + "</a>"
346 + : "";
347 + var linkedText = "this";
348 + if (typeof c.vote.linkedText != "undefined")
349 + linkedText = c.vote.linkedText.substring(0, 75);
350 + return ('<span class="status">' +
351 + 'Liked ' +
352 + '<a href="' + base +
353 + c.vote.link +
354 + '">' +
355 + linkedText +
356 + "</a>" +
357 + channel +
358 + '</span>'
359 + );
360 + } else if (c.type == "vote") {
361 + var linkedText = "this";
362 + if (typeof c.vote.linkedText != "undefined")
363 + linkedText = c.vote.linkedText.substring(0, 75);
364 + return '<span class="status">' +
365 + 'Voted <a href="' + base + c.vote.link + '">' + linkedText + "</a>" +
366 + '</span>';
367 + } else if (c.type == "contact" && c.following) {
368 + var name = c.contact;
369 + if (typeof c.contactAbout != "undefined") name = c.contactAbout.name;
370 + return '<span class="status">' +
371 + 'Followed <a href="' + base + c.contact + '">' + name + "</a>" +
372 + '</span>';
373 + } else if (c.type == "contact" && !c.following) {
374 + var name = c.contact;
375 + if (typeof c.contactAbout != "undefined") name = c.contactAbout.name;
376 + return '<span class="status">' +
377 + 'Unfollowed <a href="' + base + c.contact + '">' + name + "</a>" +
378 + '</span>';
379 + } else if (typeof c == "string") {
380 + return '<span class="status">' +
381 + "Wrote something private" +
382 + '</span>';
383 + }
384 + else if (c.type == "about") {
385 + return '<span class="status">' +
386 + "Changed something in about" +
387 + '</span>' +
388 + renderDefault(c);
389 + }
390 + else if (c.type == "issue") {
391 + return '<span class="status">' +
392 + "Created a git issue" +
393 + '</span>' +
394 + renderDefault(c);
395 + }
396 + else if (c.type == "git-update") {
397 + return '<span class="status">' +
398 + "Did a git update" +
399 + '</span>' +
400 + renderDefault(c);
401 + }
402 + else if (c.type == "ssb-dns") {
403 + return '<span class="status">' +
404 + "Updated DNS" +
405 + '</span>' +
406 + renderDefault(c);
407 + }
408 + else if (c.type == "pub") {
409 + return '<span class="status">' +
410 + "Connected to a pub" +
411 + '</span>' +
412 + renderDefault(c);
413 + }
414 + else if (c.type == "channel" && c.subscribed)
415 + return '<span class="status">' +
416 + 'Subscribed to channel <a href="' + base + 'channel/' +
417 + c.channel +
418 + '">#' +
419 + c.channel +
420 + "</a>" +
421 + '</span>';
422 + else if (c.type == "channel" && !c.subscribed)
423 + return '<span class="status">' +
424 + 'Unsubscribed from channel <a href="' + base + 'channel/' +
425 + c.channel +
426 + '">#' +
427 + c.channel +
428 + "</a>" +
429 + '</span>';
430 + else return renderDefault(c);
431 +}
432 +
433 +function renderPost(opts, c) {
434 + return '<section>' + marked(c.text, opts.marked) + "</section>";
435 +}
436 +
437 +function renderDefault(c) {
438 + return "<pre>" + JSON.stringify(c, 0, 2) + "</pre>";
439 +}

Built with git-ssb-web