Hexo 处理的代码块会被包裹在 figure table tbody tr > td.code pre 中,每一行代码又被 span 包裹,如果需要加入对 Typst 的支持,需要将所有的代码重新提取出来,处理后之后再填入,有两种方法可以选择:

  1. 前端修改:在 html 页面中读取代码块元素,提取代码处理后再填入(1)
  2. 标签插件(推荐:在 hexo 生成的过程中使用标签插件 (2) 处理

前端修改

此方法可能需要刷新才能正常显示
此方法会将所有的 plaintext 识别为 typst,可以通过在第一行加一行说明语言来处理

引入js文件

1
2
3
4
5
<script crossorigin="anonymous" src="https://foolishfox.cn/js/hl.js"></script>
<script
id="script-main"
src="https://cdn.jsdelivr.net/npm/@myriaddreamin/highlighter-typst/dist/cjs/contrib/hljs/typst.bundle.js"
></script>

注册 Typst

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const run = () => {
$typst$parserModule.then(() => {
hljs.registerLanguage(
'typst',
window.hljsTypst({
codeBlockDefaultLanguage: 'typst',
}),
);
hljs.configure({
classPrefix: '' // don't append class prefix
});
}).then(() => {
document.querySelectorAll('figure table tbody tr > td.code pre').forEach(el => {
if (!el.getAttribute('data-highlighted')) {
var pre = document.createElement("pre");
var lang = el.parentNode.parentNode.parentNode.parentNode.parentNode.classList[1];
var code = "";
for (i = 0; i < el.childElementCount; i++){
ch = el.children[i];
if (ch.tagName == "SPAN") code += ch.textContent;
if (ch.tagName == "BR") code += "\n";
}
if (lang == "plaintext") {
lang = "typst";
}
if (lang != "plaintext") {
el.innerHTML = hljs.highlight(code, {language: lang}).value
}
}
});
});
}
run();

标签插件

文件下载:

查看 Hexo 原生处理代码块的逻辑 (4)

registerplugins/tag/index.ts
12
13
14
15
const code = require('./code')(ctx);

tag.register('code', code, true);
tag.register('codeblock', code, true);

code 模块中,先对传入的参数进行了解析,然后再调用 hexo-utilshighlight 进行渲染:

codeplugins/tag/code.ts
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function parseArgs(args: string[]): HighlightOptions {
......
return {
lang,
language_attr,
firstLine,
caption,
line_number,
line_threshold,
mark,
wrap
};
}
module.exports = ctx => function codeTag(args, content) {
......
const options = parseArgs(args);
options.lines_length = content.split('\n').length;
content = ctx.extend.highlight.exec(ctx.config.syntax_highlighter, {
context: ctx,
args: [content, options]
});
return content.replace(/{/g, '&#123;').replace(/}/g, '&#125;');
};

所以我们可以在返回 content 之前对其进行处理,处理的逻辑与前一种方法是类似的,首先导入所需要的模块:

importtypst.js
1
2
3
const hljs = require('highlight.js');
const highlight = require('hexo-util').highlight;
const typstBunlde = require('./typst.bundle');

然后获取 highlight 处理过后的代码块:

highlight processtypst.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const codeTypst = (args, content) => {
const options = parseArgs(args);
options.lines_length = content.split('\n').length;

const line_threshold = options.line_threshold || 0;
const shouldUseLineNumbers = options.line_number;
const surpassesLineThreshold = options.lines_length > line_threshold;
const gutter = shouldUseLineNumbers && surpassesLineThreshold;
const hljsOptions = {
caption: options.caption,
firstLine: options.firstLine,
gutter,
mark: options.mark,
};

var contentHljs = highlight(content, hljsOptions);
contentHljs = contentHljs.replace(/{/g, '&#123;').replace(/}/g, '&#125;');
......
}

然后注册 Typst 语言,并提取代码进行处理:

extract and processtypst.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const codeTypst = (args, content) => {
......
return typstBunlde.parserModule.then(() => {
hljs.registerLanguage(
'typst',
typstBunlde.hljsTypst({
codeBlockDefaultLanguage: 'typst',
}),
)
}).then(() => {
var re = /<td class="code"><pre>(.+)<\/pre><\/td>/i;
var code = hljs.highlight(content, {language: 'typst'}).value;
contentHljs = contentHljs.replace("highlight plaintext", "highlight typst")
return contentHljs.replace(re, `<td class="code"><pre>${code}</pre></td>`);
});
}

最后在 Hexo 中注册这个标签插件,由于注册 Typst 语言并处理的过程是异步的,所以这里也要开启异步选项:

register with asynctypst.js
1
hexo.extend.tag.register('typst', codeTypst, {ends: true, async: true});

示例

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Typst Code block tag
* Syntax:
* {% typst [options] %}
* code snippet
* {% endtypst %}
* @param {String} title Caption text
* @param {String} url Source link
* @param {String} link_text Text of the link
* @param {Object} line_number Show line number, value must be a boolean
* @param {Object} first_line Specify the first line number, value must be a number
* @param {Object} mark Line highlight specific line(s), each value separated by a comma. Specify number range using a dash
* Example: `mark:1,4-7,10` will mark line 1, 4 to 7 and 10.
* @returns {String} Code snippet with code highlighting
*/

预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% typst %}
#import "@preview/tablex:0.0.6": tablex, rowspanx, colspanx, hlinex, vlinex

#let three-line-table(columns, headers, contents, caption, lang: "en",rows: auto) = {figure(
tablex(
columns: columns,
rows: rows,
align: center + horizon,
auto-lines: false,
repeat-header: true,
hlinex(stroke: 1.5pt),
..headers,
hlinex(stroke: 0.75pt),
..contents,
hlinex(stroke: 1.5pt),
),
kind: table,
supplement: [#if (lang == "en") {"Table"} else {"表"}],
caption: caption,
)}
{% endtypst %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "@preview/tablex:0.0.6": tablex, rowspanx, colspanx, hlinex, vlinex

#let three-line-table(columns, headers, contents, caption, lang: "en",rows: auto) = {figure(
  tablex(
    columns: columns,
    rows: rows,
    align: center + horizon,
    auto-lines: false,
    repeat-header: true,
    hlinex(stroke: 1.5pt),
    ..headers,
    hlinex(stroke: 0.75pt),
    ..contents,
    hlinex(stroke: 1.5pt),
  ),
  kind: table,
  supplement: [#if (lang == "en") {"Table"} else {"表"}],
  caption: caption,
)}

参考资料

  1. 在 Hugo 中为 Typst 配置语法高亮(PaperMod 主题)
  2. 标签插件(Tag)
  3. highlighter
  4. 代码高亮