Website Element Blocker (Long-press to block)

Long-press on an element on your phone to bring up a menu to block it. Remembers your choices.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Website Element Blocker (Long-press to block)
// @name:zh-CN  网页元素屏蔽器 (长按屏蔽)
// @namespace   http://tampermonkey.net/
// @version     1.6
// @description Long-press on an element on your phone to bring up a menu to block it. Remembers your choices.
// @description:zh-CN 在手机上长按一个元素,会弹出一个菜单来屏蔽它。可以记住你的选择。
// @author      Your AI Assistant
// @match       *://*/*
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand
// @grant       GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 ---
    const LONG_PRESS_DURATION = 500; // 长按时长 (毫秒)
    const STORAGE_KEY_PREFIX = 'element_blocker_';

    let pressTimer = null;
    let longPressFired = false;
    let targetElement = null;
    let highlightElements = [];

    // --- 样式注入 ---
    GM_addStyle(`
        .blocker-highlight-wrapper {
            position: relative !important;
            z-index: 2147483646 !important;
        }
        
        .blocker-highlight-border {
            position: absolute;
            background: linear-gradient(90deg, 
                rgba(255, 0, 0, 0.8) 0%, 
                rgba(255, 127, 0, 0.8) 25%, 
                rgba(255, 255, 0, 0.8) 50%, 
                rgba(0, 255, 0, 0.8) 75%, 
                rgba(0, 0, 255, 0.8) 100%);
            animation: blocker-rainbow 2s linear infinite;
            z-index: 2147483645;
            pointer-events: none;
        }
        
        .blocker-highlight-top {
            top: -4px;
            left: 0;
            right: 0;
            height: 4px;
        }
        
        .blocker-highlight-right {
            top: 0;
            right: -4px;
            bottom: 0;
            width: 4px;
        }
        
        .blocker-highlight-bottom {
            bottom: -4px;
            left: 0;
            right: 0;
            height: 4px;
        }
        
        .blocker-highlight-left {
            top: 0;
            left: -4px;
            bottom: 0;
            width: 4px;
        }
        
        @keyframes blocker-rainbow {
            0% { background-position: 0% 50%; }
            100% { background-position: 100% 50%; }
        }

        #blocker-menu {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: #333;
            color: white;
            padding: 10px;
            border-radius: 8px;
            z-index: 2147483647 !important;
            display: flex;
            flex-direction: column;
            gap: 8px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.4);
            font-family: sans-serif;
            font-size: 16px;
        }
        #blocker-menu button {
            background-color: #555;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 5px;
            cursor: pointer;
            text-align: center;
        }
        #blocker-menu button:hover {
            background-color: #777;
        }
    `);

    // --- 核心功能 ---
    function getCssSelector(el) {
        if (!(el instanceof Element)) return;
        const path = [];
        while (el.nodeType === Node.ELEMENT_NODE) {
            let selector = el.nodeName.toLowerCase();
            if (el.id) {
                selector += `#${el.id}`;
                path.unshift(selector);
                break;
            } else {
                let sib = el, nth = 1;
                while (sib.previousElementSibling) {
                    sib = sib.previousElementSibling;
                    if (sib.nodeName.toLowerCase() == selector) nth++;
                }
                if (nth != 1) selector += `:nth-of-type(${nth})`;
            }
            path.unshift(selector);
            el = el.parentNode;
        }
        return path.join(' > ');
    }

    function createHighlightBorder(className) {
        const border = document.createElement('div');
        border.className = `blocker-highlight-border ${className}`;
        return border;
    }

    function highlightElement(el) {
        unhighlightAll();
        if (!el) return;
        
        // 创建一个包装元素来包含边框
        const wrapper = document.createElement('div');
        wrapper.className = 'blocker-highlight-wrapper';
        
        // 保存原始元素的样式和位置
        const originalStyle = el.getAttribute('style') || '';
        const originalPosition = window.getComputedStyle(el).position;
        
        // 确保元素有相对定位以便边框正确放置
        if (originalPosition === 'static') {
            el.style.position = 'relative';
        }
        
        // 添加四个边框
        wrapper.appendChild(createHighlightBorder('blocker-highlight-top'));
        wrapper.appendChild(createHighlightBorder('blocker-highlight-right'));
        wrapper.appendChild(createHighlightBorder('blocker-highlight-bottom'));
        wrapper.appendChild(createHighlightBorder('blocker-highlight-left'));
        
        // 用包装元素包裹原始元素
        el.parentNode.insertBefore(wrapper, el);
        wrapper.appendChild(el);
        
        // 保存引用以便之后移除
        highlightElements.push({
            wrapper: wrapper,
            originalStyle: originalStyle,
            originalPosition: originalPosition,
            element: el
        });
    }

    function unhighlightAll() {
        highlightElements.forEach(item => {
            // 恢复原始元素的样式和位置
            if (item.originalPosition === 'static') {
                item.element.style.position = '';
            }
            item.element.setAttribute('style', item.originalStyle);
            
            // 将元素放回原处并移除包装
            item.wrapper.parentNode.insertBefore(item.element, item.wrapper);
            item.wrapper.remove();
        });
        
        highlightElements = [];
    }

    function showBlockMenu(el) {
        // 移除旧菜单
        const oldMenu = document.getElementById('blocker-menu');
        if (oldMenu) oldMenu.remove();

        const menu = document.createElement('div');
        menu.id = 'blocker-menu';

        let currentEl = el;
        let level = 0;

        const createButton = (elem, text) => {
            const button = document.createElement('button');
            button.textContent = text;
            button.onclick = (e) => {
                e.stopPropagation();
                blockElement(elem);
                hideBlockMenu();
            };
            button.onmouseover = () => highlightElement(elem);
            button.onmouseout = () => highlightElement(targetElement);
            return button;
        };

        // 添加 "屏蔽当前" 按钮
        menu.appendChild(createButton(currentEl, `屏蔽当前 (${currentEl.tagName.toLowerCase()})`));

        // 添加 "屏蔽上层" 按钮 (最多5层)
        while (currentEl.parentElement && level < 5) {
            currentEl = currentEl.parentElement;
            if (currentEl.tagName.toLowerCase() === 'body' || currentEl.tagName.toLowerCase() === 'html') break;
            level++;
            menu.appendChild(createButton(currentEl, `屏蔽上${level}层 (${currentEl.tagName.toLowerCase()})`));
        }

        const cancelButton = document.createElement('button');
        cancelButton.textContent = '取消';
        cancelButton.style.backgroundColor = '#800';
        cancelButton.onclick = hideBlockMenu;
        menu.appendChild(cancelButton);

        document.body.appendChild(menu);
        highlightElement(el);
    }

    function hideBlockMenu() {
        const menu = document.getElementById('blocker-menu');
        if (menu) menu.remove();
        unhighlightAll();
    }

    function blockElement(el) {
        const selector = getCssSelector(el);
        if (!selector) return;

        const hostname = window.location.hostname;
        const key = STORAGE_KEY_PREFIX + hostname;
        const blockedSelectors = GM_getValue(key, []);

        if (!blockedSelectors.includes(selector)) {
            blockedSelectors.push(selector);
            GM_setValue(key, blockedSelectors);
            applyBlocking(hostname);
            console.log(`[Element Blocker] 已屏蔽: ${selector}`);
        }
    }

    function applyBlocking(hostname) {
        const key = STORAGE_KEY_PREFIX + hostname;
        const selectors = GM_getValue(key, []);
        if (selectors.length > 0) {
            let style = document.getElementById('dynamic-blocker-style');
            if (!style) {
                style = document.createElement('style');
                style.id = 'dynamic-blocker-style';
                document.head.appendChild(style);
            }
            style.textContent = `${selectors.join(', ')} { display: none !important; }`;
            console.log(`[Element Blocker] 已应用 ${selectors.length} 条屏蔽规则于 ${hostname}`);
        }
    }

    function clearBlockingRules() {
        const hostname = window.location.hostname;
        const key = STORAGE_KEY_PREFIX + hostname;
        GM_setValue(key, []);
        const style = document.getElementById('dynamic-blocker-style');
        if (style) style.textContent = '';
        alert(`已清除网站 [${hostname}] 的所有屏蔽规则。`);
    }

    // --- 事件监听 ---
    function onTouchStart(e) {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
            return;
        }

        longPressFired = false;
        targetElement = e.target;
        pressTimer = window.setTimeout(() => {
            longPressFired = true;
            e.preventDefault();
            showBlockMenu(targetElement);
        }, LONG_PRESS_DURATION);
    }

    function onTouchEnd(e) {
        clearTimeout(pressTimer);
    }

    function onTouchMove(e) {
        clearTimeout(pressTimer);
    }

    // 绑定事件
    window.addEventListener('touchstart', onTouchStart, { passive: false });
    window.addEventListener('touchend', onTouchEnd);
    window.addEventListener('touchmove', onTouchMove);

    // --- 页面加载时应用规则 & 注册菜单 ---
    applyBlocking(window.location.hostname);
    GM_registerMenuCommand('清除当前网站的屏蔽规则', clearBlockingRules);
})();