diff --git a/src/components/nostr/LinkPreview/PlainLink.tsx b/src/components/nostr/LinkPreview/PlainLink.tsx index a0f439a..401ba7d 100644 --- a/src/components/nostr/LinkPreview/PlainLink.tsx +++ b/src/components/nostr/LinkPreview/PlainLink.tsx @@ -3,12 +3,17 @@ interface PlainLinkProps { } export function PlainLink({ url }: PlainLinkProps) { + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + return ( {url} diff --git a/src/components/nostr/MediaEmbed.tsx b/src/components/nostr/MediaEmbed.tsx index 715d25d..7d4a8c7 100644 --- a/src/components/nostr/MediaEmbed.tsx +++ b/src/components/nostr/MediaEmbed.tsx @@ -150,6 +150,11 @@ export function MediaEmbed({ // Audio rendering if (mediaType === "audio") { + const handleAudioClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onAudioClick) onAudioClick(); + }; + return (
{!onAudioClick ? ( diff --git a/src/components/nostr/QuotedEvent.tsx b/src/components/nostr/QuotedEvent.tsx index 1e8b8f0..c1b036b 100644 --- a/src/components/nostr/QuotedEvent.tsx +++ b/src/components/nostr/QuotedEvent.tsx @@ -55,6 +55,7 @@ export function QuotedEvent({ href="#" onClick={(e) => { e.preventDefault(); + e.stopPropagation(); onOpen(pointer); }} className="inline-flex items-center gap-1 text-accent underline decoration-dotted break-all" @@ -95,7 +96,10 @@ export function QuotedEvent({ > {/* Preview header - always visible */}
)} diff --git a/src/components/nostr/kinds/Kind9Renderer.tsx b/src/components/nostr/kinds/Kind9Renderer.tsx new file mode 100644 index 0000000..40d5c08 --- /dev/null +++ b/src/components/nostr/kinds/Kind9Renderer.tsx @@ -0,0 +1,62 @@ +import { RichText } from "../RichText"; +import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer"; +import { useNostrEvent } from "@/hooks/useNostrEvent"; +import { UserName } from "../UserName"; +import { MessageCircle } from "lucide-react"; +import { useGrimoire } from "@/core/state"; +import { getTagValues } from "@/lib/nostr-utils"; +import { isValidHexEventId } from "@/lib/nostr-validation"; + +/** + * Renderer for Kind 9 - Chat Message (NIP-C7) + * Displays chat messages with optional quoted parent message + */ +export function Kind9Renderer({ event, depth = 0 }: BaseEventProps) { + const { addWindow } = useGrimoire(); + + // Parse 'q' tag for quoted parent message (NIP-C7 reply format) + const quotedEventIds = getTagValues(event, "q"); + const quotedEventId = quotedEventIds[0]; // First q tag + const parentEvent = useNostrEvent(quotedEventId); + + const handleQuoteClick = () => { + if (!parentEvent || !quotedEventId) return; + const pointer = isValidHexEventId(quotedEventId) + ? { + id: quotedEventId, + } + : quotedEventId; + + addWindow( + "open", + { pointer }, + `Quoted message from ${parentEvent.pubkey.slice(0, 8)}...`, + ); + }; + + return ( + + {/* Show quoted parent message if this is a reply */} + {quotedEventId && parentEvent && parentEvent.kind === 9 && ( +
+ +
+ + + + +
+
+ )} + + {/* Main message content */} + +
+ ); +} diff --git a/src/components/nostr/kinds/index.tsx b/src/components/nostr/kinds/index.tsx index 55bd849..7e5780a 100644 --- a/src/components/nostr/kinds/index.tsx +++ b/src/components/nostr/kinds/index.tsx @@ -3,6 +3,7 @@ import { Kind1Renderer } from "./Kind1Renderer"; import { Kind3Renderer } from "./Kind3Renderer"; import { RepostRenderer } from "./RepostRenderer"; import { Kind7Renderer } from "./Kind7Renderer"; +import { Kind9Renderer } from "./Kind9Renderer"; import { Kind20Renderer } from "./Kind20Renderer"; import { Kind21Renderer } from "./Kind21Renderer"; import { Kind22Renderer } from "./Kind22Renderer"; @@ -26,6 +27,7 @@ const kindRenderers: Record> = { 3: Kind3Renderer, // Contact List 6: RepostRenderer, // Repost 7: Kind7Renderer, // Reaction + 9: Kind9Renderer, // Chat Message (NIP-C7) 16: RepostRenderer, // Generic Repost 20: Kind20Renderer, // Picture (NIP-68) 21: Kind21Renderer, // Video Event (NIP-71) @@ -96,6 +98,7 @@ export { Kind16Renderer, } from "./RepostRenderer"; export { Kind7Renderer } from "./Kind7Renderer"; +export { Kind9Renderer } from "./Kind9Renderer"; export { Kind20Renderer } from "./Kind20Renderer"; export { Kind21Renderer } from "./Kind21Renderer"; export { Kind22Renderer } from "./Kind22Renderer";