@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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -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/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/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/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
|
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
7
7
|
|
|
8
8
|
import { PortalContent } from '@/features/Portal/router';
|
|
9
9
|
import { useChatStore } from '@/store/chat';
|
|
10
|
+
import { portalThreadSelectors } from '@/store/chat/selectors';
|
|
10
11
|
|
|
11
12
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
12
13
|
container: css`
|
|
@@ -17,7 +18,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
17
18
|
const Layout = () => {
|
|
18
19
|
const [showMobilePortal, isPortalThread, togglePortal] = useChatStore((s) => [
|
|
19
20
|
s.showPortal,
|
|
20
|
-
|
|
21
|
+
portalThreadSelectors.showThread(s),
|
|
21
22
|
s.togglePortal,
|
|
22
23
|
]);
|
|
23
24
|
const { t } = useTranslation('portal');
|
|
@@ -13,8 +13,8 @@ import { DEFAULT_CHAT_GROUP_CHAT_CONFIG } from '@/const/settings';
|
|
|
13
13
|
import { type GroupMemberConfig, chatGroupService } from '@/services/chatGroup';
|
|
14
14
|
import { useAgentStore } from '@/store/agent';
|
|
15
15
|
import { useAgentGroupStore } from '@/store/agentGroup';
|
|
16
|
-
import { useFileStore } from '@/store/file';
|
|
17
16
|
import { useHomeStore } from '@/store/home';
|
|
17
|
+
import { usePageStore } from '@/store/page';
|
|
18
18
|
|
|
19
19
|
interface HostConfig {
|
|
20
20
|
model?: string;
|
|
@@ -45,7 +45,7 @@ export const useCreateMenuItems = () => {
|
|
|
45
45
|
s.switchToGroup,
|
|
46
46
|
]);
|
|
47
47
|
const [createGroup, loadGroups] = useAgentGroupStore((s) => [s.createGroup, s.loadGroups]);
|
|
48
|
-
const createNewPage =
|
|
48
|
+
const createNewPage = usePageStore((s) => s.createNewPage);
|
|
49
49
|
|
|
50
50
|
const [isCreatingGroup, setIsCreatingGroup] = useState(false);
|
|
51
51
|
const [isCreatingSessionGroup, setIsCreatingSessionGroup] = useState(false);
|
|
@@ -1 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useUnmount } from 'ahooks';
|
|
4
|
+
import { Suspense, memo } from 'react';
|
|
5
|
+
import { useParams } from 'react-router-dom';
|
|
6
|
+
import { createStoreUpdater } from 'zustand-utils';
|
|
7
|
+
|
|
8
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
|
9
|
+
import PageExplorer from '@/features/PageExplorer';
|
|
10
|
+
import { usePageStore } from '@/store/page';
|
|
11
|
+
import { getIdFromIdentifier } from '@/utils/identifier';
|
|
12
|
+
|
|
13
|
+
import PageTitle from '../PageTitle';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pages route - dedicated page for managing documents/pages
|
|
17
|
+
* This is extracted from the /resource route to have its own dedicated space
|
|
18
|
+
*/
|
|
19
|
+
const PagesPage = memo(() => {
|
|
20
|
+
const storeUpdater = createStoreUpdater(usePageStore);
|
|
21
|
+
const params = useParams<{ id: string }>();
|
|
22
|
+
|
|
23
|
+
const pageId = getIdFromIdentifier(params.id ?? '', 'docs');
|
|
24
|
+
storeUpdater('selectedPageId', pageId);
|
|
25
|
+
|
|
26
|
+
// Clear activeAgentId when unmounting (leaving chat page)
|
|
27
|
+
useUnmount(() => {
|
|
28
|
+
usePageStore.setState({ selectedPageId: undefined });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<PageTitle />
|
|
34
|
+
<Suspense fallback={<Loading debugId="PagesPage" />}>
|
|
35
|
+
<PageExplorer pageId={pageId} />
|
|
36
|
+
</Suspense>
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
PagesPage.displayName = 'PagesPage';
|
|
42
|
+
|
|
43
|
+
export default PagesPage;
|
|
@@ -6,7 +6,7 @@ import { VList, type VListHandle } from 'virtua';
|
|
|
6
6
|
|
|
7
7
|
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
|
8
8
|
import PageEmpty from '@/features/PageEmpty';
|
|
9
|
-
import {
|
|
9
|
+
import { pageSelectors, usePageStore } from '@/store/page';
|
|
10
10
|
import { type LobeDocument } from '@/types/document';
|
|
11
11
|
|
|
12
12
|
import Item from '../List/Item';
|
|
@@ -19,27 +19,27 @@ const Content = memo<ContentProps>(({ searchKeyword }) => {
|
|
|
19
19
|
const virtuaRef = useRef<VListHandle>(null);
|
|
20
20
|
const fetchedCountRef = useRef(-1);
|
|
21
21
|
|
|
22
|
-
const [hasMore, isLoadingMore, loadMoreDocuments] =
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const [hasMore, isLoadingMore, loadMoreDocuments] = usePageStore((s) => [
|
|
23
|
+
pageSelectors.hasMoreDocuments(s),
|
|
24
|
+
pageSelectors.isLoadingMoreDocuments(s),
|
|
25
25
|
s.loadMoreDocuments,
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const allFilteredDocuments = usePageStore(pageSelectors.getFilteredDocuments);
|
|
29
29
|
|
|
30
30
|
// Filter by search keyword
|
|
31
|
-
const
|
|
32
|
-
if (!searchKeyword.trim()) return
|
|
31
|
+
const displayDocuments = useMemo(() => {
|
|
32
|
+
if (!searchKeyword.trim()) return allFilteredDocuments;
|
|
33
33
|
|
|
34
34
|
const keyword = searchKeyword.toLowerCase();
|
|
35
|
-
return
|
|
36
|
-
const content =
|
|
37
|
-
const title =
|
|
35
|
+
return allFilteredDocuments.filter((doc: LobeDocument) => {
|
|
36
|
+
const content = doc.content?.toLowerCase() || '';
|
|
37
|
+
const title = doc.title?.toLowerCase() || '';
|
|
38
38
|
return content.includes(keyword) || title.includes(keyword);
|
|
39
39
|
});
|
|
40
|
-
}, [
|
|
40
|
+
}, [allFilteredDocuments, searchKeyword]);
|
|
41
41
|
|
|
42
|
-
const count =
|
|
42
|
+
const count = displayDocuments.length;
|
|
43
43
|
const isSearching = searchKeyword.trim().length > 0;
|
|
44
44
|
|
|
45
45
|
// Handle scroll - use findItemIndex (official pattern)
|
|
@@ -74,9 +74,9 @@ const Content = memo<ContentProps>(({ searchKeyword }) => {
|
|
|
74
74
|
ref={virtuaRef}
|
|
75
75
|
style={{ height: '100%' }}
|
|
76
76
|
>
|
|
77
|
-
{
|
|
78
|
-
<Flexbox gap={1} key={
|
|
79
|
-
<Item pageId={
|
|
77
|
+
{displayDocuments.map((doc) => (
|
|
78
|
+
<Flexbox gap={1} key={doc.id} padding={'4px 8px'}>
|
|
79
|
+
<Item pageId={doc.id} />
|
|
80
80
|
</Flexbox>
|
|
81
81
|
))}
|
|
82
82
|
{showLoading && (
|
|
@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
|
|
5
5
|
import EmojiPicker from '@/components/EmojiPicker';
|
|
6
6
|
import { useIsDark } from '@/hooks/useIsDark';
|
|
7
|
-
import { useFileStore } from '@/store/file';
|
|
8
7
|
import { useGlobalStore } from '@/store/global';
|
|
9
8
|
import { globalGeneralSelectors } from '@/store/global/selectors';
|
|
9
|
+
import { usePageStore } from '@/store/page';
|
|
10
10
|
|
|
11
11
|
interface EditingProps {
|
|
12
12
|
currentEmoji?: string;
|
|
@@ -20,7 +20,7 @@ const Editing = memo<EditingProps>(({ documentId, title, currentEmoji, toggleEdi
|
|
|
20
20
|
const isDarkMode = useIsDark();
|
|
21
21
|
const { t } = useTranslation('file');
|
|
22
22
|
|
|
23
|
-
const editing =
|
|
23
|
+
const editing = usePageStore((s) => s.renamingPageId === documentId);
|
|
24
24
|
|
|
25
25
|
const [newTitle, setNewTitle] = useState(title);
|
|
26
26
|
const [newEmoji, setNewEmoji] = useState(currentEmoji);
|
|
@@ -35,7 +35,7 @@ const Editing = memo<EditingProps>(({ documentId, title, currentEmoji, toggleEdi
|
|
|
35
35
|
if (newTitle && title !== newTitle) updates.title = newTitle;
|
|
36
36
|
if (newEmoji !== undefined && currentEmoji !== newEmoji) updates.emoji = newEmoji;
|
|
37
37
|
|
|
38
|
-
await
|
|
38
|
+
await usePageStore.getState().renamePage(documentId, updates.title || title, updates.emoji);
|
|
39
39
|
} catch (error) {
|
|
40
40
|
console.error('Failed to update page:', error);
|
|
41
41
|
}
|
|
@@ -4,7 +4,7 @@ import { memo, useCallback, useMemo } from 'react';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
6
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
7
|
-
import {
|
|
7
|
+
import { pageSelectors, usePageStore } from '@/store/page';
|
|
8
8
|
|
|
9
9
|
import Actions from './Actions';
|
|
10
10
|
import Editing from './Editing';
|
|
@@ -17,18 +17,13 @@ interface DocumentItemProps {
|
|
|
17
17
|
|
|
18
18
|
const PageListItem = memo<DocumentItemProps>(({ pageId, className }) => {
|
|
19
19
|
const { t } = useTranslation('file');
|
|
20
|
-
const [editing, selectedPageId, document] =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return [s.renamingPageId === pageId, s.selectedPageId, doc] as const;
|
|
25
|
-
},
|
|
26
|
-
[pageId],
|
|
27
|
-
),
|
|
28
|
-
);
|
|
20
|
+
const [editing, selectedPageId, document] = usePageStore((s) => {
|
|
21
|
+
const doc = pageSelectors.getDocumentById(pageId)(s);
|
|
22
|
+
return [s.renamingPageId === pageId, s.selectedPageId, doc] as const;
|
|
23
|
+
});
|
|
29
24
|
|
|
30
|
-
const selectPage =
|
|
31
|
-
const setRenamingPageId =
|
|
25
|
+
const selectPage = usePageStore((s) => s.selectPage);
|
|
26
|
+
const setRenamingPageId = usePageStore((s) => s.setRenamingPageId);
|
|
32
27
|
|
|
33
28
|
const active = selectedPageId === pageId;
|
|
34
29
|
const title = document?.title || t('pageList.untitled');
|
|
@@ -4,7 +4,7 @@ import { Copy, CopyPlus, Pencil, Trash2 } from 'lucide-react';
|
|
|
4
4
|
import { useCallback } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { usePageStore } from '@/store/page';
|
|
8
8
|
|
|
9
9
|
interface ActionProps {
|
|
10
10
|
documentContent?: string;
|
|
@@ -19,8 +19,8 @@ export const useDropdownMenu = ({
|
|
|
19
19
|
}: ActionProps): (() => MenuProps['items']) => {
|
|
20
20
|
const { t } = useTranslation(['common', 'file']);
|
|
21
21
|
const { message, modal } = App.useApp();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
22
|
+
const removePage = usePageStore((s) => s.removePage);
|
|
23
|
+
const duplicatePage = usePageStore((s) => s.duplicatePage);
|
|
24
24
|
|
|
25
25
|
const handleDelete = () => {
|
|
26
26
|
modal.confirm({
|
|
@@ -30,7 +30,7 @@ export const useDropdownMenu = ({
|
|
|
30
30
|
okText: t('delete'),
|
|
31
31
|
onOk: async () => {
|
|
32
32
|
try {
|
|
33
|
-
await
|
|
33
|
+
await removePage(pageId);
|
|
34
34
|
message.success(t('pageEditor.deleteSuccess', { ns: 'file' }));
|
|
35
35
|
} catch (error) {
|
|
36
36
|
console.error('Failed to delete page:', error);
|
|
@@ -53,7 +53,7 @@ export const useDropdownMenu = ({
|
|
|
53
53
|
|
|
54
54
|
const handleDuplicate = async () => {
|
|
55
55
|
try {
|
|
56
|
-
await
|
|
56
|
+
await duplicatePage(pageId);
|
|
57
57
|
} catch (error) {
|
|
58
58
|
console.error('Failed to duplicate page:', error);
|
|
59
59
|
}
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
8
8
|
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
|
9
|
-
import {
|
|
9
|
+
import { pageSelectors, usePageStore } from '@/store/page';
|
|
10
10
|
|
|
11
11
|
import Item from './Item';
|
|
12
12
|
|
|
@@ -16,17 +16,17 @@ import Item from './Item';
|
|
|
16
16
|
const PageList = () => {
|
|
17
17
|
const { t } = useTranslation(['file', 'common']);
|
|
18
18
|
|
|
19
|
-
const [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const [filteredDocuments, hasMore, isLoadingMore, openAllPagesDrawer] = usePageStore((s) => [
|
|
20
|
+
pageSelectors.getFilteredDocumentsLimited(s),
|
|
21
|
+
pageSelectors.hasMoreFilteredDocuments(s),
|
|
22
|
+
pageSelectors.isLoadingMoreDocuments(s),
|
|
23
23
|
s.openAllPagesDrawer,
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
27
|
<Flexbox gap={1}>
|
|
28
|
-
{
|
|
29
|
-
<Item key={
|
|
28
|
+
{filteredDocuments.map((doc) => (
|
|
29
|
+
<Item key={doc.id} pageId={doc.id} />
|
|
30
30
|
))}
|
|
31
31
|
{isLoadingMore && <SkeletonList rows={3} />}
|
|
32
32
|
{hasMore && !isLoadingMore && (
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
|
8
8
|
import PageEmpty from '@/features/PageEmpty';
|
|
9
|
-
import {
|
|
9
|
+
import { pageSelectors, usePageStore } from '@/store/page';
|
|
10
10
|
|
|
11
11
|
import Actions from './Actions';
|
|
12
12
|
import AllPagesDrawer from './AllPagesDrawer';
|
|
@@ -22,12 +22,18 @@ export enum GroupKey {
|
|
|
22
22
|
*/
|
|
23
23
|
const Body = memo(() => {
|
|
24
24
|
const { t } = useTranslation('file');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
25
|
+
|
|
26
|
+
// Initialize documents list via SWR
|
|
27
|
+
const useFetchDocuments = usePageStore((s) => s.useFetchDocuments);
|
|
28
|
+
useFetchDocuments();
|
|
29
|
+
|
|
30
|
+
const isLoading = usePageStore(pageSelectors.isDocumentsLoading);
|
|
31
|
+
|
|
32
|
+
const filteredDocumentsCount = usePageStore(pageSelectors.filteredDocumentsCount);
|
|
33
|
+
const filteredDocuments = usePageStore(pageSelectors.getFilteredDocumentsLimited);
|
|
34
|
+
const searchKeywords = usePageStore((s) => s.searchKeywords);
|
|
29
35
|
const dropdownMenu = useDropdownMenu();
|
|
30
|
-
const [allPagesDrawerOpen, closeAllPagesDrawer] =
|
|
36
|
+
const [allPagesDrawerOpen, closeAllPagesDrawer] = usePageStore((s) => [
|
|
31
37
|
s.allPagesDrawerOpen,
|
|
32
38
|
s.closeAllPagesDrawer,
|
|
33
39
|
]);
|
|
@@ -53,16 +59,16 @@ const Body = memo(() => {
|
|
|
53
59
|
title={
|
|
54
60
|
<Text ellipsis fontSize={12} type={'secondary'} weight={500}>
|
|
55
61
|
{t('pageList.title')}
|
|
56
|
-
{
|
|
62
|
+
{filteredDocumentsCount > 0 && ` ${filteredDocumentsCount}`}
|
|
57
63
|
</Text>
|
|
58
64
|
}
|
|
59
65
|
>
|
|
60
66
|
<Suspense fallback={<SkeletonList />}>
|
|
61
|
-
{
|
|
67
|
+
{isLoading ? (
|
|
62
68
|
<SkeletonList />
|
|
63
69
|
) : (
|
|
64
70
|
<Flexbox gap={1} paddingBlock={1}>
|
|
65
|
-
{
|
|
71
|
+
{filteredDocuments.length === 0 ? (
|
|
66
72
|
<PageEmpty search={Boolean(searchKeywords.trim())} />
|
|
67
73
|
) : (
|
|
68
74
|
<List />
|
|
@@ -6,14 +6,14 @@ import { Hash, LucideCheck } from 'lucide-react';
|
|
|
6
6
|
import { useMemo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
|
-
import { useFileStore } from '@/store/file';
|
|
10
9
|
import { useGlobalStore } from '@/store/global';
|
|
11
10
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
11
|
+
import { usePageStore } from '@/store/page';
|
|
12
12
|
|
|
13
13
|
export const useDropdownMenu = (): MenuProps['items'] => {
|
|
14
14
|
const { t } = useTranslation();
|
|
15
|
-
const showOnlyPagesNotInLibrary =
|
|
16
|
-
const setShowOnlyPagesNotInLibrary =
|
|
15
|
+
const showOnlyPagesNotInLibrary = usePageStore((s) => s.showOnlyPagesNotInLibrary);
|
|
16
|
+
const setShowOnlyPagesNotInLibrary = usePageStore((s) => s.setShowOnlyPagesNotInLibrary);
|
|
17
17
|
|
|
18
18
|
const [pagePageSize, updateSystemStatus] = useGlobalStore((s) => [
|
|
19
19
|
systemStatusSelectors.pagePageSize(s),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useNavigate } from 'react-router-dom';
|
|
2
|
+
import { createStoreUpdater } from 'zustand-utils';
|
|
3
|
+
|
|
4
|
+
import { usePageStore } from '@/store/page';
|
|
5
|
+
|
|
6
|
+
const DataSync = () => {
|
|
7
|
+
const usePageStoreUpdater = createStoreUpdater(usePageStore);
|
|
8
|
+
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
usePageStoreUpdater('navigate', navigate);
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default DataSync;
|
|
@@ -5,12 +5,12 @@ import { SquarePenIcon } from 'lucide-react';
|
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { usePageStore } from '@/store/page';
|
|
9
9
|
|
|
10
10
|
const AddButton = memo(() => {
|
|
11
11
|
const { t } = useTranslation('file');
|
|
12
12
|
|
|
13
|
-
const createNewPage =
|
|
13
|
+
const createNewPage = usePageStore((s) => s.createNewPage);
|
|
14
14
|
|
|
15
15
|
const handleNewDocument = () => {
|
|
16
16
|
const untitledTitle = t('pageList.untitled');
|
|
@@ -4,6 +4,7 @@ import { Flexbox } from '@lobehub/ui';
|
|
|
4
4
|
import { type FC } from 'react';
|
|
5
5
|
import { Outlet } from 'react-router-dom';
|
|
6
6
|
|
|
7
|
+
import DataSync from './DataSync';
|
|
7
8
|
import Sidebar from './Sidebar';
|
|
8
9
|
import { styles } from './style';
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ const DesktopPagesLayout: FC = () => {
|
|
|
14
15
|
<Flexbox className={styles.mainContainer} flex={1} height={'100%'}>
|
|
15
16
|
<Outlet />
|
|
16
17
|
</Flexbox>
|
|
18
|
+
<DataSync />
|
|
17
19
|
</>
|
|
18
20
|
);
|
|
19
21
|
};
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Suspense, memo } from 'react';
|
|
4
|
-
import { useParams } from 'react-router-dom';
|
|
5
4
|
|
|
6
5
|
import Loading from '@/components/Loading/BrandTextLoading';
|
|
7
|
-
import
|
|
8
|
-
import { standardizeIdentifier } from '@/utils/identifier';
|
|
6
|
+
import PageExplorerPlaceholder from '@/features/PageExplorer/PageExplorerPlaceholder';
|
|
9
7
|
|
|
10
|
-
import PageTitle from './
|
|
8
|
+
import PageTitle from './PageTitle';
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* Pages route - dedicated page for managing documents/pages
|
|
14
12
|
* This is extracted from the /resource route to have its own dedicated space
|
|
15
13
|
*/
|
|
16
14
|
const PagesPage = memo(() => {
|
|
17
|
-
const { id } = useParams<{ id: string }>();
|
|
18
|
-
|
|
19
15
|
return (
|
|
20
16
|
<>
|
|
21
17
|
<PageTitle />
|
|
22
18
|
<Suspense fallback={<Loading debugId="PagesPage" />}>
|
|
23
|
-
<
|
|
19
|
+
<PageExplorerPlaceholder />
|
|
24
20
|
</Suspense>
|
|
25
21
|
</>
|
|
26
22
|
);
|
|
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
10
10
|
dayjs.extend(relativeTime);
|
|
11
11
|
|
|
12
12
|
interface AutoSaveHintProps {
|
|
13
|
-
lastUpdatedTime?: Date | null;
|
|
13
|
+
lastUpdatedTime?: string | Date | null;
|
|
14
14
|
saveStatus: 'idle' | 'saving' | 'saved';
|
|
15
15
|
style?: CSSProperties;
|
|
16
16
|
}
|
|
@@ -76,7 +76,11 @@ export const UserActionsBar = memo<UserActionsProps>(({ actionsConfig, id, data
|
|
|
76
76
|
// Use external config if provided, otherwise use defaults
|
|
77
77
|
// Append extra actions from factories
|
|
78
78
|
const barItems = useMemo(() => {
|
|
79
|
-
const base = actionsConfig?.bar ?? [
|
|
79
|
+
const base = actionsConfig?.bar ?? [
|
|
80
|
+
defaultActions.regenerate,
|
|
81
|
+
defaultActions.edit,
|
|
82
|
+
defaultActions.copy,
|
|
83
|
+
];
|
|
80
84
|
return [...base, ...extraBarItems];
|
|
81
85
|
}, [actionsConfig?.bar, defaultActions.regenerate, defaultActions.edit, extraBarItems]);
|
|
82
86
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type CSSProperties, memo } from 'react';
|
|
4
|
+
|
|
5
|
+
import AutoSaveHintBase from '@/components/Editor/AutoSaveHint';
|
|
6
|
+
import { useDocumentStore } from '@/store/document';
|
|
7
|
+
import { editorSelectors } from '@/store/document/slices/editor';
|
|
8
|
+
|
|
9
|
+
export interface AutoSaveHintProps {
|
|
10
|
+
/**
|
|
11
|
+
* Document ID to get save status from DocumentStore
|
|
12
|
+
*/
|
|
13
|
+
documentId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Custom styles
|
|
16
|
+
*/
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* AutoSave hint component that reads from DocumentStore
|
|
22
|
+
* Use this component externally to display save status for a document
|
|
23
|
+
*/
|
|
24
|
+
const AutoSaveHint = memo<AutoSaveHintProps>(({ documentId, style }) => {
|
|
25
|
+
const saveStatus = useDocumentStore((s) => editorSelectors.saveStatus(documentId)(s));
|
|
26
|
+
const lastUpdatedTime = useDocumentStore(
|
|
27
|
+
(s) => editorSelectors.lastUpdatedTime(documentId)(s) ?? null,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<AutoSaveHintBase lastUpdatedTime={lastUpdatedTime} saveStatus={saveStatus} style={style} />
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
AutoSaveHint.displayName = 'AutoSaveHint';
|
|
36
|
+
|
|
37
|
+
export default AutoSaveHint;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND } from '@lobehub/editor';
|
|
3
|
+
import { DiffAction, IEditor, LITEXML_DIFFNODE_ALL_COMMAND } from '@lobehub/editor';
|
|
4
4
|
import { Block, Icon } from '@lobehub/ui';
|
|
5
5
|
import { Button, Space } from 'antd';
|
|
6
6
|
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
|
@@ -9,8 +9,7 @@ import { memo, useEffect, useState } from 'react';
|
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
|
|
11
11
|
import { useIsDark } from '@/hooks/useIsDark';
|
|
12
|
-
|
|
13
|
-
import { usePageEditorStore } from './store';
|
|
12
|
+
import { useDocumentStore } from '@/store/document';
|
|
14
13
|
|
|
15
14
|
const styles = createStaticStyles(({ css }) => ({
|
|
16
15
|
container: css`
|
|
@@ -36,18 +35,36 @@ const styles = createStaticStyles(({ css }) => ({
|
|
|
36
35
|
`,
|
|
37
36
|
}));
|
|
38
37
|
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
const useIsEditorInit = (editor: IEditor) => {
|
|
39
|
+
const [isEditInit, setEditInit] = useState<boolean>(!!editor.getLexicalEditor());
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!editor) return;
|
|
43
|
+
|
|
44
|
+
const onInit = () => {
|
|
45
|
+
console.log('init: id', editor.getLexicalEditor()?._key);
|
|
46
|
+
setEditInit(true);
|
|
47
|
+
};
|
|
48
|
+
editor.on('initialized', onInit);
|
|
49
|
+
return () => {
|
|
50
|
+
editor.off('initialized', onInit);
|
|
51
|
+
};
|
|
52
|
+
}, [editor]);
|
|
53
|
+
|
|
54
|
+
return isEditInit;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const useEditorHasPendingDiffs = (editor: IEditor) => {
|
|
43
58
|
const [hasPendingDiffs, setHasPendingDiffs] = useState(false);
|
|
59
|
+
const isEditInit = useIsEditorInit(editor);
|
|
44
60
|
|
|
45
61
|
// Listen to editor state changes to detect diff nodes
|
|
46
62
|
useEffect(() => {
|
|
47
63
|
if (!editor) return;
|
|
48
64
|
|
|
49
65
|
const lexicalEditor = editor.getLexicalEditor();
|
|
50
|
-
|
|
66
|
+
|
|
67
|
+
if (!lexicalEditor || !isEditInit) return;
|
|
51
68
|
|
|
52
69
|
const checkForDiffNodes = () => {
|
|
53
70
|
const editorState = lexicalEditor.getEditorState();
|
|
@@ -67,15 +84,39 @@ const DiffAllToolbar = memo(() => {
|
|
|
67
84
|
// Check initially
|
|
68
85
|
checkForDiffNodes();
|
|
69
86
|
|
|
70
|
-
// Register update listener
|
|
71
87
|
const unregister = lexicalEditor.registerUpdateListener(() => {
|
|
72
88
|
checkForDiffNodes();
|
|
73
89
|
});
|
|
90
|
+
// Register update listener
|
|
91
|
+
return () => {
|
|
92
|
+
unregister();
|
|
93
|
+
};
|
|
94
|
+
}, [editor, isEditInit]);
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
return hasPendingDiffs;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
interface DiffAllToolbarProps {
|
|
100
|
+
documentId: string;
|
|
101
|
+
editor: IEditor;
|
|
102
|
+
}
|
|
103
|
+
const DiffAllToolbar = memo<DiffAllToolbarProps>(({ documentId }) => {
|
|
104
|
+
const { t } = useTranslation('editor');
|
|
105
|
+
const isDarkMode = useIsDark();
|
|
106
|
+
const [editor, performSave, markDirty] = useDocumentStore((s) => [
|
|
107
|
+
s.editor!,
|
|
108
|
+
s.performSave,
|
|
109
|
+
s.markDirty,
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const hasPendingDiffs = useEditorHasPendingDiffs(editor);
|
|
113
|
+
|
|
114
|
+
if (!hasPendingDiffs) return null;
|
|
77
115
|
|
|
78
|
-
|
|
116
|
+
const handleSave = async () => {
|
|
117
|
+
markDirty(documentId);
|
|
118
|
+
await performSave();
|
|
119
|
+
};
|
|
79
120
|
|
|
80
121
|
return (
|
|
81
122
|
<div className={styles.container}>
|
|
@@ -90,10 +131,10 @@ const DiffAllToolbar = memo(() => {
|
|
|
90
131
|
<Space>
|
|
91
132
|
<Button
|
|
92
133
|
onClick={async () => {
|
|
93
|
-
editor
|
|
134
|
+
editor?.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
|
|
94
135
|
action: DiffAction.Reject,
|
|
95
136
|
});
|
|
96
|
-
await
|
|
137
|
+
await handleSave();
|
|
97
138
|
}}
|
|
98
139
|
size={'small'}
|
|
99
140
|
type="text"
|
|
@@ -104,10 +145,10 @@ const DiffAllToolbar = memo(() => {
|
|
|
104
145
|
<Button
|
|
105
146
|
color={'default'}
|
|
106
147
|
onClick={async () => {
|
|
107
|
-
editor
|
|
148
|
+
editor?.dispatchCommand(LITEXML_DIFFNODE_ALL_COMMAND, {
|
|
108
149
|
action: DiffAction.Accept,
|
|
109
150
|
});
|
|
110
|
-
await
|
|
151
|
+
await handleSave();
|
|
111
152
|
}}
|
|
112
153
|
size={'small'}
|
|
113
154
|
variant="filled"
|