This is still not perfect, but it reduces the shift that happens when navigating between pages that have and don't have the table of contents. It also tries to reduce the theme flicker that happens by moving its loading to an earlier moment. Release Notes: - N/A
101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
let scrollTimeout;
|
|
|
|
const listenActive = () => {
|
|
const elems = document.querySelector(".pagetoc").children;
|
|
[...elems].forEach((el) => {
|
|
el.addEventListener("click", (event) => {
|
|
clearTimeout(scrollTimeout);
|
|
[...elems].forEach((el) => el.classList.remove("active"));
|
|
el.classList.add("active");
|
|
// Prevent scroll updates for a short period
|
|
scrollTimeout = setTimeout(() => {
|
|
scrollTimeout = null;
|
|
}, 100); // Adjust timing as needed
|
|
});
|
|
});
|
|
};
|
|
|
|
const getPagetoc = () =>
|
|
document.querySelector(".pagetoc") || autoCreatePagetoc();
|
|
|
|
const autoCreatePagetoc = () => {
|
|
const main = document.querySelector("#content > main");
|
|
const content = Object.assign(document.createElement("div"), {
|
|
className: "content-wrap",
|
|
});
|
|
content.append(...main.childNodes);
|
|
main.prepend(content);
|
|
main.insertAdjacentHTML(
|
|
"afterbegin",
|
|
'<div class="sidetoc"><nav class="pagetoc"></nav></div>',
|
|
);
|
|
return document.querySelector(".pagetoc");
|
|
};
|
|
const updateFunction = () => {
|
|
if (scrollTimeout) return; // Skip updates if within the cooldown period from a click
|
|
const headers = [...document.getElementsByClassName("header")];
|
|
const scrolledY = window.scrollY;
|
|
let lastHeader = null;
|
|
|
|
// Find the last header that is above the current scroll position
|
|
for (let i = headers.length - 1; i >= 0; i--) {
|
|
if (scrolledY >= headers[i].offsetTop) {
|
|
lastHeader = headers[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
const pagetocLinks = [...document.querySelector(".pagetoc").children];
|
|
pagetocLinks.forEach((link) => link.classList.remove("active"));
|
|
|
|
if (lastHeader) {
|
|
const activeLink = pagetocLinks.find(
|
|
(link) => lastHeader.href === link.href,
|
|
);
|
|
if (activeLink) activeLink.classList.add("active");
|
|
}
|
|
};
|
|
|
|
window.addEventListener("load", () => {
|
|
const pagetoc = getPagetoc();
|
|
const headers = [...document.getElementsByClassName("header")];
|
|
|
|
const nonH1Headers = headers.filter(
|
|
(header) => !header.parentElement.tagName.toLowerCase().startsWith("h1"),
|
|
);
|
|
const sidetoc = document.querySelector(".sidetoc");
|
|
const tocContainer = document.querySelector(".toc-container");
|
|
|
|
if (nonH1Headers.length === 0) {
|
|
if (sidetoc) {
|
|
sidetoc.style.display = "none";
|
|
}
|
|
if (tocContainer) {
|
|
tocContainer.classList.add("no-toc");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (tocContainer) {
|
|
tocContainer.classList.add("has-toc");
|
|
}
|
|
|
|
const tocTitle = Object.assign(document.createElement("p"), {
|
|
className: "toc-title",
|
|
textContent: "On This Page",
|
|
});
|
|
pagetoc.appendChild(tocTitle);
|
|
|
|
headers.forEach((header) => {
|
|
const link = Object.assign(document.createElement("a"), {
|
|
textContent: header.text,
|
|
href: header.href,
|
|
className: `pagetoc-${header.parentElement.tagName}`,
|
|
});
|
|
pagetoc.appendChild(link);
|
|
});
|
|
updateFunction();
|
|
listenActive();
|
|
window.addEventListener("scroll", updateFunction);
|
|
});
|