在 Confluence 文章页面上浮动展示文章目录,并支持展开和折叠功能
目前為
// ==UserScript==
// @name Confluence Floating TOC
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 在 Confluence 文章页面上浮动展示文章目录,并支持展开和折叠功能
// @author mkdir700
// @match https://*.atlassian.net/wiki/*
// @grant none
// @license MIT
// ==/UserScript==
// 递归处理已有的 TOC,重新生成新的 TOC
function genertateTOCFromExistingToc(toc) {
if (!toc) {
return;
}
let currUl = document.createElement('ul');
for (let i = 0; i < toc.children.length; i++) {
// li > span > a > span > span
var headerTextElement = toc.children[i].querySelector('span > a > span > span');
if (!headerTextElement) {
continue;
}
var headerText = headerTextElement.textContent;
// 创建目录项
var tocItem = document.createElement('li');
// 创建链接
var tocLink = document.createElement('a');
tocLink.textContent = headerText;
// 使用标题的 id 作为 URL 片段
// 标题中的空格需要替换为 -,并且转为小写
tocLink.href = '#' + headerText.replace(/\s/g, '-');
tocItem.appendChild(tocLink);
// 如果有子目录,递归处理
var childUl = toc.children[i].querySelector('ul');
if (childUl) {
var newUl = genertateTOCFromExistingToc(childUl);
if (newUl) {
tocItem.appendChild(newUl);
}
}
currUl.appendChild(tocItem);
}
return currUl;
}
function getExistingToc() {
return document.querySelector('[data-testid="list-style-toc-level-container"]');
}
function generateTOCFormPage() {
// 创建目录列表
var tocList = document.createElement('ul');
// 获取所有标题
var headers = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
headers.forEach(function (header) {
// 过滤掉 id 为空的标题
if (!header.id) return;
// 创建目录项
var tocItem = document.createElement('li');
tocItem.style.marginLeft = (parseInt(header.tagName[1]) - 1) * 10 + 'px'; // 根据标题级别缩进
// 创建链接
var tocLink = document.createElement('a');
tocLink.textContent = header.textContent;
// 使用标题作为 URL 片段
tocLink.href = '#' + header.textContent.replace(/\s/g, '-');
tocItem.appendChild(tocLink);
// 将目录项添加到目录列表中
tocList.appendChild(tocItem);
});
return tocList;
}
function buildToggleButton(tocList) {
// 添加折叠/展开按钮
var toggleButton = document.createElement('button');
toggleButton.textContent = '折叠';
toggleButton.style.position = 'absolute';
toggleButton.style.top = '5px';
toggleButton.style.right = '5px';
toggleButton.style.backgroundColor = '#007bff';
toggleButton.style.color = '#fff';
toggleButton.style.border = 'none';
toggleButton.style.padding = '5px';
toggleButton.style.cursor = 'pointer';
var isCollapsed = false;
// 折叠和展开功能
toggleButton.addEventListener('click', function () {
if (isCollapsed) {
tocList.style.display = 'block';
toggleButton.textContent = '折叠';
} else {
tocList.style.display = 'none';
toggleButton.textContent = '展开';
}
isCollapsed = !isCollapsed;
});
return toggleButton;
}
function buildToc() {
// 创建浮动目录的容器
var tocContainer = document.createElement('div');
tocContainer.id = 'floating-toc-container';
tocContainer.style.position = 'fixed';
tocContainer.style.top = '200px'; // 设置为 200px
tocContainer.style.width = '200px';
tocContainer.style.overflowY = 'auto';
tocContainer.style.backgroundColor = '#fff';
tocContainer.style.border = '1px solid #ccc';
tocContainer.style.padding = '10px';
tocContainer.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
tocContainer.style.zIndex = '1000';
tocContainer.style.fontSize = '14px';
// 添加隐藏滚动条样式
var style = document.createElement('style');
style.innerHTML = `
#floating-toc-container {
scrollbar-width: none;
-ms-overflow-style: none;
}
#floating-toc-container::-webkit-scrollbar {
display: none;
}
`;
document.head.appendChild(style);
// 添加标题
var tocTitle = document.createElement('h3');
tocTitle.textContent = '目录';
tocTitle.style.marginTop = '0';
tocContainer.appendChild(tocTitle);
return tocContainer;
}
function generateTOC(tocContainer) {
// 清空现有目录
var tocList = tocContainer.querySelector('ul');
if (tocList) {
tocList.remove();
}
// 获取 content-body 容器
var contentBody = document.getElementById('content-body');
if (!contentBody) {
console.error('未找到 id 为 content-body 的元素');
return;
}
// 设置浮动目录的位置
tocContainer.style.left = contentBody.getBoundingClientRect().left + 'px';
// 检查是否存在已有的 TOC
var existingTOC = getExistingToc();
var toc;
if (existingTOC) {
toc = genertateTOCFromExistingToc(existingTOC);
if (!toc) {
console.error('生成目录失败');
}
} else {
toc = generateTOCFormPage();
}
tocContainer.appendChild(toc);
// 添加折叠/展开按钮
const toggleButton = buildToggleButton(toc);
tocContainer.appendChild(toggleButton);
// 动态计算最大高度
updateMaxHeight(tocContainer);
}
function updateMaxHeight(tocContainer) {
const viewportHeight = window.innerHeight;
const topOffset = parseFloat(tocContainer.style.top);
tocContainer.style.maxHeight = (viewportHeight - topOffset - 20) + 'px'; // 20px 为一些额外的间距
}
(function () {
'use strict';
var tocContainer = buildToc();
document.body.appendChild(tocContainer);
generateTOC(tocContainer);
function onUrlChange() {
generateTOC(tocContainer);
}
// 使用 history API 拦截 URL 变化
(function (history) {
var pushState = history.pushState;
var replaceState = history.replaceState;
history.pushState = function () {
var ret = pushState.apply(history, arguments);
onUrlChange();
return ret;
};
history.replaceState = function () {
var ret = replaceState.apply(history, arguments);
onUrlChange();
return ret;
};
window.addEventListener('popstate', onUrlChange);
})(window.history);
// 监听窗口大小变化,调整目录位置
window.addEventListener('resize', function () {
var contentBody = document.getElementById('content-body');
if (contentBody) {
tocContainer.style.left = contentBody.getBoundingClientRect().left + 'px';
}
updateMaxHeight(tocContainer);
});
// 确保目录在滚动时保持在视口内
window.addEventListener('scroll', function () {
updateMaxHeight(tocContainer);
});
})();