Skip to content

Commit ea0c3df

Browse files
Add copy code button
1 parent a83cf56 commit ea0c3df

File tree

2 files changed

+137
-40
lines changed

2 files changed

+137
-40
lines changed

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

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

2042
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -1376,25 +1398,59 @@ documentation. */
13761398
top: 20px;
13771399
}
13781400

1379-
a.test-arrow {
1401+
.example-wrap > a.test-arrow, .example-wrap .button-holder {
13801402
visibility: hidden;
13811403
position: absolute;
1382-
padding: 5px 10px 5px 10px;
1383-
border-radius: 5px;
1384-
font-size: 1.375rem;
13851404
top: 5px;
13861405
right: 5px;
13871406
z-index: 1;
1407+
}
1408+
a.test-arrow {
1409+
padding: 5px 10px 5px 10px;
1410+
border-radius: 5px;
1411+
font-size: 1.375rem;
13881412
color: var(--test-arrow-color);
13891413
background-color: var(--test-arrow-background-color);
13901414
}
13911415
a.test-arrow:hover {
13921416
color: var(--test-arrow-hover-color);
13931417
background-color: var(--test-arrow-hover-background-color);
13941418
}
1395-
.example-wrap:hover .test-arrow {
1419+
.example-wrap .button-holder {
1420+
display: flex;
1421+
}
1422+
.example-wrap:hover > .test-arrow {
1423+
padding: 3px 10px;
1424+
}
1425+
.example-wrap:hover > .test-arrow, .example-wrap:hover > .button-holder {
13961426
visibility: visible;
13971427
}
1428+
.example-wrap .button-holder .copy-button {
1429+
color: var(--copy-path-button-color);
1430+
background: var(--main-background-color);
1431+
height: 43px;
1432+
width: 40px;
1433+
margin-left: 5px;
1434+
padding: 2px 0 0 4px;
1435+
border: 0;
1436+
cursor: pointer;
1437+
border-radius: 5px;
1438+
}
1439+
.example-wrap .button-holder .copy-button.clicked {
1440+
padding-top: 4px;
1441+
}
1442+
.example-wrap .button-holder .copy-button::before {
1443+
filter: var(--copy-path-img-filter);
1444+
content: var(--clipboard-image-big);
1445+
width: 23px;
1446+
height: 22px;
1447+
}
1448+
.example-wrap .button-holder .copy-button:hover::before {
1449+
filter: var(--copy-path-img-hover-filter);
1450+
}
1451+
.example-wrap .button-holder .copy-button.clicked::before {
1452+
content: var(--checkmark-image);
1453+
}
13981454

13991455
.code-attribute {
14001456
font-weight: 300;
@@ -1652,27 +1708,15 @@ a.tooltip:hover::after {
16521708
}
16531709
#copy-path::before {
16541710
filter: var(--copy-path-img-filter);
1655-
/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
1656-
content: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
1657-
xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
1658-
<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 \
1659-
0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
1660-
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 \
1661-
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"/>\
1662-
<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
1663-
</svg>');
1711+
content: var(--clipboard-image);
16641712
width: 19px;
16651713
height: 18px;
16661714
}
16671715
#copy-path:hover::before {
16681716
filter: var(--copy-path-img-hover-filter);
16691717
}
16701718
#copy-path.clicked::before {
1671-
/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
1672-
content: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" xmlns="http://www.w3.org/2000/svg" \
1673-
fill="black" height="18px">\
1674-
<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
1675-
</g></svg>');
1719+
content: var(--checkmark-image);
16761720
}
16771721

16781722
@keyframes rotating {

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

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

17711771
// This section handles the copy button that appears next to the path breadcrumbs
1772+
// and the copy buttons on the code examples.
17721773
(function() {
1773-
let reset_button_timeout = null;
1774+
// Common functions to copy buttons.
1775+
function copyContentToClipboard(content) {
1776+
const el = document.createElement("textarea");
1777+
el.value = content;
1778+
el.setAttribute("readonly", "");
1779+
// To not make it appear on the screen.
1780+
el.style.position = "absolute";
1781+
el.style.left = "-9999px";
1782+
1783+
document.body.appendChild(el);
1784+
el.select();
1785+
document.execCommand("copy");
1786+
document.body.removeChild(el);
1787+
}
1788+
1789+
function copyButtonAnimation(button) {
1790+
button.classList.add("clicked");
1791+
1792+
if (button.reset_button_timeout !== undefined) {
1793+
window.clearTimeout(button.reset_button_timeout);
1794+
}
1795+
1796+
button.reset_button_timeout = window.setTimeout(() => {
1797+
button.reset_button_timeout = undefined;
1798+
button.classList.remove("clicked");
1799+
}, 1000);
1800+
}
17741801

1802+
// Copy button that appears next to the path breadcrumbs.
17751803
const but = document.getElementById("copy-path");
17761804
if (!but) {
17771805
return;
@@ -1786,29 +1814,54 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
17861814
}
17871815
});
17881816

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

1803-
if (reset_button_timeout !== null) {
1804-
window.clearTimeout(reset_button_timeout);
1821+
// Copy buttons on code examples.
1822+
function copyCode(codeElem) {
1823+
if (!codeElem) {
1824+
// Should never happen, but the world is a dark and dangerous place.
1825+
return;
18051826
}
1827+
let text = "";
1828+
onEachLazy(codeElem.childNodes, elem => {
1829+
if (elem.nodeType === Node.TEXT_NODE) {
1830+
text += elem.textContent;
1831+
}
1832+
});
1833+
copyContentToClipboard(text);
1834+
}
18061835

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

1812-
reset_button_timeout = window.setTimeout(reset_button, 1000);
1813-
};
1864+
onEachLazy(document.querySelectorAll(".docblock .example-wrap"), elem => {
1865+
elem.addEventListener("mouseover", addCopyButton);
1866+
});
18141867
}());

0 commit comments

Comments
 (0)