diff --git a/src/components/nostr/EmbeddedEvent.tsx b/src/components/nostr/EmbeddedEvent.tsx index 68b8126..2e668a0 100644 --- a/src/components/nostr/EmbeddedEvent.tsx +++ b/src/components/nostr/EmbeddedEvent.tsx @@ -1,16 +1,15 @@ +import type { EventPointer, AddressPointer } from "nostr-tools/nip19"; import { useNostrEvent } from "@/hooks/useNostrEvent"; import { KindRenderer } from "./kinds"; import { EventCardSkeleton } from "@/components/ui/skeleton"; interface EmbeddedEventProps { - /** Event ID string for regular events */ - eventId?: string; - /** AddressPointer for addressable/replaceable events */ - addressPointer?: { kind: number; pubkey: string; identifier: string }; + /** EventPointer with optional relay hints for regular events */ + eventPointer?: EventPointer; + /** AddressPointer for addressable/replaceable events (includes relay hints) */ + addressPointer?: AddressPointer; /** Callback when user clicks to open the event in new window */ - onOpen?: ( - id: string | { kind: number; pubkey: string; identifier: string }, - ) => void; + onOpen?: (id: string | AddressPointer) => void; /** Optional loading fallback */ loadingFallback?: React.ReactNode; /** Optional className for container */ @@ -20,18 +19,19 @@ interface EmbeddedEventProps { /** * Reusable component for embedding Nostr events * Handles loading state and displays the embedded event using KindRenderer + * Passes full pointer (including relay hints) for proper event resolution */ export function EmbeddedEvent({ - eventId, + eventPointer, addressPointer, onOpen, loadingFallback, className = "my-4 border border-muted rounded overflow-hidden", }: EmbeddedEventProps) { - // Determine pointer to use - const pointer = eventId || addressPointer; + // Determine pointer to use - full pointer preserves relay hints + const pointer = eventPointer || addressPointer; - // Load the event + // Load the event - passes full pointer with relay hints to useNostrEvent const event = useNostrEvent(pointer); // If event loaded, render it @@ -50,19 +50,18 @@ export function EmbeddedEvent({ // Default loading state - show clickable link if onOpen provided if (onOpen && pointer) { - const displayText = - typeof eventId === "string" - ? `@${eventId.slice(0, 8)}...` - : addressPointer - ? `@${addressPointer.identifier || addressPointer.kind}` - : "@event"; + const displayText = eventPointer + ? `@${eventPointer.id.slice(0, 8)}...` + : addressPointer + ? `@${addressPointer.identifier || addressPointer.kind}` + : "@event"; return ( { e.preventDefault(); - onOpen(pointer); + onOpen(eventPointer?.id || addressPointer!); }} className="inline-flex items-center gap-1 text-accent underline decoration-dotted break-all" > diff --git a/src/components/nostr/MarkdownContent.tsx b/src/components/nostr/MarkdownContent.tsx index 8c3b78c..8838598 100644 --- a/src/components/nostr/MarkdownContent.tsx +++ b/src/components/nostr/MarkdownContent.tsx @@ -57,9 +57,10 @@ function NostrMention({ href }: { href: string }) { ); case "note": + // note is just an event ID, wrap in EventPointer return ( { addWindow( "open", @@ -70,9 +71,10 @@ function NostrMention({ href }: { href: string }) { /> ); case "nevent": + // nevent includes full EventPointer with relay hints return ( { addWindow( "open", diff --git a/src/components/nostr/QuotedEvent.tsx b/src/components/nostr/QuotedEvent.tsx index 660acd9..624605f 100644 --- a/src/components/nostr/QuotedEvent.tsx +++ b/src/components/nostr/QuotedEvent.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import type { EventPointer, AddressPointer } from "nostr-tools/nip19"; import { useNostrEvent } from "@/hooks/useNostrEvent"; import { KindRenderer } from "./kinds"; import { UserName } from "./UserName"; @@ -7,14 +8,12 @@ import { cn } from "@/lib/utils"; import { CompactQuoteSkeleton } from "@/components/ui/skeleton"; interface QuotedEventProps { - /** Event ID string for regular events */ - eventId?: string; - /** AddressPointer for addressable/replaceable events */ - addressPointer?: { kind: number; pubkey: string; identifier: string }; + /** EventPointer with optional relay hints for regular events */ + eventPointer?: EventPointer; + /** AddressPointer for addressable/replaceable events (includes relay hints) */ + addressPointer?: AddressPointer; /** Callback when user clicks to open the event in new window */ - onOpen?: ( - id: string | { kind: number; pubkey: string; identifier: string }, - ) => void; + onOpen?: (id: string | AddressPointer) => void; /** Depth level for nesting (0 = root, 1 = first quote, 2+ = nested) */ depth?: number; /** Optional className for container */ @@ -27,7 +26,7 @@ interface QuotedEventProps { * - depth 2+: Show expandable preview only */ export function QuotedEvent({ - eventId, + eventPointer, addressPointer, onOpen, depth = 1, @@ -35,21 +34,20 @@ export function QuotedEvent({ }: QuotedEventProps) { const [isExpanded, setIsExpanded] = useState(depth < 2); - // Determine pointer to use - const pointer = eventId || addressPointer; + // Determine pointer to use - full pointer preserves relay hints + const pointer = eventPointer || addressPointer; - // Load the event + // Load the event - passes full pointer with relay hints to useNostrEvent const event = useNostrEvent(pointer); // Loading state if (!event) { if (onOpen && pointer) { - const displayText = - typeof eventId === "string" - ? `@${eventId.slice(0, 8)}...` - : addressPointer - ? `@${addressPointer.identifier || addressPointer.kind}` - : "@event"; + const displayText = eventPointer + ? `@${eventPointer.id.slice(0, 8)}...` + : addressPointer + ? `@${addressPointer.identifier || addressPointer.kind}` + : "@event"; return ( { e.preventDefault(); e.stopPropagation(); - onOpen(pointer); + onOpen(eventPointer?.id || addressPointer!); }} className="inline-flex items-center gap-1 text-accent underline decoration-dotted break-all" > diff --git a/src/components/nostr/RichText/EventEmbed.tsx b/src/components/nostr/RichText/EventEmbed.tsx index 1742b5d..22ff0c2 100644 --- a/src/components/nostr/RichText/EventEmbed.tsx +++ b/src/components/nostr/RichText/EventEmbed.tsx @@ -1,4 +1,4 @@ -import { EventPointer, AddressPointer } from "nostr-tools/nip19"; +import type { EventPointer, AddressPointer } from "nostr-tools/nip19"; import { QuotedEvent } from "../QuotedEvent"; interface EventEmbedNodeProps { @@ -11,16 +11,18 @@ interface EventEmbedNodeProps { /** * EventEmbed component for rendering quoted/embedded Nostr events * Uses QuotedEvent with depth tracking for smart expand/collapse behavior + * Passes full pointer (including relay hints) for proper event resolution */ export function EventEmbed({ node, depth = 1 }: EventEmbedNodeProps) { const { pointer } = node; + // Check if it's an EventPointer (has 'id') or AddressPointer (has 'kind' + 'pubkey') + const isEvent = "id" in pointer; + return ( ); diff --git a/src/components/nostr/kinds/HighlightDetailRenderer.tsx b/src/components/nostr/kinds/HighlightDetailRenderer.tsx index 8e1b538..de5f454 100644 --- a/src/components/nostr/kinds/HighlightDetailRenderer.tsx +++ b/src/components/nostr/kinds/HighlightDetailRenderer.tsx @@ -122,7 +122,7 @@ export function Kind9802DetailRenderer({ event }: { event: NostrEvent }) { Highlighted From { if (typeof pointer === "string") { diff --git a/src/components/nostr/kinds/RepostRenderer.tsx b/src/components/nostr/kinds/RepostRenderer.tsx index eda7bc5..3111452 100644 --- a/src/components/nostr/kinds/RepostRenderer.tsx +++ b/src/components/nostr/kinds/RepostRenderer.tsx @@ -1,4 +1,5 @@ import { Repeat2 } from "lucide-react"; +import { getEventPointerFromETag } from "applesauce-core/helpers/pointers"; import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer"; import { EmbeddedEvent } from "../EmbeddedEvent"; import { useGrimoire } from "@/core/state"; @@ -13,9 +14,9 @@ import { useGrimoire } from "@/core/state"; export function RepostRenderer({ event }: BaseEventProps) { const { addWindow } = useGrimoire(); - // Get the event being reposted (e tag) + // Get the event being reposted (e tag) with relay hints const eTag = event.tags.find((tag) => tag[0] === "e"); - const repostedEventId = eTag?.[1]; + const repostedEventPointer = eTag ? getEventPointerFromETag(eTag) : null; return ( @@ -24,9 +25,9 @@ export function RepostRenderer({ event }: BaseEventProps) { reposted - {repostedEventId && ( + {repostedEventPointer && ( { addWindow( "open", diff --git a/src/components/nostr/lists/EventRefList.tsx b/src/components/nostr/lists/EventRefList.tsx index f0543d3..9d78dcd 100644 --- a/src/components/nostr/lists/EventRefList.tsx +++ b/src/components/nostr/lists/EventRefList.tsx @@ -137,7 +137,7 @@ export function EventRefListFull({ {eventPointers.map((pointer) => ( ))}