First commit

This commit is contained in:
2026-09-30 01:49:35 +02:00
commit 6edd03aace
109 changed files with 27020 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
<script setup lang="ts">
import type { TocEntry } from '~/utils/markdown'
const props = defineProps<{ entries: TocEntry[] }>()
const { t } = useI18n()
const navEl = ref<HTMLElement | null>(null)
const activeId = ref<string>('')
let observer: IntersectionObserver | null = null
/* The article in view scrolls inside its own page of the book, so the TOC
follows — and moves — that page's scroller rather than the window. */
function ownScroller(): HTMLElement | null {
return navEl.value?.closest<HTMLElement>('.pane-scroll') ?? null
}
function observe() {
observer?.disconnect()
if (!import.meta.client || !props.entries.length) return
const headings = props.entries
.map((e) => document.getElementById(e.id))
.filter((el): el is HTMLElement => !!el)
if (!headings.length) return
observer = new IntersectionObserver(
(records) => {
const visible = records
.filter((r) => r.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
if (visible.length) activeId.value = visible[0]!.target.id
},
{ root: ownScroller(), rootMargin: '-48px 0px -65% 0px', threshold: 0 }
)
headings.forEach((h) => observer!.observe(h))
}
onMounted(() => nextTick(observe))
watch(() => props.entries, () => nextTick(observe))
onBeforeUnmount(() => observer?.disconnect())
function goTo(id: string) {
activeId.value = id
const heading = document.getElementById(id)
if (!heading) return
const scroller = ownScroller()
if (!scroller) {
heading.scrollIntoView({ behavior: 'smooth', block: 'start' })
return
}
/* scroll the page itself — scrollIntoView would also shove the stack of
open pages sideways */
const top =
heading.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop - 18
scroller.scrollTo({ top: Math.max(0, top), behavior: 'smooth' })
}
</script>
<template>
<nav v-if="entries.length" ref="navEl" class="toc" :aria-label="t('toc.aria')">
<div class="toc-title">{{ t('toc.title') }}</div>
<ol>
<li v-for="entry in entries" :key="entry.id" :class="`level-${entry.level}`">
<a
href="#"
:class="{ active: activeId === entry.id }"
@click.prevent="goTo(entry.id)"
>
{{ entry.text }}
</a>
</li>
</ol>
</nav>
</template>