moved and rewrote table-of-contents.svelte to use stateful design

This commit is contained in:
2026-02-15 13:36:29 +01:00
parent 151e1206ab
commit 6042157caf
6 changed files with 37 additions and 47 deletions

View File

@@ -0,0 +1,143 @@
<script lang="ts">
import {onMount} from 'svelte';
interface TocEntry {
text: string;
link: string;
/**
* possible values: 0, 1, 2, 3
*/
indentLevel: number;
}
let tocEntries: TocEntry[] = $state([]);
onMount(() => {
let headers = getHeaders();
headers.forEach(header => {
tocEntries.push({
text: `${(header as HTMLElement).innerHTML}`,
link: `#${getHeaderId(header)}`,
indentLevel: getHeaderLevel(header),
});
});
});
let getHeaders = function(): NodeList {
return document.querySelectorAll("h2, h3, h4, h5");
}
// Generates an ID for a given header node. Does not override
// any existing ID.
let getHeaderId = function(header: Node): string {
var id = (header as HTMLElement).id;
if (!id) {
var text = (header as HTMLElement).innerText.toLowerCase();
text = text.replaceAll(/[^a-zA-Z0-9]/gi, "-");
id = text;
(header as HTMLElement).id = id;
}
return id;
}
function getHeaderLevel(header: Node): number {
switch ((header as HTMLElement).tagName) {
case "H2":
return 0;
case "H3":
return 1;
case "H4":
return 2;
case "H5":
return 3;
default:
return 0;
}
}
</script>
{#snippet tocEntryLine({ entry }: { entry: TocEntry })}
<a class="toc-level-{entry.indentLevel}" href="{entry.link}">{@html entry.text}</a>
{/snippet}
{#if tocEntries.length > 0}
<div class="toc-container">
<ul class="toc-list">
{#each tocEntries as entry}
{@render tocEntryLine({ entry })}
{/each}
</ul>
</div>
{/if}
<style>
:global {
body {
--padding-indent-base: 44px;
--padding-level-indent: 24px;
}
.toc-container {
width: 70%;
margin-left: auto;
margin-right: auto;
background-color: var(--color-background-highlight);
padding: 16px 0;
border: dashed var(--border-dash-size) var(--color-highlight);
backdrop-filter: blur(var(--blur-radius-background));
}
.toc-list {
padding: 0;
margin: 0;
width: 100%;
}
.toc-list a {
width: 100%;
font-size: 1.0rem;
line-height: 1.4rem;
padding-top: 2px;
padding-bottom: 2px;
padding-right: var(--padding-indent-base);
margin: 0;
display: inline-block;
text-decoration: none;
box-sizing: border-box;
transition: color var(--duration-animation) ease-out,
background-color var(--duration-animation) ease-out;
}
.toc-list a, .toc-list a:link, .toc-list a:visited {
color: var(--color-text);
}
.toc-list a:hover {
color: var(--color-text-dark);
background-color: var(--color-highlight);
}
.toc-level-0 {
font-weight: 800;
padding-left: var(--padding-indent-base);
}
.toc-level-1 {
padding-left: calc(var(--padding-indent-base) + var(--padding-level-indent));
}
.toc-level-2 {
padding-left: calc(var(--padding-indent-base) + calc(var(--padding-level-indent) * 2));
}
.toc-level-3 {
padding-left: calc(var(--padding-indent-base) + calc(var(--padding-level-indent) * 3));
}
.toc-level-1::before, .toc-level-2::before, .toc-level-3::before {
content: "└ ";
}
@media screen and (max-width: 550px) {
.toc-container {
width: 95%;
}
}
}
</style>