about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume.gomez@huawei.com>2024-05-29 23:40:58 +0200
committerGuillaume Gomez <guillaume.gomez@huawei.com>2024-07-18 14:16:27 +0200
commit9946a68275dc37ef1c7d1727a7876d65646bee44 (patch)
tree2f656069e28906ce6aa2a97a815aa25fa124c922
parent52f3c71c8dc4aaed71e3035995fcbdd6d78c98c6 (diff)
downloadrust-9946a68275dc37ef1c7d1727a7876d65646bee44.tar.gz
rust-9946a68275dc37ef1c7d1727a7876d65646bee44.zip
Add copy code button
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css82
-rw-r--r--src/librustdoc/html/static/js/main.js90
2 files changed, 132 insertions, 40 deletions
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index cb8b82e8bde..aabbd21d702 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -16,6 +16,28 @@
 	--src-sidebar-width: 300px;
 	--desktop-sidebar-z-index: 100;
 	--sidebar-elems-left-padding: 24px;
+	/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
+	--clipboard-image: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
+xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
+<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
+0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
+7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
+2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
+<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
+</svg>');
+	--clipboard-image-big: url('data:image/svg+xml,<svg width="22" height="23" viewBox="0 0 24 25" \
+xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
+<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
+0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
+7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
+2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
+<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
+</svg>');
+	/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
+	--checkmark-image: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" \
+xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\
+<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
+</g></svg>');
 }
 
 /* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -1423,15 +1445,17 @@ documentation. */
 	top: 20px;
 }
 
-a.test-arrow {
+.example-wrap > a.test-arrow, .example-wrap .button-holder {
 	visibility: hidden;
 	position: absolute;
-	padding: 5px 10px 5px 10px;
-	border-radius: 5px;
-	font-size: 1.375rem;
 	top: 5px;
 	right: 5px;
 	z-index: 1;
+}
+a.test-arrow {
+	padding: 5px 10px 5px 10px;
+	border-radius: 5px;
+	font-size: 1.375rem;
 	color: var(--test-arrow-color);
 	background-color: var(--test-arrow-background-color);
 }
@@ -1439,9 +1463,41 @@ a.test-arrow:hover {
 	color: var(--test-arrow-hover-color);
 	background-color: var(--test-arrow-hover-background-color);
 }
-.example-wrap:hover .test-arrow {
+.example-wrap .button-holder {
+	display: flex;
+}
+.example-wrap:hover > .test-arrow {
+	padding: 3px 10px;
+}
+.example-wrap:hover > .test-arrow, .example-wrap:hover > .button-holder {
 	visibility: visible;
 }
+.example-wrap .button-holder .copy-button {
+	color: var(--copy-path-button-color);
+	background: var(--main-background-color);
+	height: 43px;
+	width: 40px;
+	margin-left: 5px;
+	padding: 2px 0 0 4px;
+	border: 0;
+	cursor: pointer;
+	border-radius: 5px;
+}
+.example-wrap .button-holder .copy-button.clicked {
+	padding-top: 4px;
+}
+.example-wrap .button-holder .copy-button::before {
+	filter: var(--copy-path-img-filter);
+	content: var(--clipboard-image-big);
+	width: 23px;
+	height: 22px;
+}
+.example-wrap .button-holder .copy-button:hover::before {
+	filter: var(--copy-path-img-hover-filter);
+}
+.example-wrap .button-holder .copy-button.clicked::before {
+	content: var(--checkmark-image);
+}
 
 .code-attribute {
 	font-weight: 300;
@@ -1699,15 +1755,7 @@ a.tooltip:hover::after {
 }
 #copy-path::before {
 	filter: var(--copy-path-img-filter);
-	/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
-	content: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
-xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
-<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
-0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
-7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
-2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
-<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
-</svg>');
+	content: var(--clipboard-image);
 	width: 19px;
 	height: 18px;
 }
@@ -1715,11 +1763,7 @@ xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
 	filter: var(--copy-path-img-hover-filter);
 }
 #copy-path.clicked::before {
-	/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
-	content: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" xmlns="http://www.w3.org/2000/svg" \
-		fill="black" height="18px">\
-		<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
-		</g></svg>');
+	content: var(--checkmark-image);
 }
 
 @keyframes rotating {
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 64c35660778..c2c1c5dd804 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1769,9 +1769,37 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
 }());
 
 // This section handles the copy button that appears next to the path breadcrumbs
+// and the copy buttons on the code examples.
 (function() {
-    let reset_button_timeout = null;
+    // Common functions to copy buttons.
+    function copyContentToClipboard(content) {
+        const el = document.createElement("textarea");
+        el.value = content;
+        el.setAttribute("readonly", "");
+        // To not make it appear on the screen.
+        el.style.position = "absolute";
+        el.style.left = "-9999px";
 
+        document.body.appendChild(el);
+        el.select();
+        document.execCommand("copy");
+        document.body.removeChild(el);
+    }
+
+    function copyButtonAnimation(button) {
+        button.classList.add("clicked");
+
+        if (button.reset_button_timeout !== undefined) {
+            window.clearTimeout(button.reset_button_timeout);
+        }
+
+        button.reset_button_timeout = window.setTimeout(() => {
+            button.reset_button_timeout = undefined;
+            button.classList.remove("clicked");
+        }, 1000);
+    }
+
+    // Copy button that appears next to the path breadcrumbs.
     const but = document.getElementById("copy-path");
     if (!but) {
         return;
@@ -1786,29 +1814,49 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
             }
         });
 
-        const el = document.createElement("textarea");
-        el.value = path.join("::");
-        el.setAttribute("readonly", "");
-        // To not make it appear on the screen.
-        el.style.position = "absolute";
-        el.style.left = "-9999px";
-
-        document.body.appendChild(el);
-        el.select();
-        document.execCommand("copy");
-        document.body.removeChild(el);
-
-        but.classList.add("clicked");
+        copyContentToClipboard(path.join("::"));
+        copyButtonAnimation(but);
+    };
 
-        if (reset_button_timeout !== null) {
-            window.clearTimeout(reset_button_timeout);
+    // Copy buttons on code examples.
+    function copyCode(codeElem) {
+        if (!codeElem) {
+            // Should never happen, but the world is a dark and dangerous place.
+            return;
         }
+        copyContentToClipboard(codeElem.textContent);
+    }
 
-        function reset_button() {
-            reset_button_timeout = null;
-            but.classList.remove("clicked");
+    function addCopyButton(event) {
+        let elem = event.target;
+        while (!hasClass(elem, "example-wrap")) {
+            elem = elem.parentElement;
+            if (elem.tagName === "body" || hasClass(elem, "docblock")) {
+                return;
+            }
+        }
+        // Since the button will be added, no need to keep this listener around.
+        elem.removeEventListener("mouseover", addCopyButton);
+
+        const parent = document.createElement("div");
+        parent.className = "button-holder";
+        const runButton = elem.querySelector(".test-arrow");
+        if (runButton !== null) {
+            // If there is a run button, we move it into the same div.
+            parent.appendChild(runButton);
         }
+        elem.appendChild(parent);
+        const copyButton = document.createElement("button");
+        copyButton.className = "copy-button";
+        copyButton.title = "Copy code to clipboard";
+        copyButton.addEventListener("click", () => {
+            copyCode(elem.querySelector("pre > code"));
+            copyButtonAnimation(copyButton);
+        });
+        parent.appendChild(copyButton);
+    }
 
-        reset_button_timeout = window.setTimeout(reset_button, 1000);
-    };
+    onEachLazy(document.querySelectorAll(".docblock .example-wrap"), elem => {
+        elem.addEventListener("mouseover", addCopyButton);
+    });
 }());