// ==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. // @downloadURL none // ==/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'); } })();