mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 17:46:18 +08:00
feat(core): cache navigation collapsed state (#13315)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Collapsible section state in navigation panels is now managed using a unified path-based approach, enabling more consistent and centralized control across desktop and mobile interfaces. * The collapsed/expanded state of navigation sections and nodes is now persistently tracked using hierarchical paths, improving reliability across sessions and devices. * Internal state management is streamlined, with local state replaced by a shared service, resulting in more predictable navigation behavior. * **Chores** * Removed obsolete types and legacy section management logic for improved maintainability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+7
-10
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type CollapsibleSectionName,
|
||||
NavigationPanelService,
|
||||
} from '@affine/core/modules/navigation-panel';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import { ToggleRightIcon } from '@blocksuite/icons/rc';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -22,7 +19,7 @@ import {
|
||||
} from './collapsible-section.css';
|
||||
|
||||
interface CollapsibleSectionProps extends HTMLAttributes<HTMLDivElement> {
|
||||
name: CollapsibleSectionName;
|
||||
path: string[];
|
||||
title: string;
|
||||
actions?: ReactNode;
|
||||
testId?: string;
|
||||
@@ -76,7 +73,7 @@ const CollapsibleSectionTrigger = forwardRef<
|
||||
});
|
||||
|
||||
export const CollapsibleSection = ({
|
||||
name,
|
||||
path,
|
||||
title,
|
||||
actions,
|
||||
testId,
|
||||
@@ -86,12 +83,12 @@ export const CollapsibleSection = ({
|
||||
children,
|
||||
...attrs
|
||||
}: CollapsibleSectionProps) => {
|
||||
const section = useService(NavigationPanelService).sections[name];
|
||||
const collapsed = useLiveData(section.collapsed$);
|
||||
const navigationPanelService = useService(NavigationPanelService);
|
||||
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||
|
||||
const setCollapsed = useCallback(
|
||||
(v: boolean) => section.setCollapsed(v),
|
||||
[section]
|
||||
(v: boolean) => navigationPanelService.setCollapsed(path, v),
|
||||
[navigationPanelService, path]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,11 +6,12 @@ import {
|
||||
} from '@affine/core/modules/collection';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import { ShareDocsListService } from '@affine/core/modules/share-doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { FilterMinusIcon, ViewLayersIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
@@ -26,9 +27,11 @@ const CollectionIcon = () => <ViewLayersIcon />;
|
||||
export const NavigationPanelCollectionNode = ({
|
||||
collectionId,
|
||||
operations: additionalOperations,
|
||||
parentPath,
|
||||
}: {
|
||||
collectionId: string;
|
||||
operations?: NodeOperation[];
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const { globalContextService, collectionService, workspaceDialogService } =
|
||||
@@ -37,17 +40,28 @@ export const NavigationPanelCollectionNode = ({
|
||||
CollectionService,
|
||||
WorkspaceDialogService,
|
||||
});
|
||||
const navigationPanelService = useService(NavigationPanelService);
|
||||
const active =
|
||||
useLiveData(globalContextService.globalContext.collectionId.$) ===
|
||||
collectionId;
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const path = useMemo(
|
||||
() => [...parentPath, `collection-${collectionId}`],
|
||||
[parentPath, collectionId]
|
||||
);
|
||||
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||
const setCollapsed = useCallback(
|
||||
(value: boolean) => {
|
||||
navigationPanelService.setCollapsed(path, value);
|
||||
},
|
||||
[navigationPanelService, path]
|
||||
);
|
||||
|
||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||
const name = useLiveData(collection?.name$);
|
||||
|
||||
const handleOpenCollapsed = useCallback(() => {
|
||||
setCollapsed(false);
|
||||
}, []);
|
||||
}, [setCollapsed]);
|
||||
|
||||
const handleEditCollection = useCallback(() => {
|
||||
if (!collection) {
|
||||
@@ -95,6 +109,7 @@ export const NavigationPanelCollectionNode = ({
|
||||
<NavigationPanelCollectionNodeChildren
|
||||
collection={collection}
|
||||
onAddDoc={handleAddDocToCollection}
|
||||
path={path}
|
||||
/>
|
||||
</NavigationPanelTreeNode>
|
||||
);
|
||||
@@ -103,9 +118,11 @@ export const NavigationPanelCollectionNode = ({
|
||||
const NavigationPanelCollectionNodeChildren = ({
|
||||
collection,
|
||||
onAddDoc,
|
||||
path,
|
||||
}: {
|
||||
collection: Collection;
|
||||
onAddDoc?: () => void;
|
||||
path: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const { shareDocsListService, collectionService } = useServices({
|
||||
@@ -147,6 +164,7 @@ const NavigationPanelCollectionNodeChildren = ({
|
||||
<NavigationPanelDocNode
|
||||
key={docId}
|
||||
docId={docId}
|
||||
parentPath={path}
|
||||
operations={
|
||||
allowList
|
||||
? [
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
LiveData,
|
||||
@@ -29,10 +30,12 @@ export const NavigationPanelDocNode = ({
|
||||
docId,
|
||||
isLinked,
|
||||
operations: additionalOperations,
|
||||
parentPath,
|
||||
}: {
|
||||
docId: string;
|
||||
isLinked?: boolean;
|
||||
operations?: NodeOperation[];
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const {
|
||||
@@ -48,9 +51,20 @@ export const NavigationPanelDocNode = ({
|
||||
DocDisplayMetaService,
|
||||
FeatureFlagService,
|
||||
});
|
||||
const navigationPanelService = useService(NavigationPanelService);
|
||||
const active =
|
||||
useLiveData(globalContextService.globalContext.docId.$) === docId;
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const path = useMemo(
|
||||
() => [...parentPath, `doc-${docId}`],
|
||||
[parentPath, docId]
|
||||
);
|
||||
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||
const setCollapsed = useCallback(
|
||||
(value: boolean) => {
|
||||
navigationPanelService.setCollapsed(path, value);
|
||||
},
|
||||
[navigationPanelService, path]
|
||||
);
|
||||
|
||||
const docRecord = useLiveData(docsService.list.doc$(docId));
|
||||
const DocIcon = useLiveData(
|
||||
@@ -103,7 +117,7 @@ export const NavigationPanelDocNode = ({
|
||||
openInfoModal: () => workspaceDialogService.open('doc-info', { docId }),
|
||||
openNodeCollapsed: () => setCollapsed(false),
|
||||
}),
|
||||
[docId, workspaceDialogService]
|
||||
[docId, setCollapsed, workspaceDialogService]
|
||||
);
|
||||
const operations = useNavigationPanelDocNodeOperationsMenu(docId, option);
|
||||
const { handleAddLinkedPage } = useNavigationPanelDocNodeOperations(
|
||||
@@ -150,6 +164,7 @@ export const NavigationPanelDocNode = ({
|
||||
key={`${child.docId}-${index}`}
|
||||
docId={child.docId}
|
||||
isLinked
|
||||
parentPath={path}
|
||||
/>
|
||||
))
|
||||
: null
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import {
|
||||
type FolderNode,
|
||||
OrganizeService,
|
||||
@@ -31,9 +32,9 @@ import {
|
||||
RemoveFolderIcon,
|
||||
TagsIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import { difference } from 'lodash-es';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { NavigationPanelTreeNode } from '../../tree/node';
|
||||
@@ -46,11 +47,13 @@ import { FavoriteFolderOperation } from './operations';
|
||||
export const NavigationPanelFolderNode = ({
|
||||
nodeId,
|
||||
operations,
|
||||
parentPath,
|
||||
}: {
|
||||
nodeId: string;
|
||||
operations?:
|
||||
| NodeOperation[]
|
||||
| ((type: string, node: FolderNode) => NodeOperation[]);
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
const { organizeService } = useServices({
|
||||
OrganizeService,
|
||||
@@ -78,24 +81,34 @@ export const NavigationPanelFolderNode = ({
|
||||
<NavigationPanelFolderNodeFolder
|
||||
node={node}
|
||||
operations={additionalOperations}
|
||||
parentPath={parentPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!data) return null;
|
||||
if (type === 'doc') {
|
||||
return (
|
||||
<NavigationPanelDocNode docId={data} operations={additionalOperations} />
|
||||
<NavigationPanelDocNode
|
||||
docId={data}
|
||||
operations={additionalOperations}
|
||||
parentPath={parentPath}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'collection') {
|
||||
return (
|
||||
<NavigationPanelCollectionNode
|
||||
collectionId={data}
|
||||
operations={additionalOperations}
|
||||
parentPath={parentPath}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'tag') {
|
||||
return (
|
||||
<NavigationPanelTagNode tagId={data} operations={additionalOperations} />
|
||||
<NavigationPanelTagNode
|
||||
tagId={data}
|
||||
operations={additionalOperations}
|
||||
parentPath={parentPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,9 +132,11 @@ const NavigationPanelFolderIcon: NavigationPanelTreeNodeIcon = ({
|
||||
const NavigationPanelFolderNodeFolder = ({
|
||||
node,
|
||||
operations: additionalOperations,
|
||||
parentPath,
|
||||
}: {
|
||||
node: FolderNode;
|
||||
operations?: NodeOperation[];
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const { workspaceService, featureFlagService, workspaceDialogService } =
|
||||
@@ -135,7 +150,18 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
const enableEmojiIcon = useLiveData(
|
||||
featureFlagService.flags.enable_emoji_folder_icon.$
|
||||
);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const navigationPanelService = useService(NavigationPanelService);
|
||||
const path = useMemo(
|
||||
() => [...parentPath, `folder-${node.id}`],
|
||||
[parentPath, node.id]
|
||||
);
|
||||
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||
const setCollapsed = useCallback(
|
||||
(value: boolean) => {
|
||||
navigationPanelService.setCollapsed(path, value);
|
||||
},
|
||||
[navigationPanelService, path]
|
||||
);
|
||||
|
||||
const { createPage } = usePageHelper(
|
||||
workspaceService.workspace.docCollection
|
||||
@@ -171,7 +197,7 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
target: 'doc',
|
||||
});
|
||||
setCollapsed(false);
|
||||
}, [createPage, node]);
|
||||
}, [createPage, node, setCollapsed]);
|
||||
|
||||
const handleCreateSubfolder = useCallback(
|
||||
(name: string) => {
|
||||
@@ -179,7 +205,7 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||
setCollapsed(false);
|
||||
},
|
||||
[node]
|
||||
[node, setCollapsed]
|
||||
);
|
||||
|
||||
const handleAddToFolder = useCallback(
|
||||
@@ -223,7 +249,7 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
target: type,
|
||||
});
|
||||
},
|
||||
[children, node, workspaceDialogService]
|
||||
[children, node, setCollapsed, workspaceDialogService]
|
||||
);
|
||||
|
||||
const createSubTipRenderer = useCallback(
|
||||
@@ -388,13 +414,16 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
[t]
|
||||
);
|
||||
|
||||
const handleCollapsedChange = useCallback((collapsed: boolean) => {
|
||||
if (collapsed) {
|
||||
setCollapsed(true);
|
||||
} else {
|
||||
setCollapsed(false);
|
||||
}
|
||||
}, []);
|
||||
const handleCollapsedChange = useCallback(
|
||||
(collapsed: boolean) => {
|
||||
if (collapsed) {
|
||||
setCollapsed(true);
|
||||
} else {
|
||||
setCollapsed(false);
|
||||
}
|
||||
},
|
||||
[setCollapsed]
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationPanelTreeNode
|
||||
@@ -413,6 +442,7 @@ const NavigationPanelFolderNodeFolder = ({
|
||||
key={child.id}
|
||||
nodeId={child.id as string}
|
||||
operations={childrenOperations}
|
||||
parentPath={path}
|
||||
/>
|
||||
))}
|
||||
<AddItemPlaceholder
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import type { Tag } from '@affine/core/modules/tag';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { NavigationPanelTreeNode } from '../../tree/node';
|
||||
@@ -19,18 +20,31 @@ import * as styles from './styles.css';
|
||||
export const NavigationPanelTagNode = ({
|
||||
tagId,
|
||||
operations: additionalOperations,
|
||||
parentPath,
|
||||
}: {
|
||||
tagId: string;
|
||||
operations?: NodeOperation[];
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const { tagService, globalContextService } = useServices({
|
||||
TagService,
|
||||
GlobalContextService,
|
||||
});
|
||||
const navigationPanelService = useService(NavigationPanelService);
|
||||
const active =
|
||||
useLiveData(globalContextService.globalContext.tagId.$) === tagId;
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const path = useMemo(
|
||||
() => [...parentPath, `tag-${tagId}`],
|
||||
[parentPath, tagId]
|
||||
);
|
||||
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||
const setCollapsed = useCallback(
|
||||
(value: boolean) => {
|
||||
navigationPanelService.setCollapsed(path, value);
|
||||
},
|
||||
[navigationPanelService, path]
|
||||
);
|
||||
|
||||
const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
|
||||
const tagColor = useLiveData(tagRecord?.color$);
|
||||
@@ -57,7 +71,7 @@ export const NavigationPanelTagNode = ({
|
||||
() => ({
|
||||
openNodeCollapsed: () => setCollapsed(false),
|
||||
}),
|
||||
[]
|
||||
[setCollapsed]
|
||||
);
|
||||
const operations = useNavigationPanelTagNodeOperationsMenu(tagId, option);
|
||||
const { handleNewDoc } = useNavigationPanelTagNodeOperations(tagId, option);
|
||||
@@ -86,7 +100,11 @@ export const NavigationPanelTagNode = ({
|
||||
aria-label={tagName}
|
||||
data-role="navigation-panel-tag"
|
||||
>
|
||||
<NavigationPanelTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} />
|
||||
<NavigationPanelTagNodeDocs
|
||||
tag={tagRecord}
|
||||
onNewDoc={handleNewDoc}
|
||||
path={path}
|
||||
/>
|
||||
</NavigationPanelTreeNode>
|
||||
);
|
||||
};
|
||||
@@ -99,9 +117,11 @@ export const NavigationPanelTagNode = ({
|
||||
export const NavigationPanelTagNodeDocs = ({
|
||||
tag,
|
||||
onNewDoc,
|
||||
path,
|
||||
}: {
|
||||
tag: Tag;
|
||||
onNewDoc?: () => void;
|
||||
path: string[];
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const tagDocIds = useLiveData(tag.pageIds$);
|
||||
@@ -109,7 +129,7 @@ export const NavigationPanelTagNodeDocs = ({
|
||||
return (
|
||||
<>
|
||||
{tagDocIds.map(docId => (
|
||||
<NavigationPanelDocNode key={docId} docId={docId} />
|
||||
<NavigationPanelDocNode key={docId} docId={docId} parentPath={path} />
|
||||
))}
|
||||
<AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} />
|
||||
</>
|
||||
|
||||
+7
-5
@@ -7,7 +7,7 @@ import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
@@ -22,7 +22,7 @@ export const NavigationPanelCollections = () => {
|
||||
WorkbenchService,
|
||||
NavigationPanelService,
|
||||
});
|
||||
const navigationPanelSection = navigationPanelService.sections.collections;
|
||||
const path = useMemo(() => ['collections'], []);
|
||||
const collectionMetas = useLiveData(collectionService.collectionMetas$);
|
||||
const { openPromptModal } = usePromptModal();
|
||||
|
||||
@@ -49,12 +49,13 @@ export const NavigationPanelCollections = () => {
|
||||
type: 'collection',
|
||||
});
|
||||
workbenchService.workbench.openCollection(id);
|
||||
navigationPanelSection.setCollapsed(false);
|
||||
navigationPanelService.setCollapsed(path, false);
|
||||
},
|
||||
});
|
||||
}, [
|
||||
collectionService,
|
||||
navigationPanelSection,
|
||||
navigationPanelService,
|
||||
path,
|
||||
openPromptModal,
|
||||
t,
|
||||
workbenchService.workbench,
|
||||
@@ -62,7 +63,7 @@ export const NavigationPanelCollections = () => {
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="collections"
|
||||
path={path}
|
||||
testId="navigation-panel-collections"
|
||||
title={t['com.affine.rootAppSidebar.collections']()}
|
||||
>
|
||||
@@ -71,6 +72,7 @@ export const NavigationPanelCollections = () => {
|
||||
<NavigationPanelCollectionNode
|
||||
key={collection.id}
|
||||
collectionId={collection.id}
|
||||
parentPath={path}
|
||||
/>
|
||||
))}
|
||||
<AddItemPlaceholder
|
||||
|
||||
+19
-10
@@ -6,7 +6,7 @@ import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
@@ -24,7 +24,7 @@ export const NavigationPanelFavorites = () => {
|
||||
});
|
||||
|
||||
const t = useI18n();
|
||||
const navigationPanelSection = navigationPanelService.sections.favorites;
|
||||
const path = useMemo(() => ['favorites'], []);
|
||||
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
||||
const isLoading = useLiveData(favoriteService.favoriteList.isLoading$);
|
||||
const { createPage } = usePageHelper(
|
||||
@@ -38,19 +38,23 @@ export const NavigationPanelFavorites = () => {
|
||||
newDoc.id,
|
||||
favoriteService.favoriteList.indexAt('before')
|
||||
);
|
||||
navigationPanelSection.setCollapsed(false);
|
||||
}, [createPage, navigationPanelSection, favoriteService.favoriteList]);
|
||||
navigationPanelService.setCollapsed(path, false);
|
||||
}, [createPage, favoriteService.favoriteList, navigationPanelService, path]);
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="favorites"
|
||||
path={path}
|
||||
title={t['com.affine.rootAppSidebar.favorites']()}
|
||||
testId="navigation-panel-favorites"
|
||||
headerTestId="navigation-panel-favorite-category-divider"
|
||||
>
|
||||
<NavigationPanelTreeRoot placeholder={isLoading ? 'Loading' : null}>
|
||||
{favorites.map(favorite => (
|
||||
<FavoriteNode key={favorite.id} favorite={favorite} />
|
||||
<FavoriteNode
|
||||
key={favorite.id}
|
||||
favorite={favorite}
|
||||
parentPath={path}
|
||||
/>
|
||||
))}
|
||||
<AddItemPlaceholder
|
||||
data-testid="navigation-panel-bar-add-favorite-button"
|
||||
@@ -66,19 +70,24 @@ export const NavigationPanelFavorites = () => {
|
||||
|
||||
export const FavoriteNode = ({
|
||||
favorite,
|
||||
parentPath,
|
||||
}: {
|
||||
favorite: {
|
||||
id: string;
|
||||
type: FavoriteSupportTypeUnion;
|
||||
};
|
||||
parentPath: string[];
|
||||
}) => {
|
||||
return favorite.type === 'doc' ? (
|
||||
<NavigationPanelDocNode docId={favorite.id} />
|
||||
<NavigationPanelDocNode docId={favorite.id} parentPath={parentPath} />
|
||||
) : favorite.type === 'tag' ? (
|
||||
<NavigationPanelTagNode tagId={favorite.id} />
|
||||
<NavigationPanelTagNode tagId={favorite.id} parentPath={parentPath} />
|
||||
) : favorite.type === 'folder' ? (
|
||||
<NavigationPanelFolderNode nodeId={favorite.id} />
|
||||
<NavigationPanelFolderNode nodeId={favorite.id} parentPath={parentPath} />
|
||||
) : (
|
||||
<NavigationPanelCollectionNode collectionId={favorite.id} />
|
||||
<NavigationPanelCollectionNode
|
||||
collectionId={favorite.id}
|
||||
parentPath={parentPath}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { AddOrganizeIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
@@ -18,7 +18,7 @@ export const NavigationPanelOrganize = () => {
|
||||
OrganizeService,
|
||||
NavigationPanelService,
|
||||
});
|
||||
const navigationPanelSection = navigationPanelService.sections.organize;
|
||||
const path = useMemo(() => ['organize'], []);
|
||||
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
|
||||
|
||||
const t = useI18n();
|
||||
@@ -36,15 +36,15 @@ export const NavigationPanelOrganize = () => {
|
||||
rootFolder.indexAt('before')
|
||||
);
|
||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||
navigationPanelSection.setCollapsed(false);
|
||||
navigationPanelService.setCollapsed(path, false);
|
||||
return newFolderId;
|
||||
},
|
||||
[navigationPanelSection, rootFolder]
|
||||
[navigationPanelService, path, rootFolder]
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="organize"
|
||||
path={path}
|
||||
title={t['com.affine.rootAppSidebar.organize']()}
|
||||
>
|
||||
{/* TODO(@CatsJuice): Organize loading UI */}
|
||||
@@ -53,6 +53,7 @@ export const NavigationPanelOrganize = () => {
|
||||
<NavigationPanelFolderNode
|
||||
key={child.id}
|
||||
nodeId={child.id as string}
|
||||
parentPath={path}
|
||||
/>
|
||||
))}
|
||||
<AddItemPlaceholder
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { AddTagIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
@@ -25,7 +25,7 @@ export const NavigationPanelTags = () => {
|
||||
TagService,
|
||||
NavigationPanelService,
|
||||
});
|
||||
const navigationPanelSection = navigationPanelService.sections.tags;
|
||||
const path = useMemo(() => ['tags'], []);
|
||||
const tags = useLiveData(tagService.tagList.tags$);
|
||||
const [showNewTagDialog, setShowNewTagDialog] = useState(false);
|
||||
|
||||
@@ -36,19 +36,23 @@ export const NavigationPanelTags = () => {
|
||||
setShowNewTagDialog(false);
|
||||
tagService.tagList.createTag(name, color);
|
||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
||||
navigationPanelSection.setCollapsed(false);
|
||||
navigationPanelService.setCollapsed(path, false);
|
||||
},
|
||||
[navigationPanelSection, tagService]
|
||||
[navigationPanelService, path, tagService]
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="tags"
|
||||
path={path}
|
||||
title={t['com.affine.rootAppSidebar.tags']()}
|
||||
>
|
||||
<NavigationPanelTreeRoot>
|
||||
{tags.map(tag => (
|
||||
<NavigationPanelTagNode key={tag.id} tagId={tag.id} />
|
||||
<NavigationPanelTagNode
|
||||
key={tag.id}
|
||||
tagId={tag.id}
|
||||
parentPath={path}
|
||||
/>
|
||||
))}
|
||||
<AddItemPlaceholder
|
||||
icon={<AddTagIcon />}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const RecentDocs = ({ max = 5 }: { max?: number }) => {
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
name="recent"
|
||||
path={['recent']}
|
||||
title="Recent"
|
||||
headerClassName={styles.header}
|
||||
className={styles.recentSection}
|
||||
|
||||
Reference in New Issue
Block a user