您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
快捷复制便于分享的页面标题和URL,支持自定义快捷键,支持设置是否保留URL参数
// ==UserScript== // @name share-link-copy // @namespace http://tampermonkey.net/ // @version 1.0.9 // @description 快捷复制便于分享的页面标题和URL,支持自定义快捷键,支持设置是否保留URL参数 // @license MIT // @author Lainbo // @match *://*/* // @grant GM_setClipboard // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMTAgMTM0aDc3NXY1ODNIMTEweiIvPjxwYXRoIGQ9Ik02MjUgNjg0LjhjMjcuNSAwIDUxLTkuOCA3MC42LTI5LjMgMTkuNi0xOS42IDI5LjQtNDMuMSAyOS4zLTcwLjcgMC0yNy41LTkuOC01MS4xLTI5LjMtNzAuNi0xOS41LTE5LjUtNDMuMS0yOS4zLTcwLjYtMjkuNC0xMi41IDAtMjQuNCAyLjMtMzUuNiA2LjktMTEuMiA0LjYtMjEuNCAxMC42LTMwLjYgMTguMUw0MjUgNDQyLjN2LTE1bDEzMy44LTY3LjVjOS4yIDcuNSAxOS40IDEzLjUgMzAuNiAxOC4xczIzLjEgNi45IDM1LjYgNi45YzI3LjUgMCA1MS05LjggNzAuNi0yOS40IDE5LjYtMTkuNiAyOS40LTQzLjEgMjkuMy03MC42IDAtMjcuNS05LjgtNTEuMS0yOS4zLTcwLjYtMTkuNS0xOS41LTQzLjEtMjkuMy03MC42LTI5LjQtMjcuNi0uMS01MS4xIDkuNy03MC42IDI5LjQtMTkuNSAxOS43LTI5LjMgNDMuMi0yOS40IDcwLjZ2Ny41bC0xMzMuOCA2Ny41Yy05LjItNy41LTE5LjQtMTMuNi0zMC42LTE4LjFzLTIzLjEtNi45LTM1LjYtNi45Yy0yNy41IDAtNTEgOS44LTcwLjYgMjkuNC0xOS42IDE5LjYtMjkuNCA0My4xLTI5LjQgNzAuNiAwIDI3LjUgOS44IDUxIDI5LjQgNzAuNiAxOS42IDE5LjYgNDMuMiAyOS40IDcwLjYgMjkuMyAxMi41IDAgMjQuNC0yLjMgMzUuNi02LjggMTEuMy00LjYgMjEuNS0xMC42IDMwLjYtMTguMUw1MjUgNTc3LjN2Ny41YzAgMjcuNSA5LjggNTEgMjkuNCA3MC43IDE5LjYgMTkuNiA0My4xIDI5LjQgNzAuNiAyOS4zbS00MjUgMTUwbC0xMTUgMTE1Yy0xNS44IDE1LjgtMzQgMTkuNC01NC40IDEwLjZDMTAuMiA5NTEuOCAwIDkzNi4xIDAgOTEzLjZWMTM0LjhjMC0yNy41IDkuOC01MSAyOS40LTcwLjZTNzIuNSAzNC45IDEwMCAzNC44aDgwMGMyNy41IDAgNTEuMSA5LjggNzAuNiAyOS40IDE5LjYgMTkuNiAyOS40IDQzLjEgMjkuNCA3MC42djYwMGMwIDI3LjUtOS44IDUxLjEtMjkuNCA3MC42LTE5LjYgMTkuNi00My4xIDI5LjQtNzAuNiAyOS40SDIwMHoiIGZpbGw9IiMxNjVkZmYiLz48L3N2Zz4= // ==/UserScript== (function () { 'use strict' GM_addStyle(` .els-notification-Pfq0X5 { position: fixed; bottom: 20px; right: 20px; background-color: #2da44e; color: white; padding: 12px 20px; border-radius: 4px; font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); opacity: 0; transition: opacity 0.3s ease-in-out; z-index: 9999; } .modal-overlay-8W2Q7t { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 999999; } .modal-content-1Zb3tL { background-color: #f6f8fa; padding: 24px; border-radius: 6px; width: 460px; box-shadow: 0 8px 24px rgba(140,149,159,0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; } .modal-title-cKu8SM { font-size: 20px; font-weight: 600; margin-bottom: 16px; color: #24292f; } .modal-description-KM3XGv { font-size: 14px; color: #57606a; margin-bottom: 8px; line-height: 1.5; } .modal-content-1Zb3tL input[type="text"], .modal-content-1Zb3tL textarea { width: 100%; margin-bottom: 16px; padding: 5px 12px; font-size: 14px; line-height: 20px; color: #24292f; vertical-align: middle; background-color: #ffffff; background-repeat: no-repeat; background-position: right 8px center; border: 1px solid #d0d7de; border-radius: 6px; box-shadow: inset 0 1px 0 rgba(208,215,222,0.2); box-sizing: border-box; } .modal-content-1Zb3tL textarea { height: 100px; resize: vertical; } .modal-content-1Zb3tL button { color: #ffffff; background-color: #2da44e; padding: 5px 16px; font-size: 14px; font-weight: 500; line-height: 20px; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid; border-radius: 6px; appearance: none; user-select: none; margin-left: 8px; } .modal-content-1Zb3tL button.cancel { color: #24292f; background-color: #f6f8fa; border-color: rgba(27,31,36,0.15); } .modal-content-1Zb3tL button:hover { background-color: #2c974b; } .modal-content-1Zb3tL button.cancel:hover { background-color: #f3f4f6; } .modal-hint-8yFDJi { font-size: 10px; color: #57606a; line-height: 1.1; font-style: italic; margin: 0; user-select: none; } #els-domains-7u6z9U { resize: none; margin-top: 12px; } .modal-buttons-L5xkyU { display: flex; justify-content: flex-end; margin-top: 16px; } .modal-checkbox-container-2Tgu5E { display: flex; align-items: center; margin-bottom: 16px; } .modal-checkbox-container-2Tgu5E input[type="checkbox"] { margin-right: 8px; width: auto; } .modal-checkbox-container-2Tgu5E label { font-size: 14px; color: #24292f; user-select: none; } .modal-content-1Zb3tL input[type="text"]:focus, .modal-content-1Zb3tL textarea:focus { outline: none; border-color: #0969da; box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.3); } `) // 从存储中获取保留参数的域名列表,如果没有则使用默认值 let domainsToKeepParams = GM_getValue('domainsToKeepParams', ['youtube.com', 'google.com']) // 获取保存的快捷键,默认为 'alt+c' let shortcut = GM_getValue('easyLinkShareShortcut', 'alt+c') // 获取保存的 Markdown 格式快捷键,默认为 'alt+shift+c' let markdownShortcut = GM_getValue('easyLinkShareMarkdownShortcut', 'alt+shift+c') // 添加新的 GM_getValue 调用来获取列表模式 let isBlacklistMode = GM_getValue('isBlacklistMode', false) // 用来跟踪模态框是否打开 let isModalOpen = false // 修改 URL 处理函数 function processUrl(url, domainsToKeepParams) { const urlObj = new URL(url) const domain = urlObj.hostname // 域名在列表中 const isInList = domainsToKeepParams.some(d => domain.endsWith(d)) if (isBlacklistMode) { // 黑名单模式:如果域名在列表中,则移除参数 if (isInList) { return `${urlObj.origin}${urlObj.pathname}` } } else { // 白名单模式:如果域名不在列表中,则移除参数 if (!isInList) { return `${urlObj.origin}${urlObj.pathname}` } } return url } // 创建通知功能 function createNotification() { return function showNotification(message) { const notification = document.createElement('div') notification.className = 'els-notification-Pfq0X5' notification.textContent = message document.body.appendChild(notification) setTimeout(() => { notification.style.opacity = '1' }, 10) setTimeout(() => { notification.style.opacity = '0' setTimeout(() => { document.body.removeChild(notification) }, 300) }, 3000) } } const showNotification = createNotification() function getCodePenTitle() { try { // 尝试从父窗口获取标题 return { title: window.parent.document.title, error: false } } catch (error) { // 如果无法访问父窗口,使用当前文档的标题 return { title: document.title.replace(' - CodePen', ''), error: true, } } } // 修改复制链接的主要函数 function copyLink(isMarkdown = false) { let url = window.location.href let title = document.title let showError = false // 特殊处理 CodePen if (url.includes('cdpn.io')) { try { url = window.parent.location.href const result = getCodePenTitle() title = result.title showError = result.error } catch (error) { showError = true } } if (showError) { showNotification('当前页面焦点可能在iframe中,请切换到主窗口进行复制') return // 如果出错,直接返回,不执行复制操作 } url = processUrl(url, domainsToKeepParams) const text = isMarkdown ? `[${title}](${url})` : `${title}\n${url}` GM_setClipboard(text, 'text') showNotification(isMarkdown ? 'Markdown 格式链接已复制到剪贴板!' : '链接已复制到剪贴板!') } // 解析快捷键字符串 function parseShortcut(shortcutStr) { const parts = shortcutStr.toLowerCase().split('+') return { altKey: parts.includes('alt') || parts.includes('option'), ctrlKey: parts.includes('ctrl') || parts.includes('control'), shiftKey: parts.includes('shift'), metaKey: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'), keys: parts.filter(part => !['alt', 'ctrl', 'control', 'shift', 'meta', 'win', 'option', 'cmd', 'command'].includes(part)), } } // 修改设置界面创建函数 function createSettingsUI(options) { const { shortcut, markdownShortcut, domainsToKeepParams, isBlacklistMode, onSave } = options const modalHTML = ` <div class="modal-overlay-8W2Q7t"> <div class="modal-content-1Zb3tL"> <div class="modal-title-cKu8SM">设置</div> <div class="modal-description-KM3XGv">设置快捷键组合以快速复制页面标题和链接。按 ESC 键撤销更改。</div> <input type="text" id="els-shortcut" placeholder="按下快捷键组合" value="${shortcut}" readonly> <div class="modal-description-KM3XGv">设置快捷键组合以复制 Markdown 格式的链接:</div> <input type="text" id="els-markdown-shortcut" placeholder="按下快捷键组合" value="${markdownShortcut}" readonly> <div class="modal-description-KM3XGv">参数去除模式:</div> <div class="modal-checkbox-container-2Tgu5E"> <input type="checkbox" id="els-blacklist-mode" ${isBlacklistMode ? 'checked' : ''}> <label for="els-blacklist-mode">黑名单模式(勾选后,列表中的域名复制时将不保留参数)</label> </div> <div class="modal-description-KM3XGv" id="els-domains-description-Cgt5uR">设置需要${isBlacklistMode ? '移除' : '保留'}参数的域名(每行一个):</div> <div class="modal-hint-8yFDJi" id="els-domains-hint-0DAupg">当前为${isBlacklistMode ? '黑名单' : '白名单'}模式。${isBlacklistMode ? '列表中的域名将不保留参数,其他域名将保留参数。' : '列表中的域名将保留参数,其他域名将不保留参数。'}</div> <textarea id="els-domains-7u6z9U">${domainsToKeepParams.join('\n')}</textarea> <div class="modal-buttons-L5xkyU"> <button id="els-cancel-W3OUcP" class="cancel">取消</button> <button id="els-save-e8UfX6">保存</button> </div> </div> </div> ` const modalContainer = document.createElement('div') modalContainer.innerHTML = modalHTML // 使用 top.document.body 而不是 document.body top.document.body.appendChild(modalContainer) const markdownShortcutInput = document.getElementById('els-markdown-shortcut') const shortcutInput = document.getElementById('els-shortcut') const domainsTextarea = document.getElementById('els-domains-7u6z9U') let listeningForShortcut = false let currentShortcut = [] let currentMarkdownShortcut = [] let listeningForMarkdownShortcut = false function resetShortcut() { shortcutInput.value = shortcut.toLowerCase() currentShortcut = shortcut.toLowerCase().split('+') listeningForShortcut = false } function resetMarkdownShortcut() { markdownShortcutInput.value = markdownShortcut.toLowerCase() currentMarkdownShortcut = markdownShortcut.toLowerCase().split('+') listeningForMarkdownShortcut = false } function closeSettings() { // 修改这里: 从 top.document.body 中移除 top.document.body.removeChild(modalContainer) document.removeEventListener('keydown', escapeHandler) isModalOpen = false } function escapeHandler(e) { if (e.key === 'Escape') { resetShortcut() resetMarkdownShortcut() domainsTextarea.value = domainsToKeepParams.join('\n') } } document.addEventListener('keydown', escapeHandler) shortcutInput.addEventListener('focus', () => { if (listeningForMarkdownShortcut) { resetMarkdownShortcut() } listeningForShortcut = true currentShortcut = [] shortcutInput.value = '按下快捷键组合...' }) markdownShortcutInput.addEventListener('focus', () => { if (listeningForShortcut) { resetShortcut() } listeningForMarkdownShortcut = true currentMarkdownShortcut = [] markdownShortcutInput.value = '按下快捷键组合...' }) document.addEventListener('keydown', (e) => { if (listeningForShortcut || listeningForMarkdownShortcut) { e.preventDefault() if (e.code === 'Escape') { listeningForShortcut ? resetShortcut() : resetMarkdownShortcut() return } const currentArray = listeningForShortcut ? currentShortcut : currentMarkdownShortcut const inputElement = listeningForShortcut ? shortcutInput : markdownShortcutInput if (['AltLeft', 'AltRight', 'ControlLeft', 'ControlRight', 'ShiftLeft', 'ShiftRight', 'MetaLeft', 'MetaRight'].includes(e.code)) { const modifier = e.code.replace('Left', '').replace('Right', '').toLowerCase() if (!currentArray.includes(modifier)) { currentArray.push(modifier) } } else { const keyDisplay = getKeyDisplay(e.code) if (!currentArray.includes(keyDisplay)) { currentArray.push(keyDisplay) } listeningForShortcut = false listeningForMarkdownShortcut = false } inputElement.value = currentArray.join('+') } }) document.addEventListener('keyup', (e) => { if ((listeningForShortcut || listeningForMarkdownShortcut) && ['alt', 'control', 'shift', 'meta'].includes(e.key.toLowerCase())) { listeningForShortcut = false listeningForMarkdownShortcut = false } }) const blacklistModeCheckbox = document.getElementById('els-blacklist-mode') const domainsDescription = document.getElementById('els-domains-description-Cgt5uR') const domainsHint = document.getElementById('els-domains-hint-0DAupg') blacklistModeCheckbox.addEventListener('change', (e) => { const isBlacklist = e.target.checked domainsDescription.textContent = `设置需要${isBlacklist ? '移除' : '保留'}参数的域名(每行一个):` domainsHint.textContent = `当前为${isBlacklist ? '黑名单' : '白名单'}模式。${isBlacklist ? '列表中的域名将不保留参数,其他域名将保留参数。' : '列表中的域名将保留参数,其他域名将不保留参数。'}` }) document.getElementById('els-save-e8UfX6').addEventListener('click', () => { const newShortcut = shortcutInput.value const newMarkdownShortcut = markdownShortcutInput.value const newDomains = domainsTextarea.value.split('\n').filter(d => d.trim() !== '') const newIsBlacklistMode = blacklistModeCheckbox.checked onSave({ shortcut: newShortcut, markdownShortcut: newMarkdownShortcut, domainsToKeepParams: newDomains, isBlacklistMode: newIsBlacklistMode, }) closeSettings() }) document.getElementById('els-cancel-W3OUcP').addEventListener('click', closeSettings) return closeSettings } // 修改打开设置界面函数 function openSettings() { isModalOpen = true createSettingsUI({ shortcut, markdownShortcut, domainsToKeepParams, isBlacklistMode, onSave: (newSettings) => { shortcut = newSettings.shortcut markdownShortcut = newSettings.markdownShortcut domainsToKeepParams = newSettings.domainsToKeepParams isBlacklistMode = newSettings.isBlacklistMode GM_setValue('easyLinkShareShortcut', shortcut) GM_setValue('easyLinkShareMarkdownShortcut', markdownShortcut) GM_setValue('domainsToKeepParams', domainsToKeepParams) GM_setValue('isBlacklistMode', isBlacklistMode) isModalOpen = false }, }) } function getKeyDisplay(code) { const keyMap = { Digit1: '1', Digit2: '2', Digit3: '3', Digit4: '4', Digit5: '5', Digit6: '6', Digit7: '7', Digit8: '8', Digit9: '9', Digit0: '0', KeyA: 'a', KeyB: 'b', KeyC: 'c', KeyD: 'd', KeyE: 'e', KeyF: 'f', KeyG: 'g', KeyH: 'h', KeyI: 'i', KeyJ: 'j', KeyK: 'k', KeyL: 'l', KeyM: 'm', KeyN: 'n', KeyO: 'o', KeyP: 'p', KeyQ: 'q', KeyR: 'r', KeyS: 's', KeyT: 't', KeyU: 'u', KeyV: 'v', KeyW: 'w', KeyX: 'x', KeyY: 'y', KeyZ: 'z', } return keyMap[code] || code.toLowerCase() } function matchShortcut(e, shortcut) { return (e.altKey === shortcut.altKey && e.ctrlKey === shortcut.ctrlKey && e.shiftKey === shortcut.shiftKey && e.metaKey === shortcut.metaKey && shortcut.keys.every(key => getKeyDisplay(e.code) === key.toLowerCase())) } // 修改快捷键监听 document.addEventListener('keydown', (e) => { // 如果模态框打开,不响应复制快捷键 if (isModalOpen) { return } const normalShortcut = parseShortcut(shortcut) const mdShortcut = parseShortcut(markdownShortcut) if (matchShortcut(e, normalShortcut)) { e.preventDefault() copyLink(false) } else if (matchShortcut(e, mdShortcut)) { e.preventDefault() copyLink(true) } }) // 注册油猴菜单命令 GM_registerMenuCommand('设置', openSettings) })()