@lobehub/lobehub 2.0.0-next.254 → 2.0.0-next.256

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/plugin.json +1 -0
  4. package/locales/zh-CN/plugin.json +1 -0
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-notebook/src/client/Placeholder/CreateDocument.tsx +6 -6
  7. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +23 -10
  8. package/packages/builtin-tool-notebook/src/client/Streaming/CreateDocument/index.tsx +1 -1
  9. package/packages/database/src/models/__tests__/agent.test.ts +91 -4
  10. package/packages/database/src/models/agent.ts +15 -7
  11. package/packages/editor-runtime/src/EditorRuntime.ts +1 -1
  12. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +65 -4
  13. package/packages/editor-runtime/src/__tests__/__snapshots__/EditorRuntime.real.test.ts.snap +108 -17
  14. package/packages/editor-runtime/src/__tests__/fixtures/remove-then-add.json +636 -0
  15. package/packages/editor-runtime/src/__tests__/fixtures/remove.json +1 -0
  16. package/packages/types/src/agent/agentConfig.ts +8 -8
  17. package/src/app/[variants]/(main)/chat/features/Portal/_layout/Mobile.tsx +2 -1
  18. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +2 -1
  19. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +2 -2
  20. package/src/app/[variants]/(main)/page/{features/PageTitle → PageTitle}/index.tsx +0 -1
  21. package/src/app/[variants]/(main)/page/[id]/index.tsx +43 -1
  22. package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/Content.tsx +15 -15
  23. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +3 -3
  24. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +7 -12
  25. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +5 -5
  26. package/src/app/[variants]/(main)/page/_layout/Body/List/index.tsx +7 -7
  27. package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +15 -9
  28. package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +3 -3
  29. package/src/app/[variants]/(main)/page/_layout/DataSync.tsx +15 -0
  30. package/src/app/[variants]/(main)/page/_layout/Header/AddButton.tsx +2 -2
  31. package/src/app/[variants]/(main)/page/_layout/index.tsx +2 -0
  32. package/src/app/[variants]/(main)/page/index.tsx +3 -7
  33. package/src/components/Editor/AutoSaveHint.tsx +1 -1
  34. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/Intervention/ApprovalActions.tsx +2 -2
  35. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +18 -15
  36. package/src/features/Conversation/Messages/AssistantGroup/components/Group.tsx +3 -3
  37. package/src/features/Conversation/Messages/Contexts/MessageAggregationContext.ts +15 -0
  38. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +3 -3
  39. package/src/features/Conversation/Messages/User/Actions/index.tsx +5 -1
  40. package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
  41. package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
  42. package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
  43. package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
  44. package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
  45. package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
  46. package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
  47. package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
  48. package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
  49. package/src/features/EditorCanvas/index.ts +9 -0
  50. package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
  51. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
  52. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
  53. package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
  54. package/src/features/PageEditor/Header/index.tsx +15 -18
  55. package/src/features/PageEditor/Header/useMenu.tsx +12 -9
  56. package/src/features/PageEditor/PageEditor.tsx +45 -21
  57. package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
  58. package/src/features/PageEditor/PageTitle/index.tsx +2 -2
  59. package/src/features/PageEditor/StoreUpdater.tsx +35 -308
  60. package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
  61. package/src/features/PageEditor/store/action.ts +96 -188
  62. package/src/features/PageEditor/store/initialState.ts +16 -21
  63. package/src/features/PageEditor/store/selectors.ts +3 -4
  64. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
  65. package/src/features/PageExplorer/index.tsx +34 -67
  66. package/src/features/Portal/Artifacts/index.ts +0 -2
  67. package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
  68. package/src/features/Portal/Document/Body.tsx +1 -3
  69. package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
  70. package/src/features/Portal/Document/Header.tsx +13 -10
  71. package/src/features/Portal/Document/TodoList.tsx +6 -4
  72. package/src/features/Portal/Document/Wrapper.tsx +3 -11
  73. package/src/features/Portal/Document/index.ts +0 -2
  74. package/src/features/Portal/FilePreview/index.ts +0 -2
  75. package/src/features/Portal/GroupThread/index.ts +0 -3
  76. package/src/features/Portal/MessageDetail/index.ts +0 -2
  77. package/src/features/Portal/Notebook/index.ts +0 -2
  78. package/src/features/Portal/Plugins/index.ts +0 -2
  79. package/src/features/Portal/Thread/index.ts +0 -3
  80. package/src/features/Portal/components/Header.tsx +18 -6
  81. package/src/features/Portal/router.tsx +34 -97
  82. package/src/features/Portal/type.ts +0 -2
  83. package/src/libs/next/config/define-config.ts +4 -1
  84. package/src/locales/default/plugin.ts +1 -0
  85. package/src/store/chat/slices/portal/action.test.ts +218 -15
  86. package/src/store/chat/slices/portal/action.ts +194 -41
  87. package/src/store/chat/slices/portal/initialState.ts +40 -1
  88. package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
  89. package/src/store/chat/slices/portal/selectors.test.ts +119 -17
  90. package/src/store/chat/slices/portal/selectors.ts +117 -36
  91. package/src/store/document/index.ts +17 -5
  92. package/src/store/document/slices/document/action.ts +209 -0
  93. package/src/store/document/slices/document/index.ts +6 -0
  94. package/src/store/document/slices/editor/action.test.ts +340 -0
  95. package/src/store/document/slices/editor/action.ts +133 -149
  96. package/src/store/document/slices/editor/index.ts +9 -2
  97. package/src/store/document/slices/editor/initialState.ts +66 -29
  98. package/src/store/document/slices/editor/reducer.test.ts +217 -0
  99. package/src/store/document/slices/editor/reducer.ts +67 -0
  100. package/src/store/document/slices/editor/selectors.test.ts +395 -0
  101. package/src/store/document/slices/editor/selectors.ts +107 -5
  102. package/src/store/document/store.ts +12 -13
  103. package/src/store/file/slices/document/action.ts +19 -188
  104. package/src/store/file/slices/document/initialState.ts +0 -30
  105. package/src/store/file/slices/document/selectors.ts +25 -59
  106. package/src/store/notebook/index.ts +5 -4
  107. package/src/store/page/index.ts +2 -0
  108. package/src/store/page/initialState.ts +92 -0
  109. package/src/store/page/selectors.ts +5 -0
  110. package/src/store/page/slices/crud/action.ts +477 -0
  111. package/src/store/page/slices/crud/index.ts +2 -0
  112. package/src/store/page/slices/crud/initialState.ts +7 -0
  113. package/src/store/page/slices/internal/action.ts +32 -0
  114. package/src/store/page/slices/internal/index.ts +2 -0
  115. package/src/store/page/slices/internal/reducer.ts +105 -0
  116. package/src/store/page/slices/list/action.ts +206 -0
  117. package/src/store/page/slices/list/index.ts +3 -0
  118. package/src/store/page/slices/list/initialState.ts +29 -0
  119. package/src/store/page/slices/list/selectors.ts +90 -0
  120. package/src/store/page/slices/selection/action.ts +67 -0
  121. package/src/store/page/slices/selection/index.ts +2 -0
  122. package/src/store/page/slices/selection/initialState.ts +11 -0
  123. package/src/store/page/store.ts +29 -0
  124. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
  125. package/src/utils/identifier.ts +8 -2
  126. package/src/features/Conversation/Messages/AssistantGroup/components/GroupContext.ts +0 -15
  127. package/src/features/Conversation/Messages/Supervisor/components/GroupContext.ts +0 -15
  128. package/src/features/PageEditor/Body/index.tsx +0 -68
  129. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
  130. package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
  131. package/src/features/Portal/Artifacts/useEnable.ts +0 -4
  132. package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
  133. package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
  134. package/src/features/Portal/Document/Title.tsx +0 -54
  135. package/src/features/Portal/Document/store/action.ts +0 -114
  136. package/src/features/Portal/Document/store/index.ts +0 -21
  137. package/src/features/Portal/Document/store/initialState.ts +0 -24
  138. package/src/features/Portal/Document/useEnable.ts +0 -8
  139. package/src/features/Portal/FilePreview/useEnable.ts +0 -6
  140. package/src/features/Portal/GroupThread/hook.ts +0 -9
  141. package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
  142. package/src/features/Portal/Notebook/useEnable.ts +0 -6
  143. package/src/features/Portal/Plugins/useEnable.ts +0 -6
  144. package/src/features/Portal/Thread/hook.ts +0 -8
  145. package/src/store/document/slices/notebook/action.ts +0 -119
  146. package/src/store/document/slices/notebook/index.ts +0 -3
  147. package/src/store/document/slices/notebook/initialState.ts +0 -12
  148. package/src/store/document/slices/notebook/selectors.ts +0 -26
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND } from '@lobehub/editor';
3
+ import { DiffAction, IEditor, LITEXML_DIFFNODE_ALL_COMMAND } from '@lobehub/editor';
4
4
  import { Block, Icon } from '@lobehub/ui';
5
5
  import { Button, Space } from 'antd';
6
6
  import { createStaticStyles, cssVar, cx } from 'antd-style';
@@ -9,8 +9,7 @@ import { memo, useEffect, useState } from 'react';
9
9
  import { useTranslation } from 'react-i18next';
10
10
 
11
11
  import { useIsDark } from '@/hooks/useIsDark';
12
-
13
- import { usePageEditorStore } from './store';
12
+ import { useDocumentStore } from '@/store/document';
14
13
 
15
14
  const styles = createStaticStyles(({ css }) => ({
16
15
  container: css`
@@ -36,18 +35,36 @@ const styles = createStaticStyles(({ css }) => ({
36
35
  `,
37
36
  }));
38
37
 
39
- const DiffAllToolbar = memo(() => {
40
- const { t } = useTranslation('editor');
41
- const isDarkMode = useIsDark();
42
- const [editor, performSave] = usePageEditorStore((s) => [s.editor, s.performSave]);
38
+ const useIsEditorInit = (editor: IEditor) => {
39
+ const [isEditInit, setEditInit] = useState<boolean>(!!editor.getLexicalEditor());
40
+
41
+ useEffect(() => {
42
+ if (!editor) return;
43
+
44
+ const onInit = () => {
45
+ console.log('init: id', editor.getLexicalEditor()?._key);
46
+ setEditInit(true);
47
+ };
48
+ editor.on('initialized', onInit);
49
+ return () => {
50
+ editor.off('initialized', onInit);
51
+ };
52
+ }, [editor]);
53
+
54
+ return isEditInit;
55
+ };
56
+
57
+ const useEditorHasPendingDiffs = (editor: IEditor) => {
43
58
  const [hasPendingDiffs, setHasPendingDiffs] = useState(false);
59
+ const isEditInit = useIsEditorInit(editor);
44
60
 
45
61
  // Listen to editor state changes to detect diff nodes
46
62
  useEffect(() => {
47
63
  if (!editor) return;
48
64
 
49
65
  const lexicalEditor = editor.getLexicalEditor();
50
- if (!lexicalEditor) return;
66
+
67
+ if (!lexicalEditor || !isEditInit) return;
51
68
 
52
69
  const checkForDiffNodes = () => {
53
70
  const editorState = lexicalEditor.getEditorState();
@@ -67,15 +84,39 @@ const DiffAllToolbar = memo(() => {
67
84
  // Check initially
68
85
  checkForDiffNodes();
69
86
 
70
- // Register update listener
71
87
  const unregister = lexicalEditor.registerUpdateListener(() => {
72
88
  checkForDiffNodes();
73
89
  });
90
+ // Register update listener
91
+ return () => {
92
+ unregister();
93
+ };
94
+ }, [editor, isEditInit]);
74
95
 
75
- return unregister;
76
- }, [editor]);
96
+ return hasPendingDiffs;
97
+ };
98
+
99
+ interface DiffAllToolbarProps {
100
+ documentId: string;
101
+ editor: IEditor;
102
+ }
103
+ const DiffAllToolbar = memo<DiffAllToolbarProps>(({ documentId }) => {
104
+ const { t } = useTranslation('editor');
105
+ const isDarkMode = useIsDark();
106
+ const [editor, performSave, markDirty] = useDocumentStore((s) => [
107
+ s.editor!,
108
+ s.performSave,
109
+ s.markDirty,
110
+ ]);
111
+
112
+ const hasPendingDiffs = useEditorHasPendingDiffs(editor);
113
+
114
+ if (!hasPendingDiffs) return null;
77
115
 
78
- if (!editor || !hasPendingDiffs) return null;
116
+ const handleSave = async () => {
117
+ markDirty(documentId);
118
+ await performSave();
119
+ };
79
120
 
80
121
  return (
81
122
  <div className={styles.container}>
@@ -90,10 +131,10 @@ const DiffAllToolbar = memo(() => {
90
131
  <Space>
91
132
  <Button
92
133
  onClick={async () => {
93
- editor.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
134
+ editor?.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
94
135
  action: DiffAction.Reject,
95
136
  });
96
- await performSave({ force: true });
137
+ await handleSave();
97
138
  }}
98
139
  size={'small'}
99
140
  type="text"
@@ -104,10 +145,10 @@ const DiffAllToolbar = memo(() => {
104
145
  <Button
105
146
  color={'default'}
106
147
  onClick={async () => {
107
- editor.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
148
+ editor?.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
108
149
  action: DiffAction.Accept,
109
150
  });
110
- await performSave({ force: true });
151
+ await handleSave();
111
152
  }}
112
153
  size={'small'}
113
154
  variant="filled"
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { type IEditor } from '@lobehub/editor';
4
+ import { Alert } from '@lobehub/ui';
5
+ import { Skeleton } from 'antd';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { createStoreUpdater } from 'zustand-utils';
9
+
10
+ import { useSaveDocumentHotkey } from '@/hooks/useHotkeys';
11
+ import { useDocumentStore } from '@/store/document';
12
+ import { editorSelectors } from '@/store/document/slices/editor';
13
+
14
+ import type { EditorCanvasProps } from './EditorCanvas';
15
+ import InternalEditor from './InternalEditor';
16
+
17
+ /**
18
+ * Loading skeleton for the editor
19
+ */
20
+ const EditorSkeleton = memo(() => (
21
+ <div style={{ paddingBlock: 24 }}>
22
+ <Skeleton active paragraph={{ rows: 8 }} />
23
+ </div>
24
+ ));
25
+
26
+ /**
27
+ * Error display for fetch failures
28
+ */
29
+ const EditorError = memo<{ error: Error }>(({ error }) => {
30
+ const { t } = useTranslation('file');
31
+
32
+ return (
33
+ <Alert
34
+ description={error.message || t('pageEditor.loadError', 'Failed to load document')}
35
+ showIcon
36
+ style={{ margin: 16 }}
37
+ title={t('pageEditor.error', 'Error')}
38
+ type="error"
39
+ />
40
+ );
41
+ });
42
+
43
+ export interface DocumentIdModeProps extends EditorCanvasProps {
44
+ documentId: string;
45
+ editor: IEditor | undefined;
46
+ }
47
+
48
+ /**
49
+ * EditorCanvas with documentId mode - handles data fetching internally
50
+ */
51
+ const DocumentIdMode = memo<DocumentIdModeProps>(
52
+ ({
53
+ editor,
54
+ documentId,
55
+ autoSave = true,
56
+ sourceType = 'page',
57
+ onContentChange,
58
+ style,
59
+ ...editorProps
60
+ }) => {
61
+ const { t } = useTranslation('file');
62
+
63
+ const storeUpdater = createStoreUpdater(useDocumentStore);
64
+ storeUpdater('activeDocumentId', documentId);
65
+ storeUpdater('editor', editor);
66
+
67
+ // Get document store actions
68
+ const [onEditorInit, handleContentChangeStore, useFetchDocument, flushSave] = useDocumentStore(
69
+ (s) => [s.onEditorInit, s.handleContentChange, s.useFetchDocument, s.flushSave],
70
+ );
71
+
72
+ useSaveDocumentHotkey(flushSave);
73
+
74
+ // Use SWR hook for document fetching (auto-initializes via onSuccess in DocumentStore)
75
+ const { error } = useFetchDocument(documentId, { autoSave, editor, sourceType });
76
+
77
+ // Check loading state via selector (document not yet in store)
78
+ const isLoading = useDocumentStore(editorSelectors.isDocumentLoading(documentId));
79
+
80
+ // Handle content change
81
+ const handleChange = () => {
82
+ handleContentChangeStore();
83
+ onContentChange?.();
84
+ };
85
+
86
+ // Show loading state
87
+ if (isLoading) {
88
+ return <EditorSkeleton />;
89
+ }
90
+
91
+ if (!editor) return null;
92
+
93
+ return (
94
+ <>
95
+ {error && <EditorError error={error as Error} />}
96
+ <InternalEditor
97
+ editor={editor}
98
+ onContentChange={handleChange}
99
+ onInit={onEditorInit}
100
+ placeholder={editorProps.placeholder || t('pageEditor.editorPlaceholder')}
101
+ style={style}
102
+ {...editorProps}
103
+ />
104
+ </>
105
+ );
106
+ },
107
+ );
108
+
109
+ DocumentIdMode.displayName = 'DocumentIdMode';
110
+
111
+ export default DocumentIdMode;
@@ -0,0 +1,148 @@
1
+ 'use client';
2
+
3
+ import { type IEditor, type SlashOptions } from '@lobehub/editor';
4
+ import { type ChatInputActionsProps } from '@lobehub/editor/react';
5
+ import { Editor } from '@lobehub/editor/react';
6
+ import { type CSSProperties, memo } from 'react';
7
+
8
+ import DocumentIdMode from './DocumentIdMode';
9
+ import EditorDataMode from './EditorDataMode';
10
+ import { EditorErrorBoundary } from './ErrorBoundary';
11
+ import InternalEditor from './InternalEditor';
12
+
13
+ /**
14
+ * Plugin type for the editor
15
+ * Allows any array of plugins that the Editor component accepts
16
+ */
17
+ type EditorPlugins = Parameters<typeof Editor>[0]['plugins'];
18
+
19
+ export interface EditorCanvasProps {
20
+ /**
21
+ * Whether to enable auto-save in DocumentStore. Defaults to true.
22
+ * Only applies when documentId is provided.
23
+ */
24
+ autoSave?: boolean;
25
+
26
+ /**
27
+ * Document ID to load from server.
28
+ * When provided, component will use useSWR to fetch document data.
29
+ */
30
+ documentId?: string;
31
+
32
+ /**
33
+ * Editor data to render directly (skip fetch).
34
+ * Use this when you already have the content and don't need to fetch.
35
+ */
36
+ editorData?: {
37
+ content?: string;
38
+ editorData?: unknown;
39
+ };
40
+
41
+ /**
42
+ * Extra plugins to prepend to BASE_PLUGINS (e.g., ReactLiteXmlPlugin)
43
+ */
44
+ extraPlugins?: EditorPlugins;
45
+
46
+ /**
47
+ * Whether to show the floating toolbar. Defaults to true.
48
+ */
49
+ floatingToolbar?: boolean;
50
+
51
+ /**
52
+ * Content change handler
53
+ */
54
+ onContentChange?: () => void;
55
+
56
+ /**
57
+ * Editor initialization handler
58
+ */
59
+ onInit?: (editor: IEditor) => void;
60
+
61
+ /**
62
+ * Placeholder text for empty editor
63
+ */
64
+ placeholder?: string;
65
+
66
+ /**
67
+ * Custom plugins for the editor. If provided, replaces BASE_PLUGINS entirely.
68
+ * Use this when you need complete control over plugins.
69
+ */
70
+ plugins?: EditorPlugins;
71
+
72
+ /**
73
+ * Slash menu items
74
+ */
75
+ slashItems?: SlashOptions['items'];
76
+
77
+ /**
78
+ * Source type for DocumentStore. Defaults to 'page'.
79
+ */
80
+ sourceType?: 'page' | 'notebook';
81
+
82
+ /**
83
+ * Custom styles for the editor
84
+ */
85
+ style?: CSSProperties;
86
+
87
+ /**
88
+ * Extra items to add to the floating toolbar (e.g., "Ask Copilot" button)
89
+ */
90
+ toolbarExtraItems?: ChatInputActionsProps['items'];
91
+ }
92
+
93
+ export interface EditorCanvasWithEditorProps extends EditorCanvasProps {
94
+ /**
95
+ * Editor instance
96
+ */
97
+ editor: IEditor | undefined;
98
+ }
99
+
100
+ /**
101
+ * EditorCanvas component that accepts editor as a prop
102
+ *
103
+ * Three modes of operation:
104
+ * 1. documentId mode: Pass documentId, component fetches data via useSWR, shows loading/error states
105
+ * 2. editorData mode: Pass editorData directly, skips fetch and renders immediately
106
+ * 3. Basic mode: No documentId or editorData, just renders the editor (original behavior)
107
+ *
108
+ * Features:
109
+ * - Internal ErrorBoundary for graceful error handling
110
+ * - Loading skeleton during fetch (documentId mode)
111
+ * - Error state display for fetch failures (documentId mode)
112
+ * - Auto-save integration with DocumentStore (documentId mode)
113
+ * - AutoSave hint display (documentId mode)
114
+ */
115
+ export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
116
+ ({ editor, documentId, editorData, ...props }) => {
117
+ // documentId mode - fetch and render with loading/error states
118
+ if (documentId) {
119
+ return (
120
+ <EditorErrorBoundary>
121
+ <DocumentIdMode documentId={documentId} editor={editor} {...props} />
122
+ </EditorErrorBoundary>
123
+ );
124
+ }
125
+
126
+ // editorData mode - render with provided data
127
+ if (editorData) {
128
+ return (
129
+ <EditorErrorBoundary>
130
+ <EditorDataMode editor={editor} editorData={editorData} {...props} />
131
+ </EditorErrorBoundary>
132
+ );
133
+ }
134
+
135
+ // Basic mode - original behavior
136
+ if (!editor) return null;
137
+
138
+ return (
139
+ <EditorErrorBoundary>
140
+ <InternalEditor editor={editor} {...props} />
141
+ </EditorErrorBoundary>
142
+ );
143
+ },
144
+ );
145
+
146
+ EditorCanvas.displayName = 'EditorCanvas';
147
+
148
+ export default EditorCanvas;
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import { type IEditor } from '@lobehub/editor';
4
+ import { memo, useEffect, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import type { EditorCanvasProps } from './EditorCanvas';
8
+ import InternalEditor from './InternalEditor';
9
+
10
+ export interface EditorDataModeProps extends EditorCanvasProps {
11
+ editor: IEditor | undefined;
12
+ editorData: NonNullable<EditorCanvasProps['editorData']>;
13
+ }
14
+
15
+ /**
16
+ * EditorCanvas with editorData mode - uses provided data directly
17
+ */
18
+ const EditorDataMode = memo<EditorDataModeProps>(
19
+ ({ editor, editorData, onContentChange, style, ...editorProps }) => {
20
+ const { t } = useTranslation('file');
21
+ const [isInitialized, setIsInitialized] = useState(false);
22
+
23
+ // Load content into editor on mount
24
+ useEffect(() => {
25
+ if (!editor || isInitialized) return;
26
+
27
+ const hasValidEditorData =
28
+ editorData.editorData &&
29
+ typeof editorData.editorData === 'object' &&
30
+ Object.keys(editorData.editorData as object).length > 0;
31
+
32
+ try {
33
+ if (hasValidEditorData) {
34
+ editor.setDocument('json', JSON.stringify(editorData.editorData));
35
+ } else if (editorData.content?.trim()) {
36
+ editor.setDocument('markdown', editorData.content, { keepId: true });
37
+ } else {
38
+ console.error('[EditorCanvas] load content error:', editorData);
39
+ }
40
+
41
+ setIsInitialized(true);
42
+ } catch (err) {
43
+ console.error('[EditorCanvas] Failed to load content:', err);
44
+ }
45
+ }, [editorData, editor, isInitialized]);
46
+
47
+ if (!editor) return null;
48
+
49
+ return (
50
+ <div style={{ position: 'relative', ...style }}>
51
+ <InternalEditor
52
+ editor={editor}
53
+ onContentChange={onContentChange}
54
+ placeholder={editorProps.placeholder || t('pageEditor.editorPlaceholder')}
55
+ {...editorProps}
56
+ />
57
+ </div>
58
+ );
59
+ },
60
+ );
61
+
62
+ EditorDataMode.displayName = 'EditorDataMode';
63
+
64
+ export default EditorDataMode;
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ import { Alert } from '@lobehub/ui';
4
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
5
+
6
+ interface EditorErrorBoundaryState {
7
+ error: Error | null;
8
+ hasError: boolean;
9
+ }
10
+
11
+ interface EditorErrorBoundaryProps {
12
+ children: ReactNode;
13
+ fallback?: ReactNode;
14
+ }
15
+
16
+ /**
17
+ * ErrorBoundary for EditorCanvas component.
18
+ * Catches rendering errors in the editor and displays a fallback error UI
19
+ * instead of crashing the entire page.
20
+ */
21
+ export class EditorErrorBoundary extends Component<
22
+ EditorErrorBoundaryProps,
23
+ EditorErrorBoundaryState
24
+ > {
25
+ public state: EditorErrorBoundaryState = {
26
+ error: null,
27
+ hasError: false,
28
+ };
29
+
30
+ public static getDerivedStateFromError(error: Error): Partial<EditorErrorBoundaryState> {
31
+ return { error, hasError: true };
32
+ }
33
+
34
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
35
+ console.error('[EditorErrorBoundary] Caught error in editor render:', {
36
+ componentStack: errorInfo.componentStack,
37
+ error: error.message,
38
+ stack: error.stack,
39
+ });
40
+ }
41
+
42
+ public render() {
43
+ if (this.state.hasError) {
44
+ if (this.props.fallback) {
45
+ return this.props.fallback;
46
+ }
47
+
48
+ return (
49
+ <Alert
50
+ message={this.state.error?.message || 'An unknown error occurred in the editor'}
51
+ showIcon
52
+ style={{
53
+ margin: 16,
54
+ overflow: 'hidden',
55
+ position: 'relative',
56
+ width: '100%',
57
+ }}
58
+ title="Editor Error"
59
+ type="error"
60
+ />
61
+ );
62
+ }
63
+
64
+ return this.props.children;
65
+ }
66
+ }