网页元素屏蔽器 (长按屏蔽)

在手机上长按一个元素,会弹出一个菜单来屏蔽它。可以记住你的选择。

// ==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);
})();