]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/html/static/js/main.js
New upstream version 1.69.0+dfsg1
[rustc.git] / src / librustdoc / html / static / js / main.js
CommitLineData
0731742a 1// Local js definitions:
17df50a5
XL
2/* global addClass, getSettingValue, hasClass, searchState */
3/* global onEach, onEachLazy, removeClass */
1a4d82fc 4
04454e1e
FG
5"use strict";
6
a2a8927a
XL
7// Get a value from the rustdoc-vars div, which is used to convey data from
8// Rust to the JS. If there is no such element, return null.
9function getVar(name) {
04454e1e 10 const el = document.getElementById("rustdoc-vars");
a2a8927a
XL
11 if (el) {
12 return el.attributes["data-" + name].value;
13 } else {
14 return null;
5869c6ff 15 }
a2a8927a
XL
16}
17
18// Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
19// for a resource under the root-path, with the resource-suffix.
20function resourcePath(basename, extension) {
21 return getVar("root-path") + basename + getVar("resource-suffix") + extension;
22}
23
04454e1e
FG
24function hideMain() {
25 addClass(document.getElementById(MAIN_ID), "hidden");
26}
27
28function showMain() {
29 removeClass(document.getElementById(MAIN_ID), "hidden");
30}
31
064997fb
FG
32function elemIsInParent(elem, parent) {
33 while (elem && elem !== document.body) {
34 if (elem === parent) {
35 return true;
36 }
37 elem = elem.parentElement;
38 }
39 return false;
40}
41
42function blurHandler(event, parentElem, hideCallback) {
43 if (!elemIsInParent(document.activeElement, parentElem) &&
44 !elemIsInParent(event.relatedTarget, parentElem)
45 ) {
46 hideCallback();
47 }
48}
49
487cf647
FG
50window.rootPath = getVar("root-path");
51window.currentCrate = getVar("current-crate");
fc512014 52
923072b8
FG
53function setMobileTopbar() {
54 // FIXME: It would be nicer to generate this text content directly in HTML,
55 // but with the current code it's hard to get the right information in the right place.
2b03887a 56 const mobileLocationTitle = document.querySelector(".mobile-topbar h2");
923072b8
FG
57 const locationTitle = document.querySelector(".sidebar h2.location");
58 if (mobileLocationTitle && locationTitle) {
59 mobileLocationTitle.innerHTML = locationTitle.innerHTML;
60 }
61}
62
fc512014
XL
63// Gets the human-readable string for the virtual-key code of the
64// given KeyboardEvent, ev.
65//
66// This function is meant as a polyfill for KeyboardEvent#key,
67// since it is not supported in IE 11 or Chrome for Android. We also test for
68// KeyboardEvent#keyCode because the handleShortcut handler is
69// also registered for the keydown event, because Blink doesn't fire
70// keypress on hitting the Escape key.
71//
72// So I guess you could say things are getting pretty interoperable.
73function getVirtualKey(ev) {
923072b8 74 if ("key" in ev && typeof ev.key !== "undefined") {
fc512014
XL
75 return ev.key;
76 }
77
04454e1e 78 const c = ev.charCode || ev.keyCode;
923072b8 79 if (c === 27) {
fc512014
XL
80 return "Escape";
81 }
82 return String.fromCharCode(c);
83}
84
04454e1e
FG
85const MAIN_ID = "main-content";
86const SETTINGS_BUTTON_ID = "settings-menu";
87const ALTERNATIVE_DISPLAY_ID = "alternative-display";
88const NOT_DISPLAYED_ID = "not-displayed";
064997fb 89const HELP_BUTTON_ID = "help-button";
e1599b0c 90
04454e1e
FG
91function getSettingsButton() {
92 return document.getElementById(SETTINGS_BUTTON_ID);
93}
94
064997fb
FG
95function getHelpButton() {
96 return document.getElementById(HELP_BUTTON_ID);
97}
98
5869c6ff
XL
99// Returns the current URL without any query parameter or hash.
100function getNakedUrl() {
101 return window.location.href.split("?")[0].split("#")[0];
102}
103
04454e1e
FG
104/**
105 * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode`
106 * doesn't have a parent node.
107 *
108 * @param {HTMLElement} newNode
109 * @param {HTMLElement} referenceNode
110 */
111function insertAfter(newNode, referenceNode) {
112 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
113}
114
115/**
116 * This function creates a new `<section>` with the given `id` and `classes` if it doesn't already
117 * exist.
118 *
119 * More information about this in `switchDisplayedElement` documentation.
120 *
121 * @param {string} id
122 * @param {string} classes
123 */
124function getOrCreateSection(id, classes) {
125 let el = document.getElementById(id);
126
127 if (!el) {
128 el = document.createElement("section");
129 el.id = id;
130 el.className = classes;
131 insertAfter(el, document.getElementById(MAIN_ID));
132 }
133 return el;
134}
135
136/**
137 * Returns the `<section>` element which contains the displayed element.
138 *
139 * @return {HTMLElement}
140 */
141function getAlternativeDisplayElem() {
142 return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");
143}
144
145/**
146 * Returns the `<section>` element which contains the not-displayed elements.
147 *
148 * @return {HTMLElement}
149 */
150function getNotDisplayedElem() {
151 return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");
152}
153
154/**
155 * To nicely switch between displayed "extra" elements (such as search results or settings menu)
156 * and to alternate between the displayed and not displayed elements, we hold them in two different
157 * `<section>` elements. They work in pair: one holds the hidden elements while the other
158 * contains the displayed element (there can be only one at the same time!). So basically, we switch
159 * elements between the two `<section>` elements.
160 *
161 * @param {HTMLElement} elemToDisplay
162 */
163function switchDisplayedElement(elemToDisplay) {
164 const el = getAlternativeDisplayElem();
165
166 if (el.children.length > 0) {
167 getNotDisplayedElem().appendChild(el.firstElementChild);
168 }
169 if (elemToDisplay === null) {
170 addClass(el, "hidden");
171 showMain();
172 return;
173 }
174 el.appendChild(elemToDisplay);
175 hideMain();
176 removeClass(el, "hidden");
177}
178
179function browserSupportsHistoryApi() {
180 return window.history && typeof window.history.pushState === "function";
181}
182
487cf647 183function loadCss(cssUrl) {
04454e1e 184 const link = document.createElement("link");
487cf647 185 link.href = cssUrl;
04454e1e
FG
186 link.rel = "stylesheet";
187 document.getElementsByTagName("head")[0].appendChild(link);
188}
189
1a4d82fc 190(function() {
2b03887a
FG
191 const isHelpPage = window.location.pathname.endsWith("/help.html");
192
04454e1e
FG
193 function loadScript(url) {
194 const script = document.createElement("script");
195 script.src = url;
196 document.head.append(script);
197 }
198
199 getSettingsButton().onclick = event => {
2b03887a
FG
200 if (event.ctrlKey || event.altKey || event.metaKey) {
201 return;
202 }
487cf647 203 window.hideAllModals(false);
04454e1e
FG
204 addClass(getSettingsButton(), "rotate");
205 event.preventDefault();
206 // Sending request for the CSS and the JS files at the same time so it will
207 // hopefully be loaded when the JS will generate the settings content.
487cf647
FG
208 loadCss(getVar("static-root-path") + getVar("settings-css"));
209 loadScript(getVar("static-root-path") + getVar("settings-js"));
04454e1e 210 };
1a4d82fc 211
cdc7bbd5 212 window.searchState = {
17df50a5
XL
213 loadingText: "Loading search results...",
214 input: document.getElementsByClassName("search-input")[0],
04454e1e
FG
215 outputElement: () => {
216 let el = document.getElementById("search");
217 if (!el) {
218 el = document.createElement("section");
219 el.id = "search";
220 getNotDisplayedElem().appendChild(el);
221 }
222 return el;
17df50a5
XL
223 },
224 title: document.title,
225 titleBeforeSearch: document.title,
226 timeout: null,
227 // On the search screen, so you remain on the last tab you opened.
228 //
229 // 0 for "In Names"
230 // 1 for "In Parameters"
231 // 2 for "In Return Types"
232 currentTab: 0,
233 // tab and back preserves the element that was focused.
234 focusedByTab: [null, null, null],
04454e1e 235 clearInputTimeout: () => {
17df50a5
XL
236 if (searchState.timeout !== null) {
237 clearTimeout(searchState.timeout);
238 searchState.timeout = null;
239 }
240 },
04454e1e 241 isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID,
17df50a5 242 // Sets the focus on the search bar at the top of the page
04454e1e 243 focus: () => {
17df50a5
XL
244 searchState.input.focus();
245 },
246 // Removes the focus from the search bar.
04454e1e 247 defocus: () => {
17df50a5
XL
248 searchState.input.blur();
249 },
04454e1e
FG
250 showResults: search => {
251 if (search === null || typeof search === "undefined") {
17df50a5 252 search = searchState.outputElement();
cdc7bbd5 253 }
04454e1e 254 switchDisplayedElement(search);
17df50a5 255 searchState.mouseMovedAfterSearch = false;
cdc7bbd5 256 document.title = searchState.title;
17df50a5 257 },
04454e1e
FG
258 hideResults: () => {
259 switchDisplayedElement(null);
17df50a5
XL
260 document.title = searchState.titleBeforeSearch;
261 // We also remove the query parameter from the URL.
04454e1e 262 if (browserSupportsHistoryApi()) {
5099ac24 263 history.replaceState(null, window.currentCrate + " - Rust",
17df50a5
XL
264 getNakedUrl() + window.location.hash);
265 }
266 },
04454e1e
FG
267 getQueryStringParams: () => {
268 const params = {};
17df50a5 269 window.location.search.substring(1).split("&").
04454e1e
FG
270 map(s => {
271 const pair = s.split("=");
17df50a5
XL
272 params[decodeURIComponent(pair[0])] =
273 typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
274 });
275 return params;
276 },
04454e1e
FG
277 setup: () => {
278 const search_input = searchState.input;
17df50a5
XL
279 if (!searchState.input) {
280 return;
281 }
04454e1e 282 let searchLoaded = false;
17df50a5
XL
283 function loadSearch() {
284 if (!searchLoaded) {
285 searchLoaded = true;
487cf647 286 loadScript(getVar("static-root-path") + getVar("search-js"));
923072b8 287 loadScript(resourcePath("search-index", ".js"));
17df50a5
XL
288 }
289 }
c34b1796 290
04454e1e 291 search_input.addEventListener("focus", () => {
5099ac24 292 search_input.origPlaceholder = search_input.placeholder;
17df50a5
XL
293 search_input.placeholder = "Type your search here.";
294 loadSearch();
295 });
f035d41b 296
04454e1e 297 if (search_input.value !== "") {
3c0e092e
XL
298 loadSearch();
299 }
17df50a5 300
04454e1e 301 const params = searchState.getQueryStringParams();
17df50a5 302 if (params.search !== undefined) {
487cf647 303 searchState.setLoadingSearch();
17df50a5
XL
304 loadSearch();
305 }
306 },
487cf647
FG
307 setLoadingSearch: () => {
308 const search = searchState.outputElement();
309 search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>";
310 searchState.showResults(search);
311 },
cdc7bbd5 312 };
f9f354fc 313
83c7162d 314 function getPageId() {
f035d41b 315 if (window.location.hash) {
04454e1e 316 const tmp = window.location.hash.replace(/^#/, "");
f035d41b
XL
317 if (tmp.length > 0) {
318 return tmp;
319 }
83c7162d
XL
320 }
321 return null;
322 }
323
04454e1e
FG
324 const toggleAllDocsId = "toggle-all-docs";
325 let savedHash = "";
0731742a 326
60c5eb7d 327 function handleHashes(ev) {
04454e1e 328 if (ev !== null && searchState.isDisplayed() && ev.newURL) {
60c5eb7d
XL
329 // This block occurs when clicking on an element in the navbar while
330 // in a search.
04454e1e
FG
331 switchDisplayedElement(null);
332 const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
333 if (browserSupportsHistoryApi()) {
5869c6ff 334 // `window.location.search`` contains all the query parameters, not just `search`.
5099ac24 335 history.replaceState(null, "",
5869c6ff 336 getNakedUrl() + window.location.search + "#" + hash);
dc9dc135 337 }
04454e1e 338 const elem = document.getElementById(hash);
60c5eb7d
XL
339 if (elem) {
340 elem.scrollIntoView();
dc9dc135 341 }
60c5eb7d
XL
342 }
343 // This part is used in case an element is not visible.
344 if (savedHash !== window.location.hash) {
345 savedHash = window.location.hash;
346 if (savedHash.length === 0) {
1a4d82fc
JJ
347 return;
348 }
17df50a5 349 expandSection(savedHash.slice(1)); // we remove the '#'
60c5eb7d
XL
350 }
351 }
352
353 function onHashChange(ev) {
354 // If we're in mobile mode, we should hide the sidebar in any case.
f2b60f7d 355 hideSidebar();
60c5eb7d 356 handleHashes(ev);
1a4d82fc 357 }
b7449926 358
cdc7bbd5
XL
359 function openParentDetails(elem) {
360 while (elem) {
361 if (elem.tagName === "DETAILS") {
362 elem.open = true;
363 }
364 elem = elem.parentNode;
365 }
366 }
367
b7449926 368 function expandSection(id) {
17df50a5 369 openParentDetails(document.getElementById(id));
b7449926
XL
370 }
371
e1599b0c 372 function handleEscape(ev) {
04454e1e 373 searchState.clearInputTimeout();
064997fb
FG
374 switchDisplayedElement(null);
375 if (browserSupportsHistoryApi()) {
376 history.replaceState(null, window.currentCrate + " - Rust",
377 getNakedUrl() + window.location.hash);
0531ce1d 378 }
064997fb 379 ev.preventDefault();
cdc7bbd5 380 searchState.defocus();
9ffffee4 381 window.hideAllModals(true); // true = reset focus for tooltips
0531ce1d 382 }
1a4d82fc 383
0531ce1d 384 function handleShortcut(ev) {
92a42be0 385 // Don't interfere with browser shortcuts
064997fb 386 const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
17df50a5 387 if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
92a42be0 388 return;
0531ce1d 389 }
92a42be0 390
064997fb 391 if (document.activeElement.tagName === "INPUT" &&
9c376795
FG
392 document.activeElement.type !== "checkbox" &&
393 document.activeElement.type !== "radio") {
0531ce1d
XL
394 switch (getVirtualKey(ev)) {
395 case "Escape":
e1599b0c 396 handleEscape(ev);
0531ce1d 397 break;
9346a6ac 398 }
0531ce1d
XL
399 } else {
400 switch (getVirtualKey(ev)) {
401 case "Escape":
e1599b0c 402 handleEscape(ev);
0531ce1d 403 break;
c1a9b12d 404
0531ce1d
XL
405 case "s":
406 case "S":
0531ce1d 407 ev.preventDefault();
cdc7bbd5 408 searchState.focus();
0531ce1d 409 break;
c1a9b12d 410
0531ce1d 411 case "+":
2b03887a
FG
412 ev.preventDefault();
413 expandAllDocs();
414 break;
0531ce1d
XL
415 case "-":
416 ev.preventDefault();
2b03887a 417 collapseAllDocs();
0531ce1d 418 break;
a7813a04 419
0531ce1d 420 case "?":
064997fb 421 showHelp();
0531ce1d 422 break;
29967ef6 423
29967ef6 424 default:
923072b8 425 break;
1a4d82fc 426 }
83c7162d 427 }
cdc7bbd5 428 }
83c7162d 429
cdc7bbd5
XL
430 document.addEventListener("keypress", handleShortcut);
431 document.addEventListener("keydown", handleShortcut);
1a4d82fc 432
923072b8
FG
433 function addSidebarItems() {
434 if (!window.SIDEBAR_ITEMS) {
435 return;
17df50a5 436 }
923072b8 437 const sidebar = document.getElementsByClassName("sidebar-elems")[0];
17df50a5 438
5099ac24
FG
439 /**
440 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
441 *
442 * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"
443 * @param {string} id - The HTML id of the corresponding section on the module page.
444 * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",
445 * "Modules", or "Macros".
446 */
447 function block(shortty, id, longty) {
923072b8 448 const filtered = window.SIDEBAR_ITEMS[shortty];
0731742a
XL
449 if (!filtered) {
450 return;
451 }
c34b1796 452
04454e1e 453 const h3 = document.createElement("h3");
5099ac24 454 h3.innerHTML = `<a href="index.html#${id}">${longty}</a>`;
04454e1e 455 const ul = document.createElement("ul");
2b03887a 456 ul.className = "block " + shortty;
c34b1796 457
9ffffee4 458 for (const name of filtered) {
04454e1e 459 let path;
0731742a
XL
460 if (shortty === "mod") {
461 path = name + "/index.html";
c34b1796 462 } else {
0731742a 463 path = shortty + "." + name + ".html";
c34b1796 464 }
923072b8 465 const current_page = document.location.href.split("/").pop();
04454e1e 466 const link = document.createElement("a");
923072b8 467 link.href = path;
2b03887a
FG
468 if (path === current_page) {
469 link.className = "current";
470 }
7cac9316 471 link.textContent = name;
04454e1e 472 const li = document.createElement("li");
7cac9316
XL
473 li.appendChild(link);
474 ul.appendChild(li);
c34b1796 475 }
2b03887a
FG
476 sidebar.appendChild(h3);
477 sidebar.appendChild(ul);
c34b1796
AL
478 }
479
17df50a5 480 if (sidebar) {
064997fb
FG
481 block("primitive", "primitives", "Primitive Types");
482 block("mod", "modules", "Modules");
483 block("macro", "macros", "Macros");
484 block("struct", "structs", "Structs");
485 block("enum", "enums", "Enums");
486 block("union", "unions", "Unions");
487 block("constant", "constants", "Constants");
488 block("static", "static", "Statics");
489 block("trait", "traits", "Traits");
490 block("fn", "functions", "Functions");
491 block("type", "types", "Type Definitions");
492 block("foreigntype", "foreign-types", "Foreign Types");
493 block("keyword", "keywords", "Keywords");
494 block("traitalias", "trait-aliases", "Trait Aliases");
17df50a5 495 }
923072b8 496 }
c34b1796 497
04454e1e
FG
498 window.register_implementors = imp => {
499 const implementors = document.getElementById("implementors-list");
500 const synthetic_implementors = document.getElementById("synthetic-implementors-list");
501 const inlined_types = new Set();
0531ce1d 502
f2b60f7d
FG
503 const TEXT_IDX = 0;
504 const SYNTHETIC_IDX = 1;
505 const TYPES_IDX = 2;
506
dfeec247
XL
507 if (synthetic_implementors) {
508 // This `inlined_types` variable is used to avoid having the same implementation
509 // showing up twice. For example "String" in the "Sync" doc page.
510 //
511 // By the way, this is only used by and useful for traits implemented automatically
512 // (like "Send" and "Sync").
04454e1e
FG
513 onEachLazy(synthetic_implementors.getElementsByClassName("impl"), el => {
514 const aliases = el.getAttribute("data-aliases");
dfeec247
XL
515 if (!aliases) {
516 return;
517 }
04454e1e 518 aliases.split(",").forEach(alias => {
dfeec247
XL
519 inlined_types.add(alias);
520 });
521 });
522 }
523
04454e1e 524 let currentNbImpls = implementors.getElementsByClassName("impl").length;
9c376795 525 const traitName = document.querySelector(".main-heading h1 > .trait").textContent;
04454e1e
FG
526 const baseIdName = "impl-" + traitName + "-";
527 const libs = Object.getOwnPropertyNames(imp);
528 // We don't want to include impls from this JS file, when the HTML already has them.
529 // The current crate should always be ignored. Other crates that should also be
530 // ignored are included in the attribute `data-ignore-extern-crates`.
f2b60f7d
FG
531 const script = document
532 .querySelector("script[data-ignore-extern-crates]");
533 const ignoreExternCrates = script ? script.getAttribute("data-ignore-extern-crates") : "";
04454e1e
FG
534 for (const lib of libs) {
535 if (lib === window.currentCrate || ignoreExternCrates.indexOf(lib) !== -1) {
536 continue;
537 }
538 const structs = imp[lib];
0531ce1d
XL
539
540 struct_loop:
04454e1e 541 for (const struct of structs) {
f2b60f7d 542 const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;
0531ce1d 543
f2b60f7d
FG
544 // The types list is only used for synthetic impls.
545 // If this changes, `main.js` and `write_shared.rs` both need changed.
546 if (struct[SYNTHETIC_IDX]) {
547 for (const struct_type of struct[TYPES_IDX]) {
04454e1e 548 if (inlined_types.has(struct_type)) {
0531ce1d
XL
549 continue struct_loop;
550 }
04454e1e 551 inlined_types.add(struct_type);
0531ce1d
XL
552 }
553 }
554
04454e1e 555 const code = document.createElement("h3");
f2b60f7d 556 code.innerHTML = struct[TEXT_IDX];
17df50a5 557 addClass(code, "code-header");
7cac9316 558
04454e1e
FG
559 onEachLazy(code.getElementsByTagName("a"), elem => {
560 const href = elem.getAttribute("href");
5869c6ff 561
9c376795 562 if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) {
5869c6ff 563 elem.setAttribute("href", window.rootPath + href);
1a4d82fc 564 }
5869c6ff
XL
565 });
566
04454e1e
FG
567 const currentId = baseIdName + currentNbImpls;
568 const anchor = document.createElement("a");
136023e0
XL
569 anchor.href = "#" + currentId;
570 addClass(anchor, "anchor");
571
04454e1e 572 const display = document.createElement("div");
136023e0 573 display.id = currentId;
b7449926 574 addClass(display, "impl");
136023e0
XL
575 display.appendChild(anchor);
576 display.appendChild(code);
b7449926 577 list.appendChild(display);
136023e0 578 currentNbImpls += 1;
1a4d82fc
JJ
579 }
580 }
581 };
582 if (window.pending_implementors) {
583 window.register_implementors(window.pending_implementors);
584 }
585
923072b8
FG
586 function addSidebarCrates() {
587 if (!window.ALL_CRATES) {
588 return;
589 }
590 const sidebarElems = document.getElementsByClassName("sidebar-elems")[0];
591 if (!sidebarElems) {
592 return;
593 }
594 // Draw a convenient sidebar of known crates if we have a listing
2b03887a
FG
595 const h3 = document.createElement("h3");
596 h3.innerHTML = "Crates";
923072b8 597 const ul = document.createElement("ul");
2b03887a 598 ul.className = "block crate";
923072b8
FG
599
600 for (const crate of window.ALL_CRATES) {
923072b8
FG
601 const link = document.createElement("a");
602 link.href = window.rootPath + crate + "/index.html";
2b03887a
FG
603 if (window.rootPath !== "./" && crate === window.currentCrate) {
604 link.className = "current";
605 }
923072b8
FG
606 link.textContent = crate;
607
608 const li = document.createElement("li");
609 li.appendChild(link);
610 ul.appendChild(li);
611 }
2b03887a
FG
612 sidebarElems.appendChild(h3);
613 sidebarElems.appendChild(ul);
923072b8
FG
614 }
615
2b03887a
FG
616 function expandAllDocs() {
617 const innerToggle = document.getElementById(toggleAllDocsId);
618 removeClass(innerToggle, "will-expand");
9c376795 619 onEachLazy(document.getElementsByClassName("toggle"), e => {
487cf647 620 if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {
2b03887a
FG
621 e.open = true;
622 }
623 });
624 innerToggle.title = "collapse all docs";
625 innerToggle.children[0].innerText = "\u2212"; // "\u2212" is "−" minus sign
626 }
923072b8 627
2b03887a
FG
628 function collapseAllDocs() {
629 const innerToggle = document.getElementById(toggleAllDocsId);
630 addClass(innerToggle, "will-expand");
9c376795 631 onEachLazy(document.getElementsByClassName("toggle"), e => {
2b03887a
FG
632 if (e.parentNode.id !== "implementations-list" ||
633 (!hasClass(e, "implementors-toggle") &&
634 !hasClass(e, "type-contents-toggle"))
635 ) {
636 e.open = false;
637 }
638 });
639 innerToggle.title = "expand all docs";
640 innerToggle.children[0].innerText = "+";
d9579d0f 641 }
1a4d82fc 642
17df50a5 643 function toggleAllDocs() {
04454e1e 644 const innerToggle = document.getElementById(toggleAllDocsId);
0731742a 645 if (!innerToggle) {
83c7162d
XL
646 return;
647 }
0731742a 648 if (hasClass(innerToggle, "will-expand")) {
2b03887a 649 expandAllDocs();
d9579d0f 650 } else {
2b03887a 651 collapseAllDocs();
0531ce1d
XL
652 }
653 }
654
f9f354fc 655 (function() {
04454e1e 656 const toggles = document.getElementById(toggleAllDocsId);
1b1a35ee
XL
657 if (toggles) {
658 toggles.onclick = toggleAllDocs;
659 }
660
04454e1e
FG
661 const hideMethodDocs = getSettingValue("auto-hide-method-docs") === "true";
662 const hideImplementations = getSettingValue("auto-hide-trait-implementations") === "true";
663 const hideLargeItemContents = getSettingValue("auto-hide-large-items") !== "false";
f9f354fc 664
136023e0 665 function setImplementorsTogglesOpen(id, open) {
04454e1e 666 const list = document.getElementById(id);
17df50a5 667 if (list !== null) {
04454e1e 668 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {
136023e0 669 e.open = open;
17df50a5 670 });
cdc7bbd5 671 }
60c5eb7d 672 }
17df50a5 673
136023e0
XL
674 if (hideImplementations) {
675 setImplementorsTogglesOpen("trait-implementations-list", false);
676 setImplementorsTogglesOpen("blanket-implementations-list", false);
7cac9316 677 }
cdc7bbd5 678
9c376795 679 onEachLazy(document.getElementsByClassName("toggle"), e => {
17df50a5
XL
680 if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {
681 e.open = true;
f9f354fc 682 }
17df50a5
XL
683 if (hideMethodDocs && hasClass(e, "method-toggle")) {
684 e.open = false;
532ac7d7 685 }
a1dfa0c6 686
17df50a5 687 });
f035d41b 688
04454e1e 689 const pageId = getPageId();
f035d41b
XL
690 if (pageId !== null) {
691 expandSection(pageId);
692 }
f9f354fc 693 }());
7cac9316 694
2b03887a
FG
695 window.rustdoc_add_line_numbers_to_examples = () => {
696 onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
697 const parent = x.parentNode;
698 const line_numbers = parent.querySelectorAll(".example-line-numbers");
699 if (line_numbers.length > 0) {
700 return;
701 }
702 const count = x.textContent.split("\n").length;
703 const elems = [];
704 for (let i = 0; i < count; ++i) {
705 elems.push(i + 1);
706 }
707 const node = document.createElement("pre");
708 addClass(node, "example-line-numbers");
709 node.innerHTML = elems.join("\n");
710 parent.insertBefore(node, x);
711 });
712 };
713
714 window.rustdoc_remove_line_numbers_from_examples = () => {
715 onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
716 const parent = x.parentNode;
717 const line_numbers = parent.querySelectorAll(".example-line-numbers");
718 for (const node of line_numbers) {
719 parent.removeChild(node);
720 }
721 });
722 };
723
487cf647
FG
724 if (getSettingValue("line-numbers") === "true") {
725 window.rustdoc_add_line_numbers_to_examples();
726 }
abe05a73 727
f2b60f7d
FG
728 let oldSidebarScrollPosition = null;
729
2b03887a
FG
730 // Scroll locking used both here and in source-script.js
731
732 window.rustdocMobileScrollLock = function() {
733 const mobile_topbar = document.querySelector(".mobile-topbar");
734 if (window.innerWidth <= window.RUSTDOC_MOBILE_BREAKPOINT) {
f2b60f7d
FG
735 // This is to keep the scroll position on mobile.
736 oldSidebarScrollPosition = window.scrollY;
737 document.body.style.width = `${document.body.offsetWidth}px`;
738 document.body.style.position = "fixed";
739 document.body.style.top = `-${oldSidebarScrollPosition}px`;
2b03887a
FG
740 if (mobile_topbar) {
741 mobile_topbar.style.top = `${oldSidebarScrollPosition}px`;
742 mobile_topbar.style.position = "relative";
743 }
f2b60f7d
FG
744 } else {
745 oldSidebarScrollPosition = null;
746 }
2b03887a 747 };
f2b60f7d 748
2b03887a
FG
749 window.rustdocMobileScrollUnlock = function() {
750 const mobile_topbar = document.querySelector(".mobile-topbar");
f2b60f7d
FG
751 if (oldSidebarScrollPosition !== null) {
752 // This is to keep the scroll position on mobile.
753 document.body.style.width = "";
754 document.body.style.position = "";
755 document.body.style.top = "";
2b03887a
FG
756 if (mobile_topbar) {
757 mobile_topbar.style.top = "";
758 mobile_topbar.style.position = "";
759 }
f2b60f7d
FG
760 // The scroll position is lost when resetting the style, hence why we store it in
761 // `oldSidebarScrollPosition`.
762 window.scrollTo(0, oldSidebarScrollPosition);
763 oldSidebarScrollPosition = null;
764 }
2b03887a
FG
765 };
766
767 function showSidebar() {
487cf647 768 window.hideAllModals(false);
2b03887a
FG
769 window.rustdocMobileScrollLock();
770 const sidebar = document.getElementsByClassName("sidebar")[0];
771 addClass(sidebar, "shown");
772 }
773
774 function hideSidebar() {
775 window.rustdocMobileScrollUnlock();
04454e1e 776 const sidebar = document.getElementsByClassName("sidebar")[0];
5099ac24
FG
777 removeClass(sidebar, "shown");
778 }
779
f2b60f7d 780 window.addEventListener("resize", () => {
2b03887a 781 if (window.innerWidth > window.RUSTDOC_MOBILE_BREAKPOINT &&
f2b60f7d
FG
782 oldSidebarScrollPosition !== null) {
783 // If the user opens the sidebar in "mobile" mode, and then grows the browser window,
784 // we need to switch away from mobile mode and make the main content area scrollable.
785 hideSidebar();
786 }
9ffffee4
FG
787 if (window.CURRENT_TOOLTIP_ELEMENT) {
788 // As a workaround to the behavior of `contains: layout` used in doc togglers,
789 // tooltip popovers are positioned using javascript.
487cf647
FG
790 //
791 // This means when the window is resized, we need to redo the layout.
9ffffee4
FG
792 const base = window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;
793 const force_visible = base.TOOLTIP_FORCE_VISIBLE;
794 hideTooltip(false);
487cf647 795 if (force_visible) {
9ffffee4
FG
796 showTooltip(base);
797 base.TOOLTIP_FORCE_VISIBLE = true;
487cf647
FG
798 }
799 }
f2b60f7d
FG
800 });
801
9c376795
FG
802 const mainElem = document.getElementById(MAIN_ID);
803 if (mainElem) {
804 mainElem.addEventListener("click", hideSidebar);
17df50a5 805 }
17df50a5 806
9c376795 807 onEachLazy(document.querySelectorAll("a[href^='#']"), el => {
17df50a5
XL
808 // For clicks on internal links (<A> tags with a hash property), we expand the section we're
809 // jumping to *before* jumping there. We can't do this in onHashChange, because it changes
810 // the height of the document so we wind up scrolled to the wrong place.
9c376795
FG
811 el.addEventListener("click", () => {
812 expandSection(el.hash.slice(1));
813 hideSidebar();
814 });
17df50a5
XL
815 });
816
9c376795 817 onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
04454e1e
FG
818 el.addEventListener("click", e => {
819 if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
3c0e092e
XL
820 e.preventDefault();
821 }
822 });
823 });
824
9ffffee4
FG
825 function showTooltip(e) {
826 const notable_ty = e.getAttribute("data-notable-ty");
827 if (!window.NOTABLE_TRAITS && notable_ty) {
487cf647
FG
828 const data = document.getElementById("notable-traits-data");
829 if (data) {
830 window.NOTABLE_TRAITS = JSON.parse(data.innerText);
831 } else {
9ffffee4 832 throw new Error("showTooltip() called with notable without any notable traits!");
487cf647
FG
833 }
834 }
9ffffee4 835 if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {
487cf647
FG
836 // Make this function idempotent.
837 return;
838 }
839 window.hideAllModals(false);
487cf647 840 const wrapper = document.createElement("div");
9ffffee4
FG
841 if (notable_ty) {
842 wrapper.innerHTML = "<div class=\"content\">" +
843 window.NOTABLE_TRAITS[notable_ty] + "</div>";
844 } else if (e.getAttribute("title") !== undefined) {
845 const titleContent = document.createElement("div");
846 titleContent.className = "content";
847 titleContent.appendChild(document.createTextNode(e.getAttribute("title")));
848 wrapper.appendChild(titleContent);
849 }
850 wrapper.className = "tooltip popover";
487cf647
FG
851 const focusCatcher = document.createElement("div");
852 focusCatcher.setAttribute("tabindex", "0");
9ffffee4 853 focusCatcher.onfocus = hideTooltip;
487cf647
FG
854 wrapper.appendChild(focusCatcher);
855 const pos = e.getBoundingClientRect();
856 // 5px overlap so that the mouse can easily travel from place to place
857 wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";
858 wrapper.style.left = 0;
859 wrapper.style.right = "auto";
860 wrapper.style.visibility = "hidden";
861 const body = document.getElementsByTagName("body")[0];
862 body.appendChild(wrapper);
863 const wrapperPos = wrapper.getBoundingClientRect();
864 // offset so that the arrow points at the center of the "(i)"
865 const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;
866 if (finalPos > 0) {
867 wrapper.style.left = finalPos + "px";
868 } else {
869 wrapper.style.setProperty(
870 "--popover-arrow-offset",
871 (wrapperPos.right - pos.right + 4) + "px"
872 );
873 }
874 wrapper.style.visibility = "";
9ffffee4
FG
875 window.CURRENT_TOOLTIP_ELEMENT = wrapper;
876 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
487cf647
FG
877 wrapper.onpointerleave = function(ev) {
878 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
879 if (ev.pointerType !== "mouse") {
880 return;
881 }
9ffffee4
FG
882 if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
883 hideTooltip(true);
487cf647
FG
884 }
885 };
886 }
887
9ffffee4
FG
888 function tooltipBlurHandler(event) {
889 if (window.CURRENT_TOOLTIP_ELEMENT &&
890 !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) &&
891 !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT) &&
892 !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE) &&
893 !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE)
487cf647
FG
894 ) {
895 // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
9ffffee4 896 // When I click the button on an already-opened tooltip popover, Safari
487cf647
FG
897 // hides the popover and then immediately shows it again, while everyone else hides it
898 // and it stays hidden.
899 //
900 // To work around this, make sure the click finishes being dispatched before
9ffffee4 901 // hiding the popover. Since `hideTooltip()` is idempotent, this makes Safari behave
487cf647 902 // consistently with the other two.
9ffffee4 903 setTimeout(() => hideTooltip(false), 0);
487cf647
FG
904 }
905 }
906
9ffffee4
FG
907 function hideTooltip(focus) {
908 if (window.CURRENT_TOOLTIP_ELEMENT) {
909 if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
487cf647 910 if (focus) {
9ffffee4 911 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();
487cf647 912 }
9ffffee4 913 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;
487cf647
FG
914 }
915 const body = document.getElementsByTagName("body")[0];
9ffffee4
FG
916 body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
917 window.CURRENT_TOOLTIP_ELEMENT = null;
487cf647
FG
918 }
919 }
920
9ffffee4 921 onEachLazy(document.getElementsByClassName("tooltip"), e => {
3dfed10e 922 e.onclick = function() {
9ffffee4
FG
923 this.TOOLTIP_FORCE_VISIBLE = this.TOOLTIP_FORCE_VISIBLE ? false : true;
924 if (window.CURRENT_TOOLTIP_ELEMENT && !this.TOOLTIP_FORCE_VISIBLE) {
925 hideTooltip(true);
487cf647 926 } else {
9ffffee4
FG
927 showTooltip(this);
928 window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex", "0");
929 window.CURRENT_TOOLTIP_ELEMENT.focus();
930 window.CURRENT_TOOLTIP_ELEMENT.onblur = tooltipBlurHandler;
487cf647
FG
931 }
932 return false;
933 };
934 e.onpointerenter = function(ev) {
935 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
936 if (ev.pointerType !== "mouse") {
937 return;
938 }
9ffffee4 939 showTooltip(this);
487cf647
FG
940 };
941 e.onpointerleave = function(ev) {
942 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
943 if (ev.pointerType !== "mouse") {
944 return;
945 }
9ffffee4
FG
946 if (!this.TOOLTIP_FORCE_VISIBLE &&
947 !elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) {
948 hideTooltip(true);
487cf647 949 }
3dfed10e
XL
950 };
951 });
952
04454e1e 953 const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];
5099ac24 954 if (sidebar_menu_toggle) {
04454e1e
FG
955 sidebar_menu_toggle.addEventListener("click", () => {
956 const sidebar = document.getElementsByClassName("sidebar")[0];
5099ac24 957 if (!hasClass(sidebar, "shown")) {
f2b60f7d 958 showSidebar();
ff7c6d11 959 } else {
f2b60f7d 960 hideSidebar();
ff7c6d11 961 }
5099ac24 962 });
ff7c6d11
XL
963 }
964
064997fb
FG
965 function helpBlurHandler(event) {
966 blurHandler(event, getHelpButton(), window.hidePopoverMenus);
967 }
17df50a5 968
064997fb 969 function buildHelpMenu() {
04454e1e 970 const book_info = document.createElement("span");
c295e0f8 971 book_info.className = "top";
29967ef6
XL
972 book_info.innerHTML = "You can find more information in \
973 <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
974
04454e1e 975 const shortcuts = [
e74abb32
XL
976 ["?", "Show this help dialog"],
977 ["S", "Focus the search field"],
978 ["↑", "Move up in search results"],
979 ["↓", "Move down in search results"],
17df50a5 980 ["← / →", "Switch result tab (when results focused)"],
e74abb32
XL
981 ["&#9166;", "Go to active search result"],
982 ["+", "Expand all sections"],
983 ["-", "Collapse all sections"],
04454e1e
FG
984 ].map(x => "<dt>" +
985 x[0].split(" ")
923072b8 986 .map((y, index) => ((index & 1) === 0 ? "<kbd>" + y + "</kbd>" : " " + y + " "))
04454e1e
FG
987 .join("") + "</dt><dd>" + x[1] + "</dd>").join("");
988 const div_shortcuts = document.createElement("div");
e74abb32
XL
989 addClass(div_shortcuts, "shortcuts");
990 div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";
991
04454e1e 992 const infos = [
e74abb32 993 "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
1b1a35ee
XL
994 restrict the search to a given item kind.",
995 "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
e74abb32
XL
996 <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
997 and <code>const</code>.",
dfeec247 998 "Search functions by type signature (e.g., <code>vec -&gt; usize</code> or \
064997fb 999 <code>-&gt; vec</code>)",
e74abb32
XL
1000 "Search multiple things at once by splitting your query with comma (e.g., \
1001 <code>str,u8</code> or <code>String,struct:Vec,test</code>)",
1002 "You can look for items with an exact name by putting double quotes around \
1003 your request: <code>\"string\"</code>",
1004 "Look for items inside another one by searching for a path: <code>vec::Vec</code>",
04454e1e
FG
1005 ].map(x => "<p>" + x + "</p>").join("");
1006 const div_infos = document.createElement("div");
e74abb32
XL
1007 addClass(div_infos, "infos");
1008 div_infos.innerHTML = "<h2>Search Tricks</h2>" + infos;
1009
04454e1e 1010 const rustdoc_version = document.createElement("span");
c295e0f8 1011 rustdoc_version.className = "bottom";
04454e1e 1012 const rustdoc_version_code = document.createElement("code");
a2a8927a 1013 rustdoc_version_code.innerText = "rustdoc " + getVar("rustdoc-version");
c295e0f8
XL
1014 rustdoc_version.appendChild(rustdoc_version_code);
1015
064997fb 1016 const container = document.createElement("div");
2b03887a
FG
1017 if (!isHelpPage) {
1018 container.className = "popover";
1019 }
1020 container.id = "help";
064997fb
FG
1021 container.style.display = "none";
1022
1023 const side_by_side = document.createElement("div");
1024 side_by_side.className = "side-by-side";
1025 side_by_side.appendChild(div_shortcuts);
1026 side_by_side.appendChild(div_infos);
1027
1028 container.appendChild(book_info);
1029 container.appendChild(side_by_side);
c295e0f8
XL
1030 container.appendChild(rustdoc_version);
1031
2b03887a
FG
1032 if (isHelpPage) {
1033 const help_section = document.createElement("section");
1034 help_section.appendChild(container);
1035 document.getElementById("main-content").appendChild(help_section);
1036 container.style.display = "block";
1037 } else {
1038 const help_button = getHelpButton();
1039 help_button.appendChild(container);
1040
1041 container.onblur = helpBlurHandler;
2b03887a
FG
1042 help_button.onblur = helpBlurHandler;
1043 help_button.children[0].onblur = helpBlurHandler;
1044 }
064997fb
FG
1045
1046 return container;
1047 }
1048
487cf647 1049 /**
9ffffee4 1050 * Hide popover menus, clickable tooltips, and the sidebar (if applicable).
487cf647 1051 *
9ffffee4 1052 * Pass "true" to reset focus for tooltip popovers.
487cf647
FG
1053 */
1054 window.hideAllModals = function(switchFocus) {
1055 hideSidebar();
1056 window.hidePopoverMenus();
9ffffee4 1057 hideTooltip(switchFocus);
487cf647
FG
1058 };
1059
064997fb
FG
1060 /**
1061 * Hide all the popover menus.
1062 */
1063 window.hidePopoverMenus = function() {
2b03887a 1064 onEachLazy(document.querySelectorAll(".search-form .popover"), elem => {
064997fb
FG
1065 elem.style.display = "none";
1066 });
17df50a5 1067 };
e74abb32 1068
064997fb
FG
1069 /**
1070 * Returns the help menu element (not the button).
1071 *
1072 * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1073 * built if it doesn't exist.
1074 *
1075 * @return {HTMLElement}
1076 */
1077 function getHelpMenu(buildNeeded) {
1078 let menu = getHelpButton().querySelector(".popover");
1079 if (!menu && buildNeeded) {
1080 menu = buildHelpMenu();
1081 }
1082 return menu;
1083 }
1084
1085 /**
1086 * Show the help popup menu.
1087 */
1088 function showHelp() {
9c376795
FG
1089 // Prevent `blur` events from being dispatched as a result of closing
1090 // other modals.
1091 getHelpButton().querySelector("a").focus();
064997fb
FG
1092 const menu = getHelpMenu(true);
1093 if (menu.style.display === "none") {
487cf647 1094 window.hideAllModals();
064997fb
FG
1095 menu.style.display = "";
1096 }
1097 }
1098
2b03887a
FG
1099 if (isHelpPage) {
1100 showHelp();
1101 document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1102 // Already on the help page, make help button a no-op.
1103 const target = event.target;
1104 if (target.tagName !== "A" ||
1105 target.parentElement.id !== HELP_BUTTON_ID ||
1106 event.ctrlKey ||
1107 event.altKey ||
1108 event.metaKey) {
1109 return;
1110 }
1111 event.preventDefault();
1112 });
1113 } else {
1114 document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1115 // By default, have help button open docs in a popover.
1116 // If user clicks with a moderator, though, use default browser behavior,
1117 // probably opening in a new window or tab.
1118 const target = event.target;
1119 if (target.tagName !== "A" ||
1120 target.parentElement.id !== HELP_BUTTON_ID ||
1121 event.ctrlKey ||
1122 event.altKey ||
1123 event.metaKey) {
1124 return;
1125 }
1126 event.preventDefault();
1127 const menu = getHelpMenu(true);
1128 const shouldShowHelp = menu.style.display === "none";
1129 if (shouldShowHelp) {
1130 showHelp();
1131 } else {
1132 window.hidePopoverMenus();
1133 }
1134 });
1135 }
064997fb 1136
923072b8
FG
1137 setMobileTopbar();
1138 addSidebarItems();
1139 addSidebarCrates();
cdc7bbd5 1140 onHashChange(null);
17df50a5 1141 window.addEventListener("hashchange", onHashChange);
cdc7bbd5
XL
1142 searchState.setup();
1143}());
6a06907d 1144
923072b8 1145(function() {
04454e1e 1146 let reset_button_timeout = null;
17df50a5 1147
9ffffee4
FG
1148 const but = document.getElementById("copy-path");
1149 if (!but) {
1150 return;
1151 }
1152 but.onclick = () => {
04454e1e
FG
1153 const parent = but.parentElement;
1154 const path = [];
17df50a5 1155
04454e1e
FG
1156 onEach(parent.childNodes, child => {
1157 if (child.tagName === "A") {
17df50a5
XL
1158 path.push(child.textContent);
1159 }
1160 });
6a06907d 1161
04454e1e
FG
1162 const el = document.createElement("textarea");
1163 el.value = path.join("::");
1164 el.setAttribute("readonly", "");
17df50a5 1165 // To not make it appear on the screen.
04454e1e
FG
1166 el.style.position = "absolute";
1167 el.style.left = "-9999px";
17df50a5
XL
1168
1169 document.body.appendChild(el);
1170 el.select();
04454e1e 1171 document.execCommand("copy");
17df50a5
XL
1172 document.body.removeChild(el);
1173
1174 // There is always one children, but multiple childNodes.
04454e1e 1175 but.children[0].style.display = "none";
17df50a5 1176
04454e1e 1177 let tmp;
17df50a5 1178 if (but.childNodes.length < 2) {
04454e1e 1179 tmp = document.createTextNode("✓");
17df50a5
XL
1180 but.appendChild(tmp);
1181 } else {
04454e1e 1182 onEachLazy(but.childNodes, e => {
17df50a5
XL
1183 if (e.nodeType === Node.TEXT_NODE) {
1184 tmp = e;
1185 return true;
1186 }
1187 });
04454e1e 1188 tmp.textContent = "✓";
cdc7bbd5 1189 }
6a06907d 1190
17df50a5
XL
1191 if (reset_button_timeout !== null) {
1192 window.clearTimeout(reset_button_timeout);
1193 }
6a06907d 1194
17df50a5 1195 function reset_button() {
04454e1e 1196 tmp.textContent = "";
17df50a5
XL
1197 reset_button_timeout = null;
1198 but.children[0].style.display = "";
1199 }
6a06907d 1200
17df50a5
XL
1201 reset_button_timeout = window.setTimeout(reset_button, 1000);
1202 };
1203}());