import { useState, useEffect } 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) { const [content, setContent] = useState([]); // Process the content to render mentions, links, etc. useEffect(() => { const processContent = async () => { const text = event.content; // Regular expressions for different patterns const urlRegex = /(https?:\/\/[^\s]+)/g; const nostrRegex = /nostr:(npub1|note1|nprofile1|nevent1)([a-z0-9]+)/g; const hashtagRegex = /#(\w+)/g; // Split the content by these patterns let lastIndex = 0; const parts: React.ReactNode[] = []; // Process URLs const processUrls = () => { text.replace(urlRegex, (match, url, index) => { if (index > lastIndex) { parts.push(text.substring(lastIndex, index)); } parts.push( {url} ); lastIndex = index + match.length; return match; }); }; // Process Nostr references const processNostrRefs = () => { text.replace(nostrRegex, (match, prefix, datastring, index) => { if (index > lastIndex) { parts.push(text.substring(lastIndex, index)); } try { const nostrId = `${prefix}${datastring}`; const decoded = nip19.decode(nostrId); if (decoded.type === 'npub') { const pubkey = decoded.data as string; parts.push( ); } else if (decoded.type === 'note') { parts.push( note ); } else { // For other types, just show as a link parts.push( {match} ); } } catch (e) { // If decoding fails, just render as text parts.push(match); } lastIndex = index + match.length; return match; }); }; // Process hashtags const processHashtags = () => { text.replace(hashtagRegex, (match, tag, index) => { if (index > lastIndex) { parts.push(text.substring(lastIndex, index)); } parts.push( #{tag} ); lastIndex = index + match.length; return match; }); }; // Run all processors processUrls(); processNostrRefs(); processHashtags(); // 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); } setContent(parts); }; processContent(); }, [event]); return (
{content.length > 0 ? content : event.content}
); } // Helper component to display user mentions function NostrMention({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const displayName = author.data?.metadata?.name || pubkey.slice(0, 8); return ( @{displayName} ); }