@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
@@ -0,0 +1,245 @@
1
+ 'use client';
2
+
3
+ import {
4
+ HotkeyEnum,
5
+ INSERT_HEADING_COMMAND,
6
+ getHotkeyById,
7
+ type IEditor,
8
+ } from '@lobehub/editor';
9
+ import {
10
+ ChatInputActions,
11
+ type ChatInputActionsProps,
12
+ type EditorState,
13
+ FloatActions,
14
+ } from '@lobehub/editor/react';
15
+ import { Block } from '@lobehub/ui';
16
+ import { cssVar } from 'antd-style';
17
+ import {
18
+ BoldIcon,
19
+ CodeXmlIcon,
20
+ Heading1Icon,
21
+ Heading2Icon,
22
+ Heading3Icon,
23
+ ItalicIcon,
24
+ LinkIcon,
25
+ ListIcon,
26
+ ListOrderedIcon,
27
+ ListTodoIcon,
28
+ MessageSquareQuote,
29
+ Redo2Icon,
30
+ SigmaIcon,
31
+ SquareDashedBottomCodeIcon,
32
+ StrikethroughIcon,
33
+ UnderlineIcon,
34
+ Undo2Icon,
35
+ } from 'lucide-react';
36
+ import { type CSSProperties, memo, useMemo } from 'react';
37
+ import { useTranslation } from 'react-i18next';
38
+
39
+ export interface InlineToolbarProps {
40
+ className?: string;
41
+ editor?: IEditor;
42
+ editorState?: EditorState;
43
+ /**
44
+ * Extra items to prepend to the toolbar (e.g., "Ask Copilot" button)
45
+ */
46
+ extraItems?: ChatInputActionsProps['items'];
47
+ floating?: boolean;
48
+ style?: CSSProperties;
49
+ }
50
+
51
+ const InlineToolbar = memo<InlineToolbarProps>(
52
+ ({ floating, style, className, editor, editorState, extraItems }) => {
53
+ const { t } = useTranslation('editor');
54
+
55
+ const items: ChatInputActionsProps['items'] = useMemo(() => {
56
+ if (!editorState) return [];
57
+
58
+ const baseItems = [
59
+ // Extra items (like "Ask Copilot") come first
60
+ ...(extraItems || []),
61
+ extraItems?.length ? { type: 'divider' as const } : null,
62
+ !floating && {
63
+ disabled: !editorState.canUndo,
64
+ icon: Undo2Icon,
65
+ key: 'undo',
66
+ label: t('typobar.undo', 'Undo'),
67
+ onClick: editorState.undo,
68
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Undo).keys },
69
+ },
70
+ !floating && {
71
+ disabled: !editorState.canRedo,
72
+ icon: Redo2Icon,
73
+ key: 'redo',
74
+ label: t('typobar.redo', 'Redo'),
75
+ onClick: editorState.redo,
76
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Redo).keys },
77
+ },
78
+ !floating && {
79
+ type: 'divider',
80
+ },
81
+ {
82
+ active: editorState.isBold,
83
+ icon: BoldIcon,
84
+ key: 'bold',
85
+ label: t('typobar.bold'),
86
+ onClick: editorState.bold,
87
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Bold).keys },
88
+ },
89
+ {
90
+ active: editorState.isItalic,
91
+ icon: ItalicIcon,
92
+ key: 'italic',
93
+ label: t('typobar.italic'),
94
+ onClick: editorState.italic,
95
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Italic).keys },
96
+ },
97
+ {
98
+ active: editorState.isUnderline,
99
+ icon: UnderlineIcon,
100
+ key: 'underline',
101
+ label: t('typobar.underline'),
102
+ onClick: editorState.underline,
103
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Underline).keys },
104
+ },
105
+ {
106
+ active: editorState.isStrikethrough,
107
+ icon: StrikethroughIcon,
108
+ key: 'strikethrough',
109
+ label: t('typobar.strikethrough'),
110
+ onClick: editorState.strikethrough,
111
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Strikethrough).keys },
112
+ },
113
+ {
114
+ type: 'divider',
115
+ },
116
+ !floating && {
117
+ icon: Heading1Icon,
118
+ key: 'h1',
119
+ label: t('slash.h1'),
120
+ onClick: () => {
121
+ if (editor) {
122
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h1' });
123
+ }
124
+ },
125
+ },
126
+ !floating && {
127
+ icon: Heading2Icon,
128
+ key: 'h2',
129
+ label: t('slash.h2'),
130
+ onClick: () => {
131
+ if (editor) {
132
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h2' });
133
+ }
134
+ },
135
+ },
136
+ !floating && {
137
+ icon: Heading3Icon,
138
+ key: 'h3',
139
+ label: t('slash.h3'),
140
+ onClick: () => {
141
+ if (editor) {
142
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h3' });
143
+ }
144
+ },
145
+ },
146
+ !floating && {
147
+ type: 'divider',
148
+ },
149
+ {
150
+ icon: ListIcon,
151
+ key: 'bulletList',
152
+ label: t('typobar.bulletList'),
153
+ onClick: editorState.bulletList,
154
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.BulletList).keys },
155
+ },
156
+ {
157
+ icon: ListOrderedIcon,
158
+ key: 'numberlist',
159
+ label: t('typobar.numberList'),
160
+ onClick: editorState.numberList,
161
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.NumberList).keys },
162
+ },
163
+ {
164
+ icon: ListTodoIcon,
165
+ key: 'tasklist',
166
+ label: t('typobar.taskList'),
167
+ onClick: editorState.checkList,
168
+ },
169
+ {
170
+ type: 'divider',
171
+ },
172
+ {
173
+ active: editorState.isBlockquote,
174
+ icon: MessageSquareQuote,
175
+ key: 'blockquote',
176
+ label: t('typobar.blockquote'),
177
+ onClick: editorState.blockquote,
178
+ },
179
+ {
180
+ icon: LinkIcon,
181
+ key: 'link',
182
+ label: t('typobar.link'),
183
+ onClick: editorState.insertLink,
184
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Link).keys },
185
+ },
186
+ {
187
+ icon: SigmaIcon,
188
+ key: 'math',
189
+ label: t('typobar.tex'),
190
+ onClick: editorState.insertMath,
191
+ },
192
+ {
193
+ type: 'divider',
194
+ },
195
+ {
196
+ active: editorState.isCode,
197
+ icon: CodeXmlIcon,
198
+ key: 'code',
199
+ label: t('typobar.code'),
200
+ onClick: editorState.code,
201
+ tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.CodeInline).keys },
202
+ },
203
+ !floating && {
204
+ icon: SquareDashedBottomCodeIcon,
205
+ key: 'codeblock',
206
+ label: t('typobar.codeblock'),
207
+ onClick: editorState.codeblock,
208
+ },
209
+ ];
210
+
211
+ return baseItems.filter(Boolean) as ChatInputActionsProps['items'];
212
+ }, [editor, editorState, extraItems, floating, t]);
213
+
214
+ if (!editorState) return null;
215
+
216
+ // Floating toolbar - just return the actions
217
+ if (floating) return <FloatActions className={className} items={items} style={style} />;
218
+
219
+ // Fixed toolbar - wrap in a styled container
220
+ return (
221
+ <Block
222
+ className={className}
223
+ padding={4}
224
+ shadow
225
+ style={{
226
+ background: cssVar.colorBgElevated,
227
+ borderRadius: 8,
228
+ marginBottom: 16,
229
+ marginTop: 16,
230
+ position: 'sticky',
231
+ top: 12,
232
+ zIndex: 10,
233
+ ...style,
234
+ }}
235
+ variant={'outlined'}
236
+ >
237
+ <ChatInputActions items={items} />
238
+ </Block>
239
+ );
240
+ },
241
+ );
242
+
243
+ InlineToolbar.displayName = 'InlineToolbar';
244
+
245
+ export default InlineToolbar;
@@ -0,0 +1,134 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type IEditor,
5
+ ReactCodePlugin,
6
+ ReactCodemirrorPlugin,
7
+ ReactHRPlugin,
8
+ ReactImagePlugin,
9
+ ReactLinkPlugin,
10
+ ReactListPlugin,
11
+ ReactLiteXmlPlugin,
12
+ ReactMathPlugin,
13
+ ReactTablePlugin,
14
+ ReactToolbarPlugin,
15
+ } from '@lobehub/editor';
16
+ import { Editor, useEditorState } from '@lobehub/editor/react';
17
+ import { memo, useEffect, useMemo } from 'react';
18
+ import { useTranslation } from 'react-i18next';
19
+
20
+ import type { EditorCanvasProps } from './EditorCanvas';
21
+ import InlineToolbar from './InlineToolbar';
22
+
23
+ /**
24
+ * Base plugins for the editor (without toolbar)
25
+ */
26
+ const BASE_PLUGINS = [
27
+ ReactLiteXmlPlugin,
28
+ ReactListPlugin,
29
+ ReactCodePlugin,
30
+ ReactCodemirrorPlugin,
31
+ ReactHRPlugin,
32
+ ReactLinkPlugin,
33
+ ReactTablePlugin,
34
+ ReactMathPlugin,
35
+ Editor.withProps(ReactImagePlugin, {
36
+ defaultBlockImage: true,
37
+ }),
38
+ ];
39
+
40
+ export interface InternalEditorProps extends EditorCanvasProps {
41
+ /**
42
+ * Editor instance (required)
43
+ */
44
+ editor: IEditor;
45
+ }
46
+
47
+ /**
48
+ * Internal EditorCanvas component that requires editor instance
49
+ */
50
+ const InternalEditor = memo<InternalEditorProps>(
51
+ ({
52
+ editor,
53
+ extraPlugins,
54
+ floatingToolbar = true,
55
+ onContentChange,
56
+ onInit,
57
+ placeholder,
58
+ plugins: customPlugins,
59
+ slashItems,
60
+ style,
61
+ toolbarExtraItems,
62
+ }) => {
63
+ const { t } = useTranslation('file');
64
+ const editorState = useEditorState(editor);
65
+
66
+ const finalPlaceholder = placeholder || t('pageEditor.editorPlaceholder');
67
+
68
+ // Build plugins array
69
+ const plugins = useMemo(() => {
70
+ // If custom plugins provided, use them directly
71
+ if (customPlugins) return customPlugins;
72
+
73
+ // Build base plugins with optional extra plugins prepended
74
+ const basePlugins = extraPlugins ? [...extraPlugins, ...BASE_PLUGINS] : BASE_PLUGINS;
75
+
76
+ // Add toolbar if enabled
77
+ if (floatingToolbar) {
78
+ return [
79
+ ...basePlugins,
80
+ Editor.withProps(ReactToolbarPlugin, {
81
+ children: (
82
+ <InlineToolbar
83
+ editor={editor}
84
+ editorState={editorState}
85
+ extraItems={toolbarExtraItems}
86
+ floating
87
+ />
88
+ ),
89
+ }),
90
+ ];
91
+ }
92
+
93
+ return basePlugins;
94
+ }, [customPlugins, editor, editorState, extraPlugins, floatingToolbar, toolbarExtraItems]);
95
+
96
+ useEffect(() => {
97
+ // for easier debug, mount editor instance to window
98
+ if (editor) window.__editor = editor;
99
+
100
+ return () => {
101
+ window.__editor = undefined;
102
+ };
103
+ }, [editor]);
104
+
105
+ return (
106
+ <div
107
+ onClick={(e) => {
108
+ e.stopPropagation();
109
+ e.preventDefault();
110
+ }}
111
+ >
112
+ <Editor
113
+ content={''}
114
+ editor={editor}
115
+ lineEmptyPlaceholder={finalPlaceholder}
116
+ onInit={onInit}
117
+ onTextChange={onContentChange}
118
+ placeholder={finalPlaceholder}
119
+ plugins={plugins}
120
+ slashOption={slashItems ? { items: slashItems } : undefined}
121
+ style={{
122
+ paddingBottom: 64,
123
+ ...style,
124
+ }}
125
+ type={'text'}
126
+ />
127
+ </div>
128
+ );
129
+ },
130
+ );
131
+
132
+ InternalEditor.displayName = 'InternalEditor';
133
+
134
+ export default InternalEditor;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Opens a native file selector dialog
3
+ * @param handleFiles - Callback function to handle selected files
4
+ * @param accept - MIME type filter for accepted files (default: all files)
5
+ */
1
6
  export function openFileSelector(handleFiles: (files: FileList) => void, accept = '*/*') {
2
7
  // Skip on server side
3
8
  if (typeof document === 'undefined') {
@@ -7,19 +12,16 @@ export function openFileSelector(handleFiles: (files: FileList) => void, accept
7
12
  // Create a hidden input element
8
13
  const input = document.createElement('input');
9
14
  input.type = 'file';
10
- input.accept = accept; // Accept all file types
11
- input.multiple = false; // Whether to allow multiple selection
15
+ input.accept = accept;
16
+ input.multiple = false;
12
17
 
13
18
  // Listen for file selection events
14
- // eslint-disable-next-line unicorn/prefer-add-event-listener
15
- input.onchange = (event) => {
16
- // @ts-expect-error not error
17
- const files = event.target?.files;
19
+ input.addEventListener('change', (event) => {
20
+ const files = (event.target as HTMLInputElement)?.files;
18
21
  if (files && files.length > 0) {
19
- // Handle selected files
20
22
  handleFiles(files);
21
23
  }
22
- };
24
+ });
23
25
 
24
26
  // Trigger file selector
25
27
  input.click();
@@ -0,0 +1,9 @@
1
+ export { openFileSelector } from './actions';
2
+ export { default as AutoSaveHint, type AutoSaveHintProps } from './AutoSaveHint';
3
+ export {
4
+ EditorCanvas,
5
+ type EditorCanvasProps,
6
+ type EditorCanvasWithEditorProps,
7
+ } from './EditorCanvas';
8
+ export { EditorErrorBoundary } from './ErrorBoundary';
9
+ export { default as InlineToolbar, type InlineToolbarProps } from './InlineToolbar';
@@ -1,76 +1,14 @@
1
1
  'use client';
2
2
 
3
- import {
4
- ReactCodePlugin,
5
- ReactCodemirrorPlugin,
6
- ReactHRPlugin,
7
- ReactImagePlugin,
8
- ReactLinkPlugin,
9
- ReactListPlugin,
10
- ReactLiteXmlPlugin,
11
- ReactMathPlugin,
12
- ReactTablePlugin,
13
- ReactToolbarPlugin,
14
- } from '@lobehub/editor';
15
- import { Editor } from '@lobehub/editor/react';
16
- import { Alert } from '@lobehub/ui';
17
- import { type CSSProperties, Component, type ErrorInfo, type ReactNode, memo } from 'react';
3
+ import { type CSSProperties, memo } from 'react';
18
4
  import { useTranslation } from 'react-i18next';
19
5
 
6
+ import { EditorCanvas as SharedEditorCanvas } from '@/features/EditorCanvas';
7
+
20
8
  import { usePageEditorStore } from '../store';
21
- import InlineToolbar from './InlineToolbar';
9
+ import { useAskCopilotItem } from './useAskCopilotItem';
22
10
  import { useSlashItems } from './useSlashItems';
23
11
 
24
- interface EditorErrorBoundaryState {
25
- error: Error | null;
26
- hasError: boolean;
27
- }
28
-
29
- /**
30
- * ErrorBoundary for EditorCanvas component.
31
- * Catches rendering errors in the editor and displays a fallback error UI
32
- * instead of crashing the entire page.
33
- */
34
- class EditorErrorBoundary extends Component<{ children: ReactNode }, EditorErrorBoundaryState> {
35
- public state: EditorErrorBoundaryState = {
36
- error: null,
37
- hasError: false,
38
- };
39
-
40
- public static getDerivedStateFromError(error: Error): Partial<EditorErrorBoundaryState> {
41
- return { error, hasError: true };
42
- }
43
-
44
- public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
45
- console.error('[EditorErrorBoundary] Caught error in editor render:', {
46
- componentStack: errorInfo.componentStack,
47
- error: error.message,
48
- stack: error.stack,
49
- });
50
- }
51
-
52
- public render() {
53
- if (this.state.hasError) {
54
- return (
55
- <Alert
56
- message={this.state.error?.message || 'An unknown error occurred in the editor'}
57
- showIcon
58
- style={{
59
- margin: 16,
60
- overflow: 'hidden',
61
- position: 'relative',
62
- width: '100%',
63
- }}
64
- title="Editor Error"
65
- type="error"
66
- />
67
- );
68
- }
69
-
70
- return this.props.children;
71
- }
72
- }
73
-
74
12
  interface EditorCanvasProps {
75
13
  placeholder?: string;
76
14
  style?: CSSProperties;
@@ -80,55 +18,20 @@ const EditorCanvas = memo<EditorCanvasProps>(({ placeholder, style }) => {
80
18
  const { t } = useTranslation(['file', 'editor']);
81
19
 
82
20
  const editor = usePageEditorStore((s) => s.editor);
83
- const handleContentChange = usePageEditorStore((s) => s.handleContentChange);
84
- const onEditorInit = usePageEditorStore((s) => s.onEditorInit);
21
+ const documentId = usePageEditorStore((s) => s.documentId);
85
22
 
86
23
  const slashItems = useSlashItems(editor);
87
-
88
- if (!editor) return null;
24
+ const askCopilotItem = useAskCopilotItem(editor);
89
25
 
90
26
  return (
91
- <EditorErrorBoundary>
92
- <div
93
- onClick={(e) => {
94
- e.stopPropagation();
95
- e.preventDefault();
96
- }}
97
- >
98
- <Editor
99
- content={''}
100
- editor={editor!}
101
- lineEmptyPlaceholder={placeholder || t('pageEditor.editorPlaceholder')}
102
- onInit={onEditorInit}
103
- onTextChange={handleContentChange}
104
- placeholder={placeholder || t('pageEditor.editorPlaceholder')}
105
- plugins={[
106
- ReactLiteXmlPlugin,
107
- ReactListPlugin,
108
- ReactCodePlugin,
109
- ReactCodemirrorPlugin,
110
- ReactHRPlugin,
111
- ReactLinkPlugin,
112
- ReactTablePlugin,
113
- ReactMathPlugin,
114
- Editor.withProps(ReactImagePlugin, {
115
- defaultBlockImage: true,
116
- }),
117
- Editor.withProps(ReactToolbarPlugin, {
118
- children: <InlineToolbar floating />,
119
- }),
120
- ]}
121
- slashOption={{
122
- items: slashItems,
123
- }}
124
- style={{
125
- paddingBottom: 64,
126
- ...style,
127
- }}
128
- type={'text'}
129
- />
130
- </div>
131
- </EditorErrorBoundary>
27
+ <SharedEditorCanvas
28
+ documentId={documentId}
29
+ editor={editor}
30
+ placeholder={placeholder || t('pageEditor.editorPlaceholder')}
31
+ slashItems={slashItems}
32
+ style={style}
33
+ toolbarExtraItems={askCopilotItem}
34
+ />
132
35
  );
133
36
  });
134
37
 
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+
3
+ import { nanoid } from '@lobechat/utils';
4
+ import { HIDE_TOOLBAR_COMMAND, type IEditor } from '@lobehub/editor';
5
+ import { type ChatInputActionsProps } from '@lobehub/editor/react';
6
+ import { Block } from '@lobehub/ui';
7
+ import { createStaticStyles, cssVar } from 'antd-style';
8
+ import { BotIcon } from 'lucide-react';
9
+ import { useMemo } from 'react';
10
+
11
+ import { useFileStore } from '@/store/file';
12
+ import { useGlobalStore } from '@/store/global';
13
+
14
+ const styles = createStaticStyles(({ css }) => ({
15
+ askCopilot: css`
16
+ border-radius: 6px;
17
+ color: ${cssVar.colorTextDescription};
18
+
19
+ &:hover {
20
+ color: ${cssVar.colorTextSecondary};
21
+ }
22
+ `,
23
+ }));
24
+
25
+ export const useAskCopilotItem = (editor: IEditor | undefined): ChatInputActionsProps['items'] => {
26
+ const addSelectionContext = useFileStore((s) => s.addChatContextSelection);
27
+
28
+ return useMemo(() => {
29
+ if (!editor) return [];
30
+
31
+ return [
32
+ {
33
+ children: (
34
+ <Block
35
+ align="center"
36
+ className={styles.askCopilot}
37
+ clickable
38
+ gap={8}
39
+ horizontal
40
+ onClick={() => {
41
+ const xml = (editor.getSelectionDocument?.('litexml') as string) || '';
42
+ const plainText = (editor.getSelectionDocument?.('text') as string) || '';
43
+ const content = xml.trim() || plainText.trim();
44
+
45
+ if (!content) return;
46
+
47
+ const format = xml.trim() ? 'xml' : 'text';
48
+ const preview =
49
+ (plainText || xml)
50
+ .replaceAll(/<[^>]*>/g, ' ')
51
+ .replaceAll(/\s+/g, ' ')
52
+ .trim() || undefined;
53
+
54
+ // Store action handles deduplication
55
+ addSelectionContext({
56
+ content,
57
+ format,
58
+ id: `selection-${nanoid(6)}`,
59
+ preview,
60
+ title: 'Selection',
61
+ type: 'text',
62
+ });
63
+
64
+ // Open right panel if not opened
65
+ useGlobalStore.getState().toggleRightPanel(true);
66
+
67
+ // Focus on chat input after a short delay to ensure panel is opened
68
+ setTimeout(() => {
69
+ // Find the chat input editor within the right panel
70
+ // Query all lexical editors and get the last one (which should be the chat input)
71
+ const allEditors = [...document.querySelectorAll('[data-lexical-editor="true"]')];
72
+ const chatInputEditor = allEditors.at(-1) as HTMLElement;
73
+ if (chatInputEditor) {
74
+ chatInputEditor.focus();
75
+ }
76
+ }, 300);
77
+
78
+ editor.dispatchCommand(HIDE_TOOLBAR_COMMAND, undefined);
79
+ editor.blur();
80
+ }}
81
+ paddingBlock={6}
82
+ paddingInline={12}
83
+ variant="borderless"
84
+ >
85
+ <BotIcon />
86
+ <span>Ask Copilot</span>
87
+ </Block>
88
+ ),
89
+ key: 'ask-copilot',
90
+ label: 'Ask Copilot',
91
+ onClick: () => {},
92
+ },
93
+ ];
94
+ }, [addSelectionContext, editor]);
95
+ };
@@ -28,7 +28,7 @@ import {
28
28
  import { useMemo } from 'react';
29
29
  import { useTranslation } from 'react-i18next';
30
30
 
31
- import { openFileSelector } from '@/features/PageEditor/EditorCanvas/actions';
31
+ import { openFileSelector } from '@/features/EditorCanvas';
32
32
  import { useFileStore } from '@/store/file';
33
33
 
34
34
  export const useSlashItems = (editor: IEditor | undefined): SlashOptions['items'] => {
@@ -40,7 +40,7 @@ interface FolderCrumb {
40
40
  const Breadcrumb = memo(() => {
41
41
  const { t } = useTranslation('file');
42
42
 
43
- const currentTitle = usePageEditorStore((s) => s.currentTitle);
43
+ const title = usePageEditorStore((s) => s.title);
44
44
  const knowledgeBaseId = usePageEditorStore((s) => s.knowledgeBaseId);
45
45
  const parentId = usePageEditorStore((s) => s.parentId);
46
46
 
@@ -61,7 +61,7 @@ const Breadcrumb = memo(() => {
61
61
  return null;
62
62
  }
63
63
 
64
- const documentTitle = currentTitle || t('pageEditor.titlePlaceholder');
64
+ const documentTitle = title || t('pageEditor.titlePlaceholder');
65
65
 
66
66
  return (
67
67
  <Flexbox align={'center'} className={styles.breadcrumb} flex={1} gap={0} horizontal>