// ==UserScript== // @name Add copy buttons to
 blocks
// @namespace    https://github.com/chirag127/userscripts
// @version      0.1.0
// @description  Inject a small "copy" button on every code block so you can grab the snippet without hand-selecting. One click copies innerText; button flashes "copied" for 1s.
// @author       chirag127
// @match        *://*/*
// @run-at       document-idle
// @grant        GM_setClipboard
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @homepageURL  https://github.com/chirag127/userscripts/tree/main/scripts/add-code-copy-buttons
// @supportURL   https://github.com/chirag127/userscripts/issues
// @updateURL    https://raw.githubusercontent.com/chirag127/userscripts/main/scripts/add-code-copy-buttons/add-code-copy-buttons.user.js
// @downloadURL  https://raw.githubusercontent.com/chirag127/userscripts/main/scripts/add-code-copy-buttons/add-code-copy-buttons.user.js
// ==/UserScript==

(() => {
  'use strict'

  const MARK = 'data-ccb-injected'
  const SELECTOR = 'pre code, pre.prettyprint, div.highlight pre'

  const enabled = GM_getValue('enabled', true)
  GM_registerMenuCommand(
    `Copy buttons: ${enabled ? 'ON' : 'OFF'} — click to toggle`,
    () => { GM_setValue('enabled', !enabled); location.reload() }
  )
  if (!enabled) return

  // Resolve the visual container to position the button against. For `pre code`
  // we want the 
, not the inner , so absolute positioning covers the
  // whole block. For `div.highlight pre` the 
 itself is fine.
  function hostOf(el) {
    if (el.tagName === 'CODE' && el.parentElement && el.parentElement.tagName === 'PRE') return el.parentElement
    return el
  }

  function makeButton(codeEl) {
    const btn = document.createElement('button')
    btn.type = 'button'
    btn.textContent = 'copy'
    Object.assign(btn.style, {
      position: 'absolute',
      top: '6px',
      right: '6px',
      zIndex: '2147483646',
      padding: '2px 8px',
      fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
      fontSize: '11px',
      lineHeight: '1.4',
      color: '#fff',
      background: 'rgba(0, 0, 0, 0.55)',
      border: '1px solid rgba(255, 255, 255, 0.2)',
      borderRadius: '4px',
      cursor: 'pointer',
      opacity: '0.7',
    })
    btn.addEventListener('mouseenter', () => { btn.style.opacity = '1' })
    btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.7' })
    btn.addEventListener('click', (e) => {
      e.preventDefault()
      e.stopPropagation()
      GM_setClipboard(codeEl.innerText)
      const prev = btn.textContent
      btn.textContent = 'copied'
      setTimeout(() => { btn.textContent = prev }, 1000)
    })
    return btn
  }

  function inject(el) {
    const host = hostOf(el)
    if (host.hasAttribute(MARK)) return
    host.setAttribute(MARK, '1')
    // Only override position if the host is static — don't fight a host page
    // that already positions its 
 relatively/absolutely.
    const pos = getComputedStyle(host).position
    if (pos === 'static') host.style.position = 'relative'
    host.appendChild(makeButton(el))
  }

  function scan(root) {
    root.querySelectorAll(SELECTOR).forEach(inject)
  }

  scan(document)

  // Handle SPA route changes + late-rendered code blocks (docs sites, Discourse,
  // GitHub file view, etc.). Debounced via microtask coalescing.
  let pending = false
  const mo = new MutationObserver(() => {
    if (pending) return
    pending = true
    queueMicrotask(() => { pending = false; scan(document) })
  })
  mo.observe(document.body, { childList: true, subtree: true })
})()