From 9946a68275dc37ef1c7d1727a7876d65646bee44 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 29 May 2024 23:40:58 +0200 Subject: [PATCH 1/4] Add copy code button --- src/librustdoc/html/static/css/rustdoc.css | 82 +++++++++++++++----- src/librustdoc/html/static/js/main.js | 90 +++++++++++++++++----- 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 cb8b82e8bde0a..aabbd21d70261 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 */ + --clipboard-image: url('data:image/svg+xml,\ +\ +\ +'); + --clipboard-image-big: url('data:image/svg+xml,\ +\ +\ +'); + /* Checkmark */ + --checkmark-image: url('data:image/svg+xml,\ +\ +'); } /* 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 */ - content: url('data:image/svg+xml,\ -\ -\ -'); + 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 */ - content: url('data:image/svg+xml,\ - \ - '); + 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 64c356607788c..c2c1c5dd8047b 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); + }); }()); From ddaa90fc56bf4b047061a9639097083a61155b8c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 30 May 2024 18:20:28 +0200 Subject: [PATCH 2/4] Add rustdoc GUI test for new copy code feature --- tests/rustdoc-gui/copy-code.goml | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/rustdoc-gui/copy-code.goml diff --git a/tests/rustdoc-gui/copy-code.goml b/tests/rustdoc-gui/copy-code.goml new file mode 100644 index 0000000000000..726f9bba9b644 --- /dev/null +++ b/tests/rustdoc-gui/copy-code.goml @@ -0,0 +1,48 @@ +// Checks that the "copy code" button is not triggering JS error and its display +// isn't broken. +go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html" + +define-function: ( + "check-copy-button", + [], + block { + // First we ensure that there are no "copy code" currently existing. + assert-count: (".example-wrap .copy-button", 0) + move-cursor-to: ".example-wrap" + assert-count: (".example-wrap .copy-button", 1) + // We now ensure it's only displayed when the example is hovered. + assert-css: (".example-wrap .copy-button", { "visibility": "visible" }) + move-cursor-to: ".search-input" + assert-css: (".example-wrap .copy-button", { "visibility": "hidden" }) + }, +) + +call-function: ("check-copy-button", {}) +// Checking that the run button and the copy button have the same height. +compare-elements-size: ( + ".example-wrap:nth-of-type(1) .test-arrow", + ".example-wrap:nth-of-type(1) .copy-button", + ["height"], +) +// ... and the same y position. +compare-elements-position: ( + ".example-wrap:nth-of-type(1) .test-arrow", + ".example-wrap:nth-of-type(1) .copy-button", + ["y"], +) +store-size: (".example-wrap:nth-of-type(1) .copy-button", { + "height": copy_height, + "width": copy_width, +}) +assert: |copy_height| > 0 && |copy_width| > 0 + +// Checking same things for the copy button when there is no run button. +go-to: "file://" + |DOC_PATH| + "/lib2/sub_mod/struct.Foo.html" +call-function: ("check-copy-button", {}) +// Ensure there is no run button. +assert-count: (".example-wrap .test-arrow", 0) +// Check it's the same size without a run button. +assert-size: (".example-wrap:nth-of-type(1) .copy-button", { + "height": |copy_height|, + "width": |copy_width|, +}) From 578810b4d21875ca05c89b3b6982c4bb952ba34d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 3 Jun 2024 09:55:11 +0200 Subject: [PATCH 3/4] Make "copy code" button the same size as the "copy path" button --- src/librustdoc/html/static/css/rustdoc.css | 34 +++++++--------------- tests/rustdoc-gui/copy-code.goml | 6 ++++ tests/rustdoc-gui/run-on-hover.goml | 4 +-- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index aabbd21d70261..821e6379e2add 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -25,14 +25,8 @@ xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\ 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"/>\ \ '); - --clipboard-image-big: url('data:image/svg+xml,\ -\ -\ -'); + --copy-path-height: 34px; + --copy-path-width: 33px; /* Checkmark */ --checkmark-image: url('data:image/svg+xml,\ @@ -1453,9 +1447,9 @@ documentation. */ z-index: 1; } a.test-arrow { - padding: 5px 10px 5px 10px; + padding: 5px 7px; border-radius: 5px; - font-size: 1.375rem; + font-size: 1rem; color: var(--test-arrow-color); background-color: var(--test-arrow-background-color); } @@ -1467,7 +1461,7 @@ a.test-arrow:hover { display: flex; } .example-wrap:hover > .test-arrow { - padding: 3px 10px; + padding: 2px 7px; } .example-wrap:hover > .test-arrow, .example-wrap:hover > .button-holder { visibility: visible; @@ -1475,28 +1469,24 @@ a.test-arrow:hover { .example-wrap .button-holder .copy-button { color: var(--copy-path-button-color); background: var(--main-background-color); - height: 43px; - width: 40px; + height: var(--copy-path-height); + width: var(--copy-path-width); 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; + content: var(--clipboard-image); } .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); + padding-right: 5px; } .code-attribute { @@ -1745,8 +1735,8 @@ a.tooltip:hover::after { #copy-path { color: var(--copy-path-button-color); background: var(--main-background-color); - height: 34px; - width: 33px; + height: var(--copy-path-height); + width: var(--copy-path-width); margin-left: 10px; padding: 0; padding-left: 2px; @@ -1756,8 +1746,6 @@ a.tooltip:hover::after { #copy-path::before { filter: var(--copy-path-img-filter); content: var(--clipboard-image); - width: 19px; - height: 18px; } #copy-path:hover::before { filter: var(--copy-path-img-hover-filter); diff --git a/tests/rustdoc-gui/copy-code.goml b/tests/rustdoc-gui/copy-code.goml index 726f9bba9b644..72a5bece1758c 100644 --- a/tests/rustdoc-gui/copy-code.goml +++ b/tests/rustdoc-gui/copy-code.goml @@ -14,6 +14,12 @@ define-function: ( assert-css: (".example-wrap .copy-button", { "visibility": "visible" }) move-cursor-to: ".search-input" assert-css: (".example-wrap .copy-button", { "visibility": "hidden" }) + // Checking that the copy button has the same size as the "copy path" button. + compare-elements-size: ( + "#copy-path", + ".example-wrap:nth-of-type(1) .copy-button", + ["height", "width"], + ) }, ) diff --git a/tests/rustdoc-gui/run-on-hover.goml b/tests/rustdoc-gui/run-on-hover.goml index 087dd3374f803..1698a61b14728 100644 --- a/tests/rustdoc-gui/run-on-hover.goml +++ b/tests/rustdoc-gui/run-on-hover.goml @@ -17,7 +17,7 @@ define-function: ( "visibility": "visible", "color": |color|, "background-color": |background|, - "font-size": "22px", + "font-size": "16px", "border-radius": "5px", }) move-cursor-to: ".test-arrow" @@ -25,7 +25,7 @@ define-function: ( "visibility": "visible", "color": |hover_color|, "background-color": |hover_background|, - "font-size": "22px", + "font-size": "16px", "border-radius": "5px", }) }, From 26d72512e0c7e9077134173aa60d77ba08ac3e9a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 3 Jun 2024 21:27:29 +0200 Subject: [PATCH 4/4] Unify UI between code block buttons and top buttons --- src/librustdoc/html/static/css/rustdoc.css | 21 ++++++++++++++------- tests/rustdoc-gui/run-on-hover.goml | 4 ++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 821e6379e2add..8b8f596bdeb8e 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -32,6 +32,8 @@ xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ \ '); + --button-left-margin: 4px; + --button-border-radius: 2px; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -740,6 +742,11 @@ ul.block, .block li { position: relative; margin-bottom: 10px; } + +.rustdoc .example-wrap > pre { + border-radius: 6px; +} + /* For the last child of a div, the margin will be taken care of by the margin-top of the next item. */ .rustdoc .example-wrap:last-child { @@ -1442,13 +1449,13 @@ documentation. */ .example-wrap > a.test-arrow, .example-wrap .button-holder { visibility: hidden; position: absolute; - top: 5px; - right: 5px; + top: 4px; + right: 4px; z-index: 1; } a.test-arrow { padding: 5px 7px; - border-radius: 5px; + border-radius: var(--button-border-radius); font-size: 1rem; color: var(--test-arrow-color); background-color: var(--test-arrow-background-color); @@ -1471,11 +1478,11 @@ a.test-arrow:hover { background: var(--main-background-color); height: var(--copy-path-height); width: var(--copy-path-width); - margin-left: 5px; + margin-left: var(--button-left-margin); padding: 2px 0 0 4px; border: 0; cursor: pointer; - border-radius: 5px; + border-radius: var(--button-border-radius); } .example-wrap .button-holder .copy-button::before { filter: var(--copy-path-img-filter); @@ -1652,7 +1659,7 @@ a.tooltip:hover::after { } #settings-menu, #help-button { - margin-left: 4px; + margin-left: var(--button-left-margin); display: flex; } #sidebar-button { @@ -1683,7 +1690,7 @@ a.tooltip:hover::after { justify-content: center; background-color: var(--button-background-color); border: 1px solid var(--border-color); - border-radius: 2px; + border-radius: var(--button-border-radius); color: var(--settings-button-color); /* Rare exception to specifying font sizes in rem. Since this is acting as an icon, it's okay to specify their sizes in pixels. */ diff --git a/tests/rustdoc-gui/run-on-hover.goml b/tests/rustdoc-gui/run-on-hover.goml index 1698a61b14728..b62da79b780e9 100644 --- a/tests/rustdoc-gui/run-on-hover.goml +++ b/tests/rustdoc-gui/run-on-hover.goml @@ -18,7 +18,7 @@ define-function: ( "color": |color|, "background-color": |background|, "font-size": "16px", - "border-radius": "5px", + "border-radius": "2px", }) move-cursor-to: ".test-arrow" assert-css: (".test-arrow:hover", { @@ -26,7 +26,7 @@ define-function: ( "color": |hover_color|, "background-color": |hover_background|, "font-size": "16px", - "border-radius": "5px", + "border-radius": "2px", }) }, )