From 1a9aeabbc7b638d018644bc5059172246898b060 Mon Sep 17 00:00:00 2001 From: Rishi2600 Date: Fri, 21 Nov 2025 17:09:48 +0530 Subject: [PATCH 1/2] feat: add copy to clipboard button for code blocks - Add copy button that appears on hover for all code blocks - Include visual feedback with 'Copied!' confirmation - Support both modern and fallback clipboard APIs - Style with Primer design system conventions - Mobile-friendly with always-visible button on touch devices Fixes #645 --- _layouts/default.liquid | 2 + assets/css/copyIcon.css | 79 ++++++++++++++++++++++++++++++++++++ js/copyIcon.js | 90 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 assets/css/copyIcon.css create mode 100644 js/copyIcon.js diff --git a/_layouts/default.liquid b/_layouts/default.liquid index 253f7bcbcc8..08bbd9dfaea 100644 --- a/_layouts/default.liquid +++ b/_layouts/default.liquid @@ -7,6 +7,7 @@ rel="stylesheet" href="/assets/dist/styles.css" /> + {% include "nav" %} {{ content }} {% include "footer" %} + diff --git a/assets/css/copyIcon.css b/assets/css/copyIcon.css new file mode 100644 index 00000000000..6fd6565c7e0 --- /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; + } +} \ No newline at end of file diff --git a/js/copyIcon.js b/js/copyIcon.js new file mode 100644 index 00000000000..e023ffb746e --- /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(); +} \ No newline at end of file From aff0be8350872929aaa2a82f955659b85d0c1227 Mon Sep 17 00:00:00 2001 From: Rishi2600 Date: Sat, 22 Nov 2025 09:44:24 +0530 Subject: [PATCH 2/2] fix: moved CSS/JS to docs layout and added new lines --- _layouts/default.liquid | 2 -- _layouts/docs.liquid | 3 +++ assets/css/copyIcon.css | 2 +- js/copyIcon.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/_layouts/default.liquid b/_layouts/default.liquid index 08bbd9dfaea..253f7bcbcc8 100644 --- a/_layouts/default.liquid +++ b/_layouts/default.liquid @@ -7,7 +7,6 @@ rel="stylesheet" href="/assets/dist/styles.css" /> - {% include "nav" %} {{ content }} {% include "footer" %} - 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 index 6fd6565c7e0..a85ed420f35 100644 --- a/assets/css/copyIcon.css +++ b/assets/css/copyIcon.css @@ -76,4 +76,4 @@ color: #57ab5a; border-color: #57ab5a; } -} \ No newline at end of file +} diff --git a/js/copyIcon.js b/js/copyIcon.js index e023ffb746e..b8eb7767078 100644 --- a/js/copyIcon.js +++ b/js/copyIcon.js @@ -87,4 +87,4 @@ if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", addCopyButtons); } else { addCopyButtons(); -} \ No newline at end of file +}