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)
This commit is contained in:
Claude
2026-01-12 18:30:12 +00:00
parent 7d460352e3
commit 8c4dbe28f9
2 changed files with 93 additions and 0 deletions

View File

@@ -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 (
<BaseEventContainer event={event}>
{/* Link to original live activity */}
{addressPointer && (
<button
onClick={handleActivityClick}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-1 cursor-crosshair"
>
<Video className="size-3" />
<span className="truncate">
{activityTitle || `Live chat with ${hostName}`}
</span>
</button>
)}
{/* Message content with rich text */}
<RichText event={event} className="text-sm" depth={depth} />
</BaseEventContainer>
);
}
// Export with human-readable name as primary
export { LiveChatMessageRenderer as Kind1311Renderer };

View File

@@ -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<number, React.ComponentType<BaseEventProps>> = {
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";