Files
boris/src/components/RichContent.tsx
Gigi ee7df54d87 refactor(profiles): standardize profile name extraction and improve code quality
- Create centralized profileUtils.ts with extractProfileDisplayName function
- Standardize profile name priority order: name || display_name || nip05 || fallback
- Replace duplicate profile parsing code across 6+ locations
- Add request deduplication to fetchProfiles to prevent duplicate relay requests
- Simplify RAF batching logic in useProfileLabels with helper functions
- Fix RichContent.tsx error when content.split() produces undefined parts
- Remove unused eventCount variable in profileService
- Fix React Hook dependency warnings by wrapping scheduleBatchedUpdate in useCallback
2025-11-02 22:21:43 +01:00

101 lines
2.7 KiB
TypeScript

import React from 'react'
import NostrMentionLink from './NostrMentionLink'
import { Tokens } from 'applesauce-content/helpers'
// Helper to add timestamps to error logs
const ts = () => {
const now = new Date()
const ms = now.getMilliseconds().toString().padStart(3, '0')
return `${now.toLocaleTimeString('en-US', { hour12: false })}.${ms}`
}
interface RichContentProps {
content: string
className?: string
}
/**
* Component to render text content with:
* - Clickable links
* - Resolved nostr mentions (npub, nprofile, note, nevent, naddr)
* - Plain text
*
* Handles both nostr:npub1... and plain npub1... formats
*/
const RichContent: React.FC<RichContentProps> = ({
content,
className = 'bookmark-content'
}) => {
try {
// Pattern to match:
// 1. nostr: URIs (nostr:npub1..., nostr:note1..., etc.) using applesauce Tokens.nostrLink
// 2. http(s) URLs
const nostrPattern = Tokens.nostrLink
const urlPattern = /https?:\/\/[^\s]+/gi
const combinedPattern = new RegExp(`(${nostrPattern.source}|${urlPattern.source})`, 'gi')
const parts = content.split(combinedPattern)
// Helper to check if a string is a nostr identifier (without mutating regex state)
const isNostrIdentifier = (str: string): boolean => {
const testPattern = new RegExp(nostrPattern.source, nostrPattern.flags)
return testPattern.test(str)
}
return (
<div className={className}>
{parts.map((part, index) => {
// Skip empty or undefined parts
if (!part) {
return null
}
// Handle nostr: URIs - Tokens.nostrLink matches both formats
if (part.startsWith('nostr:')) {
return (
<NostrMentionLink
key={index}
nostrUri={part}
/>
)
}
// Handle plain nostr identifiers (Tokens.nostrLink matches these too)
if (isNostrIdentifier(part)) {
return (
<NostrMentionLink
key={index}
nostrUri={`nostr:${part}`}
/>
)
}
// Handle http(s) URLs
if (part.match(/^https?:\/\//)) {
return (
<a
key={index}
href={part}
className="nostr-link"
target="_blank"
rel="noopener noreferrer"
>
{part}
</a>
)
}
// Plain text
return <React.Fragment key={index}>{part}</React.Fragment>
})}
</div>
)
} catch (err) {
console.error(`[${ts()}] [npub-resolve] RichContent: Error rendering:`, err)
return <div className={className}>Error rendering content</div>
}
}
export default RichContent