Skip to content

Commit 6e4a3a9

Browse files
authored
Unrolled build for rust-lang#125779
Rollup merge of rust-lang#125779 - GuillaumeGomez:copy-code, r=rustdoc-team [rustdoc] Add copy code feature This PR adds a "copy code" to code blocks. Since this is a JS only feature, the HTML is generated with JS when the user hovers the code block to prevent generating DOM unless needed. Two things to note: 1. I voluntarily kept the current behaviour of the run button (only when hovering a code block with a mouse) so it doesn't do anything on mobile. I plan to send a follow-up where the buttons would "expandable" or something. Still need to think which approach would be the best. 2. I used a picture and not text like the run button to remain consistent with the "copy path" button. I'd also prefer for the run button to use a picture (like what is used in mdbook) but again, that's something to be discussed later on. The rendering looks like this: ![Screenshot from 2024-06-03 21-29-48](https://github.com/rust-lang/rust/assets/3050060/a0b18f9c-b3dd-4a65-89a7-5a7a303b5c2b) ![Screenshot from 2024-06-03 21-30-20](https://github.com/rust-lang/rust/assets/3050060/b3b084ff-2716-4160-820b-d4774681a961) It can be tested [here](https://guillaume-gomez.fr/rustdoc/bar/struct.Bar.html) (without the run button) and [here](https://guillaume-gomez.fr/rustdoc/foo/struct.Bar.html) (with the run button). Fixes rust-lang#86851. r? ``@notriddle``
2 parents 188ddf4 + 26d7251 commit 6e4a3a9

File tree

4 files changed

+193
-52
lines changed

4 files changed

+193
-52
lines changed

src/librustdoc/html/static/css/rustdoc.css

+66-27
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@
1616
--src-sidebar-width: 300px;
1717
--desktop-sidebar-z-index: 100;
1818
--sidebar-elems-left-padding: 24px;
19+
/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
20+
--clipboard-image: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
21+
xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
22+
<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 \
23+
0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
24+
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 \
25+
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"/>\
26+
<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
27+
</svg>');
28+
--copy-path-height: 34px;
29+
--copy-path-width: 33px;
30+
/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
31+
--checkmark-image: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" \
32+
xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\
33+
<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
34+
</g></svg>');
35+
--button-left-margin: 4px;
36+
--button-border-radius: 2px;
1937
}
2038

2139
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -723,6 +741,11 @@ ul.block, .block li {
723741
position: relative;
724742
margin-bottom: 10px;
725743
}
744+
745+
.rustdoc .example-wrap > pre {
746+
border-radius: 6px;
747+
}
748+
726749
/* For the last child of a div, the margin will be taken care of
727750
by the margin-top of the next item. */
728751
.rustdoc .example-wrap:last-child {
@@ -1427,25 +1450,55 @@ documentation. */
14271450
top: 20px;
14281451
}
14291452

1430-
a.test-arrow {
1453+
.example-wrap > a.test-arrow, .example-wrap .button-holder {
14311454
visibility: hidden;
14321455
position: absolute;
1433-
padding: 5px 10px 5px 10px;
1434-
border-radius: 5px;
1435-
font-size: 1.375rem;
1436-
top: 5px;
1437-
right: 5px;
1456+
top: 4px;
1457+
right: 4px;
14381458
z-index: 1;
1459+
}
1460+
a.test-arrow {
1461+
padding: 5px 7px;
1462+
border-radius: var(--button-border-radius);
1463+
font-size: 1rem;
14391464
color: var(--test-arrow-color);
14401465
background-color: var(--test-arrow-background-color);
14411466
}
14421467
a.test-arrow:hover {
14431468
color: var(--test-arrow-hover-color);
14441469
background-color: var(--test-arrow-hover-background-color);
14451470
}
1446-
.example-wrap:hover .test-arrow {
1471+
.example-wrap .button-holder {
1472+
display: flex;
1473+
}
1474+
.example-wrap:hover > .test-arrow {
1475+
padding: 2px 7px;
1476+
}
1477+
.example-wrap:hover > .test-arrow, .example-wrap:hover > .button-holder {
14471478
visibility: visible;
14481479
}
1480+
.example-wrap .button-holder .copy-button {
1481+
color: var(--copy-path-button-color);
1482+
background: var(--main-background-color);
1483+
height: var(--copy-path-height);
1484+
width: var(--copy-path-width);
1485+
margin-left: var(--button-left-margin);
1486+
padding: 2px 0 0 4px;
1487+
border: 0;
1488+
cursor: pointer;
1489+
border-radius: var(--button-border-radius);
1490+
}
1491+
.example-wrap .button-holder .copy-button::before {
1492+
filter: var(--copy-path-img-filter);
1493+
content: var(--clipboard-image);
1494+
}
1495+
.example-wrap .button-holder .copy-button:hover::before {
1496+
filter: var(--copy-path-img-hover-filter);
1497+
}
1498+
.example-wrap .button-holder .copy-button.clicked::before {
1499+
content: var(--checkmark-image);
1500+
padding-right: 5px;
1501+
}
14491502

14501503
.code-attribute {
14511504
font-weight: 300;
@@ -1610,7 +1663,7 @@ a.tooltip:hover::after {
16101663
}
16111664

16121665
#settings-menu, #help-button {
1613-
margin-left: 4px;
1666+
margin-left: var(--button-left-margin);
16141667
display: flex;
16151668
}
16161669
#sidebar-button {
@@ -1641,7 +1694,7 @@ a.tooltip:hover::after {
16411694
justify-content: center;
16421695
background-color: var(--button-background-color);
16431696
border: 1px solid var(--border-color);
1644-
border-radius: 2px;
1697+
border-radius: var(--button-border-radius);
16451698
color: var(--settings-button-color);
16461699
/* Rare exception to specifying font sizes in rem. Since this is acting
16471700
as an icon, it's okay to specify their sizes in pixels. */
@@ -1693,8 +1746,8 @@ a.tooltip:hover::after {
16931746
#copy-path {
16941747
color: var(--copy-path-button-color);
16951748
background: var(--main-background-color);
1696-
height: 34px;
1697-
width: 33px;
1749+
height: var(--copy-path-height);
1750+
width: var(--copy-path-width);
16981751
margin-left: 10px;
16991752
padding: 0;
17001753
padding-left: 2px;
@@ -1703,27 +1756,13 @@ a.tooltip:hover::after {
17031756
}
17041757
#copy-path::before {
17051758
filter: var(--copy-path-img-filter);
1706-
/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
1707-
content: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
1708-
xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
1709-
<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 \
1710-
0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
1711-
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 \
1712-
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"/>\
1713-
<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
1714-
</svg>');
1715-
width: 19px;
1716-
height: 18px;
1759+
content: var(--clipboard-image);
17171760
}
17181761
#copy-path:hover::before {
17191762
filter: var(--copy-path-img-hover-filter);
17201763
}
17211764
#copy-path.clicked::before {
1722-
/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
1723-
content: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" xmlns="http://www.w3.org/2000/svg" \
1724-
fill="black" height="18px">\
1725-
<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
1726-
</g></svg>');
1765+
content: var(--checkmark-image);
17271766
}
17281767

17291768
@keyframes rotating {

src/librustdoc/html/static/js/main.js

+69-21
Original file line numberDiff line numberDiff line change
@@ -1771,9 +1771,37 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
17711771
}());
17721772

17731773
// This section handles the copy button that appears next to the path breadcrumbs
1774+
// and the copy buttons on the code examples.
17741775
(function() {
1775-
let reset_button_timeout = null;
1776+
// Common functions to copy buttons.
1777+
function copyContentToClipboard(content) {
1778+
const el = document.createElement("textarea");
1779+
el.value = content;
1780+
el.setAttribute("readonly", "");
1781+
// To not make it appear on the screen.
1782+
el.style.position = "absolute";
1783+
el.style.left = "-9999px";
17761784

1785+
document.body.appendChild(el);
1786+
el.select();
1787+
document.execCommand("copy");
1788+
document.body.removeChild(el);
1789+
}
1790+
1791+
function copyButtonAnimation(button) {
1792+
button.classList.add("clicked");
1793+
1794+
if (button.reset_button_timeout !== undefined) {
1795+
window.clearTimeout(button.reset_button_timeout);
1796+
}
1797+
1798+
button.reset_button_timeout = window.setTimeout(() => {
1799+
button.reset_button_timeout = undefined;
1800+
button.classList.remove("clicked");
1801+
}, 1000);
1802+
}
1803+
1804+
// Copy button that appears next to the path breadcrumbs.
17771805
const but = document.getElementById("copy-path");
17781806
if (!but) {
17791807
return;
@@ -1788,29 +1816,49 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
17881816
}
17891817
});
17901818

1791-
const el = document.createElement("textarea");
1792-
el.value = path.join("::");
1793-
el.setAttribute("readonly", "");
1794-
// To not make it appear on the screen.
1795-
el.style.position = "absolute";
1796-
el.style.left = "-9999px";
1797-
1798-
document.body.appendChild(el);
1799-
el.select();
1800-
document.execCommand("copy");
1801-
document.body.removeChild(el);
1802-
1803-
but.classList.add("clicked");
1819+
copyContentToClipboard(path.join("::"));
1820+
copyButtonAnimation(but);
1821+
};
18041822

1805-
if (reset_button_timeout !== null) {
1806-
window.clearTimeout(reset_button_timeout);
1823+
// Copy buttons on code examples.
1824+
function copyCode(codeElem) {
1825+
if (!codeElem) {
1826+
// Should never happen, but the world is a dark and dangerous place.
1827+
return;
18071828
}
1829+
copyContentToClipboard(codeElem.textContent);
1830+
}
18081831

1809-
function reset_button() {
1810-
reset_button_timeout = null;
1811-
but.classList.remove("clicked");
1832+
function addCopyButton(event) {
1833+
let elem = event.target;
1834+
while (!hasClass(elem, "example-wrap")) {
1835+
elem = elem.parentElement;
1836+
if (elem.tagName === "body" || hasClass(elem, "docblock")) {
1837+
return;
1838+
}
1839+
}
1840+
// Since the button will be added, no need to keep this listener around.
1841+
elem.removeEventListener("mouseover", addCopyButton);
1842+
1843+
const parent = document.createElement("div");
1844+
parent.className = "button-holder";
1845+
const runButton = elem.querySelector(".test-arrow");
1846+
if (runButton !== null) {
1847+
// If there is a run button, we move it into the same div.
1848+
parent.appendChild(runButton);
18121849
}
1850+
elem.appendChild(parent);
1851+
const copyButton = document.createElement("button");
1852+
copyButton.className = "copy-button";
1853+
copyButton.title = "Copy code to clipboard";
1854+
copyButton.addEventListener("click", () => {
1855+
copyCode(elem.querySelector("pre > code"));
1856+
copyButtonAnimation(copyButton);
1857+
});
1858+
parent.appendChild(copyButton);
1859+
}
18131860

1814-
reset_button_timeout = window.setTimeout(reset_button, 1000);
1815-
};
1861+
onEachLazy(document.querySelectorAll(".docblock .example-wrap"), elem => {
1862+
elem.addEventListener("mouseover", addCopyButton);
1863+
});
18161864
}());

tests/rustdoc-gui/copy-code.goml

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Checks that the "copy code" button is not triggering JS error and its display
2+
// isn't broken.
3+
go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html"
4+
5+
define-function: (
6+
"check-copy-button",
7+
[],
8+
block {
9+
// First we ensure that there are no "copy code" currently existing.
10+
assert-count: (".example-wrap .copy-button", 0)
11+
move-cursor-to: ".example-wrap"
12+
assert-count: (".example-wrap .copy-button", 1)
13+
// We now ensure it's only displayed when the example is hovered.
14+
assert-css: (".example-wrap .copy-button", { "visibility": "visible" })
15+
move-cursor-to: ".search-input"
16+
assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
17+
// Checking that the copy button has the same size as the "copy path" button.
18+
compare-elements-size: (
19+
"#copy-path",
20+
".example-wrap:nth-of-type(1) .copy-button",
21+
["height", "width"],
22+
)
23+
},
24+
)
25+
26+
call-function: ("check-copy-button", {})
27+
// Checking that the run button and the copy button have the same height.
28+
compare-elements-size: (
29+
".example-wrap:nth-of-type(1) .test-arrow",
30+
".example-wrap:nth-of-type(1) .copy-button",
31+
["height"],
32+
)
33+
// ... and the same y position.
34+
compare-elements-position: (
35+
".example-wrap:nth-of-type(1) .test-arrow",
36+
".example-wrap:nth-of-type(1) .copy-button",
37+
["y"],
38+
)
39+
store-size: (".example-wrap:nth-of-type(1) .copy-button", {
40+
"height": copy_height,
41+
"width": copy_width,
42+
})
43+
assert: |copy_height| > 0 && |copy_width| > 0
44+
45+
// Checking same things for the copy button when there is no run button.
46+
go-to: "file://" + |DOC_PATH| + "/lib2/sub_mod/struct.Foo.html"
47+
call-function: ("check-copy-button", {})
48+
// Ensure there is no run button.
49+
assert-count: (".example-wrap .test-arrow", 0)
50+
// Check it's the same size without a run button.
51+
assert-size: (".example-wrap:nth-of-type(1) .copy-button", {
52+
"height": |copy_height|,
53+
"width": |copy_width|,
54+
})

tests/rustdoc-gui/run-on-hover.goml

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ define-function: (
1717
"visibility": "visible",
1818
"color": |color|,
1919
"background-color": |background|,
20-
"font-size": "22px",
21-
"border-radius": "5px",
20+
"font-size": "16px",
21+
"border-radius": "2px",
2222
})
2323
move-cursor-to: ".test-arrow"
2424
assert-css: (".test-arrow:hover", {
2525
"visibility": "visible",
2626
"color": |hover_color|,
2727
"background-color": |hover_background|,
28-
"font-size": "22px",
29-
"border-radius": "5px",
28+
"font-size": "16px",
29+
"border-radius": "2px",
3030
})
3131
},
3232
)

0 commit comments

Comments
 (0)