Browse Source

Made ToC auto-expanding

master
silberengel 8 months ago
parent
commit
bd6d96ed62
  1. 7
      src/lib/components/publications/Publication.svelte
  2. 20
      src/lib/components/publications/TableOfContents.svelte
  3. 45
      src/lib/data_structures/publication_tree.ts

7
src/lib/components/publications/Publication.svelte

@ -182,7 +182,7 @@
}, },
{ threshold: 0.5 }, { threshold: 0.5 },
); );
loadMore(8); loadMore(12);
return () => { return () => {
observer.disconnect(); observer.disconnect();
@ -210,6 +210,11 @@
depth={2} depth={2}
onSectionFocused={(address: string) => onSectionFocused={(address: string) =>
publicationTree.setBookmark(address)} publicationTree.setBookmark(address)}
onLoadMore={() => {
if (!isLoading && !isDone) {
loadMore(4);
}
}}
/> />
</Sidebar> </Sidebar>
{/if} {/if}

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

@ -12,10 +12,11 @@
import Self from "./TableOfContents.svelte"; import Self from "./TableOfContents.svelte";
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
let { depth, onSectionFocused } = $props<{ let { depth, onSectionFocused, onLoadMore } = $props<{
rootAddress: string; rootAddress: string;
depth: number; depth: number;
onSectionFocused?: (address: string) => void; onSectionFocused?: (address: string) => void;
onLoadMore?: () => void;
}>(); }>();
let toc = getContext("toc") as TableOfContents; let toc = getContext("toc") as TableOfContents;
@ -58,6 +59,14 @@
} }
onSectionFocused?.(address); onSectionFocused?.(address);
// Check if this is the last entry and trigger loading more events
const currentEntries = entries;
const lastEntry = currentEntries[currentEntries.length - 1];
if (lastEntry && lastEntry.address === address) {
console.debug('[TableOfContents] Last entry clicked, triggering load more');
onLoadMore?.();
}
} }
// Check if an entry is currently visible // Check if an entry is currently visible
@ -145,27 +154,28 @@
<!-- TODO: Figure out how to style indentations. --> <!-- TODO: Figure out how to style indentations. -->
<!-- TODO: Make group title fonts the same as entry title fonts. --> <!-- TODO: Make group title fonts the same as entry title fonts. -->
<SidebarGroup> <SidebarGroup>
{#each entries as entry} {#each entries as entry, index}
{@const address = entry.address} {@const address = entry.address}
{@const expanded = toc.expandedMap.get(address) ?? false} {@const expanded = toc.expandedMap.get(address) ?? false}
{@const isLeaf = toc.leaves.has(address)} {@const isLeaf = toc.leaves.has(address)}
{@const isVisible = isEntryVisible(address)} {@const isVisible = isEntryVisible(address)}
{@const isLastEntry = index === entries.length - 1}
{#if isLeaf} {#if isLeaf}
<SidebarItem <SidebarItem
label={entry.title} label={entry.title}
href={`#${address}`} href={`#${address}`}
spanClass="px-2 text-ellipsis" spanClass="px-2 text-ellipsis"
class={isVisible ? "toc-highlight" : ""} class={`${isVisible ? "toc-highlight" : ""} ${isLastEntry ? "pb-4" : ""}`}
onclick={() => handleSectionClick(address)} onclick={() => handleSectionClick(address)}
/> />
{:else} {:else}
{@const childDepth = depth + 1} {@const childDepth = depth + 1}
<SidebarDropdownWrapper <SidebarDropdownWrapper
label={entry.title} label={entry.title}
btnClass="flex items-center p-2 w-full font-normal text-gray-900 rounded-lg transition duration-75 group hover:bg-primary-50 dark:text-white dark:hover:bg-primary-800 {isVisible ? 'toc-highlight' : ''}" btnClass="flex items-center p-2 w-full font-normal text-gray-900 rounded-lg transition duration-75 group hover:bg-primary-50 dark:text-white dark:hover:bg-primary-800 {isVisible ? 'toc-highlight' : ''} {isLastEntry ? 'pb-4' : ''}"
bind:isOpen={() => expanded, (open) => setEntryExpanded(address, open)} bind:isOpen={() => expanded, (open) => setEntryExpanded(address, open)}
> >
<Self rootAddress={address} depth={childDepth} {onSectionFocused} /> <Self rootAddress={address} depth={childDepth} {onSectionFocused} {onLoadMore} />
</SidebarDropdownWrapper> </SidebarDropdownWrapper>
{/if} {/if}
{/each} {/each}

45
src/lib/data_structures/publication_tree.ts

@ -46,6 +46,11 @@ export class PublicationTree implements AsyncIterable<NDKEvent | null> {
*/ */
#events: Map<string, NDKEvent>; #events: Map<string, NDKEvent>;
/**
* Simple cache for fetched events to avoid re-fetching.
*/
#eventCache: Map<string, NDKEvent> = new Map();
/** /**
* An ordered list of the addresses of the leaves of the tree. * An ordered list of the addresses of the leaves of the tree.
*/ */
@ -589,13 +594,11 @@ export class PublicationTree implements AsyncIterable<NDKEvent | null> {
} }
// Augment the tree with the children of the current event. // Augment the tree with the children of the current event.
for (const childAddress of currentChildAddresses) { const childPromises = currentChildAddresses
if (this.#nodes.has(childAddress)) { .filter(childAddress => !this.#nodes.has(childAddress))
continue; .map(childAddress => this.#addNode(childAddress, currentNode!));
}
await this.#addNode(childAddress, currentNode!); await Promise.all(childPromises);
}
// Push the popped address's children onto the stack for the next iteration. // Push the popped address's children onto the stack for the next iteration.
while (currentChildAddresses.length > 0) { while (currentChildAddresses.length > 0) {
@ -630,12 +633,23 @@ export class PublicationTree implements AsyncIterable<NDKEvent | null> {
address: string, address: string,
parentNode: PublicationTreeNode, parentNode: PublicationTreeNode,
): Promise<PublicationTreeNode> { ): Promise<PublicationTreeNode> {
const [kind, pubkey, dTag] = address.split(":"); // Check cache first
const event = await this.#ndk.fetchEvent({ let event = this.#eventCache.get(address);
kinds: [parseInt(kind)],
authors: [pubkey], if (!event) {
"#d": [dTag], const [kind, pubkey, dTag] = address.split(":");
}); const fetchedEvent = await this.#ndk.fetchEvent({
kinds: [parseInt(kind)],
authors: [pubkey],
"#d": [dTag],
});
// Cache the event if found
if (fetchedEvent) {
this.#eventCache.set(address, fetchedEvent);
event = fetchedEvent;
}
}
if (!event) { if (!event) {
console.debug( console.debug(
@ -665,9 +679,10 @@ export class PublicationTree implements AsyncIterable<NDKEvent | null> {
children: [], children: [],
}; };
for (const address of childAddresses) { const childPromises = childAddresses.map(address =>
this.addEventByAddress(address, event); this.addEventByAddress(address, event)
} );
await Promise.all(childPromises);
this.#nodeResolvedObservers.forEach((observer) => observer(address)); this.#nodeResolvedObservers.forEach((observer) => observer(address));

Loading…
Cancel
Save