《日·键圈时刻表》目录生成(兼容新版/旧版)

支持新版 opus 页面和旧版 read 页面,自动生成目录,支持评论区跳转,滚动隐藏头部,多区块高亮

// ==UserScript==
// @name         《日·键圈时刻表》目录生成(兼容新版/旧版)
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  支持新版 opus 页面和旧版 read 页面,自动生成目录,支持评论区跳转,滚动隐藏头部,多区块高亮
// @match        https://www.bilibili.com/opus/*
// @match        https://www.bilibili.com/read/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const isOpusPage = location.pathname.startsWith('/opus/');
    const isReadPage = location.pathname.startsWith('/read/');

    let lastScrollTop = 0;
    const headersToHide = [];

    // 隐藏头部(新版/旧版)
    function hideHeaders() {
        if (isOpusPage) {
            // 新版 opus 页头
            const opusHeader1 = document.querySelector('.bili-header__bar') || document.querySelector('.bili-header__bar.mini-header');
            const opusHeader2 = document.querySelector('.fixed-author-header') || document.querySelector('.opus-header-fixed');
            if (opusHeader1) headersToHide.push(opusHeader1);
            if (opusHeader2) headersToHide.push(opusHeader2);
        } else if (isReadPage) {
            // 旧版 read 页头
            const readHeader1 = document.querySelector('#bili-header-container');
            const readHeader2 = document.querySelector('.fixed-top-header');
            if (readHeader1) headersToHide.push(readHeader1);
            if (readHeader2) headersToHide.push(readHeader2);
        }

        if (headersToHide.length > 0) {
            window.addEventListener('scroll', () => {
                const currentScrollTop = window.scrollY || document.documentElement.scrollTop;
                headersToHide.forEach(header => {
                    header.style.transition = 'top 0.3s';
                    if (currentScrollTop > lastScrollTop) {
                        header.style.top = '-100px';
                    } else {
                        header.style.top = '0';
                    }
                });
                lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
            });
        }
    }

    setTimeout(hideHeaders, 1000);

    // 创建目录容器
    const tocContainer = document.createElement('div');
    Object.assign(tocContainer.style, {
        position: 'fixed',
        left: '10px',
        top: '10px',
        width: '388px',
        maxHeight: '96vh',
        overflowY: 'auto',
        padding: '5px',
        backgroundColor: 'rgba(255, 255, 255, 0.95)',
        borderRadius: '8px',
        boxShadow: '0 4px 15px rgba(0, 0, 0, 0.2)',
        zIndex: '1000'
    });

    const tocTitle = document.createElement('h3');
    tocTitle.innerText = '目录';
    Object.assign(tocTitle.style, {
        textAlign: 'center',
        marginBottom: '15px',
        fontFamily: 'Arial, sans-serif',
        color: '#333',
        fontSize: '18px'
    });
    tocContainer.appendChild(tocTitle);

    const highlightKeywords = ["置顶区", "消息区", "日常区", "常驻区"];

    // 生成目录
    function generateTOC() {
        if (isOpusPage) {
            // 新版 opus 页面标题结构:可能是 h2 / strong / span
            const contentRoot = document.querySelector('.opus-detail') || document.body;
            const titleNodes = contentRoot.querySelectorAll('h1, h2, h3, p strong');

            titleNodes.forEach((node, index) => {
                const text = node.innerText.trim();
                if (!text) return;

                // 过滤掉非主要标题
                const fontSize = parseFloat(window.getComputedStyle(node).fontSize);
                if (fontSize < 18) return;

                const id = 'toc-header-' + index;
                node.id = id;

                const tocItem = document.createElement('a');
                tocItem.href = '#' + id;
                tocItem.innerText = text;
                Object.assign(tocItem.style, {
                    display: 'block',
                    color: '#007bff',
                    textDecoration: 'none',
                    fontSize: '13px',
                    marginBottom: '5px'
                });

                if (highlightKeywords.some(k => text.includes(k))) {
                    tocItem.style.fontWeight = 'bold';
                    tocItem.style.color = '#ff4500';
                    tocItem.style.fontSize = '16px';
                }

                tocItem.onmouseover = () => tocItem.style.textDecoration = 'underline';
                tocItem.onmouseout = () => tocItem.style.textDecoration = 'none';

                tocContainer.appendChild(tocItem);
            });

        } else if (isReadPage) {
            const h1Tags = document.querySelectorAll('h1');
            h1Tags.forEach((h1Tag, index) => {
                const text = h1Tag.textContent.trim();
                if (!text) return;

                const id = 'toc-header-' + index;
                h1Tag.id = id;

                const tocItem = document.createElement('a');
                tocItem.href = '#' + id;
                tocItem.innerText = text;
                Object.assign(tocItem.style, {
                    display: 'block',
                    color: '#007bff',
                    textDecoration: 'none',
                    fontSize: '13px',
                    marginBottom: '5px'
                });

                if (highlightKeywords.some(k => text.includes(k))) {
                    tocItem.style.fontWeight = 'bold';
                    tocItem.style.color = '#ff4500';
                    tocItem.style.fontSize = '16px';
                }

                tocItem.onmouseover = () => tocItem.style.textDecoration = 'underline';
                tocItem.onmouseout = () => tocItem.style.textDecoration = 'none';

                tocContainer.appendChild(tocItem);
            });
        }
    }

    // 评论区入口
    function addCommentLink() {
        const commentItem = document.createElement('a');
        Object.assign(commentItem.style, {
            display: 'block',
            color: '#dc3545',
            textDecoration: 'none',
            fontSize: '16px',
            fontWeight: 'bold',
            marginTop: '10px'
        });

        if (isReadPage) {
            const commentWrapper = document.querySelector('#comment-wrapper.comment-wrapper');
            if (commentWrapper) {
                commentItem.href = '#comment-wrapper';
                commentItem.innerText = '评论区';
            }
        } else if (isOpusPage) {
            const commentSection = document.querySelector('#commentapp') || document.querySelector('.opus-comment') || document.querySelector('.bili-tabs.opus-tabs');
            if (commentSection) {
                commentSection.id = 'opus-comment-section';
                commentItem.href = '#opus-comment-section';
                commentItem.innerText = '评论区';
            }
        }

        commentItem.onmouseover = () => commentItem.style.textDecoration = 'underline';
        commentItem.onmouseout = () => commentItem.style.textDecoration = 'none';

        tocContainer.appendChild(commentItem);
    }

    setTimeout(() => {
        generateTOC();
        addCommentLink();
        if (tocContainer.childElementCount > 1) {
            document.body.appendChild(tocContainer);
        }
    }, 1500);

})();