Hacker News 评论折叠

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

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