|
|
|
|
@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
|
|
|
|
|
import CommentViewer from "$lib/components/CommentViewer.svelte"; |
|
|
|
|
import { userStore } from "$lib/stores/userStore"; |
|
|
|
|
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; |
|
|
|
|
import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils"; |
|
|
|
|
import { getMatchingTags, toNpub, getUserMetadata } from "$lib/utils/nostrUtils"; |
|
|
|
|
import EventInput from "$lib/components/EventInput.svelte"; |
|
|
|
|
import { userPubkey, isLoggedIn } from "$lib/stores/authStore.Svelte"; |
|
|
|
|
import CopyToClipboard from "$lib/components/util/CopyToClipboard.svelte"; |
|
|
|
|
@ -75,6 +75,25 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -75,6 +75,25 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
} else { |
|
|
|
|
profile = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AI-NOTE: 2025-01-24 - Ensure profile is cached for the event author |
|
|
|
|
if (newEvent.pubkey) { |
|
|
|
|
cacheProfileForPubkey(newEvent.pubkey); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AI-NOTE: 2025-01-24 - Function to ensure profile is cached for a pubkey |
|
|
|
|
async function cacheProfileForPubkey(pubkey: string) { |
|
|
|
|
try { |
|
|
|
|
const npub = toNpub(pubkey); |
|
|
|
|
if (npub) { |
|
|
|
|
// Force fetch to ensure profile is cached |
|
|
|
|
await getUserMetadata(npub, true); |
|
|
|
|
console.log(`[Events Page] Cached profile for pubkey: ${pubkey}`); |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
console.warn(`[Events Page] Failed to cache profile for ${pubkey}:`, error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Use Svelte 5 idiomatic effect to update searchValue when $page.url.searchParams.get('id') changes |
|
|
|
|
@ -185,11 +204,32 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -185,11 +204,32 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
checkCommunityStatusForResults(tTagEvents); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AI-NOTE: 2025-01-24 - Cache profiles for all search results |
|
|
|
|
cacheProfilesForEvents([...results, ...secondOrder, ...tTagEvents]); |
|
|
|
|
|
|
|
|
|
// Don't clear the current event - let the user continue viewing it |
|
|
|
|
// event = null; |
|
|
|
|
// profile = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AI-NOTE: 2025-01-24 - Function to cache profiles for multiple events |
|
|
|
|
async function cacheProfilesForEvents(events: NDKEvent[]) { |
|
|
|
|
const uniquePubkeys = new Set<string>(); |
|
|
|
|
events.forEach(event => { |
|
|
|
|
if (event.pubkey) { |
|
|
|
|
uniquePubkeys.add(event.pubkey); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
console.log(`[Events Page] Caching profiles for ${uniquePubkeys.size} unique pubkeys`); |
|
|
|
|
|
|
|
|
|
// Cache profiles in parallel |
|
|
|
|
const cachePromises = Array.from(uniquePubkeys).map(pubkey => cacheProfileForPubkey(pubkey)); |
|
|
|
|
await Promise.allSettled(cachePromises); |
|
|
|
|
|
|
|
|
|
console.log(`[Events Page] Profile caching complete`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function handleClear() { |
|
|
|
|
searchType = null; |
|
|
|
|
searchTerm = null; |
|
|
|
|
@ -233,48 +273,47 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -233,48 +273,47 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
originalEventIds: Set<string>, |
|
|
|
|
originalAddresses: Set<string>, |
|
|
|
|
): string { |
|
|
|
|
// Check if this event has e-tags referencing original events |
|
|
|
|
const eTags = getMatchingTags(event, "e"); |
|
|
|
|
for (const tag of eTags) { |
|
|
|
|
if (originalEventIds.has(tag[1])) { |
|
|
|
|
return "Reply/Reference (e-tag)"; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
const eTags = event.getMatchingTags("e"); |
|
|
|
|
const aTags = event.getMatchingTags("a"); |
|
|
|
|
|
|
|
|
|
// Check if this event has a-tags or e-tags referencing original events |
|
|
|
|
let tags = getMatchingTags(event, "a"); |
|
|
|
|
if (tags.length === 0) { |
|
|
|
|
tags = getMatchingTags(event, "e"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (const tag of tags) { |
|
|
|
|
if (originalAddresses.has(tag[1])) { |
|
|
|
|
return "Reply/Reference (a-tag)"; |
|
|
|
|
if (eTags.length > 0) { |
|
|
|
|
const referencedEventId = eTags[eTags.length - 1][1]; |
|
|
|
|
if (originalEventIds.has(referencedEventId)) { |
|
|
|
|
return "Reply"; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if this event has content references |
|
|
|
|
if (event.content) { |
|
|
|
|
for (const id of originalEventIds) { |
|
|
|
|
const neventPattern = new RegExp(`nevent1[a-z0-9]{50,}`, "i"); |
|
|
|
|
const notePattern = new RegExp(`note1[a-z0-9]{50,}`, "i"); |
|
|
|
|
if ( |
|
|
|
|
neventPattern.test(event.content) || |
|
|
|
|
notePattern.test(event.content) |
|
|
|
|
) { |
|
|
|
|
return "Content Reference"; |
|
|
|
|
if (aTags.length > 0) { |
|
|
|
|
const referencedAddress = aTags[aTags.length - 1][1]; |
|
|
|
|
if (originalAddresses.has(referencedAddress)) { |
|
|
|
|
return "Quote"; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (const address of originalAddresses) { |
|
|
|
|
const naddrPattern = new RegExp(`naddr1[a-z0-9]{50,}`, "i"); |
|
|
|
|
if (naddrPattern.test(event.content)) { |
|
|
|
|
return "Content Reference"; |
|
|
|
|
} |
|
|
|
|
return "Reference"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AI-NOTE: 2025-01-24 - Function to parse profile content from kind 0 events |
|
|
|
|
function parseProfileContent(event: NDKEvent): { |
|
|
|
|
name?: string; |
|
|
|
|
display_name?: string; |
|
|
|
|
about?: string; |
|
|
|
|
picture?: string; |
|
|
|
|
banner?: string; |
|
|
|
|
website?: string; |
|
|
|
|
lud16?: string; |
|
|
|
|
nip05?: string; |
|
|
|
|
} | null { |
|
|
|
|
if (event.kind !== 0 || !event.content) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return "Reference"; |
|
|
|
|
try { |
|
|
|
|
return JSON.parse(event.content); |
|
|
|
|
} catch (error) { |
|
|
|
|
console.warn("Failed to parse profile content:", error); |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getNeventUrl(event: NDKEvent): string { |
|
|
|
|
@ -427,6 +466,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -427,6 +466,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
</Heading> |
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
{#each searchResults as result, index} |
|
|
|
|
{@const profileData = parseProfileContent(result)} |
|
|
|
|
<button |
|
|
|
|
class="w-full text-left border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-white dark:bg-primary-900/70 hover:bg-gray-100 dark:hover:bg-primary-800 focus:bg-gray-100 dark:focus:bg-primary-800 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors overflow-hidden" |
|
|
|
|
onclick={() => handleEventFound(result)} |
|
|
|
|
@ -461,7 +501,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -461,7 +501,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
<span class="text-xs text-gray-600 dark:text-gray-400"> |
|
|
|
|
{@render userBadge( |
|
|
|
|
toNpub(result.pubkey) as string, |
|
|
|
|
undefined, |
|
|
|
|
profileData?.display_name || profileData?.name, |
|
|
|
|
)} |
|
|
|
|
</span> |
|
|
|
|
<span |
|
|
|
|
@ -474,6 +514,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -474,6 +514,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
: "Unknown date"} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
{#if result.kind === 0 && profileData} |
|
|
|
|
<div class="flex items-center gap-3 mb-2"> |
|
|
|
|
{#if profileData.picture} |
|
|
|
|
<img |
|
|
|
|
src={profileData.picture} |
|
|
|
|
alt="Profile" |
|
|
|
|
class="w-12 h-12 rounded-full object-cover border border-gray-200 dark:border-gray-600" |
|
|
|
|
onerror={(e) => { |
|
|
|
|
(e.target as HTMLImageElement).style.display = 'none'; |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{:else} |
|
|
|
|
<div class="w-12 h-12 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600"> |
|
|
|
|
<span class="text-lg font-medium text-gray-600 dark:text-gray-300"> |
|
|
|
|
{(profileData.display_name || profileData.name || result.pubkey.slice(0, 1)).toUpperCase()} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
<div class="flex flex-col min-w-0 flex-1"> |
|
|
|
|
{#if profileData.display_name || profileData.name} |
|
|
|
|
<span class="font-medium text-gray-900 dark:text-gray-100 truncate"> |
|
|
|
|
{profileData.display_name || profileData.name} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
{#if profileData.about} |
|
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2"> |
|
|
|
|
{profileData.about} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
{:else} |
|
|
|
|
{#if getSummary(result)} |
|
|
|
|
<div |
|
|
|
|
class="text-sm text-primary-900 dark:text-primary-200 mb-1 line-clamp-2" |
|
|
|
|
@ -527,6 +599,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -527,6 +599,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
: ""} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</button> |
|
|
|
|
{/each} |
|
|
|
|
@ -551,6 +624,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -551,6 +624,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
</P> |
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
{#each secondOrderResults as result, index} |
|
|
|
|
{@const profileData = parseProfileContent(result)} |
|
|
|
|
<button |
|
|
|
|
class="w-full text-left border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-gray-50 dark:bg-primary-800/50 hover:bg-gray-100 dark:hover:bg-primary-700 focus:bg-gray-100 dark:focus:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors overflow-hidden" |
|
|
|
|
onclick={() => handleEventFound(result)} |
|
|
|
|
@ -584,7 +658,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -584,7 +658,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
<span class="text-xs text-gray-600 dark:text-gray-400"> |
|
|
|
|
{@render userBadge( |
|
|
|
|
toNpub(result.pubkey) as string, |
|
|
|
|
undefined, |
|
|
|
|
profileData?.display_name || profileData?.name, |
|
|
|
|
)} |
|
|
|
|
</span> |
|
|
|
|
<span |
|
|
|
|
@ -604,6 +678,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -604,6 +678,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
originalAddresses, |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
{#if result.kind === 0 && profileData} |
|
|
|
|
<div class="flex items-center gap-3 mb-2"> |
|
|
|
|
{#if profileData.picture} |
|
|
|
|
<img |
|
|
|
|
src={profileData.picture} |
|
|
|
|
alt="Profile" |
|
|
|
|
class="w-12 h-12 rounded-full object-cover border border-gray-200 dark:border-gray-600" |
|
|
|
|
onerror={(e) => { |
|
|
|
|
(e.target as HTMLImageElement).style.display = 'none'; |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{:else} |
|
|
|
|
<div class="w-12 h-12 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600"> |
|
|
|
|
<span class="text-lg font-medium text-gray-600 dark:text-gray-300"> |
|
|
|
|
{(profileData.display_name || profileData.name || result.pubkey.slice(0, 1)).toUpperCase()} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
<div class="flex flex-col min-w-0 flex-1"> |
|
|
|
|
{#if profileData.display_name || profileData.name} |
|
|
|
|
<span class="font-medium text-gray-900 dark:text-gray-100 truncate"> |
|
|
|
|
{profileData.display_name || profileData.name} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
{#if profileData.about} |
|
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2"> |
|
|
|
|
{profileData.about} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
{:else} |
|
|
|
|
{#if getSummary(result)} |
|
|
|
|
<div |
|
|
|
|
class="text-sm text-primary-900 dark:text-primary-200 mb-1 line-clamp-2" |
|
|
|
|
@ -657,6 +763,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -657,6 +763,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
: ""} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</button> |
|
|
|
|
{/each} |
|
|
|
|
@ -675,6 +782,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -675,6 +782,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
</P> |
|
|
|
|
<div class="space-y-4"> |
|
|
|
|
{#each tTagResults as result, index} |
|
|
|
|
{@const profileData = parseProfileContent(result)} |
|
|
|
|
<button |
|
|
|
|
class="w-full text-left border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-gray-50 dark:bg-primary-800/50 hover:bg-gray-100 dark:hover:bg-primary-700 focus:bg-gray-100 dark:focus:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors overflow-hidden" |
|
|
|
|
onclick={() => handleEventFound(result)} |
|
|
|
|
@ -708,7 +816,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -708,7 +816,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
<span class="text-xs text-gray-600 dark:text-gray-400"> |
|
|
|
|
{@render userBadge( |
|
|
|
|
toNpub(result.pubkey) as string, |
|
|
|
|
undefined, |
|
|
|
|
profileData?.display_name || profileData?.name, |
|
|
|
|
)} |
|
|
|
|
</span> |
|
|
|
|
<span |
|
|
|
|
@ -721,6 +829,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -721,6 +829,38 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
: "Unknown date"} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
{#if result.kind === 0 && profileData} |
|
|
|
|
<div class="flex items-center gap-3 mb-2"> |
|
|
|
|
{#if profileData.picture} |
|
|
|
|
<img |
|
|
|
|
src={profileData.picture} |
|
|
|
|
alt="Profile" |
|
|
|
|
class="w-12 h-12 rounded-full object-cover border border-gray-200 dark:border-gray-600" |
|
|
|
|
onerror={(e) => { |
|
|
|
|
(e.target as HTMLImageElement).style.display = 'none'; |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{:else} |
|
|
|
|
<div class="w-12 h-12 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600"> |
|
|
|
|
<span class="text-lg font-medium text-gray-600 dark:text-gray-300"> |
|
|
|
|
{(profileData.display_name || profileData.name || result.pubkey.slice(0, 1)).toUpperCase()} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
<div class="flex flex-col min-w-0 flex-1"> |
|
|
|
|
{#if profileData.display_name || profileData.name} |
|
|
|
|
<span class="font-medium text-gray-900 dark:text-gray-100 truncate"> |
|
|
|
|
{profileData.display_name || profileData.name} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
{#if profileData.about} |
|
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2"> |
|
|
|
|
{profileData.about} |
|
|
|
|
</span> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
{:else} |
|
|
|
|
{#if getSummary(result)} |
|
|
|
|
<div |
|
|
|
|
class="text-sm text-primary-900 dark:text-primary-200 mb-1 line-clamp-2" |
|
|
|
|
@ -774,6 +914,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
@@ -774,6 +914,7 @@ import CommentViewer from "$lib/components/CommentViewer.svelte";
|
|
|
|
|
: ""} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</button> |
|
|
|
|
{/each} |
|
|
|
|
|