Easy Link Share

快捷复制便于分享的页面标题和URL,支持自定义快捷键,支持设置是否保留URL参数

目前为 2024-10-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         Easy Link Share
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @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'

  // 从存储中获取保留参数的域名列表,如果没有则使用默认值
  let domainsToKeepParams = GM_getValue('domainsToKeepParams', ['youtube.com', 'youtu.be'])

  // 处理URL,根据域名决定是否保留参数
  function processUrl(url) {
    const urlObj = new URL(url)
    const domain = urlObj.hostname

    if (!domainsToKeepParams.some(d => domain.endsWith(d))) {
      urlObj.search = '' // 移除所有参数
    }

    return urlObj.toString()
  }

  // 获取保存的快捷键,默认为 'alt+c'
  let shortcut = GM_getValue('easyLinkShareShortcut', 'alt+c')

  // 创建设置界面
  function createSettingsUI() {
    GM_addStyle(`
      .modal-overlay {
        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: 9999;
      }
      .modal-content {
        background-color: #f6f8fa;
        padding: 24px;
        border-radius: 6px;
        width: 400px;
        box-shadow: 0 8px 24px rgba(140,149,159,0.2);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
      }
      .modal-title {
        font-size: 20px;
        font-weight: 600;
        margin-bottom: 16px;
        color: #24292f;
      }
      .modal-description {
        font-size: 14px;
        color: #57606a;
        margin-bottom: 16px;
        line-height: 1.5;
      }
      .modal-content input, .modal-content 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 textarea {
        height: 100px;
        resize: vertical;
      }
      .modal-content 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-right: 8px;
      }
      .modal-content button.cancel {
        color: #24292f;
        background-color: #f6f8fa;
        border-color: rgba(27,31,36,0.15);
      }
      .modal-content button:hover {
        background-color: #2c974b;
      }
      .modal-content button.cancel:hover {
        background-color: #f3f4f6;
      }
      .modal-hint {
        font-size: 10px;
        color: #57606a;
        margin-top: -12px;
        margin-bottom: 8px;
        font-style: italic;
      }
      #els-domains {
        resize: none;
      }
    `)

    const modalHTML = `
    <div class="modal-overlay">
      <div class="modal-content">
        <h2 class="modal-title">Easy Link Share 设置</h2>
        <p class="modal-description">设置快捷键组合以快速复制页面标题和链接。按 ESC 键撤销更改。</p>
        <input type="text" id="els-shortcut" placeholder="按下快捷键组合" value="${shortcut}" readonly>
        <p class="modal-description">设置需要保留参数的域名(每行一个):</p>
        <p class="modal-hint">下方添加的域名在复制链接时会保留 URL 参数。</p>
        <p class="modal-hint">· 例如:YouTube的url参数形如 ?v=dQw4w9WgXcQ 这样的是有用的,应声明</p>
        <p class="modal-hint">· 又例如:Bilibili的url参数形如 ?spm_id_from=abcd 这样的追踪参数是无用的,不应声明</p>
        <p class="modal-hint">当你需要保留url上的参数时,请在此处添加,默认不会复制参数部分。</p>
        <textarea id="els-domains">${domainsToKeepParams.join('\n')}</textarea>
        <button id="els-save">保存</button>
        <button id="els-cancel" class="cancel">取消</button>
      </div>
    </div>
  `

    const modalContainer = document.createElement('div')
    modalContainer.innerHTML = modalHTML
    document.body.appendChild(modalContainer)

    const shortcutInput = document.getElementById('els-shortcut')
    const domainsTextarea = document.getElementById('els-domains')
    let listeningForShortcut = false
    let currentShortcut = []

    shortcutInput.addEventListener('focus', () => {
      listeningForShortcut = true
      currentShortcut = []
      shortcutInput.value = '按下快捷键组合...'
    })

    function resetShortcut() {
      shortcutInput.value = shortcut
      currentShortcut = shortcut.split('+')
      listeningForShortcut = false
    }

    function closeSettings() {
      modalContainer.remove()
      document.removeEventListener('keydown', escapeHandler)
    }

    function escapeHandler(e) {
      if (e.key === 'Escape') {
        resetShortcut()
        domainsTextarea.value = domainsToKeepParams.join('\n')
      }
    }

    document.addEventListener('keydown', escapeHandler)

    document.addEventListener('keydown', (e) => {
      if (listeningForShortcut) {
        e.preventDefault()
        if (e.key === 'Escape') {
          resetShortcut()
          return
        }
        const key = e.key.toLowerCase()
        if (['alt', 'control', 'shift', 'meta'].includes(key)) {
          if (!currentShortcut.includes(key)) {
            currentShortcut.push(key)
          }
        }
        else {
          if (!currentShortcut.includes(key)) {
            currentShortcut.push(key)
          }
          listeningForShortcut = false
        }
        shortcutInput.value = currentShortcut.join('+')
      }
    })

    document.addEventListener('keyup', (e) => {
      if (listeningForShortcut && ['alt', 'control', 'shift', 'meta'].includes(e.key.toLowerCase())) {
        listeningForShortcut = false
      }
    })

    document.getElementById('els-save').addEventListener('click', () => {
      const newShortcut = shortcutInput.value
      GM_setValue('easyLinkShareShortcut', newShortcut)
      shortcut = newShortcut

      const newDomains = domainsTextarea.value.split('\n').filter(d => d.trim() !== '')
      GM_setValue('domainsToKeepParams', newDomains)
      domainsToKeepParams = newDomains

      closeSettings()
    })

    document.getElementById('els-cancel').addEventListener('click', closeSettings)
  }

  // 添加通知样式
  GM_addStyle(`
    .els-notification {
      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;
    }
  `)

  // 显示通知的函数
  function showNotification(message) {
    const notification = document.createElement('div')
    notification.className = 'els-notification'
    notification.textContent = message
    document.body.appendChild(notification)

    // 使通知可见
    setTimeout(() => {
      notification.style.opacity = '1'
    }, 10)

    // 3秒后隐藏通知
    setTimeout(() => {
      notification.style.opacity = '0'
      setTimeout(() => {
        document.body.removeChild(notification)
      }, 300)
    }, 3000)
  }

  // 复制链接的主要函数
  function copyLink() {
    const title = document.title
    const url = processUrl(window.location.href)
    const text = `${title}\n${url}`

    GM_setClipboard(text, 'text')
    showNotification('链接已复制到剪贴板!')
  }

  // 解析快捷键字符串
  function parseShortcut(shortcutStr) {
    const parts = shortcutStr.toLowerCase().split('+')
    return {
      altKey: parts.includes('alt'),
      ctrlKey: parts.includes('ctrl') || parts.includes('control'),
      shiftKey: parts.includes('shift'),
      metaKey: parts.includes('meta') || parts.includes('win'),
      keys: parts.filter(part => !['alt', 'ctrl', 'control', 'shift', 'meta', 'win'].includes(part)),
    }
  }

  // 添加快捷键监听
  document.addEventListener('keydown', (e) => {
    const { altKey, ctrlKey, shiftKey, metaKey, keys } = parseShortcut(shortcut)

    if (e.altKey === altKey
      && e.ctrlKey === ctrlKey
      && e.shiftKey === shiftKey
      && e.metaKey === metaKey
      && keys.every(key => e.key.toLowerCase() === key)) {
      e.preventDefault() // 防止默认行为
      copyLink()
    }
  })

  // 注册(不可用)油猴菜单命令
  GM_registerMenuCommand('设置', createSettingsUI)
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址