From c1b861ff5fa30bac5d19c8db99d739c53be46a1a Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 6 Sep 2024 03:34:34 +0800 Subject: [PATCH] dev: switch from 11ty-syntax-hl to markdown-it-prism 11ty-syntax-hl doesn't work well with markdown-it-attr. So taking a hint from [this issue](https://github.com/11ty/eleventy-plugin-syntaxhighlight/issues/72), I'm switching to markdown-it-prism, which also renders PrismJS in node. At the same time, I decided to use a custom fence renderer which will render attributes in the `pre` block instead of the `code` block. Relevant issue: https://github.com/arve0/markdown-it-attrs/issues/152. I should also mention markdown-it-prism has a bonus of highlighting inline code blocks. --- content/pages/postlike/styleguide.md | 8 +- eleventy/markdown.js | 128 +++++++++++++++++++++++++++ eleventy/plugins.js | 6 +- 3 files changed, 138 insertions(+), 4 deletions(-) diff --git a/content/pages/postlike/styleguide.md b/content/pages/postlike/styleguide.md index 8d3ab2e24..9ecac22d2 100644 --- a/content/pages/postlike/styleguide.md +++ b/content/pages/postlike/styleguide.md @@ -282,6 +282,8 @@ Did I mention that equal-height layouts are a thing!? This is made possible with ### Code +Inline highlight, thanks to `markdown-it-prism` + `markdown-it-attr`: `class Demo { };`{language=cpp}, `function foo(a) { console.log(1); }`{language=js}. + ```cpp #include @@ -321,11 +323,15 @@ cmake .. make ``` +When no language is provided, a code block is poorly rendered: + ``` {.command-line data-a=abc} Just plain text. ``` -```txt +_Hint: Check your browser devtools for element attributes!_ + +```txt {data-copyable=true data-filename="a/file/path"} Supercalifragilisticespieladocious! Supercalifragilisticespieladocious! Supercalifragilisticespieladocious! Supercalifragilisticespieladocious! Supercalifragilisticespieladocious! Supercalifragilisticespieladocious! ``` diff --git a/eleventy/markdown.js b/eleventy/markdown.js index 33f0f8ea0..6e585216c 100644 --- a/eleventy/markdown.js +++ b/eleventy/markdown.js @@ -2,6 +2,7 @@ const markdownItAnchor = require('markdown-it-anchor'); const markdownItAttrs = require('markdown-it-attrs'); const markdownItFootnote = require('markdown-it-footnote'); const markdownItSpoiler = require('@traptitech/markdown-it-spoiler'); +const markdownItPrism = require('markdown-it-prism'); const pluginTOC = require('eleventy-plugin-toc'); module.exports = function (eleventyConfig) { @@ -49,6 +50,133 @@ module.exports = function (eleventyConfig) { // } return n; }; + + // Codeblocks and Syntax Highlighting + mdLib.use(markdownItPrism, { + highlightInlineCode: true, + }); + + const HTML_ESCAPE_TEST_RE = /[&<>"]/ + const HTML_ESCAPE_REPLACE_RE = /[&<>"]/g + const HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' + } + + function replaceUnsafeChar (ch) { + return HTML_REPLACEMENTS[ch] + } + + function escapeHtml (str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar) + } + return str + } + + + const UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g + const ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi + const UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi') + + const DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))$/i + + function replaceEntityPattern (match, name) { + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + const code = name[1].toLowerCase() === 'x' + ? parseInt(name.slice(2), 16) + : parseInt(name.slice(1), 10) + + if (isValidEntityCode(code)) { + return fromCodePoint(code) + } + + return match + } + + const decoded = decodeHTML(match) + if (decoded !== match) { + return decoded + } + + return match + } + + /* function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); + } */ + + function unescapeMd (str) { + if (str.indexOf('\\') < 0) { return str } + return str.replace(UNESCAPE_MD_RE, '$1') + } + + function unescapeAll (str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped } + return replaceEntityPattern(match, entity) + }) + } + + + // PrismJS compatibility: attributes on codeblocks should go on `pre`, not `code`. + // Adapted from https://github.com/markdown-it/markdown-it/blob/0fe7ccb4b7f30236fb05f623be6924961d296d3d/lib/renderer.mjs#L29 + /* eslint-disable */ + mdLib.renderer.rules.fence = function (tokens, idx, options, env, slf) { + const token = tokens[idx] + const info = token.info ? unescapeAll(token.info).trim() : '' + let langName = '' + let langAttrs = '' + + if (info) { + const arr = info.split(/(\s+)/g) + langName = arr[0] + langAttrs = arr.slice(2).join('') + } + + let highlighted + if (options.highlight) { + highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content) + } else { + highlighted = escapeHtml(token.content) + } + + if (highlighted.indexOf('${highlighted}\n` + } + + return `${highlighted}\n` + } + /* eslint-enable */ + }); eleventyConfig.addPlugin(pluginTOC, { diff --git a/eleventy/plugins.js b/eleventy/plugins.js index 038e72699..b98d00965 100644 --- a/eleventy/plugins.js +++ b/eleventy/plugins.js @@ -10,9 +10,9 @@ const { minify } = require('terser'); module.exports = function (eleventyConfig) { eleventyConfig.addPlugin(pluginRss); - eleventyConfig.addPlugin(pluginSyntaxHighlight, { - errorOnInvalidLanguage: true, - }); + // eleventyConfig.addPlugin(pluginSyntaxHighlight, { + // errorOnInvalidLanguage: true, + // }); eleventyConfig.addPlugin(pluginNavigation); eleventyConfig.addPlugin(pluginBundle, {