Skip to content

Instantly share code, notes, and snippets.

@keeprock
Last active February 25, 2026 06:21
Show Gist options
  • Select an option

  • Save keeprock/361f3355629e558975464f8c6a998b97 to your computer and use it in GitHub Desktop.

Select an option

Save keeprock/361f3355629e558975464f8c6a998b97 to your computer and use it in GitHub Desktop.
Anki copy code button

So you want a fancy “copy code” button on your Anki card.

Paste the snippet below into both the Front and Back templates.

Purely vibe-coded, but don't worry, I'm a professional software developer. It looks kinda fine to me.

<style>
  .prettify-flashcard .highlight { position: relative; }

  .anki-copy-btn {
    position: absolute;
    top: -8px;
    right: 5px;

    width: 30px;
    height: 30px;
    padding: 0;

    display: inline-flex;
    align-items: center;
    justify-content: center;

    border-radius: 7px;
    border: 1px solid rgba(255,255,255,0.12);
    background: rgba(0,0,0,0.18);
    color: rgba(255,255,255,0.85);

    cursor: pointer;
    user-select: none;
    z-index: 9999;

    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 120ms ease, transform 120ms ease, background 120ms ease;
    backdrop-filter: blur(6px);
  }

  .highlight:hover .anki-copy-btn,
  .highlight:focus-within .anki-copy-btn {
    opacity: 1;
    transform: translateY(0);
  }

  .anki-copy-btn:hover { background: rgba(0,0,0,0.28); }
  .anki-copy-btn:active { transform: translateY(0) scale(0.98); }

  .anki-copy-btn svg {
    width: 14px;
    height: 14px;
    display: block;
  }

  .anki-copy-btn[data-state="success"] {
    background: rgba(0,0,0,0.28);
    border-color: rgba(255,255,255,0.18);
  }
</style>

<script>
(() => {
  const ICON_COPY = `
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <path fill="currentColor" d="M16 1H6c-1.1 0-2 .9-2 2v12h2V3h10V1zm3 4H10c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h9c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16h-9V7h9v14z"/>
    </svg>
  `;
  const ICON_CHECK = `
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <path fill="currentColor" d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z"/>
    </svg>
  `;

  const SUCCESS_MS = 850;

  function setState(btn, state) {
    btn.dataset.state = state;
    btn.innerHTML = (state === "success") ? ICON_CHECK : ICON_COPY;
    btn.title = (state === "success") ? "Copied" : "Copy";
    btn.setAttribute("aria-label", (state === "success") ? "Copied" : "Copy code");
  }

  function tryExecCommandCopy(text) {
    try {
      const ta = document.createElement("textarea");
      ta.value = text;

      ta.setAttribute("readonly", "");
      ta.style.position = "fixed";
      ta.style.left = "-9999px";
      ta.style.top = "0";
      ta.style.opacity = "0";

      document.body.appendChild(ta);
      ta.focus();
      ta.select();
      ta.setSelectionRange(0, ta.value.length);

      const ok = document.execCommand("copy");
      document.body.removeChild(ta);

      return ok;
    } catch (e) {
      return false;
    }
  }

  function withTimeout(promise, ms) {
    return Promise.race([
      promise,
      new Promise((_, reject) => setTimeout(() => reject(new Error("clipboard timeout")), ms)),
    ]);
  }

  async function copyText(text) {
    const okExec = tryExecCommandCopy(text);
    if (okExec) return true;

    const write = navigator?.clipboard?.writeText;
    if (!write) return false;

    try {
      await withTimeout(navigator.clipboard.writeText(text), 200);
      return true;
    } catch (e) {
      return false;
    }
  }

  function attachToHighlight(highlightEl) {
    if (!highlightEl) return;
    if (highlightEl.querySelector(".anki-copy-btn")) return;

    const pre = highlightEl.querySelector("pre");
    if (!pre) return;

    const btn = document.createElement("button");
    btn.type = "button";
    btn.className = "anki-copy-btn";

    btn._resetTimer = null;
    btn._busy = false;

    setState(btn, "idle");

    btn.addEventListener("click", async (e) => {
      e.preventDefault();
      e.stopPropagation();

      if (btn._busy) return;
      btn._busy = true;

      try {
        await copyText(pre.innerText);const ok = await copyText(pre.innerText);
        if (ok) {
          setState(btn, "success");
          if (btn._resetTimer) clearTimeout(btn._resetTimer);
          btn._resetTimer = setTimeout(() => {
            setState(btn, "idle");
            btn._resetTimer = null;
          }, SUCCESS_MS);
        } else {
          setState(btn, "idle");
        }
      } catch (err) {
        setState(btn, "idle");
      } finally {
        btn._busy = false;
      }
    });

    highlightEl.appendChild(btn);
  }

  function enhance(root = document) {
    const highlights = root.querySelectorAll(".prettify-flashcard .highlight");
    highlights.forEach(attachToHighlight);
  }

  enhance();

  const obs = new MutationObserver(() => enhance());
  obs.observe(document.documentElement, { childList: true, subtree: true });

  setTimeout(enhance, 50);
  setTimeout(enhance, 200);
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment