summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorAlan Pearce2017-07-03 21:39:43 +0200
committerAlan Pearce2017-07-03 21:55:41 +0200
commite7b08b1dfe3f2a2596deb6e2a72bb79805d3708f (patch)
tree027fda29fe96736d4ac641a1dfe0bfe657d3fd33 /src
parenta67e38d1a82c95db5bd24183e81b31438f60dd2c (diff)
downloadhomestead-e7b08b1dfe3f2a2596deb6e2a72bb79805d3708f.tar.lz
homestead-e7b08b1dfe3f2a2596deb6e2a72bb79805d3708f.tar.zst
homestead-e7b08b1dfe3f2a2596deb6e2a72bb79805d3708f.zip
feat: Add code block highlighting
Theme is configurable
Diffstat (limited to 'src')
-rw-r--r--src/actions.js22
-rw-r--r--src/app.js50
-rw-r--r--src/domain/posts.js42
-rw-r--r--src/index.js14
-rw-r--r--src/modules/markdown.js24
-rw-r--r--src/templates/layout.html1
6 files changed, 106 insertions, 47 deletions
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 @@
 <html lang="en">
   <head>
     <meta charset="utf-8"/>
+    <link href="/css/code.css" rel="stylesheet" />
     <title></title>
   </head>
   <body>