8chan.moe Mod Shortcuts

Adds a larger checkbox and ban button to each post's title bar on 8chan.moe mod.js and thread pages, hidden by default with a toggle button in the OP title bar and clickable toggle text in non-OP posts.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        8chan.moe Mod Shortcuts
// @namespace   Violentmonkey Scripts
// @match       https://8chan.moe/mod.js?boardUri=*&threadId=*
// @match       https://8chan.moe/*/res/*
// @grant       none
// @version     1.8
// @author      Anonymous
// @license     MIT
// @description Adds a larger checkbox and ban button to each post's title bar on 8chan.moe mod.js and thread pages, hidden by default with a toggle button in the OP title bar and clickable toggle text in non-OP posts.
// ==/UserScript==

/* eslint-env browser, es6 */

(function() {
    'use strict';

    // Track toggle state (hidden by default)
    let areModShortcutsVisible = false;

    // Function to add a larger checkbox and ban button to a post
    function addModShortcuts(post) {
        const deletionCheckbox = post.querySelector('input.deletionCheckBox');
        if (!deletionCheckbox) return; // Skip if no deletion checkbox found

        // Find the postInfo or opHead div to append the new checkbox
        const postInfo = post.querySelector('.postInfo.title, .opHead.title');
        if (!postInfo) return;

        // Create a container for the large checkbox and ban button
        const modShortcutsContainer = document.createElement('span');
        modShortcutsContainer.className = 'mod-shortcuts-container';
        modShortcutsContainer.style.marginLeft = '12px';
        modShortcutsContainer.style.display = areModShortcutsVisible ? 'inline-flex' : 'none';
        modShortcutsContainer.style.verticalAlign = 'top';
        modShortcutsContainer.style.gap = '5px';

        // Create the large checkbox
        const largeCheckbox = document.createElement('input');
        largeCheckbox.type = 'checkbox';
        largeCheckbox.style.width = '48px'; // 2x larger (24px * 2 = 48px)
        largeCheckbox.style.height = '48px';
        largeCheckbox.style.border = '4px solid black'; // Thicker border
        largeCheckbox.style.cursor = 'pointer';

        // Sync the large checkbox with the original
        largeCheckbox.checked = deletionCheckbox.checked;
        largeCheckbox.addEventListener('change', () => {
            deletionCheckbox.checked = largeCheckbox.checked;
            // Trigger change event on original checkbox to ensure form functionality
            const event = new Event('change', { bubbles: true });
            deletionCheckbox.dispatchEvent(event);
        });

        // Sync the original checkbox with the large one
        deletionCheckbox.addEventListener('change', () => {
            largeCheckbox.checked = deletionCheckbox.checked;
        });

        // Create the ban button
        const banButton = document.createElement('button');
        banButton.type = 'button'; // Prevent form submission
        banButton.textContent = 'Ban';
        banButton.style.width = '48px';
        banButton.style.height = '48px';
        banButton.style.border = '4px solid black';
        banButton.style.cursor = 'pointer';
        banButton.style.backgroundColor = '#f0f0f0';
        banButton.style.borderRadius = '3px';
        banButton.style.fontSize = '14px';
        banButton.style.marginTop = '0.2em'; // Align with checkbox

        // Ban button functionality
        banButton.addEventListener('click', (event) => {
            event.preventDefault();
            event.stopPropagation();

            // Find the extraMenuButton for this post
            const extraMenuButton = post.querySelector('.extraMenuButton');
            if (!extraMenuButton) {
                console.log('No .extraMenuButton found for post');
                return;
            }

            // Simulate click to open the floating menu
            extraMenuButton.click();

            // Wait briefly for the menu to appear
            setTimeout(() => {
                const floatingMenu = post.querySelector('.floatingList.extraMenu');
                if (!floatingMenu) {
                    console.log('No .floatingList.extraMenu found after clicking extraMenuButton');
                    return;
                }

                // Find the "Ban" menu item
                const banItem = Array.from(floatingMenu.querySelectorAll('li')).find(li => li.textContent === 'Ban');
                if (!banItem) {
                    console.log('No "Ban" item found in extraMenu');
                    return;
                }

                // Simulate click on the Ban item
                banItem.click();
                console.log('Ban modal triggered for post');
            }, 100); // Delay to ensure menu appears
        });

        // Append checkbox and ban button to the container
        modShortcutsContainer.appendChild(largeCheckbox);
        modShortcutsContainer.appendChild(banButton);

        // Append the container to the postInfo div
        postInfo.appendChild(modShortcutsContainer);
    }

    // Function to toggle mod shortcuts and update all buttons and text
    function toggleModShortcuts() {
        areModShortcutsVisible = !areModShortcutsVisible;
        console.log(`Toggling mod shortcuts: areModShortcutsVisible=${areModShortcutsVisible}`);

        // Update all mod shortcuts visibility
        const containers = document.querySelectorAll('.mod-shortcuts-container');
        containers.forEach(container => {
            container.style.display = areModShortcutsVisible ? 'inline-flex' : 'none';
        });

        // Update OP toggle button
        const opToggleButton = document.querySelector('.toggle-shortcuts-button.op-toggle');
        if (opToggleButton) {
            opToggleButton.textContent = areModShortcutsVisible ? 'Hide Mod Shortcuts' : 'Show Mod Shortcuts';
            opToggleButton.style.backgroundColor = areModShortcutsVisible ? '#e0e0e0' : '#f0f0f0';
        }

        // Update non-OP toggle text
        const toggleTexts = document.querySelectorAll('.toggle-shortcuts-text .toggle-text-inner');
        toggleTexts.forEach(text => {
            text.textContent = areModShortcutsVisible ? 'Mod' : 'Mod';
        });
    }

    // Function to add toggle button to OP post
    function addToggleButton() {
        const opPost = document.querySelector('.innerOP');
        if (!opPost) {
            console.log('No .innerOP found for toggle button');
            return;
        }

        const opTitle = opPost.querySelector('.opHead.title, .postInfo.title');
        if (!opTitle) {
            console.log('No .opHead.title or .postInfo.title found in .innerOP');
            return;
        }

        // Remove existing toggle button to prevent duplicates
        const existingButton = opTitle.querySelector('.toggle-shortcuts-button.op-toggle');
        if (existingButton) {
            existingButton.remove();
        }

        // Create toggle button
        const toggleButton = document.createElement('button');
        toggleButton.type = 'button'; // Prevent form submission
        toggleButton.className = 'toggle-shortcuts-button op-toggle glowOnHover';
        toggleButton.textContent = areModShortcutsVisible ? 'Hide Mod Shortcuts' : 'Show Mod Shortcuts';
        toggleButton.style.cursor = 'pointer';
        toggleButton.style.marginLeft = '10px';
        toggleButton.style.padding = '6px 12px'; // Larger for OP
        toggleButton.style.border = '2px solid #ccc';
        toggleButton.style.backgroundColor = areModShortcutsVisible ? '#e0e0e0' : '#f0f0f0';
        toggleButton.style.borderRadius = '5px';
        toggleButton.style.verticalAlign = 'middle';
        toggleButton.style.fontSize = '16px'; // Larger for OP

        // Toggle functionality
        toggleButton.addEventListener('click', (event) => {
            event.preventDefault(); // Prevent any default behavior
            event.stopPropagation(); // Prevent bubbling
            console.log('OP toggle button clicked');
            toggleModShortcuts();
        });

        // Append button to OP title bar
        opTitle.appendChild(toggleButton);
        console.log('OP toggle button added to OP title bar');
    }

    // Function to add clickable toggle text to non-OP posts
    function addNonOpToggleText(post) {
        const postInfo = post.querySelector('.postInfo.title');
        if (!postInfo) {
            console.log('No .postInfo.title found in non-OP post');
            return;
        }

        const spanId = postInfo.querySelector('.spanId');
        const linkSelf = postInfo.querySelector('.linkSelf');
        if (!spanId || !linkSelf) {
            console.log(`Non-OP toggle text not added: spanId=${!!spanId}, linkSelf=${!!linkSelf}`);
            return;
        }

        // Remove existing toggle text to prevent duplicates
        const existingText = postInfo.querySelector('.toggle-shortcuts-text');
        if (existingText) {
            existingText.remove();
        }

        // Create wrapper span for [text]
        const toggleTextWrapper = document.createElement('span');
        toggleTextWrapper.className = 'toggle-shortcuts-text';
        toggleTextWrapper.style.display = 'inline-block';
        toggleTextWrapper.style.marginLeft = '5px';
        toggleTextWrapper.style.marginRight = '5px';

        // Create inner clickable text
        const toggleTextInner = document.createElement('span');
        toggleTextInner.className = 'toggle-text-inner';
        toggleTextInner.textContent = areModShortcutsVisible ? 'Mod' : 'Mod';
        toggleTextInner.style.cursor = 'pointer';
        toggleTextInner.style.fontSize = '14px'; // Match header text
        toggleTextInner.style.color = '#3366cc'; // Subtle blue for clickability
        toggleTextInner.style.textDecoration = 'none'; // Clean look
        toggleTextInner.addEventListener('mouseover', () => {
            toggleTextInner.style.textDecoration = 'underline'; // Hover effect
        });
        toggleTextInner.addEventListener('mouseout', () => {
            toggleTextInner.style.textDecoration = 'none';
        });

        // Toggle functionality
        toggleTextInner.addEventListener('click', (event) => {
            event.preventDefault(); // Prevent any default behavior
            event.stopPropagation(); // Prevent bubbling
            console.log('Non-OP toggle text clicked');
            toggleModShortcuts();
        });

        // Assemble [text]
        toggleTextWrapper.appendChild(document.createTextNode('['));
        toggleTextWrapper.appendChild(toggleTextInner);
        toggleTextWrapper.appendChild(document.createTextNode(']'));

        // Insert text between spanId and linkSelf
        linkSelf.insertAdjacentElement('beforebegin', toggleTextWrapper);
        console.log('Non-OP toggle text added between spanId and linkSelf');
    }

    // Process all posts (OP and replies)
    function processPosts() {
        // Handle OP
        const opPost = document.querySelector('.innerOP');
        if (opPost) {
            addModShortcuts(opPost);
            addToggleButton();
        }

        // Handle replies
        const replyPosts = document.querySelectorAll('.innerPost');
        replyPosts.forEach(post => {
            addModShortcuts(post);
            addNonOpToggleText(post);
        });
    }

    // Initial processing
    processPosts();

    // Observe for dynamically added posts (e.g., via auto-refresh or new replies)
    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        // Check if the added node is a post
                        if (node.classList.contains('innerPost')) {
                            addModShortcuts(node);
                            addNonOpToggleText(node);
                        } else if (node.classList.contains('innerOP')) {
                            addModShortcuts(node);
                            addToggleButton();
                        }
                        // Check for posts within the added node
                        node.querySelectorAll('.innerPost').forEach(post => {
                            addModShortcuts(post);
                            addNonOpToggleText(post);
                        });
                        node.querySelectorAll('.innerOP').forEach(post => {
                            addModShortcuts(post);
                            addToggleButton();
                        });
                    }
                });
            }
        });
    });

    // Observe changes in the thread list
    const threadList = document.getElementById('threadList');
    if (threadList) {
        observer.observe(threadList, {
            childList: true,
            subtree: true
        });
    } else {
        console.log('No #threadList found for MutationObserver');
    }
})();