From 0941cc4a7950921dea0c14a72225be9ee6c391d0 Mon Sep 17 00:00:00 2001 From: John Bowdre Date: Sat, 21 Oct 2023 16:13:44 -0500 Subject: [PATCH] add copy button to code blocks --- assets/css/code-copy-button.css | 105 ++++++++++++++++++++++++++++++ {static => assets}/css/syntax.css | 0 assets/js/code-copy-button.js | 56 ++++++++++++++++ layouts/partials/footer.html | 5 ++ layouts/partials/head.html | 7 ++ static/css/custom.css | 2 - 6 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 assets/css/code-copy-button.css rename {static => assets}/css/syntax.css (100%) create mode 100644 assets/js/code-copy-button.js diff --git a/assets/css/code-copy-button.css b/assets/css/code-copy-button.css new file mode 100644 index 0000000..3be53ff --- /dev/null +++ b/assets/css/code-copy-button.css @@ -0,0 +1,105 @@ +/* adapted from https://aaronluna.dev/blog/add-copy-button-to-code-blocks-hugo-chroma/ */ + +.highlight-wrapper { + display: block; +} + +/* Start: Turn off individual column border, margin, and padding when line numbers are showing */ +.highlight-wrapper .lntd pre { + padding: 0; +} + +.chroma .lntd pre { + border: 0px solid #ccc; +} + +.chroma .lntd:first-child { + padding: 7px 7px 7px 10px; + margin: 0; +} + +.chroma .lntd:last-child { + padding: 7px 10px 7px 7px; + margin: 0; +} +/* End: Turn off individual column border, margin, and padding when line numbers are showing */ + +.highlight { + position: relative; + z-index: 0; + padding: 0; + margin:40px 0 10px 0; + border-radius: 4px; +} + +.highlight > .chroma { + position: static; + z-index: 1; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: 10px; +} + +.copy-code-button { + position: absolute; + z-index: 2; + right: 0; + top: -29px; + font-size: 13px; + font-weight: 700; + line-height: 14px; + letter-spacing: 0.5px; + width: 65px; + color: #ffffff; + background-color: #000000; + border: 1.25px solid #232326; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + white-space: nowrap; + padding: 6px 6px 7px 6px; + margin: 0 0 0 1px; + cursor: pointer; + opacity: 0.6; +} + +.copy-code-button:hover, +.copy-code-button:focus, +.copy-code-button:active, +.copy-code-button:active:hover { + color: #222225; + background-color: #b3b3b3; + opacity: 0.8; +} + +.copyable-text-area { + position: absolute; + height: 0; + z-index: -1; + opacity: .01; +} +.chroma [data-lang]:before { + position: absolute; + z-index: 0; + top: -29px; + left: 0; + content: attr(data-lang); + font-size: 13px; + font-weight: 700; + color: white; + background-color: black; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding: 6px 6px 7px 6px; + line-height: 14px; + opacity: 0.6; + position: absolute; + letter-spacing: 0.5px; + border: 1.25px solid #232326; + margin: 0 0 0 1px; +} diff --git a/static/css/syntax.css b/assets/css/syntax.css similarity index 100% rename from static/css/syntax.css rename to assets/css/syntax.css diff --git a/assets/js/code-copy-button.js b/assets/js/code-copy-button.js new file mode 100644 index 0000000..75848b3 --- /dev/null +++ b/assets/js/code-copy-button.js @@ -0,0 +1,56 @@ +// adapted from https://aaronluna.dev/blog/add-copy-button-to-code-blocks-hugo-chroma/ + +function createCopyButton(highlightDiv) { + const button = document.createElement("button"); + button.className = "copy-code-button"; + button.type = "button"; + button.innerText = "Copy"; + button.addEventListener("click", () => copyCodeToClipboard(button, highlightDiv)); + highlightDiv.insertBefore(button, highlightDiv.firstChild); + const wrapper = document.createElement("div"); + wrapper.className = "highlight-wrapper"; + highlightDiv.parentNode.insertBefore(wrapper, highlightDiv); + wrapper.appendChild(highlightDiv); +} + +document.querySelectorAll(".highlight").forEach((highlightDiv) => createCopyButton(highlightDiv)); + +async function copyCodeToClipboard(button, highlightDiv) { + // Need to get just the last-child of each .line element to avoid copying the line numbers + const nodeListToCopy = highlightDiv.querySelectorAll(":last-child > .chroma > code > .line > :last-child"); + // Reduce the nodeList to a string of text with each line separated by a newline + const codeToCopy = Array.from(nodeListToCopy).reduce((accumulator, line) => accumulator + line.innerText, ""); + try { + var result = await navigator.permissions.query({ name: "clipboard-write" }); + if (result.state == "granted" || result.state == "prompt") { + await navigator.clipboard.writeText(codeToCopy); + } else { + copyCodeBlockExecCommand(codeToCopy, highlightDiv); + } + } catch (_) { + copyCodeBlockExecCommand(codeToCopy, highlightDiv); + } finally { + button.blur(); + button.innerText = "Copied!"; + setTimeout(function () { + button.innerText = "Copy"; + }, 2000); } +} + +function copyCodeBlockExecCommand(codeToCopy, highlightDiv) { + console.log("We shouldn't get here..."); + const textArea = document.createElement("textArea"); + textArea.contentEditable = "true"; + textArea.readOnly = "false"; + textArea.className = "copyable-text-area"; + textArea.value = codeToCopy; + highlightDiv.insertBefore(textArea, highlightDiv.firstChild); + const range = document.createRange(); + range.selectNodeContents(textArea); + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + textArea.setSelectionRange(0, 999999); + document.execCommand("copy"); + highlightDiv.removeChild(textArea); +} diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index e833946..7898ca5 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -2,3 +2,8 @@ + +{{ if (findRE " +{{ end }} diff --git a/layouts/partials/head.html b/layouts/partials/head.html index ae37e1e..9aaafa4 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -21,3 +21,10 @@ + +{{ if (findRE " + {{ $copyCss := resources.Get "css/code-copy-button.css" | minify }} + +{{ end }} diff --git a/static/css/custom.css b/static/css/custom.css index d09bd0d..5fcc6a3 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,5 +1,3 @@ -@import 'syntax.css'; - /* override page max-width */ .page { max-width: 72rem;