From 5657c0d7cd60e16f71eee830a8a107f241593a42 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 2 Mar 2026 13:30:22 -0600 Subject: [PATCH] Improve NIP-72 community context for comments: show 'Posted in' with community preview --- src/components/CommentContext.tsx | 15 +++-- src/components/ExternalContentHeader.tsx | 76 +++++++++++++++++++++++- src/pages/PostDetailPage.tsx | 22 ++++++- 3 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/components/CommentContext.tsx b/src/components/CommentContext.tsx index ccc24ee6..9bc1abd4 100644 --- a/src/components/CommentContext.tsx +++ b/src/components/CommentContext.tsx @@ -60,14 +60,14 @@ function getEventDisplayName(event: NostrEvent): string { const title = event.tags.find(([name]) => name === 'title')?.[1]; if (title) return title; + // Try name tag (used by communities, emoji packs, etc.) + const name = event.tags.find(([name]) => name === 'name')?.[1]; + if (name) return name; + // Try d tag (addressable events use this as an identifier) const dTag = event.tags.find(([name]) => name === 'd')?.[1]; if (dTag) return dTag; - // Try name tag (used by some events) - const name = event.tags.find(([name]) => name === 'name')?.[1]; - if (name) return name; - // Fall back to kind label from EXTRA_KINDS const kindDef = EXTRA_KINDS.find((def) => def.subKinds?.some((sub) => sub.kind === event.kind) || def.kind === event.kind, @@ -181,10 +181,13 @@ function ProfileCommentContext({ pubkey, className }: { pubkey: string; classNam function GenericAddrCommentContext({ root, className }: { root: CommentRoot; className?: string }) { const { data: event, isLoading } = useAddrEvent(root.addr); + const isCommunity = root.rootKind === '34550' || root.addr?.kind === 34550; + const prefix = isCommunity ? 'Posted in' : 'Commenting on'; + if (isLoading) { return (
- Commenting on + {prefix}
); @@ -195,7 +198,7 @@ function GenericAddrCommentContext({ root, className }: { root: CommentRoot; cla return (
- Commenting on + {prefix} {link ? ( n === 'name')?.[1] + || event?.tags.find(([n]) => n === 'd')?.[1] + || 'Community'; + const communityImage = event?.tags.find(([n]) => n === 'image')?.[1]; + const communityDescription = event?.tags.find(([n]) => n === 'description')?.[1]; + const moderatorCount = event?.tags.filter(([n, , , role]) => n === 'p' && role === 'moderator').length ?? 0; + + const link = useMemo(() => { + return `/${nip19.naddrEncode({ kind: addr.kind, pubkey: addr.pubkey, identifier: addr.identifier })}`; + }, [addr]); + + if (isLoading) { + return ( +
+
+ +
+ + +
+
+
+ ); + } + + return ( + + {communityImage ? ( + {communityName} + ) : ( +
+ +
+ )} + +
+
+ + Community + {moderatorCount > 0 && ( + · {moderatorCount} mod{moderatorCount !== 1 ? 's' : ''} + )} +
+

+ {communityName} +

+ {communityDescription && ( +

+ {communityDescription} +

+ )} +
+ + ); +} + export function ProfilePreview({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; diff --git a/src/pages/PostDetailPage.tsx b/src/pages/PostDetailPage.tsx index a5b920dc..6267ab0b 100644 --- a/src/pages/PostDetailPage.tsx +++ b/src/pages/PostDetailPage.tsx @@ -71,7 +71,7 @@ import { ProfileHoverCard } from '@/components/ProfileHoverCard'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { ContentWarningGuard } from '@/components/ContentWarningGuard'; import { MutedContentGuard } from '@/components/MutedContentGuard'; -import { ExternalContentPreview, ProfilePreview } from '@/components/ExternalContentHeader'; +import { ExternalContentPreview, ProfilePreview, CommunityPreview } from '@/components/ExternalContentHeader'; import { getParentEventId, isReplyEvent } from '@/lib/nostrEvents'; import { EmojiPackContent } from '@/components/EmojiPackContent'; import { CommunityContent } from '@/components/CommunityContent'; @@ -851,6 +851,21 @@ function PostDetailContent({ event }: { event: NostrEvent }) { return parts[1] || undefined; }, [event, isComment]); + // For kind 1111 comments on a community (kind 34550), extract the addr for the community preview + const communityRootAddr = useMemo(() => { + if (!isComment) return undefined; + const kTag = event.tags.find(([n]) => n === 'K')?.[1]; + if (kTag !== '34550') return undefined; + const aTag = event.tags.find(([n]) => n === 'A')?.[1]; + if (!aTag) return undefined; + const parts = aTag.split(':'); + const kind = parseInt(parts[0], 10); + const pubkey = parts[1]; + const identifier = parts.slice(2).join(':'); + if (!pubkey || isNaN(kind)) return undefined; + return { kind, pubkey, identifier }; + }, [event, isComment]); + // Keep the focused post pinned to top while ancestor content loads above it. // A ResizeObserver on the ancestor container re-scrolls on every layout shift // (image loads, skeleton→content swaps) for the first few seconds. @@ -900,13 +915,16 @@ function PostDetailContent({ event }: { event: NostrEvent }) { return (
- {/* Content preview for kind 1111 comments: external content or profile */} + {/* Content preview for kind 1111 comments: external content, profile, or community */} {externalIdentifier && ( )} {profileRootPubkey && ( )} + {communityRootAddr && ( + + )} {/* Ancestor thread chain if this is a reply */} {parentEventId && (