Imgur: add vote counts to comment scores in the old design

This script adds vote counts (+ and -) to the visible points on comments. They're normally not viewable on the web site. It also removes the "via Android" and "via iPhone" badges, because I have a tendency to click on them accidentally and don't feel that they add any value.

当前为 2023-12-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         Imgur: add vote counts to comment scores in the old design
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  This script adds vote counts (+ and -) to the visible points on comments. They're normally not viewable on the web site. It also removes the "via Android" and "via iPhone" badges, because I have a tendency to click on them accidentally and don't feel that they add any value.
// @author       Corrodias
// @match        https://imgur.com/gallery/*
// @match        https://imgur.com/user/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=imgur.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // The comments container is not necessarily loaded before the script runs. Wait for it.
    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector))
                return resolve(document.querySelector(selector));

            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    waitForElement('#captions').then(commentsSection => {
        const mutationObserver = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE)
                        processAddedElement(node);
                }
            }
        });

        function processAddedElement(node) {
            //console.log(node);
            if (window.location.pathname.startsWith('/gallery/') && node.classList.contains('children')) {
                // Replies were expanded, or navigation to a new post loaded a whole, new set of comments. We can safely update just the new nodes.
                updateComments(node);
            } else if (window.location.pathname.startsWith('/gallery/') && node.classList.contains('comment')) {
                // The user navigated to the "context" or "permalink" of an already displayed comment. Imgur modifies the existing structure rather than replace the nodes.
                updateComments(document.querySelector('#captions')); // The node that was added was a reply, not the top level comment, to replace all of them.
            } else if (window.location.pathname.startsWith('/user/') && node.classList.length === 0 && node.tagName === 'DIV') {
                // Comments on a user's page or replies just loaded.
                updateComments(node);
            }
        }

        function updateComments(node) {
            hideMobileBadges(node);
            removeOldPointsDetails(node);
            addPointsDetails(node);
        }

        function hideMobileBadges(node) {
            // Hide the Android/iPhone badges. Do not remove them, or React will break.
            let via = node.querySelectorAll('.via');
            for (const a of via) {
                a.style.display = 'none';
            }
        }

        function removeOldPointsDetails(node) {
            // Remove any existing vote counts so that we can calculate them fresh.
            // This is needed because navigating to the "context" or "permalink" of a comment doesn't necessarily add new nodes but rather can modify existing ones.
            let oldPoints = node.querySelectorAll('.points-detail');
            for (const a of oldPoints) {
                a.remove();
            }
        }

        function addPointsDetails(node) {
            let captionElements = node.querySelectorAll('.caption');
            for (const element of captionElements) {
                // Pull the comment data from the React objects.
                let commentData = getCommentData(element);
                if (commentData === undefined || commentData === null) continue; // no data found

                // Build then add an element with the vote counts.
                let newElement = document.createElement('span');
                newElement.textContent = '(+' + commentData.ups + ', -' + commentData.downs + ')';
                newElement.setAttribute('class', 'comment-meta-spacer points-detail');

                let points = element.firstChild.firstChild.querySelector('.comment-meta-spacer');
                points.after(newElement);
            }
        }

        function getCommentData(node) {
            for (const propertyKey in node) {
                if (propertyKey.startsWith('__reactInternalInstance$')) // The rest of the name is not always identical.
                    return node[propertyKey]._currentElement._owner._instance.props.comment;
            }

            return null;
        }

        // Update all currently displayed comments.
        updateComments(commentsSection);

        // Do the same on any comment that is loaded dynamically.
        mutationObserver.observe(commentsSection, { childList: true, subtree: true });
    });
})();