Files
expressjs.com/js/copycode.js
shubham oulkar f24f45a281 feat: add copy code btn (#1841)
* test: copy code btn

* add copy btn svg

* add copy code btn

* fix: keyboard a11y and improve design

* show "copied !" text on the copy btn

* remove space in copied text

* Remove text shift

* handle failed copy code

* Minimize delay

* remove outline on code blocks

* refactor copycode.js

* Remove border width

Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>

* Convert timerId into a Number()

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Carlos Stenzel <carlosstenzel@hotmail.com>
Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-25 19:51:12 -05:00

58 lines
1.6 KiB
JavaScript

const codeBlocks = document.querySelectorAll("pre:has(code)");
codeBlocks.forEach((block) => {
// Only add button if browser supports Clipboard API
if (!navigator.clipboard) return;
const button = createCopyButton();
block.appendChild(button);
block.setAttribute("tabindex", 0); // Add keyboard a11y for <pre></pre>
button.addEventListener("click", async () => {
await copyCode(block, button);
});
});
function createCopyButton() {
const button = document.createElement("button");
setButtonAttributes(button, {
type: "button", // button doesn't act as a submit button
title: "copy code",
"aria-label": "click to copy code",
});
return button;
}
function setButtonAttributes(button, attributes) {
for (const [key, value] of Object.entries(attributes)) {
button.setAttribute(key, value);
}
}
async function copyCode(block, button) {
const code = block.querySelector("code");
const text = code.innerText;
try {
await navigator.clipboard.writeText(text);
updateButtonState(button, "copied", "code is copied!");
} catch {
updateButtonState(button, "failed", "failed!");
}
}
function updateButtonState(button, statusClass, ariaLabel) {
button.setAttribute("aria-live", "polite");
button.setAttribute("aria-label", ariaLabel);
button.classList.add(statusClass);
// Clear any existing timer
if (button.dataset.timerId) clearTimeout(Number(button.dataset.timerId));
const timer = setTimeout(() => {
button.classList.remove(statusClass);
button.setAttribute("aria-label", "click to copy code");
}, 1000);
button.dataset.timerId = timer;
}