From 8c4dbe28f9e9873a19cfcddd55f1fa0761b7d279 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 18:30:12 +0000 Subject: [PATCH] feat: add rich text rendering for live chat messages (kind 1311) - Created LiveChatMessageRenderer component for NIP-53 live chat messages - Displays messages with RichText component for full formatting support - Links to parent live activity event (kind 30311) with clickable header - Shows activity title or fallback to "Live chat with [host]" - Registered kind 1311 in renderer registry - Exported both human-readable name (LiveChatMessageRenderer) and kind alias (Kind1311Renderer) --- .../nostr/kinds/LiveChatMessageRenderer.tsx | 87 +++++++++++++++++++ src/components/nostr/kinds/index.tsx | 6 ++ 2 files changed, 93 insertions(+) create mode 100644 src/components/nostr/kinds/LiveChatMessageRenderer.tsx diff --git a/src/components/nostr/kinds/LiveChatMessageRenderer.tsx b/src/components/nostr/kinds/LiveChatMessageRenderer.tsx new file mode 100644 index 0000000..fe98098 --- /dev/null +++ b/src/components/nostr/kinds/LiveChatMessageRenderer.tsx @@ -0,0 +1,87 @@ +import { useMemo } from "react"; +import { RichText } from "../RichText"; +import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer"; +import { useNostrEvent } from "@/hooks/useNostrEvent"; +import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; +import { getDisplayName } from "@/lib/nostr-utils"; +import { useProfile } from "@/hooks/useProfile"; +import { Video } from "lucide-react"; +import { useGrimoire } from "@/core/state"; + +/** + * Renderer for Kind 1311 - Live Chat Message (NIP-53) + * Displays live chat messages from live streaming events with rich text formatting + * and a link to the original live activity + */ +export function LiveChatMessageRenderer({ event, depth = 0 }: BaseEventProps) { + const { addWindow } = useGrimoire(); + + // Get the 'a' tag pointing to the live activity (kind 30311) + const aTag = event.tags.find((tag) => tag[0] === "a"); + const activityAddress = aTag?.[1]; // Format: kind:pubkey:d-tag + + // Parse the address pointer + const addressPointer = useMemo( + () => (activityAddress ? parseReplaceableAddress(activityAddress) : null), + [activityAddress], + ); + + // Fetch the live activity event + const liveActivity = useNostrEvent( + addressPointer + ? { + kind: addressPointer.kind, + pubkey: addressPointer.pubkey, + identifier: addressPointer.identifier || "", + relays: [], + } + : undefined, + ); + + // Get host profile for display name + const hostProfile = useProfile(addressPointer?.pubkey); + const hostName = hostProfile + ? getDisplayName(addressPointer!.pubkey, hostProfile) + : addressPointer?.pubkey.slice(0, 8); + + // Get live activity title from tags + const activityTitle = liveActivity?.tags.find((t) => t[0] === "title")?.[1]; + + const handleActivityClick = () => { + if (!addressPointer) return; + addWindow( + "open", + { + pointer: { + kind: addressPointer.kind, + pubkey: addressPointer.pubkey, + identifier: addressPointer.identifier || "", + }, + }, + activityTitle || "Live Activity", + ); + }; + + return ( + + {/* Link to original live activity */} + {addressPointer && ( + + )} + + {/* Message content with rich text */} + + + ); +} + +// Export with human-readable name as primary +export { LiveChatMessageRenderer as Kind1311Renderer }; diff --git a/src/components/nostr/kinds/index.tsx b/src/components/nostr/kinds/index.tsx index a5833b0..5f7411a 100644 --- a/src/components/nostr/kinds/index.tsx +++ b/src/components/nostr/kinds/index.tsx @@ -7,6 +7,7 @@ import { Kind3DetailView } from "./ContactListRenderer"; import { RepostRenderer } from "./RepostRenderer"; import { Kind7Renderer } from "./ReactionRenderer"; import { Kind9Renderer } from "./ChatMessageRenderer"; +import { LiveChatMessageRenderer } from "./LiveChatMessageRenderer"; import { Kind20Renderer } from "./PictureRenderer"; import { Kind21Renderer } from "./VideoRenderer"; import { Kind22Renderer } from "./ShortVideoRenderer"; @@ -85,6 +86,7 @@ const kindRenderers: Record> = { 1063: Kind1063Renderer, // File Metadata (NIP-94) 1111: Kind1111Renderer, // Post (NIP-22) 1222: VoiceMessageRenderer, // Voice Message (NIP-A0) + 1311: LiveChatMessageRenderer, // Live Chat Message (NIP-53) 1244: VoiceMessageRenderer, // Voice Message Reply (NIP-A0) 1337: Kind1337Renderer, // Code Snippet (NIP-C0) 1617: PatchRenderer, // Patch (NIP-34) @@ -227,6 +229,10 @@ export { } from "./RepostRenderer"; export { Kind7Renderer } from "./ReactionRenderer"; export { Kind9Renderer } from "./ChatMessageRenderer"; +export { + LiveChatMessageRenderer, + Kind1311Renderer, +} from "./LiveChatMessageRenderer"; export { Kind20Renderer } from "./PictureRenderer"; export { Kind21Renderer } from "./VideoRenderer"; export { Kind22Renderer } from "./ShortVideoRenderer";