diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx index d9b59fc..ed425be 100644 --- a/src/components/nostr/kinds/BaseEventRenderer.tsx +++ b/src/components/nostr/kinds/BaseEventRenderer.tsx @@ -1,15 +1,20 @@ import { useState } from "react"; import { NostrEvent } from "@/types/nostr"; import { UserName } from "../UserName"; -import { KindBadge } from "@/components/KindBadge"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; import { Menu, Copy, @@ -223,18 +228,6 @@ export function EventMenu({ event }: { event: NostrEvent }) { - -
- - -
-
- Open @@ -273,6 +266,164 @@ export function EventMenu({ event }: { event: NostrEvent }) { ); } +/** + * Event context menu - same actions as EventMenu but triggered by right-click + * Used for generic event renderers that don't have a built-in menu button + */ +export function EventContextMenu({ + event, + children, +}: { + event: NostrEvent; + children: React.ReactNode; +}) { + const { addWindow } = useGrimoire(); + const { copy, copied } = useCopy(); + const [jsonDialogOpen, setJsonDialogOpen] = useState(false); + + const openEventDetail = () => { + let pointer; + // For replaceable/parameterized replaceable events, use AddressPointer + if (isAddressableKind(event.kind)) { + // Find d-tag for identifier + const dTag = getTagValue(event, "d") || ""; + pointer = { + kind: event.kind, + pubkey: event.pubkey, + identifier: dTag, + }; + } else { + // For regular events, use EventPointer + pointer = { + id: event.id, + }; + } + + addWindow("open", { pointer }); + }; + + const copyEventId = () => { + // Get relay hints from where the event has been seen + const seenRelaysSet = getSeenRelays(event); + const relays = seenRelaysSet ? Array.from(seenRelaysSet) : []; + + // For replaceable/parameterized replaceable events, encode as naddr + if (isAddressableKind(event.kind)) { + // Find d-tag for identifier + const dTag = getTagValue(event, "d") || ""; + const naddr = nip19.naddrEncode({ + kind: event.kind, + pubkey: event.pubkey, + identifier: dTag, + relays: relays, + }); + copy(naddr); + } else { + // For regular events, encode as nevent + const nevent = nip19.neventEncode({ + id: event.id, + author: event.pubkey, + relays: relays, + }); + copy(nevent); + } + }; + + const viewEventJson = () => { + setJsonDialogOpen(true); + }; + + const zapEvent = () => { + // Create event pointer for the zap + let eventPointer; + if (isAddressableKind(event.kind)) { + const dTag = getTagValue(event, "d") || ""; + eventPointer = { + kind: event.kind, + pubkey: event.pubkey, + identifier: dTag, + }; + } else { + eventPointer = { + id: event.id, + }; + } + + // Get semantic author (e.g., zapper for zaps, host for streams) + const recipientPubkey = getSemanticAuthor(event); + + // Open zap window with event context + addWindow("zap", { + recipientPubkey, + eventPointer, + }); + }; + + const openChatWindow = () => { + // Only kind 1 notes support NIP-10 thread chat + if (event.kind === 1) { + const seenRelaysSet = getSeenRelays(event); + const relays = seenRelaysSet ? Array.from(seenRelaysSet) : []; + + // Open chat with NIP-10 thread protocol + addWindow("chat", { + protocol: "nip-10", + identifier: { + type: "thread", + value: { + id: event.id, + relays, + author: event.pubkey, + kind: event.kind, + }, + relays, + }, + }); + } + }; + + return ( + + {children} + + + + Open + + + + Zap + + {event.kind === 1 && ( + + + Chat + + )} + + + {copied ? ( + + ) : ( + + )} + {copied ? "Copied!" : "Copy ID"} + + + + View JSON + + + + + ); +} + /** * Clickable event title component * Opens the event in a new window when clicked @@ -369,21 +520,23 @@ export function BaseEventContainer({ const displayPubkey = authorOverride?.pubkey || event.pubkey; return ( -
-
-
- - - {relativeTime} - + +
+
+
+ + + {relativeTime} + +
+
- + {children} +
- {children} - -
+ ); } diff --git a/src/components/nostr/kinds/index.tsx b/src/components/nostr/kinds/index.tsx index 1a8396e..d14e79b 100644 --- a/src/components/nostr/kinds/index.tsx +++ b/src/components/nostr/kinds/index.tsx @@ -236,6 +236,7 @@ const kindRenderers: Record> = { /** * Default renderer for kinds without custom implementations * Shows basic event info with raw content + * Right-click or tap menu button to access event menu */ function DefaultKindRenderer({ event }: BaseEventProps) { return ( @@ -355,6 +356,7 @@ export { BaseEventContainer, EventAuthor, EventMenu, + EventContextMenu, } from "./BaseEventRenderer"; export type { BaseEventProps } from "./BaseEventRenderer"; export { Kind1Renderer } from "./NoteRenderer";