@lobehub/lobehub 2.0.0-next.255 → 2.0.0-next.257
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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Placeholder/CreateDocument.tsx +6 -6
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +23 -10
- package/packages/builtin-tool-notebook/src/client/Streaming/CreateDocument/index.tsx +1 -1
- package/packages/database/src/models/__tests__/agent.test.ts +91 -4
- package/packages/database/src/models/agent.ts +15 -7
- package/packages/editor-runtime/src/EditorRuntime.ts +1 -1
- package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +65 -4
- package/packages/editor-runtime/src/__tests__/__snapshots__/EditorRuntime.real.test.ts.snap +108 -17
- package/packages/editor-runtime/src/__tests__/fixtures/remove-then-add.json +636 -0
- package/packages/editor-runtime/src/__tests__/fixtures/remove.json +1 -0
- package/packages/types/src/agent/agentConfig.ts +8 -8
- package/src/app/[variants]/(main)/chat/features/Portal/_layout/Mobile.tsx +2 -1
- package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +2 -1
- package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +2 -2
- package/src/app/[variants]/(main)/page/{features/PageTitle → PageTitle}/index.tsx +0 -1
- package/src/app/[variants]/(main)/page/[id]/index.tsx +43 -1
- package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/Content.tsx +15 -15
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +3 -3
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +7 -12
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +5 -5
- package/src/app/[variants]/(main)/page/_layout/Body/List/index.tsx +7 -7
- package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +15 -9
- package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/page/_layout/DataSync.tsx +15 -0
- package/src/app/[variants]/(main)/page/_layout/Header/AddButton.tsx +2 -2
- package/src/app/[variants]/(main)/page/_layout/index.tsx +2 -0
- package/src/app/[variants]/(main)/page/index.tsx +3 -7
- package/src/components/Editor/AutoSaveHint.tsx +1 -1
- package/src/features/Conversation/Messages/User/Actions/index.tsx +5 -1
- package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
- package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
- package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
- package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
- package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
- package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
- package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
- package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
- package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
- package/src/features/EditorCanvas/index.ts +9 -0
- package/src/features/PageEditor/Copilot/index.tsx +16 -1
- package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
- package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
- package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
- package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
- package/src/features/PageEditor/Header/index.tsx +15 -18
- package/src/features/PageEditor/Header/useMenu.tsx +12 -9
- package/src/features/PageEditor/PageEditor.tsx +45 -21
- package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
- package/src/features/PageEditor/PageTitle/index.tsx +2 -2
- package/src/features/PageEditor/StoreUpdater.tsx +35 -308
- package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
- package/src/features/PageEditor/store/action.ts +96 -188
- package/src/features/PageEditor/store/initialState.ts +16 -21
- package/src/features/PageEditor/store/selectors.ts +3 -4
- package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
- package/src/features/PageExplorer/index.tsx +34 -67
- package/src/features/Portal/Artifacts/index.ts +0 -2
- package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
- package/src/features/Portal/Document/Body.tsx +1 -3
- package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
- package/src/features/Portal/Document/Header.tsx +13 -10
- package/src/features/Portal/Document/TodoList.tsx +6 -4
- package/src/features/Portal/Document/Wrapper.tsx +3 -11
- package/src/features/Portal/Document/index.ts +0 -2
- package/src/features/Portal/FilePreview/index.ts +0 -2
- package/src/features/Portal/GroupThread/index.ts +0 -3
- package/src/features/Portal/MessageDetail/index.ts +0 -2
- package/src/features/Portal/Notebook/index.ts +0 -2
- package/src/features/Portal/Plugins/index.ts +0 -2
- package/src/features/Portal/Thread/index.ts +0 -3
- package/src/features/Portal/components/Header.tsx +18 -6
- package/src/features/Portal/router.tsx +34 -97
- package/src/features/Portal/type.ts +0 -2
- package/src/features/RightPanel/index.tsx +11 -2
- package/src/locales/default/plugin.ts +1 -0
- package/src/store/chat/slices/portal/action.test.ts +218 -15
- package/src/store/chat/slices/portal/action.ts +194 -41
- package/src/store/chat/slices/portal/initialState.ts +40 -1
- package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
- package/src/store/chat/slices/portal/selectors.test.ts +119 -17
- package/src/store/chat/slices/portal/selectors.ts +117 -36
- package/src/store/document/index.ts +17 -5
- package/src/store/document/slices/document/action.ts +209 -0
- package/src/store/document/slices/document/index.ts +6 -0
- package/src/store/document/slices/editor/action.test.ts +340 -0
- package/src/store/document/slices/editor/action.ts +133 -149
- package/src/store/document/slices/editor/index.ts +9 -2
- package/src/store/document/slices/editor/initialState.ts +66 -29
- package/src/store/document/slices/editor/reducer.test.ts +217 -0
- package/src/store/document/slices/editor/reducer.ts +67 -0
- package/src/store/document/slices/editor/selectors.test.ts +395 -0
- package/src/store/document/slices/editor/selectors.ts +107 -5
- package/src/store/document/store.ts +12 -13
- package/src/store/file/slices/document/action.ts +19 -188
- package/src/store/file/slices/document/initialState.ts +0 -30
- package/src/store/file/slices/document/selectors.ts +25 -59
- package/src/store/global/initialState.ts +2 -0
- package/src/store/global/selectors/systemStatus.ts +2 -0
- package/src/store/notebook/index.ts +5 -4
- package/src/store/page/index.ts +2 -0
- package/src/store/page/initialState.ts +92 -0
- package/src/store/page/selectors.ts +5 -0
- package/src/store/page/slices/crud/action.ts +477 -0
- package/src/store/page/slices/crud/index.ts +2 -0
- package/src/store/page/slices/crud/initialState.ts +7 -0
- package/src/store/page/slices/internal/action.ts +32 -0
- package/src/store/page/slices/internal/index.ts +2 -0
- package/src/store/page/slices/internal/reducer.ts +105 -0
- package/src/store/page/slices/list/action.ts +206 -0
- package/src/store/page/slices/list/index.ts +3 -0
- package/src/store/page/slices/list/initialState.ts +29 -0
- package/src/store/page/slices/list/selectors.ts +90 -0
- package/src/store/page/slices/selection/action.ts +67 -0
- package/src/store/page/slices/selection/index.ts +2 -0
- package/src/store/page/slices/selection/initialState.ts +11 -0
- package/src/store/page/store.ts +29 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
- package/src/utils/identifier.ts +8 -2
- package/src/features/PageEditor/Body/index.tsx +0 -68
- package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
- package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
- package/src/features/Portal/Artifacts/useEnable.ts +0 -4
- package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
- package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
- package/src/features/Portal/Document/Title.tsx +0 -54
- package/src/features/Portal/Document/store/action.ts +0 -114
- package/src/features/Portal/Document/store/index.ts +0 -21
- package/src/features/Portal/Document/store/initialState.ts +0 -24
- package/src/features/Portal/Document/useEnable.ts +0 -8
- package/src/features/Portal/FilePreview/useEnable.ts +0 -6
- package/src/features/Portal/GroupThread/hook.ts +0 -9
- package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
- package/src/features/Portal/Notebook/useEnable.ts +0 -6
- package/src/features/Portal/Plugins/useEnable.ts +0 -6
- package/src/features/Portal/Thread/hook.ts +0 -8
- package/src/store/document/slices/notebook/action.ts +0 -119
- package/src/store/document/slices/notebook/index.ts +0 -3
- package/src/store/document/slices/notebook/initialState.ts +0 -12
- package/src/store/document/slices/notebook/selectors.ts +0 -26
|
@@ -1,43 +1,54 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
|
|
4
|
+
import { EditorProvider } from '@lobehub/editor/react';
|
|
4
5
|
import { Flexbox } from '@lobehub/ui';
|
|
5
6
|
import { cssVar } from 'antd-style';
|
|
6
7
|
import { type FC, memo, useEffect } from 'react';
|
|
7
8
|
|
|
8
9
|
import Loading from '@/components/Loading/BrandTextLoading';
|
|
10
|
+
import DiffAllToolbar from '@/features/EditorCanvas/DiffAllToolbar';
|
|
9
11
|
import WideScreenContainer from '@/features/WideScreenContainer';
|
|
10
|
-
import { useRegisterFilesHotkeys
|
|
12
|
+
import { useRegisterFilesHotkeys } from '@/hooks/useHotkeys';
|
|
11
13
|
import { useAgentStore } from '@/store/agent';
|
|
12
14
|
import { builtinAgentSelectors } from '@/store/agent/selectors';
|
|
13
|
-
import {
|
|
15
|
+
import { useDocumentStore } from '@/store/document';
|
|
16
|
+
import { editorSelectors } from '@/store/document/slices/editor';
|
|
17
|
+
import { usePageStore } from '@/store/page';
|
|
14
18
|
|
|
15
|
-
import Body from './Body';
|
|
16
19
|
import Copilot from './Copilot';
|
|
17
|
-
import
|
|
20
|
+
import EditorCanvas from './EditorCanvas';
|
|
18
21
|
import Header from './Header';
|
|
19
22
|
import PageAgentProvider from './PageAgentProvider';
|
|
20
23
|
import { PageEditorProvider } from './PageEditorProvider';
|
|
21
24
|
import PageTitle from './PageTitle';
|
|
25
|
+
import TitleSection from './TitleSection';
|
|
22
26
|
import { usePageEditorStore } from './store';
|
|
23
27
|
|
|
24
28
|
interface PageEditorProps {
|
|
29
|
+
emoji?: string;
|
|
25
30
|
knowledgeBaseId?: string;
|
|
26
31
|
onBack?: () => void;
|
|
27
32
|
onDelete?: () => void;
|
|
28
33
|
onDocumentIdChange?: (newId: string) => void;
|
|
34
|
+
onEmojiChange?: (emoji: string | undefined) => void;
|
|
29
35
|
onSave?: () => void;
|
|
36
|
+
onTitleChange?: (title: string) => void;
|
|
30
37
|
pageId?: string;
|
|
38
|
+
title?: string;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
const PageEditorCanvas = memo(() => {
|
|
34
42
|
const editor = usePageEditorStore((s) => s.editor);
|
|
35
|
-
const
|
|
36
|
-
|
|
43
|
+
const documentId = usePageEditorStore((s) => s.documentId);
|
|
44
|
+
|
|
45
|
+
// Get isDirty from DocumentStore
|
|
46
|
+
const isDirty = useDocumentStore((s) =>
|
|
47
|
+
documentId ? editorSelectors.isDirty(documentId)(s) : false,
|
|
48
|
+
);
|
|
37
49
|
|
|
38
50
|
// Register Files scope and save document hotkey
|
|
39
51
|
useRegisterFilesHotkeys();
|
|
40
|
-
useSaveDocumentHotkey(flushSave);
|
|
41
52
|
|
|
42
53
|
// Warn user before leaving page with unsaved changes
|
|
43
54
|
useEffect(() => {
|
|
@@ -75,10 +86,13 @@ const PageEditorCanvas = memo(() => {
|
|
|
75
86
|
width={'100%'}
|
|
76
87
|
>
|
|
77
88
|
<WideScreenContainer onClick={() => editor?.focus()} wrapperStyle={{ cursor: 'text' }}>
|
|
78
|
-
<
|
|
89
|
+
<Flexbox flex={1} style={{ overflowY: 'auto', position: 'relative' }}>
|
|
90
|
+
<TitleSection />
|
|
91
|
+
<EditorCanvas />
|
|
92
|
+
</Flexbox>
|
|
79
93
|
</WideScreenContainer>
|
|
80
94
|
</Flexbox>
|
|
81
|
-
<DiffAllToolbar />
|
|
95
|
+
{documentId && <DiffAllToolbar documentId={documentId} editor={editor!} />}
|
|
82
96
|
</Flexbox>
|
|
83
97
|
<Copilot />
|
|
84
98
|
</Flexbox>
|
|
@@ -95,31 +109,41 @@ export const PageEditor: FC<PageEditorProps> = ({
|
|
|
95
109
|
pageId,
|
|
96
110
|
knowledgeBaseId,
|
|
97
111
|
onDocumentIdChange,
|
|
112
|
+
onEmojiChange,
|
|
98
113
|
onSave,
|
|
114
|
+
onTitleChange,
|
|
99
115
|
onBack,
|
|
116
|
+
title,
|
|
117
|
+
emoji,
|
|
100
118
|
}) => {
|
|
101
119
|
const useInitBuiltinAgent = useAgentStore((s) => s.useInitBuiltinAgent);
|
|
102
120
|
const pageAgentId = useAgentStore(builtinAgentSelectors.pageAgentId);
|
|
103
121
|
|
|
104
122
|
useInitBuiltinAgent(BUILTIN_AGENT_SLUGS.pageAgent);
|
|
105
123
|
|
|
106
|
-
const
|
|
124
|
+
const deletePage = usePageStore((s) => s.deletePage);
|
|
107
125
|
|
|
108
126
|
if (!pageAgentId) return <Loading debugId="PageEditor > PageAgent Init" />;
|
|
109
127
|
|
|
110
128
|
return (
|
|
111
129
|
<PageAgentProvider pageAgentId={pageAgentId}>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
<EditorProvider>
|
|
131
|
+
<PageEditorProvider
|
|
132
|
+
emoji={emoji}
|
|
133
|
+
key={pageId}
|
|
134
|
+
knowledgeBaseId={knowledgeBaseId}
|
|
135
|
+
onBack={onBack}
|
|
136
|
+
onDelete={() => deletePage(pageId || '')}
|
|
137
|
+
onDocumentIdChange={onDocumentIdChange}
|
|
138
|
+
onEmojiChange={onEmojiChange}
|
|
139
|
+
onSave={onSave}
|
|
140
|
+
onTitleChange={onTitleChange}
|
|
141
|
+
pageId={pageId}
|
|
142
|
+
title={title}
|
|
143
|
+
>
|
|
144
|
+
<PageEditorCanvas />
|
|
145
|
+
</PageEditorProvider>
|
|
146
|
+
</EditorProvider>
|
|
123
147
|
</PageAgentProvider>
|
|
124
148
|
);
|
|
125
149
|
};
|
|
@@ -19,10 +19,14 @@ export const PageEditorProvider = memo<PageEditorProviderProps>(
|
|
|
19
19
|
pageId,
|
|
20
20
|
knowledgeBaseId,
|
|
21
21
|
onDocumentIdChange,
|
|
22
|
+
onEmojiChange,
|
|
22
23
|
onSave,
|
|
24
|
+
onTitleChange,
|
|
23
25
|
onDelete,
|
|
24
26
|
onBack,
|
|
25
27
|
parentId,
|
|
28
|
+
title,
|
|
29
|
+
emoji,
|
|
26
30
|
}) => {
|
|
27
31
|
const editor = useEditor();
|
|
28
32
|
|
|
@@ -30,25 +34,33 @@ export const PageEditorProvider = memo<PageEditorProviderProps>(
|
|
|
30
34
|
<Provider
|
|
31
35
|
createStore={() =>
|
|
32
36
|
createStore({
|
|
37
|
+
documentId: pageId,
|
|
33
38
|
editor,
|
|
39
|
+
emoji,
|
|
34
40
|
knowledgeBaseId,
|
|
35
41
|
onBack,
|
|
36
42
|
onDelete,
|
|
37
43
|
onDocumentIdChange,
|
|
44
|
+
onEmojiChange,
|
|
38
45
|
onSave,
|
|
39
|
-
|
|
46
|
+
onTitleChange,
|
|
40
47
|
parentId,
|
|
48
|
+
title,
|
|
41
49
|
})
|
|
42
50
|
}
|
|
43
51
|
>
|
|
44
52
|
<StoreUpdater
|
|
53
|
+
emoji={emoji}
|
|
45
54
|
knowledgeBaseId={knowledgeBaseId}
|
|
46
55
|
onBack={onBack}
|
|
47
56
|
onDelete={onDelete}
|
|
48
57
|
onDocumentIdChange={onDocumentIdChange}
|
|
58
|
+
onEmojiChange={onEmojiChange}
|
|
49
59
|
onSave={onSave}
|
|
60
|
+
onTitleChange={onTitleChange}
|
|
50
61
|
pageId={pageId}
|
|
51
62
|
parentId={parentId}
|
|
63
|
+
title={title}
|
|
52
64
|
/>
|
|
53
65
|
{children}
|
|
54
66
|
</Provider>
|
|
@@ -6,8 +6,8 @@ import PageTitle from '@/components/PageTitle';
|
|
|
6
6
|
import { selectors, usePageEditorStore } from '@/features/PageEditor/store';
|
|
7
7
|
|
|
8
8
|
const Title = memo(() => {
|
|
9
|
-
const pageTitle = usePageEditorStore(selectors.
|
|
10
|
-
return <PageTitle title={pageTitle} />;
|
|
9
|
+
const pageTitle = usePageEditorStore(selectors.title);
|
|
10
|
+
return pageTitle && <PageTitle title={pageTitle} />;
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
export default Title;
|
|
@@ -1,319 +1,61 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import debug from 'debug';
|
|
5
|
-
import React, { memo, useEffect, useRef } from 'react';
|
|
3
|
+
import { memo, useEffect } from 'react';
|
|
6
4
|
import { createStoreUpdater } from 'zustand-utils';
|
|
7
5
|
|
|
8
|
-
import { useFileStore } from '@/store/file';
|
|
9
|
-
import { documentSelectors } from '@/store/file/slices/document/selectors';
|
|
10
6
|
import { pageAgentRuntime } from '@/store/tool/slices/builtin/executors/lobe-page-agent';
|
|
11
7
|
|
|
12
8
|
import { type PublicState, usePageEditorStore, useStoreApi } from './store';
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type StoreUpdaterProps = Partial<PublicState>;
|
|
17
|
-
|
|
18
|
-
// State machine types
|
|
19
|
-
type InitPhase =
|
|
20
|
-
| 'idle' // Initial state
|
|
21
|
-
| 'waiting-for-data' // Waiting for SWR to fetch/return cached data
|
|
22
|
-
| 'initializing' // Setting metadata (currentDocId, title, emoji)
|
|
23
|
-
| 'loading-content' // Loading content into editor
|
|
24
|
-
| 'ready' // Initialization complete
|
|
25
|
-
| 'error'; // Error occurred
|
|
26
|
-
|
|
27
|
-
interface InitState {
|
|
28
|
-
error?: Error;
|
|
29
|
-
phase: InitPhase;
|
|
30
|
-
targetPageId: string | undefined;
|
|
10
|
+
export interface StoreUpdaterProps extends Partial<PublicState> {
|
|
11
|
+
pageId?: string;
|
|
31
12
|
}
|
|
32
13
|
|
|
14
|
+
/**
|
|
15
|
+
* StoreUpdater syncs PageEditorStore props and connects to page agent runtime.
|
|
16
|
+
*
|
|
17
|
+
* Note: Document content loading is handled by EditorCanvas via DocumentStore.
|
|
18
|
+
* Title/emoji are consumed from PageEditorStore (set via setCurrentTitle/setCurrentEmoji).
|
|
19
|
+
*/
|
|
33
20
|
const StoreUpdater = memo<StoreUpdaterProps>(
|
|
34
|
-
({
|
|
21
|
+
({
|
|
22
|
+
pageId,
|
|
23
|
+
knowledgeBaseId,
|
|
24
|
+
onDocumentIdChange,
|
|
25
|
+
onEmojiChange,
|
|
26
|
+
onSave,
|
|
27
|
+
onTitleChange,
|
|
28
|
+
onDelete,
|
|
29
|
+
onBack,
|
|
30
|
+
parentId,
|
|
31
|
+
title,
|
|
32
|
+
emoji,
|
|
33
|
+
}) => {
|
|
35
34
|
const storeApi = useStoreApi();
|
|
36
35
|
const useStoreUpdater = createStoreUpdater(storeApi);
|
|
37
36
|
|
|
38
37
|
const editor = usePageEditorStore((s) => s.editor);
|
|
39
|
-
const
|
|
40
|
-
const currentDocId = usePageEditorStore((s) => s.currentDocId);
|
|
41
|
-
|
|
42
|
-
// Use SWR hook for document fetching with caching
|
|
43
|
-
const { isLoading: isLoadingDetail, error: swrError } = useFileStore((s) =>
|
|
44
|
-
s.useFetchDocumentDetail(pageId),
|
|
45
|
-
);
|
|
46
|
-
const currentPage = useFileStore(documentSelectors.getDocumentById(pageId));
|
|
47
|
-
|
|
48
|
-
const [editorInit, setEditorInit] = React.useState(false);
|
|
49
|
-
const [contentInit, setContentInit] = React.useState(false);
|
|
50
|
-
const [phaseUpdateCounter, setPhaseUpdateCounter] = React.useState(0);
|
|
51
|
-
const lastLoadedDocIdRef = useRef<string | undefined>(undefined);
|
|
52
|
-
const initStateRef = useRef<InitState>({
|
|
53
|
-
phase: 'idle',
|
|
54
|
-
targetPageId: undefined,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Helper to transition phase and trigger re-render
|
|
58
|
-
const transitionPhase = React.useCallback((newPhase: InitPhase) => {
|
|
59
|
-
log(`Transitioning phase: ${initStateRef.current.phase} -> ${newPhase}`);
|
|
60
|
-
initStateRef.current.phase = newPhase;
|
|
61
|
-
setPhaseUpdateCounter((n) => n + 1); // Trigger re-render
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
// Update editorState in store
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
storeApi.setState({ editorState });
|
|
67
|
-
}, [editorState, storeApi]);
|
|
38
|
+
const initMeta = usePageEditorStore((s) => s.initMeta);
|
|
68
39
|
|
|
69
40
|
// Update store with props
|
|
70
|
-
useStoreUpdater('
|
|
41
|
+
useStoreUpdater('documentId', pageId);
|
|
71
42
|
useStoreUpdater('knowledgeBaseId', knowledgeBaseId);
|
|
72
43
|
useStoreUpdater('onDocumentIdChange', onDocumentIdChange);
|
|
44
|
+
useStoreUpdater('onEmojiChange', onEmojiChange);
|
|
73
45
|
useStoreUpdater('onSave', onSave);
|
|
46
|
+
useStoreUpdater('onTitleChange', onTitleChange);
|
|
74
47
|
useStoreUpdater('onDelete', onDelete);
|
|
75
48
|
useStoreUpdater('onBack', onBack);
|
|
76
49
|
useStoreUpdater('parentId', parentId);
|
|
77
50
|
|
|
78
|
-
//
|
|
51
|
+
// Initialize meta (title/emoji) with dirty tracking
|
|
79
52
|
useEffect(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// Phase handler functions
|
|
83
|
-
const handleIdlePhase = () => {
|
|
84
|
-
// Check if we can start initialization
|
|
85
|
-
if (!pageId || !editor || !editorInit) {
|
|
86
|
-
log('idle: Waiting for prerequisites', { editor: !!editor, editorInit, pageId });
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Transition to waiting-for-data
|
|
91
|
-
log('idle -> waiting-for-data:', pageId);
|
|
92
|
-
|
|
93
|
-
// Reset UI state
|
|
94
|
-
setContentInit(false);
|
|
95
|
-
storeApi.setState({
|
|
96
|
-
currentTitle: '',
|
|
97
|
-
isLoadingContent: true,
|
|
98
|
-
wordCount: 0,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
transitionPhase('waiting-for-data');
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const handleWaitingForDataPhase = () => {
|
|
105
|
-
// Check for errors
|
|
106
|
-
if (swrError && !isLoadingDetail) {
|
|
107
|
-
log('waiting-for-data: Error occurred', swrError);
|
|
108
|
-
initStateRef.current.error = swrError as Error;
|
|
109
|
-
storeApi.setState({ isLoadingContent: false });
|
|
110
|
-
transitionPhase('error');
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Wait for SWR to finish loading
|
|
115
|
-
if (isLoadingDetail) {
|
|
116
|
-
log('waiting-for-data: Still loading...');
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check if we have data
|
|
121
|
-
if (!currentPage) {
|
|
122
|
-
log('waiting-for-data: No data available yet');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Transition to initializing
|
|
127
|
-
log('waiting-for-data -> initializing');
|
|
128
|
-
transitionPhase('initializing');
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const handleInitializingPhase = () => {
|
|
132
|
-
// Check if already initialized for this pageId
|
|
133
|
-
if (lastLoadedDocIdRef.current === pageId && currentDocId === pageId) {
|
|
134
|
-
log('initializing: Already initialized, moving to loading-content');
|
|
135
|
-
transitionPhase('loading-content');
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Set metadata
|
|
140
|
-
log('initializing: Setting metadata for pageId:', pageId, {
|
|
141
|
-
hasEditorData: !!currentPage?.editorData,
|
|
142
|
-
title: currentPage?.title,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
lastLoadedDocIdRef.current = pageId;
|
|
146
|
-
setContentInit(false);
|
|
147
|
-
|
|
148
|
-
storeApi.setState({
|
|
149
|
-
currentDocId: pageId,
|
|
150
|
-
currentEmoji: currentPage?.metadata?.emoji,
|
|
151
|
-
currentTitle: currentPage?.title || '',
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Transition to loading-content
|
|
155
|
-
log('initializing -> loading-content');
|
|
156
|
-
transitionPhase('loading-content');
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const handleLoadingContentPhase = () => {
|
|
160
|
-
// Prerequisites check
|
|
161
|
-
if (!editor || !editorInit || contentInit) {
|
|
162
|
-
log('loading-content: Waiting', { contentInit, editor: !!editor, editorInit });
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Safety check: Prevent loading stale content
|
|
167
|
-
const currentState = storeApi.getState();
|
|
168
|
-
if (currentState.currentDocId && currentState.currentDocId !== pageId) {
|
|
169
|
-
log('loading-content: currentDocId mismatch, aborting', {
|
|
170
|
-
currentDocId: currentState.currentDocId,
|
|
171
|
-
pageId,
|
|
172
|
-
});
|
|
173
|
-
initStateRef.current.error = new Error('Document ID mismatch');
|
|
174
|
-
storeApi.setState({ isLoadingContent: false });
|
|
175
|
-
transitionPhase('error');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Load content (defer to avoid flushSync warning)
|
|
180
|
-
log('loading-content: Queueing content load');
|
|
181
|
-
|
|
182
|
-
queueMicrotask(() => {
|
|
183
|
-
try {
|
|
184
|
-
// Re-check state in case pageId changed during microtask
|
|
185
|
-
if (initStateRef.current.targetPageId !== pageId) {
|
|
186
|
-
log('loading-content: PageId changed during queue, aborting');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
log('Loading content for page:', pageId);
|
|
191
|
-
|
|
192
|
-
// Helper to calculate word count
|
|
193
|
-
const calculateWordCount = (text: string) =>
|
|
194
|
-
text.trim().split(/\s+/).filter(Boolean).length;
|
|
195
|
-
|
|
196
|
-
storeApi.setState({ lastUpdatedTime: null });
|
|
197
|
-
|
|
198
|
-
// Check if editorData is valid and non-empty
|
|
199
|
-
const hasValidEditorData =
|
|
200
|
-
currentPage?.editorData &&
|
|
201
|
-
typeof currentPage.editorData === 'object' &&
|
|
202
|
-
Object.keys(currentPage.editorData).length > 0;
|
|
203
|
-
|
|
204
|
-
// Load from editorData if available
|
|
205
|
-
if (hasValidEditorData) {
|
|
206
|
-
log('Loading from editorData');
|
|
207
|
-
editor.setDocument('json', JSON.stringify(currentPage.editorData));
|
|
208
|
-
const textContent = currentPage.content || '';
|
|
209
|
-
storeApi.setState({ wordCount: calculateWordCount(textContent) });
|
|
210
|
-
} else if (currentPage?.content && currentPage.content.trim()) {
|
|
211
|
-
log('Loading from content - no valid editorData found');
|
|
212
|
-
editor.setDocument('markdown', currentPage.content);
|
|
213
|
-
storeApi.setState({ wordCount: calculateWordCount(currentPage.content) });
|
|
214
|
-
} else if (currentPage?.pages) {
|
|
215
|
-
// Fallback to pages content
|
|
216
|
-
const pagesContent = currentPage.pages
|
|
217
|
-
.map((page) => page.pageContent)
|
|
218
|
-
.join('\n\n')
|
|
219
|
-
.trim();
|
|
220
|
-
if (pagesContent) {
|
|
221
|
-
log('Loading from pages content');
|
|
222
|
-
editor.setDocument('markdown', pagesContent);
|
|
223
|
-
storeApi.setState({ wordCount: calculateWordCount(pagesContent) });
|
|
224
|
-
} else {
|
|
225
|
-
log('Clearing editor - empty pages');
|
|
226
|
-
editor.setDocument('markdown', ' ');
|
|
227
|
-
storeApi.setState({ wordCount: 0 });
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
// Empty document or temp page - clear editor with minimal content
|
|
231
|
-
log('Clearing editor - empty/new page');
|
|
232
|
-
editor.setDocument('markdown', ' ');
|
|
233
|
-
storeApi.setState({ wordCount: 0 });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
setContentInit(true);
|
|
237
|
-
storeApi.setState({ isLoadingContent: false });
|
|
238
|
-
|
|
239
|
-
// Transition to ready
|
|
240
|
-
log('loading-content -> ready');
|
|
241
|
-
transitionPhase('ready');
|
|
242
|
-
} catch (error) {
|
|
243
|
-
log('Failed to load editor content:', error);
|
|
244
|
-
storeApi.setState({ isLoadingContent: false });
|
|
245
|
-
initStateRef.current.error = error as Error;
|
|
246
|
-
transitionPhase('error');
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const handleErrorPhase = () => {
|
|
252
|
-
const error = initStateRef.current.error;
|
|
253
|
-
log('error phase:', error?.message);
|
|
254
|
-
// Error state is sticky until pageId changes
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// Reset to idle if pageId changes
|
|
258
|
-
if (pageId !== state.targetPageId) {
|
|
259
|
-
log('PageId changed, resetting to idle', { from: state.targetPageId, to: pageId });
|
|
260
|
-
initStateRef.current = { phase: 'idle', targetPageId: pageId };
|
|
261
|
-
setContentInit(false);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Early exit if already ready
|
|
266
|
-
if (state.phase === 'ready') return;
|
|
267
|
-
|
|
268
|
-
// Execute phase handler
|
|
269
|
-
switch (state.phase) {
|
|
270
|
-
case 'idle': {
|
|
271
|
-
handleIdlePhase();
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
case 'waiting-for-data': {
|
|
275
|
-
handleWaitingForDataPhase();
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
case 'initializing': {
|
|
279
|
-
handleInitializingPhase();
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
case 'loading-content': {
|
|
283
|
-
handleLoadingContentPhase();
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
case 'error': {
|
|
287
|
-
handleErrorPhase();
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}, [
|
|
292
|
-
contentInit,
|
|
293
|
-
currentDocId,
|
|
294
|
-
currentPage,
|
|
295
|
-
editor,
|
|
296
|
-
editorInit,
|
|
297
|
-
isLoadingDetail,
|
|
298
|
-
pageId,
|
|
299
|
-
phaseUpdateCounter,
|
|
300
|
-
storeApi,
|
|
301
|
-
swrError,
|
|
302
|
-
transitionPhase,
|
|
303
|
-
]);
|
|
304
|
-
|
|
305
|
-
// Track editor initialization
|
|
306
|
-
useEffect(() => {
|
|
307
|
-
if (editor && !editorInit) {
|
|
308
|
-
setEditorInit(true);
|
|
309
|
-
}
|
|
310
|
-
}, [editor, editorInit]);
|
|
53
|
+
initMeta(title, emoji);
|
|
54
|
+
}, [pageId, title, emoji]);
|
|
311
55
|
|
|
312
56
|
// Connect editor to page agent runtime
|
|
313
57
|
useEffect(() => {
|
|
314
58
|
if (editor) {
|
|
315
|
-
// for easier debug , mount editor instance to window
|
|
316
|
-
window.__editor = editor;
|
|
317
59
|
pageAgentRuntime.setEditor(editor);
|
|
318
60
|
}
|
|
319
61
|
return () => {
|
|
@@ -321,35 +63,20 @@ const StoreUpdater = memo<StoreUpdaterProps>(
|
|
|
321
63
|
};
|
|
322
64
|
}, [editor]);
|
|
323
65
|
|
|
324
|
-
// Connect title handlers to page agent runtime
|
|
66
|
+
// Connect title handlers and document ID to page agent runtime
|
|
325
67
|
useEffect(() => {
|
|
326
|
-
const titleSetter = (title: string) => {
|
|
327
|
-
storeApi.setState({ currentTitle: title });
|
|
328
|
-
};
|
|
329
|
-
|
|
330
68
|
const titleGetter = () => {
|
|
331
|
-
return storeApi.getState().
|
|
69
|
+
return storeApi.getState().title || '';
|
|
332
70
|
};
|
|
333
71
|
|
|
334
|
-
pageAgentRuntime.
|
|
72
|
+
pageAgentRuntime.setCurrentDocId(pageId);
|
|
73
|
+
pageAgentRuntime.setTitleHandlers(storeApi.getState().setTitle, titleGetter);
|
|
335
74
|
|
|
336
75
|
return () => {
|
|
337
|
-
pageAgentRuntime.setTitleHandlers(null, null);
|
|
338
|
-
};
|
|
339
|
-
}, [storeApi]);
|
|
340
|
-
|
|
341
|
-
// Update current document ID in page agent runtime when page changes
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
// Use currentDocId (which includes temp docs) or fallback to pageId
|
|
344
|
-
const activeId = currentDocId || pageId;
|
|
345
|
-
log('Updating currentDocId in page agent runtime:', activeId);
|
|
346
|
-
pageAgentRuntime.setCurrentDocId(activeId);
|
|
347
|
-
|
|
348
|
-
return () => {
|
|
349
|
-
log('Clearing currentDocId on unmount');
|
|
350
76
|
pageAgentRuntime.setCurrentDocId(undefined);
|
|
77
|
+
pageAgentRuntime.setTitleHandlers(null, null);
|
|
351
78
|
};
|
|
352
|
-
}, [
|
|
79
|
+
}, [pageId, storeApi]);
|
|
353
80
|
|
|
354
81
|
return null;
|
|
355
82
|
},
|
|
@@ -11,16 +11,16 @@ import { useGlobalStore } from '@/store/global';
|
|
|
11
11
|
import { globalGeneralSelectors } from '@/store/global/selectors';
|
|
12
12
|
import { truncateByWeightedLength } from '@/utils/textLength';
|
|
13
13
|
|
|
14
|
-
import { usePageEditorStore } from '
|
|
14
|
+
import { usePageEditorStore } from './store';
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const TitleSection = memo(() => {
|
|
17
17
|
const { t } = useTranslation('file');
|
|
18
18
|
const locale = useGlobalStore(globalGeneralSelectors.currentLanguage);
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
20
|
+
const emoji = usePageEditorStore((s) => s.emoji);
|
|
21
|
+
const title = usePageEditorStore((s) => s.title);
|
|
22
|
+
const setEmoji = usePageEditorStore((s) => s.setEmoji);
|
|
23
|
+
const setTitle = usePageEditorStore((s) => s.setTitle);
|
|
24
24
|
const handleTitleSubmit = usePageEditorStore((s) => s.handleTitleSubmit);
|
|
25
25
|
|
|
26
26
|
const [isHoveringTitle, setIsHoveringTitle] = useState(false);
|
|
@@ -41,16 +41,16 @@ const Title = memo(() => {
|
|
|
41
41
|
}}
|
|
42
42
|
>
|
|
43
43
|
{/* Emoji picker above Choose Icon button */}
|
|
44
|
-
{(
|
|
44
|
+
{(emoji || showEmojiPicker) && (
|
|
45
45
|
<EmojiPicker
|
|
46
46
|
allowDelete
|
|
47
47
|
locale={locale}
|
|
48
|
-
onChange={(
|
|
49
|
-
|
|
48
|
+
onChange={(e) => {
|
|
49
|
+
setEmoji(e);
|
|
50
50
|
setShowEmojiPicker(false);
|
|
51
51
|
}}
|
|
52
52
|
onDelete={() => {
|
|
53
|
-
|
|
53
|
+
setEmoji(undefined);
|
|
54
54
|
setShowEmojiPicker(false);
|
|
55
55
|
}}
|
|
56
56
|
onOpenChange={(open) => {
|
|
@@ -60,16 +60,16 @@ const Title = memo(() => {
|
|
|
60
60
|
shape={'square'}
|
|
61
61
|
size={72}
|
|
62
62
|
title={t('pageEditor.chooseIcon')}
|
|
63
|
-
value={
|
|
63
|
+
value={emoji}
|
|
64
64
|
/>
|
|
65
65
|
)}
|
|
66
66
|
|
|
67
67
|
{/* Choose Icon button - only shown when no emoji */}
|
|
68
|
-
{!
|
|
68
|
+
{!emoji && !showEmojiPicker && (
|
|
69
69
|
<Button
|
|
70
70
|
icon={<Icon icon={SmilePlus} />}
|
|
71
71
|
onClick={() => {
|
|
72
|
-
|
|
72
|
+
setEmoji('📄');
|
|
73
73
|
setShowEmojiPicker(true);
|
|
74
74
|
}}
|
|
75
75
|
size="small"
|
|
@@ -89,7 +89,7 @@ const Title = memo(() => {
|
|
|
89
89
|
autoSize={{ minRows: 1 }}
|
|
90
90
|
onChange={(e) => {
|
|
91
91
|
const truncated = truncateByWeightedLength(e.target.value, 100);
|
|
92
|
-
|
|
92
|
+
setTitle(truncated);
|
|
93
93
|
}}
|
|
94
94
|
onKeyDown={(e) => {
|
|
95
95
|
if (e.key === 'Enter') {
|
|
@@ -105,11 +105,11 @@ const Title = memo(() => {
|
|
|
105
105
|
resize: 'none',
|
|
106
106
|
width: '100%',
|
|
107
107
|
}}
|
|
108
|
-
value={
|
|
108
|
+
value={title}
|
|
109
109
|
variant={'borderless'}
|
|
110
110
|
/>
|
|
111
111
|
</Flexbox>
|
|
112
112
|
);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
export default
|
|
115
|
+
export default TitleSection;
|