Hexo NexT 主题添加代码折叠功能

Hexo Theme Icarus 的代码折叠功能很喜欢,奈何 NexT 主题不支持相应的样式,也不打算支持,推荐的是使用 NOTE 块的实现方式,这很不优雅。

今天我发现在 NexT 主题中使用下面的格式时,代码块也会生成一个标题栏。

file or summary
1
2
3
```js file or summary
/* some code */
```

那实现起来就很简单了,只需要掏出 F12 ,在 file or summary 前面插入一个按钮,然后为按钮添加功能。

不会写 JS 没有关系,只需要咨询一下 ChatGPT 。

Hexo v5.4.2
NexT v8.15.0

配置主题加载自定义内容

在主题配置文件 _config.yaml 中,找到 custom_file_path,取消对应的注释,允许加载自定义内容

themes/next/_config.yaml
1
2
3
custom_file_path:
style: source/_data/styles.styl
bodyEnd: source/_data/body-end.njk

然后在 Hexo 主目录下,创建对文件文件。

添加代码

body-end.njk 文件中,添加 JS 代码

source/_data/body-end.njk
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
<script>
document.addEventListener("DOMContentLoaded", function() {
// 查找所有匹配的 span 标签
const spans = document.querySelectorAll("figcaption > span");

// 遍历所有匹配的 span 标签
spans.forEach(function(span) {
// 在 span 标签前插入 <i> 标签
const iElement = document.createElement("i");
iElement.className = "fas fa-angle-down";
// 插入一点空格
iElement.innerHTML = "&nbsp;&nbsp;&nbsp;";
iElement.style.userSelect = "none";
span.parentNode.insertBefore(iElement, span);

// 查找相邻的 <div class="table-container">
const tableContainer = span.parentElement.nextElementSibling;

// 为 <i> 标签添加点击事件
iElement.addEventListener("click", function() {
// 切换 tableContainer 的 "hidden" 类
tableContainer.classList.toggle("code-hidden");

// 切换 <i> 标签的类名
if (iElement.classList.contains("fa-angle-down")) {
iElement.classList.remove("fa-angle-down");
iElement.classList.add("fa-angle-right");
} else {
iElement.classList.remove("fa-angle-right");
iElement.classList.add("fa-angle-down");
}
});
});
});
</script>

styles.styl 填写对应的样式代码

source/_data/styles.styl
1
2
3
4
/* 代码块隐藏 */
.code-hidden {
display: none;
}

加入后,运行本地预览 hexo g && hexo s,效果非常完美。

现在的问题是,如果代码块不写 file or summary 内容的话,是没有这个 <figcaption> 标题栏的。

手动添加代码块标题栏

一两行的代码也没有必要进行折叠,所以添加一个检测,为大于三行且没有标题栏的代码块手动添加一个,最终 source/_data/body-end.njk 的代码如下

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
45
46
47
48
49
50
51
<script>
document.addEventListener("DOMContentLoaded", function() {
// 查找所有 div.table-container 元素
const tableContainers = document.querySelectorAll(".table-container");

// 遍历所有 div.table-container 元素
tableContainers.forEach(function(tableContainer) {
// 获取 div.table-container 内的 span 元素数量
const spanCount = tableContainer.querySelectorAll("tbody > tr > td.code > pre > span").length;

// 检查 span 元素数量是否 >= 3
if (spanCount >= 3) {
// 检查 div.table-container 前面是否有 figcaption 元素,如果没有则添加一个
const prevElement = tableContainer.previousElementSibling;
let figcaption;
let iElement;
if (!prevElement || prevElement.tagName.toLowerCase() !== "figcaption") {
// 在 div.table-container 前插入一个 figcaption 元素
figcaption = document.createElement("figcaption");

// 将 figcaption 插入到 DOM 中
tableContainer.parentNode.insertBefore(figcaption, tableContainer);
} else {
figcaption = prevElement;
}

// 创建一个 <i> 标签并添加功能
iElement = document.createElement("i");
iElement.className = "fas fa-angle-down";
// 插入一点空格
iElement.innerHTML = "&nbsp;&nbsp;&nbsp;";
figcaption.insertBefore(iElement, figcaption.firstChild);

// 为 <i> 标签添加点击事件
iElement.addEventListener("click", function() {
// 切换 tableContainer 的 "code-hidden" 类
tableContainer.classList.toggle("code-hidden");

// 切换 <i> 标签的类名
if (iElement.classList.contains("fa-angle-down")) {
iElement.classList.remove("fa-angle-down");
iElement.classList.add("fa-angle-right");
} else {
iElement.classList.remove("fa-angle-right");
iElement.classList.add("fa-angle-down");
}
});
}
});
});
</script>

然后预览一下,看一下效果,没有问题的话就可以推送到服务器了。

测试

1
2
3
hexo c
hexo g
heox d