From 963b5d46e6437e829a4f2463cbc32f9f6deb157a Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 26 Jan 2018 22:12:46 -0600 Subject: [PATCH] Refactors Liquid syntax highlighters to add line highlights. Usage (ranges are space separated): {% highlight js 1,4-6 %} One range Adds `highlight-line-active` to lines 1,4,5,6 {% highlight js 3-4 -1 %} Two ranges (add/remove), remove is N/A Adds `highlight-line-add` to lines 3,4 {% highlight js -1 3-4 %} Two ranges (add/remove), add is N/A Adds `highlight-line-remove` to lines 3,4 {% highlight js 3-4 5,8-10 %} Two ranges, both are used Adds `highlight-line-add` to lines 3-4 Adds `highlight-line-remove` to lines 5,8,9,10 --- .eleventy.js | 4 +- _src/HighlightLines.js | 33 ++++++++ _src/LiquidHighlight.js | 83 +++++++++++++++++++ _src/eleventy-liquidjs-tag-highlight-plain.js | 32 ------- ...eleventy-liquidjs-tag-highlight-prismjs.js | 37 --------- _src/eleventy-liquidjs-tag-highlight.js | 28 +++++++ css/index.css | 17 ++++ css/prism-base16-monokai.dark.css | 2 +- 8 files changed, 164 insertions(+), 72 deletions(-) create mode 100644 _src/HighlightLines.js create mode 100644 _src/LiquidHighlight.js delete mode 100644 _src/eleventy-liquidjs-tag-highlight-plain.js delete mode 100644 _src/eleventy-liquidjs-tag-highlight-prismjs.js create mode 100644 _src/eleventy-liquidjs-tag-highlight.js diff --git a/.eleventy.js b/.eleventy.js index b7faaec..4a41844 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,5 +1,5 @@ const { DateTime } = require("luxon"); -const liquidjsSyntaxHighlighter = require("./_src/eleventy-liquidjs-tag-highlight-prismjs"); +const highlighters = require("./_src/eleventy-liquidjs-tag-highlight"); function dateToISO(dateObj) { return DateTime.fromJSDate(dateObj).toISO({ includeOffset: true, suppressMilliseconds: true }); @@ -25,7 +25,7 @@ module.exports = function(eleventyConfig) { }); // compatibility with existing {% highlight js %} and others - eleventyConfig.addLiquidTag("highlight", liquidjsSyntaxHighlighter); + eleventyConfig.addLiquidTag("highlight", highlighters.prismjs); // only content in the `posts/` directory eleventyConfig.addCollection("posts", function(collection) { diff --git a/_src/HighlightLines.js b/_src/HighlightLines.js new file mode 100644 index 0000000..f3e0d50 --- /dev/null +++ b/_src/HighlightLines.js @@ -0,0 +1,33 @@ +class HighlightLines { + constructor(rangeStr) { + this.highlights = this.convertRangeToHash(rangeStr); + } + + convertRangeToHash(rangeStr) { + let hash = {}; + if( !rangeStr ) { + return hash; + } + + let ranges = rangeStr.split(",").map(function(range) { + return range.trim(); + }); + + for(let range of ranges) { + let startFinish = range.split('-'); + let start = parseInt(startFinish[0], 10); + let end = parseInt(startFinish[1] || start, 10); + + for( let j = start, k = end; j<=k; j++ ) { + hash[j] = true; + } + } + return hash; + } + + isHighlighted(lineNumber) { + return !!this.highlights[lineNumber] + } +} + +module.exports = HighlightLines; \ No newline at end of file diff --git a/_src/LiquidHighlight.js b/_src/LiquidHighlight.js new file mode 100644 index 0000000..ebaa79c --- /dev/null +++ b/_src/LiquidHighlight.js @@ -0,0 +1,83 @@ +const HighlightLines = require('./HighlightLines'); + +class LiquidHighlight { + constructor(liquidEngine) { + this.liquidEngine = liquidEngine; + this.hooks = []; + this.classHooks = []; + } + + addHook(hookFunction) { + this.hooks.push(hookFunction); + } + + addClassHook(hookFunction) { + this.classHooks.push(hookFunction); + } + + getObject() { + let ret = function(highlighter) { + return { + parse: function(tagToken, remainTokens) { + let split = tagToken.args.split(" "); + + this.language = split[0]; + this.highlights = new HighlightLines(split.length === 2 ? split[1] : ""); + this.highlightsAdd = new HighlightLines(split.length === 3 ? split[1] : ""); + this.highlightsRemove = new HighlightLines(split.length === 3 ? split[2] : ""); + + this.tokens = []; + + var stream = highlighter.liquidEngine.parser.parseStream(remainTokens); + + stream + .on('token', token => { + if (token.name === 'endhighlight') { + stream.stop(); + } else { + this.tokens.push(token); + } + }) + .on('end', x => { + throw new Error("tag highlight not closed"); + }); + + stream.start(); + }, + render: function(scope, hash) { + let tokens = this.tokens.map(token => token.raw); + let tokenStr = tokens.join('').trim(); + + for( let hook of highlighter.hooks ) { + tokenStr = hook.call(this, this.language, tokenStr); + } + + let lines = tokenStr.split("\n").map(function(line, j) { + let classHookClasses = []; + for( let classHook of highlighter.classHooks ) { + let ret = classHook(this.language, line, j); + if( ret ) { + classHookClasses.push(ret); + } + } + + return '
' + + line + + '
'; + }.bind(this)); + + return Promise.resolve(`
` + lines.join("") + "
"); + } + }; + }; + + return ret(this); + } +} + +module.exports = LiquidHighlight; \ No newline at end of file diff --git a/_src/eleventy-liquidjs-tag-highlight-plain.js b/_src/eleventy-liquidjs-tag-highlight-plain.js deleted file mode 100644 index e5a30d4..0000000 --- a/_src/eleventy-liquidjs-tag-highlight-plain.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function(liquidEngine) { - - return { - parse: function(tagToken, remainTokens) { - this.language = tagToken.args; - this.tokens = []; - - var stream = liquidEngine.parser.parseStream(remainTokens); - - stream - .on('token', token => { - if (token.name === 'endhighlight') { - stream.stop(); - } else { - this.tokens.push(token); - } - }) - .on('end', x => { - throw new Error("tag highlight not closed"); - }); - - stream.start(); - }, - render: function(scope, hash) { - var tokens = this.tokens.map(token => { - return token.raw.trim(); - }).join('').trim(); - - return Promise.resolve(`
\n` + tokens + "\n
"); - } - } -}; \ No newline at end of file diff --git a/_src/eleventy-liquidjs-tag-highlight-prismjs.js b/_src/eleventy-liquidjs-tag-highlight-prismjs.js deleted file mode 100644 index 1e8d039..0000000 --- a/_src/eleventy-liquidjs-tag-highlight-prismjs.js +++ /dev/null @@ -1,37 +0,0 @@ -const Prism = require('prismjs'); - -module.exports = function(liquidEngine) { - let langMap = { - "css": "css", - "html": "markup", - "js": "javascript" - }; - - return { - parse: function(tagToken, remainTokens) { - this.language = langMap[ tagToken.args ] || tagToken.args; - this.tokens = []; - - var stream = liquidEngine.parser.parseStream(remainTokens); - - stream - .on('token', token => { - if (token.name === 'endhighlight') { - stream.stop(); - } else { - this.tokens.push(token); - } - }) - .on('end', x => { - throw new Error("tag highlight not closed"); - }); - - stream.start() - }, - render: function(scope, hash) { - var tokens = this.tokens.map(token => token.raw).join('').trim(); - var html = Prism.highlight(tokens, Prism.languages[ this.language ]); - return Promise.resolve(`
` + html + "
"); - } - } -}; \ No newline at end of file diff --git a/_src/eleventy-liquidjs-tag-highlight.js b/_src/eleventy-liquidjs-tag-highlight.js new file mode 100644 index 0000000..4f91e8a --- /dev/null +++ b/_src/eleventy-liquidjs-tag-highlight.js @@ -0,0 +1,28 @@ +const Prism = require('prismjs'); +const LiquidHighlight = require( "./LiquidHighlight" ); + +module.exports = { + plain: function(liquidEngine) { + let highlight = new LiquidHighlight(liquidEngine); + + highlight.addClassHook(function(language, line) { + if( language === "dir" ) { + // has trailing slash + if( line.match(/\/$/) !== null ) { + return "highlight-line-isdir"; + } + } + }); + + return highlight.getObject(); + }, + prismjs: function(liquidEngine) { + let highlight = new LiquidHighlight(liquidEngine); + + highlight.addHook(function(language, htmlStr, lines) { + return Prism.highlight(htmlStr, Prism.languages[ language ]); + }); + + return highlight.getObject(); + } +}; \ No newline at end of file diff --git a/css/index.css b/css/index.css index 38f91fe..5459a5b 100644 --- a/css/index.css +++ b/css/index.css @@ -73,6 +73,23 @@ pre { margin: .5em 0; background-color: #f6f6f6; } +.highlight-line { + padding: 0.125em 1em; /* 2px 16px /16 */ +} +.highlight-line-isdir { + color: #b0b0b0; + background-color: #222; +} +.highlight-line-active { + background-color: #444; + background-color: hsla(0, 0%, 27%, .8); +} +.highlight-line-add { + background-color: #45844b; +} +.highlight-line-remove { + background-color: #902f2f; +} /* Header */ .home { diff --git a/css/prism-base16-monokai.dark.css b/css/prism-base16-monokai.dark.css index 7a872a7..dceb2a6 100644 --- a/css/prism-base16-monokai.dark.css +++ b/css/prism-base16-monokai.dark.css @@ -17,7 +17,7 @@ code[class*="language-"], pre[class*="language-"] { color: #f8f8f2; } pre[class*="language-"] { - padding: 1em; + padding: 1.5em 0; margin: .5em 0; overflow: auto; }