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";