diff --git a/src/hooks/useMarkdownToHTML.ts b/src/hooks/useMarkdownToHTML.ts index a04873ff..7e8248d2 100644 --- a/src/hooks/useMarkdownToHTML.ts +++ b/src/hooks/useMarkdownToHTML.ts @@ -24,7 +24,7 @@ export const useMarkdownToHTML = ( console.log('[useMarkdownToHTML] Hook called, markdown length:', markdown?.length || 0, 'hasRelayPool:', !!relayPool) // Resolve profile labels progressively as profiles load - const profileLabels = useProfileLabels(markdown || '') + const profileLabels = useProfileLabels(markdown || '', relayPool) console.log('[useMarkdownToHTML] Profile labels size:', profileLabels.size) // Fetch article titles diff --git a/src/hooks/useProfileLabels.ts b/src/hooks/useProfileLabels.ts index 37bef275..0b93d642 100644 --- a/src/hooks/useProfileLabels.ts +++ b/src/hooks/useProfileLabels.ts @@ -1,7 +1,9 @@ -import { useMemo } from 'react' -import { useEventModel } from 'applesauce-react/hooks' -import { Models, Helpers } from 'applesauce-core' +import { useMemo, useState, useEffect } from 'react' +import { Hooks } from 'applesauce-react' +import { Helpers, IEventStore } from 'applesauce-core' import { getContentPointers } from 'applesauce-factory/helpers' +import { RelayPool } from 'applesauce-relay' +import { fetchProfiles } from '../services/profileService' const { getPubkeyFromDecodeResult, encodeDecodeResult } = Helpers @@ -9,7 +11,9 @@ const { getPubkeyFromDecodeResult, encodeDecodeResult } = Helpers * Hook to resolve profile labels from content containing npub/nprofile identifiers * Returns a Map of encoded identifier -> display name that updates progressively as profiles load */ -export function useProfileLabels(content: string): Map { +export function useProfileLabels(content: string, relayPool?: RelayPool | null): Map { + const eventStore = Hooks.useEventStore() + // Extract profile pointers (npub and nprofile) using applesauce helpers const profileData = useMemo(() => { console.log('[useProfileLabels] Processing content, length:', content?.length || 0) @@ -18,19 +22,18 @@ export function useProfileLabels(content: string): Map { console.log('[useProfileLabels] Found pointers:', pointers.length, 'types:', pointers.map(p => p.type)) const filtered = pointers.filter(p => p.type === 'npub' || p.type === 'nprofile') console.log('[useProfileLabels] Profile pointers:', filtered.length) - const result = filtered - .map(pointer => { - try { - return { - pubkey: getPubkeyFromDecodeResult(pointer), - encoded: encodeDecodeResult(pointer) - } - } catch (err) { - console.error('[useProfileLabels] Error processing pointer:', err, pointer) - return null + const result: Array<{ pubkey: string; encoded: string }> = [] + filtered.forEach(pointer => { + try { + const pubkey = getPubkeyFromDecodeResult(pointer) + const encoded = encodeDecodeResult(pointer) + if (pubkey && encoded) { + result.push({ pubkey, encoded: encoded as string }) } - }) - .filter((p): p is { pubkey: string; encoded: string } => p !== null && !!p.pubkey) + } catch (err) { + console.error('[useProfileLabels] Error processing pointer:', err, pointer) + } + }) console.log('[useProfileLabels] Profile data after filtering:', result.length) return result } catch (err) { @@ -39,27 +42,76 @@ export function useProfileLabels(content: string): Map { } }, [content]) - // Fetch profiles for all found pubkeys (progressive loading) - const profiles = profileData.map(({ pubkey }) => - useEventModel(Models.ProfileModel, pubkey ? [pubkey] : null) - ) + const [profileLabels, setProfileLabels] = useState>(new Map()) - // Build profile labels map that updates reactively as profiles load - return useMemo(() => { + // Build initial labels from eventStore, then fetch missing profiles + useEffect(() => { + console.log('[useProfileLabels] Building labels, profileData:', profileData.length, 'hasEventStore:', !!eventStore) + + // First, get profiles from eventStore synchronously const labels = new Map() - console.log('[useProfileLabels] Building labels map, profileData:', profileData.length, 'profiles:', profiles.length) - profileData.forEach(({ encoded }, index) => { - const profile = profiles[index] - if (profile) { - const displayName = profile.name || profile.display_name || profile.nip05 - if (displayName) { - labels.set(encoded, `@${displayName}`) - console.log('[useProfileLabels] Set label:', encoded, '->', displayName) + const pubkeysToFetch: string[] = [] + + profileData.forEach(({ encoded, pubkey }) => { + if (eventStore) { + const profileEvent = eventStore.getEvent(pubkey + ':0') + if (profileEvent) { + try { + const profileData = JSON.parse(profileEvent.content || '{}') as { name?: string; display_name?: string; nip05?: string } + const displayName = profileData.display_name || profileData.name || profileData.nip05 + if (displayName) { + labels.set(encoded, `@${displayName}`) + console.log('[useProfileLabels] Found in eventStore:', encoded, '->', displayName) + } else { + pubkeysToFetch.push(pubkey) + } + } catch { + pubkeysToFetch.push(pubkey) + } + } else { + pubkeysToFetch.push(pubkey) } + } else { + pubkeysToFetch.push(pubkey) } }) - console.log('[useProfileLabels] Final labels map size:', labels.size) - return labels - }, [profileData, profiles]) + + // Update labels with what we found in eventStore + setProfileLabels(new Map(labels)) + + // Fetch missing profiles asynchronously + if (pubkeysToFetch.length > 0 && relayPool && eventStore) { + console.log('[useProfileLabels] Fetching', pubkeysToFetch.length, 'missing profiles') + fetchProfiles(relayPool, eventStore as unknown as IEventStore, pubkeysToFetch) + .then(profiles => { + // Rebuild labels map with fetched profiles + const updatedLabels = new Map(labels) + profileData.forEach(({ encoded, pubkey }) => { + if (!updatedLabels.has(encoded)) { + const profileEvent = profiles.find(p => p.pubkey === pubkey) + if (profileEvent) { + try { + const profileData = JSON.parse(profileEvent.content || '{}') as { name?: string; display_name?: string; nip05?: string } + const displayName = profileData.display_name || profileData.name || profileData.nip05 + if (displayName) { + updatedLabels.set(encoded, `@${displayName}`) + console.log('[useProfileLabels] Fetched profile:', encoded, '->', displayName) + } + } catch { + // ignore parse errors + } + } + } + }) + setProfileLabels(updatedLabels) + }) + .catch(err => { + console.error('[useProfileLabels] Error fetching profiles:', err) + }) + } + }, [profileData, eventStore, relayPool]) + + console.log('[useProfileLabels] Final labels map size:', profileLabels.size) + return profileLabels } diff --git a/src/utils/nostrUriResolver.tsx b/src/utils/nostrUriResolver.tsx index 4124bda9..d9b3a490 100644 --- a/src/utils/nostrUriResolver.tsx +++ b/src/utils/nostrUriResolver.tsx @@ -20,14 +20,17 @@ export function extractNostrUris(text: string): string[] { try { const pointers = getContentPointers(text) console.log('[nostrUriResolver] Found pointers:', pointers.length) - const result = pointers.map(pointer => { + const result: string[] = [] + pointers.forEach(pointer => { try { - return encodeDecodeResult(pointer) + const encoded = encodeDecodeResult(pointer) + if (encoded) { + result.push(encoded) + } } catch (err) { console.error('[nostrUriResolver] Error encoding pointer:', err, pointer) - return null } - }).filter((v): v is string => v !== null) + }) console.log('[nostrUriResolver] Extracted URIs:', result.length) return result } catch (err) {