: the image, then its caption in ,
// opening with the slide it came from. Promote that to a real , with the
// caption in a footer and the slide number in the plate's top edge.
function plate(p) {
const imgs = p.querySelectorAll("img");
if (!imgs.length) return;
const fig = document.createElement("figure");
fig.className = "plate";
const art = document.createElement("div");
art.className = "plate-art";
// a figure can be a set of views: slide 13 and slide 19 each show two
imgs.forEach((i) => art.appendChild(i));
fig.appendChild(art);
const em = p.querySelector("em");
if (em) {
const cap = document.createElement("figcaption");
const parts = em.innerHTML.match(/^\s*(Slides?[^—]*?)\s*—\s*([\s\S]+)$/);
cap.innerHTML = parts ? parts[2] : em.innerHTML;
if (parts) {
const no = document.createElement("span");
no.className = "plate-no";
no.textContent = parts[1].replace(/\s+/g, " ");
fig.insertBefore(no, art);
}
fig.appendChild(cap);
}
p.replaceWith(fig);
}
let marks = [];
function buildOutline() {
outline.textContent = "";
marks = [...doc.querySelectorAll("h2, h3, h4")].map((h) => {
const a = document.createElement("a");
a.className = h.tagName.toLowerCase();
a.textContent = h.textContent.replace(/\s+/g, " ").trim();
a.href = `#${h.id}`;
a.onclick = (e) => {
e.preventDefault();
h.scrollIntoView({ behavior: "smooth" });
history.replaceState(null, "", docUrl(current, h.id));
};
outline.appendChild(a);
return { h, a };
});
spy();
}
// Highlight the heading the reader is currently under, and show progress.
function spy() {
let cur = marks[0];
for (const m of marks) {
if (m.h.getBoundingClientRect().top <= 90) cur = m; else break;
}
for (const m of marks) m.a.classList.toggle("here", m === cur);
const max = document.documentElement.scrollHeight - innerHeight;
bar.style.width = `${max > 0 ? Math.min(100, (scrollY / max) * 100) : 0}%`;
}
let queued = false;
addEventListener("scroll", () => {
if (queued || !marks.length) return;
queued = true;
requestAnimationFrame(() => { queued = false; spy(); });
}, { passive: true });
async function load(path, section = "") {
err.textContent = "";
current = path;
docsNav.querySelectorAll("a").forEach((a) =>
a.toggleAttribute("aria-current", a.dataset.path === path));
try {
const r = await fetch(`${path}?t=${Date.now()}`);
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
render(await r.text(), path.replace(/[^/]*$/, ""));
} catch (e) {
doc.textContent = "";
outline.textContent = "";
err.textContent = `Could not load ${path}\n${e.message}\n\n` +
`Serve the repository root: python3 -m http.server`;
return;
}
history.replaceState(null, "", docUrl(path, section));
// The content is injected after page load, so the browser cannot scroll to a
// fragment on its own.
const anchor = section && document.getElementById(section);
if (anchor) anchor.scrollIntoView(); else scrollTo(0, 0);
spy();
}
for (const [label, path] of DOCS) {
const a = document.createElement("a");
a.textContent = label;
a.href = docUrl(path);
a.dataset.path = path;
a.onclick = (e) => { e.preventDefault(); load(path); };
docsNav.appendChild(a);
}
let startDoc = new URLSearchParams(location.search).get("doc");
let startSection = decodeURIComponent(location.hash.slice(1));
// Links written before the document moved into the query string put the
// document path in the hash. Keep those working.
if (!startDoc && isDoc(startSection)) {
startDoc = startSection;
startSection = "";
}
load(isDoc(startDoc) ? startDoc : DOCS[0][1], startSection);