// ==UserScript== // @name Search Keyword Manager // @name:zh-CN 搜索词管理器 // @namespace https://github.com/zwy/userscripts // @version 1.0.0 // @description Personal search keyword manager. Add/delete keywords, click to copy, usage count sorting, import/export support. // @description:zh-CN 个人搜索词管理器。支持添加/删除搜索词,点击复制,按使用次数排序,导入/导出功能。 // @author zwy // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function () { 'use strict'; // ── Constants ──────────────────────────────────────────────────────────── const STORAGE_KEY = 'skm_keywords'; const PANEL_VISIBLE_KEY = 'skm_panel_visible'; const TOGGLE_POS_KEY = 'skm_toggle_pos'; // ── Detect search input ─────────────────────────────────────────────────── function hasSearchInput() { const selectors = [ 'input[type="search"]', 'input[name="q"]', 'input[name="query"]', 'input[name="keyword"]', 'input[name="search"]', 'input[name="s"]', 'input[name="wd"]', 'input[name="w"]', 'input[id*="search" i]', 'input[class*="search" i]', 'input[placeholder*="搜索" i]', 'input[placeholder*="search" i]', 'input[aria-label*="search" i]', 'input[aria-label*="搜索" i]', '[role="search"] input', 'form[role="search"] input', 'form[action*="search"] input[type="text"]', ]; return document.querySelector(selectors.join(',')) !== null; } // ── Storage helpers ─────────────────────────────────────────────────────── function loadKeywords() { try { const raw = GM_getValue(STORAGE_KEY, '[]'); return JSON.parse(raw); } catch { return []; } } function saveKeywords(list) { GM_setValue(STORAGE_KEY, JSON.stringify(list)); } // ── Styles ──────────────────────────────────────────────────────────────── GM_addStyle(/* css */` #skm-toggle { position: fixed; z-index: 2147483646; width: 40px; height: 40px; border-radius: 50%; background: #01696f; color: #fff; border: none; cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.25); display: flex; align-items: center; justify-content: center; font-size: 18px; transition: background 180ms, transform 180ms; user-select: none; touch-action: none; } #skm-toggle:hover { background: #0c4e54; transform: scale(1.08); } #skm-toggle:active { transform: scale(0.95); } #skm-panel { position: fixed; z-index: 2147483647; bottom: 80px; right: 16px; width: 320px; max-height: 520px; background: #f7f6f2; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,.18); display: flex; flex-direction: column; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; color: #28251d; transition: opacity 180ms, transform 180ms; } #skm-panel.skm-hidden { opacity: 0; pointer-events: none; transform: translateY(8px); } #skm-header { display: flex; align-items: center; padding: 12px 14px 10px; border-bottom: 1px solid #dcd9d5; gap: 8px; } #skm-title { flex: 1; font-weight: 600; font-size: 13px; letter-spacing: .02em; color: #28251d; } .skm-header-btn { background: none; border: none; cursor: pointer; color: #7a7974; padding: 4px 6px; border-radius: 6px; font-size: 12px; transition: background 150ms, color 150ms; white-space: nowrap; } .skm-header-btn:hover { background: #edeae5; color: #28251d; } #skm-add-row { display: flex; gap: 6px; padding: 10px 12px 8px; border-bottom: 1px solid #dcd9d5; } #skm-add-input { flex: 1; padding: 6px 10px; border: 1px solid #d4d1ca; border-radius: 8px; font-size: 13px; outline: none; background: #fff; color: #28251d; transition: border-color 150ms, box-shadow 150ms; } #skm-add-input:focus { border-color: #01696f; box-shadow: 0 0 0 2px rgba(1,105,111,.15); } #skm-add-btn { padding: 6px 12px; background: #01696f; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background 150ms; white-space: nowrap; } #skm-add-btn:hover { background: #0c4e54; } #skm-add-btn:active { background: #0f3638; } #skm-sort-row { display: flex; align-items: center; padding: 6px 12px 4px; gap: 6px; } #skm-sort-label { font-size: 11px; color: #7a7974; flex: 1; } #skm-sort-select { font-size: 11px; border: 1px solid #d4d1ca; border-radius: 6px; padding: 2px 6px; background: #fff; color: #28251d; cursor: pointer; outline: none; } #skm-sort-select:focus { border-color: #01696f; } #skm-list { flex: 1; overflow-y: auto; padding: 4px 8px 8px; } #skm-list::-webkit-scrollbar { width: 4px; } #skm-list::-webkit-scrollbar-thumb { background: #dcd9d5; border-radius: 4px; } .skm-empty { text-align: center; color: #bab9b4; padding: 24px 12px; font-size: 13px; } .skm-item { display: flex; align-items: center; gap: 6px; padding: 5px 6px; border-radius: 8px; transition: background 120ms; } .skm-item:hover { background: #edeae5; } .skm-copy-btn { flex: 1; text-align: left; background: none; border: none; cursor: pointer; font-size: 13px; color: #28251d; padding: 3px 4px; border-radius: 6px; transition: color 120ms; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .skm-copy-btn:hover { color: #01696f; } .skm-copy-btn:active { color: #0c4e54; } .skm-count { font-size: 11px; color: #bab9b4; min-width: 20px; text-align: right; flex-shrink: 0; } .skm-count.skm-has-count { color: #7a7974; } .skm-del-btn { background: none; border: none; cursor: pointer; color: #bab9b4; padding: 2px 4px; border-radius: 4px; font-size: 14px; line-height: 1; transition: color 120ms, background 120ms; flex-shrink: 0; } .skm-del-btn:hover { color: #a12c7b; background: #e0ced7; } .skm-toast { position: fixed; z-index: 2147483647; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(0); background: #28251d; color: #f7f6f2; padding: 8px 16px; border-radius: 20px; font-size: 13px; pointer-events: none; opacity: 0; transition: opacity 200ms; white-space: nowrap; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .skm-toast.skm-toast-show { opacity: 1; } #skm-footer { padding: 8px 12px; border-top: 1px solid #dcd9d5; display: flex; gap: 6px; justify-content: flex-end; } #skm-footer .skm-footer-btn { background: none; border: 1px solid #d4d1ca; color: #7a7974; font-size: 11px; padding: 4px 10px; border-radius: 6px; cursor: pointer; transition: background 150ms, color 150ms, border-color 150ms; } #skm-footer .skm-footer-btn:hover { background: #edeae5; color: #28251d; border-color: #bab9b4; } @media (prefers-color-scheme: dark) { #skm-panel { background: #1c1b19; color: #cdccca; box-shadow: 0 8px 32px rgba(0,0,0,.5); } #skm-header, #skm-add-row, #skm-footer { border-color: #393836; } #skm-add-input { background: #201f1d; border-color: #393836; color: #cdccca; } #skm-add-input:focus { border-color: #4f98a3; } #skm-sort-select { background: #201f1d; border-color: #393836; color: #cdccca; } .skm-item:hover { background: #22211f; } .skm-copy-btn { color: #cdccca; } .skm-copy-btn:hover { color: #4f98a3; } .skm-count.skm-has-count { color: #797876; } .skm-del-btn { color: #5a5957; } .skm-del-btn:hover { color: #d163a7; background: #4c3d46; } .skm-header-btn:hover { background: #2d2c2a; color: #cdccca; } #skm-footer .skm-footer-btn { border-color: #393836; color: #797876; } #skm-footer .skm-footer-btn:hover { background: #2d2c2a; color: #cdccca; border-color: #5a5957; } .skm-empty { color: #5a5957; } #skm-title { color: #cdccca; } .skm-toast { background: #cdccca; color: #1c1b19; } } `); // ── Toast ───────────────────────────────────────────────────────────────── let toastEl = null; let toastTimer = null; function showToast(msg) { if (!toastEl) { toastEl = document.createElement('div'); toastEl.className = 'skm-toast'; document.body.appendChild(toastEl); } toastEl.textContent = msg; toastEl.classList.add('skm-toast-show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toastEl.classList.remove('skm-toast-show'), 1800); } // ── State ───────────────────────────────────────────────────────────────── let keywords = loadKeywords(); let sortMode = 'count'; // 'count' | 'alpha' | 'recent' let panelVisible = GM_getValue(PANEL_VISIBLE_KEY, false); // ── Render list ─────────────────────────────────────────────────────────── function getSortedKeywords() { const list = [...keywords]; if (sortMode === 'count') { list.sort((a, b) => (b.count || 0) - (a.count || 0)); } else if (sortMode === 'alpha') { list.sort((a, b) => a.text.localeCompare(b.text, undefined, { sensitivity: 'base' })); } else if (sortMode === 'recent') { list.sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0)); } return list; } function renderList() { const listEl = document.getElementById('skm-list'); if (!listEl) return; const sorted = getSortedKeywords(); if (sorted.length === 0) { listEl.innerHTML = '