Skip to content

Commit aca0666

Browse files
authored
copy button (#177)
- client.js only - visual feedback with css - uses navigator.clipboard.writeText - adds the copy button on all PRE > CODE (or even PRE > SPAN) elements - pass data-copy=off to disable supersedes #138
1 parent bdc138f commit aca0666

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

public/client.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,39 @@ function preventDoubleClick(event) {
334334
for (const summary of document.querySelectorAll("#observablehq-sidebar summary")) {
335335
summary.onmousedown = preventDoubleClick;
336336
}
337+
338+
// copy code cells
339+
document.addEventListener("pointerover", ({target}) => {
340+
if (typeof navigator?.clipboard?.writeText !== "function") return;
341+
if (target.nodeName === "PRE" && !target.getAttribute("data-copy")) {
342+
target.addEventListener("pointermove", move);
343+
target.addEventListener("pointerleave", out);
344+
}
345+
function out() {
346+
target.removeEventListener("pointermove", move);
347+
target.removeEventListener("pointerleave", out);
348+
target.removeEventListener("click", copy);
349+
target.removeAttribute("data-copy");
350+
}
351+
function move({offsetX: x}) {
352+
if (30 + x > parseInt(getComputedStyle(target).width)) {
353+
if (!target.getAttribute("data-copy")) {
354+
target.setAttribute("data-copy", "copy");
355+
target.addEventListener("click", copy);
356+
}
357+
} else {
358+
if (target.getAttribute("data-copy")) {
359+
target.removeAttribute("data-copy");
360+
target.removeEventListener("click", copy);
361+
}
362+
}
363+
}
364+
async function copy() {
365+
try {
366+
await navigator.clipboard.writeText(target.textContent);
367+
target.setAttribute("data-copy", "copied");
368+
} catch {
369+
target.setAttribute("data-copy", "error");
370+
}
371+
}
372+
});

public/style.css

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,7 @@ pre {
360360
overflow-x: auto;
361361
}
362362

363-
pre .language-md::after,
364-
pre .language-tex::after,
365-
pre .language-dot::after,
366-
pre .language-mermaid::after,
367-
pre .language-js::after,
368-
pre .language-sh::after,
369-
pre .language-sql::after {
363+
pre code::after {
370364
position: absolute;
371365
right: 0;
372366
top: 0;
@@ -377,13 +371,17 @@ pre .language-sql::after {
377371
padding: 0.5rem 0.5rem 0.5rem 1rem;
378372
}
379373

380-
pre .language-md::after { content: "md"; }
381-
pre .language-js::after { content: "js"; }
382-
pre .language-sh::after { content: "sh"; }
383-
pre .language-sql::after { content: "sql"; }
384-
pre .language-tex::after { content: "tex"; }
385-
pre .language-dot::after { content: "dot"; }
386-
pre .language-mermaid::after { content: "mermaid"; }
374+
pre code.language-md::after { content: "md"; }
375+
pre code.language-js::after { content: "js"; }
376+
pre code.language-sh::after { content: "sh"; }
377+
pre code.language-sql::after { content: "sql"; }
378+
pre code.language-tex::after { content: "tex"; }
379+
pre code.language-dot::after { content: "dot"; }
380+
pre code.language-mermaid::after { content: "mermaid"; }
381+
pre[data-copy="copy"] code::after { content: "copy"; }
382+
pre[data-copy="copied"] code::after { content: "copied"; }
383+
pre[data-copy="error"] code::after { content: "error"; }
384+
pre code::after { pointer-events: none; }
387385

388386
input:not([type]),
389387
input[type="email"],

0 commit comments

Comments
 (0)