@@ -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();
+}