8chan.moe Delete/Trash No Refresh

Prevents page refresh when clicking Delete or Trash buttons on 8chan.moe moderation pages and handles deletions via AJAX

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         8chan.moe Delete/Trash No Refresh
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Prevents page refresh when clicking Delete or Trash buttons on 8chan.moe moderation pages and handles deletions via AJAX
// @author       Anonymous
// @match        https://8chan.moe/*/res/*.html*
// @match        https://8chan.moe/mod.js?*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('8chan.moe Delete/Trash No Refresh script v2.1 loaded');

    // Function to handle button clicks and send AJAX request
    function handleButtonClick(event) {
        event.preventDefault(); // Prevent default button behavior
        event.stopPropagation(); // Stop event bubbling
        event.stopImmediatePropagation(); // Stop other handlers

        const button = event.target;
        console.log(`Clicked button: ${button.id} with value: ${button.value}`);

        // Try to find the form
        let form = button.closest('form');
        if (!form) {
            // Fallback: search for a form with deletion checkboxes
            form = document.querySelector('form input[name*="-"][name*="-"]:checked')?.closest('form') || document.querySelector('form');
            if (!form) {
                console.error('No form found in the document');
                alert('Error: No form found. Please report this issue.');
                return;
            }
        }
        console.log(`Form found: ${form.outerHTML}`);

        // Prevent form submission
        form.addEventListener('submit', (e) => {
            e.preventDefault();
            e.stopPropagation();
            console.log('Form submission prevented for form:', form);
        }, { capture: true, once: true });

        // Disable button to prevent multiple clicks
        button.disabled = true;
        setTimeout(() => { button.disabled = false; }, 500); // Re-enable after 500ms

        // Collect form data
        const formData = new FormData();
        formData.append('action', button.value); // Add the button's value (delete or trash)

        // Add checked post checkboxes (e.g., a-9-12)
        const checkedCheckboxes = form.querySelectorAll('input[name*="-"][name*="-"]:checked');
        if (checkedCheckboxes.length === 0) {
            console.error('No checked checkboxes found');
            alert('Error: No posts selected. Please check at least one post.');
            return;
        }
        checkedCheckboxes.forEach(checkbox => {
            formData.append(checkbox.name, 'true');
            console.log(`Added post checkbox: ${checkbox.name}=true`);
        });

        // Log form data for debugging
        for (const [key, value] of formData.entries()) {
            console.log(`FormData: ${key}=${value}`);
        }

        // Log form details
        console.log(`Form action attribute: ${form.getAttribute('action') || 'undefined'}`);
        console.log(`Form method: ${form.getAttribute('method') || 'undefined'}`);

        // Use the correct deletion endpoint
        const actionUrl = 'https://8chan.moe/contentActions.js?json=1';
        console.log(`Final action URL: ${actionUrl}`);

        // Send AJAX request
        fetch(actionUrl, {
            method: 'POST',
            body: formData, // Use multipart/form-data
            credentials: 'same-origin', // Include cookies for authentication
            headers: {
                'Accept': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
                // Omit Content-Type to let browser set multipart/form-data with boundary
            }
        })
        .then(response => {
            console.log(`Response status: ${response.status}, ok: ${response.ok}`);
            return response.json().catch(() => response.text()).then(data => ({ status: response.status, ok: response.ok, data }));
        })
        .then(({ status, ok, data }) => {
            if (ok) {
                console.log(`${button.value} action successful`, data);
                // Provide visual feedback for each checked post
                checkedCheckboxes.forEach(checkbox => {
                    const postElement = checkbox.closest('.post') || checkbox.closest('.post-container') || checkbox.closest('article');
                    if (postElement) {
                        postElement.style.opacity = '0.5'; // Visual indication
                        postElement.style.pointerEvents = 'none'; // Disable interactions
                    }
                });
            } else {
                throw new Error(`Failed to ${button.value}: ${status} ${JSON.stringify(data)}`);
            }
        })
        .catch(error => {
            console.error('Error:', error);
            alert(`Error performing ${button.value} action: ${error.message}`);
            // Revert visual changes on failure
            checkedCheckboxes.forEach(checkbox => {
                const postElement = checkbox.closest('.post') || checkbox.closest('.post-container') || checkbox.closest('article');
                if (postElement) {
                    postElement.style.opacity = '';
                    postElement.style.pointerEvents = '';
                }
            });
        });
    }

    // Add event listeners to Delete and Trash buttons
    document.addEventListener('click', function(event) {
        if (event.target.matches('#deleteFormButton') || event.target.matches('#trashFormButton')) {
            console.log('Button click detected:', event.target.id);
            handleButtonClick(event);
        }
    }, { capture: true });

    // Prevent form submission for all forms with delete/trash buttons or checkboxes
    document.querySelectorAll('form').forEach(form => {
        if (form.querySelector('#deleteFormButton') || form.querySelector('#trashFormButton') || form.querySelector('input[name*="-"][name*="-"]')) {
            form.addEventListener('submit', (e) => {
                e.preventDefault();
                e.stopPropagation();
                console.log('Form submission blocked for form:', form);
            }, { capture: true });
        }
    });

    // Global submit prevention for safety
    document.addEventListener('submit', (e) => {
        if (e.target.querySelector('#deleteFormButton') || e.target.querySelector('#trashFormButton') || e.target.querySelector('input[name*="-"][name*="-"]')) {
            e.preventDefault();
            console.log('Global form submission blocked');
        }
    }, { capture: true });

})();