diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index da41a97..85fcd87 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -123,16 +123,9 @@ export function ChatViewer({ return (
{/* Header with conversation info and controls */} -
+
- {conversation.metadata?.icon && ( - {conversation.title} - )}

{customTitle || conversation.title} diff --git a/src/components/nostr/GroupLink.tsx b/src/components/nostr/GroupLink.tsx index 810b962..35036af 100644 --- a/src/components/nostr/GroupLink.tsx +++ b/src/components/nostr/GroupLink.tsx @@ -1,22 +1,21 @@ import { MessageSquare } from "lucide-react"; import { useGrimoire } from "@/core/state"; import { cn } from "@/lib/utils"; -import { use$ } from "applesauce-react/hooks"; -import { map } from "rxjs/operators"; -import eventStore from "@/services/event-store"; import { getTagValue } from "applesauce-core/helpers"; +import type { NostrEvent } from "@/types/nostr"; /** - * Format group identifier for display - * Shows just the group-id part without the relay URL + * Format relay URL for display + * Removes protocol and trailing slash */ -function formatGroupIdForDisplay(groupId: string): string { - return groupId; +function formatRelayForDisplay(url: string): string { + return url.replace(/^wss?:\/\//, "").replace(/\/$/, ""); } export interface GroupLinkProps { groupId: string; relayUrl: string; + metadata?: NostrEvent; // Optional pre-loaded metadata className?: string; iconClassname?: string; } @@ -25,35 +24,36 @@ export interface GroupLinkProps { * GroupLink - Clickable NIP-29 group component * Displays group name (from kind 39000 metadata) or group ID * Opens chat window on click + * + * Special case: "_" group ID represents the unmanaged relay top-level group */ export function GroupLink({ groupId, relayUrl, + metadata, className, iconClassname, }: GroupLinkProps) { const { addWindow } = useGrimoire(); - // Try to fetch group metadata (kind 39000) from EventStore - // NIP-29 metadata events use #d tag with group ID - const groupMetadata = use$( - () => - eventStore - .timeline([{ kinds: [39000], "#d": [groupId], limit: 1 }]) - .pipe(map((events) => events[0])), - [groupId], - ); + // Handle special case: "_" is the unmanaged relay top-level group + const isUnmanagedGroup = groupId === "_"; // Extract group name from metadata if available - const groupName = - groupMetadata && groupMetadata.kind === 39000 - ? getTagValue(groupMetadata, "name") || groupId - : groupId; + let groupName: string; + if (isUnmanagedGroup) { + // For "_" groups, show the relay name + groupName = formatRelayForDisplay(relayUrl); + } else if (metadata && metadata.kind === 39000) { + groupName = getTagValue(metadata, "name") || groupId; + } else { + groupName = groupId; + } - // Extract group icon if available + // Extract group icon if available (not applicable for "_" groups) const groupIcon = - groupMetadata && groupMetadata.kind === 39000 - ? getTagValue(groupMetadata, "picture") + !isUnmanagedGroup && metadata && metadata.kind === 39000 + ? getTagValue(metadata, "picture") : undefined; const handleClick = () => { @@ -68,8 +68,6 @@ export function GroupLink({ }); }; - const displayName = formatGroupIdForDisplay(groupName); - return (
)} - {displayName} + {groupName}

); diff --git a/src/components/nostr/kinds/PublicChatsRenderer.tsx b/src/components/nostr/kinds/PublicChatsRenderer.tsx index 03d5f20..f2bf46d 100644 --- a/src/components/nostr/kinds/PublicChatsRenderer.tsx +++ b/src/components/nostr/kinds/PublicChatsRenderer.tsx @@ -1,5 +1,9 @@ +import { use$ } from "applesauce-react/hooks"; +import { map } from "rxjs/operators"; import { BaseEventProps, BaseEventContainer } from "./BaseEventRenderer"; import { GroupLink } from "../GroupLink"; +import eventStore from "@/services/event-store"; +import type { NostrEvent } from "@/types/nostr"; /** * Extract group references from a kind 10009 event @@ -27,10 +31,35 @@ function extractGroups(event: { tags: string[][] }): Array<{ * Public Chats Renderer (Kind 10009) * NIP-51 list of NIP-29 groups * Displays each group as a clickable link with icon and name + * Batch-loads metadata for all groups to show their names */ export function PublicChatsRenderer({ event }: BaseEventProps) { const groups = extractGroups(event); + // Batch-load metadata for all groups at once + // Filter out "_" which is the unmanaged relay group (doesn't have metadata) + const groupIds = groups.map((g) => g.groupId).filter((id) => id !== "_"); + + const groupMetadataMap = use$( + () => + groupIds.length > 0 + ? eventStore.timeline([{ kinds: [39000], "#d": groupIds }]).pipe( + map((events) => { + const metadataMap = new Map(); + for (const evt of events) { + // Extract group ID from #d tag + const dTag = evt.tags.find((t) => t[0] === "d"); + if (dTag && dTag[1]) { + metadataMap.set(dTag[1], evt); + } + } + return metadataMap; + }), + ) + : undefined, + [groupIds.join(",")], + ); + if (groups.length === 0) { return ( @@ -49,6 +78,7 @@ export function PublicChatsRenderer({ event }: BaseEventProps) { key={`${group.relayUrl}'${group.groupId}`} groupId={group.groupId} relayUrl={group.relayUrl} + metadata={groupMetadataMap?.get(group.groupId)} /> ))}