@lobehub/lobehub 2.0.0-next.255 → 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 (140) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -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/User/Actions/index.tsx +5 -1
  35. package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
  36. package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
  37. package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
  38. package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
  39. package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
  40. package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
  41. package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
  42. package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
  43. package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
  44. package/src/features/EditorCanvas/index.ts +9 -0
  45. package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
  46. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
  47. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
  48. package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
  49. package/src/features/PageEditor/Header/index.tsx +15 -18
  50. package/src/features/PageEditor/Header/useMenu.tsx +12 -9
  51. package/src/features/PageEditor/PageEditor.tsx +45 -21
  52. package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
  53. package/src/features/PageEditor/PageTitle/index.tsx +2 -2
  54. package/src/features/PageEditor/StoreUpdater.tsx +35 -308
  55. package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
  56. package/src/features/PageEditor/store/action.ts +96 -188
  57. package/src/features/PageEditor/store/initialState.ts +16 -21
  58. package/src/features/PageEditor/store/selectors.ts +3 -4
  59. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
  60. package/src/features/PageExplorer/index.tsx +34 -67
  61. package/src/features/Portal/Artifacts/index.ts +0 -2
  62. package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
  63. package/src/features/Portal/Document/Body.tsx +1 -3
  64. package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
  65. package/src/features/Portal/Document/Header.tsx +13 -10
  66. package/src/features/Portal/Document/TodoList.tsx +6 -4
  67. package/src/features/Portal/Document/Wrapper.tsx +3 -11
  68. package/src/features/Portal/Document/index.ts +0 -2
  69. package/src/features/Portal/FilePreview/index.ts +0 -2
  70. package/src/features/Portal/GroupThread/index.ts +0 -3
  71. package/src/features/Portal/MessageDetail/index.ts +0 -2
  72. package/src/features/Portal/Notebook/index.ts +0 -2
  73. package/src/features/Portal/Plugins/index.ts +0 -2
  74. package/src/features/Portal/Thread/index.ts +0 -3
  75. package/src/features/Portal/components/Header.tsx +18 -6
  76. package/src/features/Portal/router.tsx +34 -97
  77. package/src/features/Portal/type.ts +0 -2
  78. package/src/locales/default/plugin.ts +1 -0
  79. package/src/store/chat/slices/portal/action.test.ts +218 -15
  80. package/src/store/chat/slices/portal/action.ts +194 -41
  81. package/src/store/chat/slices/portal/initialState.ts +40 -1
  82. package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
  83. package/src/store/chat/slices/portal/selectors.test.ts +119 -17
  84. package/src/store/chat/slices/portal/selectors.ts +117 -36
  85. package/src/store/document/index.ts +17 -5
  86. package/src/store/document/slices/document/action.ts +209 -0
  87. package/src/store/document/slices/document/index.ts +6 -0
  88. package/src/store/document/slices/editor/action.test.ts +340 -0
  89. package/src/store/document/slices/editor/action.ts +133 -149
  90. package/src/store/document/slices/editor/index.ts +9 -2
  91. package/src/store/document/slices/editor/initialState.ts +66 -29
  92. package/src/store/document/slices/editor/reducer.test.ts +217 -0
  93. package/src/store/document/slices/editor/reducer.ts +67 -0
  94. package/src/store/document/slices/editor/selectors.test.ts +395 -0
  95. package/src/store/document/slices/editor/selectors.ts +107 -5
  96. package/src/store/document/store.ts +12 -13
  97. package/src/store/file/slices/document/action.ts +19 -188
  98. package/src/store/file/slices/document/initialState.ts +0 -30
  99. package/src/store/file/slices/document/selectors.ts +25 -59
  100. package/src/store/notebook/index.ts +5 -4
  101. package/src/store/page/index.ts +2 -0
  102. package/src/store/page/initialState.ts +92 -0
  103. package/src/store/page/selectors.ts +5 -0
  104. package/src/store/page/slices/crud/action.ts +477 -0
  105. package/src/store/page/slices/crud/index.ts +2 -0
  106. package/src/store/page/slices/crud/initialState.ts +7 -0
  107. package/src/store/page/slices/internal/action.ts +32 -0
  108. package/src/store/page/slices/internal/index.ts +2 -0
  109. package/src/store/page/slices/internal/reducer.ts +105 -0
  110. package/src/store/page/slices/list/action.ts +206 -0
  111. package/src/store/page/slices/list/index.ts +3 -0
  112. package/src/store/page/slices/list/initialState.ts +29 -0
  113. package/src/store/page/slices/list/selectors.ts +90 -0
  114. package/src/store/page/slices/selection/action.ts +67 -0
  115. package/src/store/page/slices/selection/index.ts +2 -0
  116. package/src/store/page/slices/selection/initialState.ts +11 -0
  117. package/src/store/page/store.ts +29 -0
  118. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
  119. package/src/utils/identifier.ts +8 -2
  120. package/src/features/PageEditor/Body/index.tsx +0 -68
  121. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
  122. package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
  123. package/src/features/Portal/Artifacts/useEnable.ts +0 -4
  124. package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
  125. package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
  126. package/src/features/Portal/Document/Title.tsx +0 -54
  127. package/src/features/Portal/Document/store/action.ts +0 -114
  128. package/src/features/Portal/Document/store/index.ts +0 -21
  129. package/src/features/Portal/Document/store/initialState.ts +0 -24
  130. package/src/features/Portal/Document/useEnable.ts +0 -8
  131. package/src/features/Portal/FilePreview/useEnable.ts +0 -6
  132. package/src/features/Portal/GroupThread/hook.ts +0 -9
  133. package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
  134. package/src/features/Portal/Notebook/useEnable.ts +0 -6
  135. package/src/features/Portal/Plugins/useEnable.ts +0 -6
  136. package/src/features/Portal/Thread/hook.ts +0 -8
  137. package/src/store/document/slices/notebook/action.ts +0 -119
  138. package/src/store/document/slices/notebook/index.ts +0 -3
  139. package/src/store/document/slices/notebook/initialState.ts +0 -12
  140. package/src/store/document/slices/notebook/selectors.ts +0 -26
@@ -1,316 +0,0 @@
1
- 'use client';
2
-
3
- import { nanoid } from '@lobechat/utils';
4
- import {
5
- HIDE_TOOLBAR_COMMAND,
6
- HotkeyEnum,
7
- INSERT_HEADING_COMMAND,
8
- getHotkeyById,
9
- } from '@lobehub/editor';
10
- import { ChatInputActions, type ChatInputActionsProps, FloatActions } from '@lobehub/editor/react';
11
- import { Block } from '@lobehub/ui';
12
- import { createStaticStyles, cssVar } from 'antd-style';
13
- import {
14
- BoldIcon,
15
- BotIcon,
16
- CodeXmlIcon,
17
- Heading1Icon,
18
- Heading2Icon,
19
- Heading3Icon,
20
- ItalicIcon,
21
- LinkIcon,
22
- ListIcon,
23
- ListOrderedIcon,
24
- ListTodoIcon,
25
- MessageSquareQuote,
26
- Redo2Icon,
27
- SigmaIcon,
28
- SquareDashedBottomCodeIcon,
29
- StrikethroughIcon,
30
- UnderlineIcon,
31
- Undo2Icon,
32
- } from 'lucide-react';
33
- import { type CSSProperties, memo, useMemo } from 'react';
34
- import { useTranslation } from 'react-i18next';
35
-
36
- import { useFileStore } from '@/store/file';
37
- import { useGlobalStore } from '@/store/global';
38
-
39
- import { usePageEditorStore } from '../store';
40
-
41
- interface ToolbarProps {
42
- className?: string;
43
- floating?: boolean;
44
- style?: CSSProperties;
45
- }
46
-
47
- const styles = createStaticStyles(({ css }) => ({
48
- askCopilot: css`
49
- border-radius: 6px;
50
- color: ${cssVar.colorTextDescription};
51
-
52
- &:hover {
53
- color: ${cssVar.colorTextSecondary};
54
- }
55
- `,
56
- }));
57
-
58
- const TypoBar = memo<ToolbarProps>(({ floating, style, className }) => {
59
- const { t } = useTranslation('editor');
60
- const editor = usePageEditorStore((s) => s.editor);
61
- const editorState = usePageEditorStore((s) => s.editorState);
62
- const addSelectionContext = useFileStore((s) => s.addChatContextSelection);
63
-
64
- const items: ChatInputActionsProps['items'] = useMemo(() => {
65
- if (!editorState) return [];
66
- const baseItems = [
67
- floating && {
68
- children: (
69
- <Block
70
- align="center"
71
- className={styles.askCopilot}
72
- clickable
73
- gap={8}
74
- horizontal
75
- onClick={() => {
76
- if (!editor) return;
77
-
78
- const xml = (editor.getSelectionDocument?.('litexml') as string) || '';
79
- const plainText = (editor.getSelectionDocument?.('text') as string) || '';
80
- const content = xml.trim() || plainText.trim();
81
-
82
- if (!content) return;
83
-
84
- const format = xml.trim() ? 'xml' : 'text';
85
- const preview =
86
- (plainText || xml)
87
- .replaceAll(/<[^>]*>/g, ' ')
88
- .replaceAll(/\s+/g, ' ')
89
- .trim() || undefined;
90
-
91
- // Store action handles deduplication
92
- addSelectionContext({
93
- content,
94
- format,
95
- id: `selection-${nanoid(6)}`,
96
- preview,
97
- title: 'Selection',
98
- type: 'text',
99
- });
100
-
101
- // Open right panel if not opened
102
- useGlobalStore.getState().toggleRightPanel(true);
103
-
104
- // Focus on chat input after a short delay to ensure panel is opened
105
- setTimeout(() => {
106
- // Find the chat input editor within the right panel
107
- // Query all lexical editors and get the last one (which should be the chat input)
108
- const allEditors = [...document.querySelectorAll('[data-lexical-editor="true"]')];
109
- const chatInputEditor = allEditors.at(-1) as HTMLElement;
110
- if (chatInputEditor) {
111
- chatInputEditor.focus();
112
- }
113
- }, 300);
114
-
115
- editor.dispatchCommand(HIDE_TOOLBAR_COMMAND, undefined);
116
- editor.blur();
117
- }}
118
- paddingBlock={6}
119
- paddingInline={12}
120
- variant="borderless"
121
- >
122
- <BotIcon />
123
- <span>Ask Copilot</span>
124
- </Block>
125
- ),
126
- key: 'ask-copilot',
127
- label: 'Ask Copilot',
128
- onClick: () => {},
129
- tooltipProps: { hotkey: undefined },
130
- },
131
- {
132
- type: 'divider',
133
- },
134
- !floating && {
135
- disabled: !editorState.canUndo,
136
- icon: Undo2Icon,
137
- key: 'undo',
138
- label: t('typobar.undo', 'Undo'),
139
- onClick: editorState.undo,
140
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Undo).keys },
141
- },
142
- !floating && {
143
- disabled: !editorState.canRedo,
144
- icon: Redo2Icon,
145
- key: 'redo',
146
- label: t('typobar.redo', 'Redo'),
147
- onClick: editorState.redo,
148
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Redo).keys },
149
- },
150
- !floating && {
151
- type: 'divider',
152
- },
153
- {
154
- active: editorState.isBold,
155
- icon: BoldIcon,
156
- key: 'bold',
157
- label: t('typobar.bold'),
158
- onClick: editorState.bold,
159
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Bold).keys },
160
- },
161
- {
162
- active: editorState.isItalic,
163
- icon: ItalicIcon,
164
- key: 'italic',
165
- label: t('typobar.italic'),
166
- onClick: editorState.italic,
167
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Italic).keys },
168
- },
169
- {
170
- active: editorState.isUnderline,
171
- icon: UnderlineIcon,
172
- key: 'underline',
173
- label: t('typobar.underline'),
174
- onClick: editorState.underline,
175
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Underline).keys },
176
- },
177
- {
178
- active: editorState.isStrikethrough,
179
- icon: StrikethroughIcon,
180
- key: 'strikethrough',
181
- label: t('typobar.strikethrough'),
182
- onClick: editorState.strikethrough,
183
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Strikethrough).keys },
184
- },
185
- {
186
- type: 'divider',
187
- },
188
- !floating && {
189
- icon: Heading1Icon,
190
- key: 'h1',
191
- label: t('slash.h1'),
192
- onClick: () => {
193
- if (editor) {
194
- editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h1' });
195
- }
196
- },
197
- },
198
- !floating && {
199
- icon: Heading2Icon,
200
- key: 'h2',
201
- label: t('slash.h2'),
202
- onClick: () => {
203
- if (editor) {
204
- editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h2' });
205
- }
206
- },
207
- },
208
- !floating && {
209
- icon: Heading3Icon,
210
- key: 'h3',
211
- label: t('slash.h3'),
212
- onClick: () => {
213
- if (editor) {
214
- editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h3' });
215
- }
216
- },
217
- },
218
- !floating && {
219
- type: 'divider',
220
- },
221
- {
222
- icon: ListIcon,
223
- key: 'bulletList',
224
- label: t('typobar.bulletList'),
225
- onClick: editorState.bulletList,
226
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.BulletList).keys },
227
- },
228
- {
229
- icon: ListOrderedIcon,
230
- key: 'numberlist',
231
- label: t('typobar.numberList'),
232
- onClick: editorState.numberList,
233
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.NumberList).keys },
234
- },
235
- {
236
- icon: ListTodoIcon,
237
- key: 'tasklist',
238
- label: t('typobar.taskList'),
239
- onClick: editorState.checkList,
240
- },
241
- {
242
- type: 'divider',
243
- },
244
- {
245
- active: editorState.isBlockquote,
246
- icon: MessageSquareQuote,
247
- key: 'blockquote',
248
- label: t('typobar.blockquote'),
249
- onClick: editorState.blockquote,
250
- },
251
- {
252
- icon: LinkIcon,
253
- key: 'link',
254
- label: t('typobar.link'),
255
- onClick: editorState.insertLink,
256
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Link).keys },
257
- },
258
- {
259
- icon: SigmaIcon,
260
- key: 'math',
261
- label: t('typobar.tex'),
262
- onClick: editorState.insertMath,
263
- },
264
- {
265
- type: 'divider',
266
- },
267
- {
268
- active: editorState.isCode,
269
- icon: CodeXmlIcon,
270
- key: 'code',
271
- label: t('typobar.code'),
272
- onClick: editorState.code,
273
- tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.CodeInline).keys },
274
- },
275
- !floating && {
276
- icon: SquareDashedBottomCodeIcon,
277
- key: 'codeblock',
278
- label: t('typobar.codeblock'),
279
- onClick: editorState.codeblock,
280
- },
281
- ];
282
-
283
- return baseItems.filter(Boolean) as ChatInputActionsProps['items'];
284
- }, [addSelectionContext, editor, editorState, floating, t]);
285
-
286
- if (!editorState) return null;
287
-
288
- // Floating toolbar - just return the actions
289
- if (floating) return <FloatActions className={className} items={items} style={style} />;
290
-
291
- // Fixed toolbar - wrap in a styled container
292
- return (
293
- <Block
294
- className={className}
295
- padding={4}
296
- shadow
297
- style={{
298
- background: cssVar.colorBgElevated,
299
- borderRadius: 8,
300
- marginBottom: 16,
301
- marginTop: 16,
302
- position: 'sticky',
303
- top: 12,
304
- zIndex: 10,
305
- ...style,
306
- }}
307
- variant={'outlined'}
308
- >
309
- <ChatInputActions items={items} />
310
- </Block>
311
- );
312
- });
313
-
314
- TypoBar.displayName = 'PageEditorToolbar';
315
-
316
- export default TypoBar;
@@ -1,27 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
-
5
- import AutoSaveHintBase from '@/components/Editor/AutoSaveHint';
6
-
7
- import { usePageEditorStore } from '../store';
8
-
9
- /**
10
- * AutoSaveHint - Save status indicator for page editor
11
- */
12
- const AutoSaveHint = memo(() => {
13
- const saveStatus = usePageEditorStore((s) => s.saveStatus);
14
- const lastUpdatedTime = usePageEditorStore((s) => s.lastUpdatedTime);
15
-
16
- return (
17
- <AutoSaveHintBase
18
- lastUpdatedTime={lastUpdatedTime}
19
- saveStatus={saveStatus}
20
- style={{
21
- marginLeft: 6,
22
- }}
23
- />
24
- );
25
- });
26
-
27
- export default AutoSaveHint;
@@ -1,4 +0,0 @@
1
- import { useChatStore } from '@/store/chat';
2
- import { chatPortalSelectors } from '@/store/chat/selectors';
3
-
4
- export const useEnable = () => useChatStore(chatPortalSelectors.showArtifactUI);
@@ -1,34 +0,0 @@
1
- 'use client';
2
-
3
- import { useEditor } from '@lobehub/editor/react';
4
- import { type ReactNode, memo } from 'react';
5
-
6
- import StoreUpdater from './StoreUpdater';
7
- import { DocumentEditorProvider as Provider, createStore } from './store';
8
-
9
- interface DocumentEditorProviderProps {
10
- children: ReactNode;
11
- documentId: string | undefined;
12
- topicId: string | undefined;
13
- }
14
-
15
- export const DocumentEditorProvider = memo<DocumentEditorProviderProps>(
16
- ({ children, documentId, topicId }) => {
17
- const editor = useEditor();
18
-
19
- return (
20
- <Provider
21
- createStore={() =>
22
- createStore({
23
- documentId,
24
- editor,
25
- topicId,
26
- })
27
- }
28
- >
29
- <StoreUpdater documentId={documentId} topicId={topicId} />
30
- {children}
31
- </Provider>
32
- );
33
- },
34
- );
@@ -1,80 +0,0 @@
1
- 'use client';
2
-
3
- import debug from 'debug';
4
- import { memo, useEffect, useRef, useState } from 'react';
5
- import { createStoreUpdater } from 'zustand-utils';
6
-
7
- import { useNotebookStore } from '@/store/notebook';
8
- import { notebookSelectors } from '@/store/notebook/selectors';
9
-
10
- import { useDocumentEditorStore, useDocumentEditorStoreApi } from './store';
11
-
12
- const log = debug('portal:document-store-updater');
13
-
14
- interface StoreUpdaterProps {
15
- documentId: string | undefined;
16
- topicId: string | undefined;
17
- }
18
-
19
- const StoreUpdater = memo<StoreUpdaterProps>(({ documentId, topicId }) => {
20
- const storeApi = useDocumentEditorStoreApi();
21
- const useStoreUpdater = createStoreUpdater(storeApi);
22
-
23
- const editor = useDocumentEditorStore((s) => s.editor);
24
-
25
- const document = useNotebookStore(notebookSelectors.getDocumentById(topicId, documentId));
26
-
27
- const [editorInit, setEditorInit] = useState(false);
28
- const [contentInit, setContentInit] = useState(false);
29
- const lastLoadedDocIdRef = useRef<string | undefined>(undefined);
30
-
31
- // Update store with props
32
- useStoreUpdater('documentId', documentId);
33
- useStoreUpdater('topicId', topicId);
34
-
35
- // Load content into editor when document changes
36
- useEffect(() => {
37
- if (!editorInit || !editor || !document) return;
38
-
39
- // Skip if already initialized for this document
40
- if (contentInit && lastLoadedDocIdRef.current === documentId) return;
41
-
42
- // Reset content init when document changes
43
- if (lastLoadedDocIdRef.current !== documentId) {
44
- setContentInit(false);
45
- }
46
-
47
- queueMicrotask(() => {
48
- try {
49
- log('Loading content for document:', documentId);
50
-
51
- const content = document.content || '';
52
-
53
- // Set state before setDocument to ensure lastSavedContent is correct
54
- // when handleContentChange is triggered
55
- storeApi.setState({
56
- currentTitle: document.title || '',
57
- lastSavedContent: content,
58
- });
59
-
60
- editor.setDocument('markdown', content || ' ');
61
-
62
- lastLoadedDocIdRef.current = documentId;
63
- setContentInit(true);
64
- } catch (error) {
65
- log('Failed to load editor content:', error);
66
- }
67
- });
68
- }, [editorInit, editor, document, documentId, contentInit, storeApi]);
69
-
70
- // Track editor initialization
71
- useEffect(() => {
72
- if (editor && !editorInit) {
73
- setEditorInit(true);
74
- }
75
- }, [editor, editorInit]);
76
-
77
- return null;
78
- });
79
-
80
- export default StoreUpdater;
@@ -1,54 +0,0 @@
1
- 'use client';
2
-
3
- import { Flexbox, TextArea } from '@lobehub/ui';
4
- import { memo } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
-
7
- import { useDocumentEditorStore } from './store';
8
-
9
- const Title = memo(() => {
10
- const { t } = useTranslation('file');
11
-
12
- const currentTitle = useDocumentEditorStore((s) => s.currentTitle);
13
- const setCurrentTitle = useDocumentEditorStore((s) => s.setCurrentTitle);
14
- const handleTitleSubmit = useDocumentEditorStore((s) => s.handleTitleSubmit);
15
-
16
- return (
17
- <Flexbox
18
- gap={16}
19
- onClick={(e) => {
20
- e.stopPropagation();
21
- e.preventDefault();
22
- }}
23
- paddingBlock={16}
24
- style={{
25
- cursor: 'default',
26
- }}
27
- >
28
- <TextArea
29
- autoSize={{ minRows: 1 }}
30
- onChange={(e) => {
31
- setCurrentTitle(e.target.value);
32
- }}
33
- onKeyDown={(e) => {
34
- if (e.key === 'Enter') {
35
- e.preventDefault();
36
- handleTitleSubmit();
37
- }
38
- }}
39
- placeholder={t('pageEditor.titlePlaceholder')}
40
- style={{
41
- fontSize: 24,
42
- fontWeight: 600,
43
- padding: 0,
44
- resize: 'none',
45
- width: '100%',
46
- }}
47
- value={currentTitle}
48
- variant={'borderless'}
49
- />
50
- </Flexbox>
51
- );
52
- });
53
-
54
- export default Title;
@@ -1,114 +0,0 @@
1
- 'use client';
2
-
3
- import { EDITOR_DEBOUNCE_TIME, EDITOR_MAX_WAIT } from '@lobechat/const';
4
- import debug from 'debug';
5
- import { debounce } from 'es-toolkit/compat';
6
- import { type StateCreator } from 'zustand';
7
-
8
- import { useNotebookStore } from '@/store/notebook';
9
-
10
- import { type DocumentEditorState, initialDocumentEditorState } from './initialState';
11
-
12
- const log = debug('portal:document-editor');
13
-
14
- export interface DocumentEditorAction {
15
- flushSave: () => void;
16
- handleContentChange: () => void;
17
- handleTitleSubmit: () => Promise<void>;
18
- performSave: () => Promise<void>;
19
- setCurrentTitle: (title: string) => void;
20
- }
21
-
22
- export type DocumentEditorStore = DocumentEditorState & DocumentEditorAction;
23
-
24
- const createDebouncedSave = (get: () => DocumentEditorStore) =>
25
- debounce(
26
- async () => {
27
- try {
28
- await get().performSave();
29
- } catch (error) {
30
- log('Failed to auto-save:', error);
31
- }
32
- },
33
- EDITOR_DEBOUNCE_TIME,
34
- { leading: false, maxWait: EDITOR_MAX_WAIT, trailing: true },
35
- );
36
-
37
- export const createDocumentEditorStore: (
38
- initState?: Partial<DocumentEditorState>,
39
- ) => StateCreator<DocumentEditorStore> = (initState) => (set, get) => {
40
- const debouncedSave = createDebouncedSave(get);
41
-
42
- return {
43
- ...initialDocumentEditorState,
44
- ...initState,
45
-
46
- flushSave: () => {
47
- debouncedSave.flush();
48
- },
49
-
50
- handleContentChange: () => {
51
- const { editor, lastSavedContent } = get();
52
- if (!editor) return;
53
-
54
- try {
55
- const markdownContent = (editor.getDocument('markdown') as unknown as string) || '';
56
- const contentChanged = markdownContent !== lastSavedContent;
57
-
58
- set({ isDirty: contentChanged });
59
-
60
- if (contentChanged) {
61
- debouncedSave();
62
- }
63
- } catch (error) {
64
- log('Failed to update content:', error);
65
- }
66
- },
67
-
68
- handleTitleSubmit: async () => {
69
- const { performSave, editor } = get();
70
- await performSave();
71
- editor?.focus();
72
- },
73
-
74
- performSave: async () => {
75
- const { editor, documentId, topicId, isDirty, currentTitle } = get();
76
-
77
- if (!editor || !documentId || !topicId) return;
78
-
79
- if (!isDirty) return;
80
-
81
- set({ saveStatus: 'saving' });
82
-
83
- try {
84
- const currentContent = (editor.getDocument('markdown') as unknown as string) || '';
85
-
86
- await useNotebookStore.getState().updateDocument(
87
- {
88
- content: currentContent,
89
- id: documentId,
90
- title: currentTitle || undefined,
91
- },
92
- topicId,
93
- );
94
-
95
- set({
96
- isDirty: false,
97
- lastSavedContent: currentContent,
98
- lastUpdatedTime: new Date(),
99
- saveStatus: 'saved',
100
- });
101
-
102
- log('Document saved successfully:', documentId);
103
- } catch (error) {
104
- log('Failed to save document:', error);
105
- set({ saveStatus: 'idle' });
106
- }
107
- },
108
-
109
- setCurrentTitle: (title: string) => {
110
- set({ currentTitle: title, isDirty: true });
111
- debouncedSave();
112
- },
113
- };
114
- };
@@ -1,21 +0,0 @@
1
- 'use client';
2
-
3
- import { type StoreApiWithSelector } from '@lobechat/types';
4
- import { createContext } from 'zustand-utils';
5
- import { subscribeWithSelector } from 'zustand/middleware';
6
- import { shallow } from 'zustand/shallow';
7
- import { createWithEqualityFn } from 'zustand/traditional';
8
-
9
- import { type DocumentEditorStore, createDocumentEditorStore } from './action';
10
- import { type DocumentEditorState } from './initialState';
11
-
12
- export type { DocumentEditorState } from './initialState';
13
-
14
- export const createStore = (initState?: Partial<DocumentEditorState>) =>
15
- createWithEqualityFn(subscribeWithSelector(createDocumentEditorStore(initState)), shallow);
16
-
17
- export const {
18
- useStore: useDocumentEditorStore,
19
- useStoreApi: useDocumentEditorStoreApi,
20
- Provider: DocumentEditorProvider,
21
- } = createContext<StoreApiWithSelector<DocumentEditorStore>>();
@@ -1,24 +0,0 @@
1
- 'use client';
2
-
3
- import { type IEditor } from '@lobehub/editor';
4
-
5
- export interface DocumentEditorState {
6
- currentTitle: string;
7
- documentId: string | undefined;
8
- editor?: IEditor;
9
- isDirty: boolean;
10
- lastSavedContent: string;
11
- lastUpdatedTime: Date | null;
12
- saveStatus: 'idle' | 'saving' | 'saved';
13
- topicId: string | undefined;
14
- }
15
-
16
- export const initialDocumentEditorState: DocumentEditorState = {
17
- currentTitle: '',
18
- documentId: undefined,
19
- isDirty: false,
20
- lastSavedContent: '',
21
- lastUpdatedTime: null,
22
- saveStatus: 'idle',
23
- topicId: undefined,
24
- };
@@ -1,8 +0,0 @@
1
- 'use client';
2
-
3
- import { useChatStore } from '@/store/chat';
4
- import { chatPortalSelectors } from '@/store/chat/selectors';
5
-
6
- export const useEnable = () => {
7
- return useChatStore(chatPortalSelectors.showDocument);
8
- };
@@ -1,6 +0,0 @@
1
- import { useChatStore } from '@/store/chat';
2
- import { chatPortalSelectors } from '@/store/chat/selectors';
3
-
4
- export const useEnable = () => {
5
- return useChatStore(chatPortalSelectors.showFilePreview);
6
- };