feat: event dialog

This commit is contained in:
Alejandro Gómez
2026-03-31 18:41:28 +02:00
parent ea8d2123ba
commit 89ef0e2c5e
4 changed files with 125 additions and 15 deletions

View File

@@ -3,7 +3,7 @@ import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
import { useNostrEvent } from "@/hooks/useNostrEvent";
import { DetailKindRenderer } from "./nostr/kinds";
import { EventErrorBoundary } from "./EventErrorBoundary";
import { JsonViewer } from "./JsonViewer";
import { EventJsonDialog } from "./EventJsonDialog";
import { RelayLink } from "./nostr/RelayLink";
import { EventDetailSkeleton } from "@/components/ui/skeleton";
import { Copy, CopyCheck, FileJson, Wifi } from "lucide-react";
@@ -178,11 +178,10 @@ export function EventDetailViewer({ pointer }: EventDetailViewerProps) {
</div>
{/* JSON Viewer Dialog */}
<JsonViewer
data={event}
<EventJsonDialog
event={event}
open={showJson}
onOpenChange={setShowJson}
title="Event JSON"
/>
</div>
);

View File

@@ -0,0 +1,114 @@
import { useMemo } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useCopy } from "@/hooks/useCopy";
import { useProfile } from "@/hooks/useProfile";
import { getDisplayName } from "@/lib/nostr-utils";
import { getKindName } from "@/constants/kinds";
import { Copy, CopyCheck } from "lucide-react";
import { CodeCopyButton } from "@/components/CodeCopyButton";
import { SyntaxHighlight } from "@/components/SyntaxHighlight";
import { isAddressableKind } from "@/lib/nostr-kinds";
import { getSeenRelays } from "applesauce-core/helpers/relays";
import { getTagValue } from "applesauce-core/helpers";
import { nip19 } from "nostr-tools";
import type { NostrEvent } from "@/types/nostr";
interface EventJsonDialogProps {
event: NostrEvent;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function EventJsonDialog({
event,
open,
onOpenChange,
}: EventJsonDialogProps) {
const profile = useProfile(event.pubkey);
const { copy: copyBech32, copied: copiedBech32 } = useCopy();
const { copy: copyJson, copied: copiedJson } = useCopy();
const displayName = getDisplayName(event.pubkey, profile);
const kindName = getKindName(event.kind);
const bech32Id = useMemo(() => {
const seenRelaysSet = getSeenRelays(event);
const relays = seenRelaysSet ? Array.from(seenRelaysSet) : [];
return isAddressableKind(event.kind)
? nip19.naddrEncode({
kind: event.kind,
pubkey: event.pubkey,
identifier: getTagValue(event, "d") || "",
relays,
})
: nip19.neventEncode({
id: event.id,
author: event.pubkey,
relays,
});
}, [event]);
const jsonString = useMemo(() => JSON.stringify(event, null, 2), [event]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl max-h-[80vh] flex flex-col rounded-none">
<DialogHeader>
<DialogTitle>
{displayName} - {kindName} (kind {event.kind})
</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-3 flex-1 overflow-hidden">
{/* Nostr ID */}
<div className="flex flex-col gap-1">
<span className="text-xs font-medium text-muted-foreground">
Nostr ID
</span>
<div className="flex items-center gap-2 bg-muted p-2 rounded-sm">
<code className="font-mono text-xs truncate flex-1 min-w-0">
{bech32Id}
</code>
<button
onClick={() => copyBech32(bech32Id)}
className="flex-shrink-0 p-1 hover:bg-background/50 rounded transition-colors"
aria-label="Copy Nostr ID"
>
{copiedBech32 ? (
<CopyCheck className="size-4 text-muted-foreground" />
) : (
<Copy className="size-4 text-muted-foreground" />
)}
</button>
</div>
</div>
{/* JSON */}
<div className="flex flex-col gap-1 flex-1 overflow-hidden">
<span className="text-xs font-medium text-muted-foreground">
JSON
</span>
<div className="flex-1 overflow-auto relative">
<SyntaxHighlight
code={jsonString}
language="json"
className="bg-muted p-4 pr-10 overflow-scroll"
/>
<CodeCopyButton
onCopy={() => copyJson(jsonString)}
copied={copiedJson}
label="Copy JSON"
/>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -22,7 +22,7 @@ import {
} from "lucide-react";
import { useAddWindow } from "@/core/state";
import { useCopy } from "@/hooks/useCopy";
import { JsonViewer } from "@/components/JsonViewer";
import { EventJsonDialog } from "@/components/EventJsonDialog";
import { KindBadge } from "@/components/KindBadge";
import { EmojiPickerDialog } from "./EmojiPickerDialog";
import { nip19 } from "nostr-tools";
@@ -224,11 +224,10 @@ export function ChatMessageContextMenu({
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<JsonViewer
data={event}
<EventJsonDialog
event={event}
open={jsonDialogOpen}
onOpenChange={setJsonDialogOpen}
title={`Event ${event.id.slice(0, 8)}... - Raw JSON`}
/>
{conversation && adapter && (
<EmojiPickerDialog

View File

@@ -30,7 +30,7 @@ import { useAddWindow, useGrimoire } from "@/core/state";
import { useCopy } from "@/hooks/useCopy";
import { useAccount } from "@/hooks/useAccount";
import { useSettings } from "@/hooks/useSettings";
import { JsonViewer } from "@/components/JsonViewer";
import { EventJsonDialog } from "@/components/EventJsonDialog";
import { EmojiPickerDialog } from "@/components/chat/EmojiPickerDialog";
import { formatTimestamp } from "@/hooks/useLocale";
import { nip19 } from "nostr-tools";
@@ -295,11 +295,10 @@ export function EventMenu({
View JSON
</DropdownMenuItem>
</DropdownMenuContent>
<JsonViewer
data={event}
<EventJsonDialog
event={event}
open={jsonDialogOpen}
onOpenChange={setJsonDialogOpen}
title={`Event ${event.id.slice(0, 8)}... - Raw JSON`}
/>
</DropdownMenu>
);
@@ -475,11 +474,10 @@ export function EventContextMenu({
View JSON
</ContextMenuItem>
</ContextMenuContent>
<JsonViewer
data={event}
<EventJsonDialog
event={event}
open={jsonDialogOpen}
onOpenChange={setJsonDialogOpen}
title={`Event ${event.id.slice(0, 8)}... - Raw JSON`}
/>
</ContextMenu>
);