Collapse HackerNews Parent Comments

Adds vertical bars to the left of the comments, enabling you to easily collapse the parent comments. It also can leave only a specified number of comments expanded and auto-collapse the rest

目前為 2021-02-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name            Collapse HackerNews Parent Comments
// @description     Adds vertical bars to the left of the comments, enabling you to easily collapse the parent comments. It also can leave only a specified number of comments expanded and auto-collapse the rest
// @author          BLBC (github.com/hjk789, greasyfork.org/users/679182-hjk789)
// @copyright       2020+, BLBC (github.com/hjk789, greasyfork.org/users/679182-hjk789)
// @version         1.1
// @homepage        https://github.com/hjk789/Creations/tree/master/Userscripts/Collapse-HackerNews-Parent-Comments
// @license         https://github.com/hjk789/Creations/tree/master/Userscripts/Collapse-HackerNews-Parent-Comments#license
// @grant           none
// @include         https://news.ycombinator.com/item*
// @namespace https://greasyfork.org/users/679182
// ==/UserScript==


//--------------- Settings -----------------

const collapse = true   // Whether all comments, other than the number of comments below, should be collapsed.
                        // If set to false, all comments will be left expanded and the settings below have no effect.
    const numberOfRoots = 3
    const numberOfReplies = 2
    const numberOfRepliesOfReplies = 1
//------------------------------------------

const fadeOnHoverStyle = document.createElement("style")
fadeOnHoverStyle.innerHTML = ".verticalBar:hover { background-color: gray !important; }"
document.body.appendChild(fadeOnHoverStyle)

// HackerNews puts a 1x1 image before each comment and sets it's width according to each comment depth. Each level of depth adds
// 40px of width to this image, starting from 0 which are the root comments. The ones with a 14px width are flagged comments and
// the "More comments" link. And because HackerNews layout doesn't have any easier way of identifying the hierarchy of the
// comments (it's just a list of comments pushed to the right), that's the only way I've found to achieve this.

spacingImgs = document.querySelectorAll(".ind img[height='1']:not([width='14'])")

let root = 0
let i = 0
let commentHier = []

if (collapse)   spacingImgs[0].parentElement.parentElement.querySelector(".togg").click()

collapseAll = setInterval(function()   // An interval of 1ms is being used to prevent the page from freezing until it finishes. Also, it creates a cool effect when
{                                      // the comments are being collapsed. It does make it take a few more seconds to finish in comment-heavy posts (150+) though.
    const level = spacingImgs[i].width / 40

    let commentToggle = spacingImgs[i].parentElement.parentElement.querySelector(".togg")
    if (collapse)   commentToggle.click()  // Collapse the first comment so it's expanded again below and you can read the first comment while the collapsing is under process


    // Store the current hierarchy in an array

    if (commentHier[level] == null)
        commentHier.push(commentToggle)
    else
        commentHier[level] = commentToggle

    const commentContainer = spacingImgs[i].parentElement.parentElement.parentElement.parentElement.parentElement
    commentContainer.style = "border-top: 5px transparent solid"  // To visually separate each vertical bar
    spacingImgs[i].parentElement.style = "position: relative"

    let divs = []
    for (j = spacingImgs[i].width; j >= 0; j -= 40)  // Start adding the vertical bar from the current depth and go backwards
    {
        // Create the vertical bar

        const div = document.createElement("div")
        div.className = "verticalBar"
        div.commentHier = commentHier[j/40]  // Store in an attribute of the element this comment's parent respective to the level of the vertical bar, for easy access
        div.onclick = commentHier[j/40].onclick  // When a vertical bar is clicked, collapse the respective parent comment
        div.onmouseup = function(e)
        {
            if (e.target.commentHier.getBoundingClientRect().y < 0)  // If the parent comment is off-screen above,
                e.target.commentHier.scrollIntoView()                // scroll to it
        }

        let style = "left: " + (-5 + j) + "px; width: 12px; background-color: lightgray; position: absolute; z-index: 99; transition: 0.15s; "

        // Make it so that the vertical bars are only separated when followed by comments of same level of depth

        if (j == spacingImgs[i].width && spacingImgs[i-1] != null && spacingImgs[i].width <= spacingImgs[i-1].width)
            style += "top: 5px; height: calc(100% + 8px); "
        else
            style += "top: 0px; height: calc(100% + 13px); "

        div.style = style

        divs.push(div)
    }

    for (j = divs.length - 1; j >= 0; j--)
        spacingImgs[i].parentElement.appendChild(divs[j])

    i++

    if (i == spacingImgs.length)  // When finished collapsing and adding the vertical bars to all comments, now it's time to expand only a few of the first comments
    {
        clearInterval(collapseAll)

        if (collapse)
        {
            spacingImgs[0].parentElement.parentElement.querySelector(".togg").click()  // Expand the first comment
            root = 0
            for (i=0; i < spacingImgs.length; i++)
            {
                commentToggle = spacingImgs[i].parentElement.parentElement.querySelector(".togg")

                if (spacingImgs[i].width == 0)  // If it's a root comment
                {
                    root++
                    if (root == numberOfRoots + 1)  break  // If there's already <numberOfRoots> comments expanded, then stop expanding

                    commentToggle.click()
                    sub40 = 0
                    sub80 = 0
                }
                else if (spacingImgs[i].width == 40 && sub40 < numberOfReplies)  // If it's a reply to the root comment, only expand up to <numberOfReplies>
                {
                    commentToggle.click()
                    sub40++
                    sub80 = 0
                }
                else if (spacingImgs[i].width == 80 && sub80 < numberOfRepliesOfReplies)  // If it's a reply to the reply, only expand up to <numberOfRepliesOfReplies>
                {
                    commentToggle.click()
                    sub80++
                }
            }
        }
    }

}, 1)