// ==UserScript== // @name 98助手 // @namespace duang_duang // @version 2.0.2 // @description 98tang 隐藏已访问链接,支持拖拽UI、隐藏/透明度/显示切换 // @author q // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @license MIT // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @downloadURL https://update.greasyfork.icu/scripts/560198/98%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/560198/98%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (function () { 'use strict'; // 配置 const DB_NAME = '98tang_visited_db'; const STORE_NAME = 'visited_links'; const DB_VERSION = 1; const STORAGE_KEY_POS = '98tang_ui_pos'; const STORAGE_KEY_MODE = '98tang_ui_mode'; // 'opacity', 'hidden', 'show' const STORAGE_KEY_MINIMIZED = '98tang_ui_minimized'; // UI 相关变量 const panelId = 'visited-link-panel'; const visitedClass = 'visited-item'; let currentMode = localStorage.getItem(STORAGE_KEY_MODE) || 'opacity'; let hiddenCount = 0; // IndexDB 工具类 const dbTools = { db: null, init: function () { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = (event) => { console.error("Database error: " + event.target.errorCode); reject(event); }; request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: "id" }); } }; request.onsuccess = (event) => { this.db = event.target.result; resolve(this.db); }; }); }, add: function (id) { return new Promise((resolve, reject) => { if (!this.db) return reject("DB not initialized"); const transaction = this.db.transaction([STORE_NAME], "readwrite"); const store = transaction.objectStore(STORE_NAME); const request = store.put({ id: id, timestamp: new Date().getTime() }); request.onsuccess = () => resolve(true); request.onerror = () => resolve(false); // 忽略错误 }); }, has: function (id) { return new Promise((resolve, reject) => { if (!this.db) return reject("DB not initialized"); const transaction = this.db.transaction([STORE_NAME], "readonly"); const store = transaction.objectStore(STORE_NAME); const request = store.get(id); request.onsuccess = () => resolve(!!request.result); request.onerror = () => resolve(false); }); } }; // UI 工具类 const uiTools = { updateCount: function () { const el = document.getElementById('visited-count'); if (el) el.innerText = hiddenCount; }, applyMode: function (mode) { currentMode = mode; localStorage.setItem(STORAGE_KEY_MODE, mode); // 移除所有旧模式类 const classes = document.body.classList; for (let i = classes.length - 1; i >= 0; i--) { if (classes[i].startsWith('mode-')) { classes.remove(classes[i]); } } document.body.classList.add('mode-' + mode); }, initStyle: function () { const style = document.createElement('style'); style.innerHTML = ` /* 模式样式 */ body.mode-hidden .${visitedClass} { display: none !important; } body.mode-opacity .${visitedClass} { opacity: 0.3; } body.mode-show .${visitedClass} { /* 正常显示 */ } body.mode-strikethrough .${visitedClass} a { text-decoration: line-through !important; } body.mode-opacity-strike .${visitedClass} { opacity: 0.4; } body.mode-opacity-strike .${visitedClass} a { text-decoration: line-through !important; } body.mode-yellow .${visitedClass} a { color: #aaaa00 !important; } /* 面板样式 */ #${panelId} { position: fixed; z-index: 999999; background: rgba(0, 0, 0, 0.75); color: white; padding: 6px; border-radius: 4px; font-size: 12px; font-family: sans-serif; box-shadow: 0 2px 8px rgba(0,0,0,0.3); user-select: none; width: 110px; backdrop-filter: blur(2px); transition: width 0.2s, background 0.2s; } #${panelId}.minimized { width: auto; padding: 4px 8px; background: rgba(0, 0, 0, 0.5); } #${panelId}.minimized .panel-content { display: none; } #${panelId} .panel-header { cursor: move; font-weight: bold; display: flex; justify-content: space-between; align-items: center; } #${panelId}:not(.minimized) .panel-header { border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 4px; margin-bottom: 4px; } #${panelId} .toggle-btn { cursor: pointer; font-size: 14px; line-height: 1; opacity: 0.7; padding: 0 2px; } #${panelId} .toggle-btn:hover { opacity: 1; color: #4CAF50; } #${panelId} select { background: #333; color: white; border: 1px solid #555; border-radius: 3px; padding: 2px; width: 100%; font-size: 11px; margin-bottom: 2px; cursor: pointer; } #${panelId} .stat-row { font-size: 10px; color: #ccc; text-align: right; } `; document.head.appendChild(style); }, initPanel: function () { if (document.getElementById(panelId)) return; const div = document.createElement('div'); div.id = panelId; // 恢复位置 const savedPos = JSON.parse(localStorage.getItem(STORAGE_KEY_POS) || '{"top": "20px", "left": "20px"}'); div.style.top = savedPos.top; div.style.left = savedPos.left; // 恢复折叠状态 const isMinimized = localStorage.getItem(STORAGE_KEY_MINIMIZED) === 'true'; if (isMinimized) div.classList.add('minimized'); div.innerHTML = `