Browse Source

interim changes

master
Silberengel 10 months ago
parent
commit
482f719601
  1. 60
      src/lib/components/EventDetails.svelte
  2. 47
      src/lib/components/EventSearch.svelte
  3. 91
      src/lib/components/PublicationFeed.svelte
  4. 2
      src/routes/+page.svelte
  5. 2
      src/routes/events/+page.svelte

60
src/lib/components/EventDetails.svelte

@ -3,12 +3,13 @@ @@ -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 @@ @@ -20,6 +21,7 @@
lud16?: string;
nip05?: string;
} | null;
searchValue?: string | null;
}>();
let showFullContent = $state(false);
@ -62,6 +64,43 @@ @@ -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);
}
</script>
<div class="flex flex-col space-y-4">
@ -191,6 +230,23 @@ @@ -191,6 +230,23 @@
</div>
{/if}
<!-- Identifier List -->
<div class="flex flex-col space-y-1">
<span class="text-gray-600 dark:text-gray-400">Identifiers:</span>
<div class="flex flex-wrap gap-2">
{#each getIdentifiers(event, profile) as id}
{#if id.link}
<a href={id.link}
class="px-2 py-1 rounded border font-mono text-xs bg-primary-50 dark:bg-primary-900 hover:bg-primary-100 dark:hover:bg-primary-800 transition-all {isCurrentSearch(id.value) ? 'border-primary-500 ring-2 ring-primary-400' : 'border-gray-300'}"
>{id.label}: {id.value}</a>
{:else}
<span class="px-2 py-1 rounded border font-mono text-xs bg-primary-50 dark:bg-primary-900 {isCurrentSearch(id.value) ? 'border-primary-500 ring-2 ring-primary-400' : 'border-gray-300'}"
>{id.label}: {id.value}</span>
{/if}
{/each}
</div>
</div>
<!-- Raw Event JSON -->
<details class="bg-primary-50 dark:bg-primary-900 rounded p-4">
<summary class="cursor-pointer font-semibold text-primary-700 dark:text-primary-300 mb-2">

47
src/lib/components/EventSearch.svelte

@ -51,21 +51,46 @@ @@ -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)) {

91
src/lib/components/PublicationFeed.svelte

@ -21,18 +21,24 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -94,7 +148,7 @@
let eventSet = await ndk.fetchEvents(
{
kinds: [indexKind],
limit: 16,
limit: 18,
until: before,
},
{
@ -113,24 +167,29 @@ @@ -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 @@ @@ -147,7 +206,7 @@
async function loadMorePublications() {
loadingMore = true;
await getEvents(cutoffTimestamp);
await getEvents(cutoffTimestamp, searchQuery, false);
loadingMore = false;
}

2
src/routes/+page.svelte

@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
<PublicationFeed relays={standardRelays} fallbackRelays={fallbackRelays} searchQuery={searchQuery} />
{:else}
<div class='leather w-full flex flex-row items-center justify-center gap-4 mb-4'>
<Button id="feed-toggle-btn" class="min-w-[220px] max-w-xs">
<Button id="feed-toggle-btn" class="min-w-[220px] max-w-sm">
{`Showing publications from: ${getFeedTypeFriendlyName($feedType)}`}
<ChevronDownOutline class='w-6 h-6' />
</Button>

2
src/routes/events/+page.svelte

@ -62,7 +62,7 @@ @@ -62,7 +62,7 @@
<EventSearch {loading} {error} {searchValue} {event} onEventFound={handleEventFound} />
{#if event}
<EventDetails {event} {profile} />
<EventDetails {event} {profile} {searchValue} />
<RelayActions {event} />
{#if userPubkey}
<div class="mt-8">

Loading…
Cancel
Save