diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 41c506f33dc..e936e1ca07e 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -16,6 +16,24 @@ --src-sidebar-width: 300px; --desktop-sidebar-z-index: 100; --sidebar-elems-left-padding: 24px; + /* clipboard */ + --clipboard-image: url('data:image/svg+xml,\ +\ +\ +'); + --copy-path-height: 34px; + --copy-path-width: 33px; + /* Checkmark */ + --checkmark-image: url('data:image/svg+xml,\ +\ +'); + --button-left-margin: 4px; + --button-border-radius: 2px; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -723,6 +741,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 { @@ -1427,15 +1450,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; + top: 4px; + right: 4px; z-index: 1; +} +a.test-arrow { + padding: 5px 7px; + border-radius: var(--button-border-radius); + font-size: 1rem; color: var(--test-arrow-color); background-color: var(--test-arrow-background-color); } @@ -1443,9 +1468,37 @@ 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: 2px 7px; +} +.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: var(--copy-path-height); + width: var(--copy-path-width); + margin-left: var(--button-left-margin); + padding: 2px 0 0 4px; + border: 0; + cursor: pointer; + border-radius: var(--button-border-radius); +} +.example-wrap .button-holder .copy-button::before { + filter: var(--copy-path-img-filter); + 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 { font-weight: 300; @@ -1610,7 +1663,7 @@ a.tooltip:hover::after { } #settings-menu, #help-button { - margin-left: 4px; + margin-left: var(--button-left-margin); display: flex; } #sidebar-button { @@ -1641,7 +1694,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. */ @@ -1693,8 +1746,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; @@ -1703,27 +1756,13 @@ a.tooltip:hover::after { } #copy-path::before { filter: var(--copy-path-img-filter); - /* clipboard */ - content: url('data:image/svg+xml,\ -\ -\ -'); - width: 19px; - height: 18px; + content: var(--clipboard-image); } #copy-path:hover::before { 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 9506bc9ed22..40d65ae7910 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1771,9 +1771,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; @@ -1788,29 +1816,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"); - - if (reset_button_timeout !== null) { - window.clearTimeout(reset_button_timeout); - } - - function reset_button() { - reset_button_timeout = null; - but.classList.remove("clicked"); - } - - reset_button_timeout = window.setTimeout(reset_button, 1000); + copyContentToClipboard(path.join("::")); + copyButtonAnimation(but); }; + + // 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 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); + } + + onEachLazy(document.querySelectorAll(".docblock .example-wrap"), elem => { + elem.addEventListener("mouseover", addCopyButton); + }); }()); diff --git a/tests/rustdoc-gui/copy-code.goml b/tests/rustdoc-gui/copy-code.goml new file mode 100644 index 00000000000..72a5bece175 --- /dev/null +++ b/tests/rustdoc-gui/copy-code.goml @@ -0,0 +1,54 @@ +// 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" }) + // 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"], + ) + }, +) + +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|, +}) diff --git a/tests/rustdoc-gui/run-on-hover.goml b/tests/rustdoc-gui/run-on-hover.goml index 087dd3374f8..b62da79b780 100644 --- a/tests/rustdoc-gui/run-on-hover.goml +++ b/tests/rustdoc-gui/run-on-hover.goml @@ -17,16 +17,16 @@ define-function: ( "visibility": "visible", "color": |color|, "background-color": |background|, - "font-size": "22px", - "border-radius": "5px", + "font-size": "16px", + "border-radius": "2px", }) move-cursor-to: ".test-arrow" assert-css: (".test-arrow:hover", { "visibility": "visible", "color": |hover_color|, "background-color": |hover_background|, - "font-size": "22px", - "border-radius": "5px", + "font-size": "16px", + "border-radius": "2px", }) }, )