diff options
-rw-r--r-- | config/default.toml | 5 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/app.js | 13 | ||||
-rw-r--r-- | src/domain/posts.js | 2 | ||||
-rw-r--r-- | src/responders.js | 42 | ||||
-rw-r--r-- | src/templates/feed.xml | 24 | ||||
-rw-r--r-- | test/app.test.js | 13 | ||||
-rw-r--r-- | yarn.lock | 33 |
8 files changed, 103 insertions, 31 deletions
diff --git a/config/default.toml b/config/default.toml index b52674c..5dceae1 100644 --- a/config/default.toml +++ b/config/default.toml @@ -3,6 +3,7 @@ port = 3000 [site] description = "Nobody in particular" +baseURL = "http://localhost:3000/" [[site.nav]] text = "Home" @@ -20,6 +21,10 @@ text = "johndoe@johndoe.org" url = "https://twitter.com/johndoe" text = "Twitter" +[feed] +originalDomainName = "johndoe.com" +domainStartDate = "2016-01-01" + [posts] folder = "./posts" diff --git a/package.json b/package.json index 20d7918..4d1ebe6 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "gray-matter": "^2.1.1", "highland": "^2.11.0", "highlight.js": "^9.12.0", - "hyperfast": "^2.1.0", + "hyperfast": "^2.2.0", "indent-string": "^3.1.0", "koa": "^2.2.0", "koa-helmet": "^3.2.0", diff --git a/src/app.js b/src/app.js index 912dda2..a323b9c 100644 --- a/src/app.js +++ b/src/app.js @@ -13,6 +13,13 @@ const config = require("./modules/config.js"); const Router = require("koa-router"); const router = new Router(); +const makeTagURI = (authority, startDate) => specific => + `tag:${authority},${startDate}:${specific}`; + +app.context.makeTagURI = makeTagURI( + config.feed.originalDomainName, + config.feed.domainStartDate +); app.context.getURL = router.url.bind(router); module.exports = async function() { @@ -35,6 +42,12 @@ module.exports = async function() { ); router.get( + "feed", + "/index.xml", + actions.posts(config, responders.feed, Posts.posts) + ); + + router.get( "post", "/post/:filename", actions.post(config, responders.post, Posts.posts) diff --git a/src/domain/posts.js b/src/domain/posts.js index 98488ba..ab35518 100644 --- a/src/domain/posts.js +++ b/src/domain/posts.js @@ -86,7 +86,9 @@ function taxonomise(taxonomies, posts) { module.exports = async function(config, getURL) { const posts = await getFolder(config.folder, getURL); const taxonomies = taxonomise(config.taxonomies, posts); + const lastPostDate = Math.max(Array.from(posts.values(), p => p.date)); return { + lastPostDate, posts, taxonomies, get diff --git a/src/responders.js b/src/responders.js index 8f084a3..f4ea0a6 100644 --- a/src/responders.js +++ b/src/responders.js @@ -1,6 +1,7 @@ "use strict"; const fs = require("fs"); +const URL = require("url").URL; const Case = require("case"); const hyperfast = require("hyperfast"); const indent = require("indent-string"); @@ -25,7 +26,7 @@ const findPostContent = /^(\s+)<div class="e-content/m; const postIndentLevel = baseIndentLevel + getTemplateIndent(findPostContent, "post.html"); -function indentForTemplate(text, indentLevel) { +function indentForTemplate(text, indentLevel = 0) { return indent(text, indentLevel).slice(indentLevel).replace(/\n+$/, ""); } @@ -37,7 +38,8 @@ const templates = { layout: templateReader("layout.html"), home: templateReader("home.html", baseIndentLevel), post: templateReader("post.html", baseIndentLevel), - list: templateReader("list.html", baseIndentLevel) + list: templateReader("list.html", baseIndentLevel), + feed: templateReader("feed.xml") }; function title(siteTitle, pageTitle) { @@ -62,6 +64,22 @@ const renderPost = ctx => post => { }; }; +const renderPostAtom = (ctx, config) => post => { + return { + id: ctx.makeTagURI(`post:${post.basename}`), + title: post.data.get("title"), + updated: post.data.get("date"), + summary: post.data.get("summary"), + "link[rel=alternate]": { + href: new URL(ctx.getURL("post", post.basename), config.site.baseURL) + }, + "content > div": { + _html: post.body + }, + "author > name": config.author.name + }; +}; + function layout(config, pageTitle, pageElement) { return hyperfast(templates.layout, { title: title(config.author.name, pageTitle), @@ -125,5 +143,25 @@ module.exports = { post.data.get("title"), hyperfast(templates.post, renderPost(ctx)(post)) ); + }, + + feed(ctx, config, { posts, lastPostDate }) { + ctx.type = "atom"; + + ctx.body = hyperfast( + templates.feed, + { + "feed > title": config.author.name, + "feed > link": { + href: config.site.baseURL + }, + "feed > id": { + _text: ctx.makeTagURI("feed") + }, + "feed > updated": lastPostDate, + "feed > entry": posts.map(renderPostAtom(ctx, config)) + }, + { xmlMode: true } + ).outerHTML; } }; diff --git a/src/templates/feed.xml b/src/templates/feed.xml new file mode 100644 index 0000000..03d68d7 --- /dev/null +++ b/src/templates/feed.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + <title>Example Feed</title> + <link href="http://example.org/"/> + <id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id> + <updated>2003-12-13T18:30:02Z</updated> + + <entry> + <title>Atom-Powered Robots Run Amok</title> + <link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/> + <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> + <updated>2003-12-13T18:30:02Z</updated> + <summary>Some text.</summary> + <content type="xhtml"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>This is the entry content.</p> + </div> + </content> + <author> + <name>John Doe</name> + </author> + </entry> + +</feed> diff --git a/test/app.test.js b/test/app.test.js index f0ab3da..33c8905 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -169,5 +169,18 @@ test("highlight css", async function(t) { t.regex(res.text, /^\.hljs/m); }); +test("feed", async function(t) { + const res = await request(app.listen()).get("/index.xml"); + + t.is(res.statusCode, 200); + t.is(res.type, "application/atom+xml"); + t.regex(res.text, /^<\?xml/); + + const $ = parseResponse(res); + + t.is($("feed > title").text(), "John Doe"); + t.is($("feed > link").attr("href"), "http://localhost:3000/"); +}); + test(notFound, "/post/non-existent", /Post not found/); test(notFound, "/tag/non-existent", /tag non-existent not found/); diff --git a/yarn.lock b/yarn.lock index 9d2e7de..fc6330a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -743,10 +743,6 @@ buf-compare@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/buf-compare/-/buf-compare-1.0.1.tgz#fef28da8b8113a0a0db4430b0b6467b69730b34a" -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1471,20 +1467,13 @@ domutils@1.4: dependencies: domelementtype "1" -domutils@1.5, domutils@1.5.1: +domutils@1.5, domutils@1.5.1, domutils@^1.5.0, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: dom-serializer "0" domelementtype "1" -domutils@^1.5.0, domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" - dependencies: - dom-serializer "0" - domelementtype "1" - dont-sniff-mimetype@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" @@ -2581,9 +2570,9 @@ husky@^0.13.4: is-ci "^1.0.9" normalize-path "^1.0.0" -hyperfast@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hyperfast/-/hyperfast-2.1.0.tgz#72c91f87126c54e6fe0ad2264ece278b3a6c0a56" +hyperfast@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hyperfast/-/hyperfast-2.2.0.tgz#27d9f1e8a81f02332b6af5dd96281e20d91a7f58" dependencies: css-select "^1.1.0" domutils "^1.5.0" @@ -4320,7 +4309,7 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: version "2.2.11" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.11.tgz#0796b31f8d7688007ff0b93a8088d34aa17c0f72" dependencies: @@ -4332,18 +4321,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable string_decoder "~1.0.0" util-deprecate "~1.0.1" -readable-stream@^2.0.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - readable-stream@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" |