Copy Selected Links as Markdown

Copy selected link(s) on any page as Markdown: [text](url).

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name                 Copy Selected Links as Markdown
// @name:zh-CN           复制选中链接为 Markdown
// @namespace            https://github.com/utags
// @homepageURL          https://github.com/utags/userscripts#readme
// @supportURL           https://github.com/utags/userscripts/issues
// @version              0.1.2
// @description          Copy selected link(s) on any page as Markdown: [text](url).
// @description:zh-CN    在任意页面将选中的链接复制为 Markdown 格式:[文本](链接)。
// @icon                 data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2064%2064%22%20fill%3D%22none%22%3E%3Crect%20x%3D%228%22%20y%3D%228%22%20width%3D%2248%22%20height%3D%2248%22%20rx%3D%2210%22%20stroke%3D%22%231f2937%22%20stroke-width%3D%224%22/%3E%3Cpath%20d%3D%22M18%2046V18l14%2022L46%2018v28%22%20stroke%3D%22%231f2937%22%20stroke-width%3D%226%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22/%3E%3C/svg%3E
// @author               Pipecraft
// @license              MIT
// @match                *://*/*
// @run-at               document-idle
// @grant                GM_registerMenuCommand
// ==/UserScript==
//
;(() => {
  'use strict'
  function escapeMD(s) {
    s = String(s || '')
    return s
      .replaceAll('|', '\\|')
      .replaceAll('[', '\\[')
      .replaceAll(']', '\\]')
  }
  function getSelectionAnchors() {
    const sel = globalThis.getSelection()
    if (!sel || sel.rangeCount === 0) return []
    const set = /* @__PURE__ */ new Set()
    for (let i = 0; i < sel.rangeCount; i++) {
      const range = sel.getRangeAt(i)
      const common = range.commonAncestorContainer
      const rootEl =
        common.nodeType === Node.TEXT_NODE ? common.parentElement : common
      if (rootEl) {
        const as = rootEl.querySelectorAll('a[href]')
        for (const a of Array.from(as)) {
          if (a instanceof HTMLAnchorElement) {
            try {
              if (range.intersectsNode(a)) set.add(a)
            } catch (e) {}
          }
        }
      }
      let startNode = range.startContainer
      if (startNode && startNode.nodeType === Node.TEXT_NODE)
        startNode = startNode.parentElement
      let cur = startNode instanceof HTMLElement ? startNode : void 0
      while (cur) {
        if (cur instanceof HTMLAnchorElement && cur.getAttribute('href')) {
          set.add(cur)
          break
        }
        cur = cur.parentElement
      }
    }
    return Array.from(set)
  }
  function buildMarkdown() {
    const sel = globalThis.getSelection()
    const textSel = sel ? sel.toString().trim() : ''
    const anchors = getSelectionAnchors()
    const origin = location.origin
    if (anchors.length === 1) {
      const a = anchors[0]
      const name = textSel || (a.textContent || '').trim() || a.href
      const url = new URL(a.getAttribute('href') || a.href, origin).href
      return '['.concat(escapeMD(name), '](').concat(escapeMD(url), ')')
    }
    if (anchors.length > 1) {
      return anchors
        .map((a) => {
          const name = (a.textContent || '').trim() || a.href
          const url = new URL(a.getAttribute('href') || a.href, origin).href
          return '- ['.concat(escapeMD(name), '](').concat(escapeMD(url), ')')
        })
        .join('\n')
    }
    if (textSel) {
      const m = /https?:\/\/[^\s)]+/.exec(textSel)
      if (m) {
        const url = m[0]
        const name = textSel.length > url.length ? textSel : url
        return '['.concat(escapeMD(name), '](').concat(escapeMD(url), ')')
      }
    }
    return '['
      .concat(escapeMD(document.title), '](')
      .concat(escapeMD(location.href), ')')
  }
  async function copyText(s) {
    try {
      await navigator.clipboard.writeText(s)
      return
    } catch (e) {}
    try {
      const ta = document.createElement('textarea')
      ta.style.position = 'fixed'
      ta.style.opacity = '0'
      ta.value = s
      document.body.append(ta)
      ta.focus()
      ta.select()
      document.execCommand('copy')
      ta.remove()
    } catch (e) {}
  }
  function run() {
    const md = buildMarkdown()
    void copyText(md)
  }
  try {
    const gmRegisterMenuCommand = globalThis.GM_registerMenuCommand
    if (typeof gmRegisterMenuCommand === 'function') {
      gmRegisterMenuCommand(
        '\u590D\u5236\u9009\u4E2D\u94FE\u63A5\u4E3A Markdown',
        run
      )
    }
  } catch (e) {}
  globalThis.addEventListener('keydown', (e) => {
    if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.code === 'KeyM') {
      e.preventDefault()
      run()
    }
  })
})()