Hacker News 评论折叠

折叠 Hacker News 上的嵌套评论,只显示直接回复,添加展开按钮查看子评论

Versión del día 29/4/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Hacker News 评论折叠
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  折叠 Hacker News 上的嵌套评论,只显示直接回复,添加展开按钮查看子评论
// @author       Claude
// @match        https://news.ycombinator.com/item*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 页面加载完成后执行
    window.addEventListener('load', function() {
        // 等待 DOM 完全加载
        setTimeout(initCommentFolding, 500);
    });

    function initCommentFolding() {
        const comments = document.querySelectorAll('.comment-tree tr.comtr');
        if (!comments.length) return;

        // 找出所有评论及其嵌套级别
        const commentMap = new Map();
        const topLevelComments = [];

        comments.forEach(comment => {
            // 获取评论缩进级别
            const indent = comment.querySelector('td.ind img');
            const indentLevel = indent ? parseInt(indent.getAttribute('width')) / 40 : 0;
            
            // 获取评论 ID
            const id = comment.id;
            if (!id) return;
            
            // 储存评论信息
            commentMap.set(id, {
                element: comment,
                level: indentLevel,
                replies: [],
                isHidden: false
            });
            
            // 如果是顶级评论,加入列表
            if (indentLevel === 0) {
                topLevelComments.push(id);
            }
        });
        
        // 建立评论层级关系
        let currentParent = null;
        let lastLevel = 0;
        const parentStack = [];

        comments.forEach(comment => {
            const id = comment.id;
            if (!id || !commentMap.has(id)) return;
            
            const commentInfo = commentMap.get(id);
            const currentLevel = commentInfo.level;
            
            if (currentLevel === 0) {
                // 顶级评论
                currentParent = id;
                lastLevel = 0;
                parentStack.length = 0;
                parentStack.push(id);
            } else {
                // 找到适当的父评论
                if (currentLevel > lastLevel) {
                    // 向下一级,当前父评论不变
                    parentStack.push(currentParent);
                } else if (currentLevel < lastLevel) {
                    // 回到上一级,弹出堆栈
                    for (let i = 0; i < (lastLevel - currentLevel); i++) {
                        parentStack.pop();
                    }
                    currentParent = parentStack[parentStack.length - 1];
                }
                // 当前级别相同,父评论在堆栈的最后一个
                else {
                    currentParent = parentStack[parentStack.length - 2];
                }
                
                // 将当前评论添加到父评论的回复列表中
                if (commentMap.has(currentParent)) {
                    commentMap.get(currentParent).replies.push(id);
                }
                
                // 隐藏非顶级评论
                if (currentLevel > 0) {
                    comment.style.display = 'none';
                    commentInfo.isHidden = true;
                }
                
                lastLevel = currentLevel;
                currentParent = id;
            }
        });
        
        // 为每个有回复的顶级评论添加展开/折叠按钮
        topLevelComments.forEach(commentId => {
            const comment = commentMap.get(commentId);
            if (comment && comment.replies.length > 0) {
                addToggleButton(comment, commentMap);
            }
        });
    }

    function addToggleButton(comment, commentMap) {
        const commentElement = comment.element;
        const commentHead = commentElement.querySelector('.comhead');
        
        if (!commentHead) return;
        
        // 创建展开/折叠按钮
        const toggleButton = document.createElement('span');
        toggleButton.className = 'toggle-button';
        toggleButton.textContent = `[展开 ${comment.replies.length} 条回复]`;
        toggleButton.style.cursor = 'pointer';
        toggleButton.style.color = '#ff6600';
        toggleButton.style.marginLeft = '10px';
        
        // 添加点击事件
        toggleButton.addEventListener('click', function() {
            const isExpanded = toggleButton.textContent.includes('折叠');
            
            if (isExpanded) {
                // 折叠所有回复
                toggleReplies(comment.replies, commentMap, false);
                toggleButton.textContent = `[展开 ${comment.replies.length} 条回复]`;
            } else {
                // 展开所有回复
                toggleReplies(comment.replies, commentMap, true);
                toggleButton.textContent = `[折叠回复]`;
            }
        });
        
        commentHead.appendChild(toggleButton);
    }

    function toggleReplies(replyIds, commentMap, isVisible) {
        replyIds.forEach(replyId => {
            const reply = commentMap.get(replyId);
            if (!reply) return;
            
            // 设置当前回复的可见性
            reply.element.style.display = isVisible ? '' : 'none';
            reply.isHidden = !isVisible;
            
            // 如果是收起操作,则递归收起所有子回复
            if (!isVisible && reply.replies.length > 0) {
                toggleReplies(reply.replies, commentMap, false);
            }
        });
    }
})();