diff --git a/assets/sass/content.scss b/assets/sass/content.scss index 1a03fc8..8a93c03 100644 --- a/assets/sass/content.scss +++ b/assets/sass/content.scss @@ -48,6 +48,54 @@ pre { padding: 1rem; border-radius: 5px; overflow-x: auto; + position: relative; +} + +.code-block-wrapper { + position: relative; + margin-bottom: 1.5rem; +} + +.copy-code-button { + position: absolute; + top: 8px; + right: 6px; + + width: 20px ; + height: 20px; + + padding: 0; + margin: 0; + + background: transparent; + border: none; + + display: flex; + align-items: center; + justify-content: center; + + cursor: pointer; + color: currentColor; + + z-index: 20; +} + +.copy-code-button svg { + width: 14px ; + height: 14px ; + display: block; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } details { @@ -58,5 +106,5 @@ details { } details[open] { - border-color: 1px solid var(--text-color); + border-color: var(--text-color); } diff --git a/assets/sass/global.scss b/assets/sass/global.scss index c0dfe14..96c5097 100644 --- a/assets/sass/global.scss +++ b/assets/sass/global.scss @@ -30,6 +30,7 @@ pre, code { font-family: Monaco, "Ubuntu Mono", Inconsolata, Consolas, monospace; } + pre { line-height: 1.4em; } @@ -57,4 +58,4 @@ em { &:focus { transform: translateY(0%); } -} +} \ No newline at end of file diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 429e9a6..36b1a1f 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -24,6 +24,7 @@ + @@ -45,6 +46,9 @@

Helmet.js

{{ block "main" . }}{{ end }} + + + diff --git a/static/js/copy-code.js b/static/js/copy-code.js new file mode 100644 index 0000000..5e0b0d4 --- /dev/null +++ b/static/js/copy-code.js @@ -0,0 +1,73 @@ +document.addEventListener("DOMContentLoaded", () => { + const blocks = document.querySelectorAll( + "pre > code.language-js, pre > code.language-javascript" + ); + + const COPY_SVG = ` + +`; + + const CHECK_SVG = ` + +`; + + function setButtonState(btn, iconHTML, srText) { + btn.innerHTML = ` + ${iconHTML} + ${srText} + `; + btn.setAttribute("aria-label", srText); + } + + blocks.forEach((code) => { + const pre = code.parentElement; + + let wrapper = pre.parentElement; + if (!wrapper.classList.contains("code-block-wrapper")) { + wrapper = document.createElement("div"); + wrapper.className = "code-block-wrapper"; + pre.replaceWith(wrapper); + wrapper.appendChild(pre); + } + + const btn = document.createElement("button"); + btn.className = "copy-code-button"; + setButtonState(btn, COPY_SVG, "Copy code"); + + wrapper.appendChild(btn); + + let resetTimeout = null; + let isCooling = false; + + btn.addEventListener("click", async () => { + if (isCooling) return; + isCooling = true; + + try { + await navigator.clipboard.writeText(code.innerText.trim()); + setButtonState(btn, CHECK_SVG, "Copied"); + } catch { + setButtonState(btn, COPY_SVG, "Copy code"); + } + + if (resetTimeout) clearTimeout(resetTimeout); + + resetTimeout = setTimeout(() => { + setButtonState(btn, COPY_SVG, "Copy code"); + isCooling = false; + resetTimeout = null; + }, 1000); + }); + }); +});