mirror of
https://github.com/dergigi/boris.git
synced 2026-02-01 21:24:21 +01:00
- 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
101 lines
2.7 KiB
TypeScript
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
|
|
|