From 45647620054435b1b1aacb83267ec74c0e7e88e4 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 9 Jul 2025 18:23:27 +0200 Subject: [PATCH] Accelerated 30040 loading for the search bar on the landing page --- src/lib/components/PublicationFeed.svelte | 285 +++++++++------------- src/routes/+page.svelte | 36 ++- 2 files changed, 142 insertions(+), 179 deletions(-) diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/PublicationFeed.svelte index 9ea5096..514db6a 100644 --- a/src/lib/components/PublicationFeed.svelte +++ b/src/lib/components/PublicationFeed.svelte @@ -19,182 +19,141 @@ eventsInView?.at(eventsInView.length - 1)?.created_at ?? new Date().getTime() ); - // Debounced search function - const debouncedSearch = debounce(async (query: string) => { - console.debug('[PublicationFeed] Search query changed:', query); - if (query.trim()) { - console.debug('[PublicationFeed] Clearing events and searching with query:', query); - eventsInView = []; - await getEvents(undefined, query, true); - } else { - console.debug('[PublicationFeed] Clearing events and resetting search'); - eventsInView = []; - await getEvents(undefined, '', true); - } - }, 300); - - $effect(() => { - console.debug('[PublicationFeed] Search query effect triggered:', searchQuery); - debouncedSearch(searchQuery); - }); + let allIndexEvents: NDKEvent[] = $state([]); - async function getEvents(before: number | undefined = undefined, search: string = '', reset: boolean = false) { + async function fetchAllIndexEventsFromRelays() { loading = true; const ndk = $ndkInstance; const primaryRelays: string[] = relays; const fallback: string[] = fallbackRelays.filter((r: string) => !primaryRelays.includes(r)); - relayStatuses = Object.fromEntries(primaryRelays.map((r: string) => [r, 'pending'])); + const allRelays = [...primaryRelays, ...fallback]; + relayStatuses = Object.fromEntries(allRelays.map((r: string) => [r, 'pending'])); let allEvents: NDKEvent[] = []; - let fetchedCount = 0; // Track number of new events - - console.debug('[getEvents] Called with before:', before, 'search:', search); - - // Function to filter events based on search query - const filterEventsBySearch = (events: NDKEvent[]) => { - if (!search) return events; - const query = search.toLowerCase(); - console.debug('[PublicationFeed] Filtering events with query:', query, 'Total events before filter:', events.length); - - // Check if the query is a NIP-05 address - const isNip05Query = /^[a-z0-9._-]+@[a-z0-9.-]+$/i.test(query); - console.debug('[PublicationFeed] Is NIP-05 query:', isNip05Query); - - const filtered = events.filter(event => { - const title = getMatchingTags(event, 'title')[0]?.[1]?.toLowerCase() ?? ''; - const authorName = getMatchingTags(event, 'author')[0]?.[1]?.toLowerCase() ?? ''; - const authorPubkey = event.pubkey.toLowerCase(); - const nip05 = getMatchingTags(event, 'nip05')[0]?.[1]?.toLowerCase() ?? ''; - - // For NIP-05 queries, only match against NIP-05 tags - if (isNip05Query) { - const matches = nip05 === query; - if (matches) { - console.debug('[PublicationFeed] Event matches NIP-05 search:', { - id: event.id, - nip05, - authorPubkey - }); - } - return matches; - } - // For regular queries, match against all fields - const matches = ( - title.includes(query) || - authorName.includes(query) || - authorPubkey.includes(query) || - nip05.includes(query) - ); + // Helper to fetch from a single relay with timeout + async function fetchFromRelay(relay: string): Promise { + try { + const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], ndk); + let eventSet = await ndk.fetchEvents( + { + kinds: [indexKind], + }, + { + groupable: false, + skipVerification: false, + skipValidation: false, + }, + relaySet + ).withTimeout(5000); + eventSet = filterValidIndexEvents(eventSet); + relayStatuses = { ...relayStatuses, [relay]: 'found' }; + return Array.from(eventSet); + } catch (err) { + console.error(`Error fetching from relay ${relay}:`, err); + relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; + return []; + } + } + + // Fetch from all relays in parallel, do not block on any single relay + const results = await Promise.allSettled( + allRelays.map(fetchFromRelay) + ); + for (const result of results) { + if (result.status === 'fulfilled') { + allEvents = allEvents.concat(result.value); + } + } + // Deduplicate by tagAddress + const eventMap = new Map(allEvents.map(event => [event.tagAddress(), event])); + allIndexEvents = Array.from(eventMap.values()); + // Sort by created_at descending + allIndexEvents.sort((a, b) => b.created_at! - a.created_at!); + // Initially show first page + eventsInView = allIndexEvents.slice(0, 30); + endOfFeed = allIndexEvents.length <= 30; + loading = false; + } + + // Function to filter events based on search query + const filterEventsBySearch = (events: NDKEvent[]) => { + if (!searchQuery) return events; + const query = searchQuery.toLowerCase(); + console.debug('[PublicationFeed] Filtering events with query:', query, 'Total events before filter:', events.length); + + // Check if the query is a NIP-05 address + const isNip05Query = /^[a-z0-9._-]+@[a-z0-9.-]+$/i.test(query); + console.debug('[PublicationFeed] Is NIP-05 query:', isNip05Query); + + const filtered = events.filter(event => { + const title = getMatchingTags(event, 'title')[0]?.[1]?.toLowerCase() ?? ''; + const authorName = getMatchingTags(event, 'author')[0]?.[1]?.toLowerCase() ?? ''; + const authorPubkey = event.pubkey.toLowerCase(); + const nip05 = getMatchingTags(event, 'nip05')[0]?.[1]?.toLowerCase() ?? ''; + + // For NIP-05 queries, only match against NIP-05 tags + if (isNip05Query) { + const matches = nip05 === query; if (matches) { - console.debug('[PublicationFeed] Event matches search:', { + console.debug('[PublicationFeed] Event matches NIP-05 search:', { id: event.id, - title, - authorName, - authorPubkey, - nip05 + nip05, + authorPubkey }); } return matches; - }); - console.debug('[PublicationFeed] Events after filtering:', filtered.length); - return filtered; - }; - - // First, try primary relays - let foundEventsInPrimary = false; - await Promise.all( - primaryRelays.map(async (relay: string) => { - try { - const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], ndk); - let eventSet = await ndk.fetchEvents( - { - kinds: [indexKind], - limit: 30, - until: before, - }, - { - groupable: false, - skipVerification: false, - skipValidation: false, - }, - relaySet - ).withTimeout(2500); - eventSet = filterValidIndexEvents(eventSet); - const eventArray = filterEventsBySearch(Array.from(eventSet)); - fetchedCount += eventArray.length; // Count new events - if (eventArray.length > 0) { - allEvents = allEvents.concat(eventArray); - relayStatuses = { ...relayStatuses, [relay]: 'found' }; - foundEventsInPrimary = true; - } else { - relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; - } - console.debug(`[getEvents] Fetched ${eventArray.length} events from relay: ${relay} (search: "${search}")`); - } catch (err) { - console.error(`Error fetching from primary relay ${relay}:`, err); - relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; - } - }) - ); - - // Only try fallback relays if no events were found in primary relays - if (!foundEventsInPrimary && fallback.length > 0) { - console.debug('[getEvents] No events found in primary relays, trying fallback relays'); - relayStatuses = { ...relayStatuses, ...Object.fromEntries(fallback.map((r: string) => [r, 'pending'])) }; - await Promise.all( - fallback.map(async (relay: string) => { - try { - const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], ndk); - let eventSet = await ndk.fetchEvents( - { - kinds: [indexKind], - limit: 18, - until: before, - }, - { - groupable: false, - skipVerification: false, - skipValidation: false, - }, - relaySet - ).withTimeout(2500); - eventSet = filterValidIndexEvents(eventSet); - const eventArray = filterEventsBySearch(Array.from(eventSet)); - fetchedCount += eventArray.length; // Count new events - if (eventArray.length > 0) { - allEvents = allEvents.concat(eventArray); - relayStatuses = { ...relayStatuses, [relay]: 'found' }; - } else { - relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; - } - console.debug(`[getEvents] Fetched ${eventArray.length} events from relay: ${relay} (search: "${search}")`); - } catch (err) { - console.error(`Error fetching from fallback relay ${relay}:`, err); - relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; - } - }) + } + + // For regular queries, match against all fields + const matches = ( + title.includes(query) || + authorName.includes(query) || + authorPubkey.includes(query) || + nip05.includes(query) ); - } - // Deduplicate and sort - const eventMap = reset - ? new Map(allEvents.map(event => [event.tagAddress(), event])) - : new Map([...eventsInView, ...allEvents].map(event => [event.tagAddress(), event])); - const uniqueEvents = Array.from(eventMap.values()); - uniqueEvents.sort((a, b) => b.created_at! - a.created_at!); - eventsInView = uniqueEvents; - const pageSize = fallback.length > 0 ? 18 : 30; - if (fetchedCount < pageSize) { - endOfFeed = true; + if (matches) { + console.debug('[PublicationFeed] Event matches search:', { + id: event.id, + title, + authorName, + authorPubkey, + nip05 + }); + } + return matches; + }); + console.debug('[PublicationFeed] Events after filtering:', filtered.length); + return filtered; + }; + + // Debounced search function + const debouncedSearch = debounce(async (query: string) => { + console.debug('[PublicationFeed] Search query changed:', query); + if (query.trim()) { + const filtered = filterEventsBySearch(allIndexEvents); + eventsInView = filtered.slice(0, 30); + endOfFeed = filtered.length <= 30; } else { - endOfFeed = false; + eventsInView = allIndexEvents.slice(0, 30); + endOfFeed = allIndexEvents.length <= 30; } - console.debug(`[getEvents] Total unique events after deduplication: ${uniqueEvents.length}`); - console.debug(`[getEvents] endOfFeed set to: ${endOfFeed} (fetchedCount: ${fetchedCount}, pageSize: ${pageSize})`); - loading = false; - console.debug('Relay statuses:', relayStatuses); + }, 300); + + $effect(() => { + console.debug('[PublicationFeed] Search query effect triggered:', searchQuery); + debouncedSearch(searchQuery); + }); + + async function loadMorePublications() { + loadingMore = true; + const current = eventsInView.length; + let source = searchQuery.trim() ? filterEventsBySearch(allIndexEvents) : allIndexEvents; + eventsInView = source.slice(0, current + 30); + endOfFeed = eventsInView.length >= source.length; + loadingMore = false; } - const getSkeletonIds = (): string[] => { + function getSkeletonIds(): string[] { const skeletonHeight = 124; // The height of the skeleton component in pixels. const skeletonCount = Math.floor(window.innerHeight / skeletonHeight) - 2; const skeletonIds = []; @@ -204,14 +163,8 @@ return skeletonIds; } - async function loadMorePublications() { - loadingMore = true; - await getEvents(cutoffTimestamp, searchQuery, false); - loadingMore = false; - } - onMount(async () => { - await getEvents(); + await fetchAllIndexEventsFromRelays(); }); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cd6dc39..02277f7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -10,6 +10,12 @@ localStorage.setItem(feedTypeStorageKey, $feedType); }); + $effect(() => { + if (!$ndkSignedIn && $feedType !== FeedType.StandardRelays) { + feedType.set(FeedType.StandardRelays); + } + }); + const getFeedTypeFriendlyName = (feedType: FeedType): string => { switch (feedType) { case FeedType.StandardRelays: @@ -32,19 +38,19 @@
- {#if !$ndkSignedIn} - - {:else} -
- - + {/if} + + + {#if $ndkSignedIn} Your Relays -
+ {/if} + + {#if !$ndkSignedIn} + + {:else} {#if $feedType === FeedType.StandardRelays} {:else if $feedType === FeedType.UserRelays}