diff --git a/src/lib/components/EventDetails.svelte b/src/lib/components/EventDetails.svelte index 50fb65c..a98ca36 100644 --- a/src/lib/components/EventDetails.svelte +++ b/src/lib/components/EventDetails.svelte @@ -3,12 +3,13 @@ import { getMimeTags } from "$lib/utils/mime"; import { userBadge } from "$lib/snippets/UserSnippets.svelte"; import { toNpub } from "$lib/utils/nostrUtils"; - import { neventEncode, naddrEncode } from "$lib/utils"; + import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils"; import { standardRelays } from "$lib/consts"; import type { NDKEvent } from '$lib/utils/nostrUtils'; import { getMatchingTags } from '$lib/utils/nostrUtils'; + import { nip19 } from 'nostr-tools'; - const { event, profile = null } = $props<{ + const { event, profile = null, searchValue = null } = $props<{ event: NDKEvent; profile?: { name?: string; @@ -20,6 +21,7 @@ lud16?: string; nip05?: string; } | null; + searchValue?: string | null; }>(); let showFullContent = $state(false); @@ -62,6 +64,43 @@ }); } }); + + // --- Identifier helpers --- + function getIdentifiers(event: NDKEvent, profile: any): { label: string, value: string, link?: string }[] { + const ids: { label: string, value: string, link?: string }[] = []; + if (event.kind === 0) { + // NIP-05 + const nip05 = profile?.nip05 || getMatchingTags(event, 'nip05')[0]?.[1]; + if (nip05) ids.push({ label: 'NIP-05', value: nip05 }); + // npub + const npub = toNpub(event.pubkey); + if (npub) ids.push({ label: 'npub', value: npub, link: `/events?id=${npub}` }); + // nprofile + ids.push({ label: 'nprofile', value: nprofileEncode(event.pubkey, standardRelays), link: `/events?id=${nprofileEncode(event.pubkey, standardRelays)}` }); + // nevent + ids.push({ label: 'nevent', value: neventEncode(event, standardRelays), link: `/events?id=${neventEncode(event, standardRelays)}` }); + // hex pubkey + ids.push({ label: 'pubkey', value: event.pubkey }); + } else { + // nevent + ids.push({ label: 'nevent', value: neventEncode(event, standardRelays), link: `/events?id=${neventEncode(event, standardRelays)}` }); + // naddr (if addressable) + try { + const naddr = naddrEncode(event, standardRelays); + ids.push({ label: 'naddr', value: naddr, link: `/events?id=${naddr}` }); + } catch {} + // hex id + ids.push({ label: 'id', value: event.id }); + } + return ids; + } + + function isCurrentSearch(value: string): boolean { + if (!searchValue) return false; + // Compare ignoring case and possible nostr: prefix + const norm = (s: string) => s.replace(/^nostr:/, '').toLowerCase(); + return norm(value) === norm(searchValue); + }
@@ -191,6 +230,23 @@
{/if} + +
+ Identifiers: +
+ {#each getIdentifiers(event, profile) as id} + {#if id.link} + {id.label}: {id.value} + {:else} + {id.label}: {id.value} + {/if} + {/each} +
+
+
diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte index e0ae258..36cbdf3 100644 --- a/src/lib/components/EventSearch.svelte +++ b/src/lib/components/EventSearch.svelte @@ -51,21 +51,46 @@ let filterOrId: any = cleanedQuery; console.log('[Events] Cleaned query:', cleanedQuery); + // NIP-05 address pattern: user@domain + if (/^[a-z0-9._-]+@[a-z0-9.-]+$/i.test(cleanedQuery)) { + try { + const [name, domain] = cleanedQuery.split('@'); + const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`); + const data = await res.json(); + const pubkey = data.names?.[name]; + if (pubkey) { + filterOrId = { kinds: [0], authors: [pubkey] }; + const profileEvent = await fetchEventWithFallback($ndkInstance, filterOrId, 10000); + if (profileEvent) { + handleFoundEvent(profileEvent); + return; + } else { + localError = 'No profile found for this NIP-05 address.'; + return; + } + } else { + localError = 'NIP-05 address not found.'; + return; + } + } catch (e) { + localError = 'Error resolving NIP-05 address.'; + return; + } + } + // If it's a 64-char hex, try as event id first, then as pubkey (profile) if (/^[a-f0-9]{64}$/i.test(cleanedQuery)) { // Try as event id filterOrId = cleanedQuery; - const event = await fetchEventWithFallback($ndkInstance, filterOrId, 10000); - - if (!event) { - // Try as pubkey (profile event) - filterOrId = { kinds: [0], authors: [cleanedQuery] }; - const profileEvent = await fetchEventWithFallback($ndkInstance, filterOrId, 10000); - if (profileEvent) { - handleFoundEvent(profileEvent); - } - } else { - handleFoundEvent(event); + const eventResult = await fetchEventWithFallback($ndkInstance, filterOrId, 10000); + // Always try as pubkey (profile event) as well + const profileFilter = { kinds: [0], authors: [cleanedQuery] }; + const profileEvent = await fetchEventWithFallback($ndkInstance, profileFilter, 10000); + // Prefer profile if found and pubkey matches query + if (profileEvent && profileEvent.pubkey.toLowerCase() === cleanedQuery.toLowerCase()) { + handleFoundEvent(profileEvent); + } else if (eventResult) { + handleFoundEvent(eventResult); } return; } else if (/^(nevent|note|naddr|npub|nprofile)[a-z0-9]+$/i.test(cleanedQuery)) { diff --git a/src/lib/components/PublicationFeed.svelte b/src/lib/components/PublicationFeed.svelte index bb10b5b..8f13028 100644 --- a/src/lib/components/PublicationFeed.svelte +++ b/src/lib/components/PublicationFeed.svelte @@ -21,18 +21,24 @@ // Debounced search function const debouncedSearch = debounce(async (query: string) => { + console.debug('[PublicationFeed] Search query changed:', query); if (query.trim()) { - await getEvents(undefined, query); + console.debug('[PublicationFeed] Clearing events and searching with query:', query); + eventsInView = []; + await getEvents(undefined, query, true); } else { - await getEvents(); + 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); }); - async function getEvents(before: number | undefined = undefined, search: string = '') { + async function getEvents(before: number | undefined = undefined, search: string = '', reset: boolean = false) { loading = true; const ndk = $ndkInstance; const primaryRelays: string[] = relays; @@ -41,18 +47,61 @@ 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(); - return events.filter(event => { + 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 author = event.pubkey.toLowerCase(); - return title.includes(query) || author.includes(query); + 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) + ); + 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; }; // First, try primary relays + let foundEventsInPrimary = false; await Promise.all( primaryRelays.map(async (relay: string) => { try { @@ -76,16 +125,21 @@ if (eventArray.length > 0) { allEvents = allEvents.concat(eventArray); relayStatuses = { ...relayStatuses, [relay]: 'found' }; + foundEventsInPrimary = true; } else { relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; } - } catch { + 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' }; } }) ); - // If no events found, try fallback relays - if (allEvents.length === 0 && fallback.length > 0) { + + // 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) => { @@ -94,7 +148,7 @@ let eventSet = await ndk.fetchEvents( { kinds: [indexKind], - limit: 16, + limit: 18, until: before, }, { @@ -113,24 +167,29 @@ } else { relayStatuses = { ...relayStatuses, [relay]: 'notfound' }; } - } catch { + 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' }; } }) ); } // Deduplicate and sort - const eventMap = new Map([...eventsInView, ...allEvents].map(event => [event.tagAddress(), event])); + 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; - // Set endOfFeed if fewer than limit events were fetched - if ((before !== undefined && fetchedCount < 30 && fallback.length === 0) || - (before !== undefined && fetchedCount < 16 && fallback.length > 0)) { + const pageSize = fallback.length > 0 ? 18 : 30; + if (fetchedCount < pageSize) { endOfFeed = true; } else { endOfFeed = false; } + 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); } @@ -147,7 +206,7 @@ async function loadMorePublications() { loadingMore = true; - await getEvents(cutoffTimestamp); + await getEvents(cutoffTimestamp, searchQuery, false); loadingMore = false; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 468e734..d3cee5c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -36,7 +36,7 @@ {:else}
- diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index 3d1f38b..1e5f7f6 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -62,7 +62,7 @@ {#if event} - + {#if userPubkey}