From affd80ca2ebab15d90ede1a8d93048f25bb2d3ec Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 2 Nov 2025 21:31:16 +0100 Subject: [PATCH] refactor: standardize profile display name fallbacks across codebase - Add getProfileDisplayName() utility function for consistent profile name resolution - Update all components to use standardized npub fallback format instead of hex - Fix useProfileLabels hook to include fallback npub labels when profiles lack names - Refactor NostrMentionLink to eliminate duplication between npub/nprofile cases - Remove debug console.log statements from RichContent component - Update AuthorCard, SidebarHeader, HighlightItem, Support, BlogPostCard, ResolvedMention, and useEventLoader to use new utilities --- src/components/AuthorCard.tsx | 5 ++- src/components/BlogPostCard.tsx | 4 +-- src/components/HighlightItem.tsx | 5 ++- src/components/NostrMentionLink.tsx | 47 +++++++++++--------------- src/components/ResolvedMention.tsx | 4 +-- src/components/RichContent.tsx | 5 +-- src/components/SidebarHeader.tsx | 6 ++-- src/components/Support.tsx | 3 +- src/hooks/useEventLoader.ts | 5 +-- src/hooks/useProfileLabels.ts | 51 ++++++++++++++++++++++++++--- src/utils/nostrUriResolver.tsx | 17 ++++++++++ 11 files changed, 98 insertions(+), 54 deletions(-) diff --git a/src/components/AuthorCard.tsx b/src/components/AuthorCard.tsx index b008397c..d55e8a96 100644 --- a/src/components/AuthorCard.tsx +++ b/src/components/AuthorCard.tsx @@ -5,6 +5,7 @@ import { faUserCircle } from '@fortawesome/free-solid-svg-icons' import { useEventModel } from 'applesauce-react/hooks' import { Models } from 'applesauce-core' import { nip19 } from 'nostr-tools' +import { getProfileDisplayName } from '../utils/nostrUriResolver' interface AuthorCardProps { authorPubkey: string @@ -16,9 +17,7 @@ const AuthorCard: React.FC = ({ authorPubkey, clickable = true const profile = useEventModel(Models.ProfileModel, [authorPubkey]) const getAuthorName = () => { - if (profile?.name) return profile.name - if (profile?.display_name) return profile.display_name - return `${authorPubkey.slice(0, 8)}...${authorPubkey.slice(-8)}` + return getProfileDisplayName(profile, authorPubkey) } const authorImage = profile?.picture || profile?.image diff --git a/src/components/BlogPostCard.tsx b/src/components/BlogPostCard.tsx index 9801a7af..2787890f 100644 --- a/src/components/BlogPostCard.tsx +++ b/src/components/BlogPostCard.tsx @@ -7,6 +7,7 @@ import { BlogPostPreview } from '../services/exploreService' import { useEventModel } from 'applesauce-react/hooks' import { Models } from 'applesauce-core' import { isKnownBot } from '../config/bots' +import { getProfileDisplayName } from '../utils/nostrUriResolver' interface BlogPostCardProps { post: BlogPostPreview @@ -24,8 +25,7 @@ const BlogPostCard: React.FC = ({ post, href, level, readingP // No need to preload all images at once - this causes ERR_INSUFFICIENT_RESOURCES // when there are many blog posts. - const displayName = profile?.name || profile?.display_name || - `${post.author.slice(0, 8)}...${post.author.slice(-4)}` + const displayName = getProfileDisplayName(profile, post.author) const rawName = (profile?.name || profile?.display_name || '').toLowerCase() // Hide bot authors by name/display_name diff --git a/src/components/HighlightItem.tsx b/src/components/HighlightItem.tsx index 67db5cd4..90d4e9e5 100644 --- a/src/components/HighlightItem.tsx +++ b/src/components/HighlightItem.tsx @@ -18,6 +18,7 @@ import CompactButton from './CompactButton' import { HighlightCitation } from './HighlightCitation' import { useNavigate } from 'react-router-dom' import NostrMentionLink from './NostrMentionLink' +import { getProfileDisplayName } from '../utils/nostrUriResolver' // Helper to detect if a URL is an image const isImageUrl = (url: string): boolean => { @@ -127,9 +128,7 @@ export const HighlightItem: React.FC = ({ // Get display name for the user const getUserDisplayName = () => { - if (profile?.name) return profile.name - if (profile?.display_name) return profile.display_name - return `${highlight.pubkey.slice(0, 8)}...` // fallback to short pubkey + return getProfileDisplayName(profile, highlight.pubkey) } diff --git a/src/components/NostrMentionLink.tsx b/src/components/NostrMentionLink.tsx index bc0f41cd..2baf9f1f 100644 --- a/src/components/NostrMentionLink.tsx +++ b/src/components/NostrMentionLink.tsx @@ -2,6 +2,7 @@ import React from 'react' import { nip19 } from 'nostr-tools' import { useEventModel } from 'applesauce-react/hooks' import { Models, Helpers } from 'applesauce-core' +import { getProfileDisplayName } from '../utils/nostrUriResolver' const { getPubkeyFromDecodeResult } = Helpers @@ -46,41 +47,31 @@ const NostrMentionLink: React.FC = ({ ) } + // Helper function to render profile links (used for both npub and nprofile) + const renderProfileLink = (pubkey: string) => { + const npub = nip19.npubEncode(pubkey) + const displayName = getProfileDisplayName(profile, pubkey) + + return ( + + @{displayName} + + ) + } + // Render based on decoded type switch (decoded.type) { case 'npub': { const pk = decoded.data - const npub = nip19.npubEncode(pk) - // Fallback: show npub without "npub1" prefix - const fallbackDisplay = `@${npub.slice(5, 12)}...` - const displayName = profile?.name || profile?.display_name || profile?.nip05 || fallbackDisplay - - return ( - - @{displayName} - - ) + return renderProfileLink(pk) } case 'nprofile': { const { pubkey: pk } = decoded.data - const npub = nip19.npubEncode(pk) - // Fallback: show npub without "npub1" prefix - const fallbackDisplay = `@${npub.slice(5, 12)}...` - const displayName = profile?.name || profile?.display_name || profile?.nip05 || fallbackDisplay - - return ( - - @{displayName} - - ) + return renderProfileLink(pk) } case 'naddr': { const { kind, pubkey: pk, identifier: addrIdentifier } = decoded.data diff --git a/src/components/ResolvedMention.tsx b/src/components/ResolvedMention.tsx index e3c65961..c393728b 100644 --- a/src/components/ResolvedMention.tsx +++ b/src/components/ResolvedMention.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom' import { useEventModel } from 'applesauce-react/hooks' import { Models, Helpers } from 'applesauce-core' import { decode, npubEncode } from 'nostr-tools/nip19' -import { getNpubFallbackDisplay } from '../utils/nostrUriResolver' +import { getProfileDisplayName } from '../utils/nostrUriResolver' const { getPubkeyFromDecodeResult } = Helpers @@ -21,7 +21,7 @@ const ResolvedMention: React.FC = ({ encoded }) => { } const profile = pubkey ? useEventModel(Models.ProfileModel, [pubkey]) : undefined - const display = profile?.name || profile?.display_name || profile?.nip05 || (pubkey ? getNpubFallbackDisplay(pubkey) : encoded) + const display = pubkey ? getProfileDisplayName(profile, pubkey) : encoded const npub = pubkey ? npubEncode(pubkey) : undefined if (npub) { diff --git a/src/components/RichContent.tsx b/src/components/RichContent.tsx index 643aa3d0..de8fdb1c 100644 --- a/src/components/RichContent.tsx +++ b/src/components/RichContent.tsx @@ -2,7 +2,7 @@ import React from 'react' import NostrMentionLink from './NostrMentionLink' import { Tokens } from 'applesauce-content/helpers' -// Helper to add timestamps to logs +// Helper to add timestamps to error logs const ts = () => { const now = new Date() const ms = now.getMilliseconds().toString().padStart(3, '0') @@ -26,8 +26,6 @@ const RichContent: React.FC = ({ content, className = 'bookmark-content' }) => { - console.log(`[${ts()}] [npub-resolve] RichContent: Rendering, content length:`, content?.length || 0) - try { // Pattern to match: // 1. nostr: URIs (nostr:npub1..., nostr:note1..., etc.) using applesauce Tokens.nostrLink @@ -37,7 +35,6 @@ const RichContent: React.FC = ({ const combinedPattern = new RegExp(`(${nostrPattern.source}|${urlPattern.source})`, 'gi') const parts = content.split(combinedPattern) - console.log(`[${ts()}] [npub-resolve] RichContent: Split into parts:`, parts.length) // Helper to check if a string is a nostr identifier (without mutating regex state) const isNostrIdentifier = (str: string): boolean => { diff --git a/src/components/SidebarHeader.tsx b/src/components/SidebarHeader.tsx index 405351ec..fdf1334c 100644 --- a/src/components/SidebarHeader.tsx +++ b/src/components/SidebarHeader.tsx @@ -8,6 +8,7 @@ import { Models } from 'applesauce-core' import IconButton from './IconButton' import { faBooks } from '../icons/customIcons' import { preloadImage } from '../hooks/useImageCache' +import { getProfileDisplayName } from '../utils/nostrUriResolver' interface SidebarHeaderProps { onToggleCollapse: () => void @@ -29,10 +30,7 @@ const SidebarHeader: React.FC = ({ onToggleCollapse, onLogou const getUserDisplayName = () => { if (!activeAccount) return 'Unknown User' - if (profile?.name) return profile.name - if (profile?.display_name) return profile.display_name - if (profile?.nip05) return profile.nip05 - return `${activeAccount.pubkey.slice(0, 8)}...${activeAccount.pubkey.slice(-8)}` + return getProfileDisplayName(profile, activeAccount.pubkey) } const profileImage = getProfileImage() diff --git a/src/components/Support.tsx b/src/components/Support.tsx index 228d05cd..a11e956b 100644 --- a/src/components/Support.tsx +++ b/src/components/Support.tsx @@ -10,6 +10,7 @@ import { Models } from 'applesauce-core' import { useEventModel } from 'applesauce-react/hooks' import { useNavigate } from 'react-router-dom' import { nip19 } from 'nostr-tools' +import { getProfileDisplayName } from '../utils/nostrUriResolver' interface SupportProps { relayPool: RelayPool @@ -182,7 +183,7 @@ const SupporterCard: React.FC = ({ supporter, isWhale }) => const profile = useEventModel(Models.ProfileModel, [supporter.pubkey]) const picture = profile?.picture - const name = profile?.name || profile?.display_name || `${supporter.pubkey.slice(0, 8)}...` + const name = getProfileDisplayName(profile, supporter.pubkey) const handleClick = () => { const npub = nip19.npubEncode(supporter.pubkey) diff --git a/src/hooks/useEventLoader.ts b/src/hooks/useEventLoader.ts index ecd70ee4..1164a8b8 100644 --- a/src/hooks/useEventLoader.ts +++ b/src/hooks/useEventLoader.ts @@ -6,6 +6,7 @@ import { ReadableContent } from '../services/readerService' import { eventManager } from '../services/eventManager' import { fetchProfiles } from '../services/profileService' import { useDocumentTitle } from './useDocumentTitle' +import { getNpubFallbackDisplay } from '../utils/nostrUriResolver' interface UseEventLoaderProps { eventId?: string @@ -40,7 +41,7 @@ export function useEventLoader({ // Initial title let title = `Note (${event.kind})` if (event.kind === 1) { - title = `Note by @${event.pubkey.slice(0, 8)}...` + title = `Note by ${getNpubFallbackDisplay(event.pubkey)}` } // Emit immediately @@ -83,7 +84,7 @@ export function useEventLoader({ } } } - + if (resolved) { const updatedTitle = `Note by @${resolved}` setCurrentTitle(updatedTitle) diff --git a/src/hooks/useProfileLabels.ts b/src/hooks/useProfileLabels.ts index dd6d1806..6d01ed44 100644 --- a/src/hooks/useProfileLabels.ts +++ b/src/hooks/useProfileLabels.ts @@ -4,6 +4,7 @@ import { Helpers, IEventStore } from 'applesauce-core' import { getContentPointers } from 'applesauce-factory/helpers' import { RelayPool } from 'applesauce-relay' import { fetchProfiles, loadCachedProfiles } from '../services/profileService' +import { getNpubFallbackDisplay } from '../utils/nostrUriResolver' const { getPubkeyFromDecodeResult, encodeDecodeResult } = Helpers @@ -55,9 +56,15 @@ export function useProfileLabels(content: string, relayPool?: RelayPool | null): const displayName = profileData.display_name || profileData.name || profileData.nip05 if (displayName) { labels.set(encoded, `@${displayName}`) + } else { + // Use fallback npub display if profile has no name + const fallback = getNpubFallbackDisplay(pubkey) + labels.set(encoded, fallback) } } catch { - // Ignore parsing errors, will fetch later + // Use fallback npub display if parsing fails + const fallback = getNpubFallbackDisplay(pubkey) + labels.set(encoded, fallback) } } }) @@ -113,16 +120,30 @@ export function useProfileLabels(content: string, relayPool?: RelayPool | null): if (displayName) { labels.set(encoded, `@${displayName}`) } else { - pubkeysToFetch.push(pubkey) + // Use fallback npub display if profile has no name + const fallback = getNpubFallbackDisplay(pubkey) + labels.set(encoded, fallback) } } catch { - pubkeysToFetch.push(pubkey) + // Use fallback npub display if parsing fails + const fallback = getNpubFallbackDisplay(pubkey) + labels.set(encoded, fallback) } } else { + // No profile found yet, will use fallback after fetch or keep empty + // We'll set fallback labels for missing profiles at the end pubkeysToFetch.push(pubkey) } }) + // Set fallback labels for profiles that weren't found + profileData.forEach(({ encoded, pubkey }) => { + if (!labels.has(encoded)) { + const fallback = getNpubFallbackDisplay(pubkey) + labels.set(encoded, fallback) + } + }) + setProfileLabels(new Map(labels)) // Fetch missing profiles asynchronously @@ -142,9 +163,15 @@ export function useProfileLabels(content: string, relayPool?: RelayPool | null): const displayName = profileData.display_name || profileData.name || profileData.nip05 if (displayName) { updatedLabels.set(encoded, `@${displayName}`) + } else { + // Use fallback npub display if profile has no name + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } } catch { - // Ignore parsing errors + // Use fallback npub display if parsing fails + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } } else if (eventStore) { // Fallback: check eventStore (in case fetchProfiles stored but didn't return) @@ -155,11 +182,25 @@ export function useProfileLabels(content: string, relayPool?: RelayPool | null): const displayName = profileData.display_name || profileData.name || profileData.nip05 if (displayName) { updatedLabels.set(encoded, `@${displayName}`) + } else { + // Use fallback npub display if profile has no name + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } } catch { - // Ignore parsing errors + // Use fallback npub display if parsing fails + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } + } else { + // No profile found, use fallback + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } + } else { + // No eventStore, use fallback + const fallback = getNpubFallbackDisplay(pubkey) + updatedLabels.set(encoded, fallback) } } }) diff --git a/src/utils/nostrUriResolver.tsx b/src/utils/nostrUriResolver.tsx index 85b0a591..8d9775ad 100644 --- a/src/utils/nostrUriResolver.tsx +++ b/src/utils/nostrUriResolver.tsx @@ -134,6 +134,23 @@ export function getNpubFallbackDisplay(pubkey: string): string { } } +/** + * Get display name for a profile with consistent priority order + * Returns: profile.name || profile.display_name || profile.nip05 || npub fallback + * @param profile Profile object with optional name, display_name, and nip05 fields + * @param pubkey The pubkey in hex format (required for fallback) + * @returns Display name string + */ +export function getProfileDisplayName( + profile: { name?: string; display_name?: string; nip05?: string } | null | undefined, + pubkey: string +): string { + if (profile?.name) return profile.name + if (profile?.display_name) return profile.display_name + if (profile?.nip05) return profile.nip05 + return getNpubFallbackDisplay(pubkey) +} + /** * Process markdown to replace nostr URIs while skipping those inside markdown links * This prevents nested markdown link issues when nostr identifiers appear in URLs