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: [],