"use client"; import { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; import { cn } from "@/lib/utils"; type Heading = { id: string; text: string; level: number; }; function getHeadings(): Heading[] { const article = document.querySelector("article"); if (!article) return []; const elements = article.querySelectorAll("h2[id], h3[id]"); return Array.from(elements).map((el) => ({ id: el.id, text: el.textContent?.replace(/#$/, "").trim() ?? "", level: el.tagName === "H3" ? 3 : 2, })); } export function TableOfContents() { const pathname = usePathname(); const [headings, setHeadings] = useState([]); const [activeId, setActiveId] = useState(""); useEffect(() => { const timer = setTimeout(() => setHeadings(getHeadings()), 100); return () => clearTimeout(timer); }, [pathname]); useEffect(() => { if (headings.length === 0) return; const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { setActiveId(entry.target.id); } } }, { rootMargin: "0px 0px -75% 0px", threshold: 0.1 }, ); for (const h of headings) { const el = document.getElementById(h.id); if (el) observer.observe(el); } return () => observer.disconnect(); }, [headings]); if (headings.length === 0) return null; return ( ); }