From e7b08b1dfe3f2a2596deb6e2a72bb79805d3708f Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 3 Jul 2017 21:39:43 +0200 Subject: feat: Add code block highlighting Theme is configurable --- src/actions.js | 22 +++++++++++++++++++++ src/app.js | 50 +++++++++++++++++++++++++++-------------------- src/domain/posts.js | 42 +++++++++++++++++++++++++-------------- src/index.js | 14 ++++++++----- src/modules/markdown.js | 24 +++++++++++++++++------ src/templates/layout.html | 1 + 6 files changed, 106 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/actions.js b/src/actions.js index 8a04671..7c7482f 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,5 +1,7 @@ "use strict"; +const fs = require("fs"); +const path = require("path"); const send = require("koa-send"); const responders = require("./responders"); @@ -10,6 +12,25 @@ function home(config, posts) { }; } +function highlightTheme(config) { + const theme = config.posts.code.theme; + const themeFile = path.resolve( + __dirname, + `../node_modules/highlight.js/styles/${theme}.css` + ); + + if (!fs.existsSync(themeFile)) { + throw new Error(`Couldn't find highlight theme ${theme}`); + } + + const css = fs.readFileSync(themeFile, "utf-8"); + + return async function(ctx, next) { + ctx.type = "css"; + ctx.body = css; + }; +} + function post(config, posts) { return async function(ctx, next) { ctx.assert(posts.has(ctx.params.filename), 404, "Post not found"); @@ -41,6 +62,7 @@ async function serveFiles(ctx) { module.exports = { home, + highlightTheme, post, taxonGenerator, serveFiles diff --git a/src/app.js b/src/app.js index 1a0c5cb..6be44bd 100644 --- a/src/app.js +++ b/src/app.js @@ -14,32 +14,40 @@ const router = new Router(); app.context.getURL = router.url.bind(router); -const Posts = require("./domain/posts.js")(config.posts, basename => - router.url("post", basename) -); - -router.get("home", "/", actions.home(config, Posts.posts)); +module.exports = async function() { + const Posts = await require("./domain/posts.js")(config.posts, basename => + router.url("post", basename) + ); -router.get("post", "/post/:filename", actions.post(config, Posts.posts)); + router.get("home", "/", actions.home(config, Posts.posts)); -for (let [term, items] of Posts.taxonomies) { router.get( - `taxon-${term}`, - `/${term}/:value`, - actions.taxonGenerator(config, term, items) + "highlight-theme", + "/css/code.css", + actions.highlightTheme(config) ); -} -app.use( - helmet({ - hsts: { - setIf: ctx => ctx.secure - } - }) -); + router.get("post", "/post/:filename", actions.post(config, Posts.posts)); + + for (let [term, items] of Posts.taxonomies) { + router.get( + `taxon-${term}`, + `/${term}/:value`, + actions.taxonGenerator(config, term, items) + ); + } + + app.use( + helmet({ + hsts: { + setIf: ctx => ctx.secure + } + }) + ); -app.use(router.routes()).use(router.allowedMethods()); + app.use(router.routes()).use(router.allowedMethods()); -app.use(actions.serveFiles); + app.use(actions.serveFiles); -module.exports = app; + return app; +}; diff --git a/src/domain/posts.js b/src/domain/posts.js index fd4fb3d..98488ba 100644 --- a/src/domain/posts.js +++ b/src/domain/posts.js @@ -1,9 +1,12 @@ "use strict"; +const h = require("highland"); const fs = require("fs"); const path = require("path"); +const { promisify } = require("util"); const matter = require("gray-matter"); const markdown = require("../modules/markdown.js"); +const predentation = require("predentation"); const { indentForTemplate, postIndentLevel } = require("../responders.js"); const grayMatterOptions = { @@ -30,26 +33,35 @@ function getTitle(file) { return path.basename(file.path, path.extname(file.path)); } -function get(getURL, filename) { +async function indentBody(content) { + return await h( + h + .of(content) + .map(markdown) + .map(html => indentForTemplate(html, postIndentLevel)) + .pipe(predentation()) + ) + .invoke("toString", ["utf-8"]) + .toPromise(Promise); +} + +async function get(getURL, filename) { const fileMatter = matter.read(filename, grayMatterOptions); fileMatter.basename = getTitle(fileMatter); delete fileMatter.orig; - fileMatter.body = indentForTemplate( - markdown(fileMatter.content), - postIndentLevel - ); + fileMatter.body = await indentBody(fileMatter.content); fileMatter.url = getURL(fileMatter.basename); - return canonicaliseMetadata(fileMatter); + return Promise.resolve(canonicaliseMetadata(fileMatter)); } -function getFolder(folder, getURL) { - return new Map( - fs - .readdirSync(folder) - .map(f => path.resolve(folder, f)) - .map(get.bind(this, getURL)) - .map(f => [getTitle(f), f]) +async function getFolder(folder, getURL) { + const files = (await promisify(fs.readdir)(folder)).map(f => + path.resolve(folder, f) ); + + const posts = await Promise.all(files.map(get.bind(this, getURL))); + + return new Map(posts.map(f => [getTitle(f), f])); } function taxonomise(taxonomies, posts) { @@ -71,8 +83,8 @@ function taxonomise(taxonomies, posts) { return taxons; } -module.exports = function(config, getURL) { - const posts = getFolder(config.folder, getURL); +module.exports = async function(config, getURL) { + const posts = await getFolder(config.folder, getURL); const taxonomies = taxonomise(config.taxonomies, posts); return { posts, diff --git a/src/index.js b/src/index.js index 2c75554..b7bc1a3 100644 --- a/src/index.js +++ b/src/index.js @@ -11,8 +11,12 @@ if (targetDir) { const app = require("./app.js"); -module.exports = app; - -app.listen(PORT, () => { - console.log(`App listening on port ${PORT}`); -}); +(async function() { + try { + (await app()).listen(PORT, () => { + console.log(`App listening on port ${PORT}`); + }); + } catch (error) { + console.error("App startup error", error); + } +})(); diff --git a/src/modules/markdown.js b/src/modules/markdown.js index 9f0af45..6a0ec9d 100644 --- a/src/modules/markdown.js +++ b/src/modules/markdown.js @@ -1,12 +1,24 @@ -'use strict' +"use strict"; -const Markdown = require('markdown-it') +const highlight = require("highlight.js"); +const Markdown = require("markdown-it"); const markdownOptions = { html: true, - typographer: true -} + typographer: true, + highlight: function(str, lang) { + if (lang && highlight.getLanguage(lang)) { + try { + return ` +${highlight.highlight(lang, str).value}`; + } catch (error) { + console.error("highlighting failed", error); + } + } + return ""; + } +}; -const markdown = new Markdown(markdownOptions) +const markdown = new Markdown(markdownOptions); -module.exports = markdown.render.bind(markdown) +module.exports = markdown.render.bind(markdown); diff --git a/src/templates/layout.html b/src/templates/layout.html index 65fdede..86eec59 100644 --- a/src/templates/layout.html +++ b/src/templates/layout.html @@ -2,6 +2,7 @@ + -- cgit 1.4.1