diff --git a/_layouts/docs.liquid b/_layouts/docs.liquid index 01ff7698992..7a5116aac74 100644 --- a/_layouts/docs.liquid +++ b/_layouts/docs.liquid @@ -2,6 +2,8 @@ layout: default --- + +
@@ -97,3 +99,4 @@ layout: default anchors.add(); anchors.remove('.h4'); + diff --git a/assets/css/copyIcon.css b/assets/css/copyIcon.css new file mode 100644 index 00000000000..a85ed420f35 --- /dev/null +++ b/assets/css/copyIcon.css @@ -0,0 +1,79 @@ +.code-block-wrapper { + position: relative; + margin: 1em 0; +} + +.code-block-wrapper pre { + margin: 0; +} + +.copy-button { + position: absolute; + top: 8px; + right: 8px; + display: flex; + align-items: center; + gap: 4px; + padding: 6px 10px; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #d0d7de; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + color: #24292f; + cursor: pointer; + opacity: 0; + transition: opacity 0.2s ease, background-color 0.2s ease; + z-index: 10; +} + +.copy-button:hover { + background-color: #f6f8fa; + border-color: #b1b7c1; +} + +.copy-button:active { + background-color: #e9ecef; +} + +.copy-button.copied { + color: #1a7f37; + border-color: #1a7f37; +} + +.copy-button svg { + flex-shrink: 0; +} + +.copy-button .copy-text { + user-select: none; +} + +.code-block-wrapper:hover .copy-button { + opacity: 1; +} + +@media (hover: none) { + .copy-button { + opacity: 1; + } +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .copy-button { + background-color: rgba(45, 51, 59, 0.95); + color: #c9d1d9; + border-color: #444c56; + } + + .copy-button:hover { + background-color: #30363d; + border-color: #6e7681; + } + + .copy-button.copied { + color: #57ab5a; + border-color: #57ab5a; + } +} diff --git a/js/copyIcon.js b/js/copyIcon.js new file mode 100644 index 00000000000..b8eb7767078 --- /dev/null +++ b/js/copyIcon.js @@ -0,0 +1,90 @@ +function copyToClipboard(text) { + if(navigatior.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.style.position = "fixed"; + textArea.style.opacity = "0"; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + return Promise.resolve(); + } +} + +function addCopyButtons() { + //find all codeBlocks + const codeBlocks = document.querySelectorAll("pre > code"); + + codeBlocks.forEach((codeBlock) => { + const pre = codeBlock.parentElement + + if(!pre.parentElement.classList.contains("code-block-wrapper")) { + const wrapper = document.createElement("div"); + wrapper.className = "code-block-wrapper"; + pre.parentNode.insertBefore(wrapper, pre) + wrapper.appendChild(pre); + } + + if(pre.parentElement.querySelector(".copy-button")) { + return + } + + //create copy button + const copyButton = document.createElement("button"); + copyButton.className = "copy-button" + copyButton.innerHTML = ` + + + + + Copy + `; + copyButton.setAttribute("aria-label", "Copy code to clipboard"); + copyButton.setAttribute("type", "button"); + + //add click event + copyButton.addEventListener("click", async () => { + const code = codeBlock.textContent; + + try { + await copyToClipboard(code); + + //show success feedback + copyButton.classList.add("copied"); + copyButton.innerHTML = ` + + + + Copied! + `; + + //reset button after 2 secs + setTimeout(() => { + copyButton.classList.remove("copied"); + copyButton.innerHTML = ` + + + + + Copy + `; + }, 2000); + } catch (err) { + console.error(`Failed to copy: ${err}`); + } + }); + + //add button to wrapper + pre.parentElement.appendChild(copyButton); + + }); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtons); +} else { + addCopyButtons(); +}