MediaWiki:GeneralMdTocGen.js
MediaWiki界面页面
更多操作
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
// 使用豆包AI生成
// 等待页面完全加载(确保嵌入的 Markdown 内容已渲染)
$(document).ready(function() {
// 仅在目标页面执行(根据实际 URL 调整)
if (window.location.href.includes('EaseCation_Wiki:%E6%80%BB%E5%88%99')) {
generateTOCFromMarkdownHeadings();
}
});
function generateTOCFromMarkdownHeadings() {
// 1. 获取嵌入的 Markdown 内容容器(根据插件渲染结果调整选择器)
// 假设 External Content 插件将 Markdown 渲染到 class 为 "external-content" 的容器中
const markdownContainer = document.querySelector('.external-content, #mw-content-text');
if (!markdownContainer) return;
// 2. 提取 Markdown 中通过 # 标识的标题(h1-h6 标签,对应 # 到 ######)
// Markdown 渲染器通常会将 # 转换为 ,## 转换为 ,以此类推
const headings = Array.from(markdownContainer.querySelectorAll('h1, h2, h3, h4, h5, h6'))
.filter(heading => {
// 过滤空标题
return heading.textContent.trim() !== '';
})
.map(heading => {
// 解析层级(h1 → 1,h2 → 2,...,h6 → 6)
const level = parseInt(heading.tagName.toLowerCase().replace('h', ''));
// 生成唯一 ID(用于锚点跳转)
const id = heading.id || `md-heading-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
heading.id = id;
return {
level: level,
text: heading.textContent.trim(),
id: id
};
});
if (headings.length === 0) return; // 无标题时不生成 TOC
// 3. 生成 MediaWiki 原生风格 TOC 结构
const tocContainer = document.createElement('div');
tocContainer.id = 'toc';
tocContainer.className = 'toc mw-toc';
tocContainer.innerHTML = `
目录
`;
const tocList = tocContainer.querySelector('#toc-list');
// 4. 构建层级目录(处理 h1-h6 的嵌套关系)
let currentLevel = 1; // 初始层级从 h1 开始
let currentParent = tocList; // 当前列表容器(初始为根列表)
const levelStack = [tocList]; // 存储各层级的列表容器,用于层级回退
headings.forEach(heading => {
const { level, text, id } = heading;
// 调整层级:加深(如 h2 → h3)或回退(如 h3 → h1)
if (level > currentLevel) {
// 层级加深:在当前最后一个列表项后创建子列表
const lastLi = currentParent.lastElementChild;
if (lastLi) {
const subList = document.createElement('ul');
subList.className = `mw-toc-list mw-toc-level-${level}`;
lastLi.appendChild(subList);
levelStack.push(subList);
currentParent = subList;
}
} else if (level < currentLevel) {
// 层级回退:从栈中弹出多余层级,回到对应父级
const stepsToBack = currentLevel - level;
for (let i = 0; i < stepsToBack; i++) {
levelStack.pop();
}
currentParent = levelStack[levelStack.length - 1];
}
// 生成目录项(带编号和锚点)
const li = document.createElement('li');
li.className = `toclevel-${level}`;
li.innerHTML = `
${getTocNumber(heading, headings)}
${text}
`;
currentParent.appendChild(li);
// 更新当前层级
currentLevel = level;
});
// 5. 添加 TOC 折叠/展开功能(匹配 MediaWiki 原生交互)
const toctitle = tocContainer.querySelector('#toctitle');
toctitle.addEventListener('click', () => {
const isCollapsed = tocList.classList.contains('mw-toc-collapsed');
if (isCollapsed) {
tocList.classList.remove('mw-toc-collapsed');
toctitle.querySelector('h2').textContent = '目录';
} else {
tocList.classList.add('mw-toc-collapsed');
toctitle.querySelector('h2').textContent = '目录 [+]';
}
});
// 6. 将 TOC 插入到页面正文顶部
const contentStart = markdownContainer.firstElementChild;
if (contentStart) {
markdownContainer.insertBefore(tocContainer, contentStart);
} else {
markdownContainer.appendChild(tocContainer);
}
}
// 生成 MediaWiki 风格的目录编号(如 1, 1.1, 1.1.1...)
function getTocNumber(heading, allHeadings) {
const index = allHeadings.indexOf(heading);
if (index === 0) return '1';
const numberParts = [];
// 遍历当前标题之前的所有标题,计算层级编号
for (let i = 0; i <= index; i++) {
const current = allHeadings[i];
if (current === heading) {
// 找到同层级的上一个标题,编号 +1
let prevSibling = null;
for (let j = i - 1; j >= 0; j--) {
if (allHeadings[j].level === current.level) {
prevSibling = allHeadings[j];
break;
}
}
if (prevSibling) {
const prevNum = getTocNumber(prevSibling, allHeadings);
const lastPart = parseInt(prevNum.split('.').pop());
numberParts.push(lastPart + 1);
} else {
// 同层级第一个标题,编号为 1
numberParts.push(1);
}
break;
} else if (current.level < heading.level) {
// 继承父层级编号(如 h3 继承 h2 的编号前缀)
const parentNum = getTocNumber(current, allHeadings);
if (!numberParts.includes(parentNum)) {
numberParts.push(...parentNum.split('.'));
}
}
}
return numberParts.join('.');
}