From c40be6836a08dda4215825c639a7b00f58726332 Mon Sep 17 00:00:00 2001 From: Robert McGovern Date: Thu, 2 Feb 2023 14:36:15 +0000 Subject: [PATCH] base files from eleventy-plugin-syntaxhighlight-4.2.0 and chromahighlight npm package as dependency --- .editorconfig | 9 ++ .eleventy.js | 59 ++++++++++ .eleventyignore | 1 + .eslintrc.js | 17 +++ .github/FUNDING.yml | 2 + .github/workflows/ci.yml | 23 ++++ .gitignore | 4 + LICENSE | 21 ++++ README.md | 8 ++ demo/eleventy-config.js | 10 ++ demo/prism-theme.css | 148 +++++++++++++++++++++++++ demo/test-liquid.liquid | 33 ++++++ demo/test-markdown.md | 62 +++++++++++ demo/test-nunjucks.njk | 74 +++++++++++++ demo/test.css | 15 +++ package.json | 57 ++++++++++ src/CharacterWrap.js | 151 ++++++++++++++++++++++++++ src/HighlightLines.js | 33 ++++++ src/HighlightLinesGroup.js | 68 ++++++++++++ src/HighlightPairedShortcode.js | 34 ++++++ src/LiquidHighlightTag.js | 49 +++++++++ src/PrismLoader.js | 42 +++++++ src/PrismNormalizeAlias.js | 42 +++++++ src/getAttributes.js | 62 +++++++++++ src/hasTemplateFormat.js | 13 +++ src/markdownSyntaxHighlightOptions.js | 44 ++++++++ syntax-highlight.webc | 16 +++ test/11tyjs-diff/.eleventy.js | 5 + test/11tyjs-diff/test.11ty.js | 4 + test/11tyjs-test/.eleventy.js | 5 + test/11tyjs-test/test.11ty.js | 4 + test/CharacterWrapTest.js | 13 +++ test/GetAttributesTest.js | 54 +++++++++ test/HasTemplateFormatTest.js | 25 +++++ test/HighlightLinesGroupTest.js | 93 ++++++++++++++++ test/HighlightLinesTest.js | 41 +++++++ test/HighlightPairedShortcodeTest.js | 104 ++++++++++++++++++ test/JavaScriptFunctionTest.js | 24 ++++ test/LiquidHighlightTagTest.js | 43 ++++++++ test/MarkdownHighlightTest.js | 85 +++++++++++++++ 40 files changed, 1597 insertions(+) create mode 100644 .editorconfig create mode 100644 .eleventy.js create mode 100644 .eleventyignore create mode 100644 .eslintrc.js create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 demo/eleventy-config.js create mode 100644 demo/prism-theme.css create mode 100644 demo/test-liquid.liquid create mode 100644 demo/test-markdown.md create mode 100644 demo/test-nunjucks.njk create mode 100644 demo/test.css create mode 100644 package.json create mode 100644 src/CharacterWrap.js create mode 100644 src/HighlightLines.js create mode 100644 src/HighlightLinesGroup.js create mode 100644 src/HighlightPairedShortcode.js create mode 100644 src/LiquidHighlightTag.js create mode 100644 src/PrismLoader.js create mode 100644 src/PrismNormalizeAlias.js create mode 100644 src/getAttributes.js create mode 100644 src/hasTemplateFormat.js create mode 100644 src/markdownSyntaxHighlightOptions.js create mode 100644 syntax-highlight.webc create mode 100644 test/11tyjs-diff/.eleventy.js create mode 100644 test/11tyjs-diff/test.11ty.js create mode 100644 test/11tyjs-test/.eleventy.js create mode 100644 test/11tyjs-test/test.11ty.js create mode 100644 test/CharacterWrapTest.js create mode 100644 test/GetAttributesTest.js create mode 100644 test/HasTemplateFormatTest.js create mode 100644 test/HighlightLinesGroupTest.js create mode 100644 test/HighlightLinesTest.js create mode 100644 test/HighlightPairedShortcodeTest.js create mode 100644 test/JavaScriptFunctionTest.js create mode 100644 test/LiquidHighlightTagTest.js create mode 100644 test/MarkdownHighlightTest.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d415404 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 diff --git a/.eleventy.js b/.eleventy.js new file mode 100644 index 0000000..c8c787b --- /dev/null +++ b/.eleventy.js @@ -0,0 +1,59 @@ +const pkg = require("./package.json"); +const Prism = require("prismjs"); +const hasTemplateFormat = require("./src/hasTemplateFormat"); +const HighlightPairedShortcode = require("./src/HighlightPairedShortcode"); +const LiquidHighlightTag = require("./src/LiquidHighlightTag"); +const CharacterWrap = require("./src/CharacterWrap"); +const markdownPrismJs = require("./src/markdownSyntaxHighlightOptions"); + +module.exports = { + initArguments: { Prism }, + configFunction: function(eleventyConfig, options = {}) { + try { + eleventyConfig.versionCheck(pkg["11ty"].compatibility); + } catch(e) { + console.log( `WARN: Eleventy Plugin (${pkg.name}) Compatibility: ${e.message}` ); + } + + options = Object.assign({ + alwaysWrapLineHighlights: false, + // eligible to change the default to \n in a new major version. + lineSeparator: "
", + preAttributes: {}, + codeAttributes: {} + }, options); + + // TODO hbs? + if( hasTemplateFormat(options.templateFormats, "liquid") ) { + eleventyConfig.addLiquidTag("highlight", (liquidEngine) => { + // {% highlight js 0 2 %} + let highlight = new LiquidHighlightTag(liquidEngine); + return highlight.getObject(options); + }); + } + + if( hasTemplateFormat(options.templateFormats, "njk") ) { + eleventyConfig.addPairedNunjucksShortcode("highlight", (content, args) => { + // {% highlight "js 0 2-3" %} + let [language, ...highlightNumbers] = args.split(" "); + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + }); + } + + if( hasTemplateFormat(options.templateFormats, "md") ) { + // ```js/0,2-3 + eleventyConfig.addMarkdownHighlighter(markdownPrismJs(options)); + } + + if( hasTemplateFormat(options.templateFormats, "11ty.js") ) { + eleventyConfig.addJavaScriptFunction("highlight", (language, content, highlight1, highlight2) => { + let highlightLines = [highlight1, highlight2].filter(entry => entry).join(" "); + let result = HighlightPairedShortcode(content, language, highlightLines, options); + return result; + }); + } + } +}; + +module.exports.pairedShortcode = HighlightPairedShortcode; +module.exports.CharacterWrap = CharacterWrap; diff --git a/.eleventyignore b/.eleventyignore new file mode 100644 index 0000000..b43bf86 --- /dev/null +++ b/.eleventyignore @@ -0,0 +1 @@ +README.md diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..62e8c16 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + env: { + es6: true, + node: true + }, + extends: "eslint:recommended", + parserOptions: { + sourceType: "module", + ecmaVersion: 2017 + }, + rules: { + indent: ["error", 2], + "linebreak-style": ["error", "unix"], + quotes: ["error", "double"], + semi: ["error", "always"] + } +}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9c56567 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +open_collective: 11ty diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cf31d19 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +on: + push: + branches-ignore: + - "gh-pages" +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest", "windows-latest"] + node: ["14", "16", "18"] + name: Node.js ${{ matrix.node }} on ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + # cache: npm + - run: npm install + - run: npm test +env: + YARN_GPG: no diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d9291e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +_site/ +package-lock.json +yarn.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..89a4362 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Zach Leatherman @zachleat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c55781d --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +

eleventy Logo

+ +# eleventy-plugin-syntaxhighlight 🕚⚡️🎈🐀 + +A pack of [Eleventy](https://github.com/11ty/eleventy) plugins for syntax highlighting. No browser/client JavaScript here, these highlight transformations are all done at build-time. + +## Read the [Full Documentation on 11ty.dev](https://www.11ty.dev/docs/plugins/syntaxhighlight/) + diff --git a/demo/eleventy-config.js b/demo/eleventy-config.js new file mode 100644 index 0000000..c6f37fc --- /dev/null +++ b/demo/eleventy-config.js @@ -0,0 +1,10 @@ +const syntaxHighlight = require("../.eleventy.js"); + +module.exports = function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight, { + // alwaysWrapLineHighlights: true + preAttributes: { tabindex: 0 } + }); + + eleventyConfig.setTemplateFormats("njk,liquid,md,css"); +}; diff --git a/demo/prism-theme.css b/demo/prism-theme.css new file mode 100644 index 0000000..4e2d4e7 --- /dev/null +++ b/demo/prism-theme.css @@ -0,0 +1,148 @@ +pre { + display: block; + padding: .75rem 1rem; + line-height: 1.5; + + overflow-x: auto; + background-color: #eee; + font-size: 0.875em; /* 14px /16 */ + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; + + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + + background-color: #272822; + color: #fff; +} + + +/** + * a11y-dark theme for JavaScript, CSS, and HTML + * Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css + * @author ericwbailey + */ + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #d4d0ab; +} + +.token.punctuation { + color: #fefefe; +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: #ffa07a; +} + +.token.boolean, +.token.number { + color: #00e0e0; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #abe338; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #00e0e0; +} + +.token.atrule, +.token.attr-value, +.token.function { + color: #ffd700; +} + +.token.keyword { + color: #00e0e0; +} + +.token.regex, +.token.important { + color: #ffd700; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +@media screen and (-ms-high-contrast: active) { + code[class*="language-"], + pre[class*="language-"] { + color: windowText; + background: window; + } + + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: window; + } + + .token.important { + background: highlight; + color: window; + font-weight: normal; + } + + .token.atrule, + .token.attr-value, + .token.function, + .token.keyword, + .token.operator, + .token.selector { + font-weight: bold; + } + + .token.attr-value, + .token.comment, + .token.doctype, + .token.function, + .token.keyword, + .token.operator, + .token.property, + .token.string { + color: highlight; + } + + .token.attr-value, + .token.url { + font-weight: normal; + } +} diff --git a/demo/test-liquid.liquid b/demo/test-liquid.liquid new file mode 100644 index 0000000..d714e2c --- /dev/null +++ b/demo/test-liquid.liquid @@ -0,0 +1,33 @@ + + + + + + + + + + +{% highlight js %} +function myFunction() { + return true; +} +{% endhighlight %} + +{% highlight js %} +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +{% endhighlight %} + +{% highlight js 1,3 %} +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +{% endhighlight %} + + diff --git a/demo/test-markdown.md b/demo/test-markdown.md new file mode 100644 index 0000000..15ba5e8 --- /dev/null +++ b/demo/test-markdown.md @@ -0,0 +1,62 @@ + + + + + + + + + + +```ts +function myFunction() { + return true; +} +``` + +```typescript +function myFunction() { + return true; +} +``` + +```js +function myFunction() { + return true; +} +``` + +```js +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +``` + +## Dash line + +```js/- +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +``` + +```js/1,3 +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +``` + +## Scrollbar + +```js +import { aReallyLongFunctionNameThatCouldBeLongerButThisShouldBeLongEnoughByNowHopefully as anEvenLongerFunctionNameWithMoreCharactersThanCouldBeImaginedByAnyOnePersonInThisEntireWorldOfPeopleThatOneMightKnowAtLeastThatIsWhatIsTheorizedByThisLongName } from 'wow-this-is-so-long-you-might-need-a-scrollbar-to-see-it.long-ol-file-extension-that-should-not-be-this-long-on-a-real-site-but-this-is-to-demonstrate-the-accessibility-of-tabindex-and-scrollbars.js'; +``` + + + diff --git a/demo/test-nunjucks.njk b/demo/test-nunjucks.njk new file mode 100644 index 0000000..02513be --- /dev/null +++ b/demo/test-nunjucks.njk @@ -0,0 +1,74 @@ + + + + + + + + + + + +{% highlight "js" %} +function myFunction() { + return true; +} +{% endhighlight %} + +{% highlight "js" %} +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +{% endhighlight %} + +{% highlight "js 1,3" %} +let multilineString = ` + this is the first line + this is the middle line + this is the last line +`; +{% endhighlight %} + +{% highlight "js 1,3" %} +alert("test"); + +let multilineString = buildSchema(` + this is the first line + this is the middle line + this is the last line +`); + +alert("test"); +{% endhighlight %} + +{% highlight "js" %} +module.exports = function({collections}) { + return ``; +}; +{% endhighlight %} + +{% highlight "js 1,3" %} +module.exports = function({collections}) { + return ``; +}; +{% endhighlight %} + +{% highlight "typescript" %} +function myFunction() { + return true; +} +{% endhighlight %} + +{% highlight "ts" %} +function myFunction() { + return true; +} +{% endhighlight %} + + diff --git a/demo/test.css b/demo/test.css new file mode 100644 index 0000000..5ab54a7 --- /dev/null +++ b/demo/test.css @@ -0,0 +1,15 @@ +.highlight-line { + display: inline-block; +} + +/* allow highlighting empty lines */ +.highlight-line:empty:before { + content: " "; +} + +.highlight-line:not(:last-child) { + min-width: 100%; +} +.highlight-line .highlight-line:not(:last-child) { + min-width: 0; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..54fd43d --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "@11ty/eleventy-plugin-syntaxhighlight", + "version": "4.2.0", + "description": "Prism.js based syntax highlighting for Markdown, Liquid, Nunjucks, and 11ty.js templates.", + "publishConfig": { + "access": "public" + }, + "main": ".eleventy.js", + "scripts": { + "test": "npx ava", + "demo": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js", + "start": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js --serve" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/11ty/eleventy-plugin-syntaxhighlight.git" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + }, + "keywords": [ + "eleventy", + "eleventy-plugin" + ], + "author": { + "name": "Zach Leatherman", + "email": "zachleatherman@gmail.com", + "url": "https://zachleat.com/" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/11ty/eleventy-plugin-syntaxhighlight/issues" + }, + "homepage": "https://www.11ty.dev/docs/plugins/syntaxhighlight/", + "11ty": { + "compatibility": ">=0.5.4" + }, + "devDependencies": { + "@11ty/eleventy": "^1.0.2", + "ava": "^5.0.1", + "liquidjs": "^9.42.1", + "markdown-it": "^13.0.1" + }, + "dependencies": { + "chroma-highlight": "^2.4.2", + "linkedom": "^0.14.19", + "prismjs": "^1.29.0" + }, + "ava": { + "environmentVariables": {}, + "failFast": false, + "files": [ + "./test/*.js" + ] + } +} diff --git a/src/CharacterWrap.js b/src/CharacterWrap.js new file mode 100644 index 0000000..c269110 --- /dev/null +++ b/src/CharacterWrap.js @@ -0,0 +1,151 @@ +const HighlightPairedShortcode = require("./HighlightPairedShortcode"); +const {parseHTML} = require("linkedom"); + +class IndexCounter { + constructor() { + this.index = 0; + } + + add() { + this.index++; + } + + valueOf() { + return this.index; + } +} + +class CharacterWrap { + constructor() { + this.multipleCursors = false; + // array of character indeces + this.typingConfig = ["0"]; + + this.contentTransforms = []; + + this.classPrefix = "charwrap"; + } + + setClassPrefix(prefix) { + this.classPrefix = prefix; + } + + setMultipleCursors(useMultipleCursors) { + this.multipleCursors = !!useMultipleCursors; + } + + setTypingConfigArray(typingConfig) { + this.typingConfig = typingConfig; + } + + addContentTransform(callback) { + this.contentTransforms.push(callback); + } + + // 0 => type to show all + // 1 => type to show from 1+ + // 1,2 => type to show from 1 to 2 + // 2,3 5,6 => type to show from 2 to 3 and from 5 to 6 + // in other words, show 1 and 4 and 7+ + getTypingConfigResults(indexCounter) { + let charIndex = indexCounter.valueOf(); + let lowestIndex = 99999999; + let waitToShow = {}; + let showCursor = false; + + for(let cfg of this.typingConfig) { + let start, end; + cfg = "" + cfg; + + if(cfg.indexOf(",") > -1) { // start,length + let split = cfg.split(","); + start = parseInt(split[0], 10); + end = split.length > 1 ? start + parseInt(split[1], 10) : (charIndex+1); + } else if(cfg.indexOf("-") > -1) { // start,end + let split = cfg.split("-"); + start = parseInt(split[0], 10); + end = split.length > 1 ? parseInt(split[1], 10) : (charIndex+1); + } else { + start = parseInt(cfg, 10); + end = charIndex + 1; + } + + for(let j = start+1; j < end; j++) { + waitToShow[j] = true; + } + if(this.multipleCursors && start === charIndex) { + showCursor = true; + } + lowestIndex = Math.min(lowestIndex, start); + } + + if(!this.multipleCursors && lowestIndex === charIndex) { + showCursor = true; + } + return { showTyped: !waitToShow[charIndex], showCursor }; + } + + modifyNode(node, indexCounter) { + let classes = [this.classPrefix]; + let showTyped = true; + let showCursor = false; + + indexCounter.add(); + let results = this.getTypingConfigResults(indexCounter); + showTyped = results.showTyped; + showCursor = results.showCursor; + + if(showTyped) { + classes.push(`${this.classPrefix}-typed ${this.classPrefix}-typed-initial`); + } + if(showCursor) { + classes.push(`${this.classPrefix}-cursor ${this.classPrefix}-cursor-initial`); + } + node.className = classes.join(" "); + node.setAttribute("data-index", indexCounter.valueOf()); + } + + walkTree(doc, root, indexCounter = null) { + for(let node of root.childNodes) { + if(node.nodeType === 3) { + let characters = Array.from(node.textContent); // convert string to character array + for(let char of characters) { + let newTextEl = doc.createElement("span"); + this.modifyNode(newTextEl, indexCounter); + newTextEl.innerHTML = char; + node.parentNode.insertBefore(newTextEl, node); + } + node.remove(); + } else if(node.nodeType === 1) { + if(node.classList.contains(this.classPrefix)) { + continue; + } + if(node.nodeName === "BR") { + this.modifyNode(node, indexCounter); + } else { + this.walkTree(doc, node, indexCounter); + } + } + } + } + + wrapContent(content, codeFormat) { + for(let transform of this.contentTransforms) { + let result = transform(content); + if(result === false) { + return content; + } + } + + let highlightedContent = HighlightPairedShortcode(content, codeFormat, "", { + trim: false + }); + let {document} = parseHTML(`${highlightedContent}`); + let counter = new IndexCounter(); + let bodyEl = document.getElementsByTagName("body")[0]; + this.walkTree(document, bodyEl, counter); + return bodyEl.innerHTML; + } +} + +module.exports = CharacterWrap; diff --git a/src/HighlightLines.js b/src/HighlightLines.js new file mode 100644 index 0000000..4aa8312 --- /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; diff --git a/src/HighlightLinesGroup.js b/src/HighlightLinesGroup.js new file mode 100644 index 0000000..47c0aaa --- /dev/null +++ b/src/HighlightLinesGroup.js @@ -0,0 +1,68 @@ +const HighlightLines = require("./HighlightLines"); + +class HighlightLinesGroup { + constructor(str, delimiter) { + this.init(str, delimiter); + } + + init(str = "", delimiter = " ") { + this.str = str; + this.delimiter = delimiter; + + let split = str.split(this.delimiter); + this.highlights = new HighlightLines(split.length === 1 ? split[0] : ""); + this.highlightsAdd = new HighlightLines(split.length === 2 ? split[0] : ""); + this.highlightsRemove = new HighlightLines(split.length === 2 ? split[1] : ""); + } + + isHighlighted(lineNumber) { + return this.highlights.isHighlighted(lineNumber); + } + + isHighlightedAdd(lineNumber) { + return this.highlightsAdd.isHighlighted(lineNumber); + } + + isHighlightedRemove(lineNumber) { + return this.highlightsRemove.isHighlighted(lineNumber); + } + + hasTagMismatch(line) { + let startCount = line.split(" or on the line. + // for example, we can’t wrap with + if(this.hasTagMismatch(line)) { + return line; + } + + return before + line + after; + } + + getLineMarkup(lineNumber, line, extraClasses = []) { + let extraClassesStr = (extraClasses.length ? " " + extraClasses.join(" ") : ""); + + if (this.isHighlighted(lineNumber)) { + return this.splitLineMarkup(line, ``, ``); + } + if (this.isHighlightedAdd(lineNumber)) { + return this.splitLineMarkup(line, ``, ``); + } + if (this.isHighlightedRemove(lineNumber)) { + return this.splitLineMarkup(line, ``, ``); + } + + return this.splitLineMarkup( line, ``, ``); + } +} + +module.exports = HighlightLinesGroup; diff --git a/src/HighlightPairedShortcode.js b/src/HighlightPairedShortcode.js new file mode 100644 index 0000000..cb79a6c --- /dev/null +++ b/src/HighlightPairedShortcode.js @@ -0,0 +1,34 @@ +const Prism = require("prismjs"); +const PrismLoader = require("./PrismLoader"); +const HighlightLinesGroup = require("./HighlightLinesGroup"); +const getAttributes = require("./getAttributes"); + +module.exports = function (content, language, highlightNumbers, options = {}) { + // default to on + if(options.trim === undefined || options.trim === true) { + content = content.trim(); + } + + let highlightedContent; + if( language === "text" ) { + highlightedContent = content; + } else { + highlightedContent = Prism.highlight(content, PrismLoader(language), language); + } + + let group = new HighlightLinesGroup(highlightNumbers); + let lines = highlightedContent.split(/\r?\n/); + lines = lines.map(function(line, j) { + if(options.alwaysWrapLineHighlights || highlightNumbers) { + let lineContent = group.getLineMarkup(j, line); + return lineContent; + } + return line; + }); + + const context = { content: content, language: language, options: options }; + const preAttributes = getAttributes(options.preAttributes, context); + const codeAttributes = getAttributes(options.codeAttributes, context); + + return `` + lines.join(options.lineSeparator || "
") + ""; +}; diff --git a/src/LiquidHighlightTag.js b/src/LiquidHighlightTag.js new file mode 100644 index 0000000..26eb7ce --- /dev/null +++ b/src/LiquidHighlightTag.js @@ -0,0 +1,49 @@ +const HighlightPairedShortcode = require("./HighlightPairedShortcode"); + +class LiquidHighlightTag { + constructor(liquidEngine) { + this.liquidEngine = liquidEngine; + } + + getObject(options = {}) { + let ret = function(highlighter) { + return { + parse: function(tagToken, remainTokens) { + let split = tagToken.args.split(" "); + + this.language = split.shift(); + this.highlightLines = split.join(" "); + + 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 ${tagToken.getText()} not closed`); + }); + + stream.start(); + }, + render: function(scope, hash) { + let tokens = this.tokens.map(token => { + return token.raw || token.getText(); + }); + let tokenStr = tokens.join("").trim(); + return Promise.resolve(HighlightPairedShortcode(tokenStr, this.language, this.highlightLines, options)); + } + }; + }; + + return ret(this); + } +} + +module.exports = LiquidHighlightTag; diff --git a/src/PrismLoader.js b/src/PrismLoader.js new file mode 100644 index 0000000..a3515a2 --- /dev/null +++ b/src/PrismLoader.js @@ -0,0 +1,42 @@ +const Prism = require("prismjs"); +const PrismLoader = require("prismjs/components/index.js"); +// Avoid "Language does not exist: " console logs +PrismLoader.silent = true; + +const PrismAlias = require("./PrismNormalizeAlias"); + +module.exports = function(language) { + let diffRemovedRawName = language; + if(language.startsWith("diff-")) { + diffRemovedRawName = language.substr("diff-".length); + } + // aliasing should ignore diff- + let aliasedName = PrismAlias(diffRemovedRawName); + + if(!Prism.languages[ aliasedName ]) { + PrismLoader(aliasedName); + } + if(!Prism.languages[ aliasedName ]) { + throw new Error(`"${language}" is not a valid Prism.js language for eleventy-plugin-syntaxhighlight`); + } + + if(!language.startsWith("diff-")) { + return Prism.languages[ aliasedName ]; + } + + // language has diff- prefix + let fullLanguageName = `diff-${aliasedName}`; + + if(!Prism.languages.diff) { + PrismLoader("diff"); + // Bundled Plugin + require("prismjs/plugins/diff-highlight/prism-diff-highlight"); + } + + // Store into with aliased keys + // ts -> diff-typescript + // js -> diff-javascript + Prism.languages[ fullLanguageName ] = Prism.languages.diff; + + return Prism.languages[ fullLanguageName ]; +}; diff --git a/src/PrismNormalizeAlias.js b/src/PrismNormalizeAlias.js new file mode 100644 index 0000000..40e9bb3 --- /dev/null +++ b/src/PrismNormalizeAlias.js @@ -0,0 +1,42 @@ +const Prism = require("prismjs"); + +const HARDCODED_ALIASES = { + njk: "jinja2", + nunjucks: "jinja2", +}; + +// This was added to make `ts` resolve to `typescript` correctly. +// The Prism loader doesn’t seem to always handle aliasing correctly. +module.exports = function(language) { + try { + // Careful this is not public API stuff: + // https://github.com/PrismJS/prism/issues/2146 + const PrismComponents = require("prismjs/components.json"); + let langs = PrismComponents.languages; + + // Manual override + if(HARDCODED_ALIASES[language]) { + language = HARDCODED_ALIASES[language]; + } + + if(langs[ language ]) { + return language; + } + for(let langName in langs) { + if(Array.isArray(langs[langName].alias)) { + for(let alias of langs[langName].alias) { + if(alias === language) { + return langName; + } + } + } else if(langs[langName].alias === language) { + return langName; + } + } + } catch(e) { + // Couldn’t find the components file, aliases may not resolve correctly + // See https://github.com/11ty/eleventy-plugin-syntaxhighlight/issues/19 + } + + return language; +} diff --git a/src/getAttributes.js b/src/getAttributes.js new file mode 100644 index 0000000..603c6a5 --- /dev/null +++ b/src/getAttributes.js @@ -0,0 +1,62 @@ +function attributeEntryToString(attribute, context) { + let [key, value] = attribute; + + if (typeof value === "function") { // Callback must return a string or a number + value = value(context); // Run the provided callback and store the result + } + + if (typeof value !== "string" && typeof value !== "number") { + throw new Error( + `Attribute "${key}" must have, or evaluate to, a value of type string or number, not "${typeof value}".` + ); + } + + return `${key}="${value}"`; +} + +/** + * ## Usage + * The function `getAttributes` is used to convert an object, `attributes`, with HTML attributes as keys and the values as the corresponding HTML attribute's values. + * If it is falsey, an empty string will be returned. + * + * ```js + getAttributes({ + tabindex: 0, + 'data-language': function (context) { return context.language; }, + 'data-otherStuff': 'value' + }) // => ' tabindex="0" data-language="JavaScript" data-otherStuff="value"' + ``` + * + * @param {{[s: string]: string | number}} attributes An object with key-value pairs that represent attributes. + * @param {object} context An object with the current context. + * @param {string} context.content The code to parse and highlight. + * @param {string} context.language The language for the current instance. + * @param {object} context.options The options passed to the syntax highlighter. + * @returns {string} A string containing the above HTML attributes preceded by a single space. + */ +function getAttributes(attributes, context = {}) { + let langClass = context.language ? `language-${context.language}` : ""; + + if (!attributes) { + return langClass ? ` class="${langClass}"` : ""; + } else if (typeof attributes === "object") { + if(!("class" in attributes) && langClass) { + // class attribute should be first in order + let tempAttrs = { class: langClass }; + for(let key in attributes) { + tempAttrs[key] = attributes[key]; + } + attributes = tempAttrs; + } + + const formattedAttributes = Object.entries(attributes).map( + entry => attributeEntryToString(entry, context) + ); + + return formattedAttributes.length ? ` ${formattedAttributes.join(" ")}` : ""; + } else if (typeof attributes === "string") { + throw new Error("Syntax highlighter plugin custom attributes on
 and  must be an object. Received: " + JSON.stringify(attributes));
+  }
+}
+
+module.exports = getAttributes;
diff --git a/src/hasTemplateFormat.js b/src/hasTemplateFormat.js
new file mode 100644
index 0000000..ee81e89
--- /dev/null
+++ b/src/hasTemplateFormat.js
@@ -0,0 +1,13 @@
+module.exports = function(templateFormats = ["*"], format = false) {
+  if(!Array.isArray(templateFormats)) {
+    templateFormats = [templateFormats];
+  }
+
+  if( Array.isArray(templateFormats) ) {
+    if( templateFormats.indexOf("*") > -1 || templateFormats.indexOf(format) > -1 ) {
+      return true;
+    }
+  }
+
+  return false;
+};
diff --git a/src/markdownSyntaxHighlightOptions.js b/src/markdownSyntaxHighlightOptions.js
new file mode 100644
index 0000000..befb7c2
--- /dev/null
+++ b/src/markdownSyntaxHighlightOptions.js
@@ -0,0 +1,44 @@
+const Prism = require("prismjs");
+const PrismLoader = require("./PrismLoader");
+const HighlightLinesGroup = require("./HighlightLinesGroup");
+const getAttributes = require("./getAttributes");
+
+module.exports = function (options = {}) {
+  return function(str, language) {
+    if(!language) {
+      // empty string means defer to the upstream escaping code built into markdown lib.
+      return "";
+    }
+
+
+    let split = language.split("/");
+    if( split.length ) {
+      language = split.shift();
+    }
+
+    let html;
+    if(language === "text") {
+      html = str;
+    } else {
+      html = Prism.highlight(str, PrismLoader(language), language);
+    }
+
+    let hasHighlightNumbers = split.length > 0;
+    let highlights = new HighlightLinesGroup(split.join("/"), "/");
+    let lines = html.split("\n").slice(0, -1); // The last line is empty.
+
+    lines = lines.map(function(line, j) {
+      if(options.alwaysWrapLineHighlights || hasHighlightNumbers) {
+        let lineContent = highlights.getLineMarkup(j, line);
+        return lineContent;
+      }
+      return line;
+    });
+
+    const context = { content: str, language: language, options: options };
+    const preAttributes = getAttributes(options.preAttributes, context);
+    const codeAttributes = getAttributes(options.codeAttributes, context);
+
+    return `${lines.join(options.lineSeparator || "
")}
`; + }; +}; diff --git a/syntax-highlight.webc b/syntax-highlight.webc new file mode 100644 index 0000000..b96e973 --- /dev/null +++ b/syntax-highlight.webc @@ -0,0 +1,16 @@ + diff --git a/test/11tyjs-diff/.eleventy.js b/test/11tyjs-diff/.eleventy.js new file mode 100644 index 0000000..40a3b76 --- /dev/null +++ b/test/11tyjs-diff/.eleventy.js @@ -0,0 +1,5 @@ +const syntaxHighlight = require("../../"); + +module.exports = function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight); +}; diff --git a/test/11tyjs-diff/test.11ty.js b/test/11tyjs-diff/test.11ty.js new file mode 100644 index 0000000..e912a55 --- /dev/null +++ b/test/11tyjs-diff/test.11ty.js @@ -0,0 +1,4 @@ +module.exports = function(data) { + let result = this.highlight("diff-js", "-var test;"); + return result; +}; diff --git a/test/11tyjs-test/.eleventy.js b/test/11tyjs-test/.eleventy.js new file mode 100644 index 0000000..40a3b76 --- /dev/null +++ b/test/11tyjs-test/.eleventy.js @@ -0,0 +1,5 @@ +const syntaxHighlight = require("../../"); + +module.exports = function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight); +}; diff --git a/test/11tyjs-test/test.11ty.js b/test/11tyjs-test/test.11ty.js new file mode 100644 index 0000000..67c309f --- /dev/null +++ b/test/11tyjs-test/test.11ty.js @@ -0,0 +1,4 @@ +module.exports = function(data) { + let result = this.highlight("js", "var test;"); + return result; +}; diff --git a/test/CharacterWrapTest.js b/test/CharacterWrapTest.js new file mode 100644 index 0000000..cbc60bd --- /dev/null +++ b/test/CharacterWrapTest.js @@ -0,0 +1,13 @@ +const test = require("ava"); +const CharacterWrap = require("../src/CharacterWrap"); + +test("CharacterWrap", async t => { + let wrapper = new CharacterWrap(); + t.is(wrapper.wrapContent("", "html"), `
<html></html>
`); +}); + +test("CharacterWrap with different class prefix", async t => { + let wrapper = new CharacterWrap(); + wrapper.setClassPrefix("customprefix"); + t.is(wrapper.wrapContent("", "html"), `
<html></html>
`); +}); diff --git a/test/GetAttributesTest.js b/test/GetAttributesTest.js new file mode 100644 index 0000000..d555968 --- /dev/null +++ b/test/GetAttributesTest.js @@ -0,0 +1,54 @@ +const test = require("ava"); +const ga = require("../src/getAttributes"); + +test("Falsy", t => { + t.is(ga(false), ""); + t.is(ga(null), ""); + t.is(ga(undefined), ""); + t.is(ga(""), ""); + t.throws(() => { + ga(" test='1'"); + }); +}); + +test("Object syntax", t => { + t.is(ga({}), ""); + t.is(ga({ hi: 1 }), ' hi="1"'); + t.is(ga({ hi: 1, bye: 2 }), ' hi="1" bye="2"'); + t.is(ga({ class: "my-class", bye: 2 }), ' class="my-class" bye="2"'); + t.is(ga({ hi: function(ctx) { return '1'; }, bye: 2 }), ' hi="1" bye="2"'); +}); + +test("Function callback", t => { + t.is(ga({ "data-lang": ({language}) => language }, { + language: "html" + }), ' class="language-html" data-lang="html"'); +}); + +test("Function callback with class attribute (override)", t => { + t.is(ga({ + class: ({language}) => "my-custom-"+language + }, { + language: "html" + }), ' class="my-custom-html"'); +}); + +test("Function callback must return string or integer", t => { + t.throws(() => { + ga({ "data-lang": ({language}) => undefined }, { + language: "html" + }) + }); + + t.throws(() => { + ga({ "data-lang": ({language}) => {} }, { + language: "html" + }) + }); + + t.throws(() => { + ga({ "data-lang": ({language}) => false }, { + language: "html" + }) + }); +}); diff --git a/test/HasTemplateFormatTest.js b/test/HasTemplateFormatTest.js new file mode 100644 index 0000000..ad0cda5 --- /dev/null +++ b/test/HasTemplateFormatTest.js @@ -0,0 +1,25 @@ +const test = require("ava"); +const hasTemplateFormat = require("../src/hasTemplateFormat"); + +test("hasTemplateFormats", t => { + t.true(hasTemplateFormat("*", "liquid")); + t.false(hasTemplateFormat([], "liquid")); + + // options not specified, defaults to * + t.true(hasTemplateFormat(undefined, "liquid")); + t.false(hasTemplateFormat(null, "liquid")); + + t.true(hasTemplateFormat("*", false)); + t.false(hasTemplateFormat([], false)); + + // options not specified, defaults to * + t.true(hasTemplateFormat(undefined, false)); + t.false(hasTemplateFormat(null, false)); + + t.true(hasTemplateFormat(["*"], "liquid")); + t.true(hasTemplateFormat(["liquid"], "liquid")); + t.true(hasTemplateFormat(["liquid", "njk"], "liquid")); + t.true(hasTemplateFormat(["liquid", "njk"], "njk")); + t.true(hasTemplateFormat(["liquid", "njk", "md"], "md")); + t.false(hasTemplateFormat(["liquid", "njk", "md"], "pug")); +}); diff --git a/test/HighlightLinesGroupTest.js b/test/HighlightLinesGroupTest.js new file mode 100644 index 0000000..9c575a8 --- /dev/null +++ b/test/HighlightLinesGroupTest.js @@ -0,0 +1,93 @@ +const test = require("ava"); +const HighlightLinesGroup = require("../src/HighlightLinesGroup"); + +test("Empty", t => { + let hilite = new HighlightLinesGroup(""); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), false); +}); + +test("Highlight irrelevant (-)", t => { + let hilite = new HighlightLinesGroup("-"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), false); +}); + +test("Highlight simple (0)", t => { + let hilite = new HighlightLinesGroup("0"); + t.is(hilite.isHighlighted(0), true); + t.is(hilite.isHighlighted(1), false); +}); + + +test("Highlight simple (1)", t => { + let hilite = new HighlightLinesGroup("1"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), true); +}); + +test("Highlight complex", t => { + let hilite = new HighlightLinesGroup("1-2,4"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), true); + t.is(hilite.isHighlighted(2), true); + t.is(hilite.isHighlighted(3), false); + t.is(hilite.isHighlighted(4), true); + t.is(hilite.isHighlighted(5), false); +}); + +test("Add/Remove", t => { + let hilite = new HighlightLinesGroup("1-2,4 3"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), false); + t.is(hilite.isHighlighted(2), false); + t.is(hilite.isHighlighted(3), false); + t.is(hilite.isHighlighted(4), false); + t.is(hilite.isHighlighted(5), false); + + t.is(hilite.isHighlightedAdd(0), false); + t.is(hilite.isHighlightedAdd(1), true); + t.is(hilite.isHighlightedAdd(2), true); + t.is(hilite.isHighlightedAdd(3), false); + t.is(hilite.isHighlightedAdd(4), true); + t.is(hilite.isHighlightedAdd(5), false); + + t.is(hilite.isHighlightedRemove(0), false); + t.is(hilite.isHighlightedRemove(1), false); + t.is(hilite.isHighlightedRemove(2), false); + t.is(hilite.isHighlightedRemove(3), true); + t.is(hilite.isHighlightedRemove(4), false); + t.is(hilite.isHighlightedRemove(5), false); +}); + +test("Add/Remove New Delimiter", t => { + let hilite = new HighlightLinesGroup("1-2,4/3", "/"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), false); + t.is(hilite.isHighlighted(2), false); + t.is(hilite.isHighlighted(3), false); + t.is(hilite.isHighlighted(4), false); + t.is(hilite.isHighlighted(5), false); + + t.is(hilite.isHighlightedAdd(0), false); + t.is(hilite.isHighlightedAdd(1), true); + t.is(hilite.isHighlightedAdd(2), true); + t.is(hilite.isHighlightedAdd(3), false); + t.is(hilite.isHighlightedAdd(4), true); + t.is(hilite.isHighlightedAdd(5), false); + + t.is(hilite.isHighlightedRemove(0), false); + t.is(hilite.isHighlightedRemove(1), false); + t.is(hilite.isHighlightedRemove(2), false); + t.is(hilite.isHighlightedRemove(3), true); + t.is(hilite.isHighlightedRemove(4), false); + t.is(hilite.isHighlightedRemove(5), false); +}); + +test("Split Line Markup", t => { + let hilite = new HighlightLinesGroup("", " "); + t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "BEFORETestAFTER"); + t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "BEFORETestAFTER"); + t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "Test"); + t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "Test"); +}); diff --git a/test/HighlightLinesTest.js b/test/HighlightLinesTest.js new file mode 100644 index 0000000..cbbb330 --- /dev/null +++ b/test/HighlightLinesTest.js @@ -0,0 +1,41 @@ +const test = require("ava"); +const HighlightLines = require("../src/HighlightLines"); + +test("HighlightLines empty", t => { + let hilite = new HighlightLines(""); + t.is(hilite.isHighlighted(0), false); +}); + +test("HighlightLines single 0", t => { + let hilite = new HighlightLines("0"); + t.is(hilite.isHighlighted(0), true); + t.is(hilite.isHighlighted(1), false); +}); + +test("HighlightLines single 1", t => { + let hilite = new HighlightLines("1"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), true); +}); + +test("HighlightLines range", t => { + let hilite = new HighlightLines("1-3"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), true); + t.is(hilite.isHighlighted(2), true); + t.is(hilite.isHighlighted(3), true); + t.is(hilite.isHighlighted(4), false); +}); + +test("HighlightLines multiple ranges", t => { + let hilite = new HighlightLines("1-3,5-7"); + t.is(hilite.isHighlighted(0), false); + t.is(hilite.isHighlighted(1), true); + t.is(hilite.isHighlighted(2), true); + t.is(hilite.isHighlighted(3), true); + t.is(hilite.isHighlighted(4), false); + t.is(hilite.isHighlighted(5), true); + t.is(hilite.isHighlighted(6), true); + t.is(hilite.isHighlighted(7), true); + t.is(hilite.isHighlighted(8), false); +}); diff --git a/test/HighlightPairedShortcodeTest.js b/test/HighlightPairedShortcodeTest.js new file mode 100644 index 0000000..59c8b44 --- /dev/null +++ b/test/HighlightPairedShortcodeTest.js @@ -0,0 +1,104 @@ +const test = require("ava"); +const HighlightPairedShortcode = require("../src/HighlightPairedShortcode"); + +test("Base", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +}); + +test("Base with LF EOL, always wrap highlights", async t => { + t.is(await HighlightPairedShortcode('alert();\nalert();', +"js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +}); + +test("Base with LF EOL, no wrap highlights", async t => { + t.is(await HighlightPairedShortcode('alert();\nalert();', +"js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); +}); + +test("Base with CRLF EOL, always wrap highlights", async t => { + t.is(await HighlightPairedShortcode('alert();\r\nalert();', +"js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +}); + +test("Base with CRLF EOL, no wrap highlights", async t => { + t.is(await HighlightPairedShortcode('alert();\r\nalert();', +"js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); +}); + +test("Base with custom attributes", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "", { alwaysWrapLineHighlights: true, preAttributes: { tabindex: 0, 'data-testid': 'code' } }), `
alert();
alert();
`); +}); + +test("Base change the line separator", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "", { + alwaysWrapLineHighlights: true, + lineSeparator: "\n", +}), `
alert();
+alert();
`); +}); + +test("Base No line highlights", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", ""), `
alert();
alert();
`); +}); + +test("Highlight Active", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); + + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "0-1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +}); + +test("Highlight Add/Remove", async t => { + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "0 1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); + + t.is(await HighlightPairedShortcode(`alert(); +alert();`, "js", "1 0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +}); + +test("Test loader typescript", async t => { + let script = `function greeter(person) { + return "Hello, " + person; +} + +let user = "Jane User"; + +document.body.textContent = greeter(user);`; + + t.is(await HighlightPairedShortcode(script, "typescript"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); +}); + +test("Test loader ts", async t => { + let script = `function greeter(person) { + return "Hello, " + person; +} + +let user = "Jane User"; + +document.body.textContent = greeter(user);` + + t.is(await HighlightPairedShortcode(script, "ts"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); +}); + +test("Test loader invalid language", async t => { + await t.throwsAsync(async () => { + await HighlightPairedShortcode("", "asldkjflksdaj"); + }); +}); + +test("Trim content option (defaults true)", async t => { + t.is(await HighlightPairedShortcode(` alert(); +alert(); `, "js", "", {}), `
alert();
alert();
`); + + t.is(await HighlightPairedShortcode(` alert(); +alert(); `, "js", "", { trim: true }), `
alert();
alert();
`); + + t.is(await HighlightPairedShortcode(` alert(); +alert(); `, "js", "", { trim: false }), `
 alert();
alert();
`); + +}); diff --git a/test/JavaScriptFunctionTest.js b/test/JavaScriptFunctionTest.js new file mode 100644 index 0000000..2a11965 --- /dev/null +++ b/test/JavaScriptFunctionTest.js @@ -0,0 +1,24 @@ +const test = require("ava"); +const Eleventy = require('@11ty/eleventy'); + +test("JavaScript Function", async t => { + let elev = new Eleventy("./test/11tyjs-test/", "./test/11tyjs-test/_site/", { + configPath: "./test/11tyjs-test/.eleventy.js" + }); + let json = await elev.toJSON(); + + t.is(json.length, 1); + let rendered = json[0].content; + t.is(`
var test;
`, rendered); +}); + +test("JavaScript Function Diff", async t => { + let elev = new Eleventy("./test/11tyjs-diff/", "./test/11tyjs-diff/_site/", { + configPath: "./test/11tyjs-diff/.eleventy.js" + }); + let json = await elev.toJSON(); + + t.is(json.length, 1); + let rendered = json[0].content; + t.is(`
-var test;
`, rendered); +}); diff --git a/test/LiquidHighlightTagTest.js b/test/LiquidHighlightTagTest.js new file mode 100644 index 0000000..8d6eaa4 --- /dev/null +++ b/test/LiquidHighlightTagTest.js @@ -0,0 +1,43 @@ +const test = require("ava"); +const { Liquid } = require('liquidjs'); +const LiquidHighlightTag = require("../src/LiquidHighlightTag"); + +async function renderLiquid(str, data = {}, engine = null) { + if(!engine) { + engine = new Liquid(); + } + + let result = await engine.parseAndRender(str, data); + return result; +} + +test("Test Render", async t => { + t.is("Hi Zach", await renderLiquid("Hi {{name}}", {name: "Zach"})); +}); + +test("Test Highlight Tag Render", async t => { + let engine = new Liquid(); + let tag = new LiquidHighlightTag(engine); + engine.registerTag("highlight", tag.getObject()); + + let rendered = await renderLiquid("{% highlight js %}var test;{% endhighlight %}", {}, engine); + t.is(`
var test;
`, rendered); +}); + +test("Njk Alias", async t => { + let engine = new Liquid(); + let tag = new LiquidHighlightTag(engine); + engine.registerTag("highlight", tag.getObject()); + + let rendered = await renderLiquid("{% highlight njk %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); + t.is(`
{% raw %}hello{% endraw %}
`, rendered); +}); + +test("Nunjucks alias", async t => { + let engine = new Liquid(); + let tag = new LiquidHighlightTag(engine); + engine.registerTag("highlight", tag.getObject()); + + let rendered = await renderLiquid("{% highlight nunjucks %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); + t.is(`
{% raw %}hello{% endraw %}
`, rendered); +}); diff --git a/test/MarkdownHighlightTest.js b/test/MarkdownHighlightTest.js new file mode 100644 index 0000000..1dced51 --- /dev/null +++ b/test/MarkdownHighlightTest.js @@ -0,0 +1,85 @@ +const test = require("ava"); +const md = require("markdown-it"); +const markdownPrismJsOptions = require("../src/markdownSyntaxHighlightOptions"); + +test("Test Markdown Highlighter", t => { + let mdLib = md(); + mdLib.set({ + highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) + }); + t.is(mdLib.render(`\`\`\`js +alert(); +\`\`\``).trim(), `
alert();
`); +}); + +test("Test Markdown Highlighter No Line Highlights", t => { + let mdLib = md(); + mdLib.set({ + highlight: markdownPrismJsOptions() + }); + t.is(mdLib.render(`\`\`\`js +alert(); +\`\`\``).trim(), `
alert();
`); +}); + +test("Markdown with `preAttributes`", t => { + let mdLib = md(); + mdLib.set({ + highlight: markdownPrismJsOptions({ + alwaysWrapLineHighlights: true, + preAttributes: { + // will override class="language-js" + class: ({language}) => "not-a-lang-" + language + } + }) + }); + t.is(mdLib.render(`\`\`\`js +alert(); +\`\`\``).trim(), `
alert();
`); +}); + +test("Test Njk Alias", t => { + let mdLib = md(); + mdLib.set({ + highlight: markdownPrismJsOptions() + }); + t.is(mdLib.render(`\`\`\`njk +{% raw %}hello{% endraw %} +\`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); +}); + +test("Test Nunjucks Alias", t => { + let mdLib = md(); + mdLib.set({ + highlight: markdownPrismJsOptions() + }); + t.is(mdLib.render(`\`\`\`nunjucks +{% raw %}hello{% endraw %} +\`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); +}); + + +// test("Test Markdown Highlighter Block Comment", t => { +// let mdLib = md(); +// mdLib.set({ +// highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) +// }); +// t.is(mdLib.render(`\`\`\`js +// /* +// * this is a string +// */ +// \`\`\``).trim(), `
/*
* this is a string
*/
`); +// }); + +// TODO this still ain’t working right with the line highlighter. +// test("Test Markdown Highlighter GraphQL Example", t => { +// let mdLib = md(); +// mdLib.set({ +// highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) +// }); +// t.is(mdLib.render(`\`\`\`js +// var schema = buildSchema(\`type Query { +// hello: String +// }\`); +// \`\`\``).trim(), ``); +// });