var path = require('path'); var pull = require("pull-stream"); var marked = require("ssb-marked"); var htime = require("human-time"); var emojis = require("emoji-named-characters"); var cat = require("pull-cat"); var h = require('hyperscript'); var refs = require('ssb-ref') var emojiDir = path.join(require.resolve("emoji-named-characters"), "../pngs"); exports.wrapPage = wrapPage; exports.MdRenderer = MdRenderer; exports.renderEmoji = renderEmoji; exports.formatMsgs = formatMsgs; exports.renderThread = renderThread; exports.renderAbout = renderAbout; exports.renderShowAll = renderShowAll; exports.renderRssItem = renderRssItem; exports.wrapRss = wrapRss; function MdRenderer(opts) { marked.Renderer.call(this, {}); this.opts = opts; } MdRenderer.prototype = new marked.Renderer(); MdRenderer.prototype.urltransform = function(href) { if (!href) return false; switch (href[0]) { case "#": return this.opts.base + "channel/" + href.slice(1); case "%": if (!refs.isMsgId(href)) return false return this.opts.msg_base + encodeURIComponent(href); case "@": if (!refs.isFeedId(href)) return false href = (this.opts.mentions && this.opts.mentions[href.substr(1)]) || href; return this.opts.feed_base + href; case "&": if (!refs.isBlobId(href)) return false return this.opts.blob_base + href; } if (href.indexOf("javascript:") === 0) return false; return href; }; MdRenderer.prototype.image = function(href, title, text) { if (text.endsWith(".svg")) return h('object', { type: 'image/svg+xml', data: href, alt: text }).outerHTML; else return h('img', { src: this.opts.img_base + href, alt: text, title: title }).outerHTML; }; function renderEmoji(emoji) { var opts = this.renderer.opts; var url = opts.mentions && opts.mentions[emoji] ? opts.blob_base + encodeURIComponent(opts.mentions[emoji]) : emoji in emojis && opts.emoji_base + escape(emoji) + '.png'; return url ? h('img.ssb-emoji', { src: url, alt: ':' + escape(emoji) + ':', title: ':' + escape(emoji) + ':', height: 16, width: 16 }).outerHTML : ":" + emoji + ":"; } function escape(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function formatMsgs(id, ext, opts) { switch (ext || "html") { case "html": return pull(renderThread(opts, id, ''), wrapPage(id)); case "js": return pull(renderThread(opts), wrapJSEmbed(opts)); case "json": return wrapJSON(); case "rss": return pull(renderRssItem(opts), wrapRss(id, opts)); default: return null; } } function wrap(before, after) { return function(read) { return cat([pull.once(before), read, pull.once(after)]); }; } function callToAction() { return h('a.call-to-action', { href: 'https://www.scuttlebutt.nz' }, 'Join Scuttlebutt now').outerHTML; } function toolTipTop() { return h('span.top-tip', 'You are reading content from ', h('a', { href: 'https://www.scuttlebutt.nz' }, 'Scuttlebutt')).outerHTML; } function renderAbout(opts, about, showAllHTML = "") { if (about.publicWebHosting === false || (about.publicWebHosting == null && opts.requireOptIn)) { return pull( pull.map(renderMsg.bind(this, opts, '')), wrap(toolTipTop() + '
', '
' + callToAction()) ); } var figCaption = h('figcaption'); figCaption.innerHTML = 'Feed of ' + escape(about.name) + '
' + marked(String(about.description || ''), opts.marked); return pull( pull.map(renderMsg.bind(this, opts, '')), wrap(toolTipTop() + '
' + h('article', h('header', h('figure', h('img', { src: opts.img_base + about.image, style: 'max-height: 200px; max-width: 200px;' }), figCaption) )).outerHTML, showAllHTML + '
' + callToAction()) ); } function renderThread(opts, id, showAllHTML = "") { return pull( pull.map(renderMsg.bind(this, opts, id)), wrap(toolTipTop() + '
', showAllHTML + '
' + callToAction()) ); } function renderRssItem(opts) { return pull( pull.map(renderRss.bind(this, opts)) ); } function wrapPage(id) { return wrap( "" + "" + "" + id + " | ssb-viewer" + "" + '' + styles + "", "" ); } function wrapRss(id, opts) { return wrap( '' + '' + '' + '' + id + ' | ssb-viewer', ''+ '' ); } var styles = ` `; function wrapJSON() { var first = true; return pull(pull.map(JSON.stringify), join(","), wrap("[", "]")); } function wrapJSEmbed(opts) { return pull( wrap('', ""), pull.map(docWrite), opts.base_token && rewriteBase(new RegExp(opts.base_token, "g")) ); } function rewriteBase(token) { // detect the origin of the script and rewrite the js/html to use it return pull( replace(token, '" + SSB_VIEWER_ORIGIN + "/'), wrap( "var SSB_VIEWER_ORIGIN = (function () {" + 'var scripts = document.getElementsByTagName("script")\n' + "var script = scripts[scripts.length-1]\n" + "if (!script) return location.origin\n" + 'return script.src.replace(/\\/%.*$/, "")\n' + "}())\n", "" ) ); } function join(delim) { var first = true; return pull.map(function(val) { if (!first) return delim + String(val); first = false; return val; }); } function replace(re, rep) { return pull.map(function(val) { return String(val).replace(re, rep); }); } function docWrite(str) { return "document.write(" + JSON.stringify(str) + ")\n"; } function renderMsg(opts, id, msg) { var c = msg.value.content || {}; if (opts.renderPrivate == false && typeof(msg.value.content) == 'string') return '' if (opts.renderSubscribe == false && c.type == "channel" && c.subscribed != undefined) return '' if (opts.renderVote == false && c.type == "vote") return '' if (opts.renderChess == false && c.type.startsWith("chess")) return '' if (opts.renderTalenet == false && c.type.startsWith("talenet")) return '' if (opts.renderFollow == false && c.type == "contact") return '' if (opts.renderAbout == false && c.type == "about") return '' if (opts.renderPub == false && c.type == "pub") return '' if (msg.author.publicWebHosting === false) return h('article', 'User has chosen not to be hosted publicly').outerHTML; if (msg.author.publicWebHosting == null && opts.requireOptIn) return h('article', 'User has not chosen to be hosted publicly').outerHTML; var name = encodeURIComponent(msg.key); return h('article#' + name, h('header', h('figure', h('img', { alt: '', src: opts.img_base + msg.author.image, height: 50, width: 50 }), h('figcaption', h('a.ssb-avatar-name', { href: opts.base + escape(msg.value.author) }, msg.author.name), msgTimestamp(msg, opts.base + name), ' ', h('small', h('code', msg.key)) ))), render(opts, id, c)).outerHTML; } function renderRss(opts, msg) { var c = msg.value.content || {}; var name = encodeURIComponent(msg.key); let content = h('div', render(opts, c)).innerHTML; if (!content) { return null; } return ( '' + '' + escape(c.type || 'private') + '' + '' + escape(msg.author.name) + '' + '' + '' + opts.base + escape(name) + '' + '' + new Date(msg.value.timestamp).toUTCString() + '' + '' + msg.key + '' + '' ); } function msgTimestamp(msg, link) { var date = new Date(msg.value.timestamp); var isoStr = date.toISOString(); return h('time.ssb-timestamp', { datetime: isoStr }, h('a', { href: link, title: isoStr }, formatDate(date))); } function formatDate(date) { return htime(date); } function render(opts, id, c) { var base = opts.base; if (!c) return if (c.type === "post") { var channel = c.channel ? h('div.top-right', h('a', { href: base + 'channel/' + c.channel }, '#' + c.channel)) : ""; return [channel, renderPost(opts, id, c)]; } else if (c.type == "vote" && c.vote.expression == "Dig") { var channel = c.channel ? [' in ', h('a', { href: base + 'channel/' + c.channel }, '#' + c.channel)] : ""; var linkedText = "this"; if (typeof c.vote.linkedText != "undefined") linkedText = c.vote.linkedText.substring(0, 75); return h('span.status', ['Liked ', h('a', { href: base + encodeURIComponent(c.vote.link) }, linkedText), channel]); } else if (c.type == "vote") { var linkedText = "this"; if (c.vote && typeof c.vote.linkedText === "string") linkedText = c.vote.linkedText.substring(0, 75); return h('span.status', ['Voted ', h('a', { href: base + encodeURIComponent(c.vote.link) }, linkedText)]); } else if (c.type == "contact" && c.following) { var name = c.contact; if (c.contactAbout) name = c.contactAbout.name; return h('span.status', ['Followed ', h('a', { href: base + c.contact }, name)]); } else if (c.type == "contact" && !c.following) { var name = c.contact; if (c.contactAbout) name = c.contactAbout.name; return h('span.status', ['Unfollowed ', h('a', { href: base + c.contact }, name)]); } else if (typeof c == "string") { return h('span.status', 'Wrote something private') } else if (c.type == "chess_move") { return h('span.status', 'Moved a chess piece') } else if (c.type == "chess_invite") { return h('span.status', 'Started a chess game') } else if (c.type == "about") { return [h('span.status', 'Changed something in about'), renderDefault(c)]; } else if (c.type == "issue") { return [h('span.status', "Created a git issue" + (c.repoName ? " in repo " + c.repoName : ""), renderPost(opts, id, c))]; } else if (c.type == "git-repo") { return h('span.status', "Created a git repo " + c.name); } else if (c.type == "git-update") { var s = h('span.status'); s.innerHTML = "Did a git update " + (c.repoName != undefined ? " in repo " + escape(c.repoName) : "") + '
' + (Array.isArray(c.commits) ? c.commits.filter(Boolean).map(com => { return "-" +escape(com.title || com.sha1); }).join('
') : ""); return s; } else if (c.type == "ssb-dns") { return [h('span.status', 'Updated DNS'), renderDefault(c)]; } else if (c.type == "pub") { var host = c.address && c.address.host return h('span.status', 'Connected to the pub ' + host); } else if (c.type == "npm-packages") { return [h('span.status', 'Pushed npm packages')]; } else if (c.type == "channel" && c.subscribed) return h('span.status', 'Subscribed to channel ', h('a', { href: base + 'channel/' + c.channel }, '#' + c.channel)); else if (c.type == "channel" && !c.subscribed) return h('span.status', 'Unsubscribed from channel ', h('a', { href: base + 'channel/' + c.channel }, '#' + c.channel)) else if (c.type == "blog") { //%RTXvyZ2fZWwTyWdlk0lYGk5sKw5Irj+Wk4QwxyOVG5g=.sha256 var channel = c.channel ? h('div.top-right', h('a', { href: base + 'channel/' + c.channel }, '#' + c.channel)) : ""; var s = h('section'); s.innerHTML = marked(String(c.blogContent), opts.marked) return [channel, h('h2', String(c.title)), s]; } else if (c.type === 'gathering') { return h('div', renderGathering(opts, id, c)) } else return renderDefault(c); } function renderGathering(opts, id, c) { const title = h('h2', String(c.about.title)) const startEpoch = c.about.startDateTime && c.about.startDateTime.epoch const time = startEpoch ? h('h3', new Date(startEpoch).toUTCString()) : '' const image = h('p', h('img', { src: opts.img_base + c.about.image })) const attending = h('h3.attending', c.numberAttending + ' attending') const desc = h('div') desc.innerHTML = marked(String(c.about.description), opts.marked) return h('section', [title, time, image, attending, desc] ) } function renderPost(opts, id, c) { opts.mentions = {}; if (Array.isArray(c.mentions)) { c.mentions.forEach(function (link) { if (link && link.name && link.link) opts.mentions[link.name] = link.link; }); } var s = h('section'); var content = ''; if (c.root && c.root != id) content += 'Re: ' + h('a', { href: '/' + encodeURIComponent(c.root) }, c.root.substring(0, 10)).outerHTML + '
'; s.innerHTML = content + marked(String(c.text), opts.marked); return s; } function renderDefault(c) { return h('pre', JSON.stringify(c, 0, 2)); } function renderShowAll(showAll, url) { if (!showAll) return '
' + h('a', { href : url + '?showAll' }, 'Show whole feed').outerHTML; }