From 2c6be28d760b841c6c0fa8257cb34f95370add04 Mon Sep 17 00:00:00 2001 From: Tissevert Date: Sat, 2 Mar 2019 23:44:09 +0100 Subject: [PATCH] Implement metadata for articles and comments --- share/defaultWording.conf | 2 + share/js/domRenderer.js | 21 +++++---- share/js/main.js | 7 +-- share/js/{comments.js => metadata.js} | 66 ++++++++++++++++++++++----- share/js/template.js | 35 ++++++++++++++ src/Blog/Wording.hs | 11 +++-- src/Dom.hs | 8 ++-- 7 files changed, 120 insertions(+), 30 deletions(-) rename share/js/{comments.js => metadata.js} (50%) create mode 100644 share/js/template.js diff --git a/share/defaultWording.conf b/share/defaultWording.conf index 1c615dc..3ff6618 100644 --- a/share/defaultWording.conf +++ b/share/defaultWording.conf @@ -2,7 +2,9 @@ allLink = See all allPage = All articles allTaggedPage = All articles tagged ${tag} commentsSection = Comments +dateFormat = en-US latestLink = See only latest latestPage = Latest articles latestTaggedPage = Latest articles tagged ${tag} +metadata = ${?by ${author} ?}on ${date}${? tagged ${tags}?} tagsList = Tags diff --git a/share/js/domRenderer.js b/share/js/domRenderer.js index 0a86e9b..aa4a57b 100644 --- a/share/js/domRenderer.js +++ b/share/js/domRenderer.js @@ -8,9 +8,7 @@ function DomRenderer(modules) { function replaceMarkdown() { var div = document.getElementById('contents'); if(div.children[0] && div.children[0].tagName.toLowerCase() == 'article') { - var re = new RegExp('/' + blog.path.articlesPath + '/([^.]+)\.html'); - var key = decodeURI(document.location.pathname.replace(re, '$1')); - convertArticle(div.children[0], key); + convertArticle(div.children[0], true); } else { var articles = div.getElementsByClassName('articles')[0]; if(articles != undefined) { @@ -23,13 +21,15 @@ function DomRenderer(modules) { } } - function convertArticle(article, key) { + 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(key != undefined) { - modules.comments.get(key).forEach(article.appendChild.bind(article)); + if(comments) { + modules.metadata.getComments(article.id) + .forEach(article.appendChild.bind(article)); } } else { console.log('No content found for this article'); @@ -58,16 +58,17 @@ function DomRenderer(modules) { 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.comments.get(key))); + ].concat(limit != undefined ? [] : modules.metadata.getComments(key))); } function pageTitle(tag, all) { if(tag != undefined) { - var template = blog.wording[all ? 'allTaggedPage' : 'latestTaggedPage']; - return template.replace(/([^$]|^)\$(?:{tag}|tag([^a-zA-Z]|$))/, '$1' + tag + '$2'); + var template = all ? 'allTaggedPage' : 'latestTaggedPage'; + return modules.template.render(template, {tag: tag}); } else { return blog.wording[all ? 'allPage' : 'latestPage']; } diff --git a/share/js/main.js b/share/js/main.js index 4659256..d9fd9d1 100644 --- a/share/js/main.js +++ b/share/js/main.js @@ -5,9 +5,10 @@ window.addEventListener('load', function() { var fun = unitJS.Fun(); var md = new Remarkable(remarkableConfig); md.block.ruler.enable(['footnote']); - var comments = Comments({async: async, cache: cache, dom: dom}); - var domRenderer = DomRenderer({comments: comments, fun: fun, md: md, dom: dom}); - var navigation = Navigation({async: async, cache: cache, fun: fun, md: md, dom: dom, domRenderer: domRenderer}); + 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(); }); diff --git a/share/js/comments.js b/share/js/metadata.js similarity index 50% rename from share/js/comments.js rename to share/js/metadata.js index 37fa297..675b0d2 100644 --- a/share/js/comments.js +++ b/share/js/metadata.js @@ -1,4 +1,4 @@ -function Comments(modules) { +function Metadata(modules) { var comments = modules.cache.make(function(threadId) { return modules.async.bind( modules.async.http({method: 'GET', url: url(threadId)}), @@ -16,14 +16,15 @@ function Comments(modules) { ); }) return { - get: get + get: get, + getComments: getComments }; function url(threadId) { return blog.path.commentsAt + '/api/v1/statuses/' + threadId + '/context'; } - function get(articleKey) { + function getComments(articleKey) { var threadId = blog.articles[articleKey].metadata.comments; if(blog.path.commentsAt != undefined && threadId != undefined) { var ul = modules.dom.make('ul'); @@ -55,15 +56,58 @@ function Comments(modules) { function render(comments) { return comments.descendants.map(function(descendant) { return modules.dom.make('li', {}, [ - modules.dom.make('cite', {}, [ - modules.dom.make('a', { - href: descendant.account.url, - innerText: descendant.account.username - }), - modules.dom.make('p', {innerText: descendant.created_at}), - modules.dom.make('div', {innerHTML: descendant.content}) - ]) + modules.dom.make('div', { + innerHTML: modules.template.render('metadata', { + author: author(descendant.account.url, descendant.account.username), + date: date(descendant.created_at) + }) + }), + modules.dom.make('div', {innerHTML: descendant.content}) ]); }); } + + 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 '' + author + ''; + } + } + + 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 '' + tag + ''; + }).join(', '); + } + + function get(key) { + return modules.dom.make('div', { + innerHTML: modules.template.render('metadata', { + author: author(key), + date: date(key), + tags: tags(key) + }) + }); + } } diff --git a/share/js/template.js b/share/js/template.js new file mode 100644 index 0000000..aed435a --- /dev/null +++ b/share/js/template.js @@ -0,0 +1,35 @@ +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; + } + } + } +} diff --git a/src/Blog/Wording.hs b/src/Blog/Wording.hs index 9d3023e..f61b61f 100644 --- a/src/Blog/Wording.hs +++ b/src/Blog/Wording.hs @@ -28,22 +28,25 @@ data Wording = Wording { , allPage :: Text , allTaggedPage :: Template , commentsSection :: Text + , dateFormat :: Text , latestLink :: Text , latestPage :: Text , latestTaggedPage :: Template + , metadata :: Text , tagsList :: Text } keys :: [String] keys = [ - "allLink", "allPage", "allTaggedPage", "commentsSection" - , "latestLink", "latestPage", "latestTaggedPage", "tagsList" + "allLink", "allPage", "allTaggedPage", "commentsSection", "dateFormat" + , "latestLink", "latestPage", "latestTaggedPage", "metadata", "tagsList" ] values :: [Wording -> Text] values = [ allLink, allPage, showTemplate . allTaggedPage, commentsSection - , latestLink, latestPage, showTemplate . latestTaggedPage, tagsList + , dateFormat, latestLink, latestPage, showTemplate . latestTaggedPage + , metadata, tagsList ] texts :: Wording -> [Text] @@ -97,8 +100,10 @@ build arguments = do , allPage = wording ! "allPage" , allTaggedPage , commentsSection = wording ! "commentsSection" + , dateFormat = wording ! "dateFormat" , latestLink = wording ! "latestLink" , latestPage = wording ! "latestPage" , latestTaggedPage + , metadata = wording ! "metadata" , tagsList = wording ! "tagsList" } diff --git a/src/Dom.hs b/src/Dom.hs index 0ed66df..61067ae 100644 --- a/src/Dom.hs +++ b/src/Dom.hs @@ -28,7 +28,7 @@ class Page a where content :: a -> HtmlGenerator () instance Page Article where - card (Article {title, metadata}) = do + card (Article {title, Article.metadata}) = do description <- getDescription (Map.lookup "summary" metadata) makeCard title (pack description) (Map.lookup "featuredImage" metadata) where @@ -57,7 +57,7 @@ instance Page ArticlesList where article :: Bool -> Article -> HtmlGenerator () article raw (Article {key, body, title}) = do url <- absoluteLink . ( key <.> extension) <$> (Blog.get $path.$articlesPath) - article_ (do + article_ [id_ $ pack key] (do header_ (do a_ [href_ . pack $ url] . h1_ $ toHtml title ) @@ -76,7 +76,9 @@ makeCard title description image = do maybeImage = maybe (return ()) (og "image" . pack) tag :: String -> HtmlGenerator () -tag tagName = li_ (a_ [href_ . pack $ absoluteLink tagName] $ toHtml tagName) +tag tagName = li_ ( + a_ [href_ . pack $ absoluteLink tagName, class_ "tag"] $ toHtml tagName + ) defaultBanner :: HtmlGenerator () defaultBanner = do