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
This commit is contained in:
Gigi
2025-11-02 21:31:16 +01:00
parent 5e1ed6b8de
commit affd80ca2e
11 changed files with 98 additions and 54 deletions

View File

@@ -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<AuthorCardProps> = ({ 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

View File

@@ -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<BlogPostCardProps> = ({ 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

View File

@@ -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<HighlightItemProps> = ({
// 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)
}

View File

@@ -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<NostrMentionLinkProps> = ({
)
}
// 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 (
<a
href={`/p/${npub}`}
className={className}
onClick={onClick}
>
@{displayName}
</a>
)
}
// 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 (
<a
href={`/p/${npub}`}
className={className}
onClick={onClick}
>
@{displayName}
</a>
)
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 (
<a
href={`/p/${npub}`}
className={className}
onClick={onClick}
>
@{displayName}
</a>
)
return renderProfileLink(pk)
}
case 'naddr': {
const { kind, pubkey: pk, identifier: addrIdentifier } = decoded.data

View File

@@ -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<ResolvedMentionProps> = ({ 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) {

View File

@@ -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<RichContentProps> = ({
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<RichContentProps> = ({
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 => {

View File

@@ -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<SidebarHeaderProps> = ({ 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()

View File

@@ -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<SupporterCardProps> = ({ 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)

View File

@@ -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)

View File

@@ -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)
}
}
})

View File

@@ -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