NodeSeek 楼中楼 (Threads)

为 NodeSeek 网站提供嵌套评论(楼中楼)功能,让对话浏览更轻松。

当前为 2025-07-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         NodeSeek Threads
// @name:zh-CN   NodeSeek 楼中楼 (Threads)
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Enhances NodeSeek with nested comments, making conversations easier to follow.
// @description:zh-CN 为 NodeSeek 网站提供嵌套评论(楼中楼)功能,让对话浏览更轻松。
// @author       Dean & Gemini
// @match        https://www.nodeseek.com/post-*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @icon         https://www.google.com/s2/favicons?sz=64&domain=nodeseek.com
// @license      MIT
// @homepageURL  https://github.com/your-username/nodeseek-threads
// ==/UserScript==

(function() {
    'use strict';

    // --- Settings for signature display ---
    let showSignatures = GM_getValue('showSignatures', true);
    GM_registerMenuCommand(`${showSignatures ? 'Hide' : 'Show'} Signatures`, () => {
        GM_setValue('showSignatures', !showSignatures);
        window.location.reload();
    });

    // --- Core CSS Styles ---
    const nestedAvatarSize = 24;
    const avatarMargin = 8;
    const nestedIndent = nestedAvatarSize + avatarMargin;

    let styles = `
        /* --- Light Mode: Top-level Comments --- */
        body.light-layout .comments > .content-item {
            background: rgba(0,0,0,.05) !important;
            border: 1px solid rgba(0,0,0,.05) !important;
            padding: 10px 10px 10px 16px !important;
            margin-bottom: 10px !important;
            border-radius: 1px !important;
            transition: background .3s, border .3s;
            position: relative;
        }
        body.light-layout .comments > .content-item::before,
        body.light-layout .comments > .content-item::after {
            content: ""; position: absolute; top: 0; bottom: 0; width: 3px;
        }
        body.light-layout .comments > .content-item::before { left: 0; background-color: rgba(0,0,0,.1); }
        body.light-layout .comments > .content-item::after { left: 3px; background-color: rgba(0,0,0,.2); }

        /* --- Dark Mode: Top-level Comments --- */
        body.dark-layout .comments > .content-item {
            background: hsla(0,0%,100%,.01) !important;
            border: 1px solid hsla(0,0%,100%,.05) !important;
            padding: 10px 10px 10px 16px !important;
            margin-bottom: 10px !important;
            border-radius: 1px !important;
            transition: background .3s, border .3s;
            position: relative;
        }
        body.dark-layout .comments > .content-item::before,
        body.dark-layout .comments > .content-item::after {
            content: ""; position: absolute; top: 0; bottom: 0; width: 3px;
        }
        body.dark-layout .comments > .content-item::before { left: 0; background-color: hsla(0,0%,100%,.05); }
        body.dark-layout .comments > .content-item::after { left: 3px; background-color: hsla(0,0%,100%,.1); }

        /* --- Nested Replies Container --- */
        .nested-replies-container {
            border: 1px solid rgba(0,0,0,.1);
            border-left: none;
            margin-top: 10px;
            padding: 0 8px 5px 12px;
            border-radius: 1px;
            transition: border .3s;
            background: hsla(0,0%,100%,.01) !important;
            position: relative;
        }
        body.dark-layout .nested-replies-container {
             border-color: hsla(0,0%,100%,.1) !important;
             border-left: none;
        }
        .nested-replies-container::before,
        .nested-replies-container::after {
            content: ""; position: absolute; top: 0; bottom: 0; width: 2px;
        }
        body.light-layout .nested-replies-container::before { left: 0; background-color: rgba(0,0,0,.05); }
        body.light-layout .nested-replies-container::after { left: 2px; background-color: rgba(0,0,0,.1); }
        body.dark-layout .nested-replies-container::before { left: 0; background-color: hsla(0,0%,100%,.05); }
        body.dark-layout .nested-replies-container::after { left: 2px; background-color: hsla(0,0%,100%,.1); }

        /* --- Nested Comment Items --- */
        .nested-replies-container > .content-item.is-nested {
            border: none !important; padding: 8px 0 0 0 !important; margin-bottom: 0 !important;
            border-bottom: 1px dashed rgba(0,0,0,.08) !important; background: transparent !important; transition: border .3s;
        }
        body.dark-layout .nested-replies-container > .content-item.is-nested {
            border-bottom-color: hsla(0,0%,100%,.08) !important;
        }
        .nested-replies-container > .content-item.is-nested:last-child { border-bottom: none !important; }
        .nested-replies-container > .content-item.is-nested:first-child { padding-top: 5px !important; }

        /* --- Nested Elements Layout --- */
        .is-nested .avatar-normal {
            width: ${nestedAvatarSize}px !important;
            height: ${nestedAvatarSize}px !important;
        }
        .is-nested > .post-content, .is-nested > .signature {
            margin-left: ${nestedIndent}px;
        }
    `;

    if (!showSignatures) {
        styles += `.signature { display: none !important; }`;
    }

    GM_addStyle(styles);

    function processComments() {
        const comments = document.querySelectorAll('.content-item');
        const commentMap = new Map();
        comments.forEach(comment => {
            const floorId = comment.getAttribute('id');
            if (floorId) commentMap.set(floorId, comment);
        });

        comments.forEach(comment => {
            if (comment.classList.contains('nested-processed') || comment.parentElement.classList.contains('nested-replies-container')) {
                return;
            }

            const replyLink = comment.querySelector('.post-content a[href*="/post-"]');
            if (replyLink && replyLink.hash && !replyLink.closest('blockquote')) {
                const parentFloorId = replyLink.hash.substring(1);
                if (parentFloorId !== '0') {
                    const parentComment = commentMap.get(parentFloorId);
                    if (parentComment && parentComment !== comment) {
                        let container = parentComment.querySelector('.nested-replies-container');
                        if (!container) {
                            container = document.createElement('div');
                            container.className = 'nested-replies-container';
                            parentComment.appendChild(container);
                        }
                        container.appendChild(comment);
                        comment.classList.add('is-nested');
                    }
                }
            }
            comment.classList.add('nested-processed');
        });
    }

    const observer = new MutationObserver(() => { setTimeout(processComments, 200); });
    window.addEventListener('load', () => {
        processComments();
        const commentsContainer = document.querySelector('.comments');
        if (commentsContainer) {
            observer.observe(commentsContainer, { childList: true, subtree: true });
        }
    });
})();