"use client"; import { useEffect, useMemo, useState } from "react"; import { nip19 } from "nostr-tools"; import { loadNostrProfile, resolveEventFromRelays, shortenPubkey, type NostrProfile, } from "@/lib/nostr"; function njump(bech32: string): string { return `https://njump.me/${bech32}`; } // Inline @mention chip: avatar + display name, resolved live from the author's // kind:0 profile. Falls back to a shortened pubkey until (or unless) it loads. export function NostrMention({ bech32 }: { bech32: string }) { const pubkey = useMemo(() => { try { const d = nip19.decode(bech32); if (d.type === "npub") return d.data as string; if (d.type === "nprofile") return (d.data as { pubkey: string }).pubkey; } catch {} return null; }, [bech32]); const [profile, setProfile] = useState(null); useEffect(() => { if (!pubkey) return; let cancelled = false; loadNostrProfile(pubkey) .then((p) => !cancelled && setProfile(p)) .catch(() => {}); return () => { cancelled = true; }; }, [pubkey]); // Show the username; fall back to a shortened npub only when no profile. const npub = useMemo(() => { if (bech32.startsWith("npub")) return bech32; return pubkey ? nip19.npubEncode(pubkey) : bech32; }, [bech32, pubkey]); const name = profile?.name || profile?.displayName || shortenPubkey(npub); return ( {profile?.picture && ( { (e.target as HTMLImageElement).style.display = "none"; }} /> )} @{name} ); } // Compact author line used inside an embedded note. function NoteAuthor({ pubkey }: { pubkey: string }) { const [profile, setProfile] = useState(null); useEffect(() => { let cancelled = false; loadNostrProfile(pubkey) .then((p) => !cancelled && setProfile(p)) .catch(() => {}); return () => { cancelled = true; }; }, [pubkey]); const fallback = useMemo(() => { try { return shortenPubkey(nip19.npubEncode(pubkey)); } catch { return shortenPubkey(pubkey); } }, [pubkey]); const name = profile?.name || profile?.displayName || fallback; return ( {profile?.picture && ( { (e.target as HTMLImageElement).style.display = "none"; }} /> )} {name} ); } // Embedded referenced note (note/nevent/naddr). Renders as a bordered card. // Uses span/block elements (not
) so it stays valid when the reference // sits inside a markdown paragraph. export function NostrNote({ bech32 }: { bech32: string }) { const [event, setEvent] = useState(null); const [state, setState] = useState<"loading" | "done" | "error">("loading"); useEffect(() => { let cancelled = false; setState("loading"); resolveEventFromRelays(bech32) .then((ev) => { if (cancelled) return; if (ev) { setEvent(ev); setState("done"); } else { setState("error"); } }) .catch(() => !cancelled && setState("error")); return () => { cancelled = true; }; }, [bech32]); return ( {state === "loading" && ( Loading note… )} {state === "error" && ( View referenced note on njump.me )} {state === "done" && event && ( View note {(event.content || "").slice(0, 1000)} {(event.content || "").length > 1000 ? "…" : ""} )} ); } // Dispatches a `nostr:` reference to the right embed by entity type. export function NostrEntity({ bech32 }: { bech32: string }) { let type: string; try { type = nip19.decode(bech32).type; } catch { return <>nostr:{bech32}; } if (type === "npub" || type === "nprofile") return ; if (type === "note" || type === "nevent" || type === "naddr") return ; return <>nostr:{bech32}; }