8chan Toggle All Media per Post

Adds a [+]/[-] button to expand/collapse all media in a single post on 8chan.moe/se.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         8chan Toggle All Media per Post
// @namespace    sneed
// @version      1.5
// @description  Adds a [+]/[-] button to expand/collapse all media in a single post on 8chan.moe/se.
// @author       Gemini 2.5
// @license      MIT
// @match        https://8chan.moe/*/res/*.html*
// @match        https://8chan.se/*/res/*.html*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const EXPAND_TEXT = '[+]';
    const COLLAPSE_TEXT = '[-]';
    const BUTTON_CLASS = 'toggle-all-media-btn'; // Class for the button

    /**
     * Cleans up extra visible a.hideLink elements in a media cell after collapse.
     * @param {HTMLElement} uploadCell - The figure.uploadCell element.
     */
    function cleanupExtraHideLinks(uploadCell) {
        // We only expect one *functional* hide link per audio/video player when expanded.
        // When collapsed, there should ideally be zero *visible* hide links.
        // This function targets cells that are NOT expanded and might have leftover links.
        if (uploadCell.classList.contains('expandedCell')) {
            return; // Only clean up collapsed cells
        }

        const hideLinks = uploadCell.querySelectorAll('a.hideLink');

        if (hideLinks.length > 1) {
             // console.log(`Cleanup: Found ${hideLinks.length} hide links in a non-expanded cell. Hiding extras.`);
             // Keep the first one potentially, or just hide all visible extras
             // Let's hide all except the first one found, as the first one might be the "correct" one if any interaction happened.
             for (let i = 1; i < hideLinks.length; i++) {
                 hideLinks[i].style.display = 'none';
             }
             // Even the first one shouldn't be visible if the cell isn't expanded, based on normal behavior.
             // Let's ensure all hide links are hidden if the cell is not expanded.
             hideLinks.forEach(link => link.style.display = 'none');
        } else if (hideLinks.length === 1) {
             // If there's exactly one hide link, ensure it's hidden if the cell is not expanded
             hideLinks[0].style.display = 'none';
        }
        // If length is 0 or 1 (and handled above), nothing more needed.
    }


    /**
     * Adds a toggle button to expand/collapse all media in a post if it has multiple uploads.
     * @param {HTMLElement} postElement - The post element (.postCell or .opCell).
     */
    function addExpandToggleButton(postElement) {
        const panelUploads = postElement.querySelector('.panelUploads');

        if (!panelUploads || postElement.querySelector(`.${BUTTON_CLASS}`)) {
            return;
        }

        const uploadCells = panelUploads.querySelectorAll('.uploadCell');
        if (uploadCells.length <= 1) {
            return;
        }

        const button = document.createElement('span');
        button.textContent = EXPAND_TEXT;
        button.title = 'Toggle expand/collapse all media in this post';
        button.classList.add(BUTTON_CLASS);
        button.dataset.state = 'collapsed'; // Initial state assumes things start collapsed

        // button.style.display = 'block';
        button.style.marginBottom = '5px';
        button.style.cursor = 'pointer';
        button.style.fontSize = '0.9em';
        button.style.fontWeight = 'bold';
        button.style.color = 'var(--link-color, blue)';
        button.style.userSelect = 'none';

        button.addEventListener('click', (event) => {
            event.preventDefault();
            event.stopPropagation();

            const currentPost = event.target.closest('.postCell, .opCell');
            if (!currentPost) return;

            const currentPanelUploads = currentPost.querySelector('.panelUploads');
            if (!currentPanelUploads) return;

            const mediaItems = currentPanelUploads.querySelectorAll('.uploadCell');
            const currentState = button.dataset.state;

            if (currentState === 'collapsed') {
                // --- Action: Expand currently collapsed items ---
                mediaItems.forEach(cell => {
                    // Find items that are NOT expanded
                    if (!cell.classList.contains('expandedCell')) {
                         const link = cell.querySelector('a.imgLink');
                         if (link) {
                             // console.log('Expanding:', link.href);
                             link.click(); // Click the main link to trigger expansion
                         }
                    }
                });
                // Update state AFTER action
                button.dataset.state = 'expanded';
                button.textContent = COLLAPSE_TEXT;

            } else { // currentState === 'expanded'
                // --- Action: Collapse currently expanded items ---
                mediaItems.forEach(cell => {
                    // Find items that ARE expanded
                    if (cell.classList.contains('expandedCell')) {
                        // For expanded audio/video, the collapse button is a.hideLink
                        const hideLink = cell.querySelector('a.hideLink');
                        if (hideLink) {
                            // console.log('Collapsing (via hideLink):', cell.querySelector('a.imgLink')?.href);
                            hideLink.click(); // Click the hide link
                        } else {
                            // For expanded images, the collapse action is clicking a.imgLink again
                            const mainLink = cell.querySelector('a.imgLink');
                             if (mainLink) {
                                 // console.log('Collapsing (via imgLink):', mainLink.href);
                                 mainLink.click();
                            }
                        }
                    }
                });

                // Update state AFTER action
                button.dataset.state = 'collapsed';
                button.textContent = EXPAND_TEXT;

                // --- Cleanup: Hide any extra, visible hide links after collapse ---
                // Add a small delay to allow the native script's collapse animation/DOM changes to finish
                // before cleaning up.
                 setTimeout(() => {
                     mediaItems.forEach(cell => {
                          cleanupExtraHideLinks(cell);
                     });
                 }, 50); // 50ms delay should be sufficient
            }
        });

        const firstUploadCell = panelUploads.querySelector('.uploadCell');
        if (firstUploadCell) {
            panelUploads.insertBefore(button, firstUploadCell);
        } else {
             panelUploads.appendChild(button);
        }
    }

    /**
     * Observes the main post container for newly added posts and adds buttons to them.
     */
    function observeNewPosts() {
        const targetNode = document.querySelector('#divThreads .divPosts');
        if (!targetNode) {
            console.warn('Toggle All Media: Could not find target node for MutationObserver.');
            return;
        }

        const config = { childList: true };

        const callback = function(mutationsList, observer) {
            for(const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches('.postCell, .opCell')) {
                                addExpandToggleButton(node);
                            } else {
                                // Check for posts potentially nested within the added node (e.g. in a wrapper)
                                node.querySelectorAll('.postCell, .opCell').forEach(addExpandToggleButton);
                            }
                        }
                    });
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    }

    // --- Main Execution ---
    document.querySelectorAll('.postCell, .opCell').forEach(addExpandToggleButton);
    requestAnimationFrame(observeNewPosts);

})();