// Shiki's HTML (svelte.config.js's highlight.highlighter hook) is plain, // static markup with no interactivity of its own — the copy button and its // click handling only exist once this runs client-side. Called from // docs/+layout.svelte's afterNavigate, since the layout persists across // docs navigations and wouldn't otherwise see a later page's code blocks. const COPY_ICON = ``; export function wireCodeCopyButtons() { for (const pre of document.querySelectorAll("pre.shiki")) { // afterNavigate can fire for a same-page hash-only navigation too, where // this pre is the exact same DOM node as last time — skip it rather // than stack a second button on top of the first. if (pre.querySelector(":scope > .copy-button")) continue; const code = pre.querySelector("code"); if (!code) continue; const button = document.createElement("button"); button.type = "button"; button.className = "copy-button"; button.setAttribute("aria-label", "Copy code"); button.innerHTML = COPY_ICON; pre.appendChild(button); // One listener on the block itself, not the button — a click on the // button bubbles up to it too, so "click anywhere on the code block" // and "click the button" both take this one path. pre.addEventListener("click", () => { // A click that ends a manual text-selection drag (e.g. selecting one // line to copy by hand) still fires a click event on mouseup — skip // the whole-block copy then, rather than clobber a selection the // reader just deliberately made with the button's own click handler. const selection = window.getSelection(); if (selection && !selection.isCollapsed && pre.contains(selection.anchorNode)) { return; } // Both `?.`s matter: navigator.clipboard is absent entirely on an // insecure origin, and writeText itself can still reject (a denied // permission) even when it exists. Either way, there's nothing more // useful to do client-side than leave the text selectable as-is — // the click feedback below runs regardless. navigator.clipboard?.writeText(code.textContent ?? "")?.catch(() => {}); button.classList.add("copied"); setTimeout(() => button.classList.remove("copied"), 500); }); } }