Greasy Fork

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.

目前为 2025-04-23 提交的版本。查看 最新版本

// ==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.7
// @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.
// ==/UserScript==

(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 = 'middle';
        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 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');
        if (existingButton) {
            existingButton.remove();
        }

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

        // Toggle functionality
        toggleButton.addEventListener('click', (event) => {
            event.preventDefault(); // Prevent any default behavior
            event.stopPropagation(); // Prevent bubbling to parent elements
            areModShortcutsVisible = !areModShortcutsVisible;
            const containers = document.querySelectorAll('.mod-shortcuts-container');
            containers.forEach(container => {
                container.style.display = areModShortcutsVisible ? 'inline-flex' : 'none';
            });
            toggleButton.textContent = areModShortcutsVisible ? 'Hide Mod Shortcuts' : 'Show Mod Shortcuts';
            toggleButton.style.backgroundColor = areModShortcutsVisible ? '#e0e0e0' : '#f0f0f0';
        });

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

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

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

    // Initial processing
    processPosts();
    addToggleButton();

    // 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') || node.classList.contains('innerOP')) {
                            addModShortcuts(node);
                            // Add toggle button if OP is added dynamically
                            if (node.classList.contains('innerOP')) {
                                addToggleButton();
                            }
                        }
                        // Check for posts within the added node
                        node.querySelectorAll('.innerPost, .innerOP').forEach(post => {
                            addModShortcuts(post);
                            // Add toggle button if OP is found
                            if (post.classList.contains('innerOP')) {
                                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');
    }
})();