// ==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/jquery@3.6.0/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 // @downloadURL https://update.greasyfork.icu/scripts/561164/%E8%BD%BB%E9%87%8F%E7%BA%A7%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2-%E7%B2%BE%E7%BE%8E%E4%BE%A7%E8%BE%B9%E6%A0%8F%E7%89%88%20%28Gemini%E4%BF%AE%E6%94%B9%E7%89%88%29.user.js // @updateURL https://update.greasyfork.icu/scripts/561164/%E8%BD%BB%E9%87%8F%E7%BA%A7%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2-%E7%B2%BE%E7%BE%8E%E4%BE%A7%E8%BE%B9%E6%A0%8F%E7%89%88%20%28Gemini%E4%BF%AE%E6%94%B9%E7%89%88%29.meta.js // ==/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(`
`); $('#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: '', pin_filled: '', settings: '', layout: '', more: '', power: '', close: '', shield: '' }; 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 = ` ${letter} `; 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 = '
'; subItems.forEach(sub => { var subDomain = getDomain(sub.url.replace("%s", "")); // 1. 核心代码:生成文字头像 (例如 "谷歌" -> "谷") var subFallback = generateLetterIcon(sub.name); // 2. 将 src 默认设为文字头像 // class="wish-group-subicon" 会被脚本底部的逻辑尝试替换为真实图标,如果替换失败,就保持显示文字 iconHtml += ``; }); iconHtml += '
'; } else { // 空组的情况:显示组名的第一个字 var groupLetterIcon = generateLetterIcon(item.name); iconHtml = ``; } html += `
${iconHtml}
${item.name} ${shortcutHint}
`; } 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 += `
${item.name} ${shortcutHint}
`; } }); 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 = ` `; var triggersHtml = ''; if (pos === 'auto') { triggersHtml = `
`; } else if (pos === 'left') { triggersHtml = `
`; } else { triggersHtml = `
`; } // 问题2:在Top Row添加 域名管理按钮 (#wish-domain-btn) 和 移动关闭按钮 (#wish-close-btn) var html = ` ${css} ${triggersHtml}
${ICONS.shield}
${ICONS.layout}
${isPinned ? ICONS.pin_filled : ICONS.pin_outline}
${ICONS.settings}
${ICONS.power}
${ICONS.close}
${ICONS.more}
${renderList()}
搜索源管理
编辑组 (勾选要加入的引擎)
确认删除以下项目?
批量导入

每行一条记录,使用分隔符隔开名称和URL。新项目将添加至列表末尾。

`; $("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(``); searchDataList.forEach(item => { if (!item.url) return; var label = item.url.startsWith("group://") ? `组: ${item.name}` : item.name; $defaultGroup.append(``); }); $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 = $(`${it.name}`); // 组内图标也使用文字兜底逻辑 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 = $(`
${isGroup ? ` ` : `` } ${!isGroup ? ` ` : ''}
`); 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 = $(``); $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(`
${$row.find(".ws-input-name").val()}${$row.find(".ws-input-url").val().substring(0,50)}
`); }); $("#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);