Refactor ToC generation and active link handling

The SVG HTML was moved into a constant for cleaner code.
This commit is contained in:
Emil Gulamov 2024-04-02 21:59:53 +04:00
parent 65037e910f
commit 1fc42828b3

View file

@ -170,50 +170,20 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
}, },
}); });
// Function to create and add an item to the ToC list const SVG_HTML_STRING = '<svg class="w-0 h-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
function addToC(heading: HTMLElement, tocList: HTMLElement | null) {
const li = document.createElement("li"); function setActiveLinkById(id: string | null) {
li.className = "toc-level-" + heading.tagName.toLowerCase(); const listItems = document.querySelectorAll("#toc li");
li.innerHTML =` listItems.forEach(item => item.classList.remove("selected"));
<svg class="w-0 h-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15">
<path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"> if (!id) return;
</path>
</svg>
<a href="#${heading.id}">${heading.textContent}</a>`;
tocList?.appendChild(li);
return li;
}
function setActiveLink(id: string) {
document.querySelectorAll("#toc li").forEach((li) => {
li.classList.remove("selected");
});
const activeLink = document.querySelector(`#toc a[href="#${id}"]`); const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
if (activeLink) {
const li: HTMLElement | null = activeLink.parentElement;
li?.classList.add("selected");
}
}
function generateToC(article: HTMLElement | null, tocList: HTMLElement | null) { if (!activeLink) return;
// Observe headings and add them to the ToC
let headings: NodeListOf<HTMLElement> | [] = article const listItem = activeLink.parentElement;
? article.querySelectorAll("h1, h2, h3, h4, h5, h6") listItem?.classList.add("selected");
: [];
headings.forEach((heading: Element, i: number) => {
if (heading instanceof HTMLElement) {
addToC(heading, tocList);
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: "top 20%",
end: () =>
`bottom top+=${i === headings.length - 1 ? 0 : headings[i + 1].getBoundingClientRect().height}`,
onEnter: () => setActiveLink(heading.id),
},
});
}
});
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
@ -221,6 +191,37 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
const article: HTMLElement | null = document.querySelector("article"); const article: HTMLElement | null = document.querySelector("article");
// The ToC container <ul> element // The ToC container <ul> element
const tocList: HTMLElement | null = document.querySelector("#toc ul"); const tocList: HTMLElement | null = document.querySelector("#toc ul");
generateToC(article, tocList);
const headings: NodeListOf<HTMLElement> | [] = article ? article.querySelectorAll("h1, h2, h3, h4, h5, h6") : [];
headings.forEach((heading, i) => {
if (heading instanceof HTMLElement) {
const listItem = document.createElement("li");
listItem.className = "toc-level-" + heading.tagName.toLowerCase();
const tempDiv = document.createElement("div");
tempDiv.innerHTML = SVG_HTML_STRING;
const svg = tempDiv.firstChild;
listItem.appendChild(svg as Node);
const link = document.createElement("a");
link.href = "#" + heading.id;
link.textContent = heading.textContent;
listItem.appendChild(link);
tocList?.appendChild(listItem);
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: "top 20%",
end: () => `bottom top+=${i === headings.length - 1 ? 0 : (headings[i + 1] as HTMLElement).getBoundingClientRect().height}`,
onEnter: () => setActiveLinkById(heading.id),
onLeaveBack: () => setActiveLinkById((headings[i - 1] as HTMLElement)?.id),
},
});
}
});
}); });
</script> </script>