// ==UserScript== // @name 快速搜索选中内容 // @name:en Quick Search Selected Text (URL Jump+Page Dark Detect) // @namespace http://tampermonkey.net/ // @version 1.7 // @description 选中网页文本或双击Alt/Option键弹出搜索框,输入内容按回车默认Google搜索,输入网址按回车直接跳转。根据页面背景适配深/浅色模式。 // @description:en Double-press Alt/Option key for search popup. Enter searches Google by default, or navigates if input is a URL. Adapts to page background color. // @author Ven (powered by Gemini) // @match *://*/* // @grant GM_addStyle // @grant GM_openInTab // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/533310/%E5%BF%AB%E9%80%9F%E6%90%9C%E7%B4%A2%E9%80%89%E4%B8%AD%E5%86%85%E5%AE%B9.user.js // @updateURL https://update.greasyfork.icu/scripts/533310/%E5%BF%AB%E9%80%9F%E6%90%9C%E7%B4%A2%E9%80%89%E4%B8%AD%E5%86%85%E5%AE%B9.meta.js // ==/UserScript== (function() { 'use strict'; // --- 配置项 --- const TRIGGER_KEY = 'Alt'; const DOUBLE_PRESS_INTERVAL = 300; const DEFAULT_SEARCH_ENGINE_NAME = 'Google'; const DARK_MODE_LUMINANCE_THRESHOLD = 0.35; const SEARCH_ENGINES = [ { name: '百度', url: 'https://www.baidu.com/s?wd=' }, { name: 'Google', url: 'https://www.google.com/search?q=' }, { name: '淘宝', url: 'https://s.taobao.com/search?q=' }, { name: '京东', url: 'https://search.jd.com/Search?keyword={query}&enc=utf-8&wq={query}' } ]; // --- 状态变量 --- let lastKeyPressTime = 0; let popupVisible = false; let popupElement = null; // --- 样式 (与 v1.6 相同) --- GM_addStyle(` /* --- 通用样式 --- */ #quick-search-popup-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.85); z-index: 99999; border-radius: 12px; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 380px; max-width: 650px; opacity: 0; transition: opacity 0.2s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; visibility: hidden; box-sizing: border-box; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; background-color: rgba(255, 255, 255, 0.7); backdrop-filter: blur(12px) saturate(150%); -webkit-backdrop-filter: blur(12px) saturate(150%); background-image: linear-gradient(to bottom right, rgba(255, 255, 255, 0.75), rgba(245, 245, 255, 0.65)); border: 1px solid rgba(200, 200, 200, 0.5); box-shadow: 0 0 10px 3px rgba(100, 150, 255, 0.15), 0 0 25px 8px rgba(200, 150, 255, 0.1), 0 4px 15px rgba(0, 0, 0, 0.1); } #quick-search-popup-container.visible { opacity: 1; transform: translate(-50%, -50%) scale(1); visibility: visible; } #quick-search-popup-container h3 { margin: 0 0 15px 0; padding-bottom: 10px; font-size: 18px; font-weight: 500; border-bottom: 1px solid rgba(0, 0, 0, 0.1); text-align: center; color: #222; cursor: default; transition: color 0.3s ease, border-color 0.3s ease; } #quick-search-input { width: 100%; padding: 10px 12px; border: 1px solid rgba(204, 204, 204, 0.8); border-radius: 6px; margin-bottom: 15px; font-size: 15px; box-sizing: border-box; background-color: rgba(255, 255, 255, 0.8); color: #333; user-select: text; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease; } #quick-search-input::placeholder { color: #999; transition: color 0.3s ease; } #quick-search-input:focus { outline: none; border-color: rgba(0, 123, 255, 0.8); box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2); } #quick-search-buttons { display: flex; gap: 10px; flex-wrap: wrap; } .quick-search-button { padding: 8px 15px; border: none; background-color: #007bff; color: white; border-radius: 6px; cursor: pointer; font-size: 14px; text-align: center; transition: background-color 0.2s ease, transform 0.1s ease; flex-grow: 1; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .quick-search-button:hover { background-color: #0056b3; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } .quick-search-button:active { transform: translateY(0px); background-color: #004085; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } /* --- 深色模式样式 (通过 .dark-mode 类应用) --- */ #quick-search-popup-container.dark-mode { background-color: rgba(40, 40, 45, 0.75); background-image: linear-gradient(to bottom right, rgba(50, 50, 55, 0.8), rgba(30, 30, 35, 0.7)); border: 1px solid rgba(100, 100, 100, 0.5); box-shadow: 0 0 12px 4px rgba(120, 180, 255, 0.25), 0 0 30px 10px rgba(220, 180, 255, 0.2), 0 4px 20px rgba(0, 0, 0, 0.3); } #quick-search-popup-container.dark-mode h3 { color: #eee; border-bottom-color: rgba(255, 255, 255, 0.15); } #quick-search-popup-container.dark-mode #quick-search-input { background-color: rgba(50, 50, 55, 0.8); border-color: rgba(100, 100, 100, 0.7); color: #eee; } #quick-search-popup-container.dark-mode #quick-search-input::placeholder { color: #bbb; } #quick-search-popup-container.dark-mode #quick-search-input:focus { border-color: rgba(100, 170, 255, 0.8); box-shadow: 0 0 0 2px rgba(100, 170, 255, 0.25); } `); // --- 功能函数 --- // --- 颜色处理和亮度计算 (与 v1.6 相同) --- function parseColor(colorString) { if (!colorString) return null; let match = colorString.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/i); if (match) { return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]), a: match[4] !== undefined ? parseFloat(match[4]) : 1 }; } match = colorString.match(/^#([a-f0-9]{3}|[a-f0-9]{6})$/i); if (match) { let hex = match[1]; if (hex.length === 3) hex = hex.split('').map(char => char + char).join(''); return { r: parseInt(hex.substring(0, 2), 16), g: parseInt(hex.substring(2, 4), 16), b: parseInt(hex.substring(4, 6), 16), a: 1 }; } if (colorString.toLowerCase() === 'transparent') { return { r: 0, g: 0, b: 0, a: 0 }; } return null; } function getLuminance(rgb) { if (!rgb) return 1; const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => { c /= 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } function isPageDark() { try { let element = document.body; let bgColor = window.getComputedStyle(element).backgroundColor; let rgb = parseColor(bgColor); if (rgb && rgb.a < 0.1) { element = document.documentElement; bgColor = window.getComputedStyle(element).backgroundColor; rgb = parseColor(bgColor); if (rgb && rgb.a < 0.1) return false; } if (!rgb) { console.warn("Quick Search Script: Could not determine page background color."); return false; } const luminance = getLuminance(rgb); return luminance < DARK_MODE_LUMINANCE_THRESHOLD; } catch (error) { console.error("Quick Search Script: Error detecting page darkness:", error); return false; } } // --- URL 检测函数 --- /** * 检查字符串是否像一个 URL * @param {string} text - 要检查的文本 * @returns {boolean} - 如果像 URL 返回 true */ function isLikelyUrl(text) { if (!text) return false; text = text.trim(); if (text.includes(' ') || text.length < 4) { // URL 不应包含空格,且长度通常大于3 return false; } // 检查是否以常见协议开头 if (/^(https?:\/\/|ftp:\/\/|file:\/\/)/i.test(text)) { return true; } // 检查是否是常见的域名模式 (e.g., example.com, localhost, IP) // 允许 localhost if (text.toLowerCase() === 'localhost' || text.startsWith('localhost:')) { return true; } // 允许 IP 地址 (简单检查) if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/.test(text)) { // 进一步检查 IP 段是否有效 (0-255) const parts = text.split(':')[0].split('.'); if (parts.length === 4 && parts.every(part => parseInt(part) >= 0 && parseInt(part) <= 255)) { return true; } } // 检查类域名格式: 包含至少一个点,点前后有字符,顶级域名至少2个字母 if (text.includes('.') && /^[a-z0-9-]+(\.[a-z0-9-]+)+(\:[0-9]+)?(\/.*)?$/i.test(text)) { // 排除明显不是 URL 的情况,例如文件名 "file.txt" if (text.includes('.') && !text.endsWith('.') && text.indexOf('.') > 0) { // 检查顶级域名部分是否至少有两个字母 const parts = text.split('/'); // 移除路径部分 const domainParts = parts[0].split(':'); // 移除端口部分 const hostParts = domainParts[0].split('.'); if (hostParts.length > 1 && hostParts[hostParts.length - 1].length >= 2 && /^[a-z]+$/i.test(hostParts[hostParts.length - 1])) { return true; } } } return false; } // 创建搜索弹窗 (逻辑不变) function createPopup() { if (popupElement) return; popupElement = document.createElement('div'); popupElement.id = 'quick-search-popup-container'; popupElement.innerHTML = `