// ==UserScript== // @name 移动端优化广告标记器(增强版) // @namespace http://tampermonkey.net/ // @version 14.3 // @description 专为手机优化的广告标记工具,支持拖拽菜单、长按标记和二级目录菜单,预览时可临时拦截 // @author 优化助手 // @match *://*/* // @icon https://img.icons8.com/color/96/000000/ad-block.png // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_notification // @grant GM_deleteValue // @grant GM_listValues // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 检查是否在Tampermonkey环境中 if (typeof GM_registerMenuCommand === 'undefined') { console.error('Tampermonkey API不可用,请确保脚本在Tampermonkey中运行'); showBrowserAlert(); return; } function showBrowserAlert() { const alertDiv = document.createElement('div'); alertDiv.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #ff3b30; color: white; padding: 15px 20px; border-radius: 10px; z-index: 999999; box-shadow: 0 4px 12px rgba(0,0,0,0.15); font-family: Arial, sans-serif; text-align: center; max-width: 90vw; `; alertDiv.innerHTML = ` ⚠️ 脚本运行环境错误
请确保在Tampermonkey中运行此脚本
油猴菜单功能需要Tampermonkey扩展 `; document.body.appendChild(alertDiv); setTimeout(() => alertDiv.remove(), 5000); } // 配置 const CONFIG = { colors: { marker: '#ff3b30', preview: '#34c759', block: '#ff9500', background: 'rgba(255, 255, 255, 0.95)', card: '#f2f2f7', text: '#1d1d1f', textSecondary: '#8e8e93', border: '#c7c7cc', success: '#34c759', warning: '#ffcc00', error: '#ff3b30', info: '#007aff' }, sizes: { menuWidth: '75vw', menuMinWidth: 220, menuMaxWidth: 400, menuHeight: '50vh', menuMinHeight: 200, menuMaxHeight: 500, compactHeight: '35vh', buttonHeight: 40, touchSize: 40, fontSize: 13, iconSize: 18, borderRadius: 10 }, behavior: { autoBlock: true, swipeToClose: true, vibration: true, doubleTapDelay: 300, longPressDelay: 800, previewDuration: 2000, defaultCompactMode: true, autoHideMenu: true, hideDelay: 2000, immediateBlock: true, draggableMenu: true, rememberMenuPosition: true, resizableMenu: true }, gestures: { swipeThreshold: 50, velocityThreshold: 0.3, dragThreshold: 5, resizeThreshold: 10 } }; // 状态管理 const STATE = { isMarkerMode: false, isBlockingEnabled: true, isPreviewMode: false, isMenuVisible: false, isCompactMode: CONFIG.behavior.defaultCompactMode, menuAutoHideTimer: null, isTemporaryBlocking: false, temporaryBlockedElements: [], settings: { showToast: true, enableVibration: true, enableDoubleTap: true, enableLongPress: true, autoExpandMenu: false, highlightColor: '#ff3b30', darkMode: false, draggableMenu: true, resizableMenu: true, menuPosition: { x: 50, y: 50 }, menuSize: { width: 300, height: 350 }, rememberMenuPosition: true, showMarkerHint: true, vibrationStrength: 'medium' }, adRules: [], blockedElements: [], currentDomain: window.location.hostname, currentSelector: '', statistics: { totalBlocked: 0, todayBlocked: 0, rulesCount: 0, domainsCount: 0, lastUpdate: null }, elements: { menu: null, floatingBtn: null, controlPanel: null, settingsPanel: null }, touchState: { startX: 0, startY: 0, startTime: 0, isSwiping: false, isDraggingMenu: false, isResizingMenu: false, lastTapTime: 0, longPressTimer: null, longPressTarget: null, longPressActive: false, dragStartX: 0, dragStartY: 0, menuStartX: 0, menuStartY: 0, resizeStartX: 0, resizeStartY: 0, menuStartWidth: 0, menuStartHeight: 0 }, menuCommands: [], expandedDomains: new Set() }; // ==================== 移动端优化的CSS ==================== GM_addStyle(` /* 重置和基础样式 */ #marker-mobile-menu, #marker-floating-btn, .marker-indicator { all: initial; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; box-sizing: border-box; } /* 悬浮按钮 - 修复显示逻辑 */ #marker-floating-btn { position: fixed; bottom: 80px; right: 20px; width: 50px; height: 50px; background: ${CONFIG.colors.marker}; color: white; border: none; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 22px; cursor: pointer; z-index: 999998; box-shadow: 0 4px 20px rgba(0,0,0,0.2); opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -webkit-tap-highlight-color: transparent; touch-action: manipulation; user-select: none; pointer-events: none; } #marker-floating-btn.visible { opacity: 1; transform: scale(1); pointer-events: auto; } #marker-floating-btn:active { transform: scale(0.95); } /* 标记模式指示器 */ .marker-indicator { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: linear-gradient(135deg, ${CONFIG.colors.marker}, #ff6b6b); color: white; padding: 10px 20px; border-radius: 25px; font-size: 14px; font-weight: 600; z-index: 999997; box-shadow: 0 4px 20px rgba(0,0,0,0.15); opacity: 0; transition: opacity 0.3s; white-space: nowrap; pointer-events: none; } .marker-indicator.visible { opacity: 1; } /* 移动端菜单 - 可拖动和调整大小 */ #marker-mobile-menu { position: fixed; min-width: ${CONFIG.sizes.menuMinWidth}px; min-height: ${CONFIG.sizes.menuMinHeight}px; max-width: 90vw; max-height: 90vh; background: ${CONFIG.colors.background}; backdrop-filter: blur(20px); border-radius: ${CONFIG.sizes.borderRadius}px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); z-index: 999999; opacity: 0; visibility: hidden; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); transform: scale(0.9); border: 1px solid rgba(255,255,255,0.1); user-select: none; cursor: default; resize: none; } #marker-mobile-menu.visible { opacity: 1; visibility: visible; transform: scale(1); } #marker-mobile-menu.compact { height: ${CONFIG.sizes.compactHeight}; max-height: ${CONFIG.sizes.menuMaxHeight}px; } #marker-mobile-menu.expanded { height: ${CONFIG.sizes.menuHeight}; max-height: ${CONFIG.sizes.menuMaxHeight}px; } #marker-mobile-menu.marker-mode-active { border: 2px solid ${CONFIG.colors.marker}; } #marker-mobile-menu.draggable .menu-header { cursor: move; user-select: none; } /* 菜单头部的拖动区域 */ .menu-drag-area { position: absolute; top: 0; left: 0; right: 0; height: 40px; cursor: move; z-index: 1000; } /* 关闭按钮样式增强 */ .menu-close-btn { width: 30px; height: 30px; border: none; border-radius: 8px; background: rgba(255, 59, 48, 0.1) !important; color: ${CONFIG.colors.error} !important; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; -webkit-tap-highlight-color: transparent; transition: all 0.2s; user-select: none; position: relative; z-index: 1001; } .menu-close-btn:active { background: rgba(255, 59, 48, 0.2) !important; transform: scale(0.9) !important; } /* 确保按钮在暗色模式下也可见 */ .dark-mode .menu-close-btn { background: rgba(255, 59, 48, 0.2) !important; color: #ff7b7b !important; } /* 菜单头部的拖动提示 */ .drag-handle { display: inline-block; margin-left: 8px; font-size: 14px; opacity: 0.6; cursor: move; } /* 调整大小手柄 */ .resize-handle { position: absolute; width: 20px; height: 20px; bottom: 0; right: 0; cursor: nwse-resize; z-index: 1000; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.1); border-top-left-radius: 8px; color: ${CONFIG.colors.textSecondary}; font-size: 12px; } .resize-handle:hover { background: rgba(0, 0, 0, 0.2); } /* 暗色模式 */ #marker-mobile-menu.dark-mode { background: rgba(0, 0, 0, 0.95); color: #ffffff; border: 1px solid rgba(255,255,255,0.1); } .dark-mode { color: #ffffff !important; } .dark-mode .menu-header { background: rgba(30, 30, 30, 0.9) !important; color: #ffffff !important; } .dark-mode .card { background: rgba(40, 40, 40, 0.9) !important; border: 1px solid rgba(255,255,255,0.1) !important; color: #ffffff !important; } .dark-mode .quick-action-btn { background: rgba(60, 60, 60, 0.9) !important; color: #ffffff !important; } /* 菜单容器 */ .menu-container { width: 100%; height: 100%; display: flex; flex-direction: column; } /* 菜单头部 */ .menu-header { padding: 16px; background: rgba(255, 255, 255, 0.9); border-bottom: 1px solid ${CONFIG.colors.border}; flex-shrink: 0; position: relative; } .menu-title { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: ${CONFIG.colors.text}; } .menu-controls { position: absolute; top: 16px; right: 16px; display: flex; gap: 8px; z-index: 1002; } .icon-btn { width: 30px; height: 30px; border: none; border-radius: 8px; background: ${CONFIG.colors.card}; color: ${CONFIG.colors.text}; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; -webkit-tap-highlight-color: transparent; transition: all 0.2s; user-select: none; } .icon-btn:active { transform: scale(0.95); background: ${CONFIG.colors.border}; } /* 菜单内容 */ .menu-content { flex: 1; overflow-y: auto; padding: 16px; padding-bottom: 60px; } /* 快速操作区域 */ .quick-actions { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 16px; } .quick-action-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60px; border: none; border-radius: ${CONFIG.sizes.borderRadius}px; background: ${CONFIG.colors.card}; color: ${CONFIG.colors.text}; font-size: 12px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: all 0.2s; padding: 8px 4px; user-select: none; } .quick-action-btn:active { transform: scale(0.95); background: ${CONFIG.colors.border}; } .quick-action-btn span:first-child { font-size: 20px; margin-bottom: 4px; } /* 卡片 */ .card { background: ${CONFIG.colors.card}; border-radius: ${CONFIG.sizes.borderRadius}px; padding: 16px; margin-bottom: 16px; border: 1px solid ${CONFIG.colors.border}; } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .card-title { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; color: ${CONFIG.colors.text}; } /* 元素信息 - 修复样式 */ .element-info { font-size: 12px; } .element-line { display: flex; justify-content: space-between; margin-bottom: 6px; } .element-label { color: ${CONFIG.colors.textSecondary}; min-width: 50px; flex-shrink: 0; } .element-value { color: ${CONFIG.colors.text}; font-weight: 500; text-align: right; flex: 1; word-break: break-all; padding-left: 10px; } .selector-display { margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.05); border-radius: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 11px; word-break: break-all; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: all 0.2s; max-height: 100px; overflow-y: auto; } .selector-display:active { background: rgba(0,0,0,0.1); } /* 按钮网格 */ .button-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 12px; } .primary-btn { height: ${CONFIG.sizes.buttonHeight}px; border: none; border-radius: ${CONFIG.sizes.borderRadius}px; background: ${CONFIG.colors.info}; color: white; font-size: 13px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; -webkit-tap-highlight-color: transparent; transition: all 0.2s; user-select: none; } .primary-btn:active { transform: scale(0.98); opacity: 0.9; } .primary-btn.success { background: ${CONFIG.colors.success}; } .primary-btn.warning { background: ${CONFIG.colors.warning}; } .primary-btn.danger { background: ${CONFIG.colors.error}; } /* 规则列表 */ .rules-list { max-height: 200px; overflow-y: auto; } .rule-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: white; border-radius: 8px; margin-bottom: 8px; border: 1px solid ${CONFIG.colors.border}; } .rule-selector { font-size: 11px; color: ${CONFIG.colors.text}; word-break: break-all; flex: 1; } .action-btn { width: 28px; height: 28px; border: none; border-radius: 6px; background: transparent; color: ${CONFIG.colors.textSecondary}; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; -webkit-tap-highlight-color: transparent; } .action-btn.delete:hover { color: ${CONFIG.colors.error}; } /* 菜单底部 */ .menu-footer { position: absolute; bottom: 0; left: 0; right: 0; padding: 12px 16px; background: rgba(255, 255, 255, 0.9); border-top: 1px solid ${CONFIG.colors.border}; display: flex; justify-content: space-between; align-items: center; } .status-indicators { display: flex; gap: 12px; } .status-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: ${CONFIG.colors.textSecondary}; cursor: pointer; -webkit-tap-highlight-color: transparent; padding: 4px 6px; border-radius: 6px; transition: background 0.2s; } .status-item:active { background: rgba(0,0,0,0.05); } .status-dot { width: 8px; height: 8px; border-radius: 50%; } .status-dot.active { background: ${CONFIG.colors.success}; } .status-dot.inactive { background: ${CONFIG.colors.border}; } /* 大小切换按钮 */ .size-toggle { position: absolute; bottom: -12px; left: 50%; transform: translateX(-50%); width: 40px; height: 24px; background: ${CONFIG.colors.card}; border: 1px solid ${CONFIG.colors.border}; border-radius: 12px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; z-index: 1; -webkit-tap-highlight-color: transparent; transition: all 0.2s; } .size-toggle:active { background: ${CONFIG.colors.border}; } /* 控制面板样式 */ .control-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90vw; max-width: 400px; max-height: 80vh; background: ${CONFIG.colors.background}; backdrop-filter: blur(20px); border-radius: ${CONFIG.sizes.borderRadius}px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); z-index: 1000000; display: flex; flex-direction: column; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); } .control-header { padding: 16px; background: rgba(255, 255, 255, 0.9); border-bottom: 1px solid ${CONFIG.colors.border}; display: flex; justify-content: space-between; align-items: center; } .control-title { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: ${CONFIG.colors.text}; } .control-content { flex: 1; overflow-y: auto; padding: 16px; } /* 控制面板的三个部分 */ .control-section { margin-bottom: 20px; } .section-header { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 600; color: ${CONFIG.colors.text}; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid ${CONFIG.colors.border}; } .stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px; } .stat-item { background: ${CONFIG.colors.card}; border-radius: ${CONFIG.sizes.borderRadius}px; padding: 12px; text-align: center; border: 1px solid ${CONFIG.colors.border}; } .stat-value { font-size: 20px; font-weight: 700; color: ${CONFIG.colors.info}; margin-bottom: 4px; } .stat-label { font-size: 12px; color: ${CONFIG.colors.textSecondary}; } .tools-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } .tool-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 70px; border: none; border-radius: ${CONFIG.sizes.borderRadius}px; background: ${CONFIG.colors.card}; color: ${CONFIG.colors.text}; font-size: 12px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: all 0.2s; padding: 8px 4px; } .tool-btn:active { transform: scale(0.95); background: ${CONFIG.colors.border}; } .tool-btn span:first-child { font-size: 20px; margin-bottom: 4px; } /* 规则管理区域 */ .rules-management { max-height: 200px; overflow-y: auto; border: 1px solid ${CONFIG.colors.border}; border-radius: ${CONFIG.sizes.borderRadius}px; padding: 10px; background: ${CONFIG.colors.card}; } .rule-manage-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid rgba(0,0,0,0.1); } .rule-manage-item:last-child { border-bottom: none; } .rule-info { flex: 1; } .rule-domain { font-size: 11px; color: ${CONFIG.colors.textSecondary}; margin-bottom: 2px; } .rule-selector-short { font-size: 10px; color: ${CONFIG.colors.text}; word-break: break-all; } /* 拦截目录样式 */ .directory-section { max-height: 200px; overflow-y: auto; border: 1px solid ${CONFIG.colors.border}; border-radius: ${CONFIG.sizes.borderRadius}px; padding: 10px; background: ${CONFIG.colors.card}; margin-top: 10px; } .directory-item { padding: 8px; border-bottom: 1px solid rgba(0,0,0,0.1); } .directory-item:last-child { border-bottom: none; } .domain-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 4px 0; } .domain-info { flex: 1; } .domain-name { font-size: 12px; font-weight: 500; color: ${CONFIG.colors.text}; margin-bottom: 2px; } .rule-count { font-size: 10px; color: ${CONFIG.colors.textSecondary}; } .expand-toggle { width: 24px; height: 24px; border: none; background: transparent; color: ${CONFIG.colors.textSecondary}; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: transform 0.2s; border-radius: 4px; } .expand-toggle:hover { background: rgba(0,0,0,0.05); } .domain-expanded .expand-toggle { transform: rotate(180deg); } .domain-rules { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } .domain-expanded .domain-rules { max-height: 300px; overflow-y: auto; margin-top: 8px; padding: 8px; background: rgba(0,0,0,0.03); border-radius: 8px; border: 1px solid rgba(0,0,0,0.05); } .domain-rule-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; border-bottom: 1px solid rgba(0,0,0,0.05); background: rgba(255,255,255,0.8); border-radius: 6px; margin-bottom: 4px; } .domain-rule-item:last-child { border-bottom: none; margin-bottom: 0; } .domain-rule-info { flex: 1; margin-right: 8px; } .domain-rule-selector { font-size: 9px; color: ${CONFIG.colors.text}; word-break: break-all; line-height: 1.3; } .domain-rule-match { font-size: 8px; color: ${CONFIG.colors.textSecondary}; margin-top: 2px; } .domain-rule-actions { display: flex; gap: 4px; } .domain-rule-delete { width: 22px; height: 22px; border: none; border-radius: 4px; background: rgba(255, 59, 48, 0.1); color: ${CONFIG.colors.error}; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; -webkit-tap-highlight-color: transparent; } .domain-rule-delete:hover { background: rgba(255, 59, 48, 0.2); } /* 设置面板 */ .settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90vw; max-width: 400px; max-height: 80vh; background: ${CONFIG.colors.background}; backdrop-filter: blur(20px); border-radius: ${CONFIG.sizes.borderRadius}px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); z-index: 1000000; display: flex; flex-direction: column; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); } .settings-header { padding: 16px; background: rgba(255, 255, 255, 0.9); border-bottom: 1px solid ${CONFIG.colors.border}; } .settings-title { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: ${CONFIG.colors.text}; } .settings-content { flex: 1; overflow-y: auto; padding: 16px; } .settings-section { margin-bottom: 20px; } .section-title { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; color: ${CONFIG.colors.text}; margin-bottom: 12px; } .setting-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid rgba(0,0,0,0.1); } .setting-info { flex: 1; } .setting-label { font-size: 13px; font-weight: 500; color: ${CONFIG.colors.text}; margin-bottom: 2px; } .setting-description { font-size: 11px; color: ${CONFIG.colors.textSecondary}; } /* 开关样式 */ .switch { position: relative; display: inline-block; width: 44px; height: 24px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ${CONFIG.colors.border}; transition: .3s; border-radius: 24px; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; } input:checked + .slider { background-color: ${CONFIG.colors.info}; } input:checked + .slider:before { transform: translateX(20px); } /* 设置按钮 */ .settings-footer { padding: 16px; background: rgba(255, 255, 255, 0.9); border-top: 1px solid ${CONFIG.colors.border}; display: flex; gap: 12px; } .settings-btn { flex: 1; height: 40px; border: none; border-radius: ${CONFIG.sizes.borderRadius}px; font-size: 14px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; -webkit-tap-highlight-color: transparent; transition: all 0.2s; } .settings-btn.primary { background: ${CONFIG.colors.info}; color: white; } .settings-btn.secondary { background: ${CONFIG.colors.card}; color: ${CONFIG.colors.text}; border: 1px solid ${CONFIG.colors.border}; } .settings-btn:active { transform: scale(0.98); opacity: 0.9; } /* Toast提示 */ #marker-toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: ${CONFIG.colors.text}; color: white; padding: 12px 20px; border-radius: ${CONFIG.sizes.borderRadius}px; font-size: 13px; font-weight: 500; z-index: 1000001; box-shadow: 0 4px 20px rgba(0,0,0,0.15); opacity: 0; transition: opacity 0.3s; white-space: nowrap; pointer-events: none; } /* 元素高亮 */ .marker-highlight { outline: 3px solid ${CONFIG.colors.marker} !important; outline-offset: 2px !important; position: relative !important; z-index: 999990 !important; background-color: rgba(255, 59, 48, 0.1) !important; } .preview-highlight { outline: 3px dashed ${CONFIG.colors.preview} !important; outline-offset: 3px !important; position: relative !important; z-index: 999990 !important; background-color: rgba(52, 199, 89, 0.15) !important; box-shadow: 0 0 10px rgba(52, 199, 89, 0.3) !important; animation: preview-pulse 1s ease-in-out infinite alternate; } @keyframes preview-pulse { from { outline-width: 2px; box-shadow: 0 0 5px rgba(52, 199, 89, 0.3); } to { outline-width: 4px; box-shadow: 0 0 15px rgba(52, 199, 89, 0.6); } } /* 临时拦截样式 */ .temporary-blocked { opacity: 0.5 !important; filter: grayscale(80%) !important; display: block !important; } .element-blocked { display: none !important; } /* 当拦截功能关闭时的样式 */ .interception-disabled .permanent-blocked { display: block !important; opacity: 0.5 !important; filter: blur(1px) !important; transition: all 0.3s ease; } .interception-disabled .permanent-blocked:hover { opacity: 1 !important; filter: none !important; } /* 确保拦截状态正确显示 */ body.interception-disabled { position: relative; } body.interception-disabled::after { content: '拦截功能已暂停'; position: fixed; top: 10px; right: 10px; background: rgba(255, 59, 48, 0.9); color: white; padding: 5px 10px; border-radius: 5px; font-size: 12px; z-index: 999999; pointer-events: none; animation: fadeInOut 3s ease-in-out; } @keyframes fadeInOut { 0%, 100% { opacity: 0; } 10%, 90% { opacity: 1; } } /* 空状态 */ .empty-state { text-align: center; padding: 30px 20px; color: ${CONFIG.colors.textSecondary}; } .empty-icon { font-size: 32px; margin-bottom: 10px; opacity: 0.5; } /* 滚动条样式 */ ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 2px; } ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.3); } /* 长按标记指示器 */ .long-press-indicator { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: ${CONFIG.colors.marker}; color: white; padding: 8px 16px; border-radius: 20px; font-size: 12px; font-weight: 500; z-index: 999999; box-shadow: 0 4px 15px rgba(0,0,0,0.2); opacity: 0; transition: opacity 0.3s; pointer-events: none; } .long-press-indicator.visible { opacity: 1; } /* 防止系统长按菜单 */ body.marker-mode-active * { -webkit-touch-callout: none !important; -webkit-user-select: none !important; user-select: none !important; } /* 仅在标记模式时添加 */ body.marker-mode-active { -webkit-touch-callout: none !important; -webkit-user-select: none !important; user-select: none !important; } /* 长按标记指示器 - 新增样式 */ .long-press-hint { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 20px; font-size: 13px; font-weight: 500; z-index: 999999; box-shadow: 0 4px 15px rgba(0,0,0,0.2); opacity: 0; transition: opacity 0.3s; pointer-events: none; white-space: nowrap; } .long-press-hint.visible { opacity: 1; } /* 移除阻止页面滚动的样式 */ body.marker-mode-active { overflow: auto !important; -webkit-overflow-scrolling: touch !important; } /* 允许标记模式下页面滚动 */ body.marker-mode-active * { -webkit-touch-callout: default !important; -webkit-user-select: auto !important; user-select: auto !important; pointer-events: auto !important; } /* 迷你统计样式 */ .stats-mini { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } .stat-item-mini { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; background: rgba(0,0,0,0.03); border-radius: 6px; font-size: 11px; } .stat-label-mini { color: ${CONFIG.colors.textSecondary}; } .stat-value-mini { font-weight: 600; color: ${CONFIG.colors.info}; } .domain-rules-list { max-height: 150px; overflow-y: auto; } .domain-rule-item-mini { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; margin-bottom: 4px; background: rgba(0,0,0,0.03); border-radius: 6px; font-size: 10px; border: 1px solid rgba(0,0,0,0.05); cursor: pointer; -webkit-tap-highlight-color: transparent; } .domain-rule-item-mini:active { background: rgba(0,0,0,0.08); } .domain-rule-selector-mini { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 8px; color: ${CONFIG.colors.text}; } .domain-rule-actions-mini { display: flex; gap: 4px; } .domain-rule-delete-mini { width: 20px; height: 20px; border: none; border-radius: 4px; background: rgba(255, 59, 48, 0.1); color: ${CONFIG.colors.error}; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 10px; -webkit-tap-highlight-color: transparent; } .domain-rule-delete-mini:hover { background: rgba(255, 59, 48, 0.2); } .dark-mode .status-item:active { background: rgba(255,255,255,0.05); } /* 高级操作帮助 */ .advanced-help { font-size: 11px; color: ${CONFIG.colors.textSecondary}; padding: 8px; margin-bottom: 10px; background: rgba(0,0,0,0.05); border-radius: 6px; } .dark-mode .advanced-help { background: rgba(255,255,255,0.05); } /* 规则项状态 */ .rule-status { font-size: 9px; padding: 2px 4px; border-radius: 4px; margin-left: 4px; } .rule-status.valid { background: rgba(52, 199, 89, 0.1); color: ${CONFIG.colors.success}; } .rule-status.invalid { background: rgba(255, 59, 48, 0.1); color: ${CONFIG.colors.error}; } /* 预览时临时拦截样式 */ .preview-highlight.temporary-blocked { outline: 3px dashed ${CONFIG.colors.preview} !important; outline-offset: 3px !important; position: relative !important; z-index: 999990 !important; background-color: rgba(52, 199, 89, 0.15) !important; box-shadow: 0 0 10px rgba(52, 199, 89, 0.3) !important; animation: preview-pulse 1s ease-in-out infinite alternate; } `); // ==================== 核心功能 ==================== // 触觉反馈 - 支持强度调节 function vibrate(type = 'light') { if (STATE.settings.vibrationStrength === 'off' || !window.navigator.vibrate) return; const strengthMap = { 'light': 0.5, 'medium': 1, 'strong': 1.5 }; const strength = strengthMap[STATE.settings.vibrationStrength] || 1; const patterns = { light: [30 * strength], medium: [50 * strength], heavy: [100 * strength], success: [30 * strength, 50, 30 * strength], error: [50 * strength, 100, 50 * strength] }; window.navigator.vibrate(patterns[type] || [30 * strength]); } // 生成元素选择器 function generateSelector(element) { if (!element || !element.tagName) return ''; if (element.id && element.id.trim()) { const idSelector = `#${CSS.escape(element.id)}`; try { const matches = document.querySelectorAll(idSelector); if (matches.length === 1) { return idSelector; } } catch (e) {} } const path = []; let current = element; while (current && current !== document.body) { let selector = current.tagName.toLowerCase(); if (current.id && current.id.trim()) { selector += `#${CSS.escape(current.id)}`; path.unshift(selector); break; } if (current.className && typeof current.className === 'string') { const classes = current.className.trim().split(/\s+/) .filter(c => c.length > 0 && c.length < 30) .filter(c => !c.includes(':') && !c.includes('hover') && !c.includes('active')); if (classes.length > 0) { const classPart = classes.map(c => `.${CSS.escape(c)}`).join(''); selector += classPart; } } const attrs = ['name', 'type', 'alt', 'title', 'href', 'src', 'data-id', 'data-ad', 'data-role', 'data-testid']; for (const attr of attrs) { const value = current.getAttribute(attr); if (value && value.trim() && value.length < 50) { selector += `[${attr}="${CSS.escape(value.trim())}"]`; break; } } let siblingIndex = 1; let sibling = current.previousElementSibling; while (sibling) { if (sibling.tagName === current.tagName) { siblingIndex++; } sibling = sibling.previousElementSibling; } if (siblingIndex > 1) { selector += `:nth-of-type(${siblingIndex})`; } path.unshift(selector); current = current.parentElement; } const fullSelector = path.join(' > '); try { const matches = document.querySelectorAll(fullSelector); if (matches.length > 0) { for (let i = 1; i < Math.min(3, path.length); i++) { const testSelector = path.slice(-i).join(' > '); const testMatches = document.querySelectorAll(testSelector); if (testMatches.length === 1) { return testSelector; } } return fullSelector; } } catch (e) { console.error('选择器生成错误:', e); } let fallbackSelector = element.tagName.toLowerCase(); if (element.className && typeof element.className === 'string') { const classes = element.className.trim().split(/\s+/).filter(c => c.length > 0); if (classes.length > 0) { fallbackSelector += `.${CSS.escape(classes[0])}`; } } return fallbackSelector; } // 测试选择器是否有效 function testSelector(selector) { if (!selector || selector.trim() === '') { return { valid: false, error: '选择器为空', count: 0, elements: [] }; } try { const elements = document.querySelectorAll(selector); const elementArray = Array.from(elements); const visibleElements = elementArray.filter(el => { try { const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; } catch (e) { return true; } }); return { valid: true, count: visibleElements.length, elements: visibleElements, allElements: elementArray }; } catch (e) { console.error('选择器测试失败:', e); return { valid: false, error: e.message, count: 0, elements: [] }; } } // 加载广告规则 function loadAdRules() { try { const saved = GM_getValue('adBlockRules', '[]'); STATE.adRules = JSON.parse(saved); console.log(`加载了 ${STATE.adRules.length} 条规则`); updateStatistics(); } catch (e) { console.error('加载规则失败:', e); STATE.adRules = []; } } // 保存广告规则 function saveAdRules() { try { GM_setValue('adBlockRules', JSON.stringify(STATE.adRules)); console.log('规则已保存'); updateStatistics(); } catch (e) { console.error('保存规则失败:', e); } } // 加载设置 function loadSettings() { try { const saved = GM_getValue('adMarkerSettings', '{}'); const settings = JSON.parse(saved); STATE.settings = { ...STATE.settings, ...settings }; console.log('设置已加载:', STATE.settings); } catch (e) { console.error('加载设置失败:', e); } } // 保存设置 function saveSettings() { try { GM_setValue('adMarkerSettings', JSON.stringify(STATE.settings)); console.log('设置已保存:', STATE.settings); if (STATE.elements.menu) { if (STATE.settings.darkMode) { STATE.elements.menu.classList.add('dark-mode'); } else { STATE.elements.menu.classList.remove('dark-mode'); } } if (STATE.settings.highlightColor && STATE.settings.highlightColor !== CONFIG.colors.marker) { updateHighlightColor(STATE.settings.highlightColor); } return true; } catch (e) { console.error('保存设置失败:', e); return false; } } // 更新高亮颜色 function updateHighlightColor(color) { const styleId = 'marker-highlight-color'; let style = document.getElementById(styleId); if (!style) { style = document.createElement('style'); style.id = styleId; document.head.appendChild(style); } style.textContent = ` .marker-highlight { outline-color: ${color} !important; background-color: ${color}20 !important; } .marker-indicator { background: linear-gradient(135deg, ${color}, ${color}cc) !important; } #marker-floating-btn { background: ${color} !important; } .primary-btn { background: ${color} !important; } `; } // 更新统计数据 function updateStatistics() { const now = new Date(); const today = now.toDateString(); STATE.statistics.rulesCount = STATE.adRules.length; const domains = new Set(); STATE.adRules.forEach(rule => { domains.add(rule.domain || '全局'); }); STATE.statistics.domainsCount = domains.size; const currentDomainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ).length; const blockedCount = document.querySelectorAll('.element-blocked.permanent-blocked').length; try { const stats = GM_getValue('adMarkerStatistics', '{}'); const savedStats = JSON.parse(stats); STATE.statistics.totalBlocked = savedStats.totalBlocked || 0; STATE.statistics.todayBlocked = savedStats.todayBlocked || 0; STATE.statistics.lastUpdate = savedStats.lastUpdate || null; if (STATE.statistics.lastUpdate !== today) { STATE.statistics.todayBlocked = 0; } STATE.statistics.totalBlocked += blockedCount; STATE.statistics.todayBlocked += blockedCount; } catch (e) { STATE.statistics.totalBlocked = blockedCount; STATE.statistics.todayBlocked = blockedCount; } STATE.statistics.lastUpdate = today; GM_setValue('adMarkerStatistics', JSON.stringify(STATE.statistics)); return { totalBlocked: STATE.statistics.totalBlocked, todayBlocked: STATE.statistics.todayBlocked, rulesCount: STATE.statistics.rulesCount, currentDomainRules: currentDomainRules, domainsCount: STATE.statistics.domainsCount, blockedCount: blockedCount }; } // 拦截元素 function blockElements() { if (!STATE.isBlockingEnabled && !STATE.isTemporaryBlocking) { console.log('拦截功能已禁用,跳过永久拦截'); return 0; } STATE.blockedElements = []; const domainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ); if (domainRules.length === 0) { console.log('当前域名没有永久规则'); return 0; } let blockedCount = 0; domainRules.forEach((rule, index) => { try { const elements = document.querySelectorAll(rule.selector); elements.forEach((element, elementIndex) => { // 只有在拦截功能开启时才应用永久拦截 if (STATE.isBlockingEnabled) { if (!element.classList.contains('permanent-blocked')) { element.classList.add('element-blocked'); element.classList.add('permanent-blocked'); blockedCount++; STATE.blockedElements.push({ element: element, ruleIndex: index, selector: rule.selector }); } } else if (element.classList.contains('permanent-blocked')) { // 如果拦截功能关闭,移除永久拦截 element.classList.remove('permanent-blocked'); element.classList.remove('element-blocked'); } }); } catch (e) { console.error(`应用规则 ${rule.selector} 时出错:`, e); } }); updateBlockedCount(); updateStatistics(); console.log(`本次拦截完成,共拦截了 ${blockedCount} 个元素`); setTimeout(() => { updateMiniStats(); }, 100); return blockedCount; } // 移除拦截 function unblockElements() { const blockedElements = document.querySelectorAll('.element-blocked'); blockedElements.forEach(element => { // 只移除永久拦截的 if (element.classList.contains('permanent-blocked')) { element.classList.remove('permanent-blocked'); element.classList.remove('element-blocked'); } }); STATE.blockedElements = []; updateBlockedCount(); console.log(`已移除永久拦截,临时拦截仍然有效`); return blockedElements.length; } // 强制重新拦截所有规则 function forceReBlock() { document.querySelectorAll('.element-blocked.permanent-blocked').forEach(element => { element.classList.remove('permanent-blocked'); element.classList.remove('element-blocked'); }); STATE.blockedElements = []; const blockedCount = blockElements(); updateStatistics(); return blockedCount; } // ==================== 预览功能 ==================== // 预览当前规则 - 临时拦截 function previewCurrentRule() { if (!STATE.currentSelector) { showToast('请先选择元素', 'warning'); return; } console.log('预览选择器:', STATE.currentSelector); // 清除之前的预览 clearPreview(); clearTemporaryBlocking(); const testResult = testSelector(STATE.currentSelector); if (!testResult.valid) { showToast(`选择器无效: ${testResult.error}`, 'error'); console.error('选择器无效:', testResult.error); return; } if (testResult.count === 0) { showToast('未匹配到任何元素', 'warning'); console.log('选择器未匹配到元素:', STATE.currentSelector); return; } STATE.isPreviewMode = true; STATE.isTemporaryBlocking = true; // 启用临时拦截 console.log(`找到 ${testResult.count} 个匹配元素`); // 高亮所有匹配的元素并临时拦截 testResult.elements.forEach((element, index) => { try { element.classList.add('preview-highlight'); // 应用临时拦截 element.classList.add('element-blocked'); element.classList.add('temporary-blocked'); STATE.temporaryBlockedElements.push(element); console.log(`高亮并临时拦截元素 ${index + 1}:`, element.tagName, element.className); } catch (e) { console.error(`高亮元素失败 ${index + 1}:`, e); } }); updateBlockedCount(); showToast(`预览中,临时拦截了 ${testResult.count} 个元素`, 'success'); vibrate('success'); // 自动清除预览 setTimeout(() => { if (STATE.isPreviewMode) { clearPreview(); clearTemporaryBlocking(); showToast('预览已自动清除', 'info'); } }, CONFIG.behavior.previewDuration); updateStatusIndicators(); // 更新元素匹配数量显示 const matchCountElement = document.getElementById('element-match-count'); if (matchCountElement) { matchCountElement.textContent = testResult.count; matchCountElement.style.color = testResult.count > 0 ? CONFIG.colors.success : CONFIG.colors.error; } } // 清除预览 - 同时清除临时拦截 function clearPreview() { if (!STATE.isPreviewMode) return; console.log('清除预览高亮'); STATE.isPreviewMode = false; STATE.isTemporaryBlocking = false; try { const previewElements = document.querySelectorAll('.preview-highlight'); previewElements.forEach(element => { element.classList.remove('preview-highlight'); }); console.log(`移除了 ${previewElements.length} 个预览高亮`); } catch (e) { console.error('清除预览高亮时出错:', e); } clearTemporaryBlocking(); updateStatusIndicators(); } // 清除临时拦截 function clearTemporaryBlocking() { console.log('清除临时拦截'); STATE.temporaryBlockedElements.forEach(element => { try { element.classList.remove('temporary-blocked'); // 只有没有永久拦截时才移除element-blocked if (!element.classList.contains('permanent-blocked')) { element.classList.remove('element-blocked'); } } catch (e) { console.error('清除临时拦截失败:', e); } }); STATE.temporaryBlockedElements = []; updateBlockedCount(); } // ==================== 移动端UI ==================== // 创建移动端UI元素 function createMobileUI() { // 创建悬浮按钮 - 初始不显示 if (!STATE.elements.floatingBtn) { const floatingBtn = document.createElement('button'); floatingBtn.id = 'marker-floating-btn'; floatingBtn.innerHTML = '🎯'; floatingBtn.title = '打开标记菜单'; floatingBtn.addEventListener('click', showMenu); document.body.appendChild(floatingBtn); STATE.elements.floatingBtn = floatingBtn; } // 创建标记模式指示器 - 根据设置决定是否创建 if (STATE.settings.showMarkerHint) { if (!document.getElementById('marker-mobile-indicator')) { const indicator = document.createElement('div'); indicator.id = 'marker-mobile-indicator'; indicator.className = 'marker-indicator'; indicator.textContent = '标记模式已激活 - 点击或长按页面元素进行标记'; document.body.appendChild(indicator); } } else { // 如果设置不显示提示,则移除已存在的指示器 const existingIndicator = document.getElementById('marker-mobile-indicator'); if (existingIndicator) { existingIndicator.remove(); } } // 创建长按标记指示器 if (!document.getElementById('long-press-indicator')) { const indicator = document.createElement('div'); indicator.id = 'long-press-indicator'; indicator.className = 'long-press-indicator'; indicator.textContent = '长按元素进行标记'; document.body.appendChild(indicator); } // 创建长按提示 if (!document.getElementById('long-press-hint')) { const hint = document.createElement('div'); hint.id = 'long-press-hint'; hint.className = 'long-press-hint'; hint.textContent = '长按元素进行标记'; document.body.appendChild(hint); } } // 显示悬浮按钮 function showFloatingButton() { if (STATE.elements.floatingBtn) { STATE.elements.floatingBtn.classList.add('visible'); } } // 隐藏悬浮按钮 function hideFloatingButton() { if (STATE.elements.floatingBtn) { STATE.elements.floatingBtn.classList.remove('visible'); } } // 显示标记模式指示器 function showMarkerIndicator() { if (!STATE.settings.showMarkerHint) return; const indicator = document.getElementById('marker-mobile-indicator'); if (indicator) { indicator.classList.add('visible'); } else { // 如果指示器不存在但设置需要显示,则创建它 const indicator = document.createElement('div'); indicator.id = 'marker-mobile-indicator'; indicator.className = 'marker-indicator'; indicator.textContent = '标记模式已激活 - 点击或长按页面元素进行标记'; indicator.classList.add('visible'); document.body.appendChild(indicator); } } // 隐藏标记模式指示器 function hideMarkerIndicator() { const indicator = document.getElementById('marker-mobile-indicator'); if (indicator) { indicator.classList.remove('visible'); } } // 创建移动端菜单 function createMobileMenu() { if (STATE.elements.menu) { updateMenuContent(); return; } const menu = document.createElement('div'); menu.id = 'marker-mobile-menu'; if (STATE.isCompactMode) { menu.classList.add('compact'); } else { menu.classList.add('expanded'); } if (STATE.settings.darkMode) { menu.classList.add('dark-mode'); } if (STATE.settings.draggableMenu) { menu.classList.add('draggable'); } // 阻止菜单内部触摸事件冒泡 menu.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true }); menu.addEventListener('touchend', function(e) { e.stopPropagation(); }, { passive: true }); loadMenuPositionAndSize(menu); menu.innerHTML = ` `; document.body.appendChild(menu); STATE.elements.menu = menu; setupMenuEvents(); updateMenuContent(); } // 加载菜单位置和大小 function loadMenuPositionAndSize(menu) { if (STATE.settings.menuPosition && STATE.settings.menuSize) { const pos = STATE.settings.menuPosition; const size = STATE.settings.menuSize; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; let x = (pos.x / 100) * viewportWidth; let y = (pos.y / 100) * viewportHeight; let width = Math.max(CONFIG.sizes.menuMinWidth, Math.min(size.width, viewportWidth * 0.9)); let height = Math.max(CONFIG.sizes.menuMinHeight, Math.min(size.height, viewportHeight * 0.9)); x = Math.max(10, Math.min(x, viewportWidth - width - 10)); y = Math.max(10, Math.min(y, viewportHeight - height - 10)); menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.style.width = width + 'px'; menu.style.height = height + 'px'; menu.style.transform = 'none'; } } // 保存菜单位置和大小 function saveMenuPositionAndSize() { if (!STATE.elements.menu || (!STATE.settings.rememberMenuPosition && !STATE.settings.resizableMenu)) return; const menu = STATE.elements.menu; const rect = menu.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const x = (rect.left / viewportWidth) * 100; const y = (rect.top / viewportHeight) * 100; const width = rect.width; const height = rect.height; STATE.settings.menuPosition = { x: x, y: y }; STATE.settings.menuSize = { width: width, height: height }; saveSettings(); } // 设置菜单事件 function setupMenuEvents() { const menu = STATE.elements.menu; if (!menu) return; // 清除所有现有的事件监听器(通过克隆和替换) const newMenu = menu.cloneNode(true); menu.parentNode.replaceChild(newMenu, menu); STATE.elements.menu = newMenu; // 关闭按钮事件 - 修复版 const closeBtn = newMenu.querySelector('#menu-close-btn'); if (closeBtn) { closeBtn.addEventListener('click', function(e) { console.log('关闭按钮被点击'); e.stopPropagation(); e.preventDefault(); hideMenu(); return false; }, { capture: true }); closeBtn.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true, capture: true }); closeBtn.addEventListener('touchend', function(e) { e.stopPropagation(); e.preventDefault(); hideMenu(); return false; }, { capture: true }); } // 大小切换按钮 const sizeBtn = newMenu.querySelector('#menu-size-btn'); if (sizeBtn) { sizeBtn.addEventListener('click', function(e) { e.stopPropagation(); toggleMenuSize(); }); } const sizeToggle = newMenu.querySelector('#size-toggle'); if (sizeToggle) { sizeToggle.addEventListener('click', function(e) { e.stopPropagation(); toggleMenuSize(); }); } // 快速操作按钮 const quickActions = newMenu.querySelector('#quick-actions'); if (quickActions) { quickActions.querySelector('#quick-mark-btn').addEventListener('click', function(e) { e.stopPropagation(); toggleMarkerMode(); }); quickActions.querySelector('#quick-preview-btn').addEventListener('click', function(e) { e.stopPropagation(); console.log('快速预览按钮被点击'); previewCurrentRule(); }); quickActions.querySelector('#quick-save-btn').addEventListener('click', function(e) { e.stopPropagation(); saveCurrentRule(); }); quickActions.querySelector('#quick-block-btn').addEventListener('click', function(e) { e.stopPropagation(); toggleBlocking(); // 更新按钮文字 const btn = e.target.closest('#quick-block-btn') || e.target; const iconSpan = btn.querySelector('span:first-child'); const textSpan = btn.querySelector('span:nth-child(2)'); if (iconSpan && textSpan) { iconSpan.textContent = STATE.isBlockingEnabled ? '⏸️' : '▶️'; textSpan.textContent = STATE.isBlockingEnabled ? '暂停拦截' : '恢复拦截'; } }); } // 刷新元素信息按钮 const refreshElementBtn = newMenu.querySelector('#refresh-element-btn'); if (refreshElementBtn) { refreshElementBtn.addEventListener('click', function(e) { e.stopPropagation(); updateCurrentElementInfo(); showToast('元素信息已刷新', 'info'); vibrate('light'); }); } // 高级操作帮助切换 const toggleHelpBtn = newMenu.querySelector('#toggle-advanced-help'); const advancedHelp = newMenu.querySelector('#advanced-help'); if (toggleHelpBtn && advancedHelp) { toggleHelpBtn.addEventListener('click', function(e) { e.stopPropagation(); if (advancedHelp.style.display === 'none') { advancedHelp.style.display = 'block'; toggleHelpBtn.innerHTML = ''; toggleHelpBtn.title = '隐藏帮助'; } else { advancedHelp.style.display = 'none'; toggleHelpBtn.innerHTML = ''; toggleHelpBtn.title = '显示帮助'; } vibrate('light'); }); } // 高级操作按钮 const advancedActions = newMenu.querySelector('#advanced-actions'); if (advancedActions) { // 预览按钮 const previewBtn = advancedActions.querySelector('#preview-rule-btn'); if (previewBtn) { previewBtn.addEventListener('click', function(e) { e.stopPropagation(); console.log('高级操作预览按钮被点击'); previewCurrentRule(); }); } advancedActions.querySelector('#reset-selection-btn').addEventListener('click', function(e) { e.stopPropagation(); resetSelection(); }); advancedActions.querySelector('#save-rule-btn').addEventListener('click', function(e) { e.stopPropagation(); saveCurrentRule(); }); advancedActions.querySelector('#clear-block-btn').addEventListener('click', function(e) { e.stopPropagation(); clearCurrentBlock(); }); advancedActions.querySelector('#quick-block-apply-btn').addEventListener('click', function(e) { e.stopPropagation(); if (STATE.currentSelector) { const blockedCount = blockElements(); showToast(`已应用拦截,拦截了 ${blockedCount} 个元素`, 'success'); } else { showToast('请先选择元素', 'warning'); } }); advancedActions.querySelector('#open-control-panel-btn').addEventListener('click', function(e) { e.stopPropagation(); if (STATE.elements.controlPanel) { STATE.elements.controlPanel.remove(); STATE.elements.controlPanel = null; } showControlPanel(); }); } // 选择器显示点击事件 const selectorDisplay = newMenu.querySelector('#current-selector-display'); if (selectorDisplay) { selectorDisplay.addEventListener('click', function(e) { e.stopPropagation(); showSelectorEditor(); }); } // 统计相关按钮 const refreshStatsBtn = newMenu.querySelector('#refresh-stats-btn'); if (refreshStatsBtn) { refreshStatsBtn.addEventListener('click', function(e) { e.stopPropagation(); updateStatistics(); updateMiniStats(); showToast('统计信息已刷新', 'info'); vibrate('light'); }); } // 刷新规则按钮 const refreshRulesBtn = newMenu.querySelector('#refresh-rules-btn'); if (refreshRulesBtn) { refreshRulesBtn.addEventListener('click', function(e) { e.stopPropagation(); updateDomainRulesList(); showToast('规则列表已刷新', 'info'); vibrate('light'); }); } // 状态指示器点击事件 const markerStatusItem = newMenu.querySelector('#marker-status-item'); if (markerStatusItem) { markerStatusItem.addEventListener('click', function(e) { e.stopPropagation(); toggleMarkerMode(); }); } const blockStatusItem = newMenu.querySelector('#block-status-item'); if (blockStatusItem) { blockStatusItem.addEventListener('click', function(e) { e.stopPropagation(); toggleBlocking(); }); } const previewStatusItem = newMenu.querySelector('#preview-status-item'); if (previewStatusItem) { previewStatusItem.addEventListener('click', function(e) { e.stopPropagation(); clearPreview(); showToast('已清除预览', 'info'); }); } // 触觉反馈 const buttons = newMenu.querySelectorAll('button'); buttons.forEach(btn => { btn.addEventListener('touchstart', function(e) { e.stopPropagation(); vibrate('light'); }, { passive: true }); }); // 阻止菜单内容区域的触摸事件冒泡 const menuContent = newMenu.querySelector('.menu-content'); if (menuContent) { menuContent.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true }); menuContent.addEventListener('touchend', function(e) { e.stopPropagation(); }, { passive: true }); } if (STATE.settings.draggableMenu) { setupMenuDragEvents(newMenu); } if (STATE.settings.resizableMenu) { setupMenuResizeEvents(newMenu); } console.log('菜单事件设置完成'); } // 设置菜单拖动事件 function setupMenuDragEvents(menu) { const dragArea = menu.querySelector('.menu-drag-area'); if (!dragArea) return; let isDragging = false; let startX, startY, initialX, initialY; const handleDragStart = (clientX, clientY) => { if (!STATE.settings.draggableMenu) return; isDragging = true; startX = clientX; startY = clientY; const rect = menu.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; STATE.touchState.isDraggingMenu = true; menu.style.transition = 'none'; menu.classList.add('dragging'); }; const handleDragMove = (clientX, clientY) => { if (!isDragging || !STATE.settings.draggableMenu) return; const deltaX = clientX - startX; const deltaY = clientY - startY; let newX = initialX + deltaX; let newY = initialY + deltaY; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const menuWidth = menu.offsetWidth; const menuHeight = menu.offsetHeight; newX = Math.max(0, Math.min(newX, viewportWidth - menuWidth)); newY = Math.max(0, Math.min(newY, viewportHeight - menuHeight)); menu.style.left = newX + 'px'; menu.style.top = newY + 'px'; menu.style.transform = 'none'; }; const handleDragEnd = () => { if (isDragging && STATE.settings.draggableMenu) { isDragging = false; STATE.touchState.isDraggingMenu = false; menu.style.transition = ''; menu.classList.remove('dragging'); saveMenuPositionAndSize(); vibrate('light'); } }; dragArea.addEventListener('touchstart', (e) => { if (e.target.closest('#menu-close-btn')) { return; } const touch = e.touches[0]; handleDragStart(touch.clientX, touch.clientY); e.stopPropagation(); }, { passive: true }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; const touch = e.touches[0]; handleDragMove(touch.clientX, touch.clientY); e.preventDefault(); }, { passive: false }); document.addEventListener('touchend', () => { handleDragEnd(); }); dragArea.addEventListener('mousedown', (e) => { if (e.target.closest('#menu-close-btn')) { return; } handleDragStart(e.clientX, e.clientY); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; handleDragMove(e.clientX, e.clientY); }); document.addEventListener('mouseup', () => { handleDragEnd(); }); } // 设置菜单调整大小事件 function setupMenuResizeEvents(menu) { const resizeHandle = menu.querySelector('#resize-handle'); if (!resizeHandle) return; let isResizing = false; let startX, startY, initialWidth, initialHeight; const handleResizeStart = (clientX, clientY) => { if (!STATE.settings.resizableMenu) return; isResizing = true; startX = clientX; startY = clientY; initialWidth = menu.offsetWidth; initialHeight = menu.offsetHeight; STATE.touchState.isResizingMenu = true; menu.style.transition = 'none'; menu.classList.add('resizing'); }; const handleResizeMove = (clientX, clientY) => { if (!isResizing || !STATE.settings.resizableMenu) return; const deltaX = clientX - startX; const deltaY = clientY - startY; let newWidth = initialWidth + deltaX; let newHeight = initialHeight + deltaY; newWidth = Math.max(CONFIG.sizes.menuMinWidth, Math.min(newWidth, window.innerWidth * 0.9)); newHeight = Math.max(CONFIG.sizes.menuMinHeight, Math.min(newHeight, window.innerHeight * 0.9)); menu.style.width = newWidth + 'px'; menu.style.height = newHeight + 'px'; }; const handleResizeEnd = () => { if (isResizing && STATE.settings.resizableMenu) { isResizing = false; STATE.touchState.isResizingMenu = false; menu.style.transition = ''; menu.classList.remove('resizing'); saveMenuPositionAndSize(); vibrate('light'); } }; resizeHandle.addEventListener('touchstart', (e) => { const touch = e.touches[0]; handleResizeStart(touch.clientX, touch.clientY); e.stopPropagation(); e.preventDefault(); }, { passive: false }); document.addEventListener('touchmove', (e) => { if (!isResizing) return; const touch = e.touches[0]; handleResizeMove(touch.clientX, touch.clientY); e.preventDefault(); }, { passive: false }); document.addEventListener('touchend', () => { handleResizeEnd(); }); resizeHandle.addEventListener('mousedown', (e) => { handleResizeStart(e.clientX, e.clientY); e.stopPropagation(); e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isResizing) return; handleResizeMove(e.clientX, e.clientY); }); document.addEventListener('mouseup', () => { handleResizeEnd(); }); } // 切换菜单大小模式 function toggleMenuSize() { const menu = STATE.elements.menu; if (!menu) return; const sizeIcon = document.getElementById('size-icon'); const sizeToggle = document.getElementById('size-toggle'); STATE.isCompactMode = !STATE.isCompactMode; if (STATE.isCompactMode) { menu.classList.remove('expanded'); menu.classList.add('compact'); document.getElementById('advanced-actions').style.display = 'none'; document.getElementById('stats-card').style.display = 'none'; document.getElementById('domain-rules-card').style.display = 'none'; if (sizeIcon) sizeIcon.textContent = '🔽'; if (sizeToggle) sizeToggle.textContent = '⬆️'; if (sizeToggle) sizeToggle.title = '展开'; } else { menu.classList.remove('compact'); menu.classList.add('expanded'); document.getElementById('advanced-actions').style.display = 'block'; document.getElementById('stats-card').style.display = 'block'; document.getElementById('domain-rules-card').style.display = 'block'; if (sizeIcon) sizeIcon.textContent = '🔼'; if (sizeToggle) sizeToggle.textContent = '⬇️'; if (sizeToggle) sizeToggle.title = '收起'; } GM_setValue('preferCompactMode', STATE.isCompactMode); vibrate('medium'); } // 显示菜单 function showMenu() { console.log('显示菜单被调用'); // 隐藏悬浮按钮 hideFloatingButton(); // 创建菜单 createMobileMenu(); const menu = STATE.elements.menu; if (menu) { // 强制重排以确保动画生效 menu.style.display = 'block'; menu.offsetHeight; menu.classList.add('visible'); menu.classList.remove('marker-mode-active'); STATE.isMenuVisible = true; if (STATE.settings.autoExpandMenu && STATE.isCompactMode) { toggleMenuSize(); } updateMenuContent(); console.log('菜单已显示'); } } // 隐藏菜单 function hideMenu() { console.log('隐藏菜单被调用'); const menu = STATE.elements.menu; if (menu) { menu.style.opacity = '0'; menu.style.transform = 'scale(0.9)'; setTimeout(() => { menu.classList.remove('visible'); STATE.isMenuVisible = false; if (menu.parentNode) { menu.parentNode.removeChild(menu); } STATE.elements.menu = null; // 如果标记模式激活,显示悬浮按钮 if (STATE.isMarkerMode) { setTimeout(() => { showFloatingButton(); }, 300); } console.log('菜单已完全隐藏'); }, 200); } else { // 如果没有菜单,确保悬浮按钮显示(如果标记模式激活) if (STATE.isMarkerMode) { showFloatingButton(); } } } // 显示长按标记指示器 function showLongPressIndicator() { const indicator = document.getElementById('long-press-indicator'); if (indicator) { indicator.classList.add('visible'); indicator.offsetHeight; setTimeout(() => { indicator.classList.remove('visible'); }, 1500); } } // 显示长按提示 function showLongPressHint() { const hint = document.getElementById('long-press-hint'); if (hint) { hint.classList.add('visible'); setTimeout(() => { hint.classList.remove('visible'); }, 3000); } } // ==================== 控制面板功能 ==================== // 显示控制面板 function showControlPanel() { const existingPanel = document.querySelector('.control-panel'); if (existingPanel) { existingPanel.remove(); } const stats = updateStatistics(); const domainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ); const domainGroups = {}; STATE.adRules.forEach(rule => { const domain = rule.domain || '全局'; if (!domainGroups[domain]) { domainGroups[domain] = []; } domainGroups[domain].push(rule); }); const domainKeys = Object.keys(domainGroups); const visibleDomains = domainKeys.slice(0, 3); const hiddenDomains = domainKeys.slice(3); const panel = document.createElement('div'); panel.className = `control-panel ${STATE.settings.darkMode ? 'dark-mode' : ''}`; panel.innerHTML = `
🎛️ 控制中心
${STATE.isBlockingEnabled ? '⏸️' : '▶️'} ${STATE.isBlockingEnabled ? '暂停拦截' : '恢复拦截'}
当前状态: ${STATE.isBlockingEnabled ? '拦截已启用' : '拦截已暂停'}
注意:暂停拦截只影响永久保存的规则,预览时的临时拦截仍然有效
总规则数:${stats.rulesCount} | 本站规则:${domainRules.length}
⚙️ 规则管理
${domainRules.length > 0 ? domainRules.slice(0, 5).map(rule => `
${rule.domain || '全局'}
${rule.selector.length > 40 ? rule.selector.substring(0, 40) + '...' : rule.selector}
`).join('') : '
暂无规则
' } ${domainRules.length > 5 ? `
还有 ${domainRules.length - 5} 条规则未显示
` : ''}
📊 数据统计
${stats.totalBlocked}
总拦截数
${stats.todayBlocked}
今日拦截
${stats.rulesCount}
总规则数
${domainRules.length}
本站规则
📁 拦截目录
${Object.keys(domainGroups).length > 0 ? visibleDomains.map(domain => { const isExpanded = STATE.expandedDomains.has(domain); const rules = domainGroups[domain]; return createDirectoryItem(domain, rules, isExpanded); }).join('') : '
暂无拦截规则
' } ${hiddenDomains.length > 0 ? `
` : ''}
🛠️ 实用工具
`; document.body.appendChild(panel); STATE.elements.controlPanel = panel; setupControlPanelEvents(panel, stats, domainGroups, hiddenDomains); } // 创建目录项目 function createDirectoryItem(domain, rules, isExpanded) { return `
${domain}
${rules.length} 条规则
${rules.map(rule => { const testResult = testSelector(rule.selector); return `
${rule.selector.length > 50 ? rule.selector.substring(0, 50) + '...' : rule.selector}
匹配: ${testResult.count} 个元素 | ${testResult.valid ? '有效' : '无效'}
`; }).join('')}
`; } // 设置控制面板事件 function setupControlPanelEvents(panel, stats, domainGroups, hiddenDomains) { const closeControlBtn = panel.querySelector('#close-control-btn'); const closeControlPanelBtn = panel.querySelector('#close-control-panel-btn'); function handleControlClose(e) { console.log('控制面板关闭按钮被点击'); e.stopPropagation(); e.preventDefault(); panel.remove(); STATE.elements.controlPanel = null; } if (closeControlBtn) { closeControlBtn.addEventListener('click', handleControlClose, { capture: true }); closeControlBtn.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { capture: true }); closeControlBtn.addEventListener('touchend', handleControlClose, { capture: true }); } if (closeControlPanelBtn) { closeControlPanelBtn.addEventListener('click', handleControlClose, { capture: true }); closeControlPanelBtn.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { capture: true }); closeControlPanelBtn.addEventListener('touchend', handleControlClose, { capture: true }); } panel.querySelector('#toggle-block-control-btn').addEventListener('click', () => { toggleBlocking(); panel.remove(); STATE.elements.controlPanel = null; }); panel.querySelectorAll('.delete-rule-btn').forEach(btn => { btn.addEventListener('click', (e) => { const selector = e.target.dataset.selector; const domain = e.target.dataset.domain || undefined; deleteRuleFromControlPanel(selector, domain, panel); }); }); panel.querySelectorAll('.delete-directory-rule').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const selector = e.target.dataset.selector; const domain = e.target.dataset.domain || undefined; deleteRuleFromControlPanel(selector, domain, panel); }); }); panel.querySelectorAll('.domain-header').forEach(header => { header.addEventListener('click', (e) => { if (e.target.classList.contains('delete-directory-rule')) { return; } const domain = e.currentTarget.dataset.domain; toggleDomainExpansion(domain, e.currentTarget.closest('.directory-item')); }); }); panel.querySelectorAll('.expand-toggle').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const domain = e.target.dataset.domain; toggleDomainExpansion(domain, e.target.closest('.directory-item')); }); }); const toggleHiddenBtn = panel.querySelector('#toggle-hidden-domains-btn'); const hiddenDomainsList = panel.querySelector('.hidden-domains-list'); if (toggleHiddenBtn && hiddenDomainsList) { toggleHiddenBtn.addEventListener('click', () => { if (hiddenDomainsList.style.display === 'none') { hiddenDomainsList.style.display = 'block'; toggleHiddenBtn.innerHTML = `📂 隐藏更多域名 (${hiddenDomains.length}个)`; } else { hiddenDomainsList.style.display = 'none'; toggleHiddenBtn.innerHTML = `📂 显示更多域名 (${hiddenDomains.length}个)`; } }); } panel.querySelector('#clear-domain-rules-btn').addEventListener('click', () => { if (confirm('确定要清空本站的所有规则吗?这只会删除当前域名(' + STATE.currentDomain + ')的规则。')) { clearCurrentDomainRules(); panel.remove(); STATE.elements.controlPanel = null; } }); panel.querySelector('#clear-all-rules-btn').addEventListener('click', () => { if (confirm('确定要清空所有规则吗?这将删除所有域名和全局规则!')) { clearAllRules(); panel.remove(); STATE.elements.controlPanel = null; } }); panel.querySelector('#reblock-all-tool').addEventListener('click', () => { const blockedCount = forceReBlock(); showToast(`重新拦截了 ${blockedCount} 个元素`, 'success'); }); panel.querySelector('#export-rules-tool').addEventListener('click', exportRules); panel.querySelector('#import-rules-tool').addEventListener('click', importRules); panel.querySelector('#toggle-marker-tool').addEventListener('click', () => { toggleMarkerMode(); panel.remove(); STATE.elements.controlPanel = null; }); panel.querySelector('#reset-stats-tool').addEventListener('click', () => { if (confirm('确定要重置所有统计数据吗?')) { resetStatistics(); panel.remove(); STATE.elements.controlPanel = null; } }); panel.querySelector('#open-settings-from-control').addEventListener('click', () => { panel.remove(); STATE.elements.controlPanel = null; showSettingsFromMenu(); }); } // 切换域名展开状态 function toggleDomainExpansion(domain, directoryItem) { if (STATE.expandedDomains.has(domain)) { STATE.expandedDomains.delete(domain); directoryItem.classList.remove('domain-expanded'); const domainRules = directoryItem.querySelector('.domain-rules'); const expandToggle = directoryItem.querySelector('.expand-toggle'); domainRules.style.display = 'none'; expandToggle.innerHTML = '▼'; expandToggle.title = '展开'; } else { STATE.expandedDomains.add(domain); directoryItem.classList.add('domain-expanded'); const domainRules = directoryItem.querySelector('.domain-rules'); const expandToggle = directoryItem.querySelector('.expand-toggle'); domainRules.style.display = 'block'; expandToggle.innerHTML = '▲'; expandToggle.title = '折叠'; } } // 从控制面板删除规则 function deleteRuleFromControlPanel(selector, domain, panel) { const content = panel.querySelector('#control-content'); const scrollPosition = content ? content.scrollTop : 0; const ruleElement = panel.querySelector(`.domain-rule-item[data-selector="${selector}"][data-domain="${domain}"]`); const domainItem = ruleElement ? ruleElement.closest('.directory-item') : null; const domainName = domainItem ? domainItem.querySelector('.domain-name').textContent : domain; const success = deleteRule(selector, domain, false); if (success) { const remainingRules = STATE.adRules.filter(rule => (domain === '全局' ? !rule.domain : rule.domain === domainName) ); if (remainingRules.length > 0) { const isExpanded = STATE.expandedDomains.has(domainName); const newRulesHtml = remainingRules.map(rule => { const testResult = testSelector(rule.selector); return `
${rule.selector.length > 50 ? rule.selector.substring(0, 50) + '...' : rule.selector}
匹配: ${testResult.count} 个元素 | ${testResult.valid ? '有效' : '无效'}
`; }).join(''); const domainRulesElement = domainItem.querySelector('.domain-rules'); if (domainRulesElement) { domainRulesElement.innerHTML = newRulesHtml; domainRulesElement.querySelectorAll('.delete-directory-rule').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const selector = e.target.dataset.selector; const domain = e.target.dataset.domain || undefined; deleteRuleFromControlPanel(selector, domain, panel); }); }); const ruleCountElement = domainItem.querySelector('.rule-count'); if (ruleCountElement) { ruleCountElement.textContent = `${remainingRules.length} 条规则`; } } } else { if (domainItem) { domainItem.remove(); } } if (content) { content.scrollTop = scrollPosition; } updateControlPanelStats(panel); } } // 更新控制面板统计信息 function updateControlPanelStats(panel) { const stats = updateStatistics(); const domainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ); const statsGrid = panel.querySelector('.stats-grid'); if (statsGrid) { statsGrid.innerHTML = `
${stats.totalBlocked}
总拦截数
${stats.todayBlocked}
今日拦截
${stats.rulesCount}
总规则数
${domainRules.length}
本站规则
`; } const rulesManagement = panel.querySelector('.rules-management'); if (rulesManagement) { rulesManagement.innerHTML = domainRules.length > 0 ? domainRules.slice(0, 5).map(rule => `
${rule.domain || '全局'}
${rule.selector.length > 40 ? rule.selector.substring(0, 40) + '...' : rule.selector}
`).join('') : '
暂无规则
'; if (domainRules.length > 5) { rulesManagement.innerHTML += `
还有 ${domainRules.length - 5} 条规则未显示
`; } rulesManagement.querySelectorAll('.delete-rule-btn').forEach(btn => { btn.addEventListener('click', (e) => { const selector = e.target.dataset.selector; const domain = e.target.dataset.domain || undefined; deleteRuleFromControlPanel(selector, domain, panel); }); }); } } // ==================== 设置功能 ==================== // 显示设置面板(通过油猴菜单调用) function showSettingsFromMenu() { console.log('从油猴菜单打开设置'); createSettingsPanel(); } // 创建设置面板 function createSettingsPanel() { if (STATE.elements.settingsPanel) { STATE.elements.settingsPanel.remove(); STATE.elements.settingsPanel = null; } const panel = document.createElement('div'); panel.className = `settings-panel ${STATE.settings.darkMode ? 'dark-mode' : ''}`; STATE.elements.settingsPanel = panel; panel.innerHTML = `
⚙️ 广告标记器设置
💬 提示设置
显示提示消息
显示操作成功/失败的提示
显示标记模式提示
显示"标记模式已激活"提示条
📳 触觉反馈
震动强度
调节标记元素时的震动强度
👆 手势设置
启用双击保存
双击元素快速保存规则
启用长按手势
长按元素触发标记
📱 菜单设置
自动展开菜单
打开菜单时自动切换到展开模式
菜单可拖动
允许拖动菜单到任意位置
菜单可调整大小
允许调整菜单的宽度和高度
记住菜单设置
记住菜单的位置和大小
暗色模式
使用深色主题界面
🎨 颜色设置
高亮颜色
标记元素的高亮颜色
`; document.body.appendChild(panel); const closeSettingsBtn = panel.querySelector('#close-settings-btn'); if (closeSettingsBtn) { function handleSettingsClose(e) { console.log('设置面板关闭按钮被点击'); e.stopPropagation(); e.preventDefault(); panel.remove(); STATE.elements.settingsPanel = null; } closeSettingsBtn.addEventListener('click', handleSettingsClose, { capture: true }); closeSettingsBtn.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { capture: true }); closeSettingsBtn.addEventListener('touchend', handleSettingsClose, { capture: true }); } panel.querySelector('#save-settings-btn').addEventListener('click', () => { saveSettingsFromPanel(panel); }); panel.querySelector('#reset-settings-btn').addEventListener('click', () => { if (confirm('确定要重置所有设置为默认值吗?')) { resetSettings(panel); } }); const colorPicker = panel.querySelector('#highlight-color-picker'); if (colorPicker) { colorPicker.addEventListener('input', (e) => { STATE.settings.highlightColor = e.target.value; }); } // 震动强度按钮事件 const vibrationBtns = panel.querySelectorAll('.vibration-btn'); vibrationBtns.forEach(btn => { btn.addEventListener('click', (e) => { const strength = e.target.dataset.strength; STATE.settings.vibrationStrength = strength; // 更新按钮样式 vibrationBtns.forEach(b => { b.style.background = 'transparent'; b.style.color = CONFIG.colors.text; b.classList.remove('active'); }); e.target.style.background = CONFIG.colors.info; e.target.style.color = 'white'; e.target.classList.add('active'); // 测试震动(如果不是关闭) if (strength !== 'off' && window.navigator.vibrate) { const strengthMap = { 'light': 0.5, 'medium': 1, 'strong': 1.5 }; const testStrength = strengthMap[strength] || 1; window.navigator.vibrate([50 * testStrength]); } }); }); const checkboxes = panel.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(toggle => { toggle.addEventListener('change', (e) => { const settingId = e.target.id.replace('-toggle', ''); const settingMap = { 'show-toast': 'showToast', 'show-marker-hint': 'showMarkerHint', 'vibration': 'enableVibration', 'auto-expand': 'autoExpandMenu', 'dark-mode': 'darkMode', 'double-tap': 'enableDoubleTap', 'long-press': 'enableLongPress', 'draggable-menu': 'draggableMenu', 'resizable-menu': 'resizableMenu', 'remember-position': 'rememberMenuPosition' }; const settingKey = settingMap[settingId]; if (settingKey) { STATE.settings[settingKey] = e.target.checked; // 如果是标记提示设置改变,立即更新UI if (settingKey === 'showMarkerHint') { if (e.target.checked) { // 如果开启,创建指示器 if (!document.getElementById('marker-mobile-indicator')) { const indicator = document.createElement('div'); indicator.id = 'marker-mobile-indicator'; indicator.className = 'marker-indicator'; indicator.textContent = '标记模式已激活 - 点击或长按页面元素进行标记'; if (STATE.isMarkerMode) { indicator.classList.add('visible'); } document.body.appendChild(indicator); } } else { // 如果关闭,移除指示器 const indicator = document.getElementById('marker-mobile-indicator'); if (indicator) { indicator.remove(); } } } if ((settingKey === 'draggableMenu' || settingKey === 'resizableMenu') && !e.target.checked) { STATE.settings.rememberMenuPosition = false; document.getElementById('remember-position-toggle').checked = false; } } }); }); } // 从面板保存设置 function saveSettingsFromPanel(panel) { const success = saveSettings(); if (success) { panel.remove(); STATE.elements.settingsPanel = null; showToast('设置已保存', 'success'); vibrate('success'); if (STATE.elements.menu) { STATE.elements.menu.remove(); STATE.elements.menu = null; createMobileMenu(); if (STATE.isMenuVisible) { showMenu(); } } } else { showToast('保存设置失败', 'error'); } } // 重置设置 function resetSettings(panel) { STATE.settings = { showToast: true, enableVibration: true, enableDoubleTap: true, enableLongPress: true, autoExpandMenu: false, highlightColor: CONFIG.colors.marker, darkMode: false, draggableMenu: true, resizableMenu: true, menuPosition: { x: 50, y: 50 }, menuSize: { width: 300, height: 350 }, rememberMenuPosition: true, showMarkerHint: true, vibrationStrength: 'medium' }; saveSettings(); updateHighlightColor(CONFIG.colors.marker); if (STATE.elements.menu) { STATE.elements.menu.remove(); STATE.elements.menu = null; createMobileMenu(); if (STATE.isMenuVisible) { showMenu(); } } panel.remove(); STATE.elements.settingsPanel = null; showToast('设置已重置为默认值', 'success'); vibrate('success'); } // ==================== 核心操作 ==================== // 保存当前规则 function saveCurrentRule() { if (!STATE.currentSelector) { showToast('请先选择元素', 'warning'); return; } const testResult = testSelector(STATE.currentSelector); if (!testResult.valid) { showToast(`选择器无效: ${testResult.error}`, 'error'); return; } if (testResult.count === 0) { const confirmSave = confirm('选择器没有匹配到任何元素,确定要保存吗?\n(可能用于拦截动态加载的内容)'); if (!confirmSave) return; } const exists = STATE.adRules.some(rule => rule.selector === STATE.currentSelector && (!rule.domain || rule.domain === STATE.currentDomain) ); if (exists) { showToast('规则已存在', 'warning'); return; } const newRule = { selector: STATE.currentSelector, domain: STATE.currentDomain, timestamp: new Date().toISOString(), pageUrl: window.location.href, pageTitle: document.title.substring(0, 50), matchCount: testResult.count }; STATE.adRules.push(newRule); saveAdRules(); // 清除临时拦截 clearTemporaryBlocking(); // 应用永久拦截 const blockedCount = blockElements(); if (blockedCount > 0) { showToast(`规则已保存并永久拦截了 ${blockedCount} 个元素`, 'success'); } else { showToast('规则已保存,但没有匹配到元素', 'info'); } updateMenuContent(); vibrate('success'); } // 重置选择 function resetSelection() { document.querySelectorAll('.marker-highlight').forEach(el => el.classList.remove('marker-highlight')); STATE.currentSelector = ''; clearPreview(); clearTemporaryBlocking(); updateMenuContent(); showToast('已重置选择', 'info'); vibrate('light'); } // 清除当前拦截 function clearCurrentBlock() { if (!STATE.currentSelector) { showToast('请先选择元素', 'warning'); return; } try { let clearedCount = 0; const elements = document.querySelectorAll(STATE.currentSelector); elements.forEach(element => { if (element.classList.contains('element-blocked')) { element.classList.remove('element-blocked'); element.classList.remove('temporary-blocked'); element.classList.remove('permanent-blocked'); clearedCount++; } }); STATE.blockedElements = STATE.blockedElements.filter(item => { return !item.element.matches(STATE.currentSelector); }); updateBlockedCount(); if (clearedCount > 0) { showToast(`已清除 ${clearedCount} 个拦截`, 'success'); } else { showToast('未找到已拦截的元素', 'info'); } vibrate('medium'); } catch (e) { showToast('清除失败: ' + e.message, 'error'); } } // 显示选择器编辑器 function showSelectorEditor() { const selector = STATE.currentSelector || ''; const newSelector = prompt('编辑选择器(支持CSS选择器语法):', selector); if (newSelector !== null && newSelector.trim() !== '') { STATE.currentSelector = newSelector.trim(); updateMenuContent(); showToast('选择器已更新', 'info'); vibrate('light'); } } // ==================== 菜单内容更新 ==================== // 更新菜单内容 function updateMenuContent() { if (!STATE.elements.menu || !STATE.isMenuVisible) return; updateCurrentElementInfo(); updateStatusIndicators(); updateBlockedCount(); updateMiniStats(); updateDomainRulesList(); } // 更新当前元素信息 function updateCurrentElementInfo() { const tagElement = document.getElementById('element-tag'); const idElement = document.getElementById('element-id'); const classElement = document.getElementById('element-class'); const matchCountElement = document.getElementById('element-match-count'); const selectorDisplay = document.getElementById('current-selector-display'); if (!tagElement || !idElement || !classElement || !matchCountElement || !selectorDisplay) return; if (STATE.currentSelector) { try { const testResult = testSelector(STATE.currentSelector); const matchCount = testResult.count; if (testResult.elements.length > 0) { const element = testResult.elements[0]; // 获取元素信息 const tag = element.tagName.toLowerCase(); const id = element.id || '无'; let classes = element.className; if (typeof classes === 'string' && classes.trim()) { // 限制类名显示长度 if (classes.length > 30) { classes = classes.substring(0, 30) + '...'; } } else { classes = '无'; } // 更新元素信息 tagElement.textContent = tag; idElement.textContent = id; classElement.textContent = classes; matchCountElement.textContent = matchCount; matchCountElement.style.color = matchCount > 0 ? CONFIG.colors.success : CONFIG.colors.error; // 更新选择器显示 let displaySelector = STATE.currentSelector; if (displaySelector.length > 100) { displaySelector = displaySelector.substring(0, 100) + '...'; } selectorDisplay.textContent = displaySelector; selectorDisplay.title = STATE.currentSelector; selectorDisplay.style.borderColor = testResult.valid ? CONFIG.colors.success : CONFIG.colors.error; return; } } catch (e) { console.error('获取元素信息失败:', e); } } // 默认显示 tagElement.textContent = '无'; idElement.textContent = '无'; classElement.textContent = '无'; matchCountElement.textContent = '0'; matchCountElement.style.color = CONFIG.colors.text; selectorDisplay.textContent = '未选择元素'; selectorDisplay.title = ''; selectorDisplay.style.borderColor = CONFIG.colors.border; } // 更新状态指示器 function updateStatusIndicators() { const markerDot = document.getElementById('marker-status-dot'); const blockDot = document.getElementById('block-status-dot'); const previewDot = document.getElementById('preview-status-dot'); if (markerDot) { markerDot.className = `status-dot ${STATE.isMarkerMode ? 'active' : 'inactive'}`; } if (blockDot) { blockDot.className = `status-dot ${STATE.isBlockingEnabled ? 'active' : 'inactive'}`; } if (previewDot) { previewDot.className = `status-dot ${STATE.isPreviewMode ? 'active' : 'inactive'}`; } } // 更新拦截计数 function updateBlockedCount() { const countElement = document.getElementById('blocked-count'); if (countElement) { const blockedCount = document.querySelectorAll('.element-blocked').length; countElement.textContent = blockedCount; STATE.blockedElements.length = blockedCount; } } // 更新迷你统计信息 function updateMiniStats() { const stats = updateStatistics(); const menu = STATE.elements.menu; if (!menu) return; const totalBlockedEl = menu.querySelector('#stats-total-blocked'); const todayBlockedEl = menu.querySelector('#stats-today-blocked'); const domainRulesEl = menu.querySelector('#stats-domain-rules'); const totalRulesEl = menu.querySelector('#stats-total-rules'); if (totalBlockedEl) totalBlockedEl.textContent = stats.totalBlocked; if (todayBlockedEl) todayBlockedEl.textContent = stats.todayBlocked; if (totalRulesEl) totalRulesEl.textContent = stats.rulesCount; // 计算本站规则数 const currentDomainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ).length; if (domainRulesEl) domainRulesEl.textContent = currentDomainRules; } // 更新本站规则列表 function updateDomainRulesList() { const menu = STATE.elements.menu; if (!menu) return; const rulesList = menu.querySelector('#domain-rules-list'); if (!rulesList) return; const currentDomainRules = STATE.adRules.filter(rule => !rule.domain || rule.domain === STATE.currentDomain ); if (currentDomainRules.length === 0) { rulesList.innerHTML = `
暂无规则,请先标记并保存
`; return; } let html = ''; currentDomainRules.slice(0, 5).forEach((rule, index) => { const testResult = testSelector(rule.selector); const selectorShort = rule.selector.length > 40 ? rule.selector.substring(0, 40) + '...' : rule.selector; html += `
${selectorShort}
${testResult.count}
`; }); if (currentDomainRules.length > 5) { html += `
还有 ${currentDomainRules.length - 5} 条规则未显示
`; } rulesList.innerHTML = html; // 为删除按钮添加事件 rulesList.querySelectorAll('.domain-rule-delete-mini').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const selector = this.dataset.selector; const domain = this.dataset.domain || undefined; if (confirm('确定要删除这条规则吗?')) { const success = deleteRule(selector, domain); if (success) { updateDomainRulesList(); updateMiniStats(); showToast('规则已删除', 'success'); vibrate('success'); } } }); }); // 为规则项添加点击事件,点击时设置当前选择器 rulesList.querySelectorAll('.domain-rule-item-mini').forEach(item => { item.addEventListener('click', function(e) { if (e.target.classList.contains('domain-rule-delete-mini')) return; const index = this.dataset.index; const rule = currentDomainRules[index]; if (rule) { STATE.currentSelector = rule.selector; updateMenuContent(); showToast('已选择规则,点击预览查看效果', 'info'); vibrate('light'); } }); }); } // ==================== 事件处理 ==================== // 设置事件监听器 function setupEventListeners() { // 移除旧的事件监听器 document.removeEventListener('touchstart', handleTouchStart); document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); document.removeEventListener('touchstart', handleDoubleTap); document.removeEventListener('keydown', handleKeyDown); // 添加新的事件监听器 document.addEventListener('touchstart', handleTouchStart, { passive: false }); document.addEventListener('touchmove', handleTouchMove, { passive: false }); document.addEventListener('touchend', handleTouchEnd, { passive: false }); if (STATE.settings.enableDoubleTap) { document.addEventListener('touchstart', handleDoubleTap, { passive: true }); } document.addEventListener('keydown', handleKeyDown); } // 触摸开始事件 function handleTouchStart(e) { // 如果触摸到菜单或控制面板,不处理 if (e.target.closest('#marker-mobile-menu') || e.target.closest('#marker-floating-btn') || e.target.closest('.control-panel') || e.target.closest('.settings-panel')) { return; } if (!STATE.isMarkerMode) return; const touch = e.touches[0]; const target = document.elementFromPoint(touch.clientX, touch.clientY); if (!target || target === document.body) return; STATE.touchState.startX = touch.clientX; STATE.touchState.startY = touch.clientY; STATE.touchState.startTime = e.timeStamp; STATE.touchState.longPressTarget = target; // 清除现有的长按定时器 if (STATE.touchState.longPressTimer) { clearTimeout(STATE.touchState.longPressTimer); } // 显示长按提示 if (STATE.settings.enableLongPress) { showLongPressHint(); } // 设置长按定时器 STATE.touchState.longPressTimer = setTimeout(() => { if (STATE.isMarkerMode && STATE.settings.enableLongPress) { const longPressTarget = STATE.touchState.longPressTarget; if (longPressTarget && document.contains(longPressTarget)) { // 清除之前的高亮 document.querySelectorAll('.marker-highlight').forEach(el => { el.classList.remove('marker-highlight'); }); // 添加新高亮 longPressTarget.classList.add('marker-highlight'); STATE.currentSelector = generateSelector(longPressTarget); // 更新菜单内容 updateMenuContent(); // 显示长按标记指示器 showLongPressIndicator(); // 震动反馈 vibrate('medium'); // 显示提示 showToast('元素已标记,请点击保存按钮保存规则', 'success'); } } // 清除定时器引用 STATE.touchState.longPressTimer = null; }, CONFIG.behavior.longPressDelay); } // 触摸移动事件 function handleTouchMove(e) { if (!STATE.isMarkerMode || !STATE.touchState.longPressTimer) return; const touch = e.touches[0]; const deltaX = Math.abs(touch.clientX - STATE.touchState.startX); const deltaY = Math.abs(touch.clientY - STATE.touchState.startY); // 如果移动距离超过阈值,取消长按 if (deltaX > CONFIG.gestures.dragThreshold || deltaY > CONFIG.gestures.dragThreshold) { if (STATE.touchState.longPressTimer) { clearTimeout(STATE.touchState.longPressTimer); STATE.touchState.longPressTimer = null; STATE.touchState.longPressTarget = null; } } } // 触摸结束事件 function handleTouchEnd(e) { // 清除长按定时器 if (STATE.touchState.longPressTimer) { clearTimeout(STATE.touchState.longPressTimer); STATE.touchState.longPressTimer = null; } if (!STATE.isMarkerMode) return; const touch = e.changedTouches[0]; const deltaX = Math.abs(touch.clientX - STATE.touchState.startX); const deltaY = Math.abs(touch.clientY - STATE.touchState.startY); const timeDiff = e.timeStamp - STATE.touchState.startTime; // 如果是短点击(小于长按延迟的一半) if (deltaX < 10 && deltaY < 10 && timeDiff < CONFIG.behavior.longPressDelay / 2) { const target = document.elementFromPoint(touch.clientX, touch.clientY); if (target && !target.closest('#marker-mobile-menu') && !target.closest('#marker-floating-btn') && !target.closest('.settings-panel') && !target.closest('.control-panel')) { // 清除之前的高亮 document.querySelectorAll('.marker-highlight').forEach(el => { el.classList.remove('marker-highlight'); }); // 添加新高亮 target.classList.add('marker-highlight'); STATE.currentSelector = generateSelector(target); // 更新菜单内容 updateMenuContent(); // 震动反馈 vibrate('light'); // 显示提示 showToast('元素已标记,长按可更精确标记', 'info'); } } // 清除长按目标 STATE.touchState.longPressTarget = null; } // 双击事件 function handleDoubleTap(e) { if (!STATE.isMarkerMode || !STATE.settings.enableDoubleTap) return; const currentTime = e.timeStamp; const tapLength = currentTime - STATE.touchState.lastTapTime; if (tapLength < CONFIG.behavior.doubleTapDelay && tapLength > 0) { if (STATE.currentSelector) { saveCurrentRule(); vibrate('success'); } } STATE.touchState.lastTapTime = currentTime; } // 键盘事件 function handleKeyDown(e) { if (e.key === 'Escape' && STATE.isMarkerMode) { toggleMarkerMode(); } } // ==================== 核心操作 ==================== // 切换标记模式 function toggleMarkerMode() { STATE.isMarkerMode = !STATE.isMarkerMode; const menu = STATE.elements.menu; if (STATE.isMarkerMode) { // 标记模式激活 if (STATE.settings.showMarkerHint) { showMarkerIndicator(); } if (menu) { menu.classList.add('marker-mode-active'); } // 显示悬浮按钮 showFloatingButton(); // 显示菜单 showMenu(); showToast('标记模式已激活,点击或长按页面元素进行标记', 'success'); vibrate('success'); // 设置事件监听器 setTimeout(() => { setupEventListeners(); }, 100); } else { // 标记模式关闭 hideMarkerIndicator(); if (menu) { menu.classList.remove('marker-mode-active'); } // 隐藏悬浮按钮 hideFloatingButton(); // 隐藏菜单 hideMenu(); // 清除高亮和预览 document.querySelectorAll('.marker-highlight').forEach(el => el.classList.remove('marker-highlight')); clearPreview(); clearTemporaryBlocking(); showToast('标记模式已关闭', 'info'); // 移除事件监听器 document.removeEventListener('touchstart', handleTouchStart); document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); } updateStatusIndicators(); registerMenuCommands(); } // 切换拦截状态 function toggleBlocking() { STATE.isBlockingEnabled = !STATE.isBlockingEnabled; if (STATE.isBlockingEnabled) { // 启用拦截 document.body.classList.remove('interception-disabled'); const blockedCount = blockElements(); showToast(`拦截已启用,拦截了 ${blockedCount} 个元素`, 'success'); } else { // 暂停拦截 document.body.classList.add('interception-disabled'); const unblockedCount = unblockElements(); showToast(`拦截已暂停,移除了 ${unblockedCount} 个永久拦截`, 'warning'); } updateStatusIndicators(); // 更新菜单中的拦截按钮文字 const menu = STATE.elements.menu; if (menu && STATE.isMenuVisible) { const quickBlockBtn = menu.querySelector('#quick-block-btn'); if (quickBlockBtn) { const iconSpan = quickBlockBtn.querySelector('span:first-child'); const textSpan = quickBlockBtn.querySelector('span:nth-child(2)'); if (iconSpan && textSpan) { iconSpan.textContent = STATE.isBlockingEnabled ? '⏸️' : '▶️'; textSpan.textContent = STATE.isBlockingEnabled ? '暂停拦截' : '恢复拦截'; } } } vibrate('medium'); registerMenuCommands(); } // ==================== 规则管理功能 ==================== // 删除规则 function deleteRule(selector, domain = undefined, closePanel = true) { const index = STATE.adRules.findIndex(rule => rule.selector === selector && (domain === undefined ? true : (domain === '' ? !rule.domain : rule.domain === domain)) ); if (index !== -1) { STATE.adRules.splice(index, 1); saveAdRules(); forceReBlock(); showToast('规则已删除', 'success'); vibrate('success'); return true; } else { showToast('未找到要删除的规则', 'error'); return false; } } // 删除域名规则 function deleteDomainRule(domain, index) { const domainRules = STATE.adRules.filter(rule => (domain === '全局' ? !rule.domain : rule.domain === domain) ); if (index < domainRules.length) { const rule = domainRules[index]; const globalIndex = STATE.adRules.findIndex(r => r.selector === rule.selector && (domain === '全局' ? !r.domain : r.domain === domain) ); if (globalIndex !== -1) { STATE.adRules.splice(globalIndex, 1); saveAdRules(); forceReBlock(); showToast('规则已删除', 'success'); vibrate('success'); } } } // 清空本站规则 function clearCurrentDomainRules() { const initialCount = STATE.adRules.length; STATE.adRules = STATE.adRules.filter(rule => !rule.domain || rule.domain !== STATE.currentDomain ); const deletedCount = initialCount - STATE.adRules.length; saveAdRules(); forceReBlock(); showToast(`已清空本站规则,删除了 ${deletedCount} 条规则`, 'success'); vibrate('success'); } // 清空特定域名规则 function clearDomainRules(domain) { if (domain === '全局') { STATE.adRules = STATE.adRules.filter(rule => rule.domain); } else { STATE.adRules = STATE.adRules.filter(rule => rule.domain !== domain); } saveAdRules(); forceReBlock(); showToast(`${domain} 的规则已清空`, 'success'); vibrate('success'); } // 清空所有规则 function clearAllRules() { STATE.adRules = []; saveAdRules(); forceReBlock(); showToast('所有规则已清空', 'success'); vibrate('success'); } // 导出规则 function exportRules() { const data = { rules: STATE.adRules, exportDate: new Date().toISOString(), version: '1.0' }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `ad-rules-${STATE.currentDomain}-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('规则已导出', 'success'); } // 导入规则 function importRules() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); if (!data.rules || !Array.isArray(data.rules)) { throw new Error('无效的规则文件格式'); } data.rules.forEach(newRule => { const exists = STATE.adRules.some(existingRule => existingRule.selector === newRule.selector && existingRule.domain === newRule.domain ); if (!exists) { STATE.adRules.push(newRule); } }); saveAdRules(); forceReBlock(); showToast(`成功导入 ${data.rules.length} 条规则`, 'success'); } catch (error) { showToast(`导入失败: ${error.message}`, 'error'); } }; reader.readAsText(file); }); document.body.appendChild(input); input.click(); setTimeout(() => document.body.removeChild(input), 100); } // 重置统计数据 function resetStatistics() { STATE.statistics = { totalBlocked: 0, todayBlocked: 0, rulesCount: 0, domainsCount: 0, lastUpdate: null }; GM_setValue('adMarkerStatistics', JSON.stringify(STATE.statistics)); showToast('统计数据已重置', 'success'); vibrate('success'); } // ==================== 辅助功能 ==================== // 显示Toast提示 function showToast(message, type = 'info') { if (!STATE.settings.showToast) return; const oldToast = document.getElementById('marker-toast'); if (oldToast) { oldToast.remove(); } const colors = { success: CONFIG.colors.preview, warning: CONFIG.colors.block, error: CONFIG.colors.marker, info: '#007aff' }; const toast = document.createElement('div'); toast.id = 'marker-toast'; toast.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: ${colors[type]}; color: white; padding: 12px 20px; border-radius: ${CONFIG.sizes.borderRadius}px; font-size: 13px; font-weight: 500; z-index: 1000001; box-shadow: 0 4px 20px rgba(0,0,0,0.15); opacity: 0; transition: opacity 0.3s; white-space: nowrap; pointer-events: none; `; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '1'; }, 10); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 300); }, 2000); } // ==================== 油猴菜单命令 ==================== // 注册菜单命令 function registerMenuCommands() { console.log('开始注册油猴菜单命令...'); // 清理所有现有的菜单命令 if (typeof GM_unregisterMenuCommand !== 'undefined' && STATE.menuCommands.length > 0) { STATE.menuCommands.forEach(command => { try { GM_unregisterMenuCommand(command); } catch (e) { console.warn('取消注册菜单命令失败:', e); } }); STATE.menuCommands = []; } // 检查Tampermonkey API是否可用 if (typeof GM_registerMenuCommand === 'undefined') { console.error('GM_registerMenuCommand不可用'); return; } try { // 标记功能菜单 const markText = STATE.isMarkerMode ? '🎯 关闭标记模式' : '🎯 开启标记模式'; const markCmd = GM_registerMenuCommand(markText, function() { console.log('标记菜单被点击'); toggleMarkerMode(); }); STATE.menuCommands.push(markCmd); console.log('注册标记功能菜单命令成功'); // 控制面板菜单 const controlCmd = GM_registerMenuCommand('🎛️ 控制中心', function() { console.log('控制中心菜单被点击'); showControlPanel(); }); STATE.menuCommands.push(controlCmd); console.log('注册控制面板菜单命令成功'); // 设置菜单 const settingsCmd = GM_registerMenuCommand('⚙️ 设置', function() { console.log('设置菜单被点击'); showSettingsFromMenu(); }); STATE.menuCommands.push(settingsCmd); console.log('注册设置菜单命令成功'); console.log('油猴菜单命令注册完成,共注册了', STATE.menuCommands.length, '个命令'); } catch (error) { console.error('注册油猴菜单命令时出错:', error); // 尝试重新注册 setTimeout(registerMenuCommands, 1000); } } // ==================== 初始化 ==================== function init() { console.log('广告标记器脚本开始初始化...'); // 首先检查Tampermonkey API是否可用 if (typeof GM_registerMenuCommand === 'undefined') { console.error('错误:GM_registerMenuCommand未定义!请确保脚本在Tampermonkey中运行。'); showBrowserAlert(); return; } // 加载数据 loadAdRules(); // 加载设置 loadSettings(); // 应用高亮颜色 if (STATE.settings.highlightColor && STATE.settings.highlightColor !== CONFIG.colors.marker) { updateHighlightColor(STATE.settings.highlightColor); } // 加载紧凑模式偏好 const savedCompactMode = GM_getValue('preferCompactMode', CONFIG.behavior.defaultCompactMode); STATE.isCompactMode = savedCompactMode; // 创建移动端UI createMobileUI(); // 设置事件监听器 setupEventListeners(); // 注册菜单命令 registerMenuCommands(); // 设置初始拦截状态 if (!STATE.isBlockingEnabled) { document.body.classList.add('interception-disabled'); } // 初始拦截 setTimeout(() => { const blockedCount = blockElements(); console.log(`初始拦截完成,拦截了 ${blockedCount} 个元素`); if (blockedCount > 0 && STATE.settings.showToast) { showToast(`已拦截 ${blockedCount} 个广告元素`, 'success'); } }, 1500); // 显示欢迎提示 setTimeout(() => { if (STATE.settings.showToast) { showToast('移动端广告标记器已就绪,请在油猴菜单中开启标记功能', 'info'); } }, 2000); // 监听DOM变化 const observer = new MutationObserver(() => { if (STATE.isBlockingEnabled && STATE.adRules.length > 0) { setTimeout(blockElements, 300); } }); observer.observe(document.body, { childList: true, subtree: true }); console.log('移动端优化广告标记器已加载完成'); // 再次确保菜单命令已注册 setTimeout(registerMenuCommands, 3000); } // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();