Merge remote-tracking branches 'origin/hyperscript' and 'angelo/render_rss'
This commit is contained in:
@@ -16,7 +16,10 @@ var {
|
||||
formatMsgs,
|
||||
wrapPage,
|
||||
renderThread,
|
||||
renderAbout
|
||||
renderAbout,
|
||||
renderShowAll,
|
||||
renderRssItem,
|
||||
wrapRss,
|
||||
} = require('./render');
|
||||
|
||||
var appHash = hash([fs.readFileSync(__filename)])
|
||||
@@ -90,35 +93,49 @@ exports.init = function (sbot, config) {
|
||||
}
|
||||
}
|
||||
|
||||
function serveFeed(req, res, feedId) {
|
||||
function serveFeed(req, res, feedId, ext) {
|
||||
console.log("serving feed: " + feedId)
|
||||
|
||||
var showAll = req.url.endsWith("?showAll")
|
||||
var showAllHTML = showAll ? '' : '<br/><a href="' + req.url + '?showAll">Show whole feed</a>'
|
||||
var showAll = req.url.endsWith("?showAll");
|
||||
|
||||
getAbout(feedId, function (err, about) {
|
||||
if (err) return respond(res, 500, err.stack || err)
|
||||
|
||||
pull(
|
||||
sbot.createUserStream({ id: feedId, reverse: true, limit: showAll ? -1 : 10 }),
|
||||
pull.collect(function (err, logs) {
|
||||
if (err) return respond(res, 500, err.stack || err)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': ctype("html")
|
||||
})
|
||||
pull(
|
||||
pull.values(logs),
|
||||
paramap(addAuthorAbout, 8),
|
||||
paramap(addFollowAbout, 8),
|
||||
paramap(addVoteMessage, 8),
|
||||
paramap(addGitLinks, 8),
|
||||
pull(renderAbout(defaultOpts, about, showAllHTML), wrapPage(about.name)),
|
||||
toPull(res, function (err) {
|
||||
if (err) console.error('[viewer]', err)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
function render() {
|
||||
switch (ext) {
|
||||
case 'rss':
|
||||
return pull(
|
||||
// formatMsgs(feedId, ext, defaultOpts)
|
||||
renderRssItem(defaultOpts), wrapRss(about.name, defaultOpts)
|
||||
);
|
||||
default:
|
||||
return pull(
|
||||
renderAbout(defaultOpts, about,
|
||||
renderShowAll(showAll, req.url)), wrapPage(about.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pull(
|
||||
sbot.createUserStream({ id: feedId, reverse: true, limit: showAll ? -1 : (ext == 'rss' ? 25 :10) }),
|
||||
pull.collect(function (err, logs) {
|
||||
if (err) return respond(res, 500, err.stack || err)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': ctype(ext)
|
||||
})
|
||||
pull(
|
||||
pull.values(logs),
|
||||
paramap(addAuthorAbout, 8),
|
||||
paramap(addFollowAbout, 8),
|
||||
paramap(addVoteMessage, 8),
|
||||
paramap(addGitLinks, 8),
|
||||
render(),
|
||||
toPull(res, function (err) {
|
||||
if (err) console.error('[viewer]', err)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -196,7 +213,6 @@ exports.init = function (sbot, config) {
|
||||
console.log("serving channel: " + channelId)
|
||||
|
||||
var showAll = req.url.endsWith("?showAll")
|
||||
var showAllHTML = showAll ? '' : '<br/><a href="' + req.url + '?showAll">Show whole feed</a>'
|
||||
|
||||
pull(
|
||||
sbot.query.read({ limit: showAll ? 300 : 10, reverse: true, query: [{$filter: { value: { content: { channel: channelId }}}}]}),
|
||||
@@ -209,7 +225,9 @@ exports.init = function (sbot, config) {
|
||||
pull.values(logs),
|
||||
paramap(addAuthorAbout, 8),
|
||||
paramap(addVoteMessage, 8),
|
||||
pull(renderThread(defaultOpts, showAllHTML), wrapPage('#' + channelId)),
|
||||
pull(renderThread(defaultOpts,
|
||||
renderShowAll(showAll, req.url)),
|
||||
wrapPage('#' + channelId)),
|
||||
toPull(res, function (err) {
|
||||
if (err) console.error('[viewer]', err)
|
||||
})
|
||||
@@ -363,6 +381,7 @@ function ctype(name) {
|
||||
case 'js': return 'text/javascript'
|
||||
case 'css': return 'text/css'
|
||||
case 'json': return 'application/json'
|
||||
case 'rss': return 'text/xml'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"emoji-named-characters": "^1.0.2",
|
||||
"emoji-server": "^1.0.0",
|
||||
"human-time": "^0.0.1",
|
||||
"hyperscript": "2.0.2",
|
||||
"lrucache": "^1.0.2",
|
||||
"pull-cat": "^1.1.11",
|
||||
"pull-paramap": "^1.2.1",
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 emojiDir = path.join(require.resolve("emoji-named-characters"), "../pngs");
|
||||
|
||||
@@ -13,6 +14,9 @@ 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, {});
|
||||
@@ -39,17 +43,11 @@ MdRenderer.prototype.urltransform = function(href) {
|
||||
};
|
||||
|
||||
MdRenderer.prototype.image = function(href, title, text) {
|
||||
return (
|
||||
'<img src="' +
|
||||
this.opts.img_base +
|
||||
escape(href) +
|
||||
'"' +
|
||||
' alt="' +
|
||||
text +
|
||||
'"' +
|
||||
(title ? ' title="' + title + '"' : "") +
|
||||
(this.options.xhtml ? "/>" : ">")
|
||||
);
|
||||
return h('img',
|
||||
{ src: this.opts.img_base + href,
|
||||
alt: text,
|
||||
title: title
|
||||
}).outerHTML;
|
||||
};
|
||||
|
||||
function renderEmoji(emoji) {
|
||||
@@ -59,16 +57,12 @@ function renderEmoji(emoji) {
|
||||
? opts.blob_base + encodeURIComponent(mentions[emoji])
|
||||
: emoji in emojis && opts.emoji_base + escape(emoji) + '.png';
|
||||
return url
|
||||
? '<img src="' +
|
||||
url +
|
||||
'"' +
|
||||
' alt=":' +
|
||||
escape(emoji) +
|
||||
':"' +
|
||||
' title=":' +
|
||||
escape(emoji) +
|
||||
':"' +
|
||||
' class="ssb-emoji" height="16" width="16">'
|
||||
? h('img.ssb-emoji',
|
||||
{ src: url,
|
||||
alt: ':' + escape(emoji) + ':',
|
||||
title: ':' + escape(emoji) + ':',
|
||||
height: 16, width: 16
|
||||
}).outerHTML
|
||||
: ":" + emoji + ":";
|
||||
}
|
||||
|
||||
@@ -88,6 +82,8 @@ function formatMsgs(id, ext, opts) {
|
||||
return pull(renderThread(opts), wrapJSEmbed(opts));
|
||||
case "json":
|
||||
return wrapJSON();
|
||||
case "rss":
|
||||
return pull(renderRssItem(opts), wrapRss(id, opts));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -99,44 +95,52 @@ function wrap(before, 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 = "") {
|
||||
var figCaption = h('figcaption');
|
||||
figCaption.innerHTML = 'Feed of ' + about.name + '<br>' +
|
||||
(about.description != undefined ?
|
||||
marked(about.description, opts.marked) : '');
|
||||
return pull(
|
||||
pull.map(renderMsg.bind(this, opts)),
|
||||
wrap(
|
||||
'<span class="top-tip">You are reading content from ' +
|
||||
'<a href="https://www.scuttlebutt.nz">Scuttlebutt</a>' +
|
||||
'</span>' +
|
||||
'<main>' +
|
||||
'<article><header><figure>' +
|
||||
'<img src="' + opts.img_base + escape(about.image) + '" ' +
|
||||
'height="200" width="200"><figcaption>' +
|
||||
'Feed of ' + about.name + '<br/>' +
|
||||
(about.description != undefined ?
|
||||
marked(about.description, opts.marked) : '') +
|
||||
'</figcaption></figure></header></article>',
|
||||
|
||||
showAllHTML + '</main>' +
|
||||
'<a class="call-to-action" href="https://www.scuttlebutt.nz">' +
|
||||
'Join Scuttlebutt now' +
|
||||
'</a>'
|
||||
)
|
||||
wrap(toolTipTop() + '<main>' +
|
||||
h('article',
|
||||
h('header',
|
||||
h('figure',
|
||||
h('img',
|
||||
{ src: opts.img_base + about.image,
|
||||
height: 200,
|
||||
width: 200
|
||||
}),
|
||||
figCaption)
|
||||
)).outerHTML,
|
||||
showAllHTML + '</main>' + callToAction())
|
||||
);
|
||||
}
|
||||
|
||||
function renderThread(opts, showAllHTML = "") {
|
||||
return pull(
|
||||
pull.map(renderMsg.bind(this, opts)),
|
||||
wrap(
|
||||
'<span class="top-tip">You are reading content from ' +
|
||||
'<a href="https://www.scuttlebutt.nz">Scuttlebutt</a>' +
|
||||
'</span>' +
|
||||
'<main>',
|
||||
wrap(toolTipTop() + '<main>',
|
||||
showAllHTML + '</main>' + callToAction())
|
||||
);
|
||||
}
|
||||
|
||||
showAllHTML + '</main>' +
|
||||
'<a class="call-to-action" href="https://www.scuttlebutt.nz">' +
|
||||
'Join Scuttlebutt now' +
|
||||
'</a>'
|
||||
)
|
||||
function renderRssItem(opts) {
|
||||
return pull(
|
||||
pull.map(renderRss.bind(this, opts))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,6 +158,18 @@ function wrapPage(id) {
|
||||
);
|
||||
}
|
||||
|
||||
function wrapRss(id, opts) {
|
||||
return wrap(
|
||||
'<?xml version="1.0" encoding="UTF-8" ?>' +
|
||||
'<rss version="2.0">' +
|
||||
'<channel>' +
|
||||
'<title>' + id + ' | ssb-viewer</title>',
|
||||
|
||||
'</channel>'+
|
||||
'</rss>'
|
||||
);
|
||||
}
|
||||
|
||||
var styles = `
|
||||
<style>
|
||||
html { background-color: #f1f3f5; }
|
||||
@@ -320,45 +336,53 @@ function docWrite(str) {
|
||||
function renderMsg(opts, msg) {
|
||||
var c = msg.value.content || {};
|
||||
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)))),
|
||||
render(opts, c)).outerHTML;
|
||||
}
|
||||
|
||||
function renderRss(opts, msg) {
|
||||
var c = msg.value.content || {};
|
||||
var name = encodeURIComponent(msg.key);
|
||||
|
||||
let content = render(opts, c);
|
||||
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
'<article id="' +
|
||||
name +
|
||||
'">' +
|
||||
'<header>' +
|
||||
'<figure>' +
|
||||
'<img alt="" ' +
|
||||
'src="' + opts.img_base + escape(msg.author.image) + '" ' +
|
||||
'height="50" width="50">' +
|
||||
'<figcaption>' +
|
||||
'<a class="ssb-avatar-name"' +
|
||||
' href="' + opts.base +
|
||||
escape(msg.value.author) +
|
||||
'"' +
|
||||
">" + msg.author.name + "</a>" +
|
||||
msgTimestamp(msg, opts.base + name) +
|
||||
'</figcaption>' +
|
||||
'</figure>' +
|
||||
'</header>' +
|
||||
render(opts, c) +
|
||||
"</article>"
|
||||
'<item>' +
|
||||
'<title>' + msg.author.name + ' | ' + c.type + '</title>' +
|
||||
'<description><![CDATA[' + content + ']]></description>' +
|
||||
'<link>' + opts.base + escape(name) + '</link>' +
|
||||
'<pubDate>' + new Date(msg.value.timestamp).toUTCString() + '</pubDate>' +
|
||||
'<guid>' + msg.key + '</guid>' +
|
||||
'</item>'
|
||||
);
|
||||
}
|
||||
|
||||
function msgTimestamp(msg, link) {
|
||||
var date = new Date(msg.value.timestamp);
|
||||
var isoStr = date.toISOString();
|
||||
return (
|
||||
'<time class="ssb-timestamp" datetime="' + isoStr + '">' +
|
||||
'<a ' +
|
||||
'href="' + link + '" ' +
|
||||
'title="' + isoStr + '" ' +
|
||||
'>' + formatDate(date) + '</a>' +
|
||||
'</time>'
|
||||
);
|
||||
return h('time.ssb-timestamp',
|
||||
{ datetime: isoStr },
|
||||
h('a',
|
||||
{ href: link,
|
||||
title: isoStr },
|
||||
formatDate(date)));
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
// return date.toISOString().replace('T', ' ')
|
||||
return htime(date);
|
||||
}
|
||||
|
||||
@@ -366,106 +390,109 @@ function render(opts, c) {
|
||||
var base = opts.base;
|
||||
if (c.type === "post") {
|
||||
var channel = c.channel
|
||||
? '<div class="top-right"><a href="' + base + 'channel/' + c.channel + '">#' + c.channel + "</a></div>"
|
||||
: "";
|
||||
return channel + renderPost(opts, c);
|
||||
? h('div.top-right',
|
||||
h('a',
|
||||
{ href: base + 'channel/' + c.channel },
|
||||
'#' + c.channel))
|
||||
: "";
|
||||
return [channel, renderPost(opts, c)];
|
||||
} else if (c.type == "vote" && c.vote.expression == "Dig") {
|
||||
var channel = c.channel
|
||||
? ' in <a href="' + base + 'channel/' + c.channel + '">#' + c.channel + "</a>"
|
||||
: "";
|
||||
? [' 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 ('<span class="status">' +
|
||||
'Liked ' +
|
||||
'<a href="' + base +
|
||||
c.vote.link +
|
||||
'">' +
|
||||
linkedText +
|
||||
"</a>" +
|
||||
channel +
|
||||
'</span>'
|
||||
);
|
||||
linkedText = c.vote.linkedText.substring(0, 75);
|
||||
return h('span.status',
|
||||
['Liked ',
|
||||
h('a', { href: base + c.vote.link }, linkedText),
|
||||
channel]);
|
||||
} else if (c.type == "vote") {
|
||||
var linkedText = "this";
|
||||
if (typeof c.vote.linkedText != "undefined")
|
||||
linkedText = c.vote.linkedText.substring(0, 75);
|
||||
return '<span class="status">' +
|
||||
'Voted <a href="' + base + c.vote.link + '">' + linkedText + "</a>" +
|
||||
'</span>';
|
||||
return h('span.status',
|
||||
['Voted ',
|
||||
h('a', { href: base + c.vote.link }, linkedText)]);
|
||||
} else if (c.type == "contact" && c.following) {
|
||||
var name = c.contact;
|
||||
if (typeof c.contactAbout != "undefined") name = c.contactAbout.name;
|
||||
return '<span class="status">' +
|
||||
'Followed <a href="' + base + c.contact + '">' + name + "</a>" +
|
||||
'</span>';
|
||||
if (typeof c.contactAbout != "undefined")
|
||||
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 (typeof c.contactAbout != "undefined") name = c.contactAbout.name;
|
||||
return '<span class="status">' +
|
||||
'Unfollowed <a href="' + base + c.contact + '">' + name + "</a>" +
|
||||
'</span>';
|
||||
if (typeof c.contactAbout != "undefined")
|
||||
name = c.contactAbout.name;
|
||||
return h('span.status',
|
||||
['Unfollowed ',
|
||||
h('a', { href: base + c.contact }, name)]);
|
||||
} else if (typeof c == "string") {
|
||||
return '<span class="status">' +
|
||||
"Wrote something private" +
|
||||
'</span>';
|
||||
return h('span.status', 'Wrote something private')
|
||||
}
|
||||
else if (c.type == "about") {
|
||||
return '<span class="status">' +
|
||||
"Changed something in about" +
|
||||
'</span>' +
|
||||
renderDefault(c);
|
||||
return [h('span.status', 'Changed something in about'),
|
||||
renderDefault(c)];
|
||||
}
|
||||
else if (c.type == "issue") {
|
||||
return '<span class="status">' +
|
||||
"Created a git issue" + (c.repoName != undefined ? " in repo " + c.repoName : "") + renderPost(opts, c) +
|
||||
'</span>';
|
||||
return [h('span.status',
|
||||
"Created a git issue" +
|
||||
(c.repoName != undefined ? " in repo " + c.repoName : ""),
|
||||
renderPost(opts, c))];
|
||||
}
|
||||
else if (c.type == "git-update") {
|
||||
return '<span class="status">' +
|
||||
"Did a git update " + (c.repoName != undefined ? " in repo " + c.repoName : "") +
|
||||
"<br/>" +
|
||||
(c.commits != undefined ? c.commits.map(com => { return "-" +com.title; }).join("<br/>") : "") +
|
||||
'</span>'
|
||||
return h('span.status',
|
||||
"Did a git update " +
|
||||
(c.repoName != undefined ? " in repo " + c.repoName : "") +
|
||||
'<br>' +
|
||||
(c.commits != undefined ?
|
||||
c.commits.map(com => { return "-" +com.title; }).join('<br>') : ""));
|
||||
}
|
||||
else if (c.type == "ssb-dns") {
|
||||
return '<span class="status">' +
|
||||
"Updated DNS" +
|
||||
'</span>' +
|
||||
renderDefault(c);
|
||||
return [h('span.status', 'Updated DNS'), renderDefault(c)];
|
||||
}
|
||||
else if (c.type == "pub") {
|
||||
return '<span class="status">' +
|
||||
"Connected to the pub " + c.address.host +
|
||||
'</span>'
|
||||
return h('span.status', 'Connected to the pub ' + c.address.host);
|
||||
}
|
||||
else if (c.type == "channel" && c.subscribed)
|
||||
return '<span class="status">' +
|
||||
'Subscribed to channel <a href="' + base + 'channel/' +
|
||||
c.channel +
|
||||
'">#' +
|
||||
c.channel +
|
||||
"</a>" +
|
||||
'</span>';
|
||||
return h('span.status',
|
||||
'Subscribed to channel ',
|
||||
h('a',
|
||||
{ href: base + 'channel/' + c.channel },
|
||||
'#' + c.channel));
|
||||
else if (c.type == "channel" && !c.subscribed)
|
||||
return '<span class="status">' +
|
||||
'Unsubscribed from channel <a href="' + base + 'channel/' +
|
||||
c.channel +
|
||||
'">#' +
|
||||
c.channel +
|
||||
"</a>" +
|
||||
'</span>';
|
||||
return h('span.status',
|
||||
'Unsubscribed from channel ',
|
||||
h('a',
|
||||
{ href: base + 'channel/' + c.channel },
|
||||
'#' + c.channel))
|
||||
else return renderDefault(c);
|
||||
}
|
||||
|
||||
function renderPost(opts, 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;
|
||||
});
|
||||
return '<section>' + marked(String(c.text), opts.marked) + "</section>";
|
||||
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');
|
||||
s.innerHTML = marked(String(c.text), opts.marked);
|
||||
return s;
|
||||
}
|
||||
|
||||
function renderDefault(c) {
|
||||
return "<pre>" + JSON.stringify(c, 0, 2) + "</pre>";
|
||||
return h('pre', JSON.stringify(c, 0, 2));
|
||||
}
|
||||
|
||||
function renderShowAll(showAll, url) {
|
||||
if (showAll)
|
||||
return '';
|
||||
else
|
||||
return '<br>' + h('a', { href : url + '?showAll' }, 'Show whole feed').outerHTML;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user