@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
@@ -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>
@@ -1,24 +1,28 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon, Avatar, Dropdown, Skeleton, Text } from '@lobehub/ui';
3
+ import { ActionIcon, Avatar, Dropdown, Text } from '@lobehub/ui';
4
4
  import { ArrowLeftIcon, BotMessageSquareIcon, MoreHorizontal } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
+ import { AutoSaveHint } from '@/features/EditorCanvas';
9
10
  import NavHeader from '@/features/NavHeader';
10
11
  import ToggleRightPanelButton from '@/features/RightPanel/ToggleRightPanelButton';
11
12
 
12
13
  import { usePageEditorStore } from '../store';
13
- import AutoSaveHint from './AutoSaveHint';
14
14
  import Breadcrumb from './Breadcrumb';
15
15
  import { useMenu } from './useMenu';
16
16
 
17
17
  const Header = memo(() => {
18
18
  const { t } = useTranslation('file');
19
- const [currentEmoji, currentTitle, isLoadingContent, parentId, onBack] = usePageEditorStore(
20
- (s) => [s.currentEmoji, s.currentTitle, s.isLoadingContent, s.parentId, s.onBack],
21
- );
19
+ const [documentId, emoji, title, parentId, onBack] = usePageEditorStore((s) => [
20
+ s.documentId,
21
+ s.emoji,
22
+ s.title,
23
+ s.parentId,
24
+ s.onBack,
25
+ ]);
22
26
  const { menuItems } = useMenu();
23
27
 
24
28
  return (
@@ -32,22 +36,15 @@ const Header = memo(() => {
32
36
  {!parentId && (
33
37
  <>
34
38
  {/* Icon */}
35
- {currentEmoji && <Avatar avatar={currentEmoji} shape={'square'} size={28} />}
39
+ {emoji && <Avatar avatar={emoji} shape={'square'} size={28} />}
36
40
  {/* Title */}
37
- {isLoadingContent ? (
38
- <Skeleton.Button
39
- active
40
- style={{ height: 20, marginLeft: 4, maxWidth: 200, width: 200 }}
41
- />
42
- ) : (
43
- <Text ellipsis style={{ marginLeft: 4 }} weight={500}>
44
- {currentTitle || t('pageEditor.titlePlaceholder')}
45
- </Text>
46
- )}
41
+ <Text ellipsis style={{ marginLeft: 4 }} weight={500}>
42
+ {title || t('pageEditor.titlePlaceholder')}
43
+ </Text>
47
44
  </>
48
45
  )}
49
- {/* Auto Save Status - hide while loading */}
50
- {!isLoadingContent && <AutoSaveHint />}
46
+ {/* Auto Save Status */}
47
+ {documentId && <AutoSaveHint documentId={documentId} style={{ marginLeft: 6 }} />}
51
48
  </>
52
49
  }
53
50
  right={
@@ -6,6 +6,8 @@ import { CopyPlus, Download, Link2, Trash2 } from 'lucide-react';
6
6
  import { useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
+ import { useDocumentStore } from '@/store/document';
10
+ import { editorSelectors } from '@/store/document/slices/editor';
9
11
  import { useFileStore } from '@/store/file';
10
12
  import { useGlobalStore } from '@/store/global';
11
13
  import { systemStatusSelectors } from '@/store/global/selectors';
@@ -21,9 +23,12 @@ export const useMenu = (): { menuItems: any[] } => {
21
23
  const storeApi = useStoreApi();
22
24
  const { lg = true } = useResponsive();
23
25
 
24
- const lastUpdatedTime = usePageEditorStore((s) => s.lastUpdatedTime);
25
- const wordCount = usePageEditorStore((s) => s.wordCount);
26
- const currentDocId = usePageEditorStore((s) => s.currentDocId);
26
+ const documentId = usePageEditorStore((s) => s.documentId);
27
+
28
+ // Get lastUpdatedTime from DocumentStore
29
+ const lastUpdatedTime = useDocumentStore((s) =>
30
+ documentId ? editorSelectors.lastUpdatedTime(documentId)(s) : null,
31
+ );
27
32
 
28
33
  const duplicateDocument = useFileStore((s) => s.duplicateDocument);
29
34
 
@@ -36,9 +41,9 @@ export const useMenu = (): { menuItems: any[] } => {
36
41
  const showViewModeSwitch = lg;
37
42
 
38
43
  const handleDuplicate = async () => {
39
- if (!currentDocId) return;
44
+ if (!documentId) return;
40
45
  try {
41
- await duplicateDocument(currentDocId);
46
+ await duplicateDocument(documentId);
42
47
  message.success(t('pageEditor.duplicateSuccess'));
43
48
  } catch (error) {
44
49
  console.error('Failed to duplicate page:', error);
@@ -48,7 +53,7 @@ export const useMenu = (): { menuItems: any[] } => {
48
53
 
49
54
  const handleExportMarkdown = () => {
50
55
  const state = storeApi.getState();
51
- const { editor, currentTitle } = state;
56
+ const { editor, title } = state;
52
57
 
53
58
  if (!editor) return;
54
59
 
@@ -58,7 +63,7 @@ export const useMenu = (): { menuItems: any[] } => {
58
63
  const url = URL.createObjectURL(blob);
59
64
  const a = document.createElement('a');
60
65
  a.href = url;
61
- a.download = `${currentTitle || 'Untitled'}.md`;
66
+ a.download = `${title || 'Untitled'}.md`;
62
67
  document.body.append(a);
63
68
  a.click();
64
69
  a.remove();
@@ -143,7 +148,6 @@ export const useMenu = (): { menuItems: any[] } => {
143
148
  key: 'page-info',
144
149
  label: (
145
150
  <div style={{ color: cssVar.colorTextTertiary, fontSize: 12, lineHeight: 1.6 }}>
146
- <div>{t('pageEditor.wordCount', { wordCount })}</div>
147
151
  <div>
148
152
  {lastUpdatedTime
149
153
  ? t('pageEditor.editedAt', {
@@ -156,7 +160,6 @@ export const useMenu = (): { menuItems: any[] } => {
156
160
  },
157
161
  ],
158
162
  [
159
- wordCount,
160
163
  lastUpdatedTime,
161
164
  storeApi,
162
165
  t,