blob: eeab591bcd807114e353f7be6e6089597d38fb01 [file] [log] [blame]
/* global addClass, hasClass, removeClass, onEachLazy, nonnull */
"use strict";
(function() {
// Number of lines shown when code viewer is not expanded.
// DEFAULT is the first example shown by default, while HIDDEN is
// the examples hidden beneath the "More examples" toggle.
//
// NOTE: these values MUST be synchronized with certain rules in rustdoc.css!
const DEFAULT_MAX_LINES = 5;
const HIDDEN_MAX_LINES = 10;
/**
* Scroll code block to the given code location
* @param {HTMLElement} elt
* @param {[number, number]} loc
* @param {boolean} isHidden
*/
function scrollToLoc(elt, loc, isHidden) {
/** @type {HTMLElement[]} */
// blocked on https://github.com/microsoft/TypeScript/issues/29037
// @ts-expect-error
const lines = elt.querySelectorAll("[data-nosnippet]");
let scrollOffset;
// If the block is greater than the size of the viewer,
// then scroll to the top of the block. Otherwise scroll
// to the middle of the block.
const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
if (loc[1] - loc[0] > maxLines) {
const line = Math.max(0, loc[0] - 1);
scrollOffset = lines[line].offsetTop;
} else {
const halfHeight = elt.offsetHeight / 2;
const offsetTop = lines[loc[0]].offsetTop;
const lastLine = lines[loc[1]];
const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
const offsetMid = (offsetTop + offsetBot) / 2;
scrollOffset = offsetMid - halfHeight;
}
nonnull(lines[0].parentElement).scrollTo(0, scrollOffset);
nonnull(elt.querySelector(".rust")).scrollTo(0, scrollOffset);
}
/**
* @param {HTMLElement} parent
* @param {string} className
* @param {string} content
*/
function createScrapeButton(parent, className, content) {
const button = document.createElement("button");
button.className = className;
button.title = content;
parent.insertBefore(button, parent.firstChild);
return button;
}
window.updateScrapedExample = (example, buttonHolder) => {
let locIndex = 0;
const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
/** @type {HTMLAnchorElement} */
const link = nonnull(example.querySelector(".scraped-example-title a"));
let expandButton = null;
if (!example.classList.contains("expanded")) {
expandButton = createScrapeButton(buttonHolder, "expand", "Show all");
}
const isHidden = nonnull(example.parentElement).classList.contains("more-scraped-examples");
// @ts-expect-error
const locs = example.locs;
if (locs.length > 1) {
const next = createScrapeButton(buttonHolder, "next", "Next usage");
const prev = createScrapeButton(buttonHolder, "prev", "Previous usage");
// Toggle through list of examples in a given file
/** @type {function(function(): void): void} */
const onChangeLoc = changeIndex => {
removeClass(highlights[locIndex], "focus");
changeIndex();
scrollToLoc(example, locs[locIndex][0], isHidden);
addClass(highlights[locIndex], "focus");
const url = locs[locIndex][1];
const title = locs[locIndex][2];
link.href = url;
link.innerHTML = title;
};
prev.addEventListener("click", () => {
onChangeLoc(() => {
locIndex = (locIndex - 1 + locs.length) % locs.length;
});
});
next.addEventListener("click", () => {
onChangeLoc(() => {
locIndex = (locIndex + 1) % locs.length;
});
});
}
if (expandButton) {
expandButton.addEventListener("click", () => {
if (hasClass(example, "expanded")) {
removeClass(example, "expanded");
removeClass(expandButton, "collapse");
expandButton.title = "Show all";
scrollToLoc(example, locs[0][0], isHidden);
} else {
addClass(example, "expanded");
addClass(expandButton, "collapse");
expandButton.title = "Show single example";
}
});
}
};
/**
* Initialize the `locs` field
*
* @param {HTMLElement & {locs?: rustdoc.ScrapedLoc[]}} example
* @param {boolean} isHidden
*/
function setupLoc(example, isHidden) {
const locs_str = nonnull(example.attributes.getNamedItem("data-locs")).textContent;
const locs =
JSON.parse(nonnull(nonnull(locs_str)));
example.locs = locs;
// Start with the first example in view
scrollToLoc(example, locs[0][0], isHidden);
}
const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
onEachLazy(firstExamples, el => setupLoc(el, false));
onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => {
// Allow users to click the left border of the <details> section to close it,
// since the section can be large and finding the [+] button is annoying.
onEachLazy(toggle.querySelectorAll(".toggle-line, .hide-more"), button => {
button.addEventListener("click", () => {
toggle.open = false;
});
});
const moreExamples = toggle.querySelectorAll(".scraped-example");
toggle.querySelector("summary").addEventListener("click", () => {
// Wrapping in setTimeout ensures the update happens after the elements are actually
// visible. This is necessary since setupLoc calls scrollToLoc which
// depends on offsetHeight, a property that requires an element to be visible to
// compute correctly.
setTimeout(() => {
onEachLazy(moreExamples, el => setupLoc(el, true));
});
}, {once: true});
});
})();