Confluence Floating TOC

在 Confluence 文章页面上浮动展示文章目录,并支持展开和折叠功能

目前為 2024-07-09 提交的版本,檢視 最新版本

// ==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);
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址