在Hexo中支持语雀的高亮块和折叠块功能(2024/9/26更新)

一、原始效果

首先我们需要了解2个功能项的markdown输出内容,在语雀文档中创建1个高亮块和折叠块,随便输入一些内容并导出为md文档。

高亮块和折叠块效果高亮块和折叠块效果

在源码中我们可以看到对于高亮块是使用:::包裹的,不同的颜色是以:::后的英文名称,如tips、info、success、color1等区分的;而折叠块则是使用原生的html标签(details、summary)构成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
:::tips
这是一个高亮块

:::

<details>
<summary>
<span>这是一个折叠块</span>
</summary>
<p>
<span>这是折叠块内容</span>
</p>
</details>

运行Hexo,在界面中看到的效果如下:

高亮块无法解析,折叠块样式有些丑高亮块无法解析,折叠块样式有些丑

二、折叠块样式美化

折叠块使用的details标签是HTML原生支持的,不需要JS脚本就可以完成折叠/展开的效果,我们只需要使用CSS美化下结构样式即可。这个最简单,我们先整这个!

就让ChatGPT生成一套details基础样式,然后我们再针对主题色进行微调就完成了。

不得不说AI在这方面的表现十分优异,初级程序员,危 ❗ ❗ ❗不得不说AI在这方面的表现十分优异,初级程序员,危 ❗ ❗ ❗

分享一下Blog使用details样式,可以根据自己的需要再微调:

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
34
35
36
37
38
39
40
41
42
43
44
/**
* details css
*/
details {
background-color: #ffffff;
border: 1px solid #e6e6e6;
border-radius: 5px;
padding: 1em;
margin: 1rem 0;
transition: all 0.3s ease;
}
details:hover {
border-color: #01AD9F;
}
summary {
font-weight: bold;
cursor: pointer;
outline: none;
list-style: none;
position: relative;
padding-left: 25px;
transition: color 0.3s ease;
min-height: 1rem;
line-height: 1rem;
}
summary:hover {
color: #01AD9F;
}
summary::before {
content: "▶";
position: absolute;
left: 0;
font-size: 14px;
transform: rotate(0deg);
transition: transform 0.3s ease;
}
details[open] summary::before {
transform: rotate(90deg);
}
details > *:not(summary) {
margin-top: 10px;
padding-top: 1rem;
border-top: 1px solid #eee;
}

另外,也许你会发现在details、summary等标签中存在无用的class、id属性字段。我们可以创建一个自定义过滤器来清除它们:

1
2
3
4
5
6
7
8
9
10
hexo.extend.filter.register('before_post_render', function(data) {
const regex = /<(details|summary|p|span)([^>]*)(\s(class|id)="[^"]*")/g;

// 使用循环反复清除所有 class 和 id 属性
while (regex.test(data.content)) {
data.content = data.content.replace(regex, '<$1$2');
}

return data;
});

filter并不会修改md文件内容,仅在hexo页面渲染、构建时进行处理,这就会导致处理时资源重复消耗。最好能够在md文件创建时就对无用属性内容进行过滤,这样只需要处理一次!

三、高亮块样式渲染

由于高亮块内容用了特定的:::进行包裹,所以我们可以借助hexo.extend.filter.register构建自己的过滤器方法。在开始前只需要准备好一个正则匹配表达式即可(当然,这个工作也可以交给ChatGPT!)。

AI写正则表达式也是一把好手AI写正则表达式也是一把好手

现在我们可以构建自己的过滤器了,代码如下:

1
2
3
4
5
6
7
8
9
10
hexo.extend.filter.register('before_post_render', function(data) {
// 正则表达式匹配 :::标签 和内容
const regex = /:::(\w+)\n([\s\S]*?)\n:::/g;

data.content = data.content.replace(regex, function(match, tag, content) {
return `<p>${content}</p>`;
});

return data;
});

四、效果演示

这是Blog第一个高亮块

这是Blog第一个折叠块

Hello World!

五、BUG修复(2024/9/26更新)

在支持这两个功能后,我最新的游记《大连行:海水是什么颜色的?》已经使用上了高亮块。但我发现存在2个问题:

  1. markdown的高亮效果丢失;
  2. 存在多行内容时,首行以后的内容不会出现在高亮块中;

展示效果和源码展示效果和源码

5.1 markdown的高亮效果丢失

原因是因为在拓展是在before_post_render钩子处运行的,而hexo的渲染顺序是这样的:

  • 执行before_post_render过滤器
  • 使用 Markdown 或其他渲染器渲染(根据扩展名而定)
  • 使用Nunjucks渲染
  • 执行after_post_render过滤器

这使得过滤器运行完毕后,Markdown渲染器未能正确的进行内容渲染(hexo默认渲染器似乎不会对html标签内的内容进行处理)。那就意味着我们需要手动进行内容的markdown渲染了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hexo.extend.filter.register('before_post_render', function(data) {
// 正则表达式匹配 :::标签 和内容
const regex = /:::(\w+)\n([\s\S]*?)\n:::/g;

data.content = data.content.replace(regex, function(match, tag, content) {
// 调整前
// return `<p>${content}</p>`;

// 调整后
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' });
return `<p>${renderedContent}</p>`;
});

return data;
});

5.2 存在多行内容时,首行以后的内容不会出现在高亮块中

这个问题主要是下面这行代码导致的:

1
2
// 源代码
return `<p>${renderedContent}</p>`;

在Hexo中每一行文本会被渲染为<p>文本...</p>。这就导致多行内容会出现<p>标签嵌套的情况。而<p>标签嵌套是不符合HTML规范的,浏览器会自动转换第二种形式。

1
2
3
4
5
6
7
8
9
10
11
// 生成的html
<p>
<p><b>路线一:</b>在南2门花30元坐小火车到达山顶(单程30元,往返60元)</p>
<p><b>路线二:</b>在南门花60元坐观光缆车到达山顶(单程60元,往返100元)</p>
</p>

// 实际渲染的html
<p>
<b>路线一:</b>在南2门花30元坐小火车到达山顶(单程30元,往返60元)
</p>
<p><b>路线二:</b>在南门花60元坐观光缆车到达山顶(单程60元,往返100元)</p>

所以改动也很简单,将最外层的<p>标签改为<div>标签即可!

1
2
3
4
5
// old
return `<p>${renderedContent}</p>`;

// new
return `<div class="highlight-box ${tag}">${renderedContent}</div>`;

评论区