diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index 58f29db..1995b67 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -1,5 +1,6 @@ + + + {#if showTocButton && !showToc}
{#each leaves as leaf, i} - setLastElementRef(el, i)} - /> + {#if leaf == null} + + + Error loading content. One or more events could not be loaded. + + {:else} + setLastElementRef(el, i)} + /> + {/if} {/each}
diff --git a/src/lib/components/PublicationSection.svelte b/src/lib/components/PublicationSection.svelte index 5eb4f24..6b96660 100644 --- a/src/lib/components/PublicationSection.svelte +++ b/src/lib/components/PublicationSection.svelte @@ -23,24 +23,38 @@ let leafEvent: Promise = $derived.by(async () => await publicationTree.getEvent(address)); + let rootEvent: Promise = $derived.by(async () => await publicationTree.getEvent(rootAddress)); + let publicationType: Promise = $derived.by(async () => (await rootEvent)?.getMatchingTags('type')[0]?.[1]); + let leafHierarchy: Promise = $derived.by(async () => await publicationTree.getHierarchy(address)); + let leafTitle: Promise = $derived.by(async () => (await leafEvent)?.getMatchingTags('title')[0]?.[1]); + let leafContent: Promise = $derived.by(async () => asciidoctor.convert((await leafEvent)?.content ?? '')); let previousLeafEvent: NDKEvent | null = $derived.by(() => { - const index = leaves.findIndex(leaf => leaf.tagAddress() === address); - if (index === 0) { - return null; - } - return leaves[index - 1]; + let index: number; + let event: NDKEvent | null = null; + let decrement = 1; + + do { + index = leaves.findIndex(leaf => leaf?.tagAddress() === address); + if (index === 0) { + return null; + } + event = leaves[index - decrement++]; + } while (event == null && index - decrement >= 0); + + return event; }); + let previousLeafHierarchy: Promise = $derived.by(async () => { if (!previousLeafEvent) { return null; diff --git a/src/lib/data_structures/lazy.ts b/src/lib/data_structures/lazy.ts index 6be32fb..1589cba 100644 --- a/src/lib/data_structures/lazy.ts +++ b/src/lib/data_structures/lazy.ts @@ -1,16 +1,32 @@ +export enum LazyStatus { + Pending, + Resolved, + Error, +} + export class Lazy { #value?: T; #resolver: () => Promise; + status: LazyStatus; + constructor(resolver: () => Promise) { this.#resolver = resolver; + this.status = LazyStatus.Pending; } - async value(): Promise { + async value(): Promise { if (!this.#value) { - this.#value = await this.#resolver(); + try { + this.#value = await this.#resolver(); + } catch (error) { + this.status = LazyStatus.Error; + console.error(error); + return null; + } } + this.status = LazyStatus.Resolved; return this.#value; } } \ 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 466e676..d703555 100644 --- a/src/lib/data_structures/publication_tree.ts +++ b/src/lib/data_structures/publication_tree.ts @@ -8,14 +8,20 @@ enum PublicationTreeNodeType { Leaf, } +enum PublicationTreeNodeStatus { + Resolved, + Error, +} + interface PublicationTreeNode { type: PublicationTreeNodeType; + status: PublicationTreeNodeStatus; address: string; parent?: PublicationTreeNode; children?: Array>; } -export class PublicationTree implements AsyncIterable { +export class PublicationTree implements AsyncIterable { /** * The root node of the tree. */ @@ -50,6 +56,7 @@ export class PublicationTree implements AsyncIterable { const rootAddress = rootEvent.tagAddress(); this.#root = { type: this.#getNodeType(rootEvent), + status: PublicationTreeNodeStatus.Resolved, address: rootAddress, children: [], }; @@ -84,6 +91,7 @@ export class PublicationTree implements AsyncIterable { const node: PublicationTreeNode = { type: await this.#getNodeType(event), + status: PublicationTreeNodeStatus.Resolved, address, parent: parentNode, children: [], @@ -134,7 +142,7 @@ export class PublicationTree implements AsyncIterable { * @param address The address of the parent node. * @returns An array of addresses of any loaded child nodes. */ - async getChildAddresses(address: string): Promise { + async getChildAddresses(address: string): Promise> { const node = await this.#nodes.get(address)?.value(); if (!node) { throw new Error(`PublicationTree: Node with address ${address} not found.`); @@ -142,7 +150,7 @@ export class PublicationTree implements AsyncIterable { return Promise.all( node.children?.map(async child => - (await child.value()).address + (await child.value())?.address ?? null ) ?? [] ); } @@ -205,20 +213,26 @@ export class PublicationTree implements AsyncIterable { async tryMoveToFirstChild(): Promise { if (!this.target) { - throw new Error("Cursor: Target node is null or undefined."); + console.debug("Cursor: Target node is null or undefined."); + return false; } if (this.target.type === PublicationTreeNodeType.Leaf) { return false; } + + if (this.target.children == null || this.target.children.length === 0) { + return false; + } - this.target = (await this.target.children?.at(0)?.value())!; + this.target = await this.target.children?.at(0)?.value(); return true; } async tryMoveToNextSibling(): Promise { if (!this.target) { - throw new Error("Cursor: Target node is null or undefined."); + console.debug("Cursor: Target node is null or undefined."); + return false; } const parent = this.target.parent; @@ -228,25 +242,27 @@ export class PublicationTree implements AsyncIterable { } const currentIndex = await siblings.findIndexAsync( - async (sibling: Lazy) => (await sibling.value()).address === this.target!.address + async (sibling: Lazy) => (await sibling.value())?.address === this.target!.address ); if (currentIndex === -1) { return false; } - const nextSibling = (await siblings.at(currentIndex + 1)?.value()) ?? null; - if (!nextSibling) { + if (currentIndex + 1 >= siblings.length) { return false; } + const nextSibling = (await siblings.at(currentIndex + 1)?.value()); this.target = nextSibling; + return true; } tryMoveToParent(): boolean { if (!this.target) { - throw new Error("Cursor: Target node is null or undefined."); + console.debug("Cursor: Target node is null or undefined."); + return false; } const parent = this.target.parent; @@ -263,11 +279,11 @@ export class PublicationTree implements AsyncIterable { // #region Async Iterator Implementation - [Symbol.asyncIterator](): AsyncIterator { + [Symbol.asyncIterator](): AsyncIterator { return this; } - async next(): Promise> { + async next(): Promise> { if (!this.#cursor.target) { await this.#cursor.tryMoveTo(this.#bookmark); } @@ -297,7 +313,7 @@ export class PublicationTree implements AsyncIterable { } while (this.#cursor.target?.type !== PublicationTreeNodeType.Leaf); const event = await this.getEvent(this.#cursor.target!.address); - return { done: false, value: event! }; + return { done: false, value: event }; } // #endregion @@ -396,9 +412,17 @@ export class PublicationTree implements AsyncIterable { }); if (!event) { - throw new Error( + console.debug( `PublicationTree: Event with address ${address} not found.` ); + + return { + type: PublicationTreeNodeType.Leaf, + status: PublicationTreeNodeStatus.Error, + address, + parent: parentNode, + children: [], + }; } this.#events.set(address, event); @@ -406,7 +430,8 @@ export class PublicationTree implements AsyncIterable { const childAddresses = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1]); const node: PublicationTreeNode = { - type: await this.#getNodeType(event), + type: this.#getNodeType(event), + status: PublicationTreeNodeStatus.Resolved, address, parent: parentNode, children: [],