import { useMemo } from 'react'; import { type NostrEvent } from '@nostrify/nostrify'; import { Link } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { useAuthor } from '@/hooks/useAuthor'; import { cn } from '@/lib/utils'; interface NoteContentProps { event: NostrEvent; className?: string; } /** Parses content of text note events so that URLs and hashtags are linkified. */ export function NoteContent({ event, className, }: NoteContentProps) { // Process the content to render mentions, links, etc. const content = useMemo(() => { const text = event.content; // Regex to find URLs, Nostr references, and hashtags const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([023456789acdefghjklmnpqrstuvwxyz]+)|(#\w+)/g; const parts: React.ReactNode[] = []; let lastIndex = 0; let match: RegExpExecArray | null; let keyCounter = 0; while ((match = regex.exec(text)) !== null) { const [fullMatch, url, nostrPrefix, nostrData, hashtag] = match; const index = match.index; // Add text before this match if (index > lastIndex) { parts.push(text.substring(lastIndex, index)); } if (url) { // Handle URLs parts.push( {url} ); } else if (nostrPrefix && nostrData) { // Handle Nostr references try { const nostrId = `${nostrPrefix}${nostrData}`; const decoded = nip19.decode(nostrId); if (decoded.type === 'npub') { const pubkey = decoded.data; parts.push( ); } else { // For other types, just show as a link parts.push( {fullMatch} ); } } catch (e) { // If decoding fails, just render as text parts.push(fullMatch); } } else if (hashtag) { // Handle hashtags const tag = hashtag.slice(1); // Remove the # parts.push( {hashtag} ); } lastIndex = index + fullMatch.length; } // Add any remaining text if (lastIndex < text.length) { parts.push(text.substring(lastIndex)); } // If no special content was found, just use the plain text if (parts.length === 0) { parts.push(text); } return parts; }, [event]); return (
{content.length > 0 ? content : event.content}
); } // Helper component to display user mentions function NostrMention({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const npub = nip19.npubEncode(pubkey); const hasRealName = !!author.data?.metadata?.name; const displayName = author.data?.metadata?.name ?? generateDeterministicName(pubkey); return ( @{displayName} ); } // Generate a deterministic name based on pubkey function generateDeterministicName(pubkey: string): string { // Use a simple hash of the pubkey to generate consistent adjective + noun combinations const adjectives = [ 'Swift', 'Bright', 'Calm', 'Bold', 'Wise', 'Kind', 'Quick', 'Brave', 'Cool', 'Sharp', 'Clear', 'Strong', 'Smart', 'Fast', 'Keen', 'Pure', 'Noble', 'Gentle', 'Fierce', 'Steady', 'Clever', 'Proud', 'Silent', 'Wild' ]; const nouns = [ 'Fox', 'Eagle', 'Wolf', 'Bear', 'Lion', 'Tiger', 'Hawk', 'Owl', 'Deer', 'Raven', 'Falcon', 'Lynx', 'Otter', 'Whale', 'Shark', 'Dolphin', 'Phoenix', 'Dragon', 'Panther', 'Jaguar', 'Cheetah', 'Leopard', 'Puma', 'Cobra' ]; // Create a simple hash from the pubkey let hash = 0; for (let i = 0; i < pubkey.length; i++) { const char = pubkey.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } // Use absolute value to ensure positive index const adjIndex = Math.abs(hash) % adjectives.length; const nounIndex = Math.abs(hash >> 8) % nouns.length; return `${adjectives[adjIndex]}${nouns[nounIndex]}`; }