Adapt hablo's JS to SJW and stop requiring unitJS separately (since it's gonna get pulled in by sjw at compile time)
This commit is contained in:
parent
36651ddc38
commit
add68897ad
12 changed files with 418 additions and 416 deletions
98
share/js/DomRenderer.js
Normal file
98
share/js/DomRenderer.js
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import * as Dom from UnitJS.Dom;
|
||||||
|
import * as Fun from UnitJS.Fun;
|
||||||
|
import Metadata;
|
||||||
|
import Template;
|
||||||
|
import Remarkable;
|
||||||
|
|
||||||
|
return {
|
||||||
|
article: article,
|
||||||
|
articlesList: articlesList,
|
||||||
|
replaceMarkdown: replaceMarkdown
|
||||||
|
};
|
||||||
|
|
||||||
|
function replaceMarkdown() {
|
||||||
|
var div = document.getElementById('contents');
|
||||||
|
if(div.children[0] && div.children[0].tagName.toLowerCase() == 'article') {
|
||||||
|
convertArticle(div.children[0], true);
|
||||||
|
} else {
|
||||||
|
var articles = div.getElementsByClassName('articles')[0];
|
||||||
|
if(articles != undefined) {
|
||||||
|
for(var i = 0; i < articles.children.length; i++) {
|
||||||
|
convertArticle(articles.children[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No articles found for this page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertArticle(article, comments) {
|
||||||
|
var header = article.getElementsByTagName('header')[0];
|
||||||
|
header.appendChild(Metadata.get(article.id));
|
||||||
|
var text = article.getElementsByTagName('pre')[0];
|
||||||
|
if(text != undefined) {
|
||||||
|
article.replaceChild(getDiv(text.innerText), text);
|
||||||
|
if(comments) {
|
||||||
|
Metadata.getComments(article.id)
|
||||||
|
.forEach(article.appendChild.bind(article));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No content found for this article');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiv(markdown) {
|
||||||
|
var d= Dom.make('div', {
|
||||||
|
innerHTML: Remarkable.md.render(markdown)
|
||||||
|
});
|
||||||
|
var scripts = d.getElementsByTagName('script');
|
||||||
|
for(var i = 0; i < scripts.length; i++) {
|
||||||
|
var run = Dom.make('script',
|
||||||
|
{type: 'text/javascript', src: scripts[i].src, textContent: scripts[i].textContent}
|
||||||
|
);
|
||||||
|
scripts[i].parentNode.replaceChild(run, scripts[i]);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
function article(key, markdown, limit) {
|
||||||
|
var url = ["", blog.path.articlesPath, key + (limit != undefined ? '.html' : '.md')].join('/');
|
||||||
|
var lines = markdown.split(/\n/).slice(blog.articles[key].bodyOffset);
|
||||||
|
var div = getDiv(lines.slice(0, limit).join('\n'));
|
||||||
|
return Dom.make('article', {}, [
|
||||||
|
Dom.make('header', {}, [
|
||||||
|
Dom.make('a', {href: url}, [
|
||||||
|
Dom.make('h1', {innerText: blog.articles[key].title})
|
||||||
|
]),
|
||||||
|
Metadata.get(key)
|
||||||
|
]),
|
||||||
|
div
|
||||||
|
].concat(limit != undefined ? [] : Metadata.getComments(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageTitle(tag, all) {
|
||||||
|
if(tag != undefined) {
|
||||||
|
var template = all ? 'allTaggedPage' : 'latestTaggedPage';
|
||||||
|
return Template.render(template, {tag: tag});
|
||||||
|
} else {
|
||||||
|
return blog.wording[all ? 'allPage' : 'latestPage'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function otherUrl(tag, all) {
|
||||||
|
var path = [tag, all ? null : 'all.html'];
|
||||||
|
return '/' + path.filter(Fun.defined).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function articlesList(tag, all) {
|
||||||
|
return function(articlePreviews) {
|
||||||
|
return [
|
||||||
|
Dom.make('h2', {innerText: pageTitle(tag, all)}),
|
||||||
|
Dom.make('a', {
|
||||||
|
innerText: all ? blog.wording.latestLink : blog.wording.allLink,
|
||||||
|
href: otherUrl(tag, all)
|
||||||
|
}),
|
||||||
|
Dom.make('div', {class: 'articles'}, articlePreviews.filter(Fun.defined))
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
5
share/js/Main.js
Normal file
5
share/js/Main.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import replaceMarkdown from DomRenderer;
|
||||||
|
import hijackLinks from Navigation;
|
||||||
|
|
||||||
|
replaceMarkdown();
|
||||||
|
hijackLinks();
|
157
share/js/Metadata.js
Normal file
157
share/js/Metadata.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import * as Async from UnitJS.Async;
|
||||||
|
import * as Cache from UnitJS.Cache;
|
||||||
|
import * as Dom from UnitJS.Dom;
|
||||||
|
|
||||||
|
var comments = Cache.make(function(threadId) {
|
||||||
|
return Async.bind(
|
||||||
|
Async.parallel(
|
||||||
|
getJSON(url(threadId)),
|
||||||
|
getJSON(url(threadId) + '/context'),
|
||||||
|
),
|
||||||
|
Async.map(function(t) {
|
||||||
|
return [renderLink(t[0]), renderAnswers(t[1])];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
get: get,
|
||||||
|
getComments: getComments
|
||||||
|
};
|
||||||
|
|
||||||
|
function url(threadId) {
|
||||||
|
return blog.urls.comments + '/api/v1/statuses/' + threadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJSON(url) {
|
||||||
|
return Async.bind(
|
||||||
|
Async.http({method: 'GET', url: url}),
|
||||||
|
function(queryResult) {
|
||||||
|
if(queryResult.status == 200) {
|
||||||
|
try {
|
||||||
|
return Async.wrap(JSON.parse(queryResult.responseText));
|
||||||
|
} catch(e) {
|
||||||
|
return Async.fail('Server returned invalid JSON for ' + url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Async.fail('Could not load page ' + url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComments(articleKey) {
|
||||||
|
var threadId = blog.articles[articleKey].metadata.comments;
|
||||||
|
if(blog.urls.comments != undefined && threadId != undefined) {
|
||||||
|
var ul = Dom.make('ul');
|
||||||
|
var div = emptySection(ul);
|
||||||
|
Async.run(
|
||||||
|
Async.bind(
|
||||||
|
comments.get(threadId), Async.map(populateComments(div, ul))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return [div];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateComments(div, ul) {
|
||||||
|
return function(apiResults) {
|
||||||
|
var post = apiResults[0], comments = apiResults[1];
|
||||||
|
div.appendChild(post);
|
||||||
|
comments.forEach(function(comment) {ul.appendChild(comment);});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptySection(ul) {
|
||||||
|
return Dom.make('div', {class: 'comments'}, [
|
||||||
|
Dom.make('h2', {innerText: blog.wording.commentsSection}),
|
||||||
|
ul
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLink(post) {
|
||||||
|
return Dom.make('a', {
|
||||||
|
href: post.url,
|
||||||
|
innerText: blog.wording.commentsLink
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContent(descendant) {
|
||||||
|
return descendant.content.replace(/:([^: ]+):/g, function(pattern, shortcode) {
|
||||||
|
var emoji = descendant.emojis.find(function(e) {return e.shortcode == shortcode;});
|
||||||
|
if(emoji) {
|
||||||
|
return [
|
||||||
|
'<img title=', shortcode, ' alt=', shortcode, ' src=', emoji.url, ' class="emoji"/>'
|
||||||
|
].join('"');
|
||||||
|
} else {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAnswers(comments) {
|
||||||
|
return comments.descendants.map(function(descendant) {
|
||||||
|
return Dom.make('li', {}, [
|
||||||
|
Dom.make('a', {href: descendant.account.url}, [
|
||||||
|
Dom.make('img', {
|
||||||
|
src: descendant.account.avatar,
|
||||||
|
alt: descendant.account.username + "'s profile picture"
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
Dom.make('div', {
|
||||||
|
class: "metadata",
|
||||||
|
innerHTML: modules.template.render('metadata', {
|
||||||
|
author: author(descendant.account.url, descendant.account.username),
|
||||||
|
date: date(descendant.created_at)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
Dom.make('div', {innerHTML: getContent(descendant)})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function author(key, name) {
|
||||||
|
var authorUrl = key;
|
||||||
|
if(blog.articles[key] != undefined) {
|
||||||
|
authorUrl = blog.articles[key].metadata.author;
|
||||||
|
}
|
||||||
|
if(authorUrl) {
|
||||||
|
var author = name || authorUrl.replace(/.*\//, '');
|
||||||
|
return '<a href="' + authorUrl + '">' + author + '</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function date(key) {
|
||||||
|
if(blog.articles[key] != undefined) {
|
||||||
|
var date = new Date(blog.articles[key].metadata.date * 1000);
|
||||||
|
} else {
|
||||||
|
var date = new Date(key);
|
||||||
|
}
|
||||||
|
var format = blog.wording.dateFormat;
|
||||||
|
if(format[0] != '[') {
|
||||||
|
if(format[0] != '"') {
|
||||||
|
format = '"' + format + '"';
|
||||||
|
}
|
||||||
|
format = '[' + format + ']';
|
||||||
|
}
|
||||||
|
return Date.prototype.toLocaleDateString.apply(date, JSON.parse(format));
|
||||||
|
}
|
||||||
|
|
||||||
|
function tags(key) {
|
||||||
|
var tags = blog.articles[key].tagged;
|
||||||
|
return tags.length < 1 ? null : tags.map(function(tag) {
|
||||||
|
return '<a class="tag" href="/' + tag + '">' + tag + '</a>';
|
||||||
|
}).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(key) {
|
||||||
|
return Dom.make('div', {
|
||||||
|
class: "metadata",
|
||||||
|
innerHTML: modules.template.render('metadata', {
|
||||||
|
author: author(key),
|
||||||
|
date: date(key),
|
||||||
|
tags: tags(key)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
117
share/js/Navigation.js
Normal file
117
share/js/Navigation.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import * as Async from UnitJS.Async;
|
||||||
|
import * as Cache from UnitJS.Cache;
|
||||||
|
import * as Dom from UnitJS.Dom;
|
||||||
|
import * as Fun from UnitJS.Fun;
|
||||||
|
import {article, articlesList} from DomRenderer;
|
||||||
|
|
||||||
|
var articles = Cache.make(function(key) {
|
||||||
|
var url = ["", blog.path.articlesPath, key + '.md'].join('/');
|
||||||
|
return Async.bind(
|
||||||
|
Async.http({method: 'GET', url: url}),
|
||||||
|
function(queryResult) {
|
||||||
|
if(queryResult.status == 200) {
|
||||||
|
return Async.wrap(queryResult.responseText);
|
||||||
|
} else {
|
||||||
|
return Async.fail(
|
||||||
|
"Could not load article " + url + " (" + queryResult.status + " " + queryResult.statusText + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', function(e) {
|
||||||
|
if(e.state != undefined) {
|
||||||
|
navigate(e.state.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
history.replaceState({url: window.location.pathname}, 'Blog - title', window.location.pathname);
|
||||||
|
return {
|
||||||
|
hijackLinks: hijackLinks
|
||||||
|
};
|
||||||
|
|
||||||
|
function hijackLinks(domElem) {
|
||||||
|
domElem = domElem || document;
|
||||||
|
var links = domElem.getElementsByTagName('a');
|
||||||
|
for(var i = 0; i < links.length; i++) {
|
||||||
|
var a = links[i];
|
||||||
|
var href = a.getAttribute("href");
|
||||||
|
if((href[0] == "/" && href.slice(-3) != ".md") || href[0] == "#") {
|
||||||
|
a.addEventListener('click', visit(a.getAttribute("href")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function visit(url) {
|
||||||
|
return function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if(url[0] == '#') {
|
||||||
|
window.location = url;
|
||||||
|
history.replaceState({url: window.location.pathname}, 'Blog - title', url);
|
||||||
|
} else {
|
||||||
|
navigate(url);
|
||||||
|
history.pushState({url: url}, 'Blog - title', url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigate(url) {
|
||||||
|
var path = decodeURI(url).split("/").slice(1);
|
||||||
|
if(blog.tags[path[0]] != undefined) {
|
||||||
|
show(getArticlesList(path[0], path[1] == "all.html"));
|
||||||
|
} else if(path[0] == blog.path.articlesPath) {
|
||||||
|
show(getArticle(path[1].replace(/\.html$/, '')));
|
||||||
|
} else {
|
||||||
|
show(getArticlesList(null, path[0] == "all.html"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArticle(key) {
|
||||||
|
return Async.bind(
|
||||||
|
articles.get(key),
|
||||||
|
Async.map(
|
||||||
|
function(contents) {return [article(key, contents)];}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preview(key) {
|
||||||
|
return Async.bind(
|
||||||
|
articles.get(key),
|
||||||
|
function(contents) {
|
||||||
|
return Async.wrap(
|
||||||
|
article(key, contents, blog.skin.previewLinesCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function articleIds(tag, all) {
|
||||||
|
var ids = tag != undefined ? blog.tags[tag] : Object.keys(blog.articles);
|
||||||
|
var reverseDate = function (id) {return -blog.articles[id].metadata.date;};
|
||||||
|
ids.sort(Fun.compare(reverseDate));
|
||||||
|
return ids.slice(0, all ? undefined : blog.skin.previewArticlesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArticlesList(tag, all) {
|
||||||
|
return Async.bind(
|
||||||
|
Async.parallel.apply(null, articleIds(tag, all).map(preview)),
|
||||||
|
Async.map(articlesList(tag, all))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(contents) {
|
||||||
|
Async.run(
|
||||||
|
Async.bind(
|
||||||
|
contents,
|
||||||
|
Async.map(function (domElems) {
|
||||||
|
domElems = domElems.filter(Fun.defined);
|
||||||
|
var div = document.getElementById('contents');
|
||||||
|
Dom.clear(div);
|
||||||
|
for(var i = 0; i < domElems.length; i++) {
|
||||||
|
div.appendChild(domElems[i]);
|
||||||
|
}
|
||||||
|
hijackLinks(div);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
8
share/js/Remarkable.js
Normal file
8
share/js/Remarkable.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import remarkableConfig from Hablo.Config;
|
||||||
|
|
||||||
|
var md = new Remarkable(remarkableConfig);
|
||||||
|
md.block.ruler.enable(['footnote']);
|
||||||
|
|
||||||
|
return {
|
||||||
|
md: md
|
||||||
|
};
|
33
share/js/Template.js
Normal file
33
share/js/Template.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
return {
|
||||||
|
render: render
|
||||||
|
};
|
||||||
|
|
||||||
|
function render(template, environment) {
|
||||||
|
if(blog.wording[template] != undefined) {
|
||||||
|
var template = blog.wording[template];
|
||||||
|
}
|
||||||
|
template = template.replace(/{\?((?:[^?]|\?[^}])*)\?}/g, renderSub(environment));
|
||||||
|
var failed = [false];
|
||||||
|
var result = template.replace(
|
||||||
|
/([^$]|^)\$(?:{(\w+)}|(\w+)\b)/g,
|
||||||
|
substitute(environment, failed)
|
||||||
|
);
|
||||||
|
return failed[0] ? null : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSub(environment) {
|
||||||
|
return function(_, sub) {
|
||||||
|
return render(sub, environment) || '';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function substitute(environment, failed) {
|
||||||
|
return function(_, before, bracketed, raw) {
|
||||||
|
var replaced = environment[bracketed || raw];
|
||||||
|
if(replaced != undefined) {
|
||||||
|
return before + replaced;
|
||||||
|
} else {
|
||||||
|
failed[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,94 +0,0 @@
|
||||||
function DomRenderer(modules) {
|
|
||||||
return {
|
|
||||||
article: article,
|
|
||||||
articlesList: articlesList,
|
|
||||||
replaceMarkdown: replaceMarkdown
|
|
||||||
};
|
|
||||||
|
|
||||||
function replaceMarkdown() {
|
|
||||||
var div = document.getElementById('contents');
|
|
||||||
if(div.children[0] && div.children[0].tagName.toLowerCase() == 'article') {
|
|
||||||
convertArticle(div.children[0], true);
|
|
||||||
} else {
|
|
||||||
var articles = div.getElementsByClassName('articles')[0];
|
|
||||||
if(articles != undefined) {
|
|
||||||
for(var i = 0; i < articles.children.length; i++) {
|
|
||||||
convertArticle(articles.children[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No articles found for this page');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertArticle(article, comments) {
|
|
||||||
var header = article.getElementsByTagName('header')[0];
|
|
||||||
header.appendChild(modules.metadata.get(article.id));
|
|
||||||
var text = article.getElementsByTagName('pre')[0];
|
|
||||||
if(text != undefined) {
|
|
||||||
article.replaceChild(getDiv(text.innerText), text);
|
|
||||||
if(comments) {
|
|
||||||
modules.metadata.getComments(article.id)
|
|
||||||
.forEach(article.appendChild.bind(article));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No content found for this article');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDiv(markdown) {
|
|
||||||
var d= modules.dom.make('div', {
|
|
||||||
innerHTML: modules.md.render(markdown)
|
|
||||||
});
|
|
||||||
var scripts = d.getElementsByTagName('script');
|
|
||||||
for(var i = 0; i < scripts.length; i++) {
|
|
||||||
var run = modules.dom.make('script',
|
|
||||||
{type: 'text/javascript', src: scripts[i].src, textContent: scripts[i].textContent}
|
|
||||||
);
|
|
||||||
scripts[i].parentNode.replaceChild(run, scripts[i]);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
function article(key, markdown, limit) {
|
|
||||||
var url = ["", blog.path.articlesPath, key + (limit != undefined ? '.html' : '.md')].join('/');
|
|
||||||
var lines = markdown.split(/\n/).slice(blog.articles[key].bodyOffset);
|
|
||||||
var div = getDiv(lines.slice(0, limit).join('\n'));
|
|
||||||
return modules.dom.make('article', {}, [
|
|
||||||
modules.dom.make('header', {}, [
|
|
||||||
modules.dom.make('a', {href: url}, [
|
|
||||||
modules.dom.make('h1', {innerText: blog.articles[key].title})
|
|
||||||
]),
|
|
||||||
modules.metadata.get(key)
|
|
||||||
]),
|
|
||||||
div
|
|
||||||
].concat(limit != undefined ? [] : modules.metadata.getComments(key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function pageTitle(tag, all) {
|
|
||||||
if(tag != undefined) {
|
|
||||||
var template = all ? 'allTaggedPage' : 'latestTaggedPage';
|
|
||||||
return modules.template.render(template, {tag: tag});
|
|
||||||
} else {
|
|
||||||
return blog.wording[all ? 'allPage' : 'latestPage'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function otherUrl(tag, all) {
|
|
||||||
var path = [tag, all ? null : 'all.html'];
|
|
||||||
return '/' + path.filter(modules.fun.defined).join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function articlesList(tag, all) {
|
|
||||||
return function(articlePreviews) {
|
|
||||||
return [
|
|
||||||
modules.dom.make('h2', {innerText: pageTitle(tag, all)}),
|
|
||||||
modules.dom.make('a', {
|
|
||||||
innerText: all ? blog.wording.latestLink : blog.wording.allLink,
|
|
||||||
href: otherUrl(tag, all)
|
|
||||||
}),
|
|
||||||
modules.dom.make('div', {class: 'articles'}, articlePreviews.filter(modules.fun.defined))
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
window.addEventListener('load', function() {
|
|
||||||
var async = unitJS.Async();
|
|
||||||
var cache = unitJS.Cache();
|
|
||||||
var dom = unitJS.Dom();
|
|
||||||
var fun = unitJS.Fun();
|
|
||||||
var md = new Remarkable(remarkableConfig);
|
|
||||||
md.block.ruler.enable(['footnote']);
|
|
||||||
var template = Template();
|
|
||||||
var metadata = Metadata({async: async, cache: cache, dom: dom, fun:fun, template: template});
|
|
||||||
var domRenderer = DomRenderer({dom: dom, fun: fun, md: md, metadata: metadata, template: template});
|
|
||||||
var navigation = Navigation({async: async, cache: cache, dom: dom, domRenderer: domRenderer, fun: fun, md: md});
|
|
||||||
domRenderer.replaceMarkdown();
|
|
||||||
navigation.hijackLinks();
|
|
||||||
});
|
|
|
@ -1,155 +0,0 @@
|
||||||
function Metadata(modules) {
|
|
||||||
var comments = modules.cache.make(function(threadId) {
|
|
||||||
return modules.async.bind(
|
|
||||||
modules.async.parallel(
|
|
||||||
getJSON(url(threadId)),
|
|
||||||
getJSON(url(threadId) + '/context'),
|
|
||||||
),
|
|
||||||
modules.async.map(function(t) {
|
|
||||||
return [renderLink(t[0]), renderAnswers(t[1])];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
get: get,
|
|
||||||
getComments: getComments
|
|
||||||
};
|
|
||||||
|
|
||||||
function url(threadId) {
|
|
||||||
return blog.urls.comments + '/api/v1/statuses/' + threadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJSON(url) {
|
|
||||||
return modules.async.bind(
|
|
||||||
modules.async.http({method: 'GET', url: url}),
|
|
||||||
function(queryResult) {
|
|
||||||
if(queryResult.status == 200) {
|
|
||||||
try {
|
|
||||||
return modules.async.wrap(JSON.parse(queryResult.responseText));
|
|
||||||
} catch(e) {
|
|
||||||
return modules.async.fail('Server returned invalid JSON for ' + url);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return modules.async.fail('Could not load page ' + url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComments(articleKey) {
|
|
||||||
var threadId = blog.articles[articleKey].metadata.comments;
|
|
||||||
if(blog.urls.comments != undefined && threadId != undefined) {
|
|
||||||
var ul = modules.dom.make('ul');
|
|
||||||
var div = emptySection(ul);
|
|
||||||
modules.async.run(
|
|
||||||
modules.async.bind(
|
|
||||||
comments.get(threadId), modules.async.map(populateComments(div, ul))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return [div];
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateComments(div, ul) {
|
|
||||||
return function(apiResults) {
|
|
||||||
var post = apiResults[0], comments = apiResults[1];
|
|
||||||
div.appendChild(post);
|
|
||||||
comments.forEach(function(comment) {ul.appendChild(comment);});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptySection(ul) {
|
|
||||||
return modules.dom.make('div', {class: 'comments'}, [
|
|
||||||
modules.dom.make('h2', {innerText: blog.wording.commentsSection}),
|
|
||||||
ul
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLink(post) {
|
|
||||||
return modules.dom.make('a', {
|
|
||||||
href: post.url,
|
|
||||||
innerText: blog.wording.commentsLink
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContent(descendant) {
|
|
||||||
return descendant.content.replace(/:([^: ]+):/g, function(pattern, shortcode) {
|
|
||||||
var emoji = descendant.emojis.find(function(e) {return e.shortcode == shortcode;});
|
|
||||||
if(emoji) {
|
|
||||||
return [
|
|
||||||
'<img title=', shortcode, ' alt=', shortcode, ' src=', emoji.url, ' class="emoji"/>'
|
|
||||||
].join('"');
|
|
||||||
} else {
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAnswers(comments) {
|
|
||||||
return comments.descendants.map(function(descendant) {
|
|
||||||
return modules.dom.make('li', {}, [
|
|
||||||
modules.dom.make('a', {href: descendant.account.url}, [
|
|
||||||
modules.dom.make('img', {
|
|
||||||
src: descendant.account.avatar,
|
|
||||||
alt: descendant.account.username + "'s profile picture"
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
modules.dom.make('div', {
|
|
||||||
class: "metadata",
|
|
||||||
innerHTML: modules.template.render('metadata', {
|
|
||||||
author: author(descendant.account.url, descendant.account.username),
|
|
||||||
date: date(descendant.created_at)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
modules.dom.make('div', {innerHTML: getContent(descendant)})
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function author(key, name) {
|
|
||||||
var authorUrl = key;
|
|
||||||
if(blog.articles[key] != undefined) {
|
|
||||||
authorUrl = blog.articles[key].metadata.author;
|
|
||||||
}
|
|
||||||
if(authorUrl) {
|
|
||||||
var author = name || authorUrl.replace(/.*\//, '');
|
|
||||||
return '<a href="' + authorUrl + '">' + author + '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function date(key) {
|
|
||||||
if(blog.articles[key] != undefined) {
|
|
||||||
var date = new Date(blog.articles[key].metadata.date * 1000);
|
|
||||||
} else {
|
|
||||||
var date = new Date(key);
|
|
||||||
}
|
|
||||||
var format = blog.wording.dateFormat;
|
|
||||||
if(format[0] != '[') {
|
|
||||||
if(format[0] != '"') {
|
|
||||||
format = '"' + format + '"';
|
|
||||||
}
|
|
||||||
format = '[' + format + ']';
|
|
||||||
}
|
|
||||||
return Date.prototype.toLocaleDateString.apply(date, JSON.parse(format));
|
|
||||||
}
|
|
||||||
|
|
||||||
function tags(key) {
|
|
||||||
var tags = blog.articles[key].tagged;
|
|
||||||
return tags.length < 1 ? null : tags.map(function(tag) {
|
|
||||||
return '<a class="tag" href="/' + tag + '">' + tag + '</a>';
|
|
||||||
}).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(key) {
|
|
||||||
return modules.dom.make('div', {
|
|
||||||
class: "metadata",
|
|
||||||
innerHTML: modules.template.render('metadata', {
|
|
||||||
author: author(key),
|
|
||||||
date: date(key),
|
|
||||||
tags: tags(key)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
function Navigation(modules) {
|
|
||||||
var articles = modules.cache.make(function(key) {
|
|
||||||
var url = ["", blog.path.articlesPath, key + '.md'].join('/');
|
|
||||||
return modules.async.bind(
|
|
||||||
modules.async.http({method: 'GET', url: url}),
|
|
||||||
function(queryResult) {
|
|
||||||
if(queryResult.status == 200) {
|
|
||||||
return modules.async.wrap(queryResult.responseText);
|
|
||||||
} else {
|
|
||||||
return modules.async.fail(
|
|
||||||
"Could not load article " + url + " (" + queryResult.status + " " + queryResult.statusText + ")"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
window.addEventListener('popstate', function(e) {
|
|
||||||
if(e.state != undefined) {
|
|
||||||
navigate(e.state.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
history.replaceState({url: window.location.pathname}, 'Blog - title', window.location.pathname);
|
|
||||||
return {
|
|
||||||
hijackLinks: hijackLinks
|
|
||||||
};
|
|
||||||
|
|
||||||
function hijackLinks(domElem) {
|
|
||||||
domElem = domElem || document;
|
|
||||||
var links = domElem.getElementsByTagName('a');
|
|
||||||
for(var i = 0; i < links.length; i++) {
|
|
||||||
var a = links[i];
|
|
||||||
var href = a.getAttribute("href");
|
|
||||||
if((href[0] == "/" && href.slice(-3) != ".md") || href[0] == "#") {
|
|
||||||
a.addEventListener('click', visit(a.getAttribute("href")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function visit(url) {
|
|
||||||
return function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if(url[0] == '#') {
|
|
||||||
window.location = url;
|
|
||||||
history.replaceState({url: window.location.pathname}, 'Blog - title', url);
|
|
||||||
} else {
|
|
||||||
navigate(url);
|
|
||||||
history.pushState({url: url}, 'Blog - title', url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigate(url) {
|
|
||||||
var path = decodeURI(url).split("/").slice(1);
|
|
||||||
if(blog.tags[path[0]] != undefined) {
|
|
||||||
show(getArticlesList(path[0], path[1] == "all.html"));
|
|
||||||
} else if(path[0] == blog.path.articlesPath) {
|
|
||||||
show(getArticle(path[1].replace(/\.html$/, '')));
|
|
||||||
} else {
|
|
||||||
show(getArticlesList(null, path[0] == "all.html"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArticle(key) {
|
|
||||||
return modules.async.bind(
|
|
||||||
articles.get(key),
|
|
||||||
modules.async.map(
|
|
||||||
function(contents) {return [modules.domRenderer.article(key, contents)];}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function preview(key) {
|
|
||||||
return modules.async.bind(
|
|
||||||
articles.get(key),
|
|
||||||
function(contents) {
|
|
||||||
return modules.async.wrap(
|
|
||||||
modules.domRenderer.article(
|
|
||||||
key,
|
|
||||||
contents,
|
|
||||||
blog.skin.previewLinesCount
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function articleIds(tag, all) {
|
|
||||||
var ids = tag != undefined ? blog.tags[tag] : Object.keys(blog.articles);
|
|
||||||
var reverseDate = function (id) {return -blog.articles[id].metadata.date;};
|
|
||||||
ids.sort(modules.fun.compare(reverseDate));
|
|
||||||
return ids.slice(0, all ? undefined : blog.skin.previewArticlesCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArticlesList(tag, all) {
|
|
||||||
return modules.async.bind(
|
|
||||||
modules.async.parallel.apply(null, articleIds(tag, all).map(preview)),
|
|
||||||
modules.async.map(modules.domRenderer.articlesList(tag, all))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(contents) {
|
|
||||||
modules.async.run(
|
|
||||||
modules.async.bind(
|
|
||||||
contents,
|
|
||||||
modules.async.map(function (domElems) {
|
|
||||||
domElems = domElems.filter(modules.fun.defined);
|
|
||||||
var div = document.getElementById('contents');
|
|
||||||
modules.dom.clear(div);
|
|
||||||
for(var i = 0; i < domElems.length; i++) {
|
|
||||||
div.appendChild(domElems[i]);
|
|
||||||
}
|
|
||||||
hijackLinks(div);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
function Template() {
|
|
||||||
return {
|
|
||||||
render: render
|
|
||||||
};
|
|
||||||
|
|
||||||
function render(template, environment) {
|
|
||||||
if(blog.wording[template] != undefined) {
|
|
||||||
var template = blog.wording[template];
|
|
||||||
}
|
|
||||||
template = template.replace(/{\?((?:[^?]|\?[^}])*)\?}/g, renderSub(environment));
|
|
||||||
var failed = [false];
|
|
||||||
var result = template.replace(
|
|
||||||
/([^$]|^)\$(?:{(\w+)}|(\w+)\b)/g,
|
|
||||||
substitute(environment, failed)
|
|
||||||
);
|
|
||||||
return failed[0] ? null : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSub(environment) {
|
|
||||||
return function(_, sub) {
|
|
||||||
return render(sub, environment) || '';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function substitute(environment, failed) {
|
|
||||||
return function(_, before, bracketed, raw) {
|
|
||||||
var replaced = environment[bracketed || raw];
|
|
||||||
if(replaced != undefined) {
|
|
||||||
return before + replaced;
|
|
||||||
} else {
|
|
||||||
failed[0] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -77,7 +77,6 @@ page aPage =
|
||||||
head_ (do
|
head_ (do
|
||||||
meta_ [charset_ "utf-8"]
|
meta_ [charset_ "utf-8"]
|
||||||
title_ . toHtml =<< Blog.get name
|
title_ . toHtml =<< Blog.get name
|
||||||
script_ [src_ "/js/unit.js"] empty
|
|
||||||
script_ [src_ "/js/remarkable.min.js"] empty
|
script_ [src_ "/js/remarkable.min.js"] empty
|
||||||
script_ [src_ "/js/hablo.js"] empty
|
script_ [src_ "/js/hablo.js"] empty
|
||||||
optional faviconLink =<< (Blog.get $skin.$favicon)
|
optional faviconLink =<< (Blog.get $skin.$favicon)
|
||||||
|
|
Loading…
Reference in a new issue