// ==UserScript== // @name 复制网页标题和链接 (配置快捷键) // @namespace https://greasyfork.org/ // @version 0.1.0 // @description 默认 Alt+S 复制网页标题和链接。快捷键可在油猴菜单通过直接按键组合配置。 // @author 妮娜可 // @match *://*/* // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @noframes // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置管理类 class ShortcutConfig { constructor() { this.key = GM_getValue('shortcut_key', 's'); this.ctrl = GM_getValue('shortcut_ctrl', false); this.alt = GM_getValue('shortcut_alt', true); this.shift = GM_getValue('shortcut_shift', false); } save() { GM_setValue('shortcut_key', this.key); GM_setValue('shortcut_ctrl', this.ctrl); GM_setValue('shortcut_alt', this.alt); GM_setValue('shortcut_shift', this.shift); } getDisplayString() { const parts = []; if (this.ctrl) parts.push('Ctrl'); if (this.alt) parts.push('Alt'); if (this.shift) parts.push('Shift'); let displayKey = this.key.toLowerCase(); if (displayKey === ' ') displayKey = 'Space'; else if (displayKey.length === 1) displayKey = displayKey.toUpperCase(); else displayKey = displayKey.replace(/\b\w/g, l => l.toUpperCase()); parts.push(displayKey); return parts.join(' + '); } matches(event) { const keyMatch = event.key.toLowerCase() === this.key.toLowerCase(); const ctrlMatch = event.ctrlKey === this.ctrl; const altMatch = event.altKey === this.alt; const shiftMatch = event.shiftKey === this.shift; return keyMatch && ctrlMatch && altMatch && shiftMatch; } } // UI 管理类 class UIManager { constructor() { this.toastElement = null; this.promptElement = null; this.initElements(); } initElements() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.createElements()); } else { this.createElements(); } } createElements() { this.createToastElement(); this.createPromptElement(); } createToastElement() { this.toastElement = document.createElement('div'); this.toastElement.className = 'copy-toast-message'; this.toastElement.style.display = 'none'; document.body.appendChild(this.toastElement); } createPromptElement() { this.promptElement = document.createElement('div'); this.promptElement.className = 'shortcut-setup-prompt'; this.promptElement.style.display = 'none'; document.body.appendChild(this.promptElement); } showToast(message, type = 'info', duration = 2500) { if (!this.toastElement) return; this.toastElement.innerHTML = message; this.toastElement.className = 'copy-toast-message'; if (type === 'success') { this.toastElement.classList.add('success'); } else if (type === 'error') { this.toastElement.classList.add('error'); } this.toastElement.style.opacity = '0'; this.toastElement.style.display = 'flex'; void this.toastElement.offsetWidth; // 触发重排 this.toastElement.style.opacity = '1'; setTimeout(() => { this.toastElement.style.opacity = '0'; setTimeout(() => { this.toastElement.style.display = 'none'; }, 300); }, duration); } showPrompt(content) { if (!this.promptElement) return; this.promptElement.innerHTML = content; this.promptElement.style.display = 'flex'; } hidePrompt() { if (!this.promptElement) return; this.promptElement.style.display = 'none'; } } // 主应用类 class CopyTitleApp { constructor() { this.config = new ShortcutConfig(); this.ui = new UIManager(); this.isSettingShortcut = false; this.init(); } init() { this.bindEvents(); this.registerMenuCommand(); this.addStyles(); } bindEvents() { // 使用 addEventListener 而不是覆盖 onkeydown document.addEventListener('keydown', (e) => this.handleKeyDown(e), true); } registerMenuCommand() { GM_registerMenuCommand('设置复制快捷键', () => { this.startShortcutSetting(); }); } startShortcutSetting() { this.isSettingShortcut = true; const content = ` 请按下您想设置的新快捷键组合 (例如 Alt+S)。
当前: ${this.config.getDisplayString()}
按 ESC 键取消。 `; this.ui.showPrompt(content); } handleKeyDown(e) { if (this.isSettingShortcut) { this.handleShortcutSetting(e); } else { this.handleCopyShortcut(e); } } handleShortcutSetting(e) { e.preventDefault(); e.stopPropagation(); if (e.key === 'Escape') { this.cancelShortcutSetting(); return; } // 忽略单独的修饰键 if (['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) { this.updatePromptWithModifiers(e); return; } // 设置新的快捷键 this.setNewShortcut(e); } updatePromptWithModifiers(e) { const currentModifiers = []; if (e.ctrlKey) currentModifiers.push('Ctrl'); if (e.altKey) currentModifiers.push('Alt'); if (e.shiftKey) currentModifiers.push('Shift'); let heldModifiers = currentModifiers.join(' + '); if (heldModifiers) heldModifiers += ' + '; const content = ` 请按下您想设置的新快捷键组合。
当前按下: ${heldModifiers} _
(请继续按下主键, 如 A, B, 1, Enter 等)
按 ESC 键取消。 `; this.ui.showPrompt(content); } setNewShortcut(e) { this.config.key = e.key.toLowerCase(); this.config.ctrl = e.ctrlKey; this.config.alt = e.altKey; this.config.shift = e.shiftKey; this.config.save(); this.isSettingShortcut = false; this.ui.hidePrompt(); this.ui.showToast(`快捷键已更新为: ${this.config.getDisplayString()}`, 'success', 3000); } cancelShortcutSetting() { this.isSettingShortcut = false; this.ui.hidePrompt(); this.ui.showToast('快捷键设置已取消', 'info'); } handleCopyShortcut(e) { if (!this.config.matches(e)) return; // 防止修饰键本身作为主键的歧义情况 if (['control', 'alt', 'shift', 'meta'].includes(this.config.key.toLowerCase()) && !(this.config.ctrl || this.config.alt || this.config.shift)) { return; } e.preventDefault(); e.stopPropagation(); this.copyTitleAndUrl(); } copyTitleAndUrl() { const title = document.title; const url = location.href; if (!title || !url) { this.ui.showToast(this.getErrorMessage('无标题或URL'), 'error'); return; } try { GM_setClipboard('『' + title + '』\n' + url); this.ui.showToast(this.getSuccessMessage(), 'success'); } catch (err) { console.error("GM_setClipboard error:", err); this.ui.showToast(this.getErrorMessage('复制失败 (权限?)'), 'error'); } } getSuccessMessage() { return ` 复制成功! `; } getErrorMessage(text) { return ` ${text} `; } addStyles() { GM_addStyle(` .copy-toast-message { position: fixed; left: 50%; top: 50px; transform: translateX(-50%); background: rgba(50, 50, 50, 0.85); backdrop-filter: blur(8px) saturate(150%); -webkit-backdrop-filter: blur(8px) saturate(150%); padding: 12px 20px; border-radius: 8px; z-index: 2147483646; color: #fff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); display: flex; align-items: center; opacity: 0; transition: opacity 0.3s ease-in-out; min-width: 180px; justify-content: center; } .copy-toast-message svg { vertical-align: middle; } .shortcut-setup-prompt { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); background: rgba(30, 30, 30, 0.92); backdrop-filter: blur(10px) saturate(180%); -webkit-backdrop-filter: blur(10px) saturate(180%); padding: 25px 35px; border-radius: 12px; z-index: 2147483647; color: #eee; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; text-align: center; line-height: 1.6; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: 300px; max-width: 90%; } `); } } // 初始化应用 new CopyTitleApp(); })();