From 830108ac9086e9b20240b2b50a028bf4bcb3f0c2 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 14 May 2025 23:06:20 -0500 Subject: [PATCH 01/74] Opens #118#137 --- src/lib/components/Publication.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index 3ec008d..b80415b 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -167,7 +167,7 @@ {#if showTocButton && !showToc} - + Show Table of Contents {/if} {#if showTocButton && !showToc} From 5be8bedd4238aca916ea86712aac5d0d624e9916 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 14 May 2025 23:27:30 -0500 Subject: [PATCH 03/74] Remove unused Publication props --- src/lib/components/Publication.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index f7f4df5..7c7f150 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -17,10 +17,8 @@ import PublicationSection from "./PublicationSection.svelte"; import type { PublicationTree } from "$lib/data_structures/publication_tree"; - let { rootAddress, publicationType, indexEvent } = $props<{ + let { rootAddress } = $props<{ rootAddress: string, - publicationType: string, - indexEvent: NDKEvent }>(); const publicationTree = getContext('publicationTree') as PublicationTree; From 2c045dd2c006382134f3db61176da6736b6ee5dc Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 14 May 2025 23:29:51 -0500 Subject: [PATCH 04/74] Init ToC interface --- src/lib/data_structures/table_of_contents.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/lib/data_structures/table_of_contents.ts diff --git a/src/lib/data_structures/table_of_contents.ts b/src/lib/data_structures/table_of_contents.ts new file mode 100644 index 0000000..8bf447f --- /dev/null +++ b/src/lib/data_structures/table_of_contents.ts @@ -0,0 +1,6 @@ +export interface TocEntry { + title: string; + href: string; + expanded: boolean; + children: Array | null; +} From 899ee4661575dabca397a381ce449259f57752ed Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 14 May 2025 23:30:05 -0500 Subject: [PATCH 05/74] Init TableOfContents Svelte component --- src/lib/components/TableOfContents.svelte | 16 +++++++++++++++ src/lib/components/Toc.svelte | 24 ----------------------- 2 files changed, 16 insertions(+), 24 deletions(-) create mode 100644 src/lib/components/TableOfContents.svelte delete mode 100644 src/lib/components/Toc.svelte diff --git a/src/lib/components/TableOfContents.svelte b/src/lib/components/TableOfContents.svelte new file mode 100644 index 0000000..9e606fb --- /dev/null +++ b/src/lib/components/TableOfContents.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/Toc.svelte b/src/lib/components/Toc.svelte deleted file mode 100644 index 9d433b5..0000000 --- a/src/lib/components/Toc.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
-

Table of contents

- -
- - From d9c9c5b1ba5e20905041e9a2362374ee2bb3a7ad Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 15 May 2025 09:13:36 -0500 Subject: [PATCH 06/74] Update Deno lockfile --- deno.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deno.lock b/deno.lock index c97022c..96b2728 100644 --- a/deno.lock +++ b/deno.lock @@ -2902,6 +2902,8 @@ "npm:flowbite-svelte@0", "npm:flowbite@2", "npm:he@1.2", + "npm:highlight.js@^11.11.1", + "npm:node-emoji@^2.2.0", "npm:nostr-tools@2.10", "npm:playwright@^1.50.1", "npm:postcss-load-config@6", From 650aa280d45997ca082f3c5234935ccb24ee50f8 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 15 May 2025 09:14:00 -0500 Subject: [PATCH 07/74] Add comments with implementation plan --- src/lib/components/TableOfContents.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/components/TableOfContents.svelte b/src/lib/components/TableOfContents.svelte index 9e606fb..e26cdb9 100644 --- a/src/lib/components/TableOfContents.svelte +++ b/src/lib/components/TableOfContents.svelte @@ -5,12 +5,14 @@ let { rootAddress } = $props<{ rootAddress: string }>(); + // 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 `` should receive an entry in the ToC. + let toc = $state([]); const publicationTree = getContext('publicationTree') as PublicationTree; - - // TODO: Build the table of contents. - // Base hrefs on d-tags for events within the publication. From 78e4d23f718fe53abb601f555c82cc8514bbcf10 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Fri, 16 May 2025 09:07:24 -0500 Subject: [PATCH 08/74] Add a function to extract ToC entries from HTML --- src/lib/components/TableOfContents.svelte | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib/components/TableOfContents.svelte b/src/lib/components/TableOfContents.svelte index e26cdb9..71bcdd0 100644 --- a/src/lib/components/TableOfContents.svelte +++ b/src/lib/components/TableOfContents.svelte @@ -1,4 +1,5 @@ From 42cb538be5b7e906e9df7481532c769b6af8cd6e Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 26 May 2025 08:27:41 -0500 Subject: [PATCH 10/74] Move publication components into their own sub-directory --- deno.lock | 10 ++++++++++ .../components/{ => publications}/Publication.svelte | 0 .../{ => publications}/PublicationFeed.svelte | 0 .../{ => publications}/PublicationHeader.svelte | 2 +- .../{ => publications}/PublicationSection.svelte | 0 src/routes/+page.svelte | 2 +- src/routes/publication/+page.svelte | 2 +- 7 files changed, 13 insertions(+), 3 deletions(-) rename src/lib/components/{ => publications}/Publication.svelte (100%) rename src/lib/components/{ => publications}/PublicationFeed.svelte (100%) rename src/lib/components/{ => publications}/PublicationHeader.svelte (97%) rename src/lib/components/{ => publications}/PublicationSection.svelte (100%) diff --git a/deno.lock b/deno.lock index 9206145..6604928 100644 --- a/deno.lock +++ b/deno.lock @@ -3168,6 +3168,13 @@ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==" } }, + "redirects": { + "https://esm.sh/bech32": "https://esm.sh/bech32@2.0.0" + }, + "remote": { + "https://esm.sh/bech32@2.0.0": "1b943d1583f3708812c3cfccc8236cf92d8f9b191881984e26e0b9decaed9a16", + "https://esm.sh/bech32@2.0.0/denonext/bech32.mjs": "80859514ec6ba04858364eba363d5ac73ac8d97086754e0baf14fa31d4f1b63a" + }, "workspace": { "dependencies": [ "npm:@nostr-dev-kit/ndk-cache-dexie@2.5", @@ -3201,8 +3208,10 @@ "npm:@types/d3@^7.4.3", "npm:@types/he@1.2", "npm:@types/node@22", + "npm:@types/qrcode@^1.5.5", "npm:asciidoctor@3.0", "npm:autoprefixer@10", + "npm:bech32@2", "npm:d3@^7.9.0", "npm:eslint-plugin-svelte@2", "npm:flowbite-svelte-icons@2.1", @@ -3217,6 +3226,7 @@ "npm:postcss@8", "npm:prettier-plugin-svelte@3", "npm:prettier@3", + "npm:qrcode@^1.5.4", "npm:svelte-check@4", "npm:svelte@5", "npm:tailwind-merge@^3.3.0", diff --git a/src/lib/components/Publication.svelte b/src/lib/components/publications/Publication.svelte similarity index 100% rename from src/lib/components/Publication.svelte rename to src/lib/components/publications/Publication.svelte diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte similarity index 100% rename from src/lib/components/PublicationFeed.svelte rename to src/lib/components/publications/PublicationFeed.svelte diff --git a/src/lib/components/PublicationHeader.svelte b/src/lib/components/publications/PublicationHeader.svelte similarity index 97% rename from src/lib/components/PublicationHeader.svelte rename to src/lib/components/publications/PublicationHeader.svelte index 32a674a..25dfdc4 100644 --- a/src/lib/components/PublicationHeader.svelte +++ b/src/lib/components/publications/PublicationHeader.svelte @@ -2,7 +2,7 @@ import { ndkInstance } from '$lib/ndk'; import { naddrEncode } from '$lib/utils'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; - import { standardRelays } from '../consts'; + import { standardRelays } from '../../consts'; import { Card, Img } from "flowbite-svelte"; import CardActions from "$components/util/CardActions.svelte"; import { userBadge } from "$lib/snippets/UserSnippets.svelte"; diff --git a/src/lib/components/PublicationSection.svelte b/src/lib/components/publications/PublicationSection.svelte similarity index 100% rename from src/lib/components/PublicationSection.svelte rename to src/lib/components/publications/PublicationSection.svelte diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d3cee5c..dd9ba68 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3,7 +3,7 @@ import { Alert, Button, Dropdown, Radio, Input } from "flowbite-svelte"; import { ChevronDownOutline, HammerSolid } from "flowbite-svelte-icons"; import { inboxRelays, ndkSignedIn } from '$lib/ndk'; - import PublicationFeed from '$lib/components/PublicationFeed.svelte'; + import PublicationFeed from '$lib/components/publications/PublicationFeed.svelte'; import { feedType } from '$lib/stores'; $effect(() => { diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 48b156c..9316069 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -1,5 +1,5 @@ + + diff --git a/src/lib/components/TableOfContents.svelte b/src/lib/components/publications/table_of_contents.svelte.ts similarity index 52% rename from src/lib/components/TableOfContents.svelte rename to src/lib/components/publications/table_of_contents.svelte.ts index 78aa83a..548d4f5 100644 --- a/src/lib/components/TableOfContents.svelte +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,35 +1,38 @@ - - - +} diff --git a/src/lib/data_structures/table_of_contents.ts b/src/lib/data_structures/table_of_contents.ts deleted file mode 100644 index 8bf447f..0000000 --- a/src/lib/data_structures/table_of_contents.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface TocEntry { - title: string; - href: string; - expanded: boolean; - children: Array | null; -} From feab392a4478910af71dd0c6ec689ef37cd161ab Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 26 May 2025 09:07:26 -0500 Subject: [PATCH 12/74] Refactor `TableOfContents` class for least privilege --- .../publications/TableOfContents.svelte | 9 +--- .../publications/table_of_contents.svelte.ts | 51 ++++++++++++------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 50b383a..15f441e 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -1,15 +1,10 @@ - + + {#each toc as entry} + {entry.title} + {/each} + diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 3efddd1..4b6cc7a 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -132,4 +132,25 @@ export class TableOfContents { } }); } + + /** + * Iterates over all ToC entries in depth-first order. + */ + *[Symbol.iterator](): IterableIterator { + function* traverse(entry: TocEntry | null): IterableIterator { + if (!entry) { + return; + } + + yield entry; + + if (entry.children) { + for (const child of entry.children) { + yield* traverse(child); + } + } + } + + yield* traverse(this.#tocRoot); + } } From f9048c468ca5d8a5d12b4775992d60e30293925e Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 28 May 2025 08:46:52 -0500 Subject: [PATCH 15/74] Add doc comment on ToC constructor --- .../publications/table_of_contents.svelte.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 4b6cc7a..a16f72b 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -7,12 +7,22 @@ export interface TocEntry { children: Array | null; } +// TODO: Include depth in the `TocEntry` interface, and compute it when adding entries to the ToC. + export class TableOfContents { #tocRoot: TocEntry | null = null; #addresses = $state>(new Map()); #publicationTree: PublicationTree; #pagePathname: string; + /** + * Constructor for the `TableOfContents` class. The constructed ToC initially contains only the + * root entry. Additional entries must be inserted programmatically using class methods. + * + * The `TableOfContents` class should be instantiated as a page-scoped singleton so that + * `pagePathname` is correct wherever the instance is used. The singleton should be made + * made available to the entire component tree under that page. + */ constructor(rootAddress: string, publicationTree: PublicationTree, pagePathname: string) { // TODO: Build out the root entry correctly. this.#tocRoot = { From 4afbd04d5e57bb340a3a6151c4021504dd24338e Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 28 May 2025 08:47:28 -0500 Subject: [PATCH 16/74] Add a TODO --- src/lib/components/publications/TableOfContents.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index a49fd1c..891476a 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -7,6 +7,8 @@ let toc = getContext('toc') as TableOfContents; + // TODO: Check root address against ToC root address for correctness. + // 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. From 640a1263302e483bf4aca46810b7dcad0aeba7ae Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 28 May 2025 08:55:48 -0500 Subject: [PATCH 17/74] Compute depth of ToC entries when adding them --- .../publications/table_of_contents.svelte.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index a16f72b..77154aa 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,14 +1,14 @@ import { PublicationTree } from "../../data_structures/publication_tree.ts"; export interface TocEntry { + address: string; title: string; href: string; + depth: number; expanded: boolean; children: Array | null; } -// TODO: Include depth in the `TocEntry` interface, and compute it when adding entries to the ToC. - export class TableOfContents { #tocRoot: TocEntry | null = null; #addresses = $state>(new Map()); @@ -24,14 +24,6 @@ export class TableOfContents { * made available to the entire component tree under that page. */ constructor(rootAddress: string, publicationTree: PublicationTree, pagePathname: string) { - // TODO: Build out the root entry correctly. - this.#tocRoot = { - title: '', - href: '', - expanded: false, - children: null, - }; - this.#publicationTree = publicationTree; this.#pagePathname = pagePathname; @@ -88,8 +80,10 @@ export class TableOfContents { currentParentTocNode!.children ??= []; const childTocEntry: TocEntry = { + address, title: childEvent.getMatchingTags('title')[0][1], href: `${this.#pagePathname}#${this.#normalizeHashPath(childEvent.getMatchingTags('title')[0][1])}`, + depth: i + 1, expanded: false, children: null, }; @@ -130,8 +124,10 @@ export class TableOfContents { const href = `${this.#pagePathname}#${id}`; const tocEntry: TocEntry = { + address: parentEntry.address, title, href, + depth, expanded: false, children: null, }; From 2c3b9adf58178248f7c19736a7a9028301d6f537 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 31 May 2025 20:34:45 -0500 Subject: [PATCH 18/74] Set workspace tab size to 2 spaces --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e06c2f4..6953a21 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ }, "files.associations": { "*.svelte": "svelte" - } + }, + "editor.tabSize": 2 } \ No newline at end of file From d3ec3ad3e204768c96058be550b88339b013424d Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 31 May 2025 20:50:38 -0500 Subject: [PATCH 19/74] Add observability to node resolution --- src/lib/data_structures/publication_tree.ts | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index d616740..3184b29 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -1,7 +1,7 @@ -import type NDK from "@nostr-dev-kit/ndk"; -import type { NDKEvent } from "@nostr-dev-kit/ndk"; -import { Lazy } from "./lazy.ts"; -import { findIndexAsync as _findIndexAsync } from '../utils.ts'; +import type NDK from '@nostr-dev-kit/ndk'; +import type { NDKEvent } from '@nostr-dev-kit/ndk'; +import { Lazy } from './lazy.ts'; +import { SvelteSet } from "svelte/reactivity"; enum PublicationTreeNodeType { Branch, @@ -22,6 +22,14 @@ interface PublicationTreeNode { } export class PublicationTree implements AsyncIterable { + // TODO: Abstract this into a `SveltePublicationTree` wrapper class. + /** + * A reactive set of addresses of the events that have been resolved (loaded) into the tree. + * Svelte components can use this set in reactive code blocks to trigger updates when new nodes + * are added to the tree. + */ + resolvedAddresses: SvelteSet = new SvelteSet(); + /** * The root node of the tree. */ @@ -52,6 +60,8 @@ export class PublicationTree implements AsyncIterable { */ #ndk: NDK; + #onNodeResolvedCallbacks: Array<(address: string) => void> = []; + constructor(rootEvent: NDKEvent, ndk: NDK) { const rootAddress = rootEvent.tagAddress(); this.#root = { @@ -185,6 +195,16 @@ export class PublicationTree implements AsyncIterable { this.#cursor.tryMoveTo(address); } + /** + * Registers an observer function that is invoked whenever a new node is resolved. Nodes are + * added lazily. + * + * @param observer The observer function. + */ + onNodeResolved(observer: (address: string) => void) { + this.#onNodeResolvedCallbacks.push(observer); + } + // #region Iteration Cursor #cursor = new class { @@ -504,6 +524,7 @@ export class PublicationTree implements AsyncIterable { } this.#events.set(address, event); + this.resolvedAddresses.add(address); const childAddresses = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1]); @@ -519,6 +540,9 @@ export class PublicationTree implements AsyncIterable { this.addEventByAddress(address, event); } + // TODO: We may need to move this to `#addNode`, so the observer is notified more eagerly. + this.#onNodeResolvedCallbacks.forEach(observer => observer(address)); + return node; } From c6effb38397e188f9da37b03d0031a5b77fa8fbe Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 2 Jun 2025 08:50:15 -0500 Subject: [PATCH 20/74] Create Svelte-specific wrapper that proxies `PublicationTree` The wrapper keeps the core implementation framework-agnostic, but lets us build Svelte's reactivity into the wrapper. --- .../publications/Publication.svelte | 4 +- .../publications/PublicationSection.svelte | 5 +- .../svelte_publication_tree.svelte.ts | 56 +++++++++++++++++++ src/lib/data_structures/publication_tree.ts | 9 --- src/routes/publication/+page.svelte | 5 +- 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/lib/components/publications/svelte_publication_tree.svelte.ts diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index f7e026f..7147de4 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -15,13 +15,13 @@ } from "flowbite-svelte-icons"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import PublicationSection from "./PublicationSection.svelte"; - import type { PublicationTree } from "$lib/data_structures/publication_tree"; import Details from "$components/util/Details.svelte"; import { publicationColumnVisibility } from "$lib/stores"; import BlogHeader from "$components/cards/BlogHeader.svelte"; import Interactions from "$components/util/Interactions.svelte"; import TocToggle from "$components/util/TocToggle.svelte"; import { pharosInstance } from '$lib/parser'; + import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string; @@ -29,7 +29,7 @@ indexEvent: NDKEvent; }>(); - const publicationTree = getContext("publicationTree") as PublicationTree; + const publicationTree = getContext("publicationTree") as SveltePublicationTree; // #region Loading diff --git a/src/lib/components/publications/PublicationSection.svelte b/src/lib/components/publications/PublicationSection.svelte index 6c2586a..7b24fb3 100644 --- a/src/lib/components/publications/PublicationSection.svelte +++ b/src/lib/components/publications/PublicationSection.svelte @@ -6,7 +6,8 @@ import { getContext } from "svelte"; import type { Asciidoctor, Document } from "asciidoctor"; import { getMatchingTags } from '$lib/utils/nostrUtils'; - + import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; + let { address, rootAddress, @@ -19,7 +20,7 @@ ref: (ref: HTMLElement) => void, } = $props(); - const publicationTree: PublicationTree = getContext('publicationTree'); + const publicationTree: SveltePublicationTree = getContext('publicationTree'); const asciidoctor: Asciidoctor = getContext('asciidoctor'); let leafEvent: Promise = $derived.by(async () => diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts new file mode 100644 index 0000000..cd96118 --- /dev/null +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -0,0 +1,56 @@ +import { SvelteSet } from "svelte/reactivity"; +import { PublicationTree } from "../../data_structures/publication_tree.ts"; +import { NDKEvent } from "../../utils/nostrUtils.ts"; +import NDK from "@nostr-dev-kit/ndk"; + +export class SveltePublicationTree { + resolvedAddresses: SvelteSet = new SvelteSet(); + + #publicationTree: PublicationTree; + + constructor(rootEvent: NDKEvent, ndk: NDK) { + this.#publicationTree = new PublicationTree(rootEvent, ndk); + + this.#publicationTree.onNodeResolved(this.#handleNodeResolved); + } + + // #region Proxied Public Methods + + getEvent(address: string): Promise { + return this.#publicationTree.getEvent(address); + } + + getHierarchy(address: string): Promise { + return this.#publicationTree.getHierarchy(address); + } + + setBookmark(address: string) { + this.#publicationTree.setBookmark(address); + } + + // #endregion + + // #region Proxied Async Iterator Methods + + [Symbol.asyncIterator](): AsyncIterator { + return this; + } + + next(): Promise> { + return this.#publicationTree.next(); + } + + previous(): Promise> { + return this.#publicationTree.previous(); + } + + // #endregion + + // #region Private Methods + + #handleNodeResolved(address: string) { + this.resolvedAddresses.add(address); + } + + // #endregion +} \ No newline at end of file diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 3184b29..7f4c677 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -22,14 +22,6 @@ interface PublicationTreeNode { } export class PublicationTree implements AsyncIterable { - // TODO: Abstract this into a `SveltePublicationTree` wrapper class. - /** - * A reactive set of addresses of the events that have been resolved (loaded) into the tree. - * Svelte components can use this set in reactive code blocks to trigger updates when new nodes - * are added to the tree. - */ - resolvedAddresses: SvelteSet = new SvelteSet(); - /** * The root node of the tree. */ @@ -524,7 +516,6 @@ export class PublicationTree implements AsyncIterable { } this.#events.set(address, event); - this.resolvedAddresses.add(address); const childAddresses = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1]); diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 9316069..7defecf 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -3,13 +3,12 @@ import { TextPlaceholder } from "flowbite-svelte"; import type { PageProps } from "./$types"; import { onDestroy, setContext } from "svelte"; - import { PublicationTree } from "$lib/data_structures/publication_tree"; import Processor from "asciidoctor"; import ArticleNav from "$components/util/ArticleNav.svelte"; - + import { SveltePublicationTree } from "$lib/components/publications/svelte_publication_tree.svelte"; let { data }: PageProps = $props(); - const publicationTree = new PublicationTree(data.indexEvent, data.ndk); + const publicationTree = new SveltePublicationTree(data.indexEvent, data.ndk); setContext("publicationTree", publicationTree); setContext("asciidoctor", Processor()); From 34942c50466627811f60214090d5ff6cafafa878 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 2 Jun 2025 08:51:36 -0500 Subject: [PATCH 21/74] Make adding a node observable --- src/lib/data_structures/publication_tree.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 7f4c677..8c57729 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -52,6 +52,8 @@ export class PublicationTree implements AsyncIterable { */ #ndk: NDK; + #onNodeAddedCallbacks: Array<(address: string) => void> = []; + #onNodeResolvedCallbacks: Array<(address: string) => void> = []; constructor(rootEvent: NDKEvent, ndk: NDK) { @@ -187,6 +189,10 @@ export class PublicationTree implements AsyncIterable { this.#cursor.tryMoveTo(address); } + onNodeAdded(observer: (address: string) => void) { + this.#onNodeAddedCallbacks.push(observer); + } + /** * Registers an observer function that is invoked whenever a new node is resolved. Nodes are * added lazily. @@ -479,6 +485,8 @@ export class PublicationTree implements AsyncIterable { const lazyNode = new Lazy(() => this.#resolveNode(address, parentNode)); parentNode.children!.push(lazyNode); this.#nodes.set(address, lazyNode); + + this.#onNodeAddedCallbacks.forEach(observer => observer(address)); } /** From 1c47ea226ed55cfb07702b1fe8758d3f0fd1fbc0 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 2 Jun 2025 08:53:10 -0500 Subject: [PATCH 22/74] Proxy `getChildAddresses` --- .../components/publications/svelte_publication_tree.svelte.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts index cd96118..b956fd7 100644 --- a/src/lib/components/publications/svelte_publication_tree.svelte.ts +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -16,6 +16,10 @@ export class SveltePublicationTree { // #region Proxied Public Methods + getChildAddresses(address: string): Promise> { + return this.#publicationTree.getChildAddresses(address); + } + getEvent(address: string): Promise { return this.#publicationTree.getEvent(address); } From c30484b9bc5bab6add64b921ea5fe7131803fe80 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 2 Jun 2025 08:53:28 -0500 Subject: [PATCH 23/74] Use `SveltePublicationTree` in ToC class --- src/lib/components/publications/table_of_contents.svelte.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 77154aa..daf190a 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,4 +1,4 @@ -import { PublicationTree } from "../../data_structures/publication_tree.ts"; +import { SveltePublicationTree } from "./svelte_publication_tree.svelte.ts"; export interface TocEntry { address: string; @@ -12,7 +12,7 @@ export interface TocEntry { export class TableOfContents { #tocRoot: TocEntry | null = null; #addresses = $state>(new Map()); - #publicationTree: PublicationTree; + #publicationTree: SveltePublicationTree; #pagePathname: string; /** @@ -23,7 +23,7 @@ export class TableOfContents { * `pagePathname` is correct wherever the instance is used. The singleton should be made * made available to the entire component tree under that page. */ - constructor(rootAddress: string, publicationTree: PublicationTree, pagePathname: string) { + constructor(rootAddress: string, publicationTree: SveltePublicationTree, pagePathname: string) { this.#publicationTree = publicationTree; this.#pagePathname = pagePathname; From f438e68726fcee8405f87335a4d7119e8ec6bf10 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 2 Jun 2025 08:54:19 -0500 Subject: [PATCH 24/74] Remove unused import --- src/lib/data_structures/publication_tree.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 8c57729..2682947 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -1,7 +1,6 @@ import type NDK from '@nostr-dev-kit/ndk'; import type { NDKEvent } from '@nostr-dev-kit/ndk'; import { Lazy } from './lazy.ts'; -import { SvelteSet } from "svelte/reactivity"; enum PublicationTreeNodeType { Branch, From 1e450dd4b752d3c4ec1e9782e022715bc60eb3d2 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 3 Jun 2025 08:53:30 -0500 Subject: [PATCH 25/74] Improve abstraction and controllability of tree-walking methods --- src/lib/data_structures/publication_tree.ts | 118 ++++++++++++++------ 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 2682947..ea57f05 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -12,6 +12,16 @@ enum PublicationTreeNodeStatus { Error, } +export enum TreeTraversalMode { + Leaves, + All, +} + +enum TreeTraversalDirection { + Forward, + Backward, +} + interface PublicationTreeNode { type: PublicationTreeNodeType; status: PublicationTreeNodeStatus; @@ -344,54 +354,80 @@ export class PublicationTree implements AsyncIterable { return this; } - // TODO: Add `previous()` method. - - async next(): Promise> { + /** + * Return the next event in the tree for the given traversal mode. + * + * @param mode The traversal mode. Can be {@link TreeTraversalMode.Leaves} or + * {@link TreeTraversalMode.All}. + * @returns The next event in the tree, or null if the tree is empty. + */ + async next( + mode: TreeTraversalMode = TreeTraversalMode.Leaves + ): Promise> { if (!this.#cursor.target) { if (await this.#cursor.tryMoveTo(this.#bookmark)) { - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; + return this.#yieldEventAtCursor(false); } } - // Based on Raymond Chen's tree traversal algorithm example. - // https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 - do { - if (await this.#cursor.tryMoveToNextSibling()) { - while (await this.#cursor.tryMoveToFirstChild()) { - continue; - } - - if (this.#cursor.target!.status === PublicationTreeNodeStatus.Error) { - return { done: false, value: null }; - } - - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; - } - } while (this.#cursor.tryMoveToParent()); - - if (this.#cursor.target!.status === PublicationTreeNodeStatus.Error) { - return { done: false, value: null }; + if (mode === TreeTraversalMode.Leaves) { + return this.#walkLeaves(TreeTraversalDirection.Forward); } - // If we get to this point, we're at the root node (can't move up any more). - return { done: true, value: null }; + return this.#preorderWalkAll(TreeTraversalDirection.Forward); } - async previous(): Promise> { + /** + * Return the previous event in the tree for the given traversal mode. + * + * @param mode The traversal mode. Can be {@link TreeTraversalMode.Leaves} or + * {@link TreeTraversalMode.All}. + * @returns The previous event in the tree, or null if the tree is empty. + */ + async previous( + mode: TreeTraversalMode = TreeTraversalMode.Leaves + ): Promise> { if (!this.#cursor.target) { if (await this.#cursor.tryMoveTo(this.#bookmark)) { const event = await this.getEvent(this.#cursor.target!.address); return { done: false, value: event }; } } + + if (mode === TreeTraversalMode.Leaves) { + return this.#walkLeaves(TreeTraversalDirection.Backward); + } + + return this.#preorderWalkAll(TreeTraversalDirection.Backward); + } + + async #yieldEventAtCursor(done: boolean): Promise> { + const value = (await this.getEvent(this.#cursor.target!.address)) ?? null; + return { done, value }; + } + + /** + * Walks the tree in the given direction, yielding the event at each leaf. + * + * @param direction The direction to walk the tree. + * @returns The event at the leaf, or null if the tree is empty. + * + * Based on Raymond Chen's tree traversal algorithm example. + * https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 + */ + async #walkLeaves( + direction: TreeTraversalDirection = TreeTraversalDirection.Forward + ): Promise> { + const tryMoveToSibling: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToNextSibling.bind(this.#cursor) + : this.#cursor.tryMoveToPreviousSibling.bind(this.#cursor); + const tryMoveToChild: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToFirstChild.bind(this.#cursor) + : this.#cursor.tryMoveToLastChild.bind(this.#cursor); - // Based on Raymond Chen's tree traversal algorithm example. - // https://devblogs.microsoft.com/oldnewthing/20200106-00/?p=103300 do { - if (await this.#cursor.tryMoveToPreviousSibling()) { - while (await this.#cursor.tryMoveToLastChild()) { + if (await tryMoveToSibling()) { + while (await tryMoveToChild()) { continue; } @@ -399,8 +435,7 @@ export class PublicationTree implements AsyncIterable { return { done: false, value: null }; } - const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event }; + return this.#yieldEventAtCursor(false); } } while (this.#cursor.tryMoveToParent()); @@ -408,9 +443,26 @@ export class PublicationTree implements AsyncIterable { return { done: false, value: null }; } + // If we get to this point, we're at the root node (can't move up any more). return { done: true, value: null }; } + /** + * Walks the tree in the given direction, yielding the event at each node. + * + * @param direction The direction to walk the tree. + * @returns The event at the node, or null if the tree is empty. + * + * Based on Raymond Chen's preorder walk algorithm example. + * https://devblogs.microsoft.com/oldnewthing/20200107-00/?p=103304 + */ + async #preorderWalkAll( + direction: TreeTraversalDirection = TreeTraversalDirection.Forward + ): Promise> { + // TODO: Implement this. + return { done: false, value: null }; + } + // #endregion // #region Private Methods From e32354caba3181f875079f44668a3a9df11ee8e3 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 4 Jun 2025 23:35:36 -0500 Subject: [PATCH 26/74] Add option to walk entire publication tree, not just leaves --- src/lib/data_structures/publication_tree.ts | 39 ++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index ea57f05..6776626 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -370,11 +370,12 @@ export class PublicationTree implements AsyncIterable { } } - if (mode === TreeTraversalMode.Leaves) { + switch (mode) { + case TreeTraversalMode.Leaves: return this.#walkLeaves(TreeTraversalDirection.Forward); + case TreeTraversalMode.All: + return this.#preorderWalkAll(TreeTraversalDirection.Forward); } - - return this.#preorderWalkAll(TreeTraversalDirection.Forward); } /** @@ -394,11 +395,12 @@ export class PublicationTree implements AsyncIterable { } } - if (mode === TreeTraversalMode.Leaves) { + switch (mode) { + case TreeTraversalMode.Leaves: return this.#walkLeaves(TreeTraversalDirection.Backward); + case TreeTraversalMode.All: + return this.#preorderWalkAll(TreeTraversalDirection.Backward); } - - return this.#preorderWalkAll(TreeTraversalDirection.Backward); } async #yieldEventAtCursor(done: boolean): Promise> { @@ -459,8 +461,29 @@ export class PublicationTree implements AsyncIterable { async #preorderWalkAll( direction: TreeTraversalDirection = TreeTraversalDirection.Forward ): Promise> { - // TODO: Implement this. - return { done: false, value: null }; + const tryMoveToSibling: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToNextSibling.bind(this.#cursor) + : this.#cursor.tryMoveToPreviousSibling.bind(this.#cursor); + const tryMoveToChild: () => Promise = direction === TreeTraversalDirection.Forward + ? this.#cursor.tryMoveToFirstChild.bind(this.#cursor) + : this.#cursor.tryMoveToLastChild.bind(this.#cursor); + + if (await tryMoveToChild()) { + return this.#yieldEventAtCursor(false); + } + + do { + if (await tryMoveToSibling()) { + return this.#yieldEventAtCursor(false); + } + } while (this.#cursor.tryMoveToParent()); + + if (this.#cursor.target!.status === PublicationTreeNodeStatus.Error) { + return { done: false, value: null }; + } + + // If we get to this point, we're at the root node (can't move up any more). + return this.#yieldEventAtCursor(true); } // #endregion From 80c75144898ab38c1a80c8a1306d434f6f71336b Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 4 Jun 2025 23:46:53 -0500 Subject: [PATCH 27/74] Allow bookmark observers --- src/lib/data_structures/publication_tree.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 6776626..1590581 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -61,9 +61,11 @@ export class PublicationTree implements AsyncIterable { */ #ndk: NDK; - #onNodeAddedCallbacks: Array<(address: string) => void> = []; + #nodeAddedObservers: Array<(address: string) => void> = []; - #onNodeResolvedCallbacks: Array<(address: string) => void> = []; + #nodeResolvedObservers: Array<(address: string) => void> = []; + + #bookmarkMovedObservers: Array<(address: string) => void> = []; constructor(rootEvent: NDKEvent, ndk: NDK) { const rootAddress = rootEvent.tagAddress(); @@ -195,11 +197,16 @@ export class PublicationTree implements AsyncIterable { */ setBookmark(address: string) { this.#bookmark = address; + this.#bookmarkMovedObservers.forEach(observer => observer(address)); this.#cursor.tryMoveTo(address); } + onBookmarkMoved(observer: (address: string) => void) { + this.#bookmarkMovedObservers.push(observer); + } + onNodeAdded(observer: (address: string) => void) { - this.#onNodeAddedCallbacks.push(observer); + this.#nodeAddedObservers.push(observer); } /** @@ -209,7 +216,7 @@ export class PublicationTree implements AsyncIterable { * @param observer The observer function. */ onNodeResolved(observer: (address: string) => void) { - this.#onNodeResolvedCallbacks.push(observer); + this.#nodeResolvedObservers.push(observer); } // #region Iteration Cursor @@ -560,7 +567,7 @@ export class PublicationTree implements AsyncIterable { parentNode.children!.push(lazyNode); this.#nodes.set(address, lazyNode); - this.#onNodeAddedCallbacks.forEach(observer => observer(address)); + this.#nodeAddedObservers.forEach(observer => observer(address)); } /** @@ -614,7 +621,7 @@ export class PublicationTree implements AsyncIterable { } // TODO: We may need to move this to `#addNode`, so the observer is notified more eagerly. - this.#onNodeResolvedCallbacks.forEach(observer => observer(address)); + this.#nodeResolvedObservers.forEach(observer => observer(address)); return node; } From d3a62c13f6b4716a3a3c971175b5553e2f095b50 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 5 Jun 2025 08:45:13 -0500 Subject: [PATCH 28/74] Add a `getParent` abstraction to `SveltePublicationTree` --- .../publications/svelte_publication_tree.svelte.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts index b956fd7..eb8e65f 100644 --- a/src/lib/components/publications/svelte_publication_tree.svelte.ts +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -28,6 +28,14 @@ export class SveltePublicationTree { return this.#publicationTree.getHierarchy(address); } + async getParent(address: string): Promise { + const hierarchy = await this.getHierarchy(address); + + // The last element in the hierarchy is the event with the given address, so the parent is the + // second to last element. + return hierarchy.at(-2) ?? null; + } + setBookmark(address: string) { this.#publicationTree.setBookmark(address); } From 7672a103378059ef55ecc3c2d0f5805073cd0802 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 5 Jun 2025 09:26:30 -0500 Subject: [PATCH 29/74] Allow node resolution subscription on `SveltePublicationTree` --- .../svelte_publication_tree.svelte.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts index eb8e65f..aac39f2 100644 --- a/src/lib/components/publications/svelte_publication_tree.svelte.ts +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -7,6 +7,7 @@ export class SveltePublicationTree { resolvedAddresses: SvelteSet = new SvelteSet(); #publicationTree: PublicationTree; + #nodeResolvedObservers: Array<(address: string) => void> = []; constructor(rootEvent: NDKEvent, ndk: NDK) { this.#publicationTree = new PublicationTree(rootEvent, ndk); @@ -40,6 +41,14 @@ export class SveltePublicationTree { this.#publicationTree.setBookmark(address); } + /** + * Registers an observer function that is invoked whenever a new node is resolved. + * @param observer The observer function. + */ + onNodeResolved(observer: (address: string) => void) { + this.#nodeResolvedObservers.push(observer); + } + // #endregion // #region Proxied Async Iterator Methods @@ -60,8 +69,19 @@ export class SveltePublicationTree { // #region Private Methods - #handleNodeResolved(address: string) { + /** + * Observer function that is invoked whenever a new node is resolved on the publication tree. + * + * @param address The address of the resolved node. + * + * This member is declared as an arrow function to ensure that the correct `this` context is + * used when the function is invoked in this class's constructor. + */ + #handleNodeResolved = (address: string) => { this.resolvedAddresses.add(address); + for (const observer of this.#nodeResolvedObservers) { + observer(address); + } } // #endregion From 534fc23c8cb99d61521261f86aa3454a6e27e702 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 5 Jun 2025 09:26:49 -0500 Subject: [PATCH 30/74] Update doc comment on `PublicationTree` --- src/lib/data_structures/publication_tree.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 1590581..515b866 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -154,8 +154,11 @@ export class PublicationTree implements AsyncIterable { /** * Retrieves the addresses of the loaded children, if any, of the node with the given address. + * * @param address The address of the parent node. * @returns An array of addresses of any loaded child nodes. + * + * Note that this method resolves all children of the node. */ async getChildAddresses(address: string): Promise> { const node = await this.#nodes.get(address)?.value(); From ddf8b9006b669a576214cecaf9444137769cd1a7 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 5 Jun 2025 09:27:41 -0500 Subject: [PATCH 31/74] Update ToC on publication tree node resolution --- .../publications/table_of_contents.svelte.ts | 198 +++++++++++------- 1 file changed, 120 insertions(+), 78 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index daf190a..14066e9 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,98 +1,52 @@ -import { SveltePublicationTree } from "./svelte_publication_tree.svelte.ts"; +import { SvelteMap } from 'svelte/reactivity'; +import type { SveltePublicationTree } from './svelte_publication_tree.svelte.ts'; +import type { NDKEvent } from '../../utils/nostrUtils.ts'; export interface TocEntry { address: string; title: string; - href: string; + href?: string; + children: TocEntry[]; + parent?: TocEntry; depth: number; - expanded: boolean; - children: Array | null; } export class TableOfContents { - #tocRoot: TocEntry | null = null; - #addresses = $state>(new Map()); + public addressMap: SvelteMap = new SvelteMap(); + + #root: TocEntry | null = null; #publicationTree: SveltePublicationTree; #pagePathname: string; /** - * Constructor for the `TableOfContents` class. The constructed ToC initially contains only the - * root entry. Additional entries must be inserted programmatically using class methods. - * - * The `TableOfContents` class should be instantiated as a page-scoped singleton so that - * `pagePathname` is correct wherever the instance is used. The singleton should be made - * made available to the entire component tree under that page. + * Constructs a `TableOfContents` from a `SveltePublicationTree`. + * + * @param rootAddress The address of the root event. + * @param publicationTree The SveltePublicationTree instance. + * @param pagePathname The current page pathname for href generation. */ constructor(rootAddress: string, publicationTree: SveltePublicationTree, pagePathname: string) { this.#publicationTree = publicationTree; this.#pagePathname = pagePathname; - - this.insertIntoTocFromPublicationTree(rootAddress); + void this.#initRoot(rootAddress); + this.#publicationTree.onNodeResolved((address: string) => { + void this.#handleNodeResolved(address); + }); } - #normalizeHashPath(title: string): string { - // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. - return title.toLowerCase().replace(/ /g, '-'); - } + // #region Public Methods - get addresses(): Map { - return this.#addresses; + /** + * Returns the root entry of the ToC. + * + * @returns The root entry of the ToC, or `null` if the ToC has not been initialized. + */ + getRootEntry(): TocEntry | null { + return this.#root; } - async insertIntoTocFromPublicationTree(address: string): Promise { - const targetEvent = await this.#publicationTree.getEvent(address); - if (!targetEvent) { - console.warn(`[ToC] Event ${address} not found.`); - // TODO: Determine how to handle this case in the UI. - return; - } - - const hierarchyEvents = await this.#publicationTree.getHierarchy(address); - if (hierarchyEvents.length === 0) { - // This means we are at root. - return; - } - - // 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 - // initialized, this code will work fine. - let currentParentTocNode: TocEntry | null = this.#tocRoot; - for (let i = 0; i < hierarchyEvents.length; i++) { - const currentEvent = hierarchyEvents[i]; - const currentAddress = currentEvent.tagAddress(); - - if (this.#addresses.has(currentAddress)) { - continue; - } - - const currentEventChildAddresses = await this.#publicationTree.getChildAddresses(currentAddress); - for (const address of currentEventChildAddresses) { - if (address === null) { - continue; - } - - const childEvent = await this.#publicationTree.getEvent(address); - if (!childEvent) { - console.warn(`[ToC] Event ${address} not found.`); - continue; - } - - currentParentTocNode!.children ??= []; - - const childTocEntry: TocEntry = { - address, - title: childEvent.getMatchingTags('title')[0][1], - href: `${this.#pagePathname}#${this.#normalizeHashPath(childEvent.getMatchingTags('title')[0][1])}`, - depth: i + 1, - expanded: false, - children: null, - }; - currentParentTocNode!.children.push(childTocEntry); - this.#addresses.set(address, childTocEntry); - } - - currentParentTocNode = this.#addresses.get(currentAddress)!; - } + getEntry(address: string): TocEntry | undefined { + return this.addressMap.get(address); } /** @@ -128,10 +82,8 @@ export class TableOfContents { title, href, depth, - expanded: false, - children: null, + children: [], }; - parentEntry.children ??= []; parentEntry.children.push(tocEntry); this.buildTocFromDocument(header, tocEntry, depth + 1); @@ -139,6 +91,10 @@ export class TableOfContents { }); } + // #endregion + + // #region Iterator Methods + /** * Iterates over all ToC entries in depth-first order. */ @@ -157,6 +113,92 @@ export class TableOfContents { } } - yield* traverse(this.#tocRoot); + yield* traverse(this.#root); } + + // #endregion + + // #region Private Methods + + async #initRoot(rootAddress: string) { + const rootEvent = await this.#publicationTree.getEvent(rootAddress); + if (!rootEvent) { + throw new Error(`[ToC] Root event ${rootAddress} not found.`); + } + + this.#root = { + address: rootAddress, + title: this.#getTitle(rootEvent), + children: [], + depth: 0, + }; + + this.addressMap.set(rootAddress, this.#root); + // Handle any other nodes that have already been resolved. + await this.#handleNodeResolved(rootAddress); + } + + async #handleNodeResolved(address: string) { + if (this.addressMap.has(address)) { + return; + } + const event = await this.#publicationTree.getEvent(address); + if (!event) { + return; + } + + const parentEvent = await this.#publicationTree.getParent(address); + const parentAddress = parentEvent?.tagAddress(); + if (!parentAddress) { + // All non-root nodes must have a parent. + if (!this.#root || address !== this.#root.address) { + throw new Error(`[ToC] Parent not found for address ${address}`); + } + return; + } + + const parentEntry = this.addressMap.get(parentAddress); + if (!parentEntry) { + throw new Error(`[ToC] Parent ToC entry not found for address ${address}`); + } + + const entry: TocEntry = { + address, + title: this.#getTitle(event), + children: [], + parent: parentEntry, + depth: parentEntry.depth + 1, + }; + + // Michael J - 05 June 2025 - The `getChildAddresses` method forces node resolution on the + // publication tree. This is acceptable here, because the tree is always resolved top-down. + // Therefore, by the time we handle a node's resolution, its parent and siblings have already + // been resolved. + const childAddresses = await this.#publicationTree.getChildAddresses(parentAddress); + const filteredChildAddresses = childAddresses.filter((a): a is string => !!a); + const insertIndex = filteredChildAddresses.findIndex(a => a === address); + if (insertIndex === -1 || insertIndex > parentEntry.children.length) { + parentEntry.children.push(entry); + } else { + parentEntry.children.splice(insertIndex, 0, entry); + } + + this.addressMap.set(address, entry); + } + + #getTitle(event: NDKEvent | null): string { + if (!event) { + // TODO: What do we want to return in this case? + return '[untitled]'; + } + const titleTag = event.getMatchingTags?.('title')?.[0]?.[1]; + return titleTag || event.tagAddress() || '[untitled]'; + } + + #normalizeHashPath(title: string): string { + // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. + return title.toLowerCase().replace(/ /g, '-'); + } + + // #endregion } From f635d08982684cbc3b899b29a42116f63a327928 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Thu, 5 Jun 2025 09:32:35 -0500 Subject: [PATCH 32/74] Add doc comment for ToC --- .../components/publications/table_of_contents.svelte.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 14066e9..c6a26e6 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -11,6 +11,13 @@ export interface TocEntry { depth: number; } +/** + * Maintains a table of contents (ToC) for a `SveltePublicationTree`. Since publication trees are + * conceptually infinite and lazy-loading, the ToC represents only the portion of the tree that has + * been "discovered". The ToC is updated as new nodes are resolved within the publication tree. + * + * @see SveltePublicationTree + */ export class TableOfContents { public addressMap: SvelteMap = new SvelteMap(); From fa6265b346f9eca03f153f4da16f1a51976697f3 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Fri, 6 Jun 2025 09:37:49 -0500 Subject: [PATCH 33/74] Refactor and support tree node resolution via ToC entry closure --- .../publications/table_of_contents.svelte.ts | 128 ++++++++++-------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index c6a26e6..8665697 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,5 +1,5 @@ import { SvelteMap } from 'svelte/reactivity'; -import type { SveltePublicationTree } from './svelte_publication_tree.svelte.ts'; +import { SveltePublicationTree } from './svelte_publication_tree.svelte.ts'; import type { NDKEvent } from '../../utils/nostrUtils.ts'; export interface TocEntry { @@ -9,6 +9,9 @@ export interface TocEntry { children: TocEntry[]; parent?: TocEntry; depth: number; + expanded: boolean; + childrenResolved: boolean; + resolveChildren: () => Promise; } /** @@ -35,10 +38,7 @@ export class TableOfContents { constructor(rootAddress: string, publicationTree: SveltePublicationTree, pagePathname: string) { this.#publicationTree = publicationTree; this.#pagePathname = pagePathname; - void this.#initRoot(rootAddress); - this.#publicationTree.onNodeResolved((address: string) => { - void this.#handleNodeResolved(address); - }); + this.#init(rootAddress); } // #region Public Methods @@ -84,12 +84,16 @@ export class TableOfContents { if (id && title) { const href = `${this.#pagePathname}#${id}`; + // TODO: Check this logic. const tocEntry: TocEntry = { address: parentEntry.address, title, href, depth, children: [], + expanded: false, + childrenResolved: true, + resolveChildren: () => Promise.resolve(), }; parentEntry.children.push(tocEntry); @@ -127,84 +131,96 @@ export class TableOfContents { // #region Private Methods - async #initRoot(rootAddress: string) { + async #init(rootAddress: string) { const rootEvent = await this.#publicationTree.getEvent(rootAddress); if (!rootEvent) { throw new Error(`[ToC] Root event ${rootAddress} not found.`); } - this.#root = { - address: rootAddress, - title: this.#getTitle(rootEvent), - children: [], - depth: 0, - }; + this.#root = await this.#buildTocEntry(rootAddress); this.addressMap.set(rootAddress, this.#root); + + // TODO: Parallelize this. // Handle any other nodes that have already been resolved. - await this.#handleNodeResolved(rootAddress); + this.#publicationTree.resolvedAddresses.forEach(async (address) => { + await this.#buildTocEntryFromResolvedNode(address); + }); + + // Set up an observer to handle progressive resolution of the publication tree. + this.#publicationTree.onNodeResolved(async (address: string) => { + await this.#buildTocEntryFromResolvedNode(address); + }); } - async #handleNodeResolved(address: string) { - if (this.addressMap.has(address)) { - return; - } - const event = await this.#publicationTree.getEvent(address); + #getTitle(event: NDKEvent | null): string { if (!event) { - return; + // TODO: What do we want to return in this case? + return '[untitled]'; } + const titleTag = event.getMatchingTags?.('title')?.[0]?.[1]; + return titleTag || event.tagAddress() || '[untitled]'; + } + + #normalizeHashPath(title: string): string { + // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. + return title.toLowerCase().replace(/ /g, '-'); + } - const parentEvent = await this.#publicationTree.getParent(address); - const parentAddress = parentEvent?.tagAddress(); - if (!parentAddress) { - // All non-root nodes must have a parent. - if (!this.#root || address !== this.#root.address) { - throw new Error(`[ToC] Parent not found for address ${address}`); + async #buildTocEntry(address: string): Promise { + const resolver = async () => { + if (entry.childrenResolved) { + return; } - return; + + const childAddresses = await this.#publicationTree.getChildAddresses(address); + for (const childAddress of childAddresses) { + if (!childAddress) { + continue; + } + + // Michael J - 05 June 2025 - The `getChildAddresses` method forces node resolution on the + // publication tree. This is acceptable here, because the tree is always resolved + // top-down. Therefore, by the time we handle a node's resolution, its parent and + // siblings have already been resolved. + const childEntry = await this.#buildTocEntry(childAddress); + childEntry.parent = entry; + childEntry.depth = entry.depth + 1; + entry.children.push(childEntry); + this.addressMap.set(childAddress, childEntry); + } + + entry.childrenResolved = true; } - const parentEntry = this.addressMap.get(parentAddress); - if (!parentEntry) { - throw new Error(`[ToC] Parent ToC entry not found for address ${address}`); + const event = await this.#publicationTree.getEvent(address); + if (!event) { + throw new Error(`[ToC] Event ${address} not found.`); } + const depth = (await this.#publicationTree.getHierarchy(address)).length; + const entry: TocEntry = { address, title: this.#getTitle(event), + href: `${this.#pagePathname}#${address}`, children: [], - parent: parentEntry, - depth: parentEntry.depth + 1, + depth, + expanded: false, + childrenResolved: false, + resolveChildren: resolver, }; - - // Michael J - 05 June 2025 - The `getChildAddresses` method forces node resolution on the - // publication tree. This is acceptable here, because the tree is always resolved top-down. - // Therefore, by the time we handle a node's resolution, its parent and siblings have already - // been resolved. - const childAddresses = await this.#publicationTree.getChildAddresses(parentAddress); - const filteredChildAddresses = childAddresses.filter((a): a is string => !!a); - const insertIndex = filteredChildAddresses.findIndex(a => a === address); - if (insertIndex === -1 || insertIndex > parentEntry.children.length) { - parentEntry.children.push(entry); - } else { - parentEntry.children.splice(insertIndex, 0, entry); - } - - this.addressMap.set(address, entry); + + return entry; } - #getTitle(event: NDKEvent | null): string { - if (!event) { - // TODO: What do we want to return in this case? - return '[untitled]'; + async #buildTocEntryFromResolvedNode(address: string) { + if (this.addressMap.has(address)) { + return; } - const titleTag = event.getMatchingTags?.('title')?.[0]?.[1]; - return titleTag || event.tagAddress() || '[untitled]'; - } - #normalizeHashPath(title: string): string { - // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. - return title.toLowerCase().replace(/ /g, '-'); + const entry = await this.#buildTocEntry(address); + this.addressMap.set(address, entry); } // #endregion From 9a6a494f172c1915e96b9d17b9e7ebb68608a3b7 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 9 Jun 2025 22:37:11 -0500 Subject: [PATCH 34/74] Fix `NDKEvent` type imports --- src/lib/components/CommentBox.svelte | 3 +-- .../components/publications/svelte_publication_tree.svelte.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/components/CommentBox.svelte b/src/lib/components/CommentBox.svelte index c46f902..46b2f4b 100644 --- a/src/lib/components/CommentBox.svelte +++ b/src/lib/components/CommentBox.svelte @@ -2,12 +2,11 @@ import { Button, Textarea, Alert } from 'flowbite-svelte'; import { parseBasicmarkup } from '$lib/utils/markup/basicMarkupParser'; import { nip19 } from 'nostr-tools'; - import { getEventHash, signEvent, getUserMetadata, type NostrProfile } from '$lib/utils/nostrUtils'; + import { getEventHash, signEvent, getUserMetadata, type NostrProfile, type NDKEvent } from '$lib/utils/nostrUtils'; import { standardRelays, fallbackRelays } from '$lib/consts'; import { userRelays } from '$lib/stores/relayStore'; import { get } from 'svelte/store'; import { goto } from '$app/navigation'; - import type { NDKEvent } from '$lib/utils/nostrUtils'; import { onMount } from 'svelte'; const props = $props<{ diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts index aac39f2..9969ed7 100644 --- a/src/lib/components/publications/svelte_publication_tree.svelte.ts +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -1,7 +1,6 @@ import { SvelteSet } from "svelte/reactivity"; import { PublicationTree } from "../../data_structures/publication_tree.ts"; -import { NDKEvent } from "../../utils/nostrUtils.ts"; -import NDK from "@nostr-dev-kit/ndk"; +import NDK, { NDKEvent } from "@nostr-dev-kit/ndk"; export class SveltePublicationTree { resolvedAddresses: SvelteSet = new SvelteSet(); From 608ce2b8e4392e53aaeca45451282858f0c64fd2 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 9 Jun 2025 23:20:42 -0500 Subject: [PATCH 35/74] First pass at ToC layout --- .../publications/Publication.svelte | 9 ++- .../publications/TableOfContents.svelte | 65 +++++++++++++++---- src/routes/publication/+page.svelte | 7 ++ 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index 7147de4..2511de0 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -19,9 +19,9 @@ import { publicationColumnVisibility } from "$lib/stores"; import BlogHeader from "$components/cards/BlogHeader.svelte"; import Interactions from "$components/util/Interactions.svelte"; - import TocToggle from "$components/util/TocToggle.svelte"; import { pharosInstance } from '$lib/parser'; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; + import TableOfContents from "./TableOfContents.svelte"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string; @@ -160,7 +160,12 @@ {#if publicationType !== "blog" || !isLeaf} - + { + publicationTree.setBookmark(address); + }} + /> {/if} diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 891476a..aadfded 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -1,22 +1,63 @@ - - {#each toc as entry} - {entry.title} + + {#each entries as entry} + +

{entry.title}

+ {#if entry.children.length > 0} + + {/if} +
{/each} -
+ diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 7defecf..457d896 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -6,11 +6,18 @@ import Processor from "asciidoctor"; import ArticleNav from "$components/util/ArticleNav.svelte"; import { SveltePublicationTree } from "$lib/components/publications/svelte_publication_tree.svelte"; + import { TableOfContents } from "$lib/components/publications/table_of_contents.svelte"; let { data }: PageProps = $props(); const publicationTree = new SveltePublicationTree(data.indexEvent, data.ndk); + const toc = new TableOfContents( + data.indexEvent.tagAddress(), + publicationTree, + data.url?.pathname ?? "", + ); setContext("publicationTree", publicationTree); + setContext("toc", toc); setContext("asciidoctor", Processor()); // Get publication metadata for OpenGraph tags From e3848045224f615f7d7366fc9a53bddf7886f6cb Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 10 Jun 2025 08:32:46 -0500 Subject: [PATCH 36/74] Add stronger typing to publication layout store --- src/lib/stores.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/stores.ts b/src/lib/stores.ts index e38f0d4..7962e7b 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -1,5 +1,5 @@ -import { readable, writable } from "svelte/store"; -import { FeedType } from "./consts"; +import { readable, writable } from 'svelte/store'; +import { FeedType } from './consts.ts'; export let idList = writable([]); @@ -7,8 +7,16 @@ export let alexandriaKinds = readable([30040, 30041, 30818]); export let feedType = writable(FeedType.StandardRelays); +export interface PublicationLayoutVisibility { + toc: boolean; + blog: boolean; + main: boolean; + inner: boolean; + discussion: boolean; + editing: boolean; +} -const defaultVisibility = { +const defaultVisibility: PublicationLayoutVisibility = { toc: false, blog: true, main: true, @@ -18,7 +26,8 @@ const defaultVisibility = { }; function createVisibilityStore() { - const { subscribe, set, update } = writable({ ...defaultVisibility }); + const { subscribe, set, update } + = writable({ ...defaultVisibility }); return { subscribe, From d48ddd465a04be3808d239b3b4b30afc23d82b08 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 10 Jun 2025 08:33:07 -0500 Subject: [PATCH 37/74] Remove unneeded references to Pharos parser --- src/lib/components/publications/Publication.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index 2511de0..5f1a9de 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -19,7 +19,6 @@ import { publicationColumnVisibility } from "$lib/stores"; import BlogHeader from "$components/cards/BlogHeader.svelte"; import Interactions from "$components/util/Interactions.svelte"; - import { pharosInstance } from '$lib/parser'; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import TableOfContents from "./TableOfContents.svelte"; @@ -153,9 +152,6 @@ observer.disconnect(); }; }); - - // Whenever the publication changes, update rootId - let rootId = $derived($pharosInstance.getRootIndexId()); From cd204119f03a6815b3b8d8d8f58115d8bb0cc180 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 10 Jun 2025 08:52:58 -0500 Subject: [PATCH 38/74] Tidy up code formatting in ArticleNav component --- src/lib/components/util/ArticleNav.svelte | 30 ++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index a5b8631..11f804c 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -113,34 +113,46 @@
{#if shouldShowBack()} {/if} {#if !isLeaf} {#if publicationType === 'blog'} - {:else if !$publicationColumnVisibility.discussion && !$publicationColumnVisibility.toc} - {/if} {/if}
-

{title} by {@render userBadge(pubkey, author)}

+

{title}by {@render userBadge(pubkey, author)}

{#if $publicationColumnVisibility.inner} {/if} {#if publicationType !== 'blog' && !$publicationColumnVisibility.discussion} {/if}
From db4991b229c561e02ef2970d059e5a6a1a9aa909 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 00:13:49 -0500 Subject: [PATCH 39/74] Update Flowbite Svelte version NB: Upgrading to v1.0.0 or above breaks our current stlying. DO NOT UPGRADE WITHOUT FIXING STYLES. --- deno.lock | 220 ++++++++++++++++++++++++++++++------------------ import_map.json | 2 +- package.json | 2 +- 3 files changed, 140 insertions(+), 84 deletions(-) diff --git a/deno.lock b/deno.lock index 6604928..28f6a0b 100644 --- a/deno.lock +++ b/deno.lock @@ -16,14 +16,15 @@ "npm:@types/d3@^7.4.3": "7.4.3", "npm:@types/he@1.2": "1.2.3", "npm:@types/node@22": "22.13.9", + "npm:@types/qrcode@^1.5.5": "1.5.5", "npm:asciidoctor@3.0": "3.0.4_@asciidoctor+core@3.0.4", "npm:autoprefixer@10": "10.4.20_postcss@8.5.3", + "npm:bech32@2": "2.0.0", "npm:d3@7.9": "7.9.0_d3-selection@3.0.0", "npm:d3@^7.9.0": "7.9.0_d3-selection@3.0.0", "npm:eslint-plugin-svelte@2": "2.46.1_eslint@9.21.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3", "npm:flowbite-svelte-icons@2.1": "2.1.1_svelte@5.0.5__acorn@8.14.0_tailwind-merge@3.3.0", - "npm:flowbite-svelte@0": "0.48.4_svelte@5.21.0__acorn@8.14.0", - "npm:flowbite-svelte@0.44": "0.44.24_svelte@4.2.19", + "npm:flowbite-svelte@0.48": "0.48.6_svelte@5.0.5__acorn@8.14.0", "npm:flowbite@2": "2.5.2", "npm:flowbite@2.2": "2.2.1", "npm:he@1.2": "1.2.0", @@ -35,6 +36,7 @@ "npm:postcss@8": "8.5.3", "npm:prettier-plugin-svelte@3": "3.3.3_prettier@3.5.3_svelte@5.21.0__acorn@8.14.0", "npm:prettier@3": "3.5.3", + "npm:qrcode@^1.5.4": "1.5.4", "npm:svelte-check@4": "4.1.4_svelte@5.21.0__acorn@8.14.0_typescript@5.7.3", "npm:svelte@5": "5.21.0_acorn@8.14.0", "npm:svelte@5.0": "5.0.5_acorn@8.14.0", @@ -61,7 +63,7 @@ "integrity": "sha512-x2T9gW42921Zd90juEagtbViPZHNP2MWf0+6rJEkOzW7E9m3TGJtz+Guye9J0gwrpZsTMGCpfYMQy1We3X7osg==", "dependencies": [ "@asciidoctor/core", - "yargs" + "yargs@17.3.1" ] }, "@asciidoctor/core@3.0.4": { @@ -217,14 +219,14 @@ "levn" ] }, - "@floating-ui/core@1.6.9": { - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "@floating-ui/core@1.7.1": { + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", "dependencies": [ "@floating-ui/utils" ] }, - "@floating-ui/dom@1.6.13": { - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "@floating-ui/dom@1.7.1": { + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", "dependencies": [ "@floating-ui/core", "@floating-ui/utils" @@ -789,12 +791,24 @@ "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "@types/node@22.12.0": { + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dependencies": [ + "undici-types" + ] + }, "@types/node@22.13.9": { "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", "dependencies": [ "undici-types" ] }, + "@types/qrcode@1.5.5": { + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dependencies": [ + "@types/node@22.12.0" + ] + }, "@types/resolve@1.20.2": { "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, @@ -977,6 +991,9 @@ "balanced-match@1.0.2": { "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "bech32@2.0.0": { + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, "binary-extensions@2.3.0": { "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" }, @@ -1037,6 +1054,9 @@ "camelcase-css@2.0.1": { "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelcase@5.3.1": { + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, "caniuse-lite@1.0.30001702": { "integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==" }, @@ -1088,6 +1108,14 @@ "readdirp@4.1.2" ] }, + "cliui@6.0.0": { + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": [ + "string-width@4.2.3", + "strip-ansi@6.0.1", + "wrap-ansi@6.2.0" + ] + }, "cliui@7.0.4": { "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dependencies": [ @@ -1099,16 +1127,6 @@ "clsx@2.1.1": { "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" }, - "code-red@1.0.4": { - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dependencies": [ - "@jridgewell/sourcemap-codec", - "@types/estree", - "acorn@8.14.0", - "estree-walker@3.0.3", - "periscopic" - ] - }, "color-convert@2.0.1": { "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": [ @@ -1151,13 +1169,6 @@ "which" ] }, - "css-tree@2.3.1": { - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dependencies": [ - "mdn-data", - "source-map-js" - ] - }, "cssesc@3.0.0": { "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, @@ -1382,6 +1393,9 @@ "ms@2.1.3" ] }, + "decamelize@1.2.0": { + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, "deep-eql@5.0.2": { "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" }, @@ -1406,6 +1420,9 @@ "didyoumean@1.2.2": { "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "dijkstrajs@1.0.3": { + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, "dlv@1.1.3": { "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, @@ -1586,7 +1603,7 @@ "esutils", "fast-deep-equal", "file-entry-cache", - "find-up", + "find-up@5.0.0", "glob-parent@6.0.2", "ignore", "imurmurhash", @@ -1730,10 +1747,17 @@ "to-regex-range" ] }, + "find-up@4.1.0": { + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": [ + "locate-path@5.0.0", + "path-exists" + ] + }, "find-up@5.0.0": { "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dependencies": [ - "locate-path", + "locate-path@6.0.0", "path-exists" ] }, @@ -1768,24 +1792,14 @@ "tailwind-merge@3.3.0" ] }, - "flowbite-svelte@0.44.24_svelte@4.2.19": { - "integrity": "sha512-kXhJZHGpBVq5RFOoYnzRCEM8eFa81DVp4KjUbBsLJptKhizbSSBJuYApWIQb9pBCS8EBhX4PAX+RsgEDZfEqtA==", - "dependencies": [ - "@floating-ui/dom", - "apexcharts", - "flowbite@2.5.2", - "svelte@4.2.19", - "tailwind-merge@2.5.5" - ] - }, - "flowbite-svelte@0.48.4_svelte@5.21.0__acorn@8.14.0": { - "integrity": "sha512-ivlBxNi2u9+D/nFeHs+vLJU6nYjKq/ooAwdXPP3qIlEnUyIl/hVsH87JtVWwVEgF31NwwQcZeKFkWd8K5DWiGw==", + "flowbite-svelte@0.48.6_svelte@5.0.5__acorn@8.14.0": { + "integrity": "sha512-/PmeR3ipHHvda8vVY9MZlymaRoJsk8VddEeoLzIygfYwJV68ey8gHuQPC1dq9J6NDCTE5+xOPtBiYUtVjCfvZw==", "dependencies": [ "@floating-ui/dom", "apexcharts", "flowbite@3.1.2", - "svelte@5.21.0_acorn@8.14.0", - "tailwind-merge@3.0.2" + "svelte@5.0.5_acorn@8.14.0", + "tailwind-merge@3.3.0" ] }, "flowbite@2.2.1": { @@ -2115,10 +2129,16 @@ "locate-character@3.0.0": { "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" }, + "locate-path@5.0.0": { + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": [ + "p-locate@4.1.0" + ] + }, "locate-path@6.0.0": { "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dependencies": [ - "p-locate" + "p-locate@5.0.0" ] }, "lodash.castarray@4.4.0": { @@ -2145,9 +2165,6 @@ "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, - "mdn-data@2.0.30": { - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, "merge2@1.4.1": { "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, @@ -2285,18 +2302,33 @@ "word-wrap" ] }, + "p-limit@2.3.0": { + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": [ + "p-try" + ] + }, "p-limit@3.1.0": { "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dependencies": [ "yocto-queue" ] }, + "p-locate@4.1.0": { + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": [ + "p-limit@2.3.0" + ] + }, "p-locate@5.0.0": { "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dependencies": [ - "p-limit" + "p-limit@3.1.0" ] }, + "p-try@2.2.0": { + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, "package-json-from-dist@1.0.1": { "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, @@ -2328,14 +2360,6 @@ "pathval@2.0.0": { "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==" }, - "periscopic@3.1.0": { - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": [ - "@types/estree", - "estree-walker@3.0.3", - "is-reference@3.0.3" - ] - }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, @@ -2361,6 +2385,9 @@ "playwright-core" ] }, + "pngjs@5.0.0": { + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" + }, "postcss-import@15.1.0_postcss@8.5.3": { "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dependencies": [ @@ -2554,6 +2581,14 @@ "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, + "qrcode@1.5.4": { + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "dependencies": [ + "dijkstrajs", + "pngjs", + "yargs@15.4.1" + ] + }, "queue-microtask@1.2.3": { "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, @@ -2575,6 +2610,9 @@ "require-directory@2.1.1": { "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, + "require-main-filename@2.0.0": { + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "resolve-from@4.0.0": { "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, @@ -2639,6 +2677,9 @@ "semver@7.7.1": { "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" }, + "set-blocking@2.0.0": { + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "set-cookie-parser@2.7.1": { "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" }, @@ -2758,25 +2799,6 @@ "svelte@5.21.0_acorn@8.14.0" ] }, - "svelte@4.2.19": { - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", - "dependencies": [ - "@ampproject/remapping", - "@jridgewell/sourcemap-codec", - "@jridgewell/trace-mapping", - "@types/estree", - "acorn@8.14.0", - "aria-query", - "axobject-query", - "code-red", - "css-tree", - "estree-walker@3.0.3", - "is-reference@3.0.3", - "locate-character", - "magic-string", - "periscopic" - ] - }, "svelte@5.0.5_acorn@8.14.0": { "integrity": "sha512-f4WBlP5g8W6pEoDfx741lewMlemy+LIGpEqjGPWqnHVP92wqlQXl87U5O5Bi2tkSUrO95OxOoqwU8qlqiHmFKA==", "dependencies": [ @@ -2863,9 +2885,6 @@ "tailwind-merge@2.5.5": { "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==" }, - "tailwind-merge@3.0.2": { - "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==" - }, "tailwind-merge@3.3.0": { "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==" }, @@ -3026,7 +3045,7 @@ "vite@5.4.14_@types+node@22.13.9": { "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dependencies": [ - "@types/node", + "@types/node@22.13.9", "esbuild", "fsevents@2.3.3", "postcss", @@ -3042,7 +3061,7 @@ "vitest@3.1.4_@types+node@22.13.9_vite@5.4.14__@types+node@22.13.9": { "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", "dependencies": [ - "@types/node", + "@types/node@22.13.9", "@vitest/expect", "@vitest/mocker", "@vitest/pretty-format", @@ -3087,6 +3106,9 @@ "yaeti" ] }, + "which-module@2.0.1": { + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "which@2.0.2": { "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ @@ -3115,6 +3137,14 @@ "wordwrap@1.0.0": { "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" }, + "wrap-ansi@6.2.0": { + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": [ + "ansi-styles@4.3.0", + "string-width@4.2.3", + "strip-ansi@6.0.1" + ] + }, "wrap-ansi@7.0.0": { "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": [ @@ -3134,6 +3164,9 @@ "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "y18n@4.0.3": { + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, "y18n@5.0.8": { "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, @@ -3146,19 +3179,42 @@ "yaml@2.7.0": { "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==" }, + "yargs-parser@18.1.3": { + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": [ + "camelcase", + "decamelize" + ] + }, "yargs-parser@21.1.1": { "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, + "yargs@15.4.1": { + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": [ + "cliui@6.0.0", + "decamelize", + "find-up@4.1.0", + "get-caller-file", + "require-directory", + "require-main-filename", + "set-blocking", + "string-width@4.2.3", + "which-module", + "y18n@4.0.3", + "yargs-parser@18.1.3" + ] + }, "yargs@17.3.1": { "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dependencies": [ - "cliui", + "cliui@7.0.4", "escalade", "get-caller-file", "require-directory", "string-width@4.2.3", - "y18n", - "yargs-parser" + "y18n@5.0.8", + "yargs-parser@21.1.1" ] }, "yocto-queue@0.1.0": { @@ -3185,7 +3241,7 @@ "npm:asciidoctor@3.0", "npm:d3@7.9", "npm:flowbite-svelte-icons@2.1", - "npm:flowbite-svelte@0.44", + "npm:flowbite-svelte@0.48", "npm:flowbite@2.2", "npm:he@1.2", "npm:nostr-tools@2.10", @@ -3215,7 +3271,7 @@ "npm:d3@^7.9.0", "npm:eslint-plugin-svelte@2", "npm:flowbite-svelte-icons@2.1", - "npm:flowbite-svelte@0", + "npm:flowbite-svelte@0.48", "npm:flowbite@2", "npm:he@1.2", "npm:highlight.js@^11.11.1", diff --git a/import_map.json b/import_map.json index 4c3af16..af4012c 100644 --- a/import_map.json +++ b/import_map.json @@ -12,7 +12,7 @@ "tailwind-merge": "npm:tailwind-merge@2.5.x", "svelte": "npm:svelte@5.0.x", "flowbite": "npm:flowbite@2.2.x", - "flowbite-svelte": "npm:flowbite-svelte@0.44.x", + "flowbite-svelte": "npm:flowbite-svelte@0.48.x", "flowbite-svelte-icons": "npm:flowbite-svelte-icons@2.1.x", "child_process": "node:child_process" } diff --git a/package.json b/package.json index 787d2e7..3df9454 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "autoprefixer": "10.x", "eslint-plugin-svelte": "2.x", "flowbite": "2.x", - "flowbite-svelte": "0.x", + "flowbite-svelte": "0.48.x", "flowbite-svelte-icons": "2.1.x", "playwright": "^1.50.1", "postcss": "8.x", From 29c06551cf7a51585e0cc68b1dde2d6c6dfd74d7 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 00:14:34 -0500 Subject: [PATCH 40/74] Remove ToC from page context --- src/routes/publication/+page.svelte | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 457d896..a73d37e 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -10,14 +10,8 @@ let { data }: PageProps = $props(); const publicationTree = new SveltePublicationTree(data.indexEvent, data.ndk); - const toc = new TableOfContents( - data.indexEvent.tagAddress(), - publicationTree, - data.url?.pathname ?? "", - ); setContext("publicationTree", publicationTree); - setContext("toc", toc); setContext("asciidoctor", Processor()); // Get publication metadata for OpenGraph tags From b07914f9635d64e077fee3156f56ecb27d50fc0c Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 00:16:03 -0500 Subject: [PATCH 41/74] Allow two ToC component variants - A sidebar variant is meant for integration within a sidebar. This is used in the current Publication component. - An accordion variant is intended for standalone use. --- .../publications/Publication.svelte | 30 ++++++--- .../publications/TableOfContents.svelte | 65 ++++++++++++++----- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index 5f1a9de..ec4ec10 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -7,6 +7,7 @@ SidebarGroup, SidebarWrapper, Heading, + CloseButton, } from "flowbite-svelte"; import { getContext, onDestroy, onMount } from "svelte"; import { @@ -90,6 +91,10 @@ return currentBlog !== null && $publicationColumnVisibility.inner; } + function closeToc() { + publicationColumnVisibility.update((v) => ({ ...v, toc: false })); + } + function closeDiscussion() { publicationColumnVisibility.update((v) => ({ ...v, discussion: false })); } @@ -155,13 +160,20 @@ -{#if publicationType !== "blog" || !isLeaf} - { - publicationTree.setBookmark(address); - }} - /> +{#if publicationType !== 'blog' || !isLeaf} + {#if $publicationColumnVisibility.toc} + + + { + publicationTree.setBookmark(address); + }} + /> + + {/if} {/if} @@ -205,9 +217,7 @@ {#if $publicationColumnVisibility.blog}
- import type { TableOfContents, TocEntry } from '$lib/components/publications/table_of_contents.svelte'; + import { TableOfContents, type TocEntry } from '$lib/components/publications/table_of_contents.svelte'; import { getContext } from 'svelte'; - import { Accordion, AccordionItem, Card } from 'flowbite-svelte'; + import { Accordion, AccordionItem, Card, SidebarDropdownWrapper, SidebarGroup, SidebarItem } from 'flowbite-svelte'; import Self from './TableOfContents.svelte'; + import type { SveltePublicationTree } from './svelte_publication_tree.svelte'; + import { page } from '$app/state'; - let { + export type TocDisplayMode = 'accordion' | 'sidebar'; + + let { + displayMode = 'accordion', + rootAddress, depth, onSectionFocused, } = $props<{ + displayMode?: TocDisplayMode; + rootAddress: string; depth: number; onSectionFocused?: (address: string) => void; }>(); - let toc = getContext('toc') as TableOfContents; + let publicationTree = getContext('publicationTree') as SveltePublicationTree; + let toc = new TableOfContents(rootAddress, publicationTree, page.url.pathname ?? ""); - let entries = $derived( - Array + let entries = $derived.by(() => { + console.debug("[ToC] Filtering entries for depth", depth); + const entries = Array .from(toc.addressMap.values()) - .filter((entry) => entry.depth === depth) - ); + .filter((entry) => entry.depth === depth); + console.debug("[ToC] Filtered entries", entries.map((e) => e.title)); + return entries; + }); // Track the currently visible section for highlighting let currentSection = $state(null); @@ -51,13 +63,34 @@ } - - {#each entries as entry} - -

{entry.title}

+{#if displayMode === 'accordion'} + + {#each entries as entry} + + {#snippet header()} + {entry.title} + {/snippet} + {#if entry.children.length > 0} + + {/if} + + {/each} + +{:else} + + {#each entries as entry} {#if entry.children.length > 0} - + + + + {:else} + {/if} -
- {/each} -
+ {/each} + +{/if} \ No newline at end of file From 052392d7f0d09b3da76e9a22f8b3cb7f20f186d4 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 00:36:17 -0500 Subject: [PATCH 42/74] Add action bindings on ToC component elements --- .../publications/TableOfContents.svelte | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 8356509..9a1f252 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -23,50 +23,49 @@ let publicationTree = getContext('publicationTree') as SveltePublicationTree; let toc = new TableOfContents(rootAddress, publicationTree, page.url.pathname ?? ""); - let entries = $derived.by(() => { - console.debug("[ToC] Filtering entries for depth", depth); - const entries = Array + let entries = $derived( + Array .from(toc.addressMap.values()) - .filter((entry) => entry.depth === depth); - console.debug("[ToC] Filtered entries", entries.map((e) => e.title)); - return entries; - }); + .filter((entry) => entry.depth === depth) + ); - // Track the currently visible section for highlighting - let currentSection = $state(null); + function getEntryExpanded(address: string) { + return toc.getEntry(address)?.expanded; + } - // Handle section visibility changes from the IntersectionObserver - function handleSectionVisibility(address: string, isVisible: boolean) { - if (isVisible) { - currentSection = address; + function setEntryExpanded(address: string, expanded: boolean = false) { + const entry = toc.getEntry(address); + if (!entry) { + return; } - } - // Toggle expansion of a ToC entry - async function toggleExpansion(entry: TocEntry) { - // Update the current section in the ToC - const tocEntry = toc.getEntry(entry.address); - if (tocEntry) { - // Ensure the parent sections are expanded - let parent = tocEntry.parent; - while (parent) { - parent.expanded = true; - parent = parent.parent; - } + entry.expanded = expanded; + if (entry.childrenResolved) { + return; } - entry.expanded = !entry.expanded; - if (entry.expanded && !entry.childrenResolved) { - onSectionFocused?.(entry.address); - await entry.resolveChildren(); + if (expanded) { + entry.resolveChildren(); } } + + function handleEntryClick(address: string, expanded: boolean = false) { + setEntryExpanded(address, expanded); + onSectionFocused?.(address); + } + {#if displayMode === 'accordion'} {#each entries as entry} - + {@const address = entry.address} + getEntryExpanded(address), + (open) => setEntryExpanded(address, open) + } + > {#snippet header()} {entry.title} {/snippet} @@ -79,8 +78,15 @@ {:else} {#each entries as entry} + {@const address = entry.address} {#if entry.children.length > 0} - + getEntryExpanded(address), + (open) => setEntryExpanded(address, open) + } + > {:else} - + + handleEntryClick(address, !getEntryExpanded(address))} + /> {/if} {/each} From 7219e68ca3bb5b829f3a5b5b48967ed04045be57 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 07:53:43 -0500 Subject: [PATCH 43/74] Make accordion non-flush --- src/lib/components/publications/TableOfContents.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 9a1f252..9813d57 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -57,7 +57,7 @@ {#if displayMode === 'accordion'} - + {#each entries as entry} {@const address = entry.address} {entry.title} {/snippet} {#if entry.children.length > 0} - + {/if} {/each} From 2d6d38b3b38e2046d9a11f1e83a2a876edb455a8 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Jun 2025 23:07:12 -0500 Subject: [PATCH 44/74] Fix data flow between publication tree and ToC --- src/lib/components/publications/TableOfContents.svelte | 8 +------- .../components/publications/table_of_contents.svelte.ts | 4 ++-- src/lib/data_structures/publication_tree.ts | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 9813d57..50296d2 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -40,13 +40,7 @@ } entry.expanded = expanded; - if (entry.childrenResolved) { - return; - } - - if (expanded) { - entry.resolveChildren(); - } + entry.resolveChildren(); } function handleEntryClick(address: string, expanded: boolean = false) { diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 8665697..63d3b9c 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -148,8 +148,8 @@ export class TableOfContents { }); // Set up an observer to handle progressive resolution of the publication tree. - this.#publicationTree.onNodeResolved(async (address: string) => { - await this.#buildTocEntryFromResolvedNode(address); + this.#publicationTree.onNodeResolved((address: string) => { + this.#buildTocEntryFromResolvedNode(address); }); } diff --git a/src/lib/data_structures/publication_tree.ts b/src/lib/data_structures/publication_tree.ts index 515b866..a7aece6 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -623,7 +623,6 @@ export class PublicationTree implements AsyncIterable { this.addEventByAddress(address, event); } - // TODO: We may need to move this to `#addNode`, so the observer is notified more eagerly. this.#nodeResolvedObservers.forEach(observer => observer(address)); return node; From c235646ffeedfddbe75ffb4b4204d420ec6f8399 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 14 Jun 2025 14:10:42 -0500 Subject: [PATCH 45/74] Ensure ToC addresses update correctly Closing and reopening the ToC component shows updates, but it is not responsive to clicks. This will be fixed in a future commit. --- .../publications/TableOfContents.svelte | 32 ++++++++++++------- .../publications/table_of_contents.svelte.ts | 17 +++++++--- src/lib/data_structures/publication_tree.ts | 2 +- src/routes/publication/+page.svelte | 3 ++ 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 50296d2..0f66e4e 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -1,16 +1,13 @@ + {#if displayMode === 'accordion'} {#each entries as entry} @@ -79,7 +75,14 @@ {#each entries as entry} {@const address = entry.address} {@const expanded = toc.expandedMap.get(address) ?? false} - {#if entry.children.length > 0} + {@const isLeaf = toc.leaves.has(address)} + {#if isLeaf} + + onSectionFocused?.(address)} + /> + {:else} {@const childDepth = depth + 1} - {:else} - - handleEntryClick(address, !expanded)} - /> {/if} {/each} diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index a566f52..95fa0e2 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -1,4 +1,4 @@ -import { SvelteMap } from 'svelte/reactivity'; +import { SvelteMap, SvelteSet } from 'svelte/reactivity'; import { SveltePublicationTree } from './svelte_publication_tree.svelte.ts'; import type { NDKEvent } from '../../utils/nostrUtils.ts'; import { indexKind } from '../../consts.ts'; @@ -24,6 +24,7 @@ export interface TocEntry { export class TableOfContents { public addressMap: SvelteMap = new SvelteMap(); public expandedMap: SvelteMap = new SvelteMap(); + public leaves: SvelteSet = new SvelteSet(); #root: TocEntry | null = null; #publicationTree: SveltePublicationTree; @@ -186,6 +187,13 @@ export class TableOfContents { continue; } + // Michael J - 16 June 2025 - This duplicates logic in the outer function, but is necessary + // here so that we can determine whether to render an entry as a leaf before it is fully + // resolved. + if (childAddress.split(':')[0] !== indexKind.toString()) { + this.leaves.add(childAddress); + } + // Michael J - 05 June 2025 - The `getChildAddresses` method forces node resolution on the // publication tree. This is acceptable here, because the tree is always resolved // top-down. Therefore, by the time we handle a node's resolution, its parent and @@ -217,6 +225,14 @@ export class TableOfContents { resolveChildren: resolver, }; this.expandedMap.set(address, false); + + // Michael J - 16 June 2025 - We determine whether to add a leaf both here and in the inner + // resolver function. The resolver function is called when entries are resolved by expanding + // a ToC entry, and we'll reach the block below when entries are resolved by the publication + // tree. + if (event.kind !== indexKind) { + this.leaves.add(address); + } return entry; } From 7b441dc0a580ac16c703ec8eda14cc699d762006 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Fri, 27 Jun 2025 15:55:32 -0500 Subject: [PATCH 52/74] Adjust some spacing --- src/routes/publication/+page.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 34ec929..4348871 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -20,8 +20,8 @@ // Get publication metadata for OpenGraph tags let title = $derived( data.indexEvent?.getMatchingTags("title")[0]?.[1] || - data.parser?.getIndexTitle(data.parser?.getRootIndexId()) || - "Alexandria Publication", + data.parser?.getIndexTitle(data.parser?.getRootIndexId()) || + "Alexandria Publication", ); let currentUrl = data.url?.href ?? ""; @@ -33,7 +33,7 @@ ); let summary = $derived( data.indexEvent?.getMatchingTags("summary")[0]?.[1] || - "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.", + "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.", ); onDestroy(() => data.parser.reset()); From 166eb237c172cbc0eb959619a28a4f43249d45c8 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 17:42:11 -0500 Subject: [PATCH 53/74] Update SvelteKit --- deno.lock | 517 ++++++++++++++++++++++++++++++++++++++++----------- package.json | 2 +- 2 files changed, 406 insertions(+), 113 deletions(-) diff --git a/deno.lock b/deno.lock index 28f6a0b..0a222bd 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,5 @@ { - "version": "4", + "version": "5", "specifiers": { "npm:@nostr-dev-kit/ndk-cache-dexie@2.5": "2.5.13_typescript@5.7.3", "npm:@nostr-dev-kit/ndk@2.11": "2.11.2_typescript@5.7.3", @@ -10,6 +10,7 @@ "npm:@sveltejs/adapter-static@3": "3.0.8_@sveltejs+kit@2.17.3__@sveltejs+vite-plugin-svelte@4.0.4___svelte@5.21.0____acorn@8.14.0___vite@5.4.14____@types+node@22.13.9___@types+node@22.13.9__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "npm:@sveltejs/kit@2": "2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "npm:@sveltejs/kit@^2.16.0": "2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", + "npm:@sveltejs/kit@^2.22.2": "2.22.2_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_acorn@8.15.0_@types+node@22.13.9", "npm:@sveltejs/vite-plugin-svelte@4": "4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "npm:@tailwindcss/forms@0.5": "0.5.10_tailwindcss@3.4.17__postcss@8.5.3", "npm:@tailwindcss/typography@0.5": "0.5.16_tailwindcss@3.4.17__postcss@8.5.3", @@ -22,7 +23,7 @@ "npm:bech32@2": "2.0.0", "npm:d3@7.9": "7.9.0_d3-selection@3.0.0", "npm:d3@^7.9.0": "7.9.0_d3-selection@3.0.0", - "npm:eslint-plugin-svelte@2": "2.46.1_eslint@9.21.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3", + "npm:eslint-plugin-svelte@2": "2.46.1_eslint@9.21.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3_svelte@5.0.5__acorn@8.14.0", "npm:flowbite-svelte-icons@2.1": "2.1.1_svelte@5.0.5__acorn@8.14.0_tailwind-merge@3.3.0", "npm:flowbite-svelte@0.48": "0.48.6_svelte@5.0.5__acorn@8.14.0", "npm:flowbite@2": "2.5.2", @@ -64,7 +65,8 @@ "dependencies": [ "@asciidoctor/core", "yargs@17.3.1" - ] + ], + "bin": true }, "@asciidoctor/core@3.0.4": { "integrity": "sha512-41SDMi7iRRBViPe0L6VWFTe55bv6HEOJeRqMj5+E5wB1YPdUPuTucL4UAESPZM6OWmn4t/5qM5LusXomFUVwVQ==", @@ -90,7 +92,8 @@ "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "dependencies": [ "@babel/types" - ] + ], + "bin": true }, "@babel/types@7.26.9": { "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", @@ -100,73 +103,119 @@ ] }, "@esbuild/aix-ppc64@0.21.5": { - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==" + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.21.5": { - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==" + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm@0.21.5": { - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==" + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-x64@0.21.5": { - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==" + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.21.5": { - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==" + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.21.5": { - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==" + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.21.5": { - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==" + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.21.5": { - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==" + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.21.5": { - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==" + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.21.5": { - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==" + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.21.5": { - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==" + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.21.5": { - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==" + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.21.5": { - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==" + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.21.5": { - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==" + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.21.5": { - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==" + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.21.5": { - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==" + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.21.5": { - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==" + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/netbsd-x64@0.21.5": { - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==" + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-x64@0.21.5": { - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==" + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.21.5": { - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==" + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.21.5": { - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==" + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.21.5": { - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==" + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.21.5": { - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==" + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "os": ["win32"], + "cpu": ["x64"] }, "@eslint-community/eslint-utils@4.4.1_eslint@9.21.0": { "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", @@ -388,7 +437,8 @@ "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dependencies": [ "playwright" - ] + ], + "bin": true }, "@polka/url@1.0.0-next.28": { "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" @@ -407,6 +457,9 @@ "magic-string", "picomatch@4.0.2", "rollup" + ], + "optionalPeers": [ + "rollup" ] }, "@rollup/plugin-json@6.1.0_rollup@4.34.9": { @@ -414,12 +467,15 @@ "dependencies": [ "@rollup/pluginutils@5.1.4_rollup@4.34.9", "rollup" + ], + "optionalPeers": [ + "rollup" ] }, "@rollup/plugin-node-resolve@15.3.1": { "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dependencies": [ - "@rollup/pluginutils@5.1.4", + "@rollup/pluginutils@5.1.4_rollup@4.34.9", "@types/resolve", "deepmerge", "is-module", @@ -435,6 +491,9 @@ "is-module", "resolve", "rollup" + ], + "optionalPeers": [ + "rollup" ] }, "@rollup/pluginutils@5.1.4": { @@ -443,6 +502,9 @@ "@types/estree", "estree-walker@2.0.2", "picomatch@4.0.2" + ], + "optionalPeers": [ + "rollup" ] }, "@rollup/pluginutils@5.1.4_rollup@4.34.9": { @@ -452,64 +514,105 @@ "estree-walker@2.0.2", "picomatch@4.0.2", "rollup" + ], + "optionalPeers": [ + "rollup" ] }, "@rollup/rollup-android-arm-eabi@4.34.9": { - "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==" + "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", + "os": ["android"], + "cpu": ["arm"] }, "@rollup/rollup-android-arm64@4.34.9": { - "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==" + "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", + "os": ["android"], + "cpu": ["arm64"] }, "@rollup/rollup-darwin-arm64@4.34.9": { - "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==" + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@rollup/rollup-darwin-x64@4.34.9": { - "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==" + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "os": ["darwin"], + "cpu": ["x64"] }, "@rollup/rollup-freebsd-arm64@4.34.9": { - "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==" + "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@rollup/rollup-freebsd-x64@4.34.9": { - "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==" + "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@rollup/rollup-linux-arm-gnueabihf@4.34.9": { - "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==" + "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", + "os": ["linux"], + "cpu": ["arm"] }, "@rollup/rollup-linux-arm-musleabihf@4.34.9": { - "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==" + "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", + "os": ["linux"], + "cpu": ["arm"] }, "@rollup/rollup-linux-arm64-gnu@4.34.9": { - "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==" + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "os": ["linux"], + "cpu": ["arm64"] }, "@rollup/rollup-linux-arm64-musl@4.34.9": { - "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==" + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "os": ["linux"], + "cpu": ["arm64"] }, "@rollup/rollup-linux-loongarch64-gnu@4.34.9": { - "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==" + "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", + "os": ["linux"], + "cpu": ["loong64"] }, "@rollup/rollup-linux-powerpc64le-gnu@4.34.9": { - "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==" + "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@rollup/rollup-linux-riscv64-gnu@4.34.9": { - "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==" + "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@rollup/rollup-linux-s390x-gnu@4.34.9": { - "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==" + "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", + "os": ["linux"], + "cpu": ["s390x"] }, "@rollup/rollup-linux-x64-gnu@4.34.9": { - "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==" + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "os": ["linux"], + "cpu": ["x64"] }, "@rollup/rollup-linux-x64-musl@4.34.9": { - "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==" + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "os": ["linux"], + "cpu": ["x64"] }, "@rollup/rollup-win32-arm64-msvc@4.34.9": { - "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==" + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "os": ["win32"], + "cpu": ["arm64"] }, "@rollup/rollup-win32-ia32-msvc@4.34.9": { - "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==" + "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", + "os": ["win32"], + "cpu": ["ia32"] }, "@rollup/rollup-win32-x64-msvc@4.34.9": { - "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==" + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "os": ["win32"], + "cpu": ["x64"] }, "@scure/base@1.1.1": { "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" @@ -535,10 +638,16 @@ "@sindresorhus/is@4.6.0": { "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, + "@sveltejs/acorn-typescript@1.0.5_acorn@8.15.0": { + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dependencies": [ + "acorn@8.15.0" + ] + }, "@sveltejs/adapter-auto@3.3.1_@sveltejs+kit@2.17.3__@sveltejs+vite-plugin-svelte@4.0.4___svelte@5.21.0____acorn@8.14.0___vite@5.4.14____@types+node@22.13.9___@types+node@22.13.9__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==", "dependencies": [ - "@sveltejs/kit", + "@sveltejs/kit@2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "import-meta-resolve" ] }, @@ -548,20 +657,20 @@ "@rollup/plugin-commonjs", "@rollup/plugin-json", "@rollup/plugin-node-resolve@16.0.0_rollup@4.34.9", - "@sveltejs/kit", + "@sveltejs/kit@2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "rollup" ] }, "@sveltejs/adapter-static@3.0.8_@sveltejs+kit@2.17.3__@sveltejs+vite-plugin-svelte@4.0.4___svelte@5.21.0____acorn@8.14.0___vite@5.4.14____@types+node@22.13.9___@types+node@22.13.9__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dependencies": [ - "@sveltejs/kit" + "@sveltejs/kit@2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9" ] }, "@sveltejs/kit@2.17.3_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-GcNaPDr0ti4O/TonPewkML2DG7UVXkSxPN3nPMlpmx0Rs4b2kVP4gymz98WEHlfzPXdd4uOOT1Js26DtieTNBQ==", "dependencies": [ - "@sveltejs/vite-plugin-svelte", + "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "@types/cookie", "cookie", "devalue", @@ -575,21 +684,53 @@ "sirv", "svelte@5.21.0_acorn@8.14.0", "vite" - ] + ], + "bin": true + }, + "@sveltejs/kit@2.22.2_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_acorn@8.15.0_@types+node@22.13.9": { + "integrity": "sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg==", + "dependencies": [ + "@sveltejs/acorn-typescript", + "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0", + "@types/cookie", + "acorn@8.15.0", + "cookie", + "devalue", + "esm-env", + "kleur", + "magic-string", + "mrmime", + "sade", + "set-cookie-parser", + "sirv", + "svelte@5.0.5_acorn@8.14.0", + "vite", + "vitefu" + ], + "bin": true }, "@sveltejs/vite-plugin-svelte-inspector@3.0.1_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", "dependencies": [ - "@sveltejs/vite-plugin-svelte", + "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0", "debug@4.4.0", "svelte@5.21.0_acorn@8.14.0", "vite" ] }, + "@sveltejs/vite-plugin-svelte-inspector@3.0.1_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0": { + "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0", + "debug@4.4.0", + "svelte@5.0.5_acorn@8.14.0", + "vite" + ] + }, "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", "dependencies": [ - "@sveltejs/vite-plugin-svelte-inspector", + "@sveltejs/vite-plugin-svelte-inspector@3.0.1_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9", "debug@4.4.0", "deepmerge", "kleur", @@ -599,6 +740,19 @@ "vitefu" ] }, + "@sveltejs/vite-plugin-svelte@4.0.4_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0": { + "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte-inspector@3.0.1_@sveltejs+vite-plugin-svelte@4.0.4__svelte@5.21.0___acorn@8.14.0__vite@5.4.14___@types+node@22.13.9__@types+node@22.13.9_svelte@5.21.0__acorn@8.14.0_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9_svelte@5.0.5__acorn@8.14.0", + "debug@4.4.0", + "deepmerge", + "kleur", + "magic-string", + "svelte@5.0.5_acorn@8.14.0", + "vite", + "vitefu" + ] + }, "@tailwindcss/forms@0.5.10_tailwindcss@3.4.17__postcss@8.5.3": { "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", "dependencies": [ @@ -828,6 +982,9 @@ "estree-walker@3.0.3", "magic-string", "vite" + ], + "optionalPeers": [ + "vite" ] }, "@vitest/pretty-format@3.1.4": { @@ -884,10 +1041,16 @@ ] }, "acorn@7.4.1": { - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": true }, "acorn@8.14.0": { - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": true + }, + "acorn@8.15.0": { + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": true }, "ajv@6.12.6": { "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", @@ -956,7 +1119,8 @@ "handlebars", "nunjucks", "pug" - ] + ], + "bin": true }, "assert-never@1.4.0": { "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==" @@ -977,7 +1141,8 @@ "picocolors", "postcss", "postcss-value-parser" - ] + ], + "bin": true }, "axobject-query@4.1.0": { "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" @@ -1023,13 +1188,15 @@ "electron-to-chromium", "node-releases", "update-browserslist-db" - ] + ], + "bin": true }, "bufferutil@4.0.9": { "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "dependencies": [ "node-gyp-build" - ] + ], + "scripts": true }, "cac@6.7.14": { "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" @@ -1094,12 +1261,14 @@ "dependencies": [ "anymatch", "braces", - "fsevents@2.3.3", "glob-parent@5.1.2", "is-binary-path", "is-glob", "normalize-path", "readdirp@3.6.0" + ], + "optionalDependencies": [ + "fsevents@2.3.3" ] }, "chokidar@4.0.3": { @@ -1170,7 +1339,8 @@ ] }, "cssesc@3.0.0": { - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": true }, "d3-array@3.2.4": { "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", @@ -1228,7 +1398,8 @@ "commander@7.2.0", "iconv-lite", "rw" - ] + ], + "bin": true }, "d3-ease@3.0.1": { "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" @@ -1444,7 +1615,8 @@ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": [ "jake" - ] + ], + "bin": true }, "electron-to-chromium@1.5.111": { "integrity": "sha512-vJyJlO95wQRAw6K2ZGF/8nol7AcbCOnp8S6H91mwOOBbXoS9seDBYxCTPYAFsvXLxl3lc0jLXXe9GLxC4nXVog==" @@ -1480,7 +1652,8 @@ "es6-symbol", "esniff", "next-tick" - ] + ], + "scripts": true }, "es6-iterator@2.0.3": { "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", @@ -1499,7 +1672,7 @@ }, "esbuild@0.21.5": { "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dependencies": [ + "optionalDependencies": [ "@esbuild/aix-ppc64", "@esbuild/android-arm", "@esbuild/android-arm64", @@ -1523,7 +1696,9 @@ "@esbuild/win32-arm64", "@esbuild/win32-ia32", "@esbuild/win32-x64" - ] + ], + "scripts": true, + "bin": true }, "escalade@3.2.0": { "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" @@ -1553,7 +1728,31 @@ "postcss-selector-parser@6.1.2", "semver", "svelte@5.21.0_acorn@8.14.0", - "svelte-eslint-parser" + "svelte-eslint-parser@0.43.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3" + ], + "optionalPeers": [ + "svelte@^3.37.0 || ^4.0.0 || ^5.0.0" + ] + }, + "eslint-plugin-svelte@2.46.1_eslint@9.21.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3_svelte@5.0.5__acorn@8.14.0": { + "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@jridgewell/sourcemap-codec", + "eslint", + "eslint-compat-utils", + "esutils", + "known-css-properties", + "postcss", + "postcss-load-config@3.1.4_postcss@8.5.3", + "postcss-safe-parser", + "postcss-selector-parser@6.1.2", + "semver", + "svelte@5.0.5_acorn@8.14.0", + "svelte-eslint-parser@0.43.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3_svelte@5.0.5__acorn@8.14.0" + ], + "optionalPeers": [ + "svelte@5.0.5_acorn@8.14.0" ] }, "eslint-scope@7.2.2": { @@ -1613,7 +1812,8 @@ "minimatch@3.1.2", "natural-compare", "optionator" - ] + ], + "bin": true }, "esm-env@1.2.2": { "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" @@ -1721,12 +1921,18 @@ "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", "dependencies": [ "picomatch@4.0.2" + ], + "optionalPeers": [ + "picomatch@4.0.2" ] }, "fdir@6.4.4_picomatch@4.0.2": { "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dependencies": [ "picomatch@4.0.2" + ], + "optionalPeers": [ + "picomatch@4.0.2" ] }, "file-entry-cache@8.0.0": { @@ -1840,10 +2046,14 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents@2.3.2": { - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "os": ["darwin"], + "scripts": true }, "fsevents@2.3.3": { - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true }, "function-bind@1.1.2": { "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" @@ -1894,7 +2104,8 @@ "minipass", "package-json-from-dist", "path-scurry" - ] + ], + "bin": true }, "glob@8.1.0": { "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", @@ -1904,7 +2115,8 @@ "inherits", "minimatch@5.1.6", "once" - ] + ], + "deprecated": true }, "globals@14.0.0": { "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" @@ -1918,9 +2130,12 @@ "minimist", "neo-async", "source-map", - "uglify-js", "wordwrap" - ] + ], + "optionalDependencies": [ + "uglify-js" + ], + "bin": true }, "has-flag@4.0.0": { "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" @@ -1941,7 +2156,8 @@ ] }, "he@1.2.0": { - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": true }, "highlight.js@11.11.1": { "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==" @@ -1973,7 +2189,8 @@ "dependencies": [ "once", "wrappy" - ] + ], + "deprecated": true }, "inherits@2.0.4": { "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" @@ -2051,7 +2268,9 @@ "jackspeak@3.4.3": { "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dependencies": [ - "@isaacs/cliui", + "@isaacs/cliui" + ], + "optionalDependencies": [ "@pkgjs/parseargs" ] }, @@ -2062,10 +2281,12 @@ "chalk", "filelist", "minimatch@3.1.2" - ] + ], + "bin": true }, "jiti@1.21.7": { - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==" + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "bin": true }, "js-stringify@1.0.2": { "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" @@ -2074,7 +2295,8 @@ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": [ "argparse" - ] + ], + "bin": true }, "json-buffer@3.0.1": { "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" @@ -2176,7 +2398,8 @@ ] }, "mini-svg-data-uri@1.4.4": { - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==" + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": true }, "minimatch@3.1.2": { "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", @@ -2223,7 +2446,8 @@ ] }, "nanoid@3.3.8": { - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "bin": true }, "natural-compare@1.4.0": { "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" @@ -2244,7 +2468,8 @@ ] }, "node-gyp-build@4.8.4": { - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": true }, "node-releases@2.0.19": { "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" @@ -2264,7 +2489,12 @@ "@scure/base@1.1.1", "@scure/bip32", "@scure/bip39", - "nostr-wasm", + "typescript" + ], + "optionalDependencies": [ + "nostr-wasm" + ], + "optionalPeers": [ "typescript" ] }, @@ -2277,7 +2507,8 @@ "a-sync-waterfall", "asap", "commander@5.1.0" - ] + ], + "bin": true }, "object-assign@4.1.1": { "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" @@ -2376,14 +2607,18 @@ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==" }, "playwright-core@1.50.1": { - "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==" + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "bin": true }, "playwright@1.50.1": { "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dependencies": [ - "fsevents@2.3.2", "playwright-core" - ] + ], + "optionalDependencies": [ + "fsevents@2.3.2" + ], + "bin": true }, "pngjs@5.0.0": { "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" @@ -2410,6 +2645,9 @@ "lilconfig@2.1.0", "postcss", "yaml@1.10.2" + ], + "optionalPeers": [ + "postcss" ] }, "postcss-load-config@4.0.2_postcss@8.5.3": { @@ -2418,6 +2656,9 @@ "lilconfig@3.1.3", "postcss", "yaml@2.7.0" + ], + "optionalPeers": [ + "postcss" ] }, "postcss-load-config@6.0.1_postcss@8.5.3": { @@ -2425,6 +2666,9 @@ "dependencies": [ "lilconfig@3.1.3", "postcss" + ], + "optionalPeers": [ + "postcss" ] }, "postcss-nested@6.2.0_postcss@8.5.3": { @@ -2482,7 +2726,8 @@ ] }, "prettier@3.5.3": { - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==" + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "bin": true }, "promise@7.3.1": { "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", @@ -2587,7 +2832,8 @@ "dijkstrajs", "pngjs", "yargs@15.4.1" - ] + ], + "bin": true }, "queue-microtask@1.2.3": { "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" @@ -2622,7 +2868,8 @@ "is-core-module", "path-parse", "supports-preserve-symlinks-flag" - ] + ], + "bin": true }, "reusify@1.1.0": { "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" @@ -2633,6 +2880,9 @@ "rollup@4.34.9": { "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ "@rollup/rollup-android-arm-eabi", "@rollup/rollup-android-arm64", "@rollup/rollup-darwin-arm64", @@ -2652,9 +2902,9 @@ "@rollup/rollup-win32-arm64-msvc", "@rollup/rollup-win32-ia32-msvc", "@rollup/rollup-win32-x64-msvc", - "@types/estree", "fsevents@2.3.3" - ] + ], + "bin": true }, "run-parallel@1.2.0": { "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", @@ -2675,7 +2925,8 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": true }, "set-blocking@2.0.0": { "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" @@ -2765,7 +3016,8 @@ "mz", "pirates", "ts-interface-checker" - ] + ], + "bin": true }, "supports-color@7.2.0": { "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -2786,7 +3038,8 @@ "sade", "svelte@5.21.0_acorn@8.14.0", "typescript" - ] + ], + "bin": true }, "svelte-eslint-parser@0.43.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3": { "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", @@ -2797,6 +3050,23 @@ "postcss", "postcss-scss", "svelte@5.21.0_acorn@8.14.0" + ], + "optionalPeers": [ + "svelte@^3.37.0 || ^4.0.0 || ^5.0.0" + ] + }, + "svelte-eslint-parser@0.43.0_svelte@5.21.0__acorn@8.14.0_postcss@8.5.3_svelte@5.0.5__acorn@8.14.0": { + "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", + "dependencies": [ + "eslint-scope@7.2.2", + "eslint-visitor-keys@3.4.3", + "espree@9.6.1_acorn@8.14.0", + "postcss", + "postcss-scss", + "svelte@5.0.5_acorn@8.14.0" + ], + "optionalPeers": [ + "svelte@5.0.5_acorn@8.14.0" ] }, "svelte@5.0.5_acorn@8.14.0": { @@ -2913,7 +3183,8 @@ "postcss-selector-parser@6.1.2", "resolve", "sucrase" - ] + ], + "bin": true }, "thenify-all@1.6.0": { "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", @@ -2992,10 +3263,12 @@ "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==" }, "typescript@5.7.3": { - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==" + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "bin": true }, "uglify-js@3.19.3": { - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==" + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "bin": true }, "undici-types@6.20.0": { "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" @@ -3012,7 +3285,8 @@ "browserslist", "escalade", "picocolors" - ] + ], + "bin": true }, "uri-js@4.4.1": { "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", @@ -3024,7 +3298,8 @@ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "dependencies": [ "node-gyp-build" - ] + ], + "scripts": true }, "utf8-buffer@1.0.0": { "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==" @@ -3040,22 +3315,32 @@ "es-module-lexer", "pathe", "vite" - ] + ], + "bin": true }, "vite@5.4.14_@types+node@22.13.9": { "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dependencies": [ "@types/node@22.13.9", "esbuild", - "fsevents@2.3.3", "postcss", "rollup" - ] + ], + "optionalDependencies": [ + "fsevents@2.3.3" + ], + "optionalPeers": [ + "@types/node@22.13.9" + ], + "bin": true }, "vitefu@1.0.6_vite@5.4.14__@types+node@22.13.9_@types+node@22.13.9": { "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", "dependencies": [ "vite" + ], + "optionalPeers": [ + "vite" ] }, "vitest@3.1.4_@types+node@22.13.9_vite@5.4.14__@types+node@22.13.9": { @@ -3083,7 +3368,11 @@ "vite", "vite-node", "why-is-node-running" - ] + ], + "optionalPeers": [ + "@types/node@22.13.9" + ], + "bin": true }, "void-elements@3.1.0": { "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" @@ -3113,14 +3402,16 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ "isexe" - ] + ], + "bin": true }, "why-is-node-running@2.3.0": { "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dependencies": [ "siginfo", "stackback" - ] + ], + "bin": true }, "with@7.0.2": { "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", @@ -3171,13 +3462,15 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yaeti@0.0.6": { - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": true }, "yaml@1.10.2": { "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yaml@2.7.0": { - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==" + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "bin": true }, "yargs-parser@18.1.3": { "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", @@ -3257,7 +3550,7 @@ "npm:@sveltejs/adapter-auto@3", "npm:@sveltejs/adapter-node@^5.2.12", "npm:@sveltejs/adapter-static@3", - "npm:@sveltejs/kit@^2.16.0", + "npm:@sveltejs/kit@^2.22.2", "npm:@sveltejs/vite-plugin-svelte@4", "npm:@tailwindcss/forms@0.5", "npm:@tailwindcss/typography@0.5", diff --git a/package.json b/package.json index 3df9454..a200816 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@sveltejs/adapter-auto": "3.x", "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-static": "3.x", - "@sveltejs/kit": "^2.16.0", + "@sveltejs/kit": "^2.22.2", "@sveltejs/vite-plugin-svelte": "4.x", "@types/d3": "^7.4.3", "@types/he": "1.2.x", From d299017b9eabedee6c5165cb1e5169d1382bdab3 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 17:42:47 -0500 Subject: [PATCH 54/74] Jump to element via ToC --- .../components/publications/Publication.svelte | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index a32bdb9..c5efd96 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -22,6 +22,7 @@ import Interactions from "$components/util/Interactions.svelte"; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import TableOfContents from "./TableOfContents.svelte"; + import { goto } from "$app/navigation"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string; @@ -33,8 +34,6 @@ // #region Loading - // TODO: Test load handling. - let leaves = $state>([]); let isLoading = $state(false); let isDone = $state(false); @@ -82,7 +81,8 @@ // #endregion - // region Columns visibility + // #region Columns visibility + let currentBlog: null | string = $state(null); let currentBlogEvent: null | NDKEvent = $state(null); const isLeaf = $derived(indexEvent.kind === 30041); @@ -123,6 +123,10 @@ return currentBlog && currentBlogEvent && window.innerWidth < 1140; } + // #endregion + + // #region Lifecycle hooks + onDestroy(() => { // reset visibility publicationColumnVisibility.reset(); @@ -157,6 +161,8 @@ observer.disconnect(); }; }); + + // #endregion @@ -170,6 +176,9 @@ depth={2} onSectionFocused={(address: string) => { publicationTree.setBookmark(address); + goto(`#${address}`, { + replaceState: true, + }); }} /> From 5fb32e35b23906aa531fa54118a5c7f73b402a26 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 17:44:08 -0500 Subject: [PATCH 55/74] Formatting and SvelteKit 2/Svelte 5 upgrades --- src/lib/components/publications/TableOfContents.svelte | 7 +++++-- src/routes/+layout.svelte | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 9079cd6..09280b8 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -1,5 +1,8 @@ From 1af98ec9d607a64f8c8e3a5f26b2b0efccdc77c5 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:44:54 -0500 Subject: [PATCH 58/74] Remove unused component --- src/lib/components/util/TocToggle.svelte | 143 ----------------------- 1 file changed, 143 deletions(-) delete mode 100644 src/lib/components/util/TocToggle.svelte diff --git a/src/lib/components/util/TocToggle.svelte b/src/lib/components/util/TocToggle.svelte deleted file mode 100644 index 8fe9626..0000000 --- a/src/lib/components/util/TocToggle.svelte +++ /dev/null @@ -1,143 +0,0 @@ - - - -{#if $publicationColumnVisibility.toc} - - - - Table of contents -

(This ToC is only for demo purposes, and is not fully-functional.)

- {#each tocItems as item} - - {/each} -
-
-
-{/if} \ No newline at end of file From 9afcd37aafae4374495410825632e6f585edd08a Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:45:16 -0500 Subject: [PATCH 59/74] Remove unneeded load actions for publication page --- src/routes/publication/+page.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/routes/publication/+page.ts b/src/routes/publication/+page.ts index b100f70..d154cd8 100644 --- a/src/routes/publication/+page.ts +++ b/src/routes/publication/+page.ts @@ -83,10 +83,11 @@ async function fetchEventByDTag(ndk: any, dTag: string): Promise { } } +// TODO: Use path params instead of query params. export const load: Load = async ({ url, parent }: { url: URL; parent: () => Promise }) => { const id = url.searchParams.get('id'); const dTag = url.searchParams.get('d'); - const { ndk, parser } = await parent(); + const { ndk } = await parent(); if (!id && !dTag) { throw error(400, 'No publication root event ID or d tag provided.'); @@ -98,12 +99,9 @@ export const load: Load = async ({ url, parent }: { url: URL; parent: () => Prom : await fetchEventByDTag(ndk, dTag!); const publicationType = getMatchingTags(indexEvent, 'type')[0]?.[1]; - const fetchPromise = parser.fetch(indexEvent); return { - waitable: fetchPromise, publicationType, indexEvent, - url, }; }; From 8b2ac0e48f30b5e57a73d1c7e5443ae30a5e11c8 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:45:41 -0500 Subject: [PATCH 60/74] Simplify `getMatchingTags` invocation --- src/lib/components/util/ArticleNav.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 11f804c..916a298 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -16,7 +16,7 @@ }>(); let title: string = $derived(indexEvent.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(indexEvent.getMatchingTags(event, 'author')[0]?.[1] ?? 'unknown'); + let author: string = $derived(indexEvent.getMatchingTags('author')[0]?.[1] ?? 'unknown'); let pubkey: string = $derived(indexEvent.getMatchingTags('p')[0]?.[1] ?? null); let isLeaf: boolean = $derived(indexEvent.kind === 30041); From 700dec88752c2772eca383ea5f60373f4f62eaac Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:46:03 -0500 Subject: [PATCH 61/74] Proxy bookmark observers through `SveltePublicationTree` --- .../svelte_publication_tree.svelte.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/components/publications/svelte_publication_tree.svelte.ts b/src/lib/components/publications/svelte_publication_tree.svelte.ts index 9969ed7..0c1eefc 100644 --- a/src/lib/components/publications/svelte_publication_tree.svelte.ts +++ b/src/lib/components/publications/svelte_publication_tree.svelte.ts @@ -7,11 +7,13 @@ export class SveltePublicationTree { #publicationTree: PublicationTree; #nodeResolvedObservers: Array<(address: string) => void> = []; + #bookmarkMovedObservers: Array<(address: string) => void> = []; constructor(rootEvent: NDKEvent, ndk: NDK) { this.#publicationTree = new PublicationTree(rootEvent, ndk); this.#publicationTree.onNodeResolved(this.#handleNodeResolved); + this.#publicationTree.onBookmarkMoved(this.#handleBookmarkMoved); } // #region Proxied Public Methods @@ -48,6 +50,14 @@ export class SveltePublicationTree { this.#nodeResolvedObservers.push(observer); } + /** + * Registers an observer function that is invoked whenever the bookmark is moved. + * @param observer The observer function. + */ + onBookmarkMoved(observer: (address: string) => void) { + this.#bookmarkMovedObservers.push(observer); + } + // #endregion // #region Proxied Async Iterator Methods @@ -83,5 +93,19 @@ export class SveltePublicationTree { } } + /** + * Observer function that is invoked whenever the bookmark is moved on the publication tree. + * + * @param address The address of the new bookmark. + * + * This member is declared as an arrow function to ensure that the correct `this` context is + * used when the function is invoked in this class's constructor. + */ + #handleBookmarkMoved = (address: string) => { + for (const observer of this.#bookmarkMovedObservers) { + observer(address); + } + } + // #endregion } \ No newline at end of file From b8b9deb727afbb802323bf63ef4590c913ebc121 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:46:34 -0500 Subject: [PATCH 62/74] Don't invoke `goto` in `onSectionFocused` --- src/lib/components/publications/Publication.svelte | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index c5efd96..a9daf52 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -22,7 +22,6 @@ import Interactions from "$components/util/Interactions.svelte"; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import TableOfContents from "./TableOfContents.svelte"; - import { goto } from "$app/navigation"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string; @@ -174,12 +173,7 @@ displayMode='sidebar' rootAddress={rootAddress} depth={2} - onSectionFocused={(address: string) => { - publicationTree.setBookmark(address); - goto(`#${address}`, { - replaceState: true, - }); - }} + onSectionFocused={(address: string) => publicationTree.setBookmark(address)} /> {/if} From ddb56d07ecc893c97f8622edc6455e69eb648042 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:46:57 -0500 Subject: [PATCH 63/74] Clean up key and await in publication page --- src/routes/publication/+page.svelte | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 4348871..077e866 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -59,22 +59,16 @@
-{#key data} - + +
+ -{/key} - -
- {#await data.waitable} - - {:then} - - {/await}
From 458004bd9f744b9bedff1ec3e39497e67350ce93 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 28 Jun 2025 18:48:18 -0500 Subject: [PATCH 64/74] First pass at use of IndexedDB for publication bookmarks Scrolling to the bookmark doesn't yet work, so the IndexedDB usage is moot until that is fixed. IndexedDB access should be moved into a service layer before merging. --- src/routes/publication/+page.svelte | 54 +++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 077e866..5e92cbd 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -2,12 +2,14 @@ import Publication from "$lib/components/publications/Publication.svelte"; import { TextPlaceholder } from "flowbite-svelte"; import type { PageProps } from "./$types"; - import { onDestroy, setContext } from "svelte"; + import { onDestroy, onMount, setContext } from "svelte"; import Processor from "asciidoctor"; import ArticleNav from "$components/util/ArticleNav.svelte"; import { SveltePublicationTree } from "$lib/components/publications/svelte_publication_tree.svelte"; import { TableOfContents } from "$lib/components/publications/table_of_contents.svelte"; import { page } from "$app/state"; + import { goto } from "$app/navigation"; + let { data }: PageProps = $props(); const publicationTree = new SveltePublicationTree(data.indexEvent, data.ndk); @@ -23,7 +25,7 @@ data.parser?.getIndexTitle(data.parser?.getRootIndexId()) || "Alexandria Publication", ); - let currentUrl = data.url?.href ?? ""; + let currentUrl = $derived(`${page.url.origin}${page.url.pathname}${page.url.search}`); // Get image and summary from the event tags if available // If image unavailable, use the Alexandria default pic. @@ -36,6 +38,54 @@ "Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.", ); + publicationTree.onBookmarkMoved(address => { + goto(`#${address}`, { + replaceState: true, + }); + + // TODO: Extract IndexedDB interaction to a service layer. + // Store bookmark in IndexedDB + const db = indexedDB.open('alexandria', 1); + db.onupgradeneeded = () => { + const objectStore = db.result.createObjectStore('bookmarks', { keyPath: 'key' }); + }; + + db.onsuccess = () => { + const transaction = db.result.transaction(['bookmarks'], 'readwrite'); + const store = transaction.objectStore('bookmarks'); + const bookmarkKey = `${data.indexEvent.tagAddress()}`; + store.put({ key: bookmarkKey, address }); + }; + }); + + onMount(() => { + // TODO: Extract IndexedDB interaction to a service layer. + // Read bookmark from IndexedDB + const db = indexedDB.open('alexandria', 1); + db.onupgradeneeded = () => { + const objectStore = db.result.createObjectStore('bookmarks', { keyPath: 'key' }); + }; + + db.onsuccess = () => { + const transaction = db.result.transaction(['bookmarks'], 'readonly'); + const store = transaction.objectStore('bookmarks'); + const bookmarkKey = `${data.indexEvent.tagAddress()}`; + const request = store.get(bookmarkKey); + + request.onsuccess = () => { + if (request.result?.address) { + // Set the bookmark in the publication tree + publicationTree.setBookmark(request.result.address); + + // Jump to the bookmarked element + goto(`#${request.result.address}`, { + replaceState: true, + }); + } + }; + }; + }); + onDestroy(() => data.parser.reset()); From 0fc90e5cebd0759219d294a096b4ff2287711b1e Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 30 Jun 2025 09:23:39 -0500 Subject: [PATCH 65/74] Use ToC hrefs for proper navigation --- src/lib/components/publications/Publication.svelte | 5 +++-- src/lib/components/publications/TableOfContents.svelte | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index a9daf52..f183806 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -167,8 +167,9 @@ {#if publicationType !== 'blog' || !isLeaf} {#if $publicationColumnVisibility.toc} - - + - {#if displayMode === 'accordion'} @@ -80,9 +79,9 @@ {@const expanded = toc.expandedMap.get(address) ?? false} {@const isLeaf = toc.leaves.has(address)} {#if isLeaf} - onSectionFocused?.(address)} /> {:else} From b492a33cc7b92608a66c6457350c096dd6e0db71 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 30 Jun 2025 09:23:49 -0500 Subject: [PATCH 66/74] Fit ToC styling to theme --- src/app.css | 14 -------------- src/lib/components/publications/Publication.svelte | 5 +++++ .../components/publications/TableOfContents.svelte | 4 ++++ src/lib/components/util/ArticleNav.svelte | 9 +++++++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app.css b/src/app.css index 21e1a48..572628b 100644 --- a/src/app.css +++ b/src/app.css @@ -154,20 +154,6 @@ @apply text-gray-800 hover:text-primary-400 dark:text-gray-300 dark:hover:text-primary-500; } - /* Sidebar */ - aside.sidebar-leather { - @apply fixed md:sticky top-[130px] sm:top-[146px] h-[calc(100vh-130px)] sm:h-[calc(100vh-146px)] z-10; - @apply bg-primary-0 dark:bg-primary-1000 px-5 w-full sm:w-auto sm:max-w-xl; - } - - aside.sidebar-leather > div { - @apply bg-primary-50 dark:bg-gray-800 h-full px-5 py-0; - } - - a.sidebar-item-leather { - @apply hover:bg-primary-100 dark:hover:bg-gray-800; - } - div.skeleton-leather div { @apply bg-primary-100 dark:bg-primary-800; } diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index f183806..eff97b0 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -37,6 +37,7 @@ let isLoading = $state(false); let isDone = $state(false); let lastElementRef = $state(null); + let activeAddress = $state(null); let observer: IntersectionObserver; @@ -169,7 +170,11 @@ {#if $publicationColumnVisibility.toc} + {:else} + + {#each entries as entry} {@const address = entry.address} @@ -82,12 +84,14 @@ onSectionFocused?.(address)} /> {:else} {@const childDepth = depth + 1} expanded, (open) => setEntryExpanded(address, open) diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 916a298..24d2137 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -139,8 +139,13 @@ {/if} {/if}
-
-

{title}by {@render userBadge(pubkey, author)}

+
+

+ {title} +

+

+ by {@render userBadge(pubkey, author)} +

{#if $publicationColumnVisibility.inner} From 6a0a35f091f2e1618b80f9a10d8d10a7b09ca309 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 1 Jul 2025 08:06:29 -0500 Subject: [PATCH 67/74] Add border styling to ToC sidebar --- src/lib/components/publications/Publication.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index eff97b0..3bfdace 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -170,7 +170,7 @@ {#if $publicationColumnVisibility.toc} From eae495fa7776bd74a8f5b221078fcd34dbe65f73 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 1 Jul 2025 08:41:27 -0500 Subject: [PATCH 68/74] Ensure correct sorting of children of tree nodes --- .../publications/table_of_contents.svelte.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 95fa0e2..afe8920 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -181,7 +181,7 @@ export class TableOfContents { return; } - const childAddresses = await this.#publicationTree.getChildAddresses(address); + const childAddresses = await this.#publicationTree.getChildAddresses(entry.address); for (const childAddress of childAddresses) { if (!childAddress) { continue; @@ -205,6 +205,8 @@ export class TableOfContents { this.addressMap.set(childAddress, childEntry); } + await this.#matchChildrenToTagOrder(entry); + entry.childrenResolved = true; } @@ -237,6 +239,31 @@ export class TableOfContents { return entry; } + /** + * Reorders the children of a ToC entry to match the order of 'a' tags in the corresponding + * Nostr index event. + * + * @param entry The ToC entry to reorder. + */ + async #matchChildrenToTagOrder(entry: TocEntry) { + const parentEvent = await this.#publicationTree.getEvent(entry.address); + if (parentEvent?.kind === indexKind) { + const tagOrder = parentEvent.getMatchingTags('a').map(tag => tag[1]); + const addressToOrdinal = new Map(); + + // Build map of addresses to their ordinals from tag order + tagOrder.forEach((address, index) => { + addressToOrdinal.set(address, index); + }); + + entry.children.sort((a, b) => { + const aOrdinal = addressToOrdinal.get(a.address) ?? Number.MAX_SAFE_INTEGER; + const bOrdinal = addressToOrdinal.get(b.address) ?? Number.MAX_SAFE_INTEGER; + return aOrdinal - bOrdinal; + }); + } + } + #buildTocEntryFromResolvedNode(address: string) { if (this.addressMap.has(address)) { return; From f0d35e0f29184c1c1b7f9e6f4d76a5e7367b558c Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 7 Jul 2025 09:04:16 -0500 Subject: [PATCH 69/74] Add time complexity notes on child sort function --- src/lib/components/publications/table_of_contents.svelte.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index afe8920..118a4ec 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -244,6 +244,9 @@ export class TableOfContents { * Nostr index event. * * @param entry The ToC entry to reorder. + * + * This function has a time complexity of `O(n log n)`, where `n` is the number of children the + * parent event has. Average size of `n` is small enough to be negligible. */ async #matchChildrenToTagOrder(entry: TocEntry) { const parentEvent = await this.#publicationTree.getEvent(entry.address); From ff8cfd39add1186bcc6d657952b6fda36af981bd Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 7 Jul 2025 09:18:39 -0500 Subject: [PATCH 70/74] Remove unused function --- src/lib/components/publications/table_of_contents.svelte.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 118a4ec..532c0a7 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -164,11 +164,6 @@ export class TableOfContents { return titleTag || event.tagAddress() || '[untitled]'; } - #normalizeHashPath(title: string): string { - // TODO: Confirm this uses good normalization logic to produce unique hrefs within the page. - return title.toLowerCase().replace(/ /g, '-'); - } - async #buildTocEntry(address: string): Promise { const resolver = async () => { if (entry.childrenResolved) { From d62e2fef5fb86cdb4fc41ec26077dd603ff153fe Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Mon, 7 Jul 2025 09:39:00 -0500 Subject: [PATCH 71/74] Add contextual and todo comments --- .../publications/table_of_contents.svelte.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 532c0a7..aaf3ac3 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -133,6 +133,18 @@ export class TableOfContents { // #region Private Methods + /** + * Initializes the ToC from the associated publication tree. + * + * @param rootAddress The address of the publication's root event. + * + * Michael J - 07 July 2025 - NOTE: Since the publication tree is conceptually infinite and + * lazy-loading, the ToC is not guaranteed to contain all the nodes at any layer until the + * publication has been fully resolved. + * + * Michael J - 07 July 2025 - TODO: If the relay provides event metadata, use the metadata to + * initialize the ToC with all of its first-level children. + */ async #init(rootAddress: string) { const rootEvent = await this.#publicationTree.getEvent(rootAddress); if (!rootEvent) { @@ -165,6 +177,9 @@ export class TableOfContents { } async #buildTocEntry(address: string): Promise { + // Michael J - 07 July 2025 - NOTE: This arrow function is nested so as to use its containing + // scope in its operation. Do not move it to the top level without ensuring it still has access + // to the necessary variables. const resolver = async () => { if (entry.childrenResolved) { return; From 70e7674a774a9384935c6da5e38d04e4221faee9 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 8 Jul 2025 09:18:11 -0500 Subject: [PATCH 72/74] Parallelize ToC initialization step --- .../publications/table_of_contents.svelte.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index aaf3ac3..6c07f66 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -155,11 +155,12 @@ export class TableOfContents { this.addressMap.set(rootAddress, this.#root); - // TODO: Parallelize this. - // Handle any other nodes that have already been resolved. - this.#publicationTree.resolvedAddresses.forEach((address) => { - this.#buildTocEntryFromResolvedNode(address); - }); + // Handle any other nodes that have already been resolved in parallel. + await Promise.all( + Array.from(this.#publicationTree.resolvedAddresses).map((address) => + this.#buildTocEntryFromResolvedNode(address) + ) + ); // Set up an observer to handle progressive resolution of the publication tree. this.#publicationTree.onNodeResolved((address: string) => { From 43d77d0f0477af975438120f3f84778d49cc8490 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 8 Jul 2025 09:18:32 -0500 Subject: [PATCH 73/74] Remove accordion display mode from ToC component --- .../publications/TableOfContents.svelte | 93 ++++++------------- 1 file changed, 30 insertions(+), 63 deletions(-) diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 3c0b8f1..08097ed 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -4,17 +4,13 @@ type TocEntry } from '$lib/components/publications/table_of_contents.svelte'; import { getContext } from 'svelte'; - import { Accordion, AccordionItem, SidebarDropdownWrapper, SidebarGroup, SidebarItem } from 'flowbite-svelte'; + import { SidebarDropdownWrapper, SidebarGroup, SidebarItem } from 'flowbite-svelte'; import Self from './TableOfContents.svelte'; - export type TocDisplayMode = 'accordion' | 'sidebar'; - let { - displayMode = 'accordion', depth, onSectionFocused, - } = $props<{ - displayMode?: TocDisplayMode; + } = $props<{ rootAddress: string; depth: number; onSectionFocused?: (address: string) => void; @@ -46,65 +42,36 @@ } - -{#if displayMode === 'accordion'} - - {#each entries as entry} - {@const address = entry.address} - {@const expanded = toc.expandedMap.get(address) ?? false} - + + + {#each entries as entry} + {@const address = entry.address} + {@const expanded = toc.expandedMap.get(address) ?? false} + {@const isLeaf = toc.leaves.has(address)} + {#if isLeaf} + onSectionFocused?.(address)} + /> + {:else} + {@const childDepth = depth + 1} + expanded, (open) => setEntryExpanded(address, open) } > - {#snippet header()} - {entry.title} - {/snippet} - {#if entry.children.length > 0} - - {/if} - - {/each} - -{:else} - - - - {#each entries as entry} - {@const address = entry.address} - {@const expanded = toc.expandedMap.get(address) ?? false} - {@const isLeaf = toc.leaves.has(address)} - {#if isLeaf} - onSectionFocused?.(address)} + - {:else} - {@const childDepth = depth + 1} - expanded, - (open) => setEntryExpanded(address, open) - } - > - - - {/if} - {/each} - -{/if} + + {/if} + {/each} + From e714ac26317cbd6c5d597b33ddf8ff8ab228bcb1 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Tue, 8 Jul 2025 09:23:05 -0500 Subject: [PATCH 74/74] Pass publication section DOM into ToC class --- .../publications/Publication.svelte | 30 +++++++++++++++++-- .../publications/table_of_contents.svelte.ts | 8 ++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index 3bfdace..9a0dd70 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -22,6 +22,7 @@ import Interactions from "$components/util/Interactions.svelte"; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import TableOfContents from "./TableOfContents.svelte"; + import type { TableOfContents as TocType } from "./table_of_contents.svelte"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string; @@ -30,6 +31,7 @@ }>(); const publicationTree = getContext("publicationTree") as SveltePublicationTree; + const toc = getContext("toc") as TocType; // #region Loading @@ -125,6 +127,29 @@ // #endregion + /** + * Performs actions on the DOM element for a publication tree leaf when it is mounted. + * + * @param el The DOM element that was mounted. + * @param address The address of the event that was mounted. + */ + function onPublicationSectionMounted(el: HTMLElement, address: string) { + // Update last element ref for the intersection observer. + setLastElementRef(el, leaves.length); + + // Michael J - 08 July 2025 - NOTE: Updating the ToC from here somewhat breaks separation of + // concerns, since the TableOfContents component is primarily responsible for working with the + // ToC data structure. However, the Publication component has direct access to the needed DOM + // element already, and I want to avoid complicated callbacks between the two components. + // Update the ToC from the contents of the leaf section. + const entry = toc.getEntry(address); + if (!entry) { + console.warn(`[Publication] No parent found for ${address}`); + return; + } + toc.buildTocFromDocument(el, entry); + } + // #region Lifecycle hooks onDestroy(() => { @@ -201,11 +226,12 @@ Error loading content. One or more events could not be loaded. {:else} + {@const address = leaf.tagAddress()} setLastElementRef(el, i)} + {address} + ref={(el) => onPublicationSectionMounted(el, address)} /> {/if} {/each} diff --git a/src/lib/components/publications/table_of_contents.svelte.ts b/src/lib/components/publications/table_of_contents.svelte.ts index 6c07f66..0e4e310 100644 --- a/src/lib/components/publications/table_of_contents.svelte.ts +++ b/src/lib/components/publications/table_of_contents.svelte.ts @@ -74,11 +74,11 @@ export class TableOfContents { buildTocFromDocument( parentElement: HTMLElement, parentEntry: TocEntry, - depth: number = 1 ) { parentElement - .querySelectorAll(`h${depth}`) + .querySelectorAll(`h${parentEntry.depth}`) .forEach((header) => { + // TODO: Correctly update ToC state from DOM. const title = header.textContent?.trim(); const id = header.id; @@ -91,7 +91,7 @@ export class TableOfContents { address: parentEntry.address, title, href, - depth, + depth: parentEntry.depth + 1, children: [], childrenResolved: true, resolveChildren: () => Promise.resolve(), @@ -99,7 +99,7 @@ export class TableOfContents { parentEntry.children.push(tocEntry); this.expandedMap.set(tocEntry.address, false); - this.buildTocFromDocument(header, tocEntry, depth + 1); + this.buildTocFromDocument(header, tocEntry); } }); }