// ==UserScript== // @name 左键点击链接在新标签页打开 (可配置站点+UI) // @name:en Open Links in New Tab with Left Click (Configurable Sites + UI) // @namespace https://greasyfork.org/users/your-username // 建议替换为你的唯一命名空间 // @version 1.3 // @description 强制所有鼠标左键点击的普通链接都在新的浏览器标签页中打开。可自定义排除或包含特定网站,并通过侧边栏UI管理规则。 // @description:en Forces all regular links clicked with the left mouse button to open in a new browser tab. Allows whitelisting/blacklisting sites with a UI panel for management. // @author AI Assistant // @match *://*/* // @grant GM_openInTab // @grant window.open // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // --- 配置键名 --- const CONFIG_KEY_WHITELIST = 'openInNewTab_custom_whitelist_v1'; const CONFIG_KEY_BLACKLIST = 'openInNewTab_custom_blacklist_v1'; const CONFIG_KEY_PANEL_VISIBLE = 'openInNewTab_panelVisibleState_v1'; // --- 默认配置 --- const defaultWhitelistPatterns = []; const defaultBlacklistPatterns = []; let userWhitelist = []; let userBlacklist = []; let panelVisible = false; let uiPanel = null; // --- CSS 样式 --- GM_addStyle(` #openInNewTab-config-panel { position: fixed; left: 10px; top: 70px; width: 280px; max-height: calc(100vh - 90px); background-color: #f8f9fa; border: 1px solid #ced4da; border-radius: 6px; padding: 12px; z-index: 999999; /* 确保在顶层 */ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px; color: #212529; box-shadow: 0 4px 8px rgba(0,0,0,0.1); display: none; /* 默认隐藏 */ overflow-y: auto; } #openInNewTab-config-panel h3 { margin-top: 0; margin-bottom: 10px; font-size: 16px; color: #343a40; border-bottom: 1px solid #dee2e6; padding-bottom: 5px; } #openInNewTab-config-panel h4 { margin-top: 10px; margin-bottom: 5px; font-size: 14px; color: #495057; } #openInNewTab-config-panel ul { list-style-type: none; padding-left: 0; margin: 0; max-height: 200px; /* 给列表一个最大高度 */ overflow-y: auto; /* 列表内部也允许滚动 */ border: 1px solid #e9ecef; border-radius: 4px; background-color: #fff; } #openInNewTab-config-panel li { padding: 6px 8px; border-bottom: 1px solid #f1f3f5; display: flex; align-items: center; justify-content: space-between; font-size: 13px; } #openInNewTab-config-panel li:last-child { border-bottom: none; } #openInNewTab-config-panel li span { word-break: break-all; margin-right: 8px; flex-grow: 1; } #openInNewTab-config-panel .delete-btn { color: #dc3545; background-color: transparent; border: none; cursor: pointer; font-weight: bold; font-size: 16px; padding: 0 5px; line-height: 1; opacity: 0.7; } #openInNewTab-config-panel .delete-btn:hover { color: #c82333; opacity: 1; } #openInNewTab-config-panel .panel-empty-msg { font-size: 12px; color: #6c757d; padding: 8px; text-align: center; } #openInNewTab-config-panel .close-panel-btn { display: block; width: 100%; margin-top: 15px; padding: 6px 10px; font-size: 13px; color: #fff; background-color: #6c757d; border: none; border-radius: 4px; cursor: pointer; } #openInNewTab-config-panel .close-panel-btn:hover { background-color: #5a6268; } `); function createUIPanel() { if (!document.body) { // console.warn("左键新标签页UI: document.body 尚未准备好,稍后尝试创建面板。"); setTimeout(createUIPanel, 100); // 稍后重试 return; } if (document.getElementById('openInNewTab-config-panel')) return; // 防止重复创建 uiPanel = document.createElement('div'); uiPanel.id = 'openInNewTab-config-panel'; // 样式已通过 GM_addStyle 添加 const title = document.createElement('h3'); title.textContent = '新标签页打开 - 站点规则'; uiPanel.appendChild(title); const whitelistDiv = document.createElement('div'); whitelistDiv.id = 'openInNewTab-whitelist-section'; uiPanel.appendChild(whitelistDiv); const blacklistDiv = document.createElement('div'); blacklistDiv.id = 'openInNewTab-blacklist-section'; uiPanel.appendChild(blacklistDiv); const closeButton = document.createElement('button'); closeButton.textContent = '关闭面板'; closeButton.className = 'close-panel-btn'; closeButton.onclick = togglePanelVisibility; uiPanel.appendChild(closeButton); document.body.appendChild(uiPanel); renderListsUI(); // 创建后立即渲染内容 } function renderListsUI() { if (!uiPanel || !panelVisible || !document.body.contains(uiPanel)) return; const renderList = (listArray, parentDivId, listTitleText, listType) => { const parentElement = uiPanel.querySelector('#' + parentDivId); if (!parentElement) return; parentElement.innerHTML = ''; // 清空旧内容 const titleElem = document.createElement('h4'); titleElem.textContent = listTitleText; parentElement.appendChild(titleElem); if (listArray.length === 0) { const emptyMsg = document.createElement('p'); emptyMsg.textContent = '列表为空'; emptyMsg.className = 'panel-empty-msg'; parentElement.appendChild(emptyMsg); return; } const ul = document.createElement('ul'); listArray.forEach((pattern, index) => { const li = document.createElement('li'); const patternText = document.createElement('span'); patternText.textContent = pattern; const deleteBtn = document.createElement('button'); deleteBtn.innerHTML = '×'; // 使用 HTML实体 "×" deleteBtn.className = 'delete-btn'; deleteBtn.title = '删除此条规则'; deleteBtn.onclick = function() { if (confirm(`确定要从${listType === 'white' ? '白' : '黑'}名单中删除 "${pattern}" 吗?`)) { if (listType === 'white') { userWhitelist.splice(index, 1); GM_setValue(CONFIG_KEY_WHITELIST, userWhitelist.join(',')); } else { userBlacklist.splice(index, 1); GM_setValue(CONFIG_KEY_BLACKLIST, userBlacklist.join(',')); } // loadConfig(); // loadConfig 会从存储重载,这里直接重渲染即可 renderListsUI(); // console.log(`${listType === 'white' ? '白' : '黑'}名单已更新,删除了: ${pattern}`); } }; li.appendChild(patternText); li.appendChild(deleteBtn); ul.appendChild(li); }); parentElement.appendChild(ul); }; renderList(userWhitelist, 'openInNewTab-whitelist-section', '白名单 (强制启用):', 'white'); renderList(userBlacklist, 'openInNewTab-blacklist-section', '黑名单 (禁用功能):', 'black'); } function togglePanelVisibility() { panelVisible = !panelVisible; GM_setValue(CONFIG_KEY_PANEL_VISIBLE, panelVisible); if (panelVisible) { if (!uiPanel || !document.body.contains(uiPanel)) { createUIPanel(); } else { renderListsUI(); // 确保内容是最新的 uiPanel.style.display = 'block'; } } else { if (uiPanel) { uiPanel.style.display = 'none'; } } } function loadConfig() { const storedWhitelistStr = GM_getValue(CONFIG_KEY_WHITELIST); const storedBlacklistStr = GM_getValue(CONFIG_KEY_BLACKLIST); if (typeof storedWhitelistStr === 'string' && storedWhitelistStr.trim() !== '') { userWhitelist = storedWhitelistStr.split(',').map(s => s.trim()).filter(s => s); } else if (typeof storedWhitelistStr === 'undefined') { userWhitelist = [...defaultWhitelistPatterns]; // 使用副本 GM_setValue(CONFIG_KEY_WHITELIST, userWhitelist.join(',')); } else { userWhitelist = []; } if (typeof storedBlacklistStr === 'string' && storedBlacklistStr.trim() !== '') { userBlacklist = storedBlacklistStr.split(',').map(s => s.trim()).filter(s => s); } else if (typeof storedBlacklistStr === 'undefined') { userBlacklist = [...defaultBlacklistPatterns]; // 使用副本 GM_setValue(CONFIG_KEY_BLACKLIST, userBlacklist.join(',')); } else { userBlacklist = []; } // 如果面板已创建且可见,则在加载配置后刷新它 if (uiPanel && panelVisible && document.body.contains(uiPanel)) { renderListsUI(); } // console.log("左键新标签页配置已加载 - 白名单:", userWhitelist, "黑名单:", userBlacklist); } function urlMatchesPattern(url, pattern) { if (!pattern || !url) return false; let currentUrl = url; let p = pattern.trim(); if (!p.includes("://")) { currentUrl = currentUrl.replace(/^https?:\/\//, ""); } if (!p.startsWith("www.") && !p.includes("://") && currentUrl.startsWith("www.")) { currentUrl = currentUrl.replace(/^www\./, ""); } const escapedPattern = p.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); const regexString = '^' + escapedPattern.replace(/\\\*/g, '.*') + '$'; try { return new RegExp(regexString, 'i').test(currentUrl); } catch (e) { return false; } } if (typeof GM_registerMenuCommand === 'function' && typeof GM_getValue === 'function' && typeof GM_setValue === 'function') { GM_registerMenuCommand('配置“新标签页打开”白名单 (强制启用)', function() { const currentWhitelistStr = GM_getValue(CONFIG_KEY_WHITELIST, userWhitelist.join(',')); const newWhitelist = prompt( '请输入网站白名单模式 (强制在此列表网站启用“新标签页打开”功能),用英文逗号 "," 分隔:\n例如: my-allowed-site.com/*, another.com/specific-path/*', currentWhitelistStr ); if (newWhitelist !== null) { userWhitelist = newWhitelist.split(',').map(s => s.trim()).filter(s => s); GM_setValue(CONFIG_KEY_WHITELIST, userWhitelist.join(',')); alert('白名单已更新!'); loadConfig(); // 重新加载并刷新UI(如果可见) } }, 'W'); GM_registerMenuCommand('配置“新标签页打开”黑名单 (禁用功能)', function() { const currentBlacklistStr = GM_getValue(CONFIG_KEY_BLACKLIST, userBlacklist.join(',')); const newBlacklist = prompt( '请输入网站黑名单模式 (在这些网站禁用“新标签页打开”功能),用英文逗号 "," 分隔:\n例如: excluded-site.com/*, another.com/general-area/*', currentBlacklistStr ); if (newBlacklist !== null) { userBlacklist = newBlacklist.split(',').map(s => s.trim()).filter(s => s); GM_setValue(CONFIG_KEY_BLACKLIST, userBlacklist.join(',')); alert('黑名单已更新!'); loadConfig(); } }, 'B'); GM_registerMenuCommand('显示/隐藏“新标签页打开”规则面板', togglePanelVisibility, 'P'); } // --- 主逻辑开始 --- loadConfig(); // 首先加载配置 // 根据保存的状态决定是否初始显示面板 // 这需要在DOM基本可用后执行,以确保document.body存在 const initPanel = () => { if (GM_getValue(CONFIG_KEY_PANEL_VISIBLE, false) === true) { // panelVisible 初始为 false, togglePanelVisibility 会将其设为 true 并显示 togglePanelVisibility(); } }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initPanel); } else { initPanel(); // DOM 已加载 } document.addEventListener('click', function(event) { const currentPageUrl = window.location.href; let scriptShouldRunOnThisPage = true; let whitelisted = false; for (const pattern of userWhitelist) { if (urlMatchesPattern(currentPageUrl, pattern)) { whitelisted = true; break; } } if (whitelisted) { scriptShouldRunOnThisPage = true; } else { let blacklisted = false; for (const pattern of userBlacklist) { if (urlMatchesPattern(currentPageUrl, pattern)) { blacklisted = true; break; } } scriptShouldRunOnThisPage = !blacklisted; } if (!scriptShouldRunOnThisPage) return; if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; let targetElement = event.target; let anchorElement = null; for (let i = 0; i < 5 && targetElement && targetElement !== document.body; i++) { if (targetElement.tagName === 'A') { anchorElement = targetElement; break; } targetElement = targetElement.parentElement; } if (anchorElement) { const href = anchorElement.href; const rawHref = anchorElement.getAttribute('href'); if (!href || (rawHref && rawHref.startsWith('#')) || href.startsWith('javascript:')) return; if (anchorElement.hasAttribute('download')) return; event.preventDefault(); event.stopPropagation(); if (typeof GM_openInTab === 'function') { GM_openInTab(href, { active: true, insert: true }); } else if (typeof window.open === 'function') { window.open(href, '_blank'); } else { console.warn('无法在新标签页中打开链接:GM_openInTab 和 window.open 均不可用。'); } } }, true); })();