- two static quotes above the name line in every footer - Toc component renders h1-h3 from Astro's render() headings as a sticky right column on >=88rem screens (hidden below); scroll-position highlighting via a small passive listener; shown only when a page has 2+ headings, so listings and short notes stay clean Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.2 KiB
Plaintext
48 lines
1.2 KiB
Plaintext
---
|
|
interface Props {
|
|
headings: { depth: number; slug: string; text: string }[];
|
|
}
|
|
const { headings } = Astro.props;
|
|
const shown = headings.filter((h) => h.depth <= 3);
|
|
const min = Math.min(...shown.map((h) => h.depth));
|
|
---
|
|
|
|
<nav class="toc">
|
|
<p class="toc-title">On this page</p>
|
|
<ul>
|
|
{
|
|
shown.map((h) => (
|
|
<li class={`toc-d${h.depth - min}`}>
|
|
<a href={'#' + h.slug}>{h.text}</a>
|
|
</li>
|
|
))
|
|
}
|
|
</ul>
|
|
</nav>
|
|
|
|
<script>
|
|
// highlight the section currently in view
|
|
const toc = document.querySelector('.toc');
|
|
if (toc) {
|
|
const links = [...toc.querySelectorAll('a[href^="#"]')] as HTMLAnchorElement[];
|
|
const heads = links
|
|
.map((a) => ({
|
|
a,
|
|
el: document.getElementById(decodeURIComponent(a.hash.slice(1))),
|
|
}))
|
|
.filter((x) => x.el);
|
|
const update = () => {
|
|
let cur = heads[0];
|
|
for (const h of heads) {
|
|
if (h.el!.getBoundingClientRect().top <= 110) cur = h;
|
|
else break;
|
|
}
|
|
heads.forEach((h) => h.a.classList.toggle('toc-active', h === cur));
|
|
};
|
|
if (heads.length) {
|
|
document.addEventListener('scroll', update, { passive: true });
|
|
update();
|
|
}
|
|
}
|
|
</script>
|