├── .eleventyignore ├── .gitattributes ├── test ├── issue-80 │ ├── index.md │ └── .eleventy.js ├── 11tyjs-test │ ├── test.11ty.js │ └── .eleventy.js ├── issue-75 │ ├── index.njk │ └── .eleventy.js ├── 11tyjs-diff │ ├── .eleventy.js │ └── test.11ty.js ├── HasTemplateFormatTest.js ├── JavaScriptFunctionTest.mjs ├── HighlightLinesTest.js ├── GetAttributesTest.js ├── EleventyTest.mjs ├── LiquidHighlightTagTest.js ├── HighlightLinesGroupTest.js ├── MarkdownHighlightTest.js └── HighlightPairedShortcodeTest.js ├── .gitignore ├── .editorconfig ├── demo ├── test.css ├── eleventy-config.js ├── test-liquid.liquid ├── test-markdown.md ├── test-nunjucks.njk └── prism-theme.css ├── .eslintrc.js ├── src ├── hasTemplateFormat.js ├── HighlightLines.js ├── PrismNormalizeAlias.js ├── PrismLoader.js ├── HighlightPairedShortcode.js ├── LiquidHighlightTag.js ├── markdownSyntaxHighlightOptions.js ├── HighlightLinesGroup.js └── getAttributes.js ├── README.md ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── syntax-highlight.webc ├── LICENSE ├── package.json └── .eleventy.js /.eleventyignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | text eol=lf 2 | -------------------------------------------------------------------------------- /test/issue-80/index.md: -------------------------------------------------------------------------------- 1 | ```diff-javascript 2 | - foo() 3 | ``` 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | _site/ 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /test/11tyjs-test/test.11ty.js: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | let result = this.highlight("js", "var test;"); 3 | return result; 4 | }; 5 | -------------------------------------------------------------------------------- /test/issue-75/index.njk: -------------------------------------------------------------------------------- 1 | {% highlight "html" %} 2 |

Hello

3 | {% endhighlight %} 4 | {% highlight "diff-html" %} 5 | -

Hello

6 | {% endhighlight %} 7 | -------------------------------------------------------------------------------- /test/issue-75/.eleventy.js: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require("../../"); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight); 5 | }; 6 | -------------------------------------------------------------------------------- /test/issue-80/.eleventy.js: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require("../../"); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight); 5 | }; 6 | -------------------------------------------------------------------------------- /test/11tyjs-diff/.eleventy.js: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require("../../"); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight); 5 | }; 6 | -------------------------------------------------------------------------------- /test/11tyjs-test/.eleventy.js: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require("../../"); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight); 5 | }; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /test/11tyjs-diff/test.11ty.js: -------------------------------------------------------------------------------- 1 | module.exports = function(data) { 2 | let result1 = this.highlight("diff", "-var test;"); 3 | let result2 = this.highlight("diff-js", "-var test;"); 4 | return result1 + "\n" + result2; 5 | }; 6 | -------------------------------------------------------------------------------- /demo/test.css: -------------------------------------------------------------------------------- 1 | .highlight-line { 2 | display: inline-block; 3 | } 4 | 5 | /* allow highlighting empty lines */ 6 | .highlight-line:empty:before { 7 | content: " "; 8 | } 9 | 10 | .highlight-line:not(:last-child) { 11 | min-width: 100%; 12 | } 13 | .highlight-line .highlight-line:not(:last-child) { 14 | min-width: 0; 15 | } 16 | -------------------------------------------------------------------------------- /demo/eleventy-config.js: -------------------------------------------------------------------------------- 1 | const syntaxHighlight = require("../.eleventy.js"); 2 | 3 | module.exports = function(eleventyConfig) { 4 | eleventyConfig.addPlugin(syntaxHighlight, { 5 | // alwaysWrapLineHighlights: true 6 | preAttributes: { tabindex: 0 } 7 | }); 8 | 9 | eleventyConfig.setTemplateFormats("njk,liquid,md,css"); 10 | }; 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | node: true 5 | }, 6 | extends: "eslint:recommended", 7 | parserOptions: { 8 | sourceType: "module", 9 | ecmaVersion: 2017 10 | }, 11 | rules: { 12 | indent: ["error", 2], 13 | "linebreak-style": ["error", "unix"], 14 | quotes: ["error", "double"], 15 | semi: ["error", "always"] 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/hasTemplateFormat.js: -------------------------------------------------------------------------------- 1 | module.exports = function(templateFormats = ["*"], format = false) { 2 | if(!Array.isArray(templateFormats)) { 3 | templateFormats = [templateFormats]; 4 | } 5 | 6 | if( Array.isArray(templateFormats) ) { 7 | if( templateFormats.indexOf("*") > -1 || templateFormats.indexOf(format) > -1 ) { 8 | return true; 9 | } 10 | } 11 | 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

eleventy Logo

2 | 3 | # eleventy-plugin-syntaxhighlight 🕚⚡️🎈🐀 4 | 5 | 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. 6 | 7 | ## Read the [Full Documentation on 11ty.dev](https://www.11ty.dev/docs/plugins/syntaxhighlight/) 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - "gh-pages" 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 11 | node: ["16", "18", "20", "22", "24"] 12 | name: Node.js ${{ matrix.node }} on ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node }} 19 | # cache: npm 20 | - run: npm install 21 | - run: npm test 22 | env: 23 | YARN_GPG: no 24 | -------------------------------------------------------------------------------- /syntax-highlight.webc: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /demo/test-liquid.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% highlight js %} 12 | function myFunction() { 13 | return true; 14 | } 15 | {% endhighlight %} 16 | 17 | {% highlight js %} 18 | let multilineString = ` 19 | this is the first line 20 | this is the middle line 21 | this is the last line 22 | `; 23 | {% endhighlight %} 24 | 25 | {% highlight js 1,3 %} 26 | let multilineString = ` 27 | this is the first line 28 | this is the middle line 29 | this is the last line 30 | `; 31 | {% endhighlight %} 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/HighlightLines.js: -------------------------------------------------------------------------------- 1 | class HighlightLines { 2 | constructor(rangeStr) { 3 | this.highlights = this.convertRangeToHash(rangeStr); 4 | } 5 | 6 | convertRangeToHash(rangeStr) { 7 | let hash = {}; 8 | if( !rangeStr ) { 9 | return hash; 10 | } 11 | 12 | let ranges = rangeStr.split(",").map(function(range) { 13 | return range.trim(); 14 | }); 15 | 16 | for(let range of ranges) { 17 | let startFinish = range.split('-'); 18 | let start = parseInt(startFinish[0], 10); 19 | let end = parseInt(startFinish[1] || start, 10); 20 | 21 | for( let j = start, k = end; j<=k; j++ ) { 22 | hash[j] = true; 23 | } 24 | } 25 | return hash; 26 | } 27 | 28 | isHighlighted(lineNumber) { 29 | return !!this.highlights[lineNumber]; 30 | } 31 | } 32 | 33 | module.exports = HighlightLines; 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release to npm 2 | on: 3 | release: 4 | types: [published] 5 | permissions: read-all 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7 14 | - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 4.0.3 15 | with: 16 | node-version: "20" 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: npm install 19 | - run: npm test 20 | - if: ${{ github.event.release.tag_name != '' && env.NPM_PUBLISH_TAG != '' }} 21 | run: npm publish --provenance --access=public --tag=${{ env.NPM_PUBLISH_TAG }} 22 | env: 23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 24 | NPM_PUBLISH_TAG: ${{ contains(github.event.release.tag_name, '-beta.') && 'beta' || 'latest' }} 25 | -------------------------------------------------------------------------------- /test/HasTemplateFormatTest.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const hasTemplateFormat = require("../src/hasTemplateFormat"); 3 | 4 | test("hasTemplateFormats", t => { 5 | t.true(hasTemplateFormat("*", "liquid")); 6 | t.false(hasTemplateFormat([], "liquid")); 7 | 8 | // options not specified, defaults to * 9 | t.true(hasTemplateFormat(undefined, "liquid")); 10 | t.false(hasTemplateFormat(null, "liquid")); 11 | 12 | t.true(hasTemplateFormat("*", false)); 13 | t.false(hasTemplateFormat([], false)); 14 | 15 | // options not specified, defaults to * 16 | t.true(hasTemplateFormat(undefined, false)); 17 | t.false(hasTemplateFormat(null, false)); 18 | 19 | t.true(hasTemplateFormat(["*"], "liquid")); 20 | t.true(hasTemplateFormat(["liquid"], "liquid")); 21 | t.true(hasTemplateFormat(["liquid", "njk"], "liquid")); 22 | t.true(hasTemplateFormat(["liquid", "njk"], "njk")); 23 | t.true(hasTemplateFormat(["liquid", "njk", "md"], "md")); 24 | t.false(hasTemplateFormat(["liquid", "njk", "md"], "pug")); 25 | }); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zach Leatherman @zachleat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/PrismNormalizeAlias.js: -------------------------------------------------------------------------------- 1 | const HARDCODED_ALIASES = { 2 | njk: "jinja2", 3 | nunjucks: "jinja2", 4 | }; 5 | 6 | // This was added to make `ts` resolve to `typescript` correctly. 7 | // The Prism loader doesn’t seem to always handle aliasing correctly. 8 | module.exports = function(language) { 9 | try { 10 | // Careful this is not public API stuff: 11 | // https://github.com/PrismJS/prism/issues/2146 12 | const PrismComponents = require("prismjs/components.json"); 13 | let langs = PrismComponents.languages; 14 | 15 | // Manual override 16 | if(HARDCODED_ALIASES[language]) { 17 | language = HARDCODED_ALIASES[language]; 18 | } 19 | 20 | if(langs[ language ]) { 21 | return language; 22 | } 23 | for(let langName in langs) { 24 | if(Array.isArray(langs[langName].alias)) { 25 | for(let alias of langs[langName].alias) { 26 | if(alias === language) { 27 | return langName; 28 | } 29 | } 30 | } else if(langs[langName].alias === language) { 31 | return langName; 32 | } 33 | } 34 | } catch(e) { 35 | // Couldn’t find the components file, aliases may not resolve correctly 36 | // See https://github.com/11ty/eleventy-plugin-syntaxhighlight/issues/19 37 | } 38 | 39 | return language; 40 | } 41 | -------------------------------------------------------------------------------- /test/JavaScriptFunctionTest.mjs: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import Eleventy from '@11ty/eleventy'; 3 | 4 | test("JavaScript Function", async t => { 5 | let elev = new Eleventy("./test/11tyjs-test/", "./test/11tyjs-test/_site/", { 6 | configPath: "./test/11tyjs-test/.eleventy.js" 7 | }); 8 | let json = await elev.toJSON(); 9 | 10 | t.is(json.length, 1); 11 | let rendered = json[0].content; 12 | t.is(`
var test;
`, rendered); 13 | }); 14 | 15 | test("JavaScript Function Diff #76", async t => { 16 | let elev = new Eleventy("./test/11tyjs-diff/", "./test/11tyjs-diff/_site/", { 17 | configPath: "./test/11tyjs-diff/.eleventy.js" 18 | }); 19 | let json = await elev.toJSON(); 20 | 21 | t.is(json.length, 1); 22 | let rendered = json[0].content; 23 | t.is(`
-var test;
24 |
-var test;
`, rendered); 25 | }); 26 | -------------------------------------------------------------------------------- /test/HighlightLinesTest.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const HighlightLines = require("../src/HighlightLines"); 3 | 4 | test("HighlightLines empty", t => { 5 | let hilite = new HighlightLines(""); 6 | t.is(hilite.isHighlighted(0), false); 7 | }); 8 | 9 | test("HighlightLines single 0", t => { 10 | let hilite = new HighlightLines("0"); 11 | t.is(hilite.isHighlighted(0), true); 12 | t.is(hilite.isHighlighted(1), false); 13 | }); 14 | 15 | test("HighlightLines single 1", t => { 16 | let hilite = new HighlightLines("1"); 17 | t.is(hilite.isHighlighted(0), false); 18 | t.is(hilite.isHighlighted(1), true); 19 | }); 20 | 21 | test("HighlightLines range", t => { 22 | let hilite = new HighlightLines("1-3"); 23 | t.is(hilite.isHighlighted(0), false); 24 | t.is(hilite.isHighlighted(1), true); 25 | t.is(hilite.isHighlighted(2), true); 26 | t.is(hilite.isHighlighted(3), true); 27 | t.is(hilite.isHighlighted(4), false); 28 | }); 29 | 30 | test("HighlightLines multiple ranges", t => { 31 | let hilite = new HighlightLines("1-3,5-7"); 32 | t.is(hilite.isHighlighted(0), false); 33 | t.is(hilite.isHighlighted(1), true); 34 | t.is(hilite.isHighlighted(2), true); 35 | t.is(hilite.isHighlighted(3), true); 36 | t.is(hilite.isHighlighted(4), false); 37 | t.is(hilite.isHighlighted(5), true); 38 | t.is(hilite.isHighlighted(6), true); 39 | t.is(hilite.isHighlighted(7), true); 40 | t.is(hilite.isHighlighted(8), false); 41 | }); 42 | -------------------------------------------------------------------------------- /src/PrismLoader.js: -------------------------------------------------------------------------------- 1 | const Prism = require("prismjs"); 2 | const PrismLoader = require("prismjs/components/index.js"); 3 | // Avoid "Language does not exist: " console logs 4 | PrismLoader.silent = true; 5 | 6 | require("prismjs/components/prism-diff.js"); 7 | 8 | // Load diff-highlight plugin 9 | require("prismjs/plugins/diff-highlight/prism-diff-highlight"); 10 | 11 | const PrismAlias = require("./PrismNormalizeAlias"); 12 | 13 | module.exports = function(language, options = {}) { 14 | let diffRemovedRawName = language; 15 | if(language.startsWith("diff-")) { 16 | diffRemovedRawName = language.substr("diff-".length); 17 | } 18 | // aliasing should ignore diff- 19 | let aliasedName = PrismAlias(diffRemovedRawName); 20 | 21 | if(!Prism.languages[ aliasedName ]) { // matches `diff` too 22 | PrismLoader(aliasedName); 23 | } 24 | 25 | if(options.errorOnInvalidLanguage && !Prism.languages[ aliasedName ]) { 26 | throw new Error(`"${language}" is not a valid Prism.js language for eleventy-plugin-syntaxhighlight`); 27 | } 28 | 29 | if(!language.startsWith("diff-")) { 30 | return Prism.languages[ aliasedName ]; 31 | } 32 | 33 | // language has diff- prefix 34 | let fullLanguageName = `diff-${aliasedName}`; 35 | 36 | // Store into with aliased keys 37 | // ts -> diff-typescript 38 | // js -> diff-javascript 39 | Prism.languages[ fullLanguageName ] = Prism.languages.diff; 40 | 41 | return Prism.languages[ fullLanguageName ]; 42 | }; 43 | -------------------------------------------------------------------------------- /test/GetAttributesTest.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const ga = require("../src/getAttributes"); 3 | 4 | test("Falsy", t => { 5 | t.is(ga(false), ""); 6 | t.is(ga(null), ""); 7 | t.is(ga(undefined), ""); 8 | t.is(ga(""), ""); 9 | t.throws(() => { 10 | ga(" test='1'"); 11 | }); 12 | }); 13 | 14 | test("Object syntax", t => { 15 | t.is(ga({}), ""); 16 | t.is(ga({ hi: 1 }), ' hi="1"'); 17 | t.is(ga({ hi: 1, bye: 2 }), ' hi="1" bye="2"'); 18 | t.is(ga({ class: "my-class", bye: 2 }), ' class="my-class" bye="2"'); 19 | t.is(ga({ hi: function(ctx) { return '1'; }, bye: 2 }), ' hi="1" bye="2"'); 20 | }); 21 | 22 | test("Function callback", t => { 23 | t.is(ga({ "data-lang": ({language}) => language }, { 24 | language: "html" 25 | }), ' class="language-html" data-lang="html"'); 26 | }); 27 | 28 | test("Function callback with class attribute (override)", t => { 29 | t.is(ga({ 30 | class: ({language}) => "my-custom-"+language 31 | }, { 32 | language: "html" 33 | }), ' class="my-custom-html"'); 34 | }); 35 | 36 | test("Function callback must return string or integer", t => { 37 | t.throws(() => { 38 | ga({ "data-lang": ({language}) => undefined }, { 39 | language: "html" 40 | }) 41 | }); 42 | 43 | t.throws(() => { 44 | ga({ "data-lang": ({language}) => {} }, { 45 | language: "html" 46 | }) 47 | }); 48 | 49 | t.throws(() => { 50 | ga({ "data-lang": ({language}) => false }, { 51 | language: "html" 52 | }) 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/HighlightPairedShortcode.js: -------------------------------------------------------------------------------- 1 | const Prism = require("prismjs"); 2 | const PrismLoader = require("./PrismLoader"); 3 | const HighlightLinesGroup = require("./HighlightLinesGroup"); 4 | const getAttributes = require("./getAttributes"); 5 | 6 | module.exports = function (content, language, highlightNumbers, options = {}) { 7 | // default to on 8 | if(options.trim === undefined || options.trim === true) { 9 | content = content.trim(); 10 | } 11 | 12 | let highlightedContent; 13 | if( language === "text" ) { 14 | highlightedContent = content; 15 | } else { 16 | let loader = PrismLoader(language, options); 17 | if(!loader) { 18 | highlightedContent = content; 19 | } else { 20 | highlightedContent = Prism.highlight(content, loader, language); 21 | } 22 | } 23 | 24 | let group = new HighlightLinesGroup(highlightNumbers); 25 | let lines = highlightedContent.split(/\r?\n/); 26 | lines = lines.map(function(line, j) { 27 | if(options.alwaysWrapLineHighlights || highlightNumbers) { 28 | let lineContent = group.getLineMarkup(j, line); 29 | return lineContent; 30 | } 31 | return line; 32 | }); 33 | 34 | const context = { content: content, language: language, options: options }; 35 | const preAttributes = getAttributes(options.preAttributes, context); 36 | const codeAttributes = getAttributes(options.codeAttributes, context); 37 | 38 | return `` + lines.join(options.lineSeparator || "
") + ""; 39 | }; 40 | -------------------------------------------------------------------------------- /src/LiquidHighlightTag.js: -------------------------------------------------------------------------------- 1 | const HighlightPairedShortcode = require("./HighlightPairedShortcode"); 2 | 3 | class LiquidHighlightTag { 4 | constructor(liquidEngine) { 5 | this.liquidEngine = liquidEngine; 6 | } 7 | 8 | getObject(options = {}) { 9 | let ret = function(highlighter) { 10 | return { 11 | parse: function(tagToken, remainTokens) { 12 | let split = tagToken.args.split(" "); 13 | 14 | this.language = split.shift(); 15 | this.highlightLines = split.join(" "); 16 | 17 | this.tokens = []; 18 | 19 | var stream = highlighter.liquidEngine.parser.parseStream(remainTokens); 20 | 21 | stream 22 | .on("token", token => { 23 | if (token.name === "endhighlight") { 24 | stream.stop(); 25 | } else { 26 | this.tokens.push(token); 27 | } 28 | }) 29 | .on("end", x => { 30 | throw new Error(`tag ${tagToken.getText()} not closed`); 31 | }); 32 | 33 | stream.start(); 34 | }, 35 | render: function(scope, hash) { 36 | let tokens = this.tokens.map(token => { 37 | return token.raw || token.getText(); 38 | }); 39 | let tokenStr = tokens.join("").trim(); 40 | return Promise.resolve(HighlightPairedShortcode(tokenStr, this.language, this.highlightLines, options)); 41 | } 42 | }; 43 | }; 44 | 45 | return ret(this); 46 | } 47 | } 48 | 49 | module.exports = LiquidHighlightTag; 50 | -------------------------------------------------------------------------------- /demo/test-markdown.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ```ts 12 | function myFunction() { 13 | return true; 14 | } 15 | ``` 16 | 17 | ```typescript 18 | function myFunction() { 19 | return true; 20 | } 21 | ``` 22 | 23 | ```js 24 | function myFunction() { 25 | return true; 26 | } 27 | ``` 28 | 29 | ```js 30 | let multilineString = ` 31 | this is the first line 32 | this is the middle line 33 | this is the last line 34 | `; 35 | ``` 36 | 37 | ## Dash line 38 | 39 | ```js/- 40 | let multilineString = ` 41 | this is the first line 42 | this is the middle line 43 | this is the last line 44 | `; 45 | ``` 46 | 47 | ```js/1,3 48 | let multilineString = ` 49 | this is the first line 50 | this is the middle line 51 | this is the last line 52 | `; 53 | ``` 54 | 55 | ## Scrollbar 56 | 57 | ```js 58 | 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'; 59 | ``` 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@11ty/eleventy-plugin-syntaxhighlight", 3 | "version": "5.0.2", 4 | "description": "Prism.js based syntax highlighting for Markdown, Liquid, Nunjucks, WebC, and 11ty.js templates.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "main": ".eleventy.js", 9 | "scripts": { 10 | "test": "npx ava", 11 | "demo": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js", 12 | "start": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js --serve" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/11ty/eleventy-plugin-syntaxhighlight.git" 17 | }, 18 | "funding": { 19 | "type": "opencollective", 20 | "url": "https://opencollective.com/11ty" 21 | }, 22 | "keywords": [ 23 | "eleventy", 24 | "eleventy-plugin" 25 | ], 26 | "author": { 27 | "name": "Zach Leatherman", 28 | "email": "zachleatherman@gmail.com", 29 | "url": "https://zachleat.com/" 30 | }, 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/11ty/eleventy-plugin-syntaxhighlight/issues" 34 | }, 35 | "homepage": "https://www.11ty.dev/docs/plugins/syntaxhighlight/", 36 | "11ty": { 37 | "compatibility": ">=0.5.4" 38 | }, 39 | "devDependencies": { 40 | "@11ty/eleventy": "^3.1.2", 41 | "ava": "^6.4.1", 42 | "markdown-it": "^14.1.0" 43 | }, 44 | "dependencies": { 45 | "prismjs": "^1.30.0" 46 | }, 47 | "ava": { 48 | "environmentVariables": {}, 49 | "failFast": false, 50 | "files": [ 51 | "./test/*.js", 52 | "./test/*.mjs" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /demo/test-nunjucks.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% highlight "js" %} 13 | function myFunction() { 14 | return true; 15 | } 16 | {% endhighlight %} 17 | 18 | {% highlight "js" %} 19 | let multilineString = ` 20 | this is the first line 21 | this is the middle line 22 | this is the last line 23 | `; 24 | {% endhighlight %} 25 | 26 | {% highlight "js 1,3" %} 27 | let multilineString = ` 28 | this is the first line 29 | this is the middle line 30 | this is the last line 31 | `; 32 | {% endhighlight %} 33 | 34 | {% highlight "js 1,3" %} 35 | alert("test"); 36 | 37 | let multilineString = buildSchema(` 38 | this is the first line 39 | this is the middle line 40 | this is the last line 41 | `); 42 | 43 | alert("test"); 44 | {% endhighlight %} 45 | 46 | {% highlight "js" %} 47 | module.exports = function({collections}) { 48 | return ``; 51 | }; 52 | {% endhighlight %} 53 | 54 | {% highlight "js 1,3" %} 55 | module.exports = function({collections}) { 56 | return ``; 59 | }; 60 | {% endhighlight %} 61 | 62 | {% highlight "typescript" %} 63 | function myFunction() { 64 | return true; 65 | } 66 | {% endhighlight %} 67 | 68 | {% highlight "ts" %} 69 | function myFunction() { 70 | return true; 71 | } 72 | {% endhighlight %} 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/markdownSyntaxHighlightOptions.js: -------------------------------------------------------------------------------- 1 | const Prism = require("prismjs"); 2 | const PrismLoader = require("./PrismLoader"); 3 | const HighlightLinesGroup = require("./HighlightLinesGroup"); 4 | const getAttributes = require("./getAttributes"); 5 | 6 | module.exports = function (options = {}) { 7 | return function(str, language) { 8 | if(!language) { 9 | // empty string means defer to the upstream escaping code built into markdown lib. 10 | return ""; 11 | } 12 | 13 | 14 | let split = language.split("/"); 15 | if( split.length ) { 16 | language = split.shift(); 17 | } 18 | 19 | let html; 20 | if(language === "text") { 21 | html = str; 22 | } else { 23 | let loader = PrismLoader(language, options) 24 | if(!loader) { 25 | html = str; 26 | } else { 27 | html = Prism.highlight(str, loader, language); 28 | } 29 | } 30 | 31 | let hasHighlightNumbers = split.length > 0; 32 | let highlights = new HighlightLinesGroup(split.join("/"), "/"); 33 | let lines = html.split("\n"); 34 | 35 | // Trim last line if it is empty 36 | if (lines[lines.length - 1] === "") { 37 | lines = lines.slice(0, -1); 38 | } 39 | 40 | lines = lines.map(function(line, j) { 41 | if(options.alwaysWrapLineHighlights || hasHighlightNumbers) { 42 | let lineContent = highlights.getLineMarkup(j, line); 43 | return lineContent; 44 | } 45 | return line; 46 | }); 47 | 48 | const context = { content: str, language: language, options: options }; 49 | const preAttributes = getAttributes(options.preAttributes, context); 50 | const codeAttributes = getAttributes(options.codeAttributes, context); 51 | 52 | return `${lines.join(options.lineSeparator || "
")}`; 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /test/EleventyTest.mjs: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import Eleventy from '@11ty/eleventy'; 3 | 4 | function normalizeNewLines(str) { 5 | return str.replace(/\r\n/g, "\n"); 6 | } 7 | 8 | test("Diff output escaped #75", async t => { 9 | let elev = new Eleventy("./test/issue-75/", "./test/issue-75/_site/", { 10 | configPath: "./test/issue-75/.eleventy.js" 11 | }); 12 | let json = await elev.toJSON(); 13 | 14 | t.is(json.length, 1); 15 | t.is(normalizeNewLines(json[0].content.trim()), normalizeNewLines(`
<p>Hello</p>
16 |
-<p>Hello</p>
`)); 17 | }); 18 | 19 | test("diff-javascript #80", async t => { 20 | let elev = new Eleventy("./test/issue-80/", "./test/issue-80/_site/", { 21 | configPath: "./test/issue-80/.eleventy.js" 22 | }); 23 | let json = await elev.toJSON(); 24 | 25 | t.is(json.length, 1); 26 | t.is(normalizeNewLines(json[0].content.trim()), normalizeNewLines(`
- foo()
27 | 
`)); 28 | }); 29 | -------------------------------------------------------------------------------- /src/HighlightLinesGroup.js: -------------------------------------------------------------------------------- 1 | const HighlightLines = require("./HighlightLines"); 2 | 3 | class HighlightLinesGroup { 4 | constructor(str, delimiter) { 5 | this.init(str, delimiter); 6 | } 7 | 8 | init(str = "", delimiter = " ") { 9 | this.str = str; 10 | this.delimiter = delimiter; 11 | 12 | let split = str.split(this.delimiter); 13 | this.highlights = new HighlightLines(split.length === 1 ? split[0] : ""); 14 | this.highlightsAdd = new HighlightLines(split.length === 2 ? split[0] : ""); 15 | this.highlightsRemove = new HighlightLines(split.length === 2 ? split[1] : ""); 16 | } 17 | 18 | isHighlighted(lineNumber) { 19 | return this.highlights.isHighlighted(lineNumber); 20 | } 21 | 22 | isHighlightedAdd(lineNumber) { 23 | return this.highlightsAdd.isHighlighted(lineNumber); 24 | } 25 | 26 | isHighlightedRemove(lineNumber) { 27 | return this.highlightsRemove.isHighlighted(lineNumber); 28 | } 29 | 30 | hasTagMismatch(line) { 31 | let startCount = line.split(" or on the line. 43 | // for example, we can’t wrap with 44 | if(this.hasTagMismatch(line)) { 45 | return line; 46 | } 47 | 48 | return before + line + after; 49 | } 50 | 51 | getLineMarkup(lineNumber, line, extraClasses = []) { 52 | let extraClassesStr = (extraClasses.length ? " " + extraClasses.join(" ") : ""); 53 | 54 | if (this.isHighlighted(lineNumber)) { 55 | return this.splitLineMarkup(line, ``, ``); 56 | } 57 | if (this.isHighlightedAdd(lineNumber)) { 58 | return this.splitLineMarkup(line, ``, ``); 59 | } 60 | if (this.isHighlightedRemove(lineNumber)) { 61 | return this.splitLineMarkup(line, ``, ``); 62 | } 63 | 64 | return this.splitLineMarkup( line, ``, ``); 65 | } 66 | } 67 | 68 | module.exports = HighlightLinesGroup; 69 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | const Prism = require("prismjs"); 3 | const PrismLoader = require("./src/PrismLoader"); 4 | const hasTemplateFormat = require("./src/hasTemplateFormat"); 5 | const HighlightPairedShortcode = require("./src/HighlightPairedShortcode"); 6 | const LiquidHighlightTag = require("./src/LiquidHighlightTag"); 7 | const markdownPrismJs = require("./src/markdownSyntaxHighlightOptions"); 8 | 9 | module.exports = function(eleventyConfig, options){ 10 | try { 11 | eleventyConfig.versionCheck(pkg["11ty"].compatibility); 12 | } catch(e) { 13 | console.log( `WARN: Eleventy Plugin (${pkg.name}) Compatibility: ${e.message}` ); 14 | } 15 | options = Object.assign({ 16 | init: function({Prism}){}, 17 | lineSeparator: "\n", 18 | errorOnInvalidLanguage: false, 19 | alwaysWrapLineHighlights: false, 20 | preAttributes: {}, 21 | codeAttributes: {}, 22 | languages: [], 23 | }, options); 24 | 25 | for(const language of options.languages){ 26 | PrismLoader(language) 27 | } 28 | 29 | if( hasTemplateFormat(options.templateFormats, "liquid") ) { 30 | eleventyConfig.addLiquidTag("highlight", (liquidEngine) => { 31 | // {% highlight js 0 2 %} 32 | let highlight = new LiquidHighlightTag(liquidEngine); 33 | return highlight.getObject(options); 34 | }); 35 | } 36 | 37 | if( hasTemplateFormat(options.templateFormats, "njk") ) { 38 | eleventyConfig.addPairedNunjucksShortcode("highlight", (content, args) => { 39 | // {% highlight "js 0 2-3" %} 40 | let [language, ...highlightNumbers] = args.split(" "); 41 | return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); 42 | }); 43 | } 44 | 45 | if( hasTemplateFormat(options.templateFormats, "md") ) { 46 | // ```js/0,2-3 47 | eleventyConfig.addMarkdownHighlighter(markdownPrismJs(options)); 48 | } 49 | 50 | // we need to add this as many template languages (Vue, WebC) rely on JavaScript functions (not just 11ty.js) 51 | eleventyConfig.addJavaScriptFunction("highlight", (language, content, highlight1, highlight2) => { 52 | let highlightLines = [highlight1, highlight2].filter(entry => entry).join(" "); 53 | let result = HighlightPairedShortcode(content, language, highlightLines, options); 54 | return result; 55 | }); 56 | 57 | options.init({Prism}) 58 | }; 59 | 60 | module.exports.pairedShortcode = HighlightPairedShortcode; 61 | -------------------------------------------------------------------------------- /test/LiquidHighlightTagTest.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const { Liquid } = require('liquidjs'); 3 | const LiquidHighlightTag = require("../src/LiquidHighlightTag"); 4 | 5 | async function renderLiquid(str, data = {}, engine = null) { 6 | if(!engine) { 7 | engine = new Liquid(); 8 | } 9 | 10 | let result = await engine.parseAndRender(str, data); 11 | return result; 12 | } 13 | 14 | test("Test Render", async t => { 15 | t.is("Hi Zach", await renderLiquid("Hi {{name}}", {name: "Zach"})); 16 | }); 17 | 18 | test("Test Highlight Tag Render", async t => { 19 | let engine = new Liquid(); 20 | let tag = new LiquidHighlightTag(engine); 21 | engine.registerTag("highlight", tag.getObject()); 22 | 23 | let rendered = await renderLiquid("{% highlight js %}var test;{% endhighlight %}", {}, engine); 24 | t.is(`
var test;
`, rendered); 25 | }); 26 | 27 | test("Njk Alias", async t => { 28 | let engine = new Liquid(); 29 | let tag = new LiquidHighlightTag(engine); 30 | engine.registerTag("highlight", tag.getObject()); 31 | 32 | let rendered = await renderLiquid("{% highlight njk %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); 33 | t.is(`
{% raw %}hello{% endraw %}
`, rendered); 34 | }); 35 | 36 | test("Nunjucks alias", async t => { 37 | let engine = new Liquid(); 38 | let tag = new LiquidHighlightTag(engine); 39 | engine.registerTag("highlight", tag.getObject()); 40 | 41 | let rendered = await renderLiquid("{% highlight nunjucks %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); 42 | t.is(`
{% raw %}hello{% endraw %}
`, rendered); 43 | }); 44 | -------------------------------------------------------------------------------- /src/getAttributes.js: -------------------------------------------------------------------------------- 1 | function attributeEntryToString(attribute, context) { 2 | let [key, value] = attribute; 3 | 4 | if (typeof value === "function") { // Callback must return a string or a number 5 | value = value(context); // Run the provided callback and store the result 6 | } 7 | 8 | if (typeof value !== "string" && typeof value !== "number") { 9 | throw new Error( 10 | `Attribute "${key}" must have, or evaluate to, a value of type string or number, not "${typeof value}".` 11 | ); 12 | } 13 | 14 | return `${key}="${value}"`; 15 | } 16 | 17 | /** 18 | * ## Usage 19 | * 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. 20 | * If it is falsey, an empty string will be returned. 21 | * 22 | * ```js 23 | getAttributes({ 24 | tabindex: 0, 25 | 'data-language': function (context) { return context.language; }, 26 | 'data-otherStuff': 'value' 27 | }) // => ' tabindex="0" data-language="JavaScript" data-otherStuff="value"' 28 | ``` 29 | * 30 | * @param {{[s: string]: string | number}} attributes An object with key-value pairs that represent attributes. 31 | * @param {object} context An object with the current context. 32 | * @param {string} context.content The code to parse and highlight. 33 | * @param {string} context.language The language for the current instance. 34 | * @param {object} context.options The options passed to the syntax highlighter. 35 | * @returns {string} A string containing the above HTML attributes preceded by a single space. 36 | */ 37 | function getAttributes(attributes, context = {}) { 38 | let langClass = context.language ? `language-${context.language}` : ""; 39 | 40 | if (!attributes) { 41 | return langClass ? ` class="${langClass}"` : ""; 42 | } else if (typeof attributes === "object") { 43 | if(!("class" in attributes) && langClass) { 44 | // class attribute should be first in order 45 | let tempAttrs = { class: langClass }; 46 | for(let key in attributes) { 47 | tempAttrs[key] = attributes[key]; 48 | } 49 | attributes = tempAttrs; 50 | } 51 | 52 | const formattedAttributes = Object.entries(attributes).map( 53 | entry => attributeEntryToString(entry, context) 54 | ); 55 | 56 | return formattedAttributes.length ? ` ${formattedAttributes.join(" ")}` : ""; 57 | } else if (typeof attributes === "string") { 58 | throw new Error("Syntax highlighter plugin custom attributes on
 and  must be an object. Received: " + JSON.stringify(attributes));
59 |   }
60 | }
61 | 
62 | module.exports = getAttributes;
63 | 


--------------------------------------------------------------------------------
/demo/prism-theme.css:
--------------------------------------------------------------------------------
  1 | pre {
  2 |   display: block;
  3 |   padding: .75rem 1rem;
  4 |   line-height: 1.5;
  5 | 
  6 |   overflow-x: auto;
  7 |   background-color: #eee;
  8 |   font-size: 0.875em; /* 14px /16 */
  9 |   -moz-tab-size: 2;
 10 |   -o-tab-size: 2;
 11 |   tab-size: 2;
 12 | 
 13 |   text-align: left;
 14 |   white-space: pre;
 15 |   word-spacing: normal;
 16 |   word-break: normal;
 17 |   word-wrap: normal;
 18 | 
 19 |   background-color: #272822;
 20 |   color: #fff;
 21 | }
 22 | 
 23 | 
 24 | /**
 25 |  * a11y-dark theme for JavaScript, CSS, and HTML
 26 |  * Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css
 27 |  * @author ericwbailey
 28 |  */
 29 | 
 30 | /* Inline code */
 31 | :not(pre) > code[class*="language-"] {
 32 |   padding: 0.1em;
 33 |   border-radius: 0.3em;
 34 |   white-space: normal;
 35 | }
 36 | 
 37 | .token.comment,
 38 | .token.prolog,
 39 | .token.doctype,
 40 | .token.cdata {
 41 |   color: #d4d0ab;
 42 | }
 43 | 
 44 | .token.punctuation {
 45 |   color: #fefefe;
 46 | }
 47 | 
 48 | .token.property,
 49 | .token.tag,
 50 | .token.constant,
 51 | .token.symbol,
 52 | .token.deleted {
 53 |   color: #ffa07a;
 54 | }
 55 | 
 56 | .token.boolean,
 57 | .token.number {
 58 |   color: #00e0e0;
 59 | }
 60 | 
 61 | .token.selector,
 62 | .token.attr-name,
 63 | .token.string,
 64 | .token.char,
 65 | .token.builtin,
 66 | .token.inserted {
 67 |   color: #abe338;
 68 | }
 69 | 
 70 | .token.operator,
 71 | .token.entity,
 72 | .token.url,
 73 | .language-css .token.string,
 74 | .style .token.string,
 75 | .token.variable {
 76 |   color: #00e0e0;
 77 | }
 78 | 
 79 | .token.atrule,
 80 | .token.attr-value,
 81 | .token.function {
 82 |   color: #ffd700;
 83 | }
 84 | 
 85 | .token.keyword {
 86 |   color: #00e0e0;
 87 | }
 88 | 
 89 | .token.regex,
 90 | .token.important {
 91 |   color: #ffd700;
 92 | }
 93 | 
 94 | .token.important,
 95 | .token.bold {
 96 |   font-weight: bold;
 97 | }
 98 | .token.italic {
 99 |   font-style: italic;
100 | }
101 | 
102 | .token.entity {
103 |   cursor: help;
104 | }
105 | 
106 | @media screen and (-ms-high-contrast: active) {
107 |   code[class*="language-"],
108 |   pre[class*="language-"] {
109 |     color: windowText;
110 |     background: window;
111 |   }
112 | 
113 |   :not(pre) > code[class*="language-"],
114 |   pre[class*="language-"] {
115 |     background: window;
116 |   }
117 | 
118 |   .token.important {
119 |     background: highlight;
120 |     color: window;
121 |     font-weight: normal;
122 |   }
123 | 
124 |   .token.atrule,
125 |   .token.attr-value,
126 |   .token.function,
127 |   .token.keyword,
128 |   .token.operator,
129 |   .token.selector {
130 |     font-weight: bold;
131 |   }
132 | 
133 |   .token.attr-value,
134 |   .token.comment,
135 |   .token.doctype,
136 |   .token.function,
137 |   .token.keyword,
138 |   .token.operator,
139 |   .token.property,
140 |   .token.string {
141 |     color: highlight;
142 |   }
143 | 
144 |   .token.attr-value,
145 |   .token.url {
146 |     font-weight: normal;
147 |   }
148 | }
149 | 


--------------------------------------------------------------------------------
/test/HighlightLinesGroupTest.js:
--------------------------------------------------------------------------------
 1 | const test = require("ava");
 2 | const HighlightLinesGroup = require("../src/HighlightLinesGroup");
 3 | 
 4 | test("Empty", t => {
 5 |   let hilite = new HighlightLinesGroup("");
 6 |   t.is(hilite.isHighlighted(0), false);
 7 |   t.is(hilite.isHighlighted(1), false);
 8 | });
 9 | 
10 | test("Highlight irrelevant (-)", t => {
11 |   let hilite = new HighlightLinesGroup("-");
12 |   t.is(hilite.isHighlighted(0), false);
13 |   t.is(hilite.isHighlighted(1), false);
14 | });
15 | 
16 | test("Highlight simple (0)", t => {
17 |   let hilite = new HighlightLinesGroup("0");
18 |   t.is(hilite.isHighlighted(0), true);
19 |   t.is(hilite.isHighlighted(1), false);
20 | });
21 | 
22 | 
23 | test("Highlight simple (1)", t => {
24 |   let hilite = new HighlightLinesGroup("1");
25 |   t.is(hilite.isHighlighted(0), false);
26 |   t.is(hilite.isHighlighted(1), true);
27 | });
28 | 
29 | test("Highlight complex", t => {
30 |   let hilite = new HighlightLinesGroup("1-2,4");
31 |   t.is(hilite.isHighlighted(0), false);
32 |   t.is(hilite.isHighlighted(1), true);
33 |   t.is(hilite.isHighlighted(2), true);
34 |   t.is(hilite.isHighlighted(3), false);
35 |   t.is(hilite.isHighlighted(4), true);
36 |   t.is(hilite.isHighlighted(5), false);
37 | });
38 | 
39 | test("Add/Remove", t => {
40 |   let hilite = new HighlightLinesGroup("1-2,4 3");
41 |   t.is(hilite.isHighlighted(0), false);
42 |   t.is(hilite.isHighlighted(1), false);
43 |   t.is(hilite.isHighlighted(2), false);
44 |   t.is(hilite.isHighlighted(3), false);
45 |   t.is(hilite.isHighlighted(4), false);
46 |   t.is(hilite.isHighlighted(5), false);
47 | 
48 |   t.is(hilite.isHighlightedAdd(0), false);
49 |   t.is(hilite.isHighlightedAdd(1), true);
50 |   t.is(hilite.isHighlightedAdd(2), true);
51 |   t.is(hilite.isHighlightedAdd(3), false);
52 |   t.is(hilite.isHighlightedAdd(4), true);
53 |   t.is(hilite.isHighlightedAdd(5), false);
54 | 
55 |   t.is(hilite.isHighlightedRemove(0), false);
56 |   t.is(hilite.isHighlightedRemove(1), false);
57 |   t.is(hilite.isHighlightedRemove(2), false);
58 |   t.is(hilite.isHighlightedRemove(3), true);
59 |   t.is(hilite.isHighlightedRemove(4), false);
60 |   t.is(hilite.isHighlightedRemove(5), false);
61 | });
62 | 
63 | test("Add/Remove New Delimiter", t => {
64 |   let hilite = new HighlightLinesGroup("1-2,4/3", "/");
65 |   t.is(hilite.isHighlighted(0), false);
66 |   t.is(hilite.isHighlighted(1), false);
67 |   t.is(hilite.isHighlighted(2), false);
68 |   t.is(hilite.isHighlighted(3), false);
69 |   t.is(hilite.isHighlighted(4), false);
70 |   t.is(hilite.isHighlighted(5), false);
71 | 
72 |   t.is(hilite.isHighlightedAdd(0), false);
73 |   t.is(hilite.isHighlightedAdd(1), true);
74 |   t.is(hilite.isHighlightedAdd(2), true);
75 |   t.is(hilite.isHighlightedAdd(3), false);
76 |   t.is(hilite.isHighlightedAdd(4), true);
77 |   t.is(hilite.isHighlightedAdd(5), false);
78 | 
79 |   t.is(hilite.isHighlightedRemove(0), false);
80 |   t.is(hilite.isHighlightedRemove(1), false);
81 |   t.is(hilite.isHighlightedRemove(2), false);
82 |   t.is(hilite.isHighlightedRemove(3), true);
83 |   t.is(hilite.isHighlightedRemove(4), false);
84 |   t.is(hilite.isHighlightedRemove(5), false);
85 | });
86 | 
87 | test("Split Line Markup", t => {
88 |   let hilite = new HighlightLinesGroup("", " ");
89 |   t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "BEFORETestAFTER");
90 |   t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "BEFORETestAFTER");
91 |   t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "Test");
92 |   t.is(hilite.splitLineMarkup("Test", "BEFORE", "AFTER"), "Test");
93 | });
94 | 


--------------------------------------------------------------------------------
/test/MarkdownHighlightTest.js:
--------------------------------------------------------------------------------
  1 | const test = require("ava");
  2 | const md = require("markdown-it");
  3 | const markdownPrismJsOptions = require("../src/markdownSyntaxHighlightOptions");
  4 | 
  5 | test("Test Markdown Highlighter", t => {
  6 |   let mdLib = md();
  7 |   mdLib.set({
  8 |     highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true })
  9 |   });
 10 |   t.is(mdLib.render(`\`\`\`js
 11 | alert();
 12 | \`\`\``).trim(), `
alert();
`); 13 | }); 14 | 15 | test("Test Markdown Highlighter No Line Highlights", t => { 16 | let mdLib = md(); 17 | mdLib.set({ 18 | highlight: markdownPrismJsOptions() 19 | }); 20 | t.is(mdLib.render(`\`\`\`js 21 | alert(); 22 | \`\`\``).trim(), `
alert();
`); 23 | }); 24 | 25 | test("Markdown with `preAttributes`", t => { 26 | let mdLib = md(); 27 | mdLib.set({ 28 | highlight: markdownPrismJsOptions({ 29 | alwaysWrapLineHighlights: true, 30 | preAttributes: { 31 | // will override class="language-js" 32 | class: ({language}) => "not-a-lang-" + language 33 | } 34 | }) 35 | }); 36 | t.is(mdLib.render(`\`\`\`js 37 | alert(); 38 | \`\`\``).trim(), `
alert();
`); 39 | }); 40 | 41 | test("Test Njk Alias", t => { 42 | let mdLib = md(); 43 | mdLib.set({ 44 | highlight: markdownPrismJsOptions() 45 | }); 46 | t.is(mdLib.render(`\`\`\`njk 47 | {% raw %}hello{% endraw %} 48 | \`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); 49 | }); 50 | 51 | test("Test Nunjucks Alias", t => { 52 | let mdLib = md(); 53 | mdLib.set({ 54 | highlight: markdownPrismJsOptions() 55 | }); 56 | t.is(mdLib.render(`\`\`\`nunjucks 57 | {% raw %}hello{% endraw %} 58 | \`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); 59 | }); 60 | 61 | test("Markdown Invalid language", t => { 62 | let mdLib = md(); 63 | mdLib.set({ 64 | highlight: markdownPrismJsOptions({ 65 | errorOnInvalidLanguage: true 66 | }) 67 | }); 68 | 69 | t.throws(() => { 70 | mdLib.render(`\`\`\`asldkjflksdaj 71 | hello 72 | \`\`\``); 73 | }); 74 | }); 75 | 76 | test("Test loader invalid language with ignore", t => { 77 | let src = `\`\`\`asldkjflksdaj 78 | hello 79 | \`\`\``; 80 | 81 | let mdLib = md(); 82 | mdLib.set({ 83 | highlight: markdownPrismJsOptions() 84 | }); 85 | t.is(mdLib.render(src).trim(), `
hello
`); 86 | }); 87 | 88 | // test("Test Markdown Highlighter Block Comment", t => { 89 | // let mdLib = md(); 90 | // mdLib.set({ 91 | // highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) 92 | // }); 93 | // t.is(mdLib.render(`\`\`\`js 94 | // /* 95 | // * this is a string 96 | // */ 97 | // \`\`\``).trim(), `
/*
* this is a string
*/
`); 98 | // }); 99 | 100 | // TODO this still ain’t working right with the line highlighter. 101 | // test("Test Markdown Highlighter GraphQL Example", t => { 102 | // let mdLib = md(); 103 | // mdLib.set({ 104 | // highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) 105 | // }); 106 | // t.is(mdLib.render(`\`\`\`js 107 | // var schema = buildSchema(\`type Query { 108 | // hello: String 109 | // }\`); 110 | // \`\`\``).trim(), ``); 111 | // }); 112 | -------------------------------------------------------------------------------- /test/HighlightPairedShortcodeTest.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const HighlightPairedShortcode = require("../src/HighlightPairedShortcode"); 3 | 4 | test("Base", async t => { 5 | t.is(await HighlightPairedShortcode(`alert(); 6 | alert();`, "js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 7 | }); 8 | 9 | test("Base with LF EOL, always wrap highlights", async t => { 10 | t.is(await HighlightPairedShortcode('alert();\nalert();', 11 | "js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 12 | }); 13 | 14 | test("Base with LF EOL, no wrap highlights", async t => { 15 | t.is(await HighlightPairedShortcode('alert();\nalert();', 16 | "js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); 17 | }); 18 | 19 | test("Base with CRLF EOL, always wrap highlights", async t => { 20 | t.is(await HighlightPairedShortcode('alert();\r\nalert();', 21 | "js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 22 | }); 23 | 24 | test("Base with CRLF EOL, no wrap highlights", async t => { 25 | t.is(await HighlightPairedShortcode('alert();\r\nalert();', 26 | "js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); 27 | }); 28 | 29 | test("Base with custom attributes", async t => { 30 | t.is(await HighlightPairedShortcode(`alert(); 31 | alert();`, "js", "", { alwaysWrapLineHighlights: true, preAttributes: { tabindex: 0, 'data-testid': 'code' } }), `
alert();
alert();
`); 32 | }); 33 | 34 | test("Base change the line separator", async t => { 35 | t.is(await HighlightPairedShortcode(`alert(); 36 | alert();`, "js", "", { 37 | alwaysWrapLineHighlights: true, 38 | lineSeparator: "\n", 39 | }), `
alert();
 40 | alert();
`); 41 | }); 42 | 43 | test("Base No line highlights", async t => { 44 | t.is(await HighlightPairedShortcode(`alert(); 45 | alert();`, "js", ""), `
alert();
alert();
`); 46 | }); 47 | 48 | test("Highlight Active", async t => { 49 | t.is(await HighlightPairedShortcode(`alert(); 50 | alert();`, "js", "0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 51 | 52 | t.is(await HighlightPairedShortcode(`alert(); 53 | alert();`, "js", "0-1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 54 | }); 55 | 56 | test("Highlight Add/Remove", async t => { 57 | t.is(await HighlightPairedShortcode(`alert(); 58 | alert();`, "js", "0 1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 59 | 60 | t.is(await HighlightPairedShortcode(`alert(); 61 | alert();`, "js", "1 0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); 62 | }); 63 | 64 | test("Test loader typescript", async t => { 65 | let script = `function greeter(person) { 66 | return "Hello, " + person; 67 | } 68 | 69 | let user = "Jane User"; 70 | 71 | document.body.textContent = greeter(user);`; 72 | 73 | t.is(await HighlightPairedShortcode(script, "typescript"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); 74 | }); 75 | 76 | test("Test loader ts", async t => { 77 | let script = `function greeter(person) { 78 | return "Hello, " + person; 79 | } 80 | 81 | let user = "Jane User"; 82 | 83 | document.body.textContent = greeter(user);` 84 | 85 | t.is(await HighlightPairedShortcode(script, "ts"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); 86 | }); 87 | 88 | test("Test loader invalid language, with errorOnInvalidLanguage option", async t => { 89 | await t.throwsAsync(async () => { 90 | await HighlightPairedShortcode("", "asldkjflksdaj", null, { 91 | errorOnInvalidLanguage: true 92 | }); 93 | }, { message: `"asldkjflksdaj" is not a valid Prism.js language for eleventy-plugin-syntaxhighlight` }); 94 | }); 95 | 96 | test("Test loader invalid language (should pass)", async t => { 97 | t.is(await HighlightPairedShortcode("test test test", "asldkjflksdaj"), `
test test test
`) 98 | }); 99 | 100 | test("Test loader invalid language with ignore", async t => { 101 | let src = `hello 102 | hello` 103 | t.is(await HighlightPairedShortcode(src, "asldkjflksdaj"), `
hello
hello
`); 104 | }); 105 | 106 | test("Trim content option (defaults true)", async t => { 107 | t.is(await HighlightPairedShortcode(` alert(); 108 | alert(); `, "js", "", {}), `
alert();
alert();
`); 109 | 110 | t.is(await HighlightPairedShortcode(` alert(); 111 | alert(); `, "js", "", { trim: true }), `
alert();
alert();
`); 112 | 113 | t.is(await HighlightPairedShortcode(` alert(); 114 | alert(); `, "js", "", { trim: false }), `
 alert();
alert();
`); 115 | 116 | }); 117 | --------------------------------------------------------------------------------