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]}`;
}