66 lines
1.7 KiB
Plaintext
66 lines
1.7 KiB
Plaintext
---
|
|
// Path: src/components/Post/FloatingTOC.astro
|
|
import styles from "./FloatingTOC.module.scss";
|
|
---
|
|
|
|
<nav class={styles.toc} aria-label="Table of Contents">
|
|
<ul id="toc-list"></ul>
|
|
</nav>
|
|
|
|
<script>
|
|
function generateTOC() {
|
|
const content = document.getElementById("lesson-container");
|
|
const list = document.getElementById("toc-list");
|
|
|
|
if (list) list.innerHTML = "";
|
|
if (!content || !list) return;
|
|
|
|
const targets = content.querySelectorAll("[data-toc]");
|
|
|
|
targets.forEach((el, index) => {
|
|
if (!el.id) el.id = `toc-item-${index}`;
|
|
|
|
const label = el.getAttribute("data-toc");
|
|
const level = el.getAttribute("data-toc-level") || "1";
|
|
|
|
const li = document.createElement("li");
|
|
const a = document.createElement("a");
|
|
|
|
// Add data attribute for CSS to target specific lengths
|
|
li.setAttribute("data-level", level);
|
|
|
|
a.href = `#${el.id}`;
|
|
// No Icon span, just the text which we will hide via CSS
|
|
a.innerHTML = `<span class="toc-text">${label}</span>`;
|
|
|
|
a.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
});
|
|
|
|
li.appendChild(a);
|
|
list.appendChild(li);
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
// Clear all active classes
|
|
document.querySelectorAll("#toc-list a").forEach((link) => link.classList.remove("active"));
|
|
// Set current active
|
|
a.classList.add("active");
|
|
}
|
|
});
|
|
},
|
|
// Tweak: Use a 1px line exactly in the vertical center of the screen
|
|
{ rootMargin: "-50% 0px -50% 0px", threshold: 0 }
|
|
);
|
|
|
|
observer.observe(el);
|
|
});
|
|
}
|
|
|
|
generateTOC();
|
|
document.addEventListener("astro:after-swap", generateTOC);
|
|
</script>
|