Browse Source

Reorganize ToC modules

The `<TableOfContents>` component is moved to the same subdirectory as the publication components.  Most of the component logic is moved into a class defined in `table_of_contents.svelte.ts`, which will serve as a companion module for `<TableOfContents>`.
master
buttercat1791 10 months ago
parent
commit
a408c54323
  1. 20
      src/lib/components/publications/TableOfContents.svelte
  2. 62
      src/lib/components/publications/table_of_contents.svelte.ts
  3. 6
      src/lib/data_structures/table_of_contents.ts

20
src/lib/components/publications/TableOfContents.svelte

@ -0,0 +1,20 @@
<script lang='ts'>
import { page } from '$app/state';
import type { PublicationTree } from '$lib/data_structures/publication_tree';
import type { TocEntry } from '$lib/components/publications/table_of_contents.svelte';
import { getContext } from 'svelte';
let { rootAddress } = $props<{ rootAddress: string }>();
let publicationTree = getContext('publicationTree') as PublicationTree;
let tocAddresses = $state<Map<string, TocEntry>>(new Map());
let tocRoot = $state<TocEntry | null>(null);
// Determine the event kind.
// If index, use the publication tree to build the table of contents.
// If single event, build the table of contents from the rendered HTML.
// Each rendered `<h>` should receive an entry in the ToC.
</script>
<!-- TODO: Add contents. -->

62
src/lib/components/TableOfContents.svelte → src/lib/components/publications/table_of_contents.svelte.ts

@ -1,35 +1,38 @@
<script lang='ts'> import { PublicationTree } from "../../data_structures/publication_tree.ts";
import { page } from '$app/state';
import type { PublicationTree } from '$lib/data_structures/publication_tree';
import type { TocEntry } from '$lib/data_structures/table_of_contents';
import { getContext } from 'svelte';
let { rootAddress } = $props<{ rootAddress: string }>(); export interface TocEntry {
title: string;
href: string;
expanded: boolean;
children: Array<TocEntry> | null;
}
let publicationTree = getContext('publicationTree') as PublicationTree; export class TableOfContents {
tocRoot = $state<TocEntry | null>(null);
tocAddresses = $state<Map<string, TocEntry>>(new Map());
let tocAddresses = $state<Map<string, TocEntry>>(new Map()); publicationTree: PublicationTree;
let tocRoot = $state<TocEntry | null>(null); pagePathname: string;
// Determine the event kind. constructor(publicationTree: PublicationTree, pagePathname: string) {
// If index, use the publication tree to build the table of contents. this.publicationTree = publicationTree;
// If single event, build the table of contents from the rendered HTML. this.pagePathname = pagePathname;
// Each rendered `<h>` should receive an entry in the ToC. }
function normalizeHashPath(title: string): string { #normalizeHashPath(title: string): string {
// TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page.
return title.toLowerCase().replace(/ /g, '-'); return title.toLowerCase().replace(/ /g, '-');
} }
async function insertIntoTocFromPublicationTree(address: string): Promise<void> { async insertIntoTocFromPublicationTree(address: string): Promise<void> {
const targetEvent = await publicationTree.getEvent(address); const targetEvent = await this.publicationTree.getEvent(address);
if (!targetEvent) { if (!targetEvent) {
console.warn(`[ToC] Event ${address} not found.`); console.warn(`[ToC] Event ${address} not found.`);
// TODO: Determine how to handle this case in the UI. // TODO: Determine how to handle this case in the UI.
return; return;
} }
const hierarchyEvents = await publicationTree.getHierarchy(address); const hierarchyEvents = await this.publicationTree.getHierarchy(address);
if (hierarchyEvents.length === 0) { if (hierarchyEvents.length === 0) {
// This means we are at root. // This means we are at root.
return; return;
@ -38,22 +41,22 @@
// Michael J 05 May 2025 - In this loop, we assume that the parent of the current event has // Michael J 05 May 2025 - In this loop, we assume that the parent of the current event has
// already been populated into the ToC. As long as the root is set when the component is // already been populated into the ToC. As long as the root is set when the component is
// initialized, this code will work fine. // initialized, this code will work fine.
let currentParentTocNode: TocEntry | null = tocRoot; let currentParentTocNode: TocEntry | null = this.tocRoot;
for (let i = 0; i < hierarchyEvents.length; i++) { for (let i = 0; i < hierarchyEvents.length; i++) {
const currentEvent = hierarchyEvents[i]; const currentEvent = hierarchyEvents[i];
const currentAddress = currentEvent.tagAddress(); const currentAddress = currentEvent.tagAddress();
if (tocAddresses.has(currentAddress)) { if (this.tocAddresses.has(currentAddress)) {
continue; continue;
} }
const currentEventChildAddresses = await publicationTree.getChildAddresses(currentAddress); const currentEventChildAddresses = await this.publicationTree.getChildAddresses(currentAddress);
for (let address of currentEventChildAddresses) { for (const address of currentEventChildAddresses) {
if (address === null) { if (address === null) {
continue; continue;
} }
const childEvent = await publicationTree.getEvent(address); const childEvent = await this.publicationTree.getEvent(address);
if (!childEvent) { if (!childEvent) {
console.warn(`[ToC] Event ${address} not found.`); console.warn(`[ToC] Event ${address} not found.`);
continue; continue;
@ -63,21 +66,20 @@
const childTocEntry: TocEntry = { const childTocEntry: TocEntry = {
title: childEvent.getMatchingTags('title')[0][1], title: childEvent.getMatchingTags('title')[0][1],
href: `${page.url.pathname}#${normalizeHashPath(childEvent.getMatchingTags('title')[0][1])}`, href: `${this.pagePathname}#${this.#normalizeHashPath(childEvent.getMatchingTags('title')[0][1])}`,
expanded: false, expanded: false,
children: null, children: null,
}; };
currentParentTocNode!.children.push(childTocEntry); currentParentTocNode!.children.push(childTocEntry);
tocAddresses.set(address, childTocEntry); this.tocAddresses.set(address, childTocEntry);
} }
currentParentTocNode = tocAddresses.get(currentAddress)!; currentParentTocNode = this.tocAddresses.get(currentAddress)!;
} }
} }
function buildTocFromDocument(parentElement: HTMLElement): void { buildTocFromDocument(parentElement: HTMLElement): void {
const entries: TocEntry[] = []; const entries: TocEntry[] = [];
const currentPathname = page.url.pathname;
parentElement parentElement
.querySelectorAll<HTMLHeadingElement>( .querySelectorAll<HTMLHeadingElement>(
@ -89,7 +91,7 @@
// Only create an entry if the header has an ID and a title. // Only create an entry if the header has an ID and a title.
if (id && title) { if (id && title) {
const href = `${currentPathname}#${id}`; const href = `${this.pagePathname}#${id}`;
const tocEntry: TocEntry = { const tocEntry: TocEntry = {
title, title,
@ -103,6 +105,4 @@
// TODO: Update ToC state within the component. // TODO: Update ToC state within the component.
} }
</script> }
<!-- TODO: Add contents. -->

6
src/lib/data_structures/table_of_contents.ts

@ -1,6 +0,0 @@
export interface TocEntry {
title: string;
href: string;
expanded: boolean;
children: Array<TocEntry> | null;
}
Loading…
Cancel
Save