mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Fixed replies line showing after all replies have been deleted
ref https://linear.app/ghost/issue/PLG-267 - updated delete comment action so it removes comments rather than just updating their status to `'deleted'` - deleted comments that still have replies have their status updated so the replies remain visible - matches updated API behaviour where deleted comments are not shown at all
This commit is contained in:
parent
bd20ad3adb
commit
e86d44ff85
5 changed files with 139 additions and 26 deletions
|
@ -279,34 +279,60 @@ async function deleteComment({state, api, data: comment}: {state: EditableAppCon
|
|||
}
|
||||
});
|
||||
|
||||
return {
|
||||
comments: state.comments.map((c) => {
|
||||
const replies = c.replies.map((r) => {
|
||||
if (r.id === comment.id) {
|
||||
if (state.labs.commentImprovements) {
|
||||
return {
|
||||
comments: state.comments.map((c) => {
|
||||
// If the comment has replies we want to keep it so the replies are
|
||||
// still visible, but mark the comment as deleted. Otherwise remove it.
|
||||
if (c.id === comment.id) {
|
||||
if (c.replies.length > 0) {
|
||||
return {
|
||||
...c,
|
||||
status: 'deleted'
|
||||
};
|
||||
} else {
|
||||
return null; // Will be filtered out later
|
||||
}
|
||||
}
|
||||
|
||||
const updatedReplies = c.replies.filter(r => r.id !== comment.id);
|
||||
return {
|
||||
...c,
|
||||
replies: updatedReplies
|
||||
};
|
||||
}).filter(Boolean),
|
||||
commentCount: state.commentCount - 1
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
comments: state.comments.map((c) => {
|
||||
const replies = c.replies.map((r) => {
|
||||
if (r.id === comment.id) {
|
||||
return {
|
||||
...r,
|
||||
status: 'deleted'
|
||||
};
|
||||
}
|
||||
|
||||
return r;
|
||||
});
|
||||
|
||||
if (c.id === comment.id) {
|
||||
return {
|
||||
...r,
|
||||
status: 'deleted'
|
||||
...c,
|
||||
status: 'deleted',
|
||||
replies
|
||||
};
|
||||
}
|
||||
|
||||
return r;
|
||||
});
|
||||
|
||||
if (c.id === comment.id) {
|
||||
return {
|
||||
...c,
|
||||
status: 'deleted',
|
||||
replies
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...c,
|
||||
replies
|
||||
};
|
||||
}),
|
||||
commentCount: state.commentCount - 1
|
||||
};
|
||||
}),
|
||||
commentCount: state.commentCount - 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function editComment({state, api, data: {comment, parent}}: {state: EditableAppContext, api: GhostApi, data: {comment: Partial<Comment> & {id: string}, parent?: Comment}}) {
|
||||
|
|
|
@ -439,7 +439,7 @@ const RepliesLine: React.FC<{hasReplies: boolean}> = ({hasReplies}) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
return (<div className="mb-2 h-full w-px grow rounded bg-gradient-to-b from-neutral-900/10 via-neutral-900/10 to-transparent dark:from-white/10 dark:via-white/10" />);
|
||||
return (<div className="mb-2 h-full w-px grow rounded bg-gradient-to-b from-neutral-900/10 via-neutral-900/10 to-transparent dark:from-white/10 dark:via-white/10" data-testid="replies-line" />);
|
||||
};
|
||||
|
||||
type CommentLayoutProps = {
|
||||
|
|
|
@ -27,7 +27,7 @@ const AuthorContextMenu: React.FC<Props> = ({comment, close, toggleEdit}) => {
|
|||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-700" data-testid="edit" type="button" onClick={toggleEdit}>
|
||||
{t('Edit')}
|
||||
</button>
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] text-red-600 transition-colors hover:bg-neutral-100 dark:text-red-500 dark:hover:bg-neutral-700" type="button" onClick={deleteComment}>
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] text-red-600 transition-colors hover:bg-neutral-100 dark:text-red-500 dark:hover:bg-neutral-700" data-testid="delete" type="button" onClick={deleteComment}>
|
||||
{t('Delete')}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -64,7 +64,7 @@ const DeletePopup = ({comment}: {comment: Comment}) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-modal relative h-screen w-screen rounded-none bg-white p-[28px] text-center sm:h-auto sm:w-[500px] sm:rounded-xl sm:p-8 sm:text-left" onMouseDown={stopPropagation}>
|
||||
<div className="shadow-modal relative h-screen w-screen rounded-none bg-white p-[28px] text-center sm:h-auto sm:w-[500px] sm:rounded-xl sm:p-8 sm:text-left" data-testid="delete-popup" onMouseDown={stopPropagation}>
|
||||
<div className="flex h-full flex-col justify-center pt-10 sm:justify-normal sm:pt-0">
|
||||
<h1 className="mb-1.5 font-sans text-[2.2rem] font-bold tracking-tight text-black">
|
||||
<span>{t('Are you sure?')}</span>
|
||||
|
@ -73,6 +73,7 @@ const DeletePopup = ({comment}: {comment: Comment}) => {
|
|||
<div className="mt-auto flex flex-col items-center justify-start gap-4 sm:mt-8 sm:flex-row">
|
||||
<button
|
||||
className={`text-md flex h-[44px] w-full items-center justify-center rounded-md px-4 font-sans font-medium text-white transition duration-200 ease-linear sm:w-fit ${buttonColor} opacity-100 hover:opacity-90`}
|
||||
data-testid="delete-popup-confirm"
|
||||
disabled={isSubmitting}
|
||||
type="button"
|
||||
onClick={submit}
|
||||
|
@ -92,4 +93,4 @@ const DeletePopup = ({comment}: {comment: Comment}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default DeletePopup;
|
||||
export default DeletePopup;
|
||||
|
|
|
@ -361,6 +361,92 @@ test.describe('Actions', async () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('Can delete a comment', async ({page}) => {
|
||||
const loggedInMember = buildMember();
|
||||
mockedApi.setMember(loggedInMember);
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>',
|
||||
member: loggedInMember
|
||||
});
|
||||
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
|
||||
const comment = frame.getByTestId('comment-component').nth(0);
|
||||
const moreButton = comment.getByTestId('more-button').first();
|
||||
await moreButton.click();
|
||||
await frame.getByTestId('delete').click();
|
||||
|
||||
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
|
||||
|
||||
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
|
||||
await popupIframe.getByTestId('delete-popup-confirm').click();
|
||||
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('Can delete a reply', async ({page}) => {
|
||||
const loggedInMember = buildMember();
|
||||
mockedApi.setMember(loggedInMember);
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>',
|
||||
replies: [
|
||||
mockedApi.buildReply({
|
||||
html: '<p>This is a reply</p>',
|
||||
member: loggedInMember
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
|
||||
const comment = frame.getByTestId('comment-component').nth(0);
|
||||
const reply = comment.getByTestId('comment-component').nth(0);
|
||||
const moreButton = reply.getByTestId('more-button').first();
|
||||
await moreButton.click();
|
||||
await frame.getByTestId('delete').click();
|
||||
|
||||
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
|
||||
|
||||
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
|
||||
await popupIframe.getByTestId('delete-popup-confirm').click();
|
||||
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(1);
|
||||
await expect(frame.getByTestId('replies-line')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Can delete a comment with replies', async ({page}) => {
|
||||
const loggedInMember = buildMember();
|
||||
mockedApi.setMember(loggedInMember);
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>',
|
||||
member: loggedInMember,
|
||||
replies: [
|
||||
mockedApi.buildReply({
|
||||
html: '<p>This is a reply</p>'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
|
||||
const comment = frame.getByTestId('comment-component').nth(0);
|
||||
const moreButton = comment.getByTestId('more-button').first();
|
||||
await moreButton.click();
|
||||
await frame.getByTestId('delete').click();
|
||||
|
||||
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
|
||||
|
||||
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
|
||||
await popupIframe.getByTestId('delete-popup-confirm').click();
|
||||
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(2);
|
||||
await expect(frame.getByText('This comment has been removed')).toBeVisible();
|
||||
await expect(frame.getByTestId('replies-line')).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe('Sorting - flag needs to be enabled', () => {
|
||||
test('Renders Sorting Form dropdown', async ({page}) => {
|
||||
mockedApi.addComment({
|
||||
|
@ -620,7 +706,7 @@ test.describe('Actions', async () => {
|
|||
// Get the parent comment and verify initial state
|
||||
const parentComment = frame.getByTestId('comment-component').nth(0);
|
||||
const replies = await parentComment.getByTestId('comment-component').all();
|
||||
|
||||
|
||||
// Verify initial state shows parent and replies
|
||||
await expect(parentComment).toContainText('Parent comment');
|
||||
await expect(replies[0]).toBeVisible();
|
||||
|
@ -635,7 +721,7 @@ test.describe('Actions', async () => {
|
|||
|
||||
// Verify the edit form is visible
|
||||
await expect(frame.getByTestId('form-editor')).toBeVisible();
|
||||
|
||||
|
||||
// Verify replies are still visible while editing
|
||||
await expect(replies[0]).toBeVisible();
|
||||
await expect(replies[0]).toContainText('First reply');
|
||||
|
|
Loading…
Add table
Reference in a new issue