Greasy Fork is available in English.
搜索引擎切换,增加临时关闭、域名黑白名单、一键添加域名功能。修复布局问题,增加文字头像兜底。
// ==UserScript== // @name 轻量级聚合搜索-精美侧边栏版 (Gemini修改版) // @name:zh-CN 轻量级聚合搜索-精美侧边栏版 (Gemini修改版) // @description:zh-tw 轻量级聚合搜索-精美侧边栏版 (Gemini修改版) // @namespace http://bbs.91wc.net/aggregate-search.htm // @version 12.8.0 // @description 搜索引擎切换,增加临时关闭、域名黑白名单、一键添加域名功能。修复布局问题,增加文字头像兜底。 // @description:en Switch search engine, with temporary close, domain whitelist/blacklist, and one-click add domain features. // @author Wilson & Modified by Gemini // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @match *://*/* // @exclude *://www.google.com/recaptcha/* // @exclude *://gmail.com/* // @exclude *://mail.*.com/* // @exclude *://mail.163.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_openInTab // @license GPL License // ==/UserScript== (function($) { 'use strict'; try { if (window.top !== window.self) return; } catch (e) { return; } // --- 脚本运行前置检查 --- var scriptEnabled = GM_getValue("wish_script_enabled", true); var domainMode = GM_getValue("wish_domain_mode", "blacklist"); // 可选: blacklist, whitelist, disabled var domainListText = GM_getValue("wish_domain_list", "gmail.com\nmail.google.com\nmail.163.com"); var domainList = domainListText.split('\n').map(d => d.trim()).filter(Boolean); var currentHost = window.location.hostname; if (!scriptEnabled) { $('body').append(` <div id="wish-reenable-btn" title="聚合搜索已停用,点击启用" style="position: fixed; left: 10px; bottom: 10px; z-index: 2147483647; background: #f44336; color: white; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.3); font-size: 20px;"> <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg> </div> `); $('#wish-reenable-btn').on('click', function() { GM_setValue("wish_script_enabled", true); location.reload(); }); return; } if (domainMode === 'whitelist') { let isWhitelisted = domainList.some(domain => currentHost.includes(domain)); if (!isWhitelisted) { console.log('聚合搜索: 当前域名不在白名单中,脚本已禁用。'); return; } } else if (domainMode === 'blacklist') { let isBlacklisted = domainList.some(domain => currentHost.includes(domain)); if (isBlacklisted) { console.log('聚合搜索: 当前域名在黑名单中,脚本已禁用。'); return; } } // --- 默认配置 --- var DEFAULT_CONFIG = { is_google_blank: 1, cache_days: 30, trigger_width: 20, panel_width: 280, panel_width_icon: 80, panel_height: 540, win_width: 900, win_height: 700, is_pinned: false, item_height: 40, global_hotkey: "Ctrl+g", trigger_buttons: [0], trigger_mode: "hover", batch_open_delay_ms: 200, batch_open_background: true }; var defaultLinkListText = ` [谷歌搜索] [https://www.google.com/search?q=%s] [百度搜索] [https://www.baidu.com/s?wd=%s] [Bing搜索] [https://cn.bing.com/search?q=%s] [B站] [http://search.bilibili.com/all?keyword=%s] [微信] [http://weixin.sogou.com/weixin?type=2&query=%s] [Yandex] [https://yandex.com/search/?text=%s] [GitHub] [https://github.com/search?utf8=✓&q=%s] [知乎] [https://www.zhihu.com/search?type=content&q=%s] [淘宝] [https://s.taobao.com/search?q=%s] [京东] [http://search.jd.com/Search?keyword=%s] [豆瓣] [https://www.douban.com/search?source=suggest&q=%s] [YouTube] [https://www.youtube.com/results?search_query=%s] [百度翻译] [https://fanyi.baidu.com/#en/zh/%s] [谷歌翻译] [https://translate.google.com/?text=%s] [维基百科] [https://zh.wikipedia.org/wiki/%s] [Stackoverflow] [https://stackoverflow.com/search?q=%s] [Startpage] [https://www.startpage.com/sp/search?q=%s] [DuckDuckGo] [https://duckduckgo.com/?q=%s] `.trim(); var DEFAULT_FOLDER_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g0PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgaGVpZHRoPSIxOCIgZmlsbD0iI2JiYiI+PHBhdGggZD0iTTEwIDRINmEyIDIgMCAwMC0yIDJ2MTJhMiAyIDAgMDAyIDJoMTJhMiAyIDAgMDAyLTJWOGEyIDIgMCAwMC0yLTJkLTgtNi0yLTJ6Ii8+PC9zdmc+"; var ICONS = { pin_outline: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7l-2 2v2h10v-2l-2-2V5a3 3 0 0 0-3-3z"></path><path d="M12 14v8"></path></svg>', pin_filled: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7l-2 2v2h10v-2l-2-2V5a3 3 0 0 0-3-3z"></path><path d="M12 14v8"></path></svg>', settings: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1.82 1.51l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>', layout: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>', more: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>', power: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>', close: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>', shield: '<svg style="pointer-events:none" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>' }; var trim = str => (typeof str === 'string' ? str.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : str); var getDomain = url => { try { return new URL(url).hostname; } catch (e) { return ""; } }; var generateUniqueId = () => 'se-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); var parseButtonList = function(value) { if (Array.isArray(value)) return value.map(v => parseInt(v)).filter(v => !isNaN(v)); if (typeof value === "string") { return value.split(",").map(v => parseInt(v)).filter(v => !isNaN(v)); } if (typeof value === "number") return [value]; return []; }; var isEditableActive = function() { var el = document.activeElement; if (!el) return false; var tag = (el.tagName || "").toLowerCase(); if (tag === "input" || tag === "textarea" || tag === "select") return true; if (el.isContentEditable) return true; return false; }; // --- 辅助函数:根据名字生成颜色和文字头像 --- var generateLetterIcon = function(name) { if (!name) name = "?"; var letter = name.charAt(0).toUpperCase(); // 1. 根据名字计算 Hash 值,用于生成伪随机颜色 // (这样做的好处是:同一个网站每次刷新颜色是固定的,但不同网站颜色看起来是随机的) var hash = 0; for (var i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } // 2. 使用 HSL 生成颜色 // Hue (色相): 0 - 360 (全色谱随机) var h = Math.abs(hash) % 360; // Saturation (饱和度): 70% - 90% (保证颜色鲜艳) var s = 70 + (Math.abs(hash) % 20); // Lightness (亮度): 40% - 55% (关键点:控制在中间值,既能在白底看清,也能在黑底看清) var l = 40 + (Math.abs(hash) % 15); var color = `hsl(${h}, ${s}%, ${l}%)`; // 3. 生成无背景 SVG var svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> <text x="50%" y="50%" dy=".35em" text-anchor="middle" fill="${color}" font-family="sans-serif" font-weight="900" font-size="24">${letter}</text> </svg>`; return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))); }; var searchDataList = []; var searchDataMap = new Map(); var IconManager = { get: function(domain, name, callback) { if (!domain) return callback(""); var key = "icon_v2_" + domain; var cached = GM_getValue(key); var now = Date.now(); if (cached && (now - cached.time < DEFAULT_CONFIG.cache_days * 86400000)) { return callback(cached.data); } // 缓存不存在,先不返回,继续请求 var faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; GM_xmlhttpRequest({ method: "GET", url: faviconUrl, responseType: "blob", onload: function(response) { var reader = new FileReader(); reader.onloadend = function() { var base64data = reader.result; if (base64data && base64data.length > 100) { GM_setValue(key, { data: base64data, time: now }); callback(base64data); // 获取成功,回调图片 } else { // 获取失败时不存空值,下次再试,或者由UI保持显示文字头像 } }; reader.readAsDataURL(response.response); }, onerror: function() { } }); } }; var DataManager = { parseTextAndAssignIds: function(text) { var lines = text.split(/\r?\n/); var result = []; lines.forEach(line => { line = trim(line); if (!line) return; var match = line.match(/\[(.*?)\]\s*\[(.*?)\]/); if (match) { var name = match[1].trim(); var url = match[2].trim(); var isNewWindow = /\[\s*?新窗口(打开)?\s*?\]/.test(line); var isHidden = /\[\s*?隐藏\s*?\]/.test(line); var isGroup = url.startsWith("group://"); result.push({ id: isGroup ? null : generateUniqueId(), name: name, url: url, newWindow: isNewWindow, hidden: isHidden }); } }); return result; }, load: function() { var dataV2 = GM_getValue("wish_search_data_with_ids"); if (dataV2) { try { searchDataList = JSON.parse(dataV2); } catch(e) { searchDataList = this.parseTextAndAssignIds(defaultLinkListText); } } else { var oldData = GM_getValue("wish_s_searchlinklist") || defaultLinkListText; searchDataList = this.parseTextAndAssignIds(oldData); this.save(); } searchDataMap.clear(); searchDataList.forEach(item => { if (item.id) searchDataMap.set(item.id, item); }); }, save: function() { GM_setValue("wish_search_data_with_ids", JSON.stringify(searchDataList)); searchDataMap.clear(); searchDataList.forEach(item => { if (item.id) searchDataMap.set(item.id, item); }); } }; var main = function() { DataManager.load(); var savedDelay = GM_getValue("wish_config_trigger_delay", DEFAULT_CONFIG.trigger_delay); var savedPanelW = GM_getValue("wish_panel_w", DEFAULT_CONFIG.panel_width); var savedPanelWIcon = GM_getValue("wish_panel_w_icon", DEFAULT_CONFIG.panel_width_icon); var savedPanelH = GM_getValue("wish_panel_h", DEFAULT_CONFIG.panel_height); var savedItemH = GM_getValue("wish_item_height", DEFAULT_CONFIG.item_height); var savedTriggerW = GM_getValue("wish_trigger_width", DEFAULT_CONFIG.trigger_width); var savedHotkey = GM_getValue("wish_global_hotkey", DEFAULT_CONFIG.global_hotkey); var savedTriggerMode = GM_getValue("wish_trigger_mode", DEFAULT_CONFIG.trigger_mode); var savedBatchDelay = GM_getValue("wish_batch_open_delay_ms", DEFAULT_CONFIG.batch_open_delay_ms); var savedBatchBackground = GM_getValue("wish_batch_open_background", DEFAULT_CONFIG.batch_open_background); var savedDefaultGroupUrl = GM_getValue("wish_default_group_url", "__first__"); var isPinned = GM_getValue("wish_pinned", false); var savedTriggerButtons = GM_getValue("wish_trigger_mouse_buttons", DEFAULT_CONFIG.trigger_buttons.join(",")); var triggerButtons = parseButtonList(savedTriggerButtons); if (triggerButtons.length === 0) triggerButtons = DEFAULT_CONFIG.trigger_buttons.slice(); var isTemporarilyClosed = false; var CONFIG = { ...DEFAULT_CONFIG, trigger_delay: parseInt(savedDelay), panel_width: parseInt(savedPanelW), panel_width_icon: parseInt(savedPanelWIcon), panel_height: parseInt(savedPanelH), trigger_width: parseInt(savedTriggerW), item_height: parseInt(savedItemH), global_hotkey: savedHotkey, trigger_buttons: triggerButtons, trigger_mode: savedTriggerMode, batch_open_delay_ms: parseInt(savedBatchDelay), batch_open_background: savedBatchBackground, default_group_url: savedDefaultGroupUrl || "__first__" }; if (isNaN(CONFIG.trigger_width) || CONFIG.trigger_width < 2) CONFIG.trigger_width = DEFAULT_CONFIG.trigger_width; if (isNaN(CONFIG.batch_open_delay_ms) || CONFIG.batch_open_delay_ms < 0) CONFIG.batch_open_delay_ms = DEFAULT_CONFIG.batch_open_delay_ms; if (document.domain.indexOf("google.com") !== -1 && CONFIG.is_google_blank) { $("#search .rc a").attr("target", "_blank"); } var getKeyword = function() { var sidebarInput = $("#wish-search-input").val(); if (sidebarInput && sidebarInput.trim() !== "") return encodeURIComponent(sidebarInput.trim()); var val = $("input[name=q], input[name=wd], input[name=query], input[name=text], input[name=p], #kw, #search_input, input[type=search]").val(); if (!val) { var selection = window.getSelection().toString(); if(selection && selection.trim() !== "") return encodeURIComponent(selection.trim()); } return encodeURIComponent((val || "").trim()); }; var renderList = function() { var html = ""; var index = 0; searchDataList.forEach(item => { if (item.hidden) return; var isGroup = item.url.startsWith("group://"); var shortcutHint = (index < 9) ? `Alt+${index + 1}` : ""; if (isGroup) { // --- 组的处理逻辑 --- var groupIds = []; try { groupIds = JSON.parse(decodeURIComponent(item.url.replace("group://", ""))); } catch (e) {} // 获取组内前4个 var subItems = groupIds.map(id => searchDataMap.get(id)).filter(Boolean).slice(0, 4); var iconHtml = ""; if (subItems.length > 0) { // 【这里是关键】:四宫格模式 iconHtml = '<div class="wish-group-grid">'; subItems.forEach(sub => { var subDomain = getDomain(sub.url.replace("%s", "")); // 1. 核心代码:生成文字头像 (例如 "谷歌" -> "谷") var subFallback = generateLetterIcon(sub.name); // 2. 将 src 默认设为文字头像 // class="wish-group-subicon" 会被脚本底部的逻辑尝试替换为真实图标,如果替换失败,就保持显示文字 iconHtml += `<img class="wish-group-subicon" data-domain="${subDomain}" src="${subFallback}" style="width:100%;height:100%;object-fit:cover;">`; }); iconHtml += '</div>'; } else { // 空组的情况:显示组名的第一个字 var groupLetterIcon = generateLetterIcon(item.name); iconHtml = `<img class="wish-icon" src="${groupLetterIcon}" />`; } html += ` <div class="wish-item wish-group-item" data-index="${index++}" data-url='${item.url}'> <div class="wish-checkbox-placeholder"></div> <a href="javascript:;" class="wish-link"> <div class="wish-icon-container">${iconHtml}</div> <span class="wish-text" style="font-weight:600; color:var(--wish-primary)">${item.name}</span> <span class="wish-shortcut-hint">${shortcutHint}</span> </a> </div>`; } else { // --- 普通列表项的处理逻辑 --- var url = item.url; if (url.indexOf("%s") === -1) url += "%s"; var domain = getDomain(url.replace("%s", "")); // 1. 核心代码:生成文字头像 var textIcon = generateLetterIcon(item.name); // 2. 将 src 默认设为文字头像 html += ` <div class="wish-item" data-index="${index++}" data-id="${item.id}" data-url="${url}" data-original-name="${item.name}" data-target="${item.newWindow ? '_blank' : '_self'}"> <label class="wish-checkbox-wrapper"> <input type="checkbox" class="wish-check"> <span class="wish-checkmark"></span> </label> <a href="javascript:;" class="wish-link" title="${item.name}"> <div class="wish-icon-container"> <img class="wish-icon" data-domain="${domain}" data-name="${item.name}" src="${textIcon}" /> </div> <span class="wish-text">${item.name}</span> <span class="wish-shortcut-hint">${shortcutHint}</span> </a> </div>`; } }); return html; }; var initUI = function() { var pos = GM_getValue("wish_s_position", "auto"); var initialSideClass = (pos === 'right') ? 'wish-side-right' : 'wish-side-left'; var currentLayout = GM_getValue("wish_layout_mode", "full"); var initialPanelWidth = (currentLayout === 'icon-only') ? CONFIG.panel_width_icon : CONFIG.panel_width; var css = ` <style> :root { --wish-bg: rgba(255, 255, 255, 0.9); --wish-border: rgba(255, 255, 255, 0.7); --wish-shadow: 0 16px 40px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255,255,255,0.4) inset; --wish-item-bg: rgba(255, 255, 255, 0.4); --wish-item-hover: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(255,255,255,0.7)); --wish-item-active: rgba(0, 122, 255, 0.1); --wish-item-shadow: 0 2px 6px rgba(0,0,0,0.04); --wish-item-hover-shadow: 0 6px 14px rgba(0,0,0,0.09); --wish-primary: #007aff; --wish-text: #333; --wish-panel-w: ${initialPanelWidth}px; --wish-panel-h: ${CONFIG.panel_height}px; --wish-item-h: ${CONFIG.item_height}px; --wish-trigger-gap: ${CONFIG.trigger_width}px; } @media (prefers-color-scheme: dark) { :root { --wish-bg: rgba(40, 40, 40, 0.9); --wish-border: rgba(255, 255, 255, 0.1); --wish-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255,255,255,0.05) inset; --wish-item-bg: rgba(255, 255, 255, 0.05); --wish-item-hover: linear-gradient(135deg, rgba(255,255,255,0.15), rgba(255,255,255,0.1)); --wish-item-active: rgba(10, 132, 255, 0.3); --wish-item-shadow: 0 2px 5px rgba(0,0,0,0.2); --wish-item-hover-shadow: 0 6px 12px rgba(0,0,0,0.4); --wish-text: #f0f0f0; } } .wish-list::-webkit-scrollbar { width: 3px; height: 3px; } .wish-list::-webkit-scrollbar-track { background: transparent; } .wish-list::-webkit-scrollbar-thumb { background: transparent; border-radius: 3px; } .wish-list.scrolling::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.4); transition: background 0.2s; } .wish-list.scrolling::-webkit-scrollbar-thumb:hover { background: rgba(128,128,128,0.6); } .wish-trigger-zone { position: fixed; top: 0; bottom: 0; width: ${CONFIG.trigger_width}px; z-index: 2147483646; background: transparent; cursor: default; } .wish-trigger-zone.wish-highlight { z-index: 2147483649; background: rgba(0, 122, 255, 0.35); box-shadow: 0 0 16px rgba(0, 122, 255, 0.95); } #wish-trigger-left { left: 0; } #wish-trigger-right { right: 0; } #wish-panel { position: fixed; width: var(--wish-panel-w); height: var(--wish-panel-h); background: var(--wish-bg); backdrop-filter: blur(25px) saturate(180%); -webkit-backdrop-filter: blur(25px) saturate(180%); border: 1px solid var(--wish-border); box-shadow: var(--wish-shadow); z-index: 2147483647; border-radius: 16px; display: flex; flex-direction: column; opacity: 0; pointer-events: none; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; -webkit-font-smoothing: antialiased; transition: width 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.3s; } #wish-panel.wish-side-left { left: 16px; transform: translateX(-120%); } #wish-panel.wish-side-left.wish-active { transform: translateX(0); opacity: 1; pointer-events: auto; } #wish-panel.wish-side-right { right: 16px; transform: translateX(120%); } #wish-panel.wish-side-right.wish-active { transform: translateX(0); opacity: 1; pointer-events: auto; } .wish-resize-bar { position: absolute; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 10; } #wish-panel.wish-side-left .wish-resize-bar { right: -3px; } #wish-panel.wish-side-right .wish-resize-bar { left: -3px; } .wish-resize-bar-bottom { position: absolute; left: 0; right: 0; bottom: -3px; height: 6px; cursor: row-resize; z-index: 10; } /* 问题1修复:Top Row 布局调整,容纳所有按钮 */ .wish-top-row { display: flex; align-items: center; padding: 10px 8px 4px 8px; gap: 4px; flex-shrink: 0; width: 100%; box-sizing: border-box; z-index: 20; position: relative; } #wish-search-input { flex: 1 1 auto; min-width: 0; width: 100px; /* 允许缩小 */ box-sizing: border-box; padding: 0 12px; border-radius: 99px; border: 1px solid rgba(128,128,128,0.15); background: var(--wish-item-bg); color: var(--wish-text); font-size: 13px; outline: none; transition: all 0.25s; box-shadow: inset 0 2px 4px rgba(0,0,0,0.03); height: 30px; line-height: 30px; } #wish-search-input:focus { border-color: var(--wish-primary); background: var(--wish-bg); box-shadow: 0 4px 12px rgba(0,122,255,0.15); } .wish-icon-btn { width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; color: var(--wish-text); opacity: 0.6; transition: all 0.2s; background: transparent; } .wish-icon-btn:hover { opacity: 1; background: var(--wish-item-bg); color: var(--wish-primary); transform:scale(1.1); } .wish-icon-btn.active { opacity: 1; color: var(--wish-primary); background: var(--wish-item-bg); } .wish-list { flex: 1 1 auto; height: 0; overflow-y: auto; overflow-x: hidden; padding: 8px 14px; min-height: 0; } .wish-item, .wish-batch-btn, .wish-btn-light, .wish-tooltip-item { display: flex; align-items: center; height: var(--wish-item-h); border-radius: 999px; margin-bottom: 6px; background: var(--wish-item-bg); border: 1px solid transparent; box-shadow: var(--wish-item-shadow); transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1); position:relative; cursor: pointer; color: var(--wish-text); text-decoration: none !important; } .wish-batch-btn, .wish-btn-light { justify-content: center; width: 100%; font-size: calc(var(--wish-item-h) * 0.34); font-weight: 600; padding: 0; } .wish-item:hover, .wish-batch-btn:hover, .wish-btn-light:hover, .wish-tooltip-item:hover { background: var(--wish-item-hover); box-shadow: var(--wish-item-hover-shadow); transform: translateY(-2px); border-color: rgba(255,255,255,0.3); z-index: 2; } .wish-item:active, .wish-item.wish-selected, .wish-batch-btn:active, .wish-btn-light:active, .wish-tooltip-item:active { background: var(--wish-item-active); box-shadow: inset 0 2px 4px rgba(0,0,0,0.05); transform: scale(0.98); } .wish-item.wish-selected { border-color: var(--wish-primary); } .wish-batch-btn { color: var(--wish-primary); } .wish-btn-light { color: var(--wish-text); opacity: 0.9; } .wish-checkbox-wrapper { display: flex; align-items: center; height: 100%; position: relative; width: 20px; margin-right: 6px; padding-left: 10px; cursor: pointer; user-select: none; } .wish-checkbox-placeholder { width: 36px; height: 100%; } .wish-check { position: absolute; opacity: 0; cursor: pointer; } .wish-checkmark { position: absolute; left: 10px; background: transparent; border: 1.5px solid rgba(128,128,128,0.4); border-radius: 50%; width: 14px; height: 14px; transition:0.2s; } .wish-item:hover .wish-checkmark { border-color: rgba(128,128,128,0.8); } .wish-check:checked ~ .wish-checkmark { background-color: var(--wish-primary); border-color: var(--wish-primary); box-shadow: 0 2px 4px rgba(0,122,255,0.3); } .wish-checkmark:after { content: ""; position: absolute; display: none; left: 35%; top: 15%; width: 25%; height: 50%; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } .wish-check:checked ~ .wish-checkmark:after { display: block; } .wish-link { flex: 1; display: flex; align-items: center; text-decoration: none !important; color: inherit; margin-left: 2px; overflow: hidden; height: 100%; } .wish-icon-container { width: 22px; height: 22px; margin-right: 12px; flex-shrink: 0; display:flex; align-items:center; justify-content:center;} .wish-icon { width: 100%; height: 100%; border-radius: 5px; object-fit: contain; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.1)); } .wish-group-grid { width: 100%; height: 100%; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.1); gap: 1px; } .wish-group-subicon { width: 100%; height: 100%; object-fit: cover; } .wish-text { font-size: calc(var(--wish-item-h) * 0.35); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex:1;} .wish-shortcut-hint { font-size: 11px; color: inherit; margin-left: 8px; opacity: 0.8; font-family: monospace; font-weight: 600; padding-right: 10px; } .wish-footer { padding: 8px 14px 12px 14px; border-top: 1px solid rgba(128,128,128,0.1); text-align: center; flex-shrink: 0; display: none; flex-direction: column; gap: 8px; width: 100%; box-sizing: border-box; z-index: 10; } .wish-footer-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; } .wish-footer-links { font-size: 11px; color: var(--wish-text); opacity: 0.6; display:flex; gap:10px; align-items:center; } .wish-action-link { cursor: pointer; transition: opacity 0.2s; white-space:nowrap; } .wish-action-link:hover { opacity: 1; color: var(--wish-primary); } #wish-tooltip-panel { position: fixed; z-index: 2147483648; background: var(--wish-bg); backdrop-filter: blur(20px) saturate(180%); border: 1px solid var(--wish-border); box-shadow: 0 10px 40px rgba(0,0,0,0.2); border-radius: 12px; padding: 8px; display: none; width: 220px; max-height: 400px; overflow-y: auto; pointer-events: auto; } .wish-tooltip-item { padding: 0 10px; margin-bottom: 4px; } .wish-tooltip-icon { width: 20px; height: 20px; margin-right: 10px; border-radius: 4px; object-fit:contain; } .wish-tooltip-text { font-size: calc(var(--wish-item-h) * 0.35); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #wish-actions-menu { position: fixed; z-index: 2147483648; background: var(--wish-bg); backdrop-filter: blur(20px) saturate(180%); border: 1px solid var(--wish-border); box-shadow: 0 10px 40px rgba(0,0,0,0.2); border-radius: 12px; padding: 6px; display: none; flex-direction: column; gap: 4px; } #wish-panel.wish-icon-only-layout { min-width: 60px; } #wish-panel.wish-icon-only-layout .wish-top-row { justify-content: center; padding: 6px 4px; } #wish-menu-btn { display: none; } #wish-panel.wish-icon-only-layout #wish-close-btn { display: none; } /* 在icon模式下,隐藏顶部工具栏的所有按钮,只显示 menu 按钮 */ #wish-panel.wish-icon-only-layout #wish-domain-btn, #wish-panel.wish-icon-only-layout #wish-layout-btn, #wish-panel.wish-icon-only-layout #wish-pin-btn, #wish-panel.wish-icon-only-layout #wish-disable-btn, #wish-panel.wish-icon-only-layout #wish-open-setting, #wish-panel.wish-icon-only-layout #wish-search-input, #wish-panel.wish-icon-only-layout #wish-close-btn { display: none; } #wish-panel.wish-icon-only-layout #wish-menu-btn { display: flex; } #wish-panel.wish-icon-only-layout .wish-list { padding: 4px 8px; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-content: flex-start; } #wish-panel.wish-icon-only-layout .wish-item { width: 38px; height: 38px; margin-bottom: 0; justify-content: center; } #wish-panel.wish-icon-only-layout .wish-link { padding: 0; margin: 0; width: 100%; height: 100%; justify-content: center; } #wish-panel.wish-icon-only-layout .wish-icon-container { margin: 0; width: 24px; height: 24px; } #wish-panel.wish-icon-only-layout .wish-text, #wish-panel.wish-icon-only-layout .wish-shortcut-hint, #wish-panel.wish-icon-only-layout .wish-checkbox-wrapper, #wish-panel.wish-icon-only-layout .wish-checkbox-placeholder, #wish-panel.wish-icon-only-layout .wish-footer { display: none !important; } /* 设置界面样式 (保持原样) */ #wish-setting-overlay, #wish-delete-overlay, #wish-group-editor-overlay, #wish-batch-import-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); z-index: 2147483648; display: none; backdrop-filter: blur(4px); justify-content: center; align-items: center; } #wish-setting-overlay.wish-overlay-cutout-both { background: linear-gradient(to right, transparent 0, transparent var(--wish-trigger-gap), rgba(0,0,0,0.4) var(--wish-trigger-gap), rgba(0,0,0,0.4) calc(100% - var(--wish-trigger-gap)), transparent calc(100% - var(--wish-trigger-gap)), transparent 100%); } #wish-setting-overlay.wish-overlay-cutout-left { background: linear-gradient(to right, transparent 0, transparent var(--wish-trigger-gap), rgba(0,0,0,0.4) var(--wish-trigger-gap), rgba(0,0,0,0.4) 100%); } #wish-setting-overlay.wish-overlay-cutout-right { background: linear-gradient(to right, rgba(0,0,0,0.4) 0, rgba(0,0,0,0.4) calc(100% - var(--wish-trigger-gap)), transparent calc(100% - var(--wish-trigger-gap)), transparent 100%); } #wish-setting-box, #wish-delete-box, #wish-group-editor-box, #wish-batch-import-box { background: #fff; padding: 0; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); display: flex; flex-direction: column; overflow: hidden; position: relative; border: 1px solid rgba(0,0,0,0.1); max-width: 90vw; max-height: 90vh; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: #333; } @media (prefers-color-scheme: dark) { #wish-setting-box, #wish-delete-box, #wish-group-editor-box, #wish-batch-import-box { background: #222; border-color: #444; color: #eee; } } #wish-setting-box { width: ${GM_getValue('wish_win_w', CONFIG.win_width)}px; height: ${GM_getValue('wish_win_h', CONFIG.win_height)}px; } #wish-delete-box { width: 360px; max-height: 80vh; } #wish-group-editor-box { width: 450px; height: 500px; } #wish-batch-import-box { width: 500px; } .ws-header { padding: 14px 20px; border-bottom: 1px solid rgba(128,128,128,0.15); font-weight: 600; font-size: 15px; display:flex; justify-content:space-between; background: rgba(128,128,128,0.05); cursor: move; } .ws-body { flex: 1; display: flex; flex-direction: column; overflow: hidden; padding: 16px; } .ws-config-row { display: flex; gap: 12px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; } .ws-input-sm { width: 45px; padding: 4px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size:12px; background: transparent; color: inherit; } .ws-input-key { width: 90px; padding: 4px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size:12px; font-family:monospace; background: transparent; color: inherit;} .ws-toolbar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap;} .ws-btn { padding: 6px 12px; border: 1px solid rgba(128,128,128,0.3); background: rgba(128,128,128,0.05); border-radius: 20px; cursor: pointer; font-size: 12px; color: inherit; white-space:nowrap; transition: all 0.2s;} .ws-btn:hover { background: rgba(128,128,128,0.15); border-color: rgba(128,128,128,0.5); transform: translateY(-1px); } .ws-btn-red { color: #e53935; border-color: rgba(229, 57, 53, 0.3); } .ws-btn-red:hover { background: rgba(229, 57, 53, 0.1); border-color: #e53935; } .ws-btn-primary { background: var(--wish-primary); color: white; border: none; } .ws-btn-primary:hover { filter: brightness(1.1); background: var(--wish-primary); } .ws-list-container { flex: 1; border: 1px solid rgba(128,128,128,0.15); border-radius: 8px; overflow-y: auto; background: rgba(128,128,128,0.03); } .ws-list-item { display: flex; align-items: center; padding: 0 10px; height: 40px; border-bottom: 1px solid rgba(128,128,128,0.1); background: transparent; } .ws-list-item:last-child { border-bottom: none; } .ws-drag-handle { margin-right: 8px; color: #999; cursor: grab; font-size: 14px; padding: 0 6px; height:100%; display: flex; align-items: center; } .ws-drag-handle:hover { color: var(--wish-primary); background: rgba(128,128,128,0.1); } .ws-icon-preview { width: 22px; height: 22px; object-fit: contain; margin-right: 10px; flex-shrink: 0; border-radius: 4px; } .ws-item-inputs { flex: 1; display: flex; gap: 8px; align-items: center; width: 100%; } .ws-input { padding: 4px 8px; border: 1px solid transparent; background: transparent; border-radius: 6px; font-size: 13px; color: inherit; height: 30px; box-sizing: border-box; transition: 0.2s;} .ws-input:focus { border-color: var(--wish-primary); background: rgba(128,128,128,0.05); outline: none; } .ws-input-name { width: 90px; flex-shrink: 0; font-weight: 600; } .ws-input-url { flex: 1; color: #666; min-width: 0; } .ws-chk-label { font-size: 12px; opacity:0.8; display:flex; align-items:center; gap:4px; cursor:pointer; margin-left:8px; white-space:nowrap; } .ws-footer { padding: 14px 20px; border-top: 1px solid rgba(128,128,128,0.15); display: flex; justify-content: flex-end; gap: 10px; background: rgba(128,128,128,0.03); } .ws-resize-handle { position: absolute; bottom: 0; right: 0; width: 16px; height: 16px; cursor: nwse-resize; z-index: 10; opacity: 0.5; background: linear-gradient(135deg, transparent 50%, var(--wish-primary) 50%); } .ws-group-option { display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid rgba(128,128,128,0.1); cursor: pointer; transition:background 0.1s; } .ws-group-option:hover { background: rgba(128,128,128,0.05); } .ws-group-option input { margin-right: 12px; } .ws-group-option img { width: 20px; height: 20px; margin-right: 12px; object-fit:contain; border-radius:4px; } .ws-group-option span { font-size: 13px; } .ws-group-option .url-hint { font-size: 12px; opacity:0.5; margin-left: 10px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; } .del-list-item { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid rgba(128,128,128,0.1); } .del-icon { width: 18px; height: 18px; margin-right: 10px; } .del-info { display: flex; flex-direction: column; overflow: hidden; } .del-name { font-weight: bold; font-size: 13px; } .del-url { font-size: 12px; opacity:0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } </style>`; var triggersHtml = ''; if (pos === 'auto') { triggersHtml = `<div id="wish-trigger-left" class="wish-trigger-zone" title="左侧展开"></div><div id="wish-trigger-right" class="wish-trigger-zone" title="右侧展开"></div>`; } else if (pos === 'left') { triggersHtml = `<div id="wish-trigger-left" class="wish-trigger-zone" title="展开"></div>`; } else { triggersHtml = `<div id="wish-trigger-right" class="wish-trigger-zone" title="展开"></div>`; } // 问题2:在Top Row添加 域名管理按钮 (#wish-domain-btn) 和 移动关闭按钮 (#wish-close-btn) var html = ` ${css} ${triggersHtml} <input type="file" id="ws-import-file-input" accept=".json" style="display:none;"> <div id="wish-panel" class="${initialSideClass} ${isPinned ? 'wish-pinned wish-active' : ''} ${currentLayout === 'icon-only' ? 'wish-icon-only-layout' : ''}"> <div class="wish-resize-bar" title="拖动调整宽度"></div> <div class="wish-resize-bar-bottom" title="拖动调整高度"></div> <div class="wish-top-row"> <input type="text" id="wish-search-input" placeholder="输入关键词"> <div class="wish-icon-btn" id="wish-domain-btn" title="添加当前域名到名单">${ICONS.shield}</div> <div class="wish-icon-btn" id="wish-layout-btn" title="切换布局">${ICONS.layout}</div> <div class="wish-icon-btn" id="wish-pin-btn" title="置顶">${isPinned ? ICONS.pin_filled : ICONS.pin_outline}</div> <div class="wish-icon-btn" id="wish-open-setting" title="设置">${ICONS.settings}</div> <div class="wish-icon-btn" id="wish-disable-btn" title="永久关闭">${ICONS.power}</div> <div class="wish-icon-btn" id="wish-close-btn" title="临时关闭">${ICONS.close}</div> <div class="wish-icon-btn" id="wish-menu-btn" title="更多操作">${ICONS.more}</div> </div> <div class="wish-list"> ${renderList()} </div> <div class="wish-footer"> <div class="wish-footer-row"> <div class="wish-footer-links"> <span class="wish-action-link" id="wish-side-all">全选</span> <span class="wish-action-link" id="wish-side-none">不选</span> </div> <button class="wish-btn-light" id="wish-save-as-group">📁 存为组</button> </div> <button class="wish-batch-btn" id="wish-batch-open">批量打开选中</button> </div> </div> <div id="wish-actions-menu"></div> <div id="wish-tooltip-panel"></div> <div id="wish-setting-overlay"> <div id="wish-setting-box"> <div class="ws-header"><span>搜索源管理</span></div> <div class="ws-body"> <div class="ws-config-row"> <label>位置: <select id="ws-pos-select" style="padding:2px;"><option value="auto">双侧自动</option><option value="left">固定左侧</option><option value="right">固定右侧</option></select></label> <label>延迟: <input type="number" id="ws-delay-input" class="ws-input-sm" value="${CONFIG.trigger_delay}">ms</label> <label title="调整侧边栏的行高">行高: <input type="number" id="ws-itemh-input" class="ws-input-sm" value="${CONFIG.item_height}">px</label> <label title="全局唤出快捷键 (Backspace删除)">热键: <input type="text" id="ws-hotkey-input" class="ws-input-key" value="${CONFIG.global_hotkey}" readonly></label> </div> <div class="ws-config-row"> <label>触发方式: <select id="ws-trigger-mode-select" style="padding:2px;"><option value="hover">悬浮显示</option><option value="click">点击显示</option></select></label> </div> <div class="ws-config-row"> <label title="触发区宽度">触发区: <input type="number" id="ws-triggerw-input" class="ws-input-sm" value="${CONFIG.trigger_width}">px</label> </div> <div class="ws-config-row"> <label>回车默认组: <select id="ws-default-group-select" style="padding:2px; min-width:140px;"></select></label> </div> <div class="ws-config-row" id="ws-trigger-buttons-row"> <label>触发按键:</label> <label class="ws-chk-label"><input type="checkbox" class="ws-trigger-btn" value="0">左键</label> <label class="ws-chk-label"><input type="checkbox" class="ws-trigger-btn" value="1">中键</label> <label class="ws-chk-label"><input type="checkbox" class="ws-trigger-btn" value="2">右键</label> <label class="ws-chk-label"><input type="checkbox" class="ws-trigger-btn" value="3">侧键1</label> <label class="ws-chk-label"><input type="checkbox" class="ws-trigger-btn" value="4">侧键2</label> </div> <div class="ws-config-row"> <label title="批量打开间隔">批量延迟: <input type="number" id="ws-batchdelay-input" class="ws-input-sm" value="${CONFIG.batch_open_delay_ms}">ms</label> <label class="ws-chk-label"><input type="checkbox" id="ws-batchbg-input">后台打开</label> </div> <div class="ws-config-row" style="border-top: 1px solid rgba(128,128,128,0.15); padding-top: 12px; margin-top: 8px;"> <label for="ws-domain-mode-select">域名生效模式:</label> <select id="ws-domain-mode-select" style="padding:4px; border-radius:6px; background:transparent; color:inherit; border: 1px solid #ccc;"> <option value="blacklist">黑名单模式 (在以下域名禁用)</option> <option value="whitelist">白名单模式 (仅在以下域名启用)</option> <option value="disabled">不启用</option> </select> <button class="ws-btn" id="ws-add-current-domain-btn" style="margin-left: 10px;"></button> </div> <div class="ws-config-row"> <textarea id="ws-domain-list-textarea" placeholder="每行一个域名, 例如: example.com" style="width: 100%; height: 80px; border-radius: 8px; border: 1px solid rgba(128,128,128,0.15); padding: 8px; font-family: monospace; font-size: 13px; background: rgba(128,128,128,0.03); color: inherit; box-sizing: border-box; resize: vertical;"></textarea> </div> <div class="ws-toolbar"> <button class="ws-btn" id="ws-btn-all">全选</button> <button class="ws-btn" id="ws-btn-none">全不选</button> <button class="ws-btn" id="ws-btn-hide-sel">批量隐藏</button> <button class="ws-btn" id="ws-btn-show-sel">批量显示</button> <button class="ws-btn ws-btn-red" id="ws-btn-del">删除</button> <span style="flex:1"></span> <button class="ws-btn" id="ws-btn-batch-import" title="按指定格式批量添加新项目">批量导入</button> <button class="ws-btn" id="ws-btn-import" title="从文件导入配置,将覆盖现有设置">导入</button> <button class="ws-btn" id="ws-btn-export" title="将当前配置导出到文件">导出</button> <button class="ws-btn" id="ws-btn-add-group">➕ 添加组</button> <button class="ws-btn" id="ws-btn-add">➕ 新增</button> <button class="ws-btn" id="ws-btn-reset">↺ 恢复</button> </div> <div class="ws-list-container" id="ws-sortable-list"></div> </div> <div class="ws-footer"> <button class="ws-btn" id="ws-close-setting">取消</button> <button class="ws-btn ws-btn-primary" id="ws-save-setting">保存配置</button> </div> <div class="ws-resize-handle"></div> </div> </div> <!-- 其他弹窗HTML保持不变... --> <div id="wish-group-editor-overlay"> <div id="wish-group-editor-box"> <div class="ws-header"><span>编辑组 (勾选要加入的引擎)</span></div> <div class="ws-list-container" id="ws-group-selector" style="background:rgba(128,128,128,0.02);"></div> <div class="ws-footer"> <button class="ws-btn" id="ws-close-group-edit">取消</button> <button class="ws-btn ws-btn-primary" id="ws-save-group-edit">确定</button> </div> </div> </div> <div id="wish-delete-overlay"> <div id="wish-delete-box"> <div class="ws-header" style="color:#e53935">确认删除以下项目?</div> <div class="ws-list-container" id="wish-delete-list" style="max-height:300px;"></div> <div class="ws-footer"> <button class="ws-btn" id="wish-cancel-del">取消</button> <button class="ws-btn ws-btn-red" id="wish-confirm-del">确认删除</button> </div> </div> </div> <div id="wish-batch-import-overlay"> <div id="wish-batch-import-box"> <div class="ws-header"><span>批量导入</span></div> <div class="ws-body"> <p style="margin-top:0; margin-bottom:10px; font-size:12px; opacity:0.8;">每行一条记录,使用分隔符隔开名称和URL。新项目将添加至列表末尾。</p> <div class="ws-config-row" style="justify-content:center;"> <label for="ws-batch-separator">分隔符:</label> <input type="text" id="ws-batch-separator" class="ws-input-sm" value="|"> </div> <textarea id="ws-batch-input-area" placeholder="例如:\n百度|https://www.baidu.com/s?wd=%s\n必应|https://cn.bing.com/search?q=%s" style="width: 100%; height: 200px; margin-top: 10px; border-radius: 8px; border: 1px solid rgba(128,128,128,0.15); padding: 8px; font-family: monospace; font-size: 13px; background: rgba(128,128,128,0.03); color: inherit; box-sizing: border-box; resize: vertical;"></textarea> </div> <div class="ws-footer"> <button class="ws-btn" id="ws-cancel-batch-import">取消</button> <button class="ws-btn ws-btn-primary" id="ws-confirm-batch-import">添加至列表</button> </div> </div> </div> `; $("body").append(html); var $panel = $("#wish-panel"); var $tooltip = $("#wish-tooltip-panel"); var $sidebarInput = $("#wish-search-input"); var $footer = $(".wish-footer"); var tooltipTimer; var currentSelectedIndex = -1; var $actionsMenu = $("#wish-actions-menu"); // 问题3:在图标模式的菜单中添加 域名管理 和 关闭按钮 $actionsMenu.append($("#wish-domain-btn").clone(true, true)); // 增加 $actionsMenu.append($("#wish-layout-btn").clone(true, true)); $actionsMenu.append($("#wish-pin-btn").clone(true, true)); $actionsMenu.append($("#wish-open-setting").clone(true, true)); $actionsMenu.append($("#wish-disable-btn").clone(true, true)); $actionsMenu.append($("#wish-close-btn").clone(true, true)); // 增加 $("#wish-menu-btn").on("click", function(e) { e.stopPropagation(); var btnRect = this.getBoundingClientRect(); var isLeft = $panel.hasClass("wish-side-left"); var top = btnRect.top; var style = { top: top + 'px', left: 'auto', right: 'auto', display: 'flex' }; if (isLeft) { style.left = (btnRect.right + 5) + 'px'; } else { style.right = (window.innerWidth - btnRect.left + 5) + 'px'; } $actionsMenu.css(style); }); // 初始化域名按钮状态 if(domainList.some(d => currentHost.includes(d))) { $("#wish-domain-btn, #wish-actions-menu #wish-domain-btn").addClass("active"); } // 域名按钮点击事件 $(document).on("click", "#wish-domain-btn", function(e){ e.stopPropagation(); var currentList = GM_getValue("wish_domain_list", ""); var list = currentList.split('\n').map(d => d.trim()).filter(Boolean); var exists = list.some(d => currentHost.includes(d)); if(exists) { alert("当前域名已在名单中。\n请在设置中手动删除。"); } else { var modeName = domainMode === 'blacklist' ? "黑名单" : "白名单"; if(confirm(`将当前域名 ${currentHost} 添加到${modeName}?`)) { var newList = currentList + "\n" + currentHost; GM_setValue("wish_domain_list", newList.trim()); $(this).addClass("active"); $("#wish-actions-menu #wish-domain-btn").addClass("active"); } } $actionsMenu.hide(); }); $(document).on("click", "#wish-close-btn", function(e) { e.stopPropagation(); $actionsMenu.hide(); isTemporarilyClosed = true; $('#wish-trigger-left, #wish-trigger-right').remove(); hidePanel(); }); $(document).on("click", "#wish-layout-btn", function(e) { e.stopPropagation(); $actionsMenu.hide(); var isIconOnly = $panel.hasClass("wish-icon-only-layout"); if (isIconOnly) { $panel.removeClass("wish-icon-only-layout"); document.documentElement.style.setProperty('--wish-panel-w', CONFIG.panel_width + 'px'); GM_setValue("wish_layout_mode", "full"); } else { $panel.addClass("wish-icon-only-layout"); document.documentElement.style.setProperty('--wish-panel-w', CONFIG.panel_width_icon + 'px'); GM_setValue("wish_layout_mode", "icon-only"); } updateVerticalCenter(); }); $(document).on("click", "#wish-pin-btn", function(e) { e.stopPropagation(); isPinned = !isPinned; $("#wish-pin-btn, #wish-actions-menu #wish-pin-btn").toggleClass("active", isPinned).html(isPinned ? ICONS.pin_filled : ICONS.pin_outline); $panel.toggleClass("wish-pinned", isPinned); GM_setValue("wish_pinned", isPinned); if (isPinned) { $panel.addClass("wish-active"); if (!$panel.hasClass("wish-icon-only-layout")) $sidebarInput.focus(); } }); $(document).on("click", "#wish-open-setting", function(e) { e.stopPropagation(); $actionsMenu.hide(); var $c = $("#ws-sortable-list").empty(); searchDataList.forEach(item => $c.append(createSettingItem(item))); $("#ws-pos-select").val(GM_getValue("wish_s_position", "auto")); $("#ws-trigger-mode-select").val(CONFIG.trigger_mode || DEFAULT_CONFIG.trigger_mode); $("#ws-triggerw-input").val(CONFIG.trigger_width); $("#ws-batchdelay-input").val(CONFIG.batch_open_delay_ms); $("#ws-batchbg-input").prop("checked", !!CONFIG.batch_open_background); var $defaultGroup = $("#ws-default-group-select").empty(); $defaultGroup.append(`<option value="__first__">第一个搜索引擎(默认)</option>`); searchDataList.forEach(item => { if (!item.url) return; var label = item.url.startsWith("group://") ? `组: ${item.name}` : item.name; $defaultGroup.append(`<option value="${item.url}">${label}</option>`); }); $defaultGroup.val(CONFIG.default_group_url || "__first__"); $("#ws-domain-mode-select").val(GM_getValue("wish_domain_mode", "blacklist")); $("#ws-domain-list-textarea").val(GM_getValue("wish_domain_list", "gmail.com\nmail.google.com\nmail.163.com")); var triggerSet = new Set(CONFIG.trigger_buttons); $(".ws-trigger-btn").each(function() { var val = parseInt($(this).val()); $(this).prop("checked", triggerSet.has(val)); }); var toggleTriggerButtons = function() { var mode = $("#ws-trigger-mode-select").val(); $("#ws-trigger-buttons-row").toggle(mode === "click"); }; $("#ws-trigger-mode-select").off("change.wishTrigger").on("change.wishTrigger", toggleTriggerButtons); toggleTriggerButtons(); updateDomainButtonState(); var overlayClass = "wish-overlay-cutout-both"; var pos = $("#ws-pos-select").val(); if (pos === "left") overlayClass = "wish-overlay-cutout-left"; else if (pos === "right") overlayClass = "wish-overlay-cutout-right"; $("#wish-setting-overlay").removeClass("wish-overlay-cutout-both wish-overlay-cutout-left wish-overlay-cutout-right").addClass(overlayClass).css("display", "flex"); $("#wish-trigger-left, #wish-trigger-right").addClass("wish-highlight"); if(!isPinned) hidePanel(); }); $(document).on("click", "#wish-disable-btn", function(e) { e.stopPropagation(); $actionsMenu.hide(); if (confirm("确定要永久关闭聚合搜索吗?\n你可以在油猴扩展中重新启用它。")) { GM_setValue("wish_script_enabled", false); location.reload(); } }); var $list = $(".wish-list"); var scrollTimeout; $list.on('scroll', function() { $list.addClass('scrolling'); clearTimeout(scrollTimeout); scrollTimeout = setTimeout(function() { $list.removeClass('scrolling'); }, 1500); }); var updateVerticalCenter = function() { var winH = $(window).height(); var panelH = $panel.height(); var top = Math.round((winH - panelH) / 2); if (top < 10) top = 10; $panel.css("top", top + "px"); }; updateVerticalCenter(); $(window).on("resize", updateVerticalCenter); var scrollPaused = false; var scrollPauseTimer; $(window).on("scroll", function() { scrollPaused = true; clearTimeout(scrollPauseTimer); scrollPauseTimer = setTimeout(function() { scrollPaused = false; }, 200); }); var showPanel = (side, preText) => { if (isTemporarilyClosed || $panel.hasClass("wish-active")) return; if (typeof side === 'string') { $panel.removeClass("wish-side-left wish-side-right") .addClass(side === 'left' ? 'wish-side-left' : 'wish-side-right'); } $panel.addClass("wish-active"); if (!$panel.hasClass("wish-icon-only-layout")) { $sidebarInput.focus(); if (preText) { $sidebarInput.val(preText).select(); } else if ($sidebarInput.val() === "") { var sel = window.getSelection().toString().trim(); if(sel) $sidebarInput.val(sel).select(); } } }; var hidePanel = () => { if (isPinned || !$panel.hasClass("wish-active")) return; $panel.removeClass("wish-active"); $tooltip.hide(); $sidebarInput.blur(); $sidebarInput.val(""); currentSelectedIndex = -1; updateSelection(); }; var showTimer; if ((CONFIG.trigger_mode || DEFAULT_CONFIG.trigger_mode) === "hover") { var handleHoverTrigger = function(side) { if (scrollPaused || isEditableActive()) return; clearTimeout(showTimer); showTimer = setTimeout(() => showPanel(side), CONFIG.trigger_delay); }; $("#wish-trigger-left").on("mouseenter", () => handleHoverTrigger("left")); $("#wish-trigger-right").on("mouseenter", () => handleHoverTrigger("right")); $("#wish-trigger-left, #wish-trigger-right").on("mouseleave", function() { clearTimeout(showTimer); }); } else { var triggerButtonSet = new Set(CONFIG.trigger_buttons); var handleTrigger = function(side, e) { if (scrollPaused) return; if (!triggerButtonSet.has(e.button)) return; e.preventDefault(); clearTimeout(showTimer); showTimer = setTimeout(() => showPanel(side), CONFIG.trigger_delay); }; $("#wish-trigger-left").on("mousedown", function(e) { handleTrigger("left", e); }); $("#wish-trigger-right").on("mousedown", function(e) { handleTrigger("right", e); }); $("#wish-trigger-left, #wish-trigger-right").on("contextmenu", function(e) { if (triggerButtonSet.has(2)) e.preventDefault(); }); } $(document).on('mousedown', function(e) { if (!isPinned && $panel.hasClass('wish-active')) { if ($(e.target).closest('#wish-panel, .wish-trigger-zone, #wish-tooltip-panel, #wish-actions-menu').length === 0) { hidePanel(); } } if ($actionsMenu.is(":visible") && $(e.target).closest('#wish-actions-menu, #wish-menu-btn').length === 0) { $actionsMenu.hide(); } }); $(document).on('mouseleave', function(e) { if (e.toElement === null && e.relatedTarget === null) { if (!isPinned && $panel.hasClass('wish-active')) { hidePanel(); } } }); $(".wish-list").on("change", ".wish-check", function() { var count = $(".wish-check:checked").length; if(count > 0) { $footer.css("display", "flex"); $("#wish-batch-open").text(`批量打开 (${count})`); } else { $footer.hide(); } }); $footer.hide(); window.addEventListener("keydown", function(e) { if ($(e.target).is("input, textarea") && !$(e.target).is("#wish-search-input")) return; var hotkey = CONFIG.global_hotkey || ""; var isActive = $panel.hasClass("wish-active"); var parts = hotkey.toLowerCase().split("+"); var keyMatch = e.key.toLowerCase() === parts[parts.length-1]; var altMatch = parts.includes("alt") === e.altKey; var ctrlMatch = parts.includes("ctrl") === e.ctrlKey; var shiftMatch = parts.includes("shift") === e.shiftKey; var metaMatch = parts.includes("meta") === e.metaKey; if (parts.length === 1 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) { altMatch = ctrlMatch = shiftMatch = metaMatch = true; } if (keyMatch && altMatch && ctrlMatch && shiftMatch && metaMatch) { if (isTemporarilyClosed) return; e.preventDefault(); e.stopPropagation(); if (isActive) { if(!isPinned) hidePanel(); } else { var pos = GM_getValue("wish_s_position", "auto"); var selText = window.getSelection().toString().trim(); showPanel(pos === 'right' ? 'right' : 'left', selText); } return; } if (isActive) { if (e.key === "Escape") { e.preventDefault(); if(!isPinned) hidePanel(); $actionsMenu.hide(); return; } if (e.altKey && /^Digit[1-9]$/.test(e.code)) { e.preventDefault(); e.stopPropagation(); var idx = parseInt(e.code.replace("Digit", "")) - 1; triggerItem(idx); return; } var maxIndex = $(".wish-item").length - 1; if (e.key === "ArrowDown") { e.preventDefault(); currentSelectedIndex++; if (currentSelectedIndex > maxIndex) currentSelectedIndex = 0; updateSelection(); } else if (e.key === "ArrowUp") { e.preventDefault(); currentSelectedIndex--; if (currentSelectedIndex < 0) currentSelectedIndex = maxIndex; updateSelection(); } else if (e.key === "Enter") { if ($(document.activeElement).is($sidebarInput) && currentSelectedIndex === -1) { e.preventDefault(); var target = CONFIG.default_group_url || "__first__"; if (target === "__first__") { triggerFirstEngine(); } else if (target.startsWith("group://")) { openGroupByUrl(target); } else if (!triggerItemByUrl(target)) { triggerFirstEngine(); } } else if (currentSelectedIndex >= 0) { e.preventDefault(); triggerItem(currentSelectedIndex); } } } }, true); $sidebarInput.on("keydown", function(e) { if (!$panel.hasClass("wish-active")) return; if (e.altKey && /^Digit[1-9]$/.test(e.code)) { e.preventDefault(); e.stopPropagation(); var idx = parseInt(e.code.replace("Digit", "")) - 1; triggerItem(idx); } }); var updateSelection = () => { $(".wish-item").removeClass("wish-selected"); if (currentSelectedIndex >= 0) { var $el = $(`.wish-item[data-index='${currentSelectedIndex}']`); if ($el.length > 0) { $el.addClass("wish-selected"); var container = $(".wish-list")[0]; var item = $el[0]; if (item.offsetTop < container.scrollTop) { container.scrollTop = item.offsetTop; } else if (item.offsetTop + item.offsetHeight > container.scrollTop + container.offsetHeight) { container.scrollTop = item.offsetTop + item.offsetHeight - container.offsetHeight; } } } }; var triggerItem = (idx) => { var $el = $(`.wish-item[data-index='${idx}']`); if($el.length) $el.find(".wish-link").click(); }; var triggerFirstEngine = function() { var $el = $(".wish-item").filter(function() { var u = $(this).data("url"); return u && !u.startsWith("group://"); }).first(); if ($el.length) $el.find(".wish-link").click(); }; var triggerItemByUrl = function(rawUrl) { var $el = $(".wish-item").filter(function() { return $(this).data("url") === rawUrl; }).first(); if ($el.length) { $el.find(".wish-link").click(); return true; } return false; }; var openGroupByUrl = function(rawUrl) { try { var kw = getKeyword(); var idList = JSON.parse(decodeURIComponent(rawUrl.replace("group://", ""))); var currentPageDomain = window.location.hostname; var isFirstLinkOpened = false; idList.forEach(id => { var sub = searchDataMap.get(id); if (!sub) return; var subDomain = getDomain(sub.url); if (subDomain && currentPageDomain.includes(subDomain)) { return; } var finalUrl = sub.url.replace(/%s/i, kw); if (sub.newWindow) { GM_openInTab(finalUrl, {active: !isFirstLinkOpened, insert: true}); isFirstLinkOpened = true; } else { if(!isFirstLinkOpened) { window.location.href = finalUrl; isFirstLinkOpened = true; } else { GM_openInTab(finalUrl, {active: false, insert: true}); } } }); } catch(err) { console.error("Error opening group:", err); } }; var showTooltip = function($target, idListUrl) { if (!idListUrl) return; try { var idList = JSON.parse(decodeURIComponent(idListUrl)); var items = idList.map(id => searchDataMap.get(id)).filter(Boolean); if (!items || items.length === 0) return; $tooltip.empty(); items.forEach(it => { var d = getDomain(it.url.replace("%s", "")); var $tItem = $(`<a href="javascript:;" class="wish-tooltip-item" data-url="${it.url}" data-new-window="${it.newWindow}"><img class="wish-tooltip-icon" src=""><span class="wish-tooltip-text">${it.name}</span></a>`); // 组内图标也使用文字兜底逻辑 var txtIcon = generateLetterIcon(it.name); $tItem.find("img").attr("src", txtIcon); IconManager.get(d, it.name, function(b) { if(b) $tItem.find("img").attr("src", b); }); $tooltip.append($tItem); }); $tooltip.css({ display: 'block', top: '-9999px', left: '-9999px' }); // ... tooltip positioning logic same as before ... var targetRect = $target[0].getBoundingClientRect(); var panelRect = $panel[0].getBoundingClientRect(); var tipWidth = $tooltip.outerWidth(); var tipHeight = $tooltip.outerHeight(); var winWidth = window.innerWidth; var winHeight = window.innerHeight; var isLeftPanel = $panel.hasClass("wish-side-left"); var newTop = targetRect.top; if (newTop + tipHeight > winHeight) newTop = winHeight - tipHeight - 10; if (newTop < 10) newTop = 10; var newLeft; if (isLeftPanel) { newLeft = panelRect.right + 5; if (newLeft + tipWidth > winWidth) newLeft = panelRect.left - tipWidth - 5; } else { newLeft = panelRect.left - tipWidth - 5; if (newLeft < 0) newLeft = panelRect.right + 5; } $tooltip.css({ top: newTop + 'px', left: newLeft + 'px' }); } catch (e) {} }; $tooltip.on("mouseenter", () => clearTimeout(tooltipTimer)).on("mouseleave", () => $tooltip.hide()); $(document).on("mouseenter", ".wish-group-item", function() { clearTimeout(tooltipTimer); showTooltip($(this), $(this).data("url").replace("group://", "")); }); $(document).on("mouseleave", ".wish-group-item", function() { tooltipTimer = setTimeout(() => $tooltip.hide(), 400); }); $(document).on("mouseenter", ".ws-edit-group-btn, .ws-icon-preview", function() { var $row = $(this).closest(".ws-list-item"); var url = $row.find(".ws-input-url").val(); if(url.startsWith("group://")) showTooltip($row, url.replace("group://", "")); }); $(document).on("mouseleave", ".ws-edit-group-btn, .ws-icon-preview", function() { $tooltip.hide(); }); $(document).on("click", ".wish-tooltip-item", function() { var url = $(this).data("url").replace(/%s/i, getKeyword()); var newWindow = $(this).data("new-window"); if(newWindow) { GM_openInTab(url, {active: true, insert: true}); } else { window.location.href = url; } }); var isPanelResizing = false; $(".wish-resize-bar").on("mousedown", function(e) { isPanelResizing = true; e.preventDefault(); $("body").css("cursor", "col-resize"); var startX = e.pageX; var startW = $panel.width(); var isLeft = $panel.hasClass("wish-side-left"); $(document).on("mousemove.wishw", function(em) { if (!isPanelResizing) return; var dx = em.pageX - startX; var newW = isLeft ? (startW + dx) : (startW - dx); var isIconMode = $panel.hasClass("wish-icon-only-layout"); var minW = isIconMode ? 60 : 150; if (newW < minW) newW = minW; if (newW > 800) newW = 800; document.documentElement.style.setProperty('--wish-panel-w', newW + 'px'); }).on("mouseup.wishw", function() { isPanelResizing = false; $("body").css("cursor", ""); $(document).off(".wishw"); var finalWidth = $panel.width(); if ($panel.hasClass("wish-icon-only-layout")) { CONFIG.panel_width_icon = finalWidth; GM_setValue("wish_panel_w_icon", finalWidth); } else { CONFIG.panel_width = finalWidth; GM_setValue("wish_panel_w", finalWidth); } }); }); var isPanelHResizing = false; $(".wish-resize-bar-bottom").on("mousedown", function(e) { isPanelHResizing = true; e.preventDefault(); $("body").css("cursor", "row-resize"); var startY = e.pageY; var startH = $panel.height(); $(document).on("mousemove.wishh", function(em) { if (!isPanelHResizing) return; var newH = startH + (em.pageY - startY); if (newH < 200) newH = 200; $panel.css('height', newH + 'px'); }).on("mouseup.wishh", function() { isPanelHResizing = false; $("body").css("cursor", ""); $(document).off(".wishh"); GM_setValue("wish_panel_h", parseInt($panel.css("height"))); updateVerticalCenter(); }); }); $(document).on("click", ".wish-link", function(e) { var $item = $(this).closest(".wish-item"); var rawUrl = $item.data("url"); if (rawUrl.startsWith("group://")) { openGroupByUrl(rawUrl); return; } var url = rawUrl.replace(/%s/i, getKeyword()); if(e.which === 2) { GM_openInTab(url, {active: false, insert: true}); return; } if ($item.data("target") === '_blank') { GM_openInTab(url, {active: true, insert: true}); } else { window.location.href = url; } }); $(document).on("mousedown", ".wish-link", function(e){ if(e.which===2) e.preventDefault(); }); $("#wish-side-all").on("click", function() { $(".wish-list .wish-check").prop("checked", true).trigger("change"); }); $("#wish-side-none").on("click", function() { $(".wish-list .wish-check").prop("checked", false).trigger("change"); }); $("#wish-save-as-group").on("click", function() { var checked = $(".wish-check:checked"); if (checked.length < 2) return alert("请至少勾选两个网站"); var name = prompt("请输入新组的名称:", "新建搜索组"); if(!name) return; var newGroupIdList = []; checked.each(function() { var $row = $(this).closest(".wish-item"); var id = $row.data("id"); if(id) { newGroupIdList.push(id); } }); if(newGroupIdList.length === 0) return alert("未能获取有效的选中项"); var newGroupItem = { id: null, name: name, url: "group://" + encodeURIComponent(JSON.stringify(newGroupIdList)), newWindow: false, hidden: false }; searchDataList.unshift(newGroupItem); DataManager.save(); location.reload(); }); $("#wish-batch-open").on("click", function() { var checked = $(".wish-check:checked"); if (checked.length === 0) return alert("请先勾选网站"); var kw = getKeyword(); var delay = CONFIG.batch_open_delay_ms || 0; var background = !!CONFIG.batch_open_background; var i = 0; checked.each(function() { var $it = $(this).closest(".wish-item"); var u = $it.data("url"); if (u.startsWith("group://")) return; var url = u.replace(/%s/i, kw); var active = background ? false : (i === 0); var openFn = function() { GM_openInTab(url, {active: active, insert: true}); }; if (delay > 0) { setTimeout(openFn, i * delay); } else { openFn(); } i++; }); }); // 问题4:加载图片逻辑更新。默认已是文字头像,这里负责异步替换。 setTimeout(() => { $(".wish-icon").each(function() { var $img = $(this); IconManager.get($img.data("domain"), $img.data("name"), function(base64) { if (base64) $img.attr("src", base64); }); }); $(".wish-group-subicon").each(function() { var $img = $(this); // 尝试获取图标,如果获取到了就替换 src,如果没获取到(base64为空),就什么都不做(保留原本的文字头像) IconManager.get($img.data("domain"), "", function(base64) { if (base64) $img.attr("src", base64); }); }); }, 50); function createSettingItem(item) { var { id, name, url, newWindow, hidden } = item; var isGroup = url.startsWith("group://"); var domain = isGroup ? "" : getDomain(url.replace("%s", "")); var initialIcon = isGroup ? DEFAULT_FOLDER_ICON : generateLetterIcon(name); var $itemEl = $(` <div class="ws-list-item" draggable="false" data-id="${id || ''}"> <div class="ws-drag-handle" title="按住拖动排序">☰</div> <input type="checkbox" class="ws-chk-del" style="margin-right:6px;"> <img class="wish-icon ws-icon-preview" src="${initialIcon}"> <div class="ws-item-inputs"> <input type="text" class="ws-input ws-input-name" value="${name}" placeholder="名称"> ${isGroup ? `<button class="ws-btn ws-edit-group-btn" style="flex:1;text-align:left;color:#666;">📁 编辑组内容...</button> <input type="hidden" class="ws-input-url" value='${url}'>` : `<input type="text" class="ws-input ws-input-url" value="${url}" placeholder="URL">` } ${!isGroup ? ` <label class="ws-chk-label"><input type="checkbox" class="ws-input-new" ${newWindow?'checked':''}>新窗</label> <label class="ws-chk-label"><input type="checkbox" class="ws-input-hide" ${hidden?'checked':''}>隐藏</label> ` : ''} </div> </div>`); if(!isGroup) { IconManager.get(domain, name, function(b64){ if(b64) $itemEl.find(".ws-icon-preview").attr("src", b64); }); $itemEl.find(".ws-input-url").on("change", function(){ IconManager.get(getDomain(this.value.replace("%s", "")), "", (b) => $itemEl.find(".ws-icon-preview").attr("src", b)); }); } return $itemEl; } var currentEditingInput = null; $(document).on("click", ".ws-edit-group-btn", function() { currentEditingInput = $(this).siblings(".ws-input-url"); var currentUrl = currentEditingInput.val(); var currentIdList = []; try { currentIdList = JSON.parse(decodeURIComponent(currentUrl.replace("group://", ""))); } catch(e){} var $selector = $("#ws-group-selector").empty(); $("#ws-sortable-list .ws-list-item").each(function(){ var $row = $(this); var id = $row.data("id"); if (!id) return; var iconSrc = $row.find(".ws-icon-preview").attr("src"); var name = $row.find(".ws-input-name").val(); var url = $row.find(".ws-input-url").val(); var isSelected = currentIdList.includes(id); var $opt = $(`<label class="ws-group-option"><input type="checkbox" ${isSelected ? 'checked' : ''} data-id="${id}"><img src="${iconSrc}"><span>${name}</span><span class="url-hint">${url.substring(0,30)}...</span></label>`); $selector.append($opt); }); $("#wish-group-editor-overlay").css("display", "flex"); }); $("#ws-close-group-edit").click(() => $("#wish-group-editor-overlay").hide()); $("#ws-save-group-edit").click(() => { var newGroupIdList = []; $("#ws-group-selector input:checked").each(function() { newGroupIdList.push($(this).data("id")); }); var jsonStr = JSON.stringify(newGroupIdList); var encodedJson = encodeURIComponent(jsonStr); if(currentEditingInput) { currentEditingInput.val("group://" + encodedJson); } $("#wish-group-editor-overlay").hide(); }); var dragSrcEl = null; $("#ws-sortable-list").on("dragstart", ".ws-list-item", function(e) { dragSrcEl = this; e.originalEvent.dataTransfer.effectAllowed = 'move'; }); $("#ws-sortable-list").on("dragover", ".ws-list-item", function(e) { e.preventDefault(); e.originalEvent.dataTransfer.dropEffect = 'move'; return false; }); $("#ws-sortable-list").on("drop", ".ws-list-item", function(e) { e.stopPropagation(); if (dragSrcEl !== this) { var $src = $(dragSrcEl), $dest = $(this); if ($src.index() < $dest.index()) $dest.after($src); else $dest.before($src); } return false; }); $("#ws-btn-all").click(() => $(".ws-chk-del").prop("checked", true)); $("#ws-btn-none").click(() => $(".ws-chk-del").prop("checked", false)); $("#ws-btn-hide-sel").click(() => $(".ws-chk-del:checked").closest(".ws-list-item").find(".ws-input-hide").prop("checked", true)); $("#ws-btn-show-sel").click(() => $(".ws-chk-del:checked").closest(".ws-list-item").find(".ws-input-hide").prop("checked", false)); $("#ws-btn-del").click(function() { var checked = $(".ws-chk-del:checked"); if(checked.length === 0) return; var $delList = $("#wish-delete-list").empty(); checked.each(function() { var $row = $(this).closest(".ws-list-item"); $delList.append(`<div class="del-list-item"><img class="del-icon" src="${$row.find(".ws-icon-preview").attr("src")}"><div class="del-info"><span class="del-name">${$row.find(".ws-input-name").val()}</span><span class="del-url">${$row.find(".ws-input-url").val().substring(0,50)}</span></div></div>`); }); $("#wish-delete-overlay").css("display", "flex"); }); $("#wish-confirm-del").click(function() { $(".ws-chk-del:checked").closest(".ws-list-item").remove(); $("#wish-delete-overlay").hide(); }); $("#wish-cancel-del").click(() => $("#wish-delete-overlay").hide()); $("#ws-btn-add").click(() => { $("#ws-sortable-list").prepend(createSettingItem({id: generateUniqueId(), name: "新搜索", url: "https://", newWindow: false, hidden: false})).scrollTop(0); }); $("#ws-btn-add-group").click(() => { $("#ws-sortable-list").prepend(createSettingItem({id: null, name: "新分组", url: "group://" + encodeURIComponent("[]"), newWindow: false, hidden: false})).scrollTop(0); }); $("#ws-btn-reset").click(() => { if(confirm("恢复默认?")) { $("#ws-sortable-list").empty(); DataManager.parseTextAndAssignIds(defaultLinkListText).forEach(i => $("#ws-sortable-list").append(createSettingItem(i))); } }); $("#ws-close-setting").click(() => { $("#wish-setting-overlay").hide().removeClass("wish-overlay-cutout-both wish-overlay-cutout-left wish-overlay-cutout-right"); $("#wish-trigger-left, #wish-trigger-right").removeClass("wish-highlight"); }); $("#ws-hotkey-input").on("keydown", function(e) { if (e.key === "Backspace" || e.key === "Delete") { e.preventDefault(); $(this).val(""); return; } e.preventDefault(); var keys = []; if(e.ctrlKey) keys.push("Ctrl"); if(e.altKey) keys.push("Alt"); if(e.shiftKey) keys.push("Shift"); if(e.metaKey) keys.push("Meta"); if (['Control','Alt','Shift','Meta'].indexOf(e.key) === -1) keys.push(e.key); $(this).val(keys.join("+")); }); $("#ws-triggerw-input").on("input", function() { var v = parseInt($(this).val()); if (isNaN(v) || v < 2) v = 2; CONFIG.trigger_width = v; document.documentElement.style.setProperty('--wish-trigger-gap', v + "px"); $("#wish-trigger-left, #wish-trigger-right").css("width", v + "px"); }); function updateDomainButtonState() { const $btn = $("#ws-add-current-domain-btn"); const mode = $("#ws-domain-mode-select").val(); if (mode === 'blacklist') { $btn.text("添加当前域名到黑名单").show(); } else if (mode === 'whitelist') { $btn.text("添加当前域名到白名单").show(); } else { $btn.hide(); } } $("#ws-domain-mode-select").on("change", updateDomainButtonState); $("#ws-add-current-domain-btn").on("click", function() { const $textarea = $('#ws-domain-list-textarea'); let currentList = $textarea.val(); const domains = currentList.split('\n').map(d => d.trim()).filter(Boolean); if (domains.includes(currentHost)) { alert('当前域名已存在于列表中!'); return; } const newList = currentList.trim() === '' ? currentHost : currentList + '\n' + currentHost; $textarea.val(newList); alert(`已将 "${currentHost}" 添加到列表。请记得点击“保存配置”。`); }); function getDataFromSettingsUI() { var list = []; $("#ws-sortable-list .ws-list-item").each(function() { var $row = $(this); var id = $row.data("id"); var name = $row.find(".ws-input-name").val(); var url = $row.find(".ws-input-url").val(); var isGroup = url.startsWith("group://"); list.push({ id: id ? id : null, name: name, url: url, newWindow: isGroup ? false : $row.find(".ws-input-new").is(":checked"), hidden: isGroup ? false : $row.find(".ws-input-hide").is(":checked") }); }); return list; } $("#ws-save-setting").click(() => { searchDataList = getDataFromSettingsUI(); DataManager.save(); GM_setValue("wish_s_position", $("#ws-pos-select").val()); GM_setValue("wish_config_trigger_delay", $("#ws-delay-input").val()); GM_setValue("wish_item_height", $("#ws-itemh-input").val()); GM_setValue("wish_global_hotkey", $("#ws-hotkey-input").val()); GM_setValue("wish_trigger_mode", $("#ws-trigger-mode-select").val()); GM_setValue("wish_trigger_width", $("#ws-triggerw-input").val()); GM_setValue("wish_default_group_url", $("#ws-default-group-select").val()); GM_setValue("wish_batch_open_delay_ms", $("#ws-batchdelay-input").val()); GM_setValue("wish_batch_open_background", $("#ws-batchbg-input").is(":checked")); GM_setValue("wish_domain_mode", $("#ws-domain-mode-select").val()); GM_setValue("wish_domain_list", $("#ws-domain-list-textarea").val()); var triggerButtons = $(".ws-trigger-btn:checked").map(function() { return parseInt($(this).val()); }).get(); if (triggerButtons.length === 0) triggerButtons = DEFAULT_CONFIG.trigger_buttons.slice(); GM_setValue("wish_trigger_mouse_buttons", triggerButtons.join(",")); location.reload(); }); $("#ws-btn-export").on("click", function() { var dataToExport = getDataFromSettingsUI(); var jsonString = JSON.stringify(dataToExport, null, 2); var blob = new Blob([jsonString], { type: "application/json" }); var url = URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; a.download = `aggregate-search-backup-${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); $("#ws-btn-import").on("click", function() { $("#ws-import-file-input").val(null).click(); }); $("#ws-import-file-input").on("change", function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(event) { try { var importedData = JSON.parse(event.target.result); if (!Array.isArray(importedData)) { throw new Error("导入的数据不是一个有效的数组。"); } if (confirm("警告:导入将覆盖您当前的全部配置,确定要继续吗?")) { searchDataList = importedData; DataManager.save(); alert("导入成功!页面将刷新以应用更改。"); location.reload(); } } catch (err) { alert("导入失败: " + err.message); } }; reader.readAsText(file); }); $("#ws-btn-batch-import").on("click", function() { $("#wish-batch-import-overlay").css("display", "flex"); }); $("#ws-cancel-batch-import").on("click", function() { $("#wish-batch-import-overlay").hide(); }); $("#ws-confirm-batch-import").on("click", function() { var text = $("#ws-batch-input-area").val(); var separator = $("#ws-batch-separator").val(); if (!text.trim() || !separator) { return alert("请输入内容和分隔符。"); } var lines = text.split('\n').filter(line => line.trim() !== ''); var importedCount = 0; var failedLines = []; lines.forEach(line => { var parts = line.split(separator); if (parts.length >= 2) { var name = parts[0].trim(); var url = parts.slice(1).join(separator).trim(); if(name && url) { var newItemData = { id: generateUniqueId(), name: name, url: url, newWindow: false, hidden: false }; $("#ws-sortable-list").append(createSettingItem(newItemData)); importedCount++; } else { failedLines.push(line); } } else { failedLines.push(line); } }); if (importedCount > 0) { var message = `成功添加 ${importedCount} 个新的搜索引擎到列表末尾。\n请检查后点击“保存配置”按钮以应用更改。`; if(failedLines.length > 0) { message += `\n\n以下 ${failedLines.length} 行导入失败,请检查格式:\n` + failedLines.join('\n'); } alert(message); $("#wish-batch-import-overlay").hide(); $("#ws-batch-input-area").val(''); } else { alert("没有可导入的项目。请检查您的文本和分隔符是否正确。"); } }); var isWinResizing = false; $(".ws-resize-handle").on("mousedown", function(e) { isWinResizing = true; e.preventDefault(); var $box = $("#wish-setting-box"), startX = e.pageX, startY = e.pageY, startW = $box.width(), startH = $box.height(); $(document).on("mousemove.wsresize", function(em) { if (isWinResizing) { $box.css({ width: startW + (em.pageX - startX), height: startH + (em.pageY - startY) }); } }).on("mouseup.wsresize", function() { isWinResizing = false; $(document).off(".wsresize"); GM_setValue("wish_win_w", $box.width()); GM_setValue("wish_win_h", $box.height()); }); }); }; initUI(); }; main(); })(jQuery);