fix(publish): retry failed comments without duplicate replies

This commit is contained in:
Tommaso Casaburi
2026-04-10 16:02:10 +07:00
parent 02f8d3467e
commit 5f9e603b62
8 changed files with 385 additions and 17 deletions
@@ -356,8 +356,11 @@ vi.mock('../../lib/utils/thread-scroll-utils', () => ({
vi.mock('../../hooks/use-delete-failed-post', () => ({
default: () => ({
canDeleteFailedPost: false,
canRetryFailedPost: false,
isDeletingFailedPost: false,
isRetryingFailedPost: false,
onDeleteFailedPost: vi.fn(),
onRetryFailedPost: vi.fn(),
}),
}));
+14 -2
View File
@@ -3,11 +3,14 @@ import useIsMobile from '../hooks/use-is-mobile';
interface FailedPublishNoticeProps {
isDeleting: boolean;
isRetrying?: boolean;
onDelete: () => void;
onRetry?: () => void;
}
const FailedPublishNotice = ({ isDeleting, onDelete }: FailedPublishNoticeProps) => {
const FailedPublishNotice = ({ isDeleting, isRetrying = false, onDelete, onRetry }: FailedPublishNoticeProps) => {
const isMobile = useIsMobile();
const isBusy = isDeleting || isRetrying;
return (
<span className={styles.failedPublishNotice}>
@@ -18,9 +21,18 @@ const FailedPublishNotice = ({ isDeleting, onDelete }: FailedPublishNoticeProps)
<br />
</>
)}
{onRetry && (
<span className={styles.failedDeletePostAction}>
[
<button type='button' className={styles.failedDeletePostButton} disabled={isBusy} onClick={onRetry}>
Retry Publish
</button>
]
</span>
)}{' '}
<span className={styles.failedDeletePostAction}>
[
<button type='button' className={styles.failedDeletePostButton} disabled={isDeleting} onClick={onDelete}>
<button type='button' className={styles.failedDeletePostButton} disabled={isBusy} onClick={onDelete}>
Delete Post
</button>
]
+19 -4
View File
@@ -747,8 +747,15 @@ const Reply = ({
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(post);
const failedPublishNotice = canDeleteFailedPost ? <FailedPublishNotice isDeleting={isDeletingFailedPost} onDelete={onDeleteFailedPost} /> : undefined;
const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } = useDeleteFailedPost(post);
const failedPublishNotice = canDeleteFailedPost ? (
<FailedPublishNotice
isDeleting={isDeletingFailedPost}
isRetrying={isRetryingFailedPost}
onDelete={onDeleteFailedPost}
onRetry={canRetryFailedPost ? onRetryFailedPost : undefined}
/>
) : undefined;
return (
<div className={`${styles.replyDesktop} ${disableDeferredLayout ? styles.pretextVirtualizedReply : ''}`}>
@@ -927,8 +934,16 @@ const PostDesktop = ({
const stateString = useStateString(resolvedPost) || t('downloading_board');
const hasFailedState = state === 'failed';
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(resolvedPost);
const failedPublishNotice = canDeleteFailedPost ? <FailedPublishNotice isDeleting={isDeletingFailedPost} onDelete={onDeleteFailedPost} /> : undefined;
const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } =
useDeleteFailedPost(resolvedPost);
const failedPublishNotice = canDeleteFailedPost ? (
<FailedPublishNotice
isDeleting={isDeletingFailedPost}
isRetrying={isRetryingFailedPost}
onDelete={onDeleteFailedPost}
onRetry={canRetryFailedPost ? onRetryFailedPost : undefined}
/>
) : undefined;
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
+19 -4
View File
@@ -519,8 +519,15 @@ const Reply = ({
const route = boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`;
const isRouteLinkToReply = cid ? location.pathname.startsWith(route) : false;
const { hidden } = useHide({ cid });
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(post);
const failedPublishNotice = canDeleteFailedPost ? <FailedPublishNotice isDeleting={isDeletingFailedPost} onDelete={onDeleteFailedPost} /> : undefined;
const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } = useDeleteFailedPost(post);
const failedPublishNotice = canDeleteFailedPost ? (
<FailedPublishNotice
isDeleting={isDeletingFailedPost}
isRetrying={isRetryingFailedPost}
onDelete={onDeleteFailedPost}
onRetry={canRetryFailedPost ? onRetryFailedPost : undefined}
/>
) : undefined;
return (
<div className={`${styles.replyMobile} ${disableDeferredLayout ? styles.pretextVirtualizedReply : ''}`}>
@@ -648,8 +655,16 @@ const PostMobile = ({
const stateString = useStateString(resolvedPost) || t('loading_post');
const hasFailedState = state === 'failed';
const isReply = !!parentCid;
const { canDeleteFailedPost, isDeletingFailedPost, onDeleteFailedPost } = useDeleteFailedPost(resolvedPost);
const failedPublishNotice = canDeleteFailedPost ? <FailedPublishNotice isDeleting={isDeletingFailedPost} onDelete={onDeleteFailedPost} /> : undefined;
const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } =
useDeleteFailedPost(resolvedPost);
const failedPublishNotice = canDeleteFailedPost ? (
<FailedPublishNotice
isDeleting={isDeletingFailedPost}
isRetrying={isRetryingFailedPost}
onDelete={onDeleteFailedPost}
onRetry={canRetryFailedPost ? onRetryFailedPost : undefined}
/>
) : undefined;
// Author-deleted replies are hidden from thread replies; moderator removals still render their placeholder.
const filteredReplies = useMemo(() => filterRepliesForDisplay(freshRepliesForRender), [freshRepliesForRender]);