From 42ed8971e2e72a65acdcc6529e316423c57735db Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 16 Dec 2025 14:29:16 +0100 Subject: [PATCH 01/22] Added line-clamp to comment moderation ref https://linear.app/ghost/issue/FEA-491/design-truncation-and-display-for-long-comments - Longer comments in the comments UI need better truncation and display treatment. The PR clamps the original comment to show max 2 lines and make it possible to view the full comment. --- .../comments/components/comments-list.tsx | 88 +++++++++++++++---- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 62d3a5ef1fd..a51d93b3d71 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -22,7 +22,7 @@ import { TableRow } from '@tryghost/shade'; import {Comment, useDeleteComment, useHideComment, useShowComment} from '@tryghost/admin-x-framework/api/comments'; -import {forwardRef, useRef, useState} from 'react'; +import {forwardRef, useEffect, useRef, useState} from 'react'; import {useInfiniteVirtualScroll} from '@components/virtual-table/use-infinite-virtual-scroll'; const SpacerRow = ({height}: { height: number }) => ( @@ -61,6 +61,68 @@ function formatDate(dateString: string): string { }).format(date); } +function CommentContent({html, item}: {html: string; item: Comment}) { + const contentRef = useRef(null); + const [isClamped, setIsClamped] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + + useEffect(() => { + const checkIfClamped = () => { + if (contentRef.current) { + // Check if the content is clamped by comparing scrollHeight with clientHeight + setIsClamped(contentRef.current.scrollHeight > contentRef.current.clientHeight); + } + }; + + checkIfClamped(); + // Recheck on window resize + window.addEventListener('resize', checkIfClamped); + return () => window.removeEventListener('resize', checkIfClamped); + }, [html]); + + return ( +
+
+
+
+ {item.status === 'hidden' && ( +
+ + Comment hidden +
+ )} + {isClamped && !isExpanded && ( + + )} + {isExpanded && ( + + )} +
+
+
+ ); +} + function CommentsList({ items, totalItems, @@ -137,21 +199,13 @@ function CommentsList({ data-testid="comment-list-row" > - {item.status === 'hidden' - ? ( -
- -
-
- ) : ( -
- )} - {(item.count?.reports && item.count.reports > 0) ? ( -
- - {item.count.reports} {item.count.reports === 1 ? 'report' : 'reports'} -
- ) : null} + {item.html ? ( + + ) : ( + + Deleted comment + + )} {item.member?.id ? ( @@ -183,7 +237,7 @@ function CommentsList({ - {item.created_at && + {item.created_at && formatDate(item.created_at) } From f368918e877000c3d7250cc019b1a2edc785a339 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 10:07:23 +0100 Subject: [PATCH 02/22] Factored out expand button --- .../comments/components/comments-list.tsx | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index a51d93b3d71..8c1a5422f66 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -61,6 +61,20 @@ function formatDate(dateString: string): string { }).format(date); } +function ExpandButton({onClick, expanded}: {onClick: () => void; expanded: boolean}) { + return ( + + ); +} + function CommentContent({html, item}: {html: string; item: Comment}) { const contentRef = useRef(null); const [isClamped, setIsClamped] = useState(false); @@ -95,27 +109,8 @@ function CommentContent({html, item}: {html: string; item: Comment}) { Comment hidden
)} - {isClamped && !isExpanded && ( - - )} - {isExpanded && ( - + {isClamped && ( + setIsExpanded(!isExpanded)} /> )}
From f158085135e62bf3f442a271542effbc3296ed96 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 10:18:15 +0100 Subject: [PATCH 03/22] Refined hidden indicator --- .../comments/components/comments-list.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 8c1a5422f66..b14ccc49f38 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -97,22 +97,19 @@ function CommentContent({html, item}: {html: string; item: Comment}) { return (
-
-
- {item.status === 'hidden' && ( -
- - Comment hidden + + {item.status === 'hidden' + ? ( +
+ +
+ ) : ( +
)} - {isClamped && ( - setIsExpanded(!isExpanded)} /> - )} -
+ {isClamped && ( + setIsExpanded(!isExpanded)} /> + )}
); From c17d128868341d26452430c49372084fa1b66942 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 10:23:00 +0100 Subject: [PATCH 04/22] Back to the original idea --- .../comments/components/comments-list.tsx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index b14ccc49f38..0724a2fcc1a 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -97,19 +97,22 @@ function CommentContent({html, item}: {html: string; item: Comment}) { return (
- - {item.status === 'hidden' - ? ( -
- -
+
+
+ {item.status === 'hidden' && ( +
+ + Comment hidden
- ) : ( -
)} - {isClamped && ( - setIsExpanded(!isExpanded)} /> - )} + {isClamped && ( + setIsExpanded(!isExpanded)} /> + )} +
); From 95dd955b52b3e22be0ab0bf426511dce004c389f Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 12:48:53 +0100 Subject: [PATCH 05/22] Updated to semi-list layout --- .../comments/components/comments-list.tsx | 129 ++++++++---------- 1 file changed, 59 insertions(+), 70 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 0724a2fcc1a..86e5b7f6f33 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -64,7 +64,7 @@ function formatDate(dateString: string): string { function ExpandButton({onClick, expanded}: {onClick: () => void; expanded: boolean}) { return ( )} {item.status === 'hidden' && ( )} @@ -282,16 +281,6 @@ function CommentsList({ Filter by author )} - {item.status !== 'deleted' && <> - - handleDeleteClick(item)} - > - - Delete comment - - }
From 340a25537e5a0ac4c2d474dd0286a289c6832acc Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 12:50:13 +0100 Subject: [PATCH 06/22] Removed Delete --- apps/posts/src/views/comments/components/comments-list.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 86e5b7f6f33..ee498eb93cf 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -11,7 +11,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuSeparator, DropdownMenuTrigger, LucideIcon, Table, @@ -142,10 +141,6 @@ function CommentsList({ const {mutate: deleteComment} = useDeleteComment(); const [commentToDelete, setCommentToDelete] = useState(null); - const handleDeleteClick = (comment: Comment) => { - setCommentToDelete(comment); - }; - const confirmDelete = () => { if (commentToDelete) { deleteComment({id: commentToDelete.id}); From 425b5cbe3207e06df7cfc1b3d280864c244d6264 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 13:16:31 +0100 Subject: [PATCH 07/22] Added avatar --- .../comments/components/comments-list.tsx | 43 ++++++++---- .../src/components/ui/avatar.stories.tsx | 47 +++++++++++--- apps/shade/src/components/ui/avatar.tsx | 65 +++++++++++++++---- 3 files changed, 120 insertions(+), 35 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index ee498eb93cf..0a068f03302 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -7,6 +7,9 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, + Avatar, + AvatarFallback, + AvatarImage, Button, DropdownMenu, DropdownMenuContent, @@ -181,20 +184,32 @@ function CommentsList({
- {item.member?.id ? ( - - {item.member.name || 'Unknown'} - - ) : ( - - {item.member?.name || 'Unknown'} - - )} +
+ {item.member?.id ? ( + <> + + {item.member.avatar_image && ( + + )} + + + + + + {item.member.name || 'Unknown'} + + + ) : ( + + {item.member?.name || 'Unknown'} + + )} +
- +
@@ -206,7 +221,7 @@ function CommentsList({ {item.post?.id && item.post?.title ? ( {item.post.title} diff --git a/apps/shade/src/components/ui/avatar.stories.tsx b/apps/shade/src/components/ui/avatar.stories.tsx index 145cc38faa4..25da819f1d9 100644 --- a/apps/shade/src/components/ui/avatar.stories.tsx +++ b/apps/shade/src/components/ui/avatar.stories.tsx @@ -67,22 +67,53 @@ export const IconAsFallback: Story = { export const DifferentSizes: Story = { render: () => (
- - XS + + XS - - SM + + SM + + + MD - - MD + + LG - - LG + + XL
) }; +export const ExtraSmall: Story = { + args: { + size: 'xs', + children: XS + } +}; + +export const Small: Story = { + args: { + size: 'sm', + children: SM + } +}; + +export const Large: Story = { + args: { + size: 'lg', + children: LG + } +}; + +export const ExtraLarge: Story = { + args: { + size: 'xl', + children: XL + } +}; + export const MultipleAvatars: Story = { render: () => (
diff --git a/apps/shade/src/components/ui/avatar.tsx b/apps/shade/src/components/ui/avatar.tsx index a8fedeae7b4..b51ff8f0aa9 100644 --- a/apps/shade/src/components/ui/avatar.tsx +++ b/apps/shade/src/components/ui/avatar.tsx @@ -1,18 +1,38 @@ import * as React from 'react'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import {cva, type VariantProps} from 'class-variance-authority'; import {cn} from '@/lib/utils'; +const avatarVariants = cva( + 'relative flex shrink-0 overflow-hidden rounded-full', + { + variants: { + size: { + xs: 'size-4', + sm: 'size-6', + default: 'size-8', + lg: 'size-10', + xl: 'size-12' + } + }, + defaultVariants: { + size: 'default' + } + } +); + +export interface AvatarProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + const Avatar = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({className, ...props}, ref) => ( + AvatarProps +>(({className, size, ...props}, ref) => ( )); @@ -30,19 +50,38 @@ const AvatarImage = React.forwardRef< )); AvatarImage.displayName = AvatarPrimitive.Image.displayName; +const avatarFallbackVariants = cva( + 'flex size-full items-center justify-center rounded-full bg-muted', + { + variants: { + size: { + xs: 'text-[8px] [&_svg]:size-2', + sm: 'text-[10px] [&_svg]:size-3', + default: 'text-xs [&_svg]:size-4', + lg: 'text-sm [&_svg]:size-5', + xl: 'text-base [&_svg]:size-6' + } + }, + defaultVariants: { + size: 'default' + } + } +); + +export interface AvatarFallbackProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + const AvatarFallback = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({className, ...props}, ref) => ( + AvatarFallbackProps +>(({className, size, ...props}, ref) => ( )); AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; -export {Avatar, AvatarImage, AvatarFallback}; +export {Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarFallbackVariants}; From d55fca6bc3478472b08454cea9c6c7685a657dab Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 13:31:07 +0100 Subject: [PATCH 08/22] Refined spaces --- .../comments/components/comments-list.tsx | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 0a068f03302..f4af1148396 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -66,7 +66,7 @@ function formatDate(dateString: string): string { function ExpandButton({onClick, expanded}: {onClick: () => void; expanded: boolean}) { return (
); @@ -183,9 +181,15 @@ function CommentsList({
- + {item.member.name || 'Unknown'} + + + ) : ( + + {item.member?.name || 'Unknown'} + + )} @@ -218,13 +217,14 @@ function CommentsList({ on - {item.post?.id && item.post?.title ? ( - onAddFilter('post', item.post!.id)} > {item.post.title} - + ) : ( Unknown post @@ -275,16 +275,12 @@ function CommentsList({ )} - {item.post?.id && onAddFilter && ( - onAddFilter('post', item.post!.id)}> - - Filter by post - - )} {item.member?.id && onAddFilter && ( - onAddFilter('author', item.member!.id)}> - - Filter by author + + + + View member + )} From d61d43f258314c8d2e2fdaecc510b0c262ed5659 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 13:50:29 +0100 Subject: [PATCH 10/22] Added timestamp & tooltip instead of date --- .../comments/components/comments-list.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 9f624a8caae..c904ece48a5 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -21,7 +21,12 @@ import { TableCell, TableHead, TableHeader, - TableRow + TableRow, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, + formatTimestamp } from '@tryghost/shade'; import {Comment, useDeleteComment, useHideComment, useShowComment} from '@tryghost/admin-x-framework/api/comments'; import {forwardRef, useEffect, useRef, useState} from 'react'; @@ -210,11 +215,20 @@ function CommentsList({
- - {item.created_at && - formatDate(item.created_at) - } - + {item.created_at && ( + + + + + {formatTimestamp(item.created_at)} + + + + {formatDate(item.created_at)} + + + + )} on {item.post?.id && item.post?.title && onAddFilter ? ( From 6ba41fdb3b91e11a619e8e5dc36d67532d364b13 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 13:54:47 +0100 Subject: [PATCH 11/22] Fixed date formatter --- apps/posts/src/views/comments/components/comments-list.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index c904ece48a5..f0e33b906b8 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -59,13 +59,15 @@ const PlaceholderRow = forwardRef(function PlaceholderRow( function formatDate(dateString: string): string { const date = new Date(dateString); - return new Intl.DateTimeFormat('en-US', { + const formatted = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric' }).format(date); + // Remove comma between day and year (e.g., "Dec 17, 2025" -> "Dec 17 2025") + return formatted.replace(/(\d+),(\s+\d{4})/, '$1$2'); } function ExpandButton({onClick, expanded}: {onClick: () => void; expanded: boolean}) { From 87ed62e6f395e69b15a21f4391bd799e02c3813a Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 14:11:15 +0100 Subject: [PATCH 12/22] Aligning actions to top --- apps/posts/src/views/comments/components/comments-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index f0e33b906b8..6a2f562dafc 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -252,7 +252,7 @@ function CommentsList({
- +
{item.status === 'hidden' && (
From 56c52a6d22e5972975aeb0c8a77277912c60474c Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 14:17:19 +0100 Subject: [PATCH 13/22] Reverted avatar changes in Shade --- .../comments/components/comments-list.tsx | 2 +- .../src/components/ui/avatar.stories.tsx | 47 +++----------- apps/shade/src/components/ui/avatar.tsx | 65 ++++--------------- 3 files changed, 22 insertions(+), 92 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 6a2f562dafc..719d04ed386 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -197,7 +197,7 @@ function CommentsList({ onAddFilter('author', item.member!.id); }} > - + {item.member.avatar_image && ( )} diff --git a/apps/shade/src/components/ui/avatar.stories.tsx b/apps/shade/src/components/ui/avatar.stories.tsx index 25da819f1d9..145cc38faa4 100644 --- a/apps/shade/src/components/ui/avatar.stories.tsx +++ b/apps/shade/src/components/ui/avatar.stories.tsx @@ -67,53 +67,22 @@ export const IconAsFallback: Story = { export const DifferentSizes: Story = { render: () => (
- - XS + + XS - - SM - - - MD + + SM - - LG + + MD - - XL + + LG
) }; -export const ExtraSmall: Story = { - args: { - size: 'xs', - children: XS - } -}; - -export const Small: Story = { - args: { - size: 'sm', - children: SM - } -}; - -export const Large: Story = { - args: { - size: 'lg', - children: LG - } -}; - -export const ExtraLarge: Story = { - args: { - size: 'xl', - children: XL - } -}; - export const MultipleAvatars: Story = { render: () => (
diff --git a/apps/shade/src/components/ui/avatar.tsx b/apps/shade/src/components/ui/avatar.tsx index b51ff8f0aa9..a8fedeae7b4 100644 --- a/apps/shade/src/components/ui/avatar.tsx +++ b/apps/shade/src/components/ui/avatar.tsx @@ -1,38 +1,18 @@ import * as React from 'react'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; -import {cva, type VariantProps} from 'class-variance-authority'; import {cn} from '@/lib/utils'; -const avatarVariants = cva( - 'relative flex shrink-0 overflow-hidden rounded-full', - { - variants: { - size: { - xs: 'size-4', - sm: 'size-6', - default: 'size-8', - lg: 'size-10', - xl: 'size-12' - } - }, - defaultVariants: { - size: 'default' - } - } -); - -export interface AvatarProps - extends React.ComponentPropsWithoutRef, - VariantProps {} - const Avatar = React.forwardRef< React.ElementRef, - AvatarProps ->(({className, size, ...props}, ref) => ( + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( )); @@ -50,38 +30,19 @@ const AvatarImage = React.forwardRef< )); AvatarImage.displayName = AvatarPrimitive.Image.displayName; -const avatarFallbackVariants = cva( - 'flex size-full items-center justify-center rounded-full bg-muted', - { - variants: { - size: { - xs: 'text-[8px] [&_svg]:size-2', - sm: 'text-[10px] [&_svg]:size-3', - default: 'text-xs [&_svg]:size-4', - lg: 'text-sm [&_svg]:size-5', - xl: 'text-base [&_svg]:size-6' - } - }, - defaultVariants: { - size: 'default' - } - } -); - -export interface AvatarFallbackProps - extends React.ComponentPropsWithoutRef, - VariantProps {} - const AvatarFallback = React.forwardRef< React.ElementRef, - AvatarFallbackProps ->(({className, size, ...props}, ref) => ( + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( )); AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; -export {Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarFallbackVariants}; +export {Avatar, AvatarImage, AvatarFallback}; From 05e7de5c5a784ac87762f2cd06d0f309eceef9cf Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 14:21:38 +0100 Subject: [PATCH 14/22] Updated onAddFilter param --- apps/posts/src/views/comments/components/comments-list.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 719d04ed386..425b48c95b6 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -132,7 +132,7 @@ function CommentsList({ hasNextPage?: boolean; isFetchingNextPage?: boolean; fetchNextPage: () => void; - onAddFilter?: (field: string, value: string, operator?: string) => void; + onAddFilter: (field: string, value: string, operator?: string) => void; }) { const parentRef = useRef(null); const {visibleItems, spaceBefore, spaceAfter} = useInfiniteVirtualScroll({ @@ -188,7 +188,7 @@ function CommentsList({
- {item.member?.id && onAddFilter ? ( + {item.member?.id ? ( <>
- + + 4 + + + 19 + + + 2 + +
{item.status === 'hidden' && (
From 14bc55a5e2784af43307d081c3363669014df5da Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 14:57:46 +0100 Subject: [PATCH 16/22] Made replies/likes/reports dynamic --- .../src/views/comments/components/comments-list.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 2699686da5b..a9ec357c7b1 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -255,14 +255,14 @@ function CommentsList({
- - 4 + + {item.count?.replies} - - 19 + + {item.count?.likes} - - 2 + + {item.count?.reports}
From 7b2950bd1b0e937a5cec82916a575abf980e9411 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 17 Dec 2025 16:10:07 +0100 Subject: [PATCH 17/22] Rearranged comment table --- apps/admin-x-framework/src/api/comments.ts | 1 + .../comments/components/comments-list.tsx | 182 +++++++++++------- .../serializers/output/mappers/comments.js | 3 +- 3 files changed, 115 insertions(+), 71 deletions(-) diff --git a/apps/admin-x-framework/src/api/comments.ts b/apps/admin-x-framework/src/api/comments.ts index e1a98c02276..426408726da 100644 --- a/apps/admin-x-framework/src/api/comments.ts +++ b/apps/admin-x-framework/src/api/comments.ts @@ -25,6 +25,7 @@ export type Comment = { title: string; slug: string; url: string; + feature_image?: string; }; count?: { replies?: number; diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index a9ec357c7b1..c73edcbf230 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -26,6 +26,7 @@ import { TooltipContent, TooltipProvider, TooltipTrigger, + formatNumber, formatTimestamp } from '@tryghost/shade'; import {Comment, useDeleteComment, useHideComment, useShowComment} from '@tryghost/admin-x-framework/api/comments'; @@ -104,12 +105,12 @@ function CommentContent({item}: {item: Comment}) { }, [item.html]); return ( -
+
{isClamped && ( setIsExpanded(!isExpanded)} /> @@ -164,11 +165,8 @@ function CommentsList({ > - Comment - Replies - Likes - Reports - + + @@ -188,19 +186,19 @@ function CommentsList({ className="grid w-full grid-cols-[1fr_5rem] items-center gap-x-4 p-2 hover:bg-muted/50 md:grid-cols-[1fr_auto_5rem] lg:table-row lg:p-0 [&.group:hover_td]:bg-transparent" data-testid="comment-list-row" > - -
+ +
{item.member?.id ? ( <>
+ {item.status === 'hidden' && ( + <> + +
+ Hidden from members +
+ + )} +
-
-
- - {item.count?.replies} - - - {item.count?.likes} - - - {item.count?.reports} - - -
- {item.status === 'hidden' && ( -
- - Hidden -
- )} - {item.status === 'published' && ( - - )} - {item.status === 'hidden' && ( - - )} - - - - - - {item.post?.url && ( - -
- - View post - - - )} - {item.member?.id && ( - - - - View member - - - )} - - + )} + {item.status === 'hidden' && ( + + )} +
+ + + +
+ + {formatNumber(item.count?.replies)} +
+
+ + Replies + +
+
+ + + + +
+ + {formatNumber(item.count?.likes)} +
+
+ + Likes + +
+
+ + + + +
+ + {formatNumber(item.count?.reports)} +
+
+ + Reports + +
+
+
+ + + + + + {item.post?.url && ( + + + + View post + + + )} + {item.member?.id && ( + + + + View member + + + )} + + +
+ + {item.post?.feature_image ? ( + {item.post.title + ) : null} + ); })} diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js index 27ba9af0244..54a53c32b67 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js @@ -35,7 +35,8 @@ const postFields = [ 'id', 'uuid', 'title', - 'url' + 'url', + 'feature_image' ]; const countFields = [ From f6fe52e0061c67037756bf2fe8dbd1847af85684 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 17 Dec 2025 15:25:46 +0000 Subject: [PATCH 18/22] limited `feature_image` post field to Admin API comments requests --- .../utils/serializers/output/mappers/comments.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js index 54a53c32b67..079cc273281 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js @@ -36,6 +36,13 @@ const postFields = [ 'uuid', 'title', 'url', +]; + +const postFieldsAdmin = [ + 'id', + 'uuid', + 'title', + 'url', 'feature_image' ]; @@ -86,7 +93,7 @@ const commentMapper = (model, frame) => { if (jsonModel.post) { // We could use the post mapper here, but we need less field + don't need all the async behavior support url.forPost(jsonModel.post.id, jsonModel.post, frame); - response.post = _.pick(jsonModel.post, postFields); + response.post = _.pick(jsonModel.post, isPublicRequest ? postFields : postFieldsAdmin); } if (jsonModel.count && jsonModel.count.liked !== undefined) { From 58e37c371b645fdc75d29e359fb5ccca56585099 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 17 Dec 2025 15:32:33 +0000 Subject: [PATCH 19/22] updated snapshots --- .../__snapshots__/activity-feed.test.js.snap | 6 +++--- .../admin/__snapshots__/comments.test.js.snap | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap index ec9fc398af5..bd7d4ed7c99 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/activity-feed.test.js.snap @@ -22699,7 +22699,7 @@ exports[`Activity Feed API Can filter events by post id 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "18190", + "content-length": "18298", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -22871,7 +22871,7 @@ exports[`Activity Feed API Filter splitting Can use NQL OR for type only 2: [hea Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "5352", + "content-length": "5460", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -23987,7 +23987,7 @@ exports[`Activity Feed API Returns comments in activity feed 2: [headers] 1`] = Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1458", + "content-length": "1566", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap index acb48b92273..05f39859a89 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/comments.test.js.snap @@ -25,6 +25,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -71,6 +72,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "https://example.com/super_photo.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "HTML Ipsum", "url": Any, @@ -100,6 +102,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -146,6 +149,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -192,6 +196,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -238,6 +243,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -267,6 +273,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -313,6 +320,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -359,6 +367,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -405,6 +414,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -451,6 +461,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -480,6 +491,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -526,6 +538,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -555,6 +568,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -601,6 +615,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -647,6 +662,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, @@ -676,6 +692,7 @@ Object { }, "parent_id": Nullable, "post": Object { + "feature_image": "http://127.0.0.1:2369/content/images/2018/hey.jpg", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "title": "Ghostly Kitchen Sink", "url": Any, From 54471693c8f3678649796e92db11ed06a6667f04 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 17 Dec 2025 15:42:56 +0000 Subject: [PATCH 20/22] Revert "limited `feature_image` post field to Admin API comments requests" This reverts commit 10e1f16236814d635a4fe25cb8b7c7f576c632ec. --- .../utils/serializers/output/mappers/comments.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js index 079cc273281..54a53c32b67 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js @@ -36,13 +36,6 @@ const postFields = [ 'uuid', 'title', 'url', -]; - -const postFieldsAdmin = [ - 'id', - 'uuid', - 'title', - 'url', 'feature_image' ]; @@ -93,7 +86,7 @@ const commentMapper = (model, frame) => { if (jsonModel.post) { // We could use the post mapper here, but we need less field + don't need all the async behavior support url.forPost(jsonModel.post.id, jsonModel.post, frame); - response.post = _.pick(jsonModel.post, isPublicRequest ? postFields : postFieldsAdmin); + response.post = _.pick(jsonModel.post, postFields); } if (jsonModel.count && jsonModel.count.liked !== undefined) { From e0a7a44c8109c21b90738626558cb8427e786ae4 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 17 Dec 2025 16:43:11 +0000 Subject: [PATCH 21/22] disabled Gravatar whilst we work on a proper fallback --- apps/posts/src/views/comments/components/comments-list.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index c73edcbf230..3aa9eb637e3 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -199,9 +199,9 @@ function CommentsList({ }} > - {item.member.avatar_image && ( + {/* {item.member.avatar_image && ( - )} + )} */} From abf73fefbb6e6675ba0685e51df5152e6ce41c79 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 17 Dec 2025 16:47:08 +0000 Subject: [PATCH 22/22] fixed lint --- apps/posts/src/views/comments/components/comments-list.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/posts/src/views/comments/components/comments-list.tsx b/apps/posts/src/views/comments/components/comments-list.tsx index 3aa9eb637e3..4dbdf760647 100644 --- a/apps/posts/src/views/comments/components/comments-list.tsx +++ b/apps/posts/src/views/comments/components/comments-list.tsx @@ -9,7 +9,6 @@ import { AlertDialogTitle, Avatar, AvatarFallback, - AvatarImage, Button, DropdownMenu, DropdownMenuContent,