fix: comment error boundary

This commit is contained in:
Philipinho
2026-07-03 21:12:09 +01:00
parent 86687d2bf2
commit 3b57ce7c89
5 changed files with 69 additions and 22 deletions
@@ -83,6 +83,7 @@
"Failed to restore page": "Failed to restore page", "Failed to restore page": "Failed to restore page",
"Failed to fetch recent pages": "Failed to fetch recent pages", "Failed to fetch recent pages": "Failed to fetch recent pages",
"Failed to import pages": "Failed to import pages", "Failed to import pages": "Failed to import pages",
"Failed to load comments. An error occurred.": "Failed to load comments. An error occurred.",
"Failed to load page. An error occurred.": "Failed to load page. An error occurred.", "Failed to load page. An error occurred.": "Failed to load page. An error occurred.",
"Failed to update data": "Failed to update data", "Failed to update data": "Failed to update data",
"Failed to create base": "Failed to create base", "Failed to create base": "Failed to create base",
@@ -1,6 +1,7 @@
import { ActionIcon, Box, Group, ScrollArea, Title, Tooltip } from "@mantine/core"; import { ActionIcon, Box, Group, ScrollArea, Title, Tooltip } from "@mantine/core";
import { IconX } from "@tabler/icons-react"; import { IconX } from "@tabler/icons-react";
import CommentListWithTabs from "@/features/comment/components/comment-list-with-tabs.tsx"; import CommentListWithTabs from "@/features/comment/components/comment-list-with-tabs.tsx";
import { CommentErrorBoundary } from "@/features/comment/components/comment-error-boundary.tsx";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import React, { ReactNode, useEffect } from "react"; import React, { ReactNode, useEffect } from "react";
@@ -28,7 +29,11 @@ export default function Aside() {
switch (tab) { switch (tab) {
case "comments": case "comments":
component = <CommentListWithTabs />; component = (
<CommentErrorBoundary>
<CommentListWithTabs />
</CommentErrorBoundary>
);
title = "Comments"; title = "Comments";
break; break;
case "toc": case "toc":
@@ -11,6 +11,7 @@ import {
} from "@/features/comment/atoms/comment-atom"; } from "@/features/comment/atoms/comment-atom";
import CommentEditor from "@/features/comment/components/comment-editor"; import CommentEditor from "@/features/comment/components/comment-editor";
import CommentActions from "@/features/comment/components/comment-actions"; import CommentActions from "@/features/comment/components/comment-actions";
import { CommentErrorBoundary } from "@/features/comment/components/comment-error-boundary";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import { useCreateCommentMutation } from "@/features/comment/queries/comment-query"; import { useCreateCommentMutation } from "@/features/comment/queries/comment-query";
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom"; import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
@@ -170,14 +171,16 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) {
</div> </div>
</Group> </Group>
<CommentEditor <CommentErrorBoundary>
onUpdate={handleCommentEditorChange} <CommentEditor
onSave={handleAddComment} onUpdate={handleCommentEditorChange}
placeholder={t("Write a comment")} onSave={handleAddComment}
editable={true} placeholder={t("Write a comment")}
autofocus={true} editable={true}
/> autofocus={true}
<CommentActions onSave={handleAddComment} isLoading={isPending} /> />
<CommentActions onSave={handleAddComment} isLoading={isPending} />
</CommentErrorBoundary>
</Stack> </Stack>
</Dialog> </Dialog>
); );
@@ -6,13 +6,9 @@ import { Color } from "@tiptap/extension-color";
import { import {
Mention, Mention,
LinkExtension, LinkExtension,
CustomTable,
TableRow,
TableCell,
TableHeader,
TableView,
TiptapImage, TiptapImage,
SharedStorage, SharedStorage,
Attachment,
} from "@docmost/editor-ext"; } from "@docmost/editor-ext";
import ImageView from "@/features/editor/components/image/image-view"; import ImageView from "@/features/editor/components/image/image-view";
import classes from "./comment.module.css"; import classes from "./comment.module.css";
@@ -24,6 +20,8 @@ import EmojiCommand from "@/features/editor/extensions/emoji-command";
import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion"; import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion";
import MentionView from "@/features/editor/components/mention/mention-view"; import MentionView from "@/features/editor/components/mention/mention-view";
import { platformModifierKey } from "@/lib"; import { platformModifierKey } from "@/lib";
import { TableKit } from "@tiptap/extension-table";
import AttachmentView from "@/features/editor/components/attachment/attachment-view.tsx";
interface CommentEditorProps { interface CommentEditorProps {
defaultContent?: any; defaultContent?: any;
@@ -81,14 +79,11 @@ const CommentEditor = forwardRef(
return ReactNodeViewRenderer(MentionView); return ReactNodeViewRenderer(MentionView);
}, },
}), }),
SharedStorage, TableKit,
CustomTable.configure({ Attachment.configure({
resizable: false, view: AttachmentView,
View: TableView,
}), }),
TableRow, SharedStorage,
TableCell,
TableHeader,
TiptapImage.configure({ TiptapImage.configure({
view: ImageView, view: ImageView,
allowBase64: false, allowBase64: false,
@@ -139,7 +134,12 @@ const CommentEditor = forwardRef(
// websocket on another browser). Skip for editable editors to avoid // websocket on another browser). Skip for editable editors to avoid
// resetting the cursor position on every keystroke. // resetting the cursor position on every keystroke.
useEffect(() => { useEffect(() => {
if (!editable && commentEditor && !commentEditor.isDestroyed && defaultContent) { if (
!editable &&
commentEditor &&
!commentEditor.isDestroyed &&
defaultContent
) {
commentEditor.commands.setContent(defaultContent); commentEditor.commands.setContent(defaultContent);
} }
}, [defaultContent, editable, commentEditor]); }, [defaultContent, editable, commentEditor]);
@@ -0,0 +1,38 @@
import { ReactNode } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Button } from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { EmptyState } from "@/components/ui/empty-state.tsx";
type CommentErrorBoundaryProps = {
children: ReactNode;
};
// Contain comment-editor render throws (e.g. schema errors) so they don't unmount the whole app.
export function CommentErrorBoundary({ children }: CommentErrorBoundaryProps) {
const { t } = useTranslation();
return (
<ErrorBoundary
fallbackRender={({ resetErrorBoundary }) => (
<EmptyState
icon={IconAlertTriangle}
title={t("Failed to load comments. An error occurred.")}
action={
<Button
variant="default"
size="sm"
mt="xs"
onClick={resetErrorBoundary}
>
{t("Try again")}
</Button>
}
/>
)}
>
{children}
</ErrorBoundary>
);
}