YouTube: Expand All Video Comments

Adds a "Expand all" button to video comments which expands every comment and replies - no more clicking "Read more".

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            YouTube: Expand All Video Comments
// @namespace       org.sidneys.userscripts
// @homepage        https://gist.githubusercontent.com/sidneys/6756166a781bd76b97eeeda9fb0bc0c1/raw/
// @version         4.7.8
// @description     Adds a "Expand all" button to video comments which expands every comment and replies - no more clicking "Read more".
// @author          sidneys
// @icon            https://www.youtube.com/favicon.ico
// @noframes
// @match           http*://www.youtube.com/*
// @require         https://greasyfork.org/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js
// @require         https://greasyfork.org/scripts/374849-library-onelementready-es7/code/Library%20%7C%20onElementReady%20ES7.js
// @run-at          document-end
// @grant           GM_addStyle
// ==/UserScript==


/* global Debug, onElementReady */

/**
 * ESLint
 * @global
 */
Debug = false


/**
 * Applicable URL paths
 * @default
 * @constant
 */
const urlPathList = [
    '/watch'
]


/**
 * Inject Stylesheet
 */
let injectStylesheet = () => {
    console.debug('injectStylesheet')

    GM_addStyle(`
        /* =======================================
           ELEMENTS
           ======================================= */

        /* Button: Expand all Comments
           --------------------------------------- */

        .expand-all-comments-button
        {
            padding: 0;
            align-self: start;
            margin-left: var(--ytd-margin-4x, 8px);
            margin-top: 0;
        }


        .expand-all-comments-button #checkboxLabel
        {
            margin-left: var(--ytd-margin-2x, 8px);
            padding-left: 0;
            display: inline-flex;
        }

        .busy .expand-all-comments-button #checkboxContainer,
        .busy .expand-all-comments-button #checkboxLabel
        {
            animation: var(--animation-busy-on);
        }

        /* Button: Expand all Comments
           Spinner
           --------------------------------------- */

        .expand-all-comments-button #checkboxLabel::after
        {
            background-image: url("https://i.imgur.com/z4V4Os8.png");
            content: "";
            background-size: 100%;
            background-repeat: no-repeat;
            margin-left: var(--ytd-margin-2x, 8px);
            height: var(--ytd-margin-4x, 16px);
            width: var(--ytd-margin-4x, 16px);
        }

        .expand-all-comments-button #checkboxLabel,
        .expand-all-comments-button #checkboxLabel::after
        {
            transition: filter 1000ms ease-in-out;
        }

        :not(.busy) .expand-all-comments-button #checkboxLabel::after
        {

            filter: opacity(0);
        }

        .busy .expand-all-comments-button #checkboxLabel::after
        {

            filter: opacity(1);
        }


        /* =======================================
           ANIMATIONS
           ======================================= */

        :root
        {
            --animation-busy-on: 'busy-on' 500ms ease-in-out 1000ms 1 normal forwards running;
        }

        @keyframes busy-on {
            from {
                pointer-events: none;
                cursor: default;
            }
            to {
                filter: saturate(0.1);
                color: hsla(0deg, 0%, 100%, 0.5);
            }
        }
    `)
}

/**
 * Set global busy mode
 * @param {Boolean} isBusy - Yes/No
 * @param {String=} selector - Contextual element selector
 */
let setBusy = (isBusy, selector = 'ytd-comments') => {
    // console.debug('setBusy', 'isBusy:', isBusy)

    let element = document.querySelector(selector)

    if (isBusy === true) {
        element.classList.add('busy')
        return
    } else {
        element.classList.remove('busy')
    }
}

/**
 * Get Button element
 * @returns {Boolean} - On/Off
 */
let getButtonElement = () => document.querySelector('.expand-all-comments-button')

/**
 * Get Toggle state
 * @returns {Boolean} - On/Off
 */
let getToggleState = () => Boolean(getButtonElement() && getButtonElement().checked)


/**
 * Expand all comments
 */
let expandAllComments = () => {
    console.debug('expandAllComments')

    // Look for "View X replies" buttons in comment section
    onElementReady('ytd-comment-replies-renderer #more-replies.ytd-comment-replies-renderer', false, (buttonElement) => {
        // Abort if toggle disabled
        if (!getToggleState()) { return }

        /** @listens buttonElement:Event#click */
        // buttonElement.addEventListener('click', () => setBusy(false), { once: true, passive: true })

        // Busy = yes
        // setBusy(true)

        //  Click button
        buttonElement.click()
    })

    // Look for "Read More" buttons in comment section
    onElementReady('ytd-comments tp-yt-paper-button.ytd-expander#more:not([hidden])', false, (buttonElement) => {
        // Abort if toggle disabled
        if (!getToggleState()) { return }

        /** @listens buttonElement:Event#click */
        // buttonElement.addEventListener('click', () => setBusy(false), { once: true, passive: true })

        // Busy = yes
        // setBusy(true)

        //  Click button
        buttonElement.click()
    })
}


/**
 * Check if the toggle is enabled, if yes, start expanding
 */
let tryExpandAllComments = () => {
    console.debug('tryExpandAllComments')

    const toggleState = getToggleState()

    console.debug('toggle state:', toggleState)

    // Abort if toggle disabled
    if (!toggleState) { return }

    expandAllComments()
}


/**
 * Render button: 'Expand all Comments'
 * @param {Element} element - Container element
 */
let renderButton = (element) => {
    console.debug('renderButton')

    const buttonElement = document.createElement('tp-yt-paper-checkbox')
    buttonElement.className = 'expand-all-comments-button'
    buttonElement.innerHTML = `
    <div id="icon-label" class="yt-dropdown-menu">
        Expand all Comments
    </div>
    `

    // Add button
    element.appendChild(buttonElement)

    // Handle button toggle
    buttonElement.onchange = tryExpandAllComments

    // Status
    console.debug('rendered button')
}


/**
 * Init
 */
let init = () => {
    console.info('init')

    // Verify URL path
    if (!urlPathList.some(urlPath => window.location.pathname.startsWith(urlPath))) { return }

    // Add Stylesheet
    injectStylesheet()

    // Wait for menu container
    onElementReady('ytd-comments ytd-comments-header-renderer > #title', false, (element) => {
        console.debug('onElementReady', 'ytd-comments ytd-comments-header-renderer > #title')

        // Render button
        renderButton(element)
    })

    // // Wait for variable section container
    // onElementReady('ytd-item-section-renderer#sections.style-scope.ytd-comments > #contents', false, (element) => {
    //     console.debug('onElementReady', 'element:',  '#contents')
    //
    //     /**
    //      * YouTube: Detect "Load More" stuff
    //      * @listens ytd-item-section-rendere:Event#yt-load-next-continuation
    //      */
    //     element.parentElement.addEventListener('yt-load-next-continuation', (event) => {
    //         console.debug('ytd-item-section-renderer#yt-load-next-continuation')
    // 
    //         const currentTarget = event.currentTarget
    //         const shownItems = currentTarget && currentTarget.__data && currentTarget.__data.shownItems || []
    //         const shownItemsCount = shownItems.length
    //
    //         // DEBUG
    //         // console.debug('currentTarget.__data:')
    //         // console.dir(currentTarget.__data)
    //
    //         // Probe whether this is still the initial item batch, if yes, skip
    //         if (shownItemsCount === 0) { return }
    //
    //         tryExpandAllComments()
    //     })
    // })
}


/**
 * YouTube: Detect in-page navigation
 * @listens window:Event#yt-navigate-finish
 */
window.addEventListener('yt-navigate-finish', () => {
    console.debug('window#yt-navigate-finish')

    init()
})