Skip to content

Commit

Permalink
dev: switch from 11ty-syntax-hl to markdown-it-prism
Browse files Browse the repository at this point in the history
11ty-syntax-hl doesn't work well with markdown-it-attr. So taking a hint from [this issue](11ty/eleventy-plugin-syntaxhighlight#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: arve0/markdown-it-attrs#152.

I should also mention markdown-it-prism has a bonus of highlighting inline code blocks.
  • Loading branch information
TrebledJ committed Sep 5, 2024
1 parent f53fa4a commit c1b861f
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 4 deletions.
8 changes: 7 additions & 1 deletion content/pages/postlike/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <iostream>
Expand Down Expand Up @@ -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!
```
Expand Down
128 changes: 128 additions & 0 deletions eleventy/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
}

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('<pre') === 0) {
return highlighted + '\n'
}

// If language exists, inject class gently, without modifying original token.
// May be, one day we will add .deepClone() for token and simplify this part, but
// now we prefer to keep things local.
if (info) {
const i = token.attrIndex('class')
const tmpAttrs = token.attrs ? token.attrs.slice() : []

if (i < 0) {
tmpAttrs.push(['class', options.langPrefix + langName])
} else {
tmpAttrs[i] = tmpAttrs[i].slice()
tmpAttrs[i][1] += ' ' + options.langPrefix + langName
}

// Fake token just to render attributes
const tmpToken = {
attrs: tmpAttrs
}

return `<pre${slf.renderAttrs(tmpToken)}><code class="${options.langPrefix}${langName}">${highlighted}</code></pre>\n`
}

return `<pre${slf.renderAttrs(token)}><code>${highlighted}</code></pre>\n`
}
/* eslint-enable */

});

eleventyConfig.addPlugin(pluginTOC, {
Expand Down
6 changes: 3 additions & 3 deletions eleventy/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down

0 comments on commit c1b861f

Please sign in to comment.