记录一个给 ParticleX 主题修复加密文章解密后目录(TOC)消失的 bug,顺带修了一个隐藏已久的 setter 错误。
问题现象
加密文章输入正确密码解密后,右侧目录导航不显示任何内容,一片空白。
根本原因
有两个 bug 叠在一起。
Bug 1:TOC 初始化时机问题
toc.js 里的 TocComponent 在 onMounted 时扫描 #main-content 容器内的标题节点:
Vue.onMounted(() => {
const element = document.getElementById("main-content");
// 遍历子节点,找 H1~H6 标签...
});
但加密文章的 #main-content 初始是空的,内容在解密后才通过 innerHTML 动态填充。Vue 组件挂载时标题根本不存在,所以目录为空。
Bug 2:Topic 类的 level setter 写错了
Topic 类里有一个 setter:
set level(value) {
this.className = TOC_CLASSNAME_PREFIX + value; // ❌ 错误
this._level = value;
}
className 只有 getter 没有 setter,直接赋值会抛 Cannot set property className of #<Topic> which has only a getter。正确写法应该是 this._className。
这个 bug 原本在普通文章里也会触发(minLevel > 1 时会执行 value.level -= gap),只是之前没被发现。
修复方案
修复 Bug 2:先把 setter 改对
themes/particlex/source/js/lib/toc.js:
set level(value) {
this._className = TOC_CLASSNAME_PREFIX + value; // ✅ 改为 _className
this._level = value;
}
修复 Bug 1:解密后重新扫描 TOC
思路是把扫描逻辑抽成独立函数 scanToc,然后监听一个自定义事件 crypto-decrypted,解密成功后触发它。
**themes/particlex/source/js/lib/toc.js**,把 onMounted 里的扫描逻辑抽出来:
function scanToc() {
const element = document.getElementById(SCAN_FROM_ELEMENT_ID);
if (!element) return;
const toc = [];
let minLevel = 1000;
element.childNodes.forEach((value) => {
let level;
if (!(value.nodeName.length === 2 && value.nodeName[0] === "H" &&
!Number.isNaN((level = Number.parseInt(value.nodeName[1]))))) return;
toc.push(new Topic(value, level));
minLevel = Math.min(minLevel, level);
});
if (minLevel > 1 && toc.length > 0) {
const gap = minLevel - 1;
toc.forEach((value) => { value.level -= gap; });
}
topics.value = toc;
adjustTocLabelPos();
}
Vue.onMounted(() => {
scanToc();
window.addEventListener("scroll", scrollListener);
window.addEventListener("resize", resizeListener);
window.addEventListener("crypto-decrypted", scanToc); // 监听解密事件
});
Vue.onUnmounted(() => {
window.removeEventListener("scroll", scrollListener);
window.removeEventListener("resize", resizeListener);
window.removeEventListener("crypto-decrypted", scanToc); // 清理
});
**themes/particlex/source/js/lib/crypto.js**,解密成功后派发事件:
if (CryptoJS.SHA256(decrypted).toString() === shasum) {
this.cryptoStatus = "success";
content.innerHTML = decrypted;
this.render();
window.dispatchEvent(new CustomEvent("crypto-decrypted")); // 通知 TOC 重新扫描
}
效果
解密成功后,crypto-decrypted 事件触发,TOC 重新扫描此时已经填充好内容的 #main-content,目录正常渲染出来。普通文章不受影响,onMounted 时直接扫描即可。