mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 21:55:02 +03:00
refactor(core): remove collection atom (#5832)
This commit is contained in:
parent
ad9b0303c4
commit
5cd488fe1d
@ -48,9 +48,6 @@ export const recentPageIdsBaseAtom = atomWithStorage<string[]>(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PageModeOption = 'all' | 'page' | 'edgeless';
|
|
||||||
export const allPageModeSelectAtom = atom<PageModeOption>('all');
|
|
||||||
|
|
||||||
export type AllPageFilterOption = 'docs' | 'collections' | 'tags';
|
export type AllPageFilterOption = 'docs' | 'collections' | 'tags';
|
||||||
export const allPageFilterSelectAtom = atom<AllPageFilterOption>('docs');
|
export const allPageFilterSelectAtom = atom<AllPageFilterOption>('docs');
|
||||||
|
|
||||||
|
@ -5,11 +5,7 @@ import type { Workspace } from '@blocksuite/store';
|
|||||||
import { registerAffineCommand } from '@toeverything/infra/command';
|
import { registerAffineCommand } from '@toeverything/infra/command';
|
||||||
import type { createStore } from 'jotai';
|
import type { createStore } from 'jotai';
|
||||||
|
|
||||||
import {
|
import { openSettingModalAtom, openWorkspaceListModalAtom } from '../atoms';
|
||||||
openSettingModalAtom,
|
|
||||||
openWorkspaceListModalAtom,
|
|
||||||
type PageModeOption,
|
|
||||||
} from '../atoms';
|
|
||||||
import type { useNavigateHelper } from '../hooks/use-navigate-helper';
|
import type { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||||
|
|
||||||
export function registerAffineNavigationCommands({
|
export function registerAffineNavigationCommands({
|
||||||
@ -17,12 +13,10 @@ export function registerAffineNavigationCommands({
|
|||||||
store,
|
store,
|
||||||
workspace,
|
workspace,
|
||||||
navigationHelper,
|
navigationHelper,
|
||||||
setPageListMode,
|
|
||||||
}: {
|
}: {
|
||||||
t: ReturnType<typeof useAFFiNEI18N>;
|
t: ReturnType<typeof useAFFiNEI18N>;
|
||||||
store: ReturnType<typeof createStore>;
|
store: ReturnType<typeof createStore>;
|
||||||
navigationHelper: ReturnType<typeof useNavigateHelper>;
|
navigationHelper: ReturnType<typeof useNavigateHelper>;
|
||||||
setPageListMode: React.Dispatch<React.SetStateAction<PageModeOption>>;
|
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
}) {
|
}) {
|
||||||
const unsubs: Array<() => void> = [];
|
const unsubs: Array<() => void> = [];
|
||||||
@ -34,7 +28,6 @@ export function registerAffineNavigationCommands({
|
|||||||
label: t['com.affine.cmdk.affine.navigation.goto-all-pages'](),
|
label: t['com.affine.cmdk.affine.navigation.goto-all-pages'](),
|
||||||
run() {
|
run() {
|
||||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||||
setPageListMode('all');
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -47,7 +40,6 @@ export function registerAffineNavigationCommands({
|
|||||||
label: 'Go to Collection List',
|
label: 'Go to Collection List',
|
||||||
run() {
|
run() {
|
||||||
navigationHelper.jumpToCollections(workspace.id);
|
navigationHelper.jumpToCollections(workspace.id);
|
||||||
setPageListMode('all');
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -60,7 +52,6 @@ export function registerAffineNavigationCommands({
|
|||||||
label: 'Go to Tag List',
|
label: 'Go to Tag List',
|
||||||
run() {
|
run() {
|
||||||
navigationHelper.jumpToTags(workspace.id);
|
navigationHelper.jumpToTags(workspace.id);
|
||||||
setPageListMode('all');
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -101,7 +92,6 @@ export function registerAffineNavigationCommands({
|
|||||||
label: t['com.affine.cmdk.affine.navigation.goto-trash'](),
|
label: t['com.affine.cmdk.affine.navigation.goto-trash'](),
|
||||||
run() {
|
run() {
|
||||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH);
|
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.TRASH);
|
||||||
setPageListMode('all');
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* @vitest-environment happy-dom
|
|
||||||
*/
|
|
||||||
import 'fake-indexeddb/auto';
|
|
||||||
|
|
||||||
import type { CollectionService } from '@affine/core/modules/collection';
|
|
||||||
import type { Collection } from '@affine/env/filter';
|
|
||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
import { LiveData } from '@toeverything/infra';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { expect, test } from 'vitest';
|
|
||||||
|
|
||||||
import { createDefaultFilter, vars } from '../filter/vars';
|
|
||||||
import { useCollectionManager } from '../use-collection-manager';
|
|
||||||
|
|
||||||
const defaultMeta = { tags: { options: [] } };
|
|
||||||
const collectionsSubject = new BehaviorSubject<Collection[]>([]);
|
|
||||||
|
|
||||||
const mockWorkspaceCollectionService = {
|
|
||||||
collections: LiveData.from(collectionsSubject, []),
|
|
||||||
addCollection: (...collections) => {
|
|
||||||
const prev = collectionsSubject.value;
|
|
||||||
collectionsSubject.next([...collections, ...prev]);
|
|
||||||
},
|
|
||||||
deleteCollection: (...ids) => {
|
|
||||||
const prev = collectionsSubject.value;
|
|
||||||
collectionsSubject.next(prev.filter(v => !ids.includes(v.id)));
|
|
||||||
},
|
|
||||||
updateCollection: (id, updater) => {
|
|
||||||
const prev = collectionsSubject.value;
|
|
||||||
collectionsSubject.next(
|
|
||||||
prev.map(v => {
|
|
||||||
if (v.id === id) {
|
|
||||||
return updater(v);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} as CollectionService;
|
|
||||||
|
|
||||||
test('useAllPageSetting', async () => {
|
|
||||||
const settingHook = renderHook(() =>
|
|
||||||
useCollectionManager(mockWorkspaceCollectionService)
|
|
||||||
);
|
|
||||||
const prevCollection = settingHook.result.current.currentCollection;
|
|
||||||
expect(settingHook.result.current.savedCollections).toEqual([]);
|
|
||||||
settingHook.result.current.updateCollection({
|
|
||||||
...settingHook.result.current.currentCollection,
|
|
||||||
filterList: [createDefaultFilter(vars[0], defaultMeta)],
|
|
||||||
});
|
|
||||||
settingHook.rerender();
|
|
||||||
const nextCollection = settingHook.result.current.currentCollection;
|
|
||||||
expect(nextCollection).not.toBe(prevCollection);
|
|
||||||
expect(nextCollection.filterList).toEqual([
|
|
||||||
createDefaultFilter(vars[0], defaultMeta),
|
|
||||||
]);
|
|
||||||
settingHook.result.current.createCollection({
|
|
||||||
...settingHook.result.current.currentCollection,
|
|
||||||
id: '1',
|
|
||||||
});
|
|
||||||
settingHook.rerender();
|
|
||||||
expect(settingHook.result.current.savedCollections.length).toBe(1);
|
|
||||||
expect(settingHook.result.current.savedCollections[0].id).toBe('1');
|
|
||||||
});
|
|
@ -18,19 +18,18 @@ import { CollectionOperationCell } from '../operation-cell';
|
|||||||
import { CollectionListItemRenderer } from '../page-group';
|
import { CollectionListItemRenderer } from '../page-group';
|
||||||
import { ListTableHeader } from '../page-header';
|
import { ListTableHeader } from '../page-header';
|
||||||
import type { CollectionMeta, ItemListHandle, ListItem } from '../types';
|
import type { CollectionMeta, ItemListHandle, ListItem } from '../types';
|
||||||
import { useCollectionManager } from '../use-collection-manager';
|
|
||||||
import type { AllPageListConfig } from '../view';
|
import type { AllPageListConfig } from '../view';
|
||||||
import { VirtualizedList } from '../virtualized-list';
|
import { VirtualizedList } from '../virtualized-list';
|
||||||
import { CollectionListHeader } from './collection-list-header';
|
import { CollectionListHeader } from './collection-list-header';
|
||||||
|
|
||||||
const useCollectionOperationsRenderer = ({
|
const useCollectionOperationsRenderer = ({
|
||||||
info,
|
info,
|
||||||
setting,
|
service,
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
info: DeleteCollectionInfo;
|
info: DeleteCollectionInfo;
|
||||||
config: AllPageListConfig;
|
config: AllPageListConfig;
|
||||||
setting: ReturnType<typeof useCollectionManager>;
|
service: CollectionService;
|
||||||
}) => {
|
}) => {
|
||||||
const pageOperationsRenderer = useCallback(
|
const pageOperationsRenderer = useCallback(
|
||||||
(collection: Collection) => {
|
(collection: Collection) => {
|
||||||
@ -38,12 +37,12 @@ const useCollectionOperationsRenderer = ({
|
|||||||
<CollectionOperationCell
|
<CollectionOperationCell
|
||||||
info={info}
|
info={info}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
setting={setting}
|
service={service}
|
||||||
config={config}
|
config={config}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[config, info, setting]
|
[config, info, service]
|
||||||
);
|
);
|
||||||
|
|
||||||
return pageOperationsRenderer;
|
return pageOperationsRenderer;
|
||||||
@ -69,13 +68,13 @@ export const VirtualizedCollectionList = ({
|
|||||||
const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>(
|
const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const collectionService = useService(CollectionService);
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const info = useDeleteCollectionInfo();
|
const info = useDeleteCollectionInfo();
|
||||||
|
|
||||||
const collectionOperations = useCollectionOperationsRenderer({
|
const collectionOperations = useCollectionOperationsRenderer({
|
||||||
info,
|
info,
|
||||||
setting,
|
service: collectionService,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,8 +104,8 @@ export const VirtualizedCollectionList = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
return setting.deleteCollection(info, ...selectedCollectionIds);
|
return collectionService.deleteCollection(info, ...selectedCollectionIds);
|
||||||
}, [setting, info, selectedCollectionIds]);
|
}, [collectionService, info, selectedCollectionIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -10,10 +10,7 @@ import { useCallback, useMemo } from 'react';
|
|||||||
|
|
||||||
import { CollectionService } from '../../../modules/collection';
|
import { CollectionService } from '../../../modules/collection';
|
||||||
import { createTagFilter } from '../filter/utils';
|
import { createTagFilter } from '../filter/utils';
|
||||||
import {
|
import { createEmptyCollection } from '../use-collection-manager';
|
||||||
createEmptyCollection,
|
|
||||||
useCollectionManager,
|
|
||||||
} from '../use-collection-manager';
|
|
||||||
import { tagColorMap } from '../utils';
|
import { tagColorMap } from '../utils';
|
||||||
import type { AllPageListConfig } from '../view/edit-collection/edit-collection';
|
import type { AllPageListConfig } from '../view/edit-collection/edit-collection';
|
||||||
import {
|
import {
|
||||||
@ -23,38 +20,12 @@ import {
|
|||||||
import * as styles from './page-list-header.css';
|
import * as styles from './page-list-header.css';
|
||||||
import { PageListNewPageButton } from './page-list-new-page-button';
|
import { PageListNewPageButton } from './page-list-new-page-button';
|
||||||
|
|
||||||
export const PageListHeader = ({ workspaceId }: { workspaceId: string }) => {
|
export const PageListHeader = () => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
|
||||||
const { jumpToCollections } = useNavigateHelper();
|
|
||||||
|
|
||||||
const handleJumpToCollections = useCallback(() => {
|
|
||||||
jumpToCollections(workspaceId);
|
|
||||||
}, [jumpToCollections, workspaceId]);
|
|
||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
if (setting.isDefault) {
|
|
||||||
return t['com.affine.all-pages.header']();
|
return t['com.affine.all-pages.header']();
|
||||||
}
|
}, [t]);
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ cursor: 'pointer' }} onClick={handleJumpToCollections}>
|
|
||||||
{t['com.affine.collections.header']()} /
|
|
||||||
</div>
|
|
||||||
<div className={styles.titleIcon}>
|
|
||||||
<ViewLayersIcon />
|
|
||||||
</div>
|
|
||||||
<div className={styles.titleCollectionName}>
|
|
||||||
{setting.currentCollection.name}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
handleJumpToCollections,
|
|
||||||
setting.currentCollection.name,
|
|
||||||
setting.isDefault,
|
|
||||||
t,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.docListHeader}>
|
<div className={styles.docListHeader}>
|
||||||
@ -75,22 +46,19 @@ export const CollectionPageListHeader = ({
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
|
||||||
const { jumpToCollections } = useNavigateHelper();
|
const { jumpToCollections } = useNavigateHelper();
|
||||||
|
|
||||||
const handleJumpToCollections = useCallback(() => {
|
const handleJumpToCollections = useCallback(() => {
|
||||||
jumpToCollections(workspaceId);
|
jumpToCollections(workspaceId);
|
||||||
}, [jumpToCollections, workspaceId]);
|
}, [jumpToCollections, workspaceId]);
|
||||||
|
|
||||||
const { updateCollection } = useCollectionManager(
|
const collectionService = useService(CollectionService);
|
||||||
useService(CollectionService)
|
|
||||||
);
|
|
||||||
const { node, open } = useEditCollection(config);
|
const { node, open } = useEditCollection(config);
|
||||||
|
|
||||||
const handleAddPage = useAsyncCallback(async () => {
|
const handleAddPage = useAsyncCallback(async () => {
|
||||||
const ret = await open({ ...collection }, 'page');
|
const ret = await open({ ...collection }, 'page');
|
||||||
updateCollection(ret);
|
collectionService.updateCollection(collection.id, () => ret);
|
||||||
}, [collection, open, updateCollection]);
|
}, [collection, collectionService, open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -103,9 +71,7 @@ export const CollectionPageListHeader = ({
|
|||||||
<div className={styles.titleIcon}>
|
<div className={styles.titleIcon}>
|
||||||
<ViewLayersIcon />
|
<ViewLayersIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.titleCollectionName}>
|
<div className={styles.titleCollectionName}>{collection.name}</div>
|
||||||
{setting.currentCollection.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Button className={styles.addPageButton} onClick={handleAddPage}>
|
<Button className={styles.addPageButton} onClick={handleAddPage}>
|
||||||
{t['com.affine.collection.addPages']()}
|
{t['com.affine.collection.addPages']()}
|
||||||
@ -124,7 +90,7 @@ export const TagPageListHeader = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const { jumpToTags, jumpToCollection } = useNavigateHelper();
|
const { jumpToTags, jumpToCollection } = useNavigateHelper();
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const collectionService = useService(CollectionService);
|
||||||
const { open, node } = useEditCollectionName({
|
const { open, node } = useEditCollectionName({
|
||||||
title: t['com.affine.editCollection.saveCollection'](),
|
title: t['com.affine.editCollection.saveCollection'](),
|
||||||
showTips: true,
|
showTips: true,
|
||||||
@ -136,13 +102,13 @@ export const TagPageListHeader = ({
|
|||||||
|
|
||||||
const saveToCollection = useCallback(
|
const saveToCollection = useCallback(
|
||||||
(collection: Collection) => {
|
(collection: Collection) => {
|
||||||
setting.createCollection({
|
collectionService.addCollection({
|
||||||
...collection,
|
...collection,
|
||||||
filterList: [createTagFilter(tag.id)],
|
filterList: [createTagFilter(tag.id)],
|
||||||
});
|
});
|
||||||
jumpToCollection(workspaceId, collection.id);
|
jumpToCollection(workspaceId, collection.id);
|
||||||
},
|
},
|
||||||
[setting, tag.id, jumpToCollection, workspaceId]
|
[collectionService, tag.id, jumpToCollection, workspaceId]
|
||||||
);
|
);
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
open('')
|
open('')
|
||||||
|
@ -2,7 +2,7 @@ import { toast } from '@affine/component';
|
|||||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||||
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
||||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
import type { Collection } from '@affine/env/filter';
|
import type { Collection, Filter } from '@affine/env/filter';
|
||||||
import { Trans } from '@affine/i18n';
|
import { Trans } from '@affine/i18n';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { PageMeta, Tag } from '@blocksuite/store';
|
import type { PageMeta, Tag } from '@blocksuite/store';
|
||||||
@ -81,15 +81,17 @@ const usePageOperationsRenderer = () => {
|
|||||||
export const VirtualizedPageList = ({
|
export const VirtualizedPageList = ({
|
||||||
tag,
|
tag,
|
||||||
collection,
|
collection,
|
||||||
|
filters,
|
||||||
config,
|
config,
|
||||||
listItem,
|
listItem,
|
||||||
setHideHeaderCreateNewPage,
|
setHideHeaderCreateNewPage,
|
||||||
}: {
|
}: {
|
||||||
tag?: Tag;
|
tag?: Tag;
|
||||||
collection?: Collection;
|
collection?: Collection;
|
||||||
|
filters?: Filter[];
|
||||||
config?: AllPageListConfig;
|
config?: AllPageListConfig;
|
||||||
listItem?: PageMeta[];
|
listItem?: PageMeta[];
|
||||||
setHideHeaderCreateNewPage: (hide: boolean) => void;
|
setHideHeaderCreateNewPage?: (hide: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const listRef = useRef<ItemListHandle>(null);
|
const listRef = useRef<ItemListHandle>(null);
|
||||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||||
@ -101,11 +103,10 @@ export const VirtualizedPageList = ({
|
|||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredPageMetas = useFilteredPageMetas(
|
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
|
||||||
'all',
|
filters,
|
||||||
pageMetas,
|
collection,
|
||||||
currentWorkspace
|
});
|
||||||
);
|
|
||||||
const pageMetasToRender = useMemo(() => {
|
const pageMetasToRender = useMemo(() => {
|
||||||
if (listItem) {
|
if (listItem) {
|
||||||
return listItem;
|
return listItem;
|
||||||
@ -151,7 +152,7 @@ export const VirtualizedPageList = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <PageListHeader workspaceId={currentWorkspace.id} />;
|
return <PageListHeader />;
|
||||||
}, [collection, config, currentWorkspace.id, tag]);
|
}, [collection, config, currentWorkspace.id, tag]);
|
||||||
|
|
||||||
const { setTrashModal } = useTrashModalHelper(
|
const { setTrashModal } = useTrashModalHelper(
|
||||||
|
@ -23,10 +23,10 @@ import {
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { CollectionService } from '../../modules/collection';
|
||||||
import { FavoriteTag } from './components/favorite-tag';
|
import { FavoriteTag } from './components/favorite-tag';
|
||||||
import * as styles from './list.css';
|
import * as styles from './list.css';
|
||||||
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||||
import type { useCollectionManager } from './use-collection-manager';
|
|
||||||
import { ColWrapper, stopPropagationWithoutPrevent } from './utils';
|
import { ColWrapper, stopPropagationWithoutPrevent } from './utils';
|
||||||
import {
|
import {
|
||||||
type AllPageListConfig,
|
type AllPageListConfig,
|
||||||
@ -208,13 +208,13 @@ export interface CollectionOperationCellProps {
|
|||||||
collection: Collection;
|
collection: Collection;
|
||||||
info: DeleteCollectionInfo;
|
info: DeleteCollectionInfo;
|
||||||
config: AllPageListConfig;
|
config: AllPageListConfig;
|
||||||
setting: ReturnType<typeof useCollectionManager>;
|
service: CollectionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollectionOperationCell = ({
|
export const CollectionOperationCell = ({
|
||||||
collection,
|
collection,
|
||||||
config,
|
config,
|
||||||
setting,
|
service,
|
||||||
info,
|
info,
|
||||||
}: CollectionOperationCellProps) => {
|
}: CollectionOperationCellProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
@ -231,26 +231,29 @@ export const CollectionOperationCell = ({
|
|||||||
// use openRenameModal if it is in the sidebar collection list
|
// use openRenameModal if it is in the sidebar collection list
|
||||||
openEditCollectionNameModal(collection.name)
|
openEditCollectionNameModal(collection.name)
|
||||||
.then(name => {
|
.then(name => {
|
||||||
return setting.updateCollection({ ...collection, name });
|
return service.updateCollection(collection.id, collection => ({
|
||||||
|
...collection,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [collection, openEditCollectionNameModal, setting]);
|
}, [collection.id, collection.name, openEditCollectionNameModal, service]);
|
||||||
|
|
||||||
const handleEdit = useCallback(() => {
|
const handleEdit = useCallback(() => {
|
||||||
openEditCollectionModal(collection)
|
openEditCollectionModal(collection)
|
||||||
.then(collection => {
|
.then(collection => {
|
||||||
return setting.updateCollection(collection);
|
return service.updateCollection(collection.id, () => collection);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [setting, collection, openEditCollectionModal]);
|
}, [openEditCollectionModal, collection, service]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
return setting.deleteCollection(info, collection.id);
|
return service.deleteCollection(info, collection.id);
|
||||||
}, [setting, info, collection]);
|
}, [service, info, collection]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -15,12 +15,10 @@ import { TagListHeader } from './tag-list-header';
|
|||||||
export const VirtualizedTagList = ({
|
export const VirtualizedTagList = ({
|
||||||
tags,
|
tags,
|
||||||
tagMetas,
|
tagMetas,
|
||||||
setHideHeaderCreateNewTag,
|
|
||||||
onTagDelete,
|
onTagDelete,
|
||||||
}: {
|
}: {
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
tagMetas: TagMeta[];
|
tagMetas: TagMeta[];
|
||||||
setHideHeaderCreateNewTag: (hide: boolean) => void;
|
|
||||||
onTagDelete: (tagIds: string[]) => void;
|
onTagDelete: (tagIds: string[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const listRef = useRef<ItemListHandle>(null);
|
const listRef = useRef<ItemListHandle>(null);
|
||||||
@ -63,7 +61,6 @@ export const VirtualizedTagList = ({
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
groupBy={false}
|
groupBy={false}
|
||||||
atTopThreshold={80}
|
atTopThreshold={80}
|
||||||
atTopStateChange={setHideHeaderCreateNewTag}
|
|
||||||
onSelectionActiveChange={setShowFloatingToolbar}
|
onSelectionActiveChange={setShowFloatingToolbar}
|
||||||
heading={<TagListHeader />}
|
heading={<TagListHeader />}
|
||||||
selectedIds={filteredSelectedTagIds}
|
selectedIds={filteredSelectedTagIds}
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import type { CollectionService } from '@affine/core/modules/collection';
|
|
||||||
import type { Collection, Filter, VariableMap } from '@affine/env/filter';
|
import type { Collection, Filter, VariableMap } from '@affine/env/filter';
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
import type { PageMeta } from '@blocksuite/store';
|
||||||
import { useLiveData } from '@toeverything/infra/livedata';
|
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
|
||||||
import { atomWithReset } from 'jotai/utils';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { NIL } from 'uuid';
|
|
||||||
|
|
||||||
import { evalFilterList } from './filter';
|
import { evalFilterList } from './filter';
|
||||||
|
|
||||||
@ -21,79 +15,7 @@ export const createEmptyCollection = (
|
|||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const defaultCollection: Collection = createEmptyCollection(NIL, {
|
|
||||||
name: 'All',
|
|
||||||
});
|
|
||||||
const defaultCollectionAtom = atomWithReset<Collection>(defaultCollection);
|
|
||||||
export const currentCollectionAtom = atomWithReset<string>(NIL);
|
|
||||||
|
|
||||||
export type Updater<T> = (value: T) => T;
|
|
||||||
export type CollectionUpdater = Updater<Collection>;
|
|
||||||
|
|
||||||
export const useSavedCollections = (collectionService: CollectionService) => {
|
|
||||||
const addPage = useCallback(
|
|
||||||
(collectionId: string, pageId: string) => {
|
|
||||||
collectionService.updateCollection(collectionId, old => {
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
allowList: [pageId, ...(old.allowList ?? [])],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[collectionService]
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
collectionService,
|
|
||||||
addPage,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useCollectionManager = (collectionService: CollectionService) => {
|
|
||||||
const collections = useLiveData(collectionService.collections);
|
|
||||||
const { addPage } = useSavedCollections(collectionService);
|
|
||||||
const currentCollectionId = useAtomValue(currentCollectionAtom);
|
|
||||||
const [defaultCollection, updateDefaultCollection] = useAtom(
|
|
||||||
defaultCollectionAtom
|
|
||||||
);
|
|
||||||
const update = useCallback(
|
|
||||||
(collection: Collection) => {
|
|
||||||
if (collection.id === NIL) {
|
|
||||||
updateDefaultCollection(collection);
|
|
||||||
} else {
|
|
||||||
collectionService.updateCollection(collection.id, () => collection);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[updateDefaultCollection, collectionService]
|
|
||||||
);
|
|
||||||
const setTemporaryFilter = useCallback(
|
|
||||||
(filterList: Filter[]) => {
|
|
||||||
updateDefaultCollection({
|
|
||||||
...defaultCollection,
|
|
||||||
filterList: filterList,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[updateDefaultCollection, defaultCollection]
|
|
||||||
);
|
|
||||||
const currentCollection =
|
|
||||||
currentCollectionId === NIL
|
|
||||||
? defaultCollection
|
|
||||||
: collections.find(v => v.id === currentCollectionId) ??
|
|
||||||
defaultCollection;
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentCollection: currentCollection,
|
|
||||||
savedCollections: collections,
|
|
||||||
isDefault: currentCollectionId === NIL,
|
|
||||||
|
|
||||||
// actions
|
|
||||||
createCollection: collectionService.addCollection.bind(collectionService),
|
|
||||||
updateCollection: update,
|
|
||||||
deleteCollection:
|
|
||||||
collectionService.deleteCollection.bind(collectionService),
|
|
||||||
addPage,
|
|
||||||
setTemporaryFilter,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const filterByFilterList = (filterList: Filter[], varMap: VariableMap) =>
|
export const filterByFilterList = (filterList: Filter[], varMap: VariableMap) =>
|
||||||
evalFilterList(filterList, varMap);
|
evalFilterList(filterList, varMap);
|
||||||
|
|
||||||
|
@ -1,77 +1,55 @@
|
|||||||
import { allPageModeSelectAtom } from '@affine/core/atoms';
|
import type { Collection, Filter } from '@affine/env/filter';
|
||||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
|
||||||
import { usePublicPages } from '@affine/core/hooks/affine/use-is-shared-page';
|
|
||||||
import { CollectionService } from '@affine/core/modules/collection';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
import type { PageMeta } from '@blocksuite/store';
|
||||||
import type { Workspace } from '@toeverything/infra';
|
import type { Workspace } from '@toeverything/infra';
|
||||||
import { useService } from '@toeverything/infra/di';
|
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import { usePublicPages } from '../../hooks/affine/use-is-shared-page';
|
||||||
filterPage,
|
import { filterPage, filterPageByRules } from './use-collection-manager';
|
||||||
filterPageByRules,
|
|
||||||
useCollectionManager,
|
|
||||||
} from './use-collection-manager';
|
|
||||||
|
|
||||||
export const useFilteredPageMetas = (
|
export const useFilteredPageMetas = (
|
||||||
route: 'all' | 'trash',
|
workspace: Workspace,
|
||||||
pageMetas: PageMeta[],
|
pageMetas: PageMeta[],
|
||||||
workspace: Workspace
|
options: {
|
||||||
|
trash?: boolean;
|
||||||
|
filters?: Filter[];
|
||||||
|
collection?: Collection;
|
||||||
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
const { isPreferredEdgeless } = usePageHelper(workspace.blockSuiteWorkspace);
|
|
||||||
const pageMode = useAtomValue(allPageModeSelectAtom);
|
|
||||||
const { currentCollection, isDefault } = useCollectionManager(
|
|
||||||
useService(CollectionService)
|
|
||||||
);
|
|
||||||
const { getPublicMode } = usePublicPages(workspace);
|
const { getPublicMode } = usePublicPages(workspace);
|
||||||
|
|
||||||
const filteredPageMetas = useMemo(
|
const filteredPageMetas = useMemo(
|
||||||
() =>
|
() =>
|
||||||
pageMetas
|
pageMetas.filter(pageMeta => {
|
||||||
.filter(pageMeta => {
|
if (options.trash) {
|
||||||
if (pageMode === 'all') {
|
if (!pageMeta.trash) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pageMode === 'edgeless') {
|
|
||||||
return isPreferredEdgeless(pageMeta.id);
|
|
||||||
}
|
|
||||||
if (pageMode === 'page') {
|
|
||||||
return !isPreferredEdgeless(pageMeta.id);
|
|
||||||
}
|
|
||||||
console.error('unknown filter mode', pageMeta, pageMode);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.filter(pageMeta => {
|
|
||||||
if (
|
|
||||||
(route === 'trash' && !pageMeta.trash) ||
|
|
||||||
(route === 'all' && pageMeta.trash)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!currentCollection) {
|
} else if (pageMeta.trash) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
const pageData = {
|
const pageData = {
|
||||||
meta: pageMeta,
|
meta: pageMeta,
|
||||||
publicMode: getPublicMode(pageMeta.id),
|
publicMode: getPublicMode(pageMeta.id),
|
||||||
};
|
};
|
||||||
return isDefault
|
if (
|
||||||
? filterPageByRules(
|
options.filters &&
|
||||||
currentCollection.filterList,
|
!filterPageByRules(options.filters, [], pageData)
|
||||||
currentCollection.allowList,
|
) {
|
||||||
pageData
|
return false;
|
||||||
)
|
}
|
||||||
: filterPage(currentCollection, pageData);
|
|
||||||
|
if (options.collection && !filterPage(options.collection, pageData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
currentCollection,
|
|
||||||
isDefault,
|
|
||||||
isPreferredEdgeless,
|
|
||||||
getPublicMode,
|
|
||||||
pageMetas,
|
pageMetas,
|
||||||
pageMode,
|
options.trash,
|
||||||
route,
|
options.filters,
|
||||||
|
options.collection,
|
||||||
|
getPublicMode,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
|
||||||
import { style } from '@vanilla-extract/css';
|
|
||||||
export const view = style({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 10,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 600,
|
|
||||||
height: '100%',
|
|
||||||
});
|
|
||||||
export const option = style({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: 4,
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: 4,
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: cssVar('hoverColor'),
|
|
||||||
},
|
|
||||||
opacity: 0,
|
|
||||||
selectors: {
|
|
||||||
[`${view}:hover &`]: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,112 +0,0 @@
|
|||||||
import { Button, Tooltip } from '@affine/component';
|
|
||||||
import type { CollectionService } from '@affine/core/modules/collection';
|
|
||||||
import type { DeleteCollectionInfo, PropertiesMeta } from '@affine/env/filter';
|
|
||||||
import type { GetPageInfoById } from '@affine/env/page-info';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import { ViewLayersIcon } from '@blocksuite/icons';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { useCollectionManager } from '../use-collection-manager';
|
|
||||||
import * as styles from './collection-bar.css';
|
|
||||||
import {
|
|
||||||
type AllPageListConfig,
|
|
||||||
EditCollectionModal,
|
|
||||||
} from './edit-collection/edit-collection';
|
|
||||||
import { useActions } from './use-action';
|
|
||||||
|
|
||||||
interface CollectionBarProps {
|
|
||||||
getPageInfo: GetPageInfoById;
|
|
||||||
propertiesMeta: PropertiesMeta;
|
|
||||||
collectionService: CollectionService;
|
|
||||||
backToAll: () => void;
|
|
||||||
allPageListConfig: AllPageListConfig;
|
|
||||||
info: DeleteCollectionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CollectionBar = (props: CollectionBarProps) => {
|
|
||||||
const { collectionService } = props;
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const setting = useCollectionManager(collectionService);
|
|
||||||
const collection = setting.currentCollection;
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const actions = useActions({
|
|
||||||
collection,
|
|
||||||
setting,
|
|
||||||
info: props.info,
|
|
||||||
openEdit: () => setOpen(true),
|
|
||||||
});
|
|
||||||
return !setting.isDefault ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
userSelect: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '12px 20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div className={styles.view}>
|
|
||||||
<EditCollectionModal
|
|
||||||
allPageListConfig={props.allPageListConfig}
|
|
||||||
init={collection}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
onConfirm={setting.updateCollection}
|
|
||||||
/>
|
|
||||||
<ViewLayersIcon
|
|
||||||
style={{
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
content={setting.currentCollection.name}
|
|
||||||
rootOptions={{
|
|
||||||
delayDuration: 1500,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginRight: 10,
|
|
||||||
maxWidth: 200,
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{setting.currentCollection.name}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
{actions.map(action => {
|
|
||||||
return (
|
|
||||||
<Tooltip key={action.name} content={action.tooltip}>
|
|
||||||
<div
|
|
||||||
data-testid={`collection-bar-option-${action.name}`}
|
|
||||||
onClick={action.click}
|
|
||||||
className={clsx(styles.option, action.className)}
|
|
||||||
>
|
|
||||||
{action.icon}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'end',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
style={{ border: 'none', position: 'static' }}
|
|
||||||
onClick={props.backToAll}
|
|
||||||
>
|
|
||||||
{t['com.affine.collectionBar.backToAll']()}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
};
|
|
@ -9,91 +9,28 @@ import type {
|
|||||||
import type { PropertiesMeta } from '@affine/env/filter';
|
import type { PropertiesMeta } from '@affine/env/filter';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { FilterIcon } from '@blocksuite/icons';
|
import { FilterIcon } from '@blocksuite/icons';
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
|
|
||||||
import { CreateFilterMenu } from '../filter/vars';
|
import { CreateFilterMenu } from '../filter/vars';
|
||||||
import type { useCollectionManager } from '../use-collection-manager';
|
|
||||||
import * as styles from './collection-list.css';
|
import * as styles from './collection-list.css';
|
||||||
import { CollectionOperations } from './collection-operations';
|
import { CollectionOperations } from './collection-operations';
|
||||||
import {
|
import { type AllPageListConfig } from './edit-collection/edit-collection';
|
||||||
type AllPageListConfig,
|
|
||||||
EditCollectionModal,
|
|
||||||
} from './edit-collection/edit-collection';
|
|
||||||
|
|
||||||
export const CollectionList = ({
|
export const CollectionPageListOperationsMenu = ({
|
||||||
setting,
|
collection,
|
||||||
propertiesMeta,
|
|
||||||
allPageListConfig,
|
allPageListConfig,
|
||||||
userInfo,
|
userInfo,
|
||||||
disable,
|
|
||||||
}: {
|
}: {
|
||||||
setting: ReturnType<typeof useCollectionManager>;
|
collection: Collection;
|
||||||
propertiesMeta: PropertiesMeta;
|
|
||||||
allPageListConfig: AllPageListConfig;
|
allPageListConfig: AllPageListConfig;
|
||||||
userInfo: DeleteCollectionInfo;
|
userInfo: DeleteCollectionInfo;
|
||||||
disable?: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [collection, setCollection] = useState<Collection>();
|
|
||||||
const onChange = useCallback(
|
|
||||||
(filterList: Filter[]) => {
|
|
||||||
setting.updateCollection({
|
|
||||||
...setting.currentCollection,
|
|
||||||
filterList,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setting]
|
|
||||||
);
|
|
||||||
const closeUpdateCollectionModal = useCallback((open: boolean) => {
|
|
||||||
if (!open) {
|
|
||||||
setCollection(undefined);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onConfirm = useCallback(
|
|
||||||
(view: Collection) => {
|
|
||||||
setting.updateCollection(view);
|
|
||||||
closeUpdateCollectionModal(false);
|
|
||||||
},
|
|
||||||
[closeUpdateCollectionModal, setting]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<FlexWrapper alignItems="center">
|
<FlexWrapper alignItems="center">
|
||||||
{setting.isDefault ? (
|
|
||||||
<>
|
|
||||||
<Menu
|
|
||||||
items={
|
|
||||||
<CreateFilterMenu
|
|
||||||
propertiesMeta={propertiesMeta}
|
|
||||||
value={setting.currentCollection.filterList}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className={styles.filterMenuTrigger}
|
|
||||||
type="default"
|
|
||||||
icon={<FilterIcon />}
|
|
||||||
data-is-hidden={disable}
|
|
||||||
data-testid="create-first-filter"
|
|
||||||
>
|
|
||||||
{t['com.affine.filter']()}
|
|
||||||
</Button>
|
|
||||||
</Menu>
|
|
||||||
<EditCollectionModal
|
|
||||||
allPageListConfig={allPageListConfig}
|
|
||||||
init={collection}
|
|
||||||
open={!!collection}
|
|
||||||
onOpenChange={closeUpdateCollectionModal}
|
|
||||||
onConfirm={onConfirm}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<CollectionOperations
|
<CollectionOperations
|
||||||
info={userInfo}
|
info={userInfo}
|
||||||
collection={setting.currentCollection}
|
collection={collection}
|
||||||
config={allPageListConfig}
|
config={allPageListConfig}
|
||||||
setting={setting}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={styles.filterMenuTrigger}
|
className={styles.filterMenuTrigger}
|
||||||
@ -104,7 +41,41 @@ export const CollectionList = ({
|
|||||||
{t['com.affine.filter']()}
|
{t['com.affine.filter']()}
|
||||||
</Button>
|
</Button>
|
||||||
</CollectionOperations>
|
</CollectionOperations>
|
||||||
)}
|
</FlexWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllPageListOperationsMenu = ({
|
||||||
|
propertiesMeta,
|
||||||
|
filterList,
|
||||||
|
onChangeFilterList,
|
||||||
|
}: {
|
||||||
|
propertiesMeta: PropertiesMeta;
|
||||||
|
filterList: Filter[];
|
||||||
|
onChangeFilterList: (filterList: Filter[]) => void;
|
||||||
|
}) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlexWrapper alignItems="center">
|
||||||
|
<Menu
|
||||||
|
items={
|
||||||
|
<CreateFilterMenu
|
||||||
|
propertiesMeta={propertiesMeta}
|
||||||
|
value={filterList}
|
||||||
|
onChange={onChangeFilterList}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={styles.filterMenuTrigger}
|
||||||
|
type="default"
|
||||||
|
icon={<FilterIcon />}
|
||||||
|
data-testid="create-first-filter"
|
||||||
|
>
|
||||||
|
{t['com.affine.filter']()}
|
||||||
|
</Button>
|
||||||
|
</Menu>
|
||||||
</FlexWrapper>
|
</FlexWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { DeleteIcon, EditIcon, FilterIcon } from '@blocksuite/icons';
|
import { DeleteIcon, EditIcon, FilterIcon } from '@blocksuite/icons';
|
||||||
|
import { useService } from '@toeverything/infra/di';
|
||||||
import {
|
import {
|
||||||
type PropsWithChildren,
|
type PropsWithChildren,
|
||||||
type ReactElement,
|
type ReactElement,
|
||||||
@ -14,7 +15,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import type { useCollectionManager } from '../use-collection-manager';
|
import { CollectionService } from '../../../modules/collection';
|
||||||
import * as styles from './collection-operations.css';
|
import * as styles from './collection-operations.css';
|
||||||
import type { AllPageListConfig } from './index';
|
import type { AllPageListConfig } from './index';
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,6 @@ import {
|
|||||||
export const CollectionOperations = ({
|
export const CollectionOperations = ({
|
||||||
collection,
|
collection,
|
||||||
config,
|
config,
|
||||||
setting,
|
|
||||||
info,
|
info,
|
||||||
openRenameModal,
|
openRenameModal,
|
||||||
children,
|
children,
|
||||||
@ -33,9 +33,9 @@ export const CollectionOperations = ({
|
|||||||
info: DeleteCollectionInfo;
|
info: DeleteCollectionInfo;
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
config: AllPageListConfig;
|
config: AllPageListConfig;
|
||||||
setting: ReturnType<typeof useCollectionManager>;
|
|
||||||
openRenameModal?: () => void;
|
openRenameModal?: () => void;
|
||||||
}>) => {
|
}>) => {
|
||||||
|
const service = useService(CollectionService);
|
||||||
const { open: openEditCollectionModal, node: editModal } =
|
const { open: openEditCollectionModal, node: editModal } =
|
||||||
useEditCollection(config);
|
useEditCollection(config);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
@ -51,22 +51,25 @@ export const CollectionOperations = ({
|
|||||||
}
|
}
|
||||||
openEditCollectionNameModal(collection.name)
|
openEditCollectionNameModal(collection.name)
|
||||||
.then(name => {
|
.then(name => {
|
||||||
return setting.updateCollection({ ...collection, name });
|
return service.updateCollection(collection.id, () => ({
|
||||||
|
...collection,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [openRenameModal, openEditCollectionNameModal, collection, setting]);
|
}, [openRenameModal, openEditCollectionNameModal, collection, service]);
|
||||||
|
|
||||||
const showEdit = useCallback(() => {
|
const showEdit = useCallback(() => {
|
||||||
openEditCollectionModal(collection)
|
openEditCollectionModal(collection)
|
||||||
.then(collection => {
|
.then(collection => {
|
||||||
return setting.updateCollection(collection);
|
return service.updateCollection(collection.id, () => collection);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [setting, collection, openEditCollectionModal]);
|
}, [openEditCollectionModal, collection, service]);
|
||||||
|
|
||||||
const actions = useMemo<
|
const actions = useMemo<
|
||||||
Array<
|
Array<
|
||||||
@ -112,12 +115,12 @@ export const CollectionOperations = ({
|
|||||||
),
|
),
|
||||||
name: t['Delete'](),
|
name: t['Delete'](),
|
||||||
click: () => {
|
click: () => {
|
||||||
setting.deleteCollection(info, collection.id);
|
service.deleteCollection(info, collection.id);
|
||||||
},
|
},
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[t, showEditName, showEdit, setting, info, collection.id]
|
[t, showEditName, showEdit, service, info, collection.id]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from './affine-shape';
|
export * from './affine-shape';
|
||||||
export * from './collection-bar';
|
|
||||||
export * from './collection-list';
|
export * from './collection-list';
|
||||||
export * from './collection-operations';
|
export * from './collection-operations';
|
||||||
export * from './create-collection';
|
export * from './create-collection';
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
import type { Collection } from '@affine/env/filter';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { DeleteIcon, FilterIcon } from '@blocksuite/icons';
|
import { DeleteIcon, FilterIcon } from '@blocksuite/icons';
|
||||||
import { type ReactNode, useMemo } from 'react';
|
import { type ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
import type { useCollectionManager } from '../use-collection-manager';
|
|
||||||
|
|
||||||
interface CollectionBarAction {
|
interface CollectionBarAction {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
click: () => void;
|
click: () => void;
|
||||||
@ -15,14 +13,12 @@ interface CollectionBarAction {
|
|||||||
|
|
||||||
export const useActions = ({
|
export const useActions = ({
|
||||||
collection,
|
collection,
|
||||||
setting,
|
|
||||||
openEdit,
|
openEdit,
|
||||||
info,
|
onDelete,
|
||||||
}: {
|
}: {
|
||||||
info: DeleteCollectionInfo;
|
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
setting: ReturnType<typeof useCollectionManager>;
|
|
||||||
openEdit: (open: Collection) => void;
|
openEdit: (open: Collection) => void;
|
||||||
|
onDelete: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
return useMemo<CollectionBarAction[]>(() => {
|
return useMemo<CollectionBarAction[]>(() => {
|
||||||
@ -39,10 +35,8 @@ export const useActions = ({
|
|||||||
icon: <DeleteIcon style={{ color: 'var(--affine-error-color)' }} />,
|
icon: <DeleteIcon style={{ color: 'var(--affine-error-color)' }} />,
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
tooltip: t['com.affine.collection-bar.action.tooltip.delete'](),
|
tooltip: t['com.affine.collection-bar.action.tooltip.delete'](),
|
||||||
click: () => {
|
click: onDelete,
|
||||||
setting.deleteCollection(info, collection.id);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [info, collection, t, setting, openEdit]);
|
}, [t, onDelete, openEdit, collection]);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useCollectionManager } from '@affine/core/components/page-list';
|
|
||||||
import {
|
import {
|
||||||
useBlockSuitePageMeta,
|
useBlockSuitePageMeta,
|
||||||
usePageMetaHelper,
|
usePageMetaHelper,
|
||||||
@ -322,9 +321,8 @@ export const collectionToCommand = (
|
|||||||
|
|
||||||
export const useCollectionsCommands = () => {
|
export const useCollectionsCommands = () => {
|
||||||
// todo: considering collections for searching pages
|
// todo: considering collections for searching pages
|
||||||
const { savedCollections } = useCollectionManager(
|
const collectionService = useService(CollectionService);
|
||||||
useService(CollectionService)
|
const collections = useLiveData(collectionService.collections);
|
||||||
);
|
|
||||||
const query = useAtomValue(cmdkQueryAtom);
|
const query = useAtomValue(cmdkQueryAtom);
|
||||||
const navigationHelper = useNavigateHelper();
|
const navigationHelper = useNavigateHelper();
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
@ -340,7 +338,7 @@ export const useCollectionsCommands = () => {
|
|||||||
if (query.trim() === '') {
|
if (query.trim() === '') {
|
||||||
return results;
|
return results;
|
||||||
} else {
|
} else {
|
||||||
results = savedCollections.map(collection => {
|
results = collections.map(collection => {
|
||||||
const command = collectionToCommand(
|
const command = collectionToCommand(
|
||||||
collection,
|
collection,
|
||||||
navigationHelper,
|
navigationHelper,
|
||||||
@ -352,14 +350,7 @@ export const useCollectionsCommands = () => {
|
|||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}, [
|
}, [query, collections, navigationHelper, selectCollection, t, workspace]);
|
||||||
query,
|
|
||||||
savedCollections,
|
|
||||||
navigationHelper,
|
|
||||||
selectCollection,
|
|
||||||
t,
|
|
||||||
workspace,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCMDKCommandGroups = () => {
|
export const useCMDKCommandGroups = () => {
|
||||||
|
@ -4,18 +4,19 @@ import { allPageFilterSelectAtom } from '@affine/core/atoms';
|
|||||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { Workspace } from '@toeverything/infra';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
export const WorkspaceModeFilterTab = ({
|
export const WorkspaceModeFilterTab = ({
|
||||||
workspaceId,
|
|
||||||
activeFilter,
|
activeFilter,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
|
||||||
activeFilter: AllPageFilterOption;
|
activeFilter: AllPageFilterOption;
|
||||||
}) => {
|
}) => {
|
||||||
|
const workspace = useService(Workspace);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [value, setValue] = useState(activeFilter);
|
const [value, setValue] = useState(activeFilter);
|
||||||
const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom);
|
const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom);
|
||||||
@ -24,17 +25,17 @@ export const WorkspaceModeFilterTab = ({
|
|||||||
(value: AllPageFilterOption) => {
|
(value: AllPageFilterOption) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'collections':
|
case 'collections':
|
||||||
jumpToCollections(workspaceId);
|
jumpToCollections(workspace.id);
|
||||||
break;
|
break;
|
||||||
case 'tags':
|
case 'tags':
|
||||||
jumpToTags(workspaceId);
|
jumpToTags(workspace.id);
|
||||||
break;
|
break;
|
||||||
case 'docs':
|
case 'docs':
|
||||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[jumpToCollections, jumpToSubPath, jumpToTags, workspaceId]
|
[jumpToCollections, jumpToSubPath, jumpToTags, workspace]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
CollectionOperations,
|
CollectionOperations,
|
||||||
filterPage,
|
filterPage,
|
||||||
stopPropagation,
|
stopPropagation,
|
||||||
useCollectionManager,
|
|
||||||
} from '@affine/core/components/page-list';
|
} from '@affine/core/components/page-list';
|
||||||
import { CollectionService } from '@affine/core/modules/collection';
|
import { CollectionService } from '@affine/core/modules/collection';
|
||||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||||
@ -40,20 +39,20 @@ const CollectionRenderer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const collectionService = useService(CollectionService);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const dragItemId = getDropItemId('collections', collection.id);
|
const dragItemId = getDropItemId('collections', collection.id);
|
||||||
|
|
||||||
const removeFromAllowList = useCallback(
|
const removeFromAllowList = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
setting.updateCollection({
|
collectionService.updateCollection(collection.id, () => ({
|
||||||
...collection,
|
...collection,
|
||||||
allowList: collection.allowList?.filter(v => v !== id),
|
allowList: collection.allowList?.filter(v => v !== id),
|
||||||
});
|
}));
|
||||||
|
|
||||||
toast(t['com.affine.collection.removePage.success']());
|
toast(t['com.affine.collection.removePage.success']());
|
||||||
},
|
},
|
||||||
[collection, setting, t]
|
[collection, collectionService, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { setNodeRef, isOver } = useDroppable({
|
const { setNodeRef, isOver } = useDroppable({
|
||||||
@ -66,7 +65,7 @@ const CollectionRenderer = ({
|
|||||||
} else {
|
} else {
|
||||||
toast(t['com.affine.collection.addPage.success']());
|
toast(t['com.affine.collection.addPage.success']());
|
||||||
}
|
}
|
||||||
setting.addPage(collection.id, id);
|
collectionService.addPageToCollection(collection.id, id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -95,13 +94,13 @@ const CollectionRenderer = ({
|
|||||||
|
|
||||||
const onRename = useCallback(
|
const onRename = useCallback(
|
||||||
(name: string) => {
|
(name: string) => {
|
||||||
setting.updateCollection({
|
collectionService.updateCollection(collection.id, () => ({
|
||||||
...collection,
|
...collection,
|
||||||
name,
|
name,
|
||||||
});
|
}));
|
||||||
toast(t['com.affine.toastMessage.rename']());
|
toast(t['com.affine.toastMessage.rename']());
|
||||||
},
|
},
|
||||||
[collection, setting, t]
|
[collection, collectionService, t]
|
||||||
);
|
);
|
||||||
const handleOpen = useCallback(() => {
|
const handleOpen = useCallback(() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
@ -124,7 +123,6 @@ const CollectionRenderer = ({
|
|||||||
<CollectionOperations
|
<CollectionOperations
|
||||||
info={info}
|
info={info}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
setting={setting}
|
|
||||||
config={config}
|
config={config}
|
||||||
openRenameModal={handleOpen}
|
openRenameModal={handleOpen}
|
||||||
>
|
>
|
||||||
|
@ -38,7 +38,6 @@ import { WorkspaceSubPath } from '../../shared';
|
|||||||
import {
|
import {
|
||||||
createEmptyCollection,
|
createEmptyCollection,
|
||||||
MoveToTrash,
|
MoveToTrash,
|
||||||
useCollectionManager,
|
|
||||||
useEditCollectionName,
|
useEditCollectionName,
|
||||||
} from '../page-list';
|
} from '../page-list';
|
||||||
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||||
@ -177,7 +176,7 @@ export const RootAppSidebar = ({
|
|||||||
useRegisterBrowserHistoryCommands(router.back, router.forward);
|
useRegisterBrowserHistoryCommands(router.back, router.forward);
|
||||||
const userInfo = useDeleteCollectionInfo();
|
const userInfo = useDeleteCollectionInfo();
|
||||||
|
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const collection = useService(CollectionService);
|
||||||
const { node, open } = useEditCollectionName({
|
const { node, open } = useEditCollectionName({
|
||||||
title: t['com.affine.editCollection.createCollection'](),
|
title: t['com.affine.editCollection.createCollection'](),
|
||||||
showTips: true,
|
showTips: true,
|
||||||
@ -186,13 +185,13 @@ export const RootAppSidebar = ({
|
|||||||
open('')
|
open('')
|
||||||
.then(name => {
|
.then(name => {
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
setting.createCollection(createEmptyCollection(id, { name }));
|
collection.addCollection(createEmptyCollection(id, { name }));
|
||||||
navigateHelper.jumpToCollection(blockSuiteWorkspace.id, id);
|
navigateHelper.jumpToCollection(blockSuiteWorkspace.id, id);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [blockSuiteWorkspace.id, navigateHelper, open, setting]);
|
}, [blockSuiteWorkspace.id, collection, navigateHelper, open]);
|
||||||
|
|
||||||
const allPageActive = useMemo(() => {
|
const allPageActive = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
|
@ -44,7 +44,7 @@ export function useNavigateHelper() {
|
|||||||
);
|
);
|
||||||
const jumpToCollections = useCallback(
|
const jumpToCollections = useCallback(
|
||||||
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate(`/workspace/${workspaceId}/all?filterMode=collections`, {
|
return navigate(`/workspace/${workspaceId}/collection`, {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -52,7 +52,7 @@ export function useNavigateHelper() {
|
|||||||
);
|
);
|
||||||
const jumpToTags = useCallback(
|
const jumpToTags = useCallback(
|
||||||
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate(`/workspace/${workspaceId}/all?filterMode=tags`, {
|
return navigate(`/workspace/${workspaceId}/tag`, {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { Workspace } from '@toeverything/infra';
|
import { Workspace } from '@toeverything/infra';
|
||||||
import { useService } from '@toeverything/infra/di';
|
import { useService } from '@toeverything/infra/di';
|
||||||
import { useSetAtom, useStore } from 'jotai';
|
import { useStore } from 'jotai';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { allPageModeSelectAtom } from '../atoms';
|
|
||||||
import {
|
import {
|
||||||
registerAffineCreationCommands,
|
registerAffineCreationCommands,
|
||||||
registerAffineHelpCommands,
|
registerAffineHelpCommands,
|
||||||
@ -27,7 +26,6 @@ export function useRegisterWorkspaceCommands() {
|
|||||||
const languageHelper = useLanguageHelper();
|
const languageHelper = useLanguageHelper();
|
||||||
const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
||||||
const navigationHelper = useNavigateHelper();
|
const navigationHelper = useNavigateHelper();
|
||||||
const setPageListMode = useSetAtom(allPageModeSelectAtom);
|
|
||||||
const [editor] = useActiveBlocksuiteEditor();
|
const [editor] = useActiveBlocksuiteEditor();
|
||||||
|
|
||||||
// register AffineUpdatesCommands
|
// register AffineUpdatesCommands
|
||||||
@ -49,19 +47,12 @@ export function useRegisterWorkspaceCommands() {
|
|||||||
t,
|
t,
|
||||||
workspace: currentWorkspace.blockSuiteWorkspace,
|
workspace: currentWorkspace.blockSuiteWorkspace,
|
||||||
navigationHelper,
|
navigationHelper,
|
||||||
setPageListMode,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
unsub();
|
||||||
};
|
};
|
||||||
}, [
|
}, [store, t, currentWorkspace.blockSuiteWorkspace, navigationHelper]);
|
||||||
store,
|
|
||||||
t,
|
|
||||||
currentWorkspace.blockSuiteWorkspace,
|
|
||||||
navigationHelper,
|
|
||||||
setPageListMode,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// register AffineSettingsCommands
|
// register AffineSettingsCommands
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -81,6 +81,15 @@ export class CollectionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPageToCollection(collectionId: string, pageId: string) {
|
||||||
|
this.updateCollection(collectionId, old => {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
allowList: [pageId, ...(old.allowList ?? [])],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deleteCollection(info: DeleteCollectionInfo, ...ids: string[]) {
|
deleteCollection(info: DeleteCollectionInfo, ...ids: string[]) {
|
||||||
const collectionsYArray = this.collectionsYArray;
|
const collectionsYArray = this.collectionsYArray;
|
||||||
if (!collectionsYArray) {
|
if (!collectionsYArray) {
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export class PageListView {
|
||||||
|
constructor() {}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { IconButton } from '@affine/component';
|
||||||
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
|
import { PlusIcon } from '@blocksuite/icons';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
|
||||||
|
export const AllCollectionHeader = ({
|
||||||
|
showCreateNew,
|
||||||
|
onCreateCollection,
|
||||||
|
}: {
|
||||||
|
showCreateNew: boolean;
|
||||||
|
onCreateCollection?: () => void;
|
||||||
|
}) => {
|
||||||
|
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||||
|
|
||||||
|
const renderRightItem = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
type="default"
|
||||||
|
icon={<PlusIcon fontSize={16} />}
|
||||||
|
onClick={onCreateCollection}
|
||||||
|
className={clsx(
|
||||||
|
styles.headerCreateNewButton,
|
||||||
|
styles.headerCreateNewCollectionIconButton,
|
||||||
|
!showCreateNew && styles.headerCreateNewButtonHidden
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [onCreateCollection, showCreateNew]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
right={
|
||||||
|
<div
|
||||||
|
className={styles.headerRightWindows}
|
||||||
|
data-is-windows-desktop={isWindowsDesktop}
|
||||||
|
>
|
||||||
|
{renderRightItem}
|
||||||
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
center={<WorkspaceModeFilterTab activeFilter={'collections'} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,93 @@
|
|||||||
|
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||||
|
import {
|
||||||
|
CollectionListHeader,
|
||||||
|
type CollectionMeta,
|
||||||
|
createEmptyCollection,
|
||||||
|
useEditCollectionName,
|
||||||
|
VirtualizedCollectionList,
|
||||||
|
} from '@affine/core/components/page-list';
|
||||||
|
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
||||||
|
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { useLiveData } from '@toeverything/infra';
|
||||||
|
import { Workspace } from '@toeverything/infra';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { CollectionService } from '../../../modules/collection';
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
import { EmptyCollectionList } from '../page-list-empty';
|
||||||
|
import { AllCollectionHeader } from './header';
|
||||||
|
|
||||||
|
export const AllCollection = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const currentWorkspace = useService(Workspace);
|
||||||
|
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
|
||||||
|
|
||||||
|
const collectionService = useService(CollectionService);
|
||||||
|
const collections = useLiveData(collectionService.collections);
|
||||||
|
const config = useAllPageListConfig();
|
||||||
|
|
||||||
|
const collectionMetas = useMemo(() => {
|
||||||
|
const collectionsList: CollectionMeta[] = collections.map(collection => {
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
title: collection.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return collectionsList;
|
||||||
|
}, [collections]);
|
||||||
|
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
const { open, node } = useEditCollectionName({
|
||||||
|
title: t['com.affine.editCollection.createCollection'](),
|
||||||
|
showTips: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCreateCollection = useCallback(() => {
|
||||||
|
open('')
|
||||||
|
.then(name => {
|
||||||
|
const id = nanoid();
|
||||||
|
collectionService.addCollection(createEmptyCollection(id, { name }));
|
||||||
|
navigateHelper.jumpToCollection(currentWorkspace.id, id);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [collectionService, currentWorkspace, navigateHelper, open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<AllCollectionHeader
|
||||||
|
showCreateNew={!hideHeaderCreateNew}
|
||||||
|
onCreateCollection={handleCreateCollection}
|
||||||
|
/>
|
||||||
|
{collectionMetas.length > 0 ? (
|
||||||
|
<VirtualizedCollectionList
|
||||||
|
collections={collections}
|
||||||
|
collectionMetas={collectionMetas}
|
||||||
|
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
|
||||||
|
node={node}
|
||||||
|
config={config}
|
||||||
|
handleCreateCollection={handleCreateCollection}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyCollectionList
|
||||||
|
heading={
|
||||||
|
<CollectionListHeader
|
||||||
|
node={node}
|
||||||
|
onCreate={handleCreateCollection}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HubIsland />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
return <AllCollection />;
|
||||||
|
};
|
@ -8,36 +8,31 @@ import { filterContainerStyle } from '../../../components/filter-container.css';
|
|||||||
import {
|
import {
|
||||||
FilterList,
|
FilterList,
|
||||||
SaveAsCollectionButton,
|
SaveAsCollectionButton,
|
||||||
useCollectionManager,
|
|
||||||
} from '../../../components/page-list';
|
} from '../../../components/page-list';
|
||||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
|
|
||||||
export const FilterContainer = () => {
|
export const FilterContainer = ({
|
||||||
|
filters,
|
||||||
|
onChangeFilters,
|
||||||
|
}: {
|
||||||
|
filters: Filter[];
|
||||||
|
onChangeFilters: (filters: Filter[]) => void;
|
||||||
|
}) => {
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const navigateHelper = useNavigateHelper();
|
const navigateHelper = useNavigateHelper();
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const collectionService = useService(CollectionService);
|
||||||
const saveToCollection = useCallback(
|
const saveToCollection = useCallback(
|
||||||
(collection: Collection) => {
|
(collection: Collection) => {
|
||||||
setting.createCollection({
|
collectionService.addCollection({
|
||||||
...collection,
|
...collection,
|
||||||
filterList: setting.currentCollection.filterList,
|
filterList: filters,
|
||||||
});
|
});
|
||||||
navigateHelper.jumpToCollection(currentWorkspace.id, collection.id);
|
navigateHelper.jumpToCollection(currentWorkspace.id, collection.id);
|
||||||
},
|
},
|
||||||
[setting, navigateHelper, currentWorkspace.id]
|
[collectionService, filters, navigateHelper, currentWorkspace.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFilterChange = useCallback(
|
if (!filters.length) {
|
||||||
(filterList: Filter[]) => {
|
|
||||||
setting.updateCollection({
|
|
||||||
...setting.currentCollection,
|
|
||||||
filterList,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setting]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,12 +41,12 @@ export const FilterContainer = () => {
|
|||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<FilterList
|
<FilterList
|
||||||
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
|
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
|
||||||
value={setting.currentCollection.filterList}
|
value={filters}
|
||||||
onChange={onFilterChange}
|
onChange={onChangeFilters}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{setting.currentCollection.filterList.length > 0 ? (
|
{filters.length > 0 ? (
|
||||||
<SaveAsCollectionButton onConfirm={saveToCollection} />
|
<SaveAsCollectionButton onConfirm={saveToCollection} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,69 +1,33 @@
|
|||||||
import { IconButton } from '@affine/component';
|
|
||||||
import type { AllPageFilterOption } from '@affine/core/atoms';
|
|
||||||
import {
|
import {
|
||||||
CollectionList,
|
AllPageListOperationsMenu,
|
||||||
PageListNewPageButton,
|
PageListNewPageButton,
|
||||||
useCollectionManager,
|
|
||||||
} from '@affine/core/components/page-list';
|
} from '@affine/core/components/page-list';
|
||||||
import { Header } from '@affine/core/components/pure/header';
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
import type { Filter } from '@affine/env/filter';
|
||||||
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
|
|
||||||
import { PlusIcon } from '@blocksuite/icons';
|
import { PlusIcon } from '@blocksuite/icons';
|
||||||
import type { Workspace } from '@blocksuite/store';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useService } from '@toeverything/infra/di';
|
import { Workspace } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { CollectionService } from '../../../modules/collection';
|
|
||||||
import * as styles from './all-page.css';
|
import * as styles from './all-page.css';
|
||||||
import { FilterContainer } from './all-page-filter';
|
import { FilterContainer } from './all-page-filter';
|
||||||
|
|
||||||
export const AllPageHeader = ({
|
export const AllPageHeader = ({
|
||||||
workspace,
|
|
||||||
showCreateNew,
|
showCreateNew,
|
||||||
isDefaultFilter,
|
filters,
|
||||||
activeFilter,
|
onChangeFilters,
|
||||||
onCreateCollection,
|
|
||||||
}: {
|
}: {
|
||||||
workspace: Workspace;
|
|
||||||
showCreateNew: boolean;
|
showCreateNew: boolean;
|
||||||
isDefaultFilter: boolean;
|
filters: Filter[];
|
||||||
activeFilter: AllPageFilterOption;
|
onChangeFilters: (filters: Filter[]) => void;
|
||||||
onCreateCollection?: () => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const setting = useCollectionManager(useService(CollectionService));
|
const workspace = useService(Workspace);
|
||||||
const config = useAllPageListConfig();
|
|
||||||
const userInfo = useDeleteCollectionInfo();
|
|
||||||
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||||
|
|
||||||
const disableFilterButton = useMemo(() => {
|
|
||||||
return activeFilter !== 'docs' && isDefaultFilter;
|
|
||||||
}, [activeFilter, isDefaultFilter]);
|
|
||||||
|
|
||||||
const renderRightItem = useMemo(() => {
|
const renderRightItem = useMemo(() => {
|
||||||
if (activeFilter === 'tags') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
activeFilter === 'collections' &&
|
|
||||||
isDefaultFilter &&
|
|
||||||
onCreateCollection
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
type="default"
|
|
||||||
icon={<PlusIcon fontSize={16} />}
|
|
||||||
onClick={onCreateCollection}
|
|
||||||
className={clsx(
|
|
||||||
styles.headerCreateNewButton,
|
|
||||||
styles.headerCreateNewCollectionIconButton,
|
|
||||||
!showCreateNew && styles.headerCreateNewButtonHidden
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<PageListNewPageButton
|
<PageListNewPageButton
|
||||||
size="small"
|
size="small"
|
||||||
@ -75,18 +39,16 @@ export const AllPageHeader = ({
|
|||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</PageListNewPageButton>
|
</PageListNewPageButton>
|
||||||
);
|
);
|
||||||
}, [activeFilter, isDefaultFilter, onCreateCollection, showCreateNew]);
|
}, [showCreateNew]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
left={
|
left={
|
||||||
<CollectionList
|
<AllPageListOperationsMenu
|
||||||
userInfo={userInfo}
|
filterList={filters}
|
||||||
allPageListConfig={config}
|
onChangeFilterList={onChangeFilters}
|
||||||
setting={setting}
|
propertiesMeta={workspace.blockSuiteWorkspace.meta.properties}
|
||||||
propertiesMeta={workspace.meta.properties}
|
|
||||||
disable={disableFilterButton}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
@ -98,14 +60,9 @@ export const AllPageHeader = ({
|
|||||||
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
center={
|
center={<WorkspaceModeFilterTab activeFilter={'docs'} />}
|
||||||
<WorkspaceModeFilterTab
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
activeFilter={activeFilter}
|
|
||||||
/>
|
/>
|
||||||
}
|
<FilterContainer filters={filters} onChangeFilters={onChangeFilters} />
|
||||||
/>
|
|
||||||
<FilterContainer />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,211 +1,50 @@
|
|||||||
import type { AllPageFilterOption } from '@affine/core/atoms';
|
|
||||||
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||||
import {
|
import {
|
||||||
CollectionListHeader,
|
|
||||||
type CollectionMeta,
|
|
||||||
createEmptyCollection,
|
|
||||||
currentCollectionAtom,
|
|
||||||
PageListHeader,
|
PageListHeader,
|
||||||
useCollectionManager,
|
|
||||||
useEditCollectionName,
|
|
||||||
useFilteredPageMetas,
|
useFilteredPageMetas,
|
||||||
useTagMetas,
|
|
||||||
VirtualizedCollectionList,
|
|
||||||
VirtualizedPageList,
|
VirtualizedPageList,
|
||||||
} from '@affine/core/components/page-list';
|
} from '@affine/core/components/page-list';
|
||||||
import {
|
|
||||||
TagListHeader,
|
|
||||||
VirtualizedTagList,
|
|
||||||
} from '@affine/core/components/page-list/tags';
|
|
||||||
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
|
||||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||||
import { performanceRenderLogger } from '@affine/core/shared';
|
import { performanceRenderLogger } from '@affine/core/shared';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import type { Filter } from '@affine/env/filter';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
|
||||||
import { Workspace } from '@toeverything/infra';
|
import { Workspace } from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useEffect, useState } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
|
||||||
import { NIL } from 'uuid';
|
|
||||||
|
|
||||||
import { CollectionService } from '../../../modules/collection';
|
import { EmptyPageList } from '../page-list-empty';
|
||||||
import {
|
|
||||||
EmptyCollectionList,
|
|
||||||
EmptyPageList,
|
|
||||||
EmptyTagList,
|
|
||||||
} from '../page-list-empty';
|
|
||||||
import * as styles from './all-page.css';
|
import * as styles from './all-page.css';
|
||||||
import { AllPageHeader } from './all-page-header';
|
import { AllPageHeader } from './all-page-header';
|
||||||
|
|
||||||
// even though it is called all page, it is also being used for collection route as well
|
export const AllPage = () => {
|
||||||
export const AllPage = ({
|
|
||||||
activeFilter,
|
|
||||||
}: {
|
|
||||||
activeFilter: AllPageFilterOption;
|
|
||||||
}) => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const params = useParams();
|
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||||
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
|
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
|
||||||
|
|
||||||
const collectionService = useService(CollectionService);
|
const [filters, setFilters] = useState<Filter[]>([]);
|
||||||
const collections = useLiveData(collectionService.collections);
|
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
|
||||||
const setting = useCollectionManager(collectionService);
|
filters: filters,
|
||||||
const config = useAllPageListConfig();
|
|
||||||
const { tags, tagMetas, filterPageMetaByTag, deleteTags } = useTagMetas(
|
|
||||||
currentWorkspace.blockSuiteWorkspace,
|
|
||||||
pageMetas
|
|
||||||
);
|
|
||||||
const filteredPageMetas = useFilteredPageMetas(
|
|
||||||
'all',
|
|
||||||
pageMetas,
|
|
||||||
currentWorkspace
|
|
||||||
);
|
|
||||||
const tagPageMetas = useMemo(() => {
|
|
||||||
if (params.tagId) {
|
|
||||||
return filterPageMetaByTag(params.tagId);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}, [filterPageMetaByTag, params.tagId]);
|
|
||||||
|
|
||||||
const collectionMetas = useMemo(() => {
|
|
||||||
const collectionsList: CollectionMeta[] = collections.map(collection => {
|
|
||||||
return {
|
|
||||||
...collection,
|
|
||||||
title: collection.name,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
return collectionsList;
|
|
||||||
}, [collections]);
|
|
||||||
|
|
||||||
const navigateHelper = useNavigateHelper();
|
|
||||||
const { open, node } = useEditCollectionName({
|
|
||||||
title: t['com.affine.editCollection.createCollection'](),
|
|
||||||
showTips: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleCreateCollection = useCallback(() => {
|
|
||||||
open('')
|
|
||||||
.then(name => {
|
|
||||||
const id = nanoid();
|
|
||||||
setting.createCollection(createEmptyCollection(id, { name }));
|
|
||||||
navigateHelper.jumpToCollection(currentWorkspace.id, id);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, [currentWorkspace.id, navigateHelper, open, setting]);
|
|
||||||
|
|
||||||
const currentTag = useMemo(() => {
|
|
||||||
if (params.tagId) {
|
|
||||||
return tags.find(tag => tag.id === params.tagId);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}, [params.tagId, tags]);
|
|
||||||
|
|
||||||
const content = useMemo(() => {
|
|
||||||
if (filteredPageMetas.length > 0 && activeFilter === 'docs') {
|
|
||||||
return (
|
|
||||||
<VirtualizedPageList
|
|
||||||
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (activeFilter === 'collections' && !setting.isDefault) {
|
|
||||||
return (
|
|
||||||
<VirtualizedPageList
|
|
||||||
collection={setting.currentCollection}
|
|
||||||
config={config}
|
|
||||||
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (activeFilter === 'collections' && setting.isDefault) {
|
|
||||||
return collectionMetas.length > 0 ? (
|
|
||||||
<VirtualizedCollectionList
|
|
||||||
collections={collections}
|
|
||||||
collectionMetas={collectionMetas}
|
|
||||||
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
|
|
||||||
node={node}
|
|
||||||
config={config}
|
|
||||||
handleCreateCollection={handleCreateCollection}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<EmptyCollectionList
|
|
||||||
heading={
|
|
||||||
<CollectionListHeader
|
|
||||||
node={node}
|
|
||||||
onCreate={handleCreateCollection}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (activeFilter === 'tags') {
|
|
||||||
if (params.tagId) {
|
|
||||||
return tagPageMetas.length > 0 ? (
|
|
||||||
<VirtualizedPageList
|
|
||||||
tag={currentTag}
|
|
||||||
listItem={tagPageMetas}
|
|
||||||
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<EmptyPageList
|
|
||||||
type="all"
|
|
||||||
heading={<PageListHeader workspaceId={currentWorkspace.id} />}
|
|
||||||
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tags.length > 0 ? (
|
|
||||||
<VirtualizedTagList
|
|
||||||
tags={tags}
|
|
||||||
tagMetas={tagMetas}
|
|
||||||
setHideHeaderCreateNewTag={setHideHeaderCreateNew}
|
|
||||||
onTagDelete={deleteTags}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<EmptyTagList heading={<TagListHeader />} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<EmptyPageList
|
|
||||||
type="all"
|
|
||||||
heading={<PageListHeader workspaceId={currentWorkspace.id} />}
|
|
||||||
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
activeFilter,
|
|
||||||
collectionMetas,
|
|
||||||
collections,
|
|
||||||
config,
|
|
||||||
currentTag,
|
|
||||||
currentWorkspace.blockSuiteWorkspace,
|
|
||||||
currentWorkspace.id,
|
|
||||||
deleteTags,
|
|
||||||
filteredPageMetas.length,
|
|
||||||
handleCreateCollection,
|
|
||||||
node,
|
|
||||||
params.tagId,
|
|
||||||
setting.currentCollection,
|
|
||||||
setting.isDefault,
|
|
||||||
tagMetas,
|
|
||||||
tagPageMetas,
|
|
||||||
tags,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<AllPageHeader
|
<AllPageHeader
|
||||||
workspace={currentWorkspace.blockSuiteWorkspace}
|
|
||||||
showCreateNew={!hideHeaderCreateNew}
|
showCreateNew={!hideHeaderCreateNew}
|
||||||
isDefaultFilter={setting.isDefault}
|
filters={filters}
|
||||||
activeFilter={activeFilter}
|
onChangeFilters={setFilters}
|
||||||
onCreateCollection={handleCreateCollection}
|
|
||||||
/>
|
/>
|
||||||
{content}
|
{filteredPageMetas.length > 0 ? (
|
||||||
|
<VirtualizedPageList
|
||||||
|
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
||||||
|
filters={filters}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyPageList
|
||||||
|
type="all"
|
||||||
|
heading={<PageListHeader />}
|
||||||
|
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<HubIsland />
|
<HubIsland />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -215,21 +54,8 @@ export const Component = () => {
|
|||||||
performanceRenderLogger.info('AllPage');
|
performanceRenderLogger.info('AllPage');
|
||||||
|
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const currentCollection = useSetAtom(currentCollectionAtom);
|
|
||||||
const navigateHelper = useNavigateHelper();
|
const navigateHelper = useNavigateHelper();
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const activeFilter = useMemo(() => {
|
|
||||||
const query = new URLSearchParams(location.search);
|
|
||||||
const filterMode = query.get('filterMode');
|
|
||||||
if (filterMode === 'collections') {
|
|
||||||
return 'collections';
|
|
||||||
} else if (filterMode === 'tags') {
|
|
||||||
return 'tags';
|
|
||||||
}
|
|
||||||
return 'docs';
|
|
||||||
}, [location.search]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function checkJumpOnce() {
|
function checkJumpOnce() {
|
||||||
for (const [pageId] of currentWorkspace.blockSuiteWorkspace.pages) {
|
for (const [pageId] of currentWorkspace.blockSuiteWorkspace.pages) {
|
||||||
@ -252,9 +78,5 @@ export const Component = () => {
|
|||||||
navigateHelper,
|
navigateHelper,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
return <AllPage />;
|
||||||
currentCollection(NIL);
|
|
||||||
}, [currentCollection]);
|
|
||||||
|
|
||||||
return <AllPage activeFilter={activeFilter} />;
|
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
|
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
|
||||||
|
export const AllTagHeader = () => {
|
||||||
|
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
right={
|
||||||
|
<div
|
||||||
|
className={styles.headerRightWindows}
|
||||||
|
data-is-windows-desktop={isWindowsDesktop}
|
||||||
|
>
|
||||||
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
center={<WorkspaceModeFilterTab activeFilter={'tags'} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
43
packages/frontend/core/src/pages/workspace/all-tag/index.tsx
Normal file
43
packages/frontend/core/src/pages/workspace/all-tag/index.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||||
|
import { useTagMetas } from '@affine/core/components/page-list';
|
||||||
|
import {
|
||||||
|
TagListHeader,
|
||||||
|
VirtualizedTagList,
|
||||||
|
} from '@affine/core/components/page-list/tags';
|
||||||
|
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { Workspace } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
import { EmptyTagList } from '../page-list-empty';
|
||||||
|
import { AllTagHeader } from './header';
|
||||||
|
|
||||||
|
export const AllTag = () => {
|
||||||
|
const currentWorkspace = useService(Workspace);
|
||||||
|
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||||
|
|
||||||
|
const { tags, tagMetas, deleteTags } = useTagMetas(
|
||||||
|
currentWorkspace.blockSuiteWorkspace,
|
||||||
|
pageMetas
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<AllTagHeader />
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
<VirtualizedTagList
|
||||||
|
tags={tags}
|
||||||
|
tagMetas={tagMetas}
|
||||||
|
onTagDelete={deleteTags}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyTagList heading={<TagListHeader />} />
|
||||||
|
)}
|
||||||
|
<HubIsland />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
return <AllTag />;
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
import { IconButton } from '@affine/component';
|
||||||
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
|
import { PlusIcon } from '@blocksuite/icons';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
|
||||||
|
export const CollectionDetailHeader = ({
|
||||||
|
showCreateNew,
|
||||||
|
onCreate,
|
||||||
|
}: {
|
||||||
|
showCreateNew: boolean;
|
||||||
|
onCreate: () => void;
|
||||||
|
}) => {
|
||||||
|
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||||
|
|
||||||
|
const renderRightItem = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
type="default"
|
||||||
|
icon={<PlusIcon fontSize={16} />}
|
||||||
|
onClick={onCreate}
|
||||||
|
className={clsx(
|
||||||
|
styles.headerCreateNewButton,
|
||||||
|
styles.headerCreateNewCollectionIconButton,
|
||||||
|
!showCreateNew && styles.headerCreateNewButtonHidden
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [onCreate, showCreateNew]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
right={
|
||||||
|
<div
|
||||||
|
className={styles.headerRightWindows}
|
||||||
|
data-is-windows-desktop={isWindowsDesktop}
|
||||||
|
>
|
||||||
|
{renderRightItem}
|
||||||
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
center={<WorkspaceModeFilterTab activeFilter={'collections'} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -3,11 +3,11 @@ import {
|
|||||||
SidebarSwitch,
|
SidebarSwitch,
|
||||||
} from '@affine/component/app-sidebar';
|
} from '@affine/component/app-sidebar';
|
||||||
import { pushNotificationAtom } from '@affine/component/notification-center';
|
import { pushNotificationAtom } from '@affine/component/notification-center';
|
||||||
|
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||||
import {
|
import {
|
||||||
AffineShapeIcon,
|
AffineShapeIcon,
|
||||||
currentCollectionAtom,
|
|
||||||
useCollectionManager,
|
|
||||||
useEditCollection,
|
useEditCollection,
|
||||||
|
VirtualizedPageList,
|
||||||
} from '@affine/core/components/page-list';
|
} from '@affine/core/components/page-list';
|
||||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
||||||
@ -23,30 +23,54 @@ import {
|
|||||||
ViewLayersIcon,
|
ViewLayersIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import { Workspace } from '@toeverything/infra';
|
import { Workspace } from '@toeverything/infra';
|
||||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
|
||||||
import { useService } from '@toeverything/infra/di';
|
import { useService } from '@toeverything/infra/di';
|
||||||
import { useLiveData } from '@toeverything/infra/livedata';
|
import { useLiveData } from '@toeverything/infra/livedata';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
import { WorkspaceSubPath } from '../../shared';
|
import { WorkspaceSubPath } from '../../../shared';
|
||||||
import { AllPage } from './all-page/all-page';
|
import * as allPageStyles from '../all-page/all-page.css';
|
||||||
import * as styles from './collection.css';
|
import * as styles from './collection.css';
|
||||||
|
import { CollectionDetailHeader } from './header';
|
||||||
|
|
||||||
export const loader: LoaderFunction = async args => {
|
export const CollectionDetail = ({
|
||||||
const rootStore = getCurrentStore();
|
collection,
|
||||||
if (!args.params.collectionId) {
|
}: {
|
||||||
return redirect('/404');
|
collection: Collection;
|
||||||
}
|
}) => {
|
||||||
rootStore.set(currentCollectionAtom, args.params.collectionId);
|
const config = useAllPageListConfig();
|
||||||
return null;
|
const { node, open } = useEditCollection(useAllPageListConfig());
|
||||||
|
const collectionService = useService(CollectionService);
|
||||||
|
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
|
||||||
|
|
||||||
|
const handleEditCollection = useAsyncCallback(async () => {
|
||||||
|
const ret = await open({ ...collection }, 'page');
|
||||||
|
collectionService.updateCollection(ret.id, () => ret);
|
||||||
|
}, [collection, collectionService, open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={allPageStyles.root}>
|
||||||
|
<CollectionDetailHeader
|
||||||
|
showCreateNew={!hideHeaderCreateNew}
|
||||||
|
onCreate={handleEditCollection}
|
||||||
|
/>
|
||||||
|
<VirtualizedPageList
|
||||||
|
collection={collection}
|
||||||
|
config={config}
|
||||||
|
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
||||||
|
/>
|
||||||
|
<HubIsland />
|
||||||
|
{node}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Component = function CollectionPage() {
|
export const Component = function CollectionPage() {
|
||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
|
|
||||||
const collections = useLiveData(collectionService.collections);
|
const collections = useLiveData(collectionService.collections);
|
||||||
const navigate = useNavigateHelper();
|
const navigate = useNavigateHelper();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -87,7 +111,7 @@ export const Component = function CollectionPage() {
|
|||||||
return isEmpty(collection) ? (
|
return isEmpty(collection) ? (
|
||||||
<Placeholder collection={collection} />
|
<Placeholder collection={collection} />
|
||||||
) : (
|
) : (
|
||||||
<AllPage activeFilter="collections" />
|
<CollectionDetail collection={collection} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,16 +119,16 @@ const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
|||||||
|
|
||||||
const Placeholder = ({ collection }: { collection: Collection }) => {
|
const Placeholder = ({ collection }: { collection: Collection }) => {
|
||||||
const workspace = useService(Workspace);
|
const workspace = useService(Workspace);
|
||||||
const collectionService = useCollectionManager(useService(CollectionService));
|
const collectionService = useService(CollectionService);
|
||||||
const { node, open } = useEditCollection(useAllPageListConfig());
|
const { node, open } = useEditCollection(useAllPageListConfig());
|
||||||
const { jumpToCollections } = useNavigateHelper();
|
const { jumpToCollections } = useNavigateHelper();
|
||||||
const openPageEdit = useAsyncCallback(async () => {
|
const openPageEdit = useAsyncCallback(async () => {
|
||||||
const ret = await open({ ...collection }, 'page');
|
const ret = await open({ ...collection }, 'page');
|
||||||
collectionService.updateCollection(ret);
|
collectionService.updateCollection(ret.id, () => ret);
|
||||||
}, [open, collection, collectionService]);
|
}, [open, collection, collectionService]);
|
||||||
const openRuleEdit = useAsyncCallback(async () => {
|
const openRuleEdit = useAsyncCallback(async () => {
|
||||||
const ret = await open({ ...collection }, 'rule');
|
const ret = await open({ ...collection }, 'rule');
|
||||||
collectionService.updateCollection(ret);
|
collectionService.updateCollection(ret.id, () => ret);
|
||||||
}, [collection, open, collectionService]);
|
}, [collection, open, collectionService]);
|
||||||
const [showTips, setShowTips] = useState(false);
|
const [showTips, setShowTips] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
@ -2,7 +2,6 @@ import { Scrollable } from '@affine/component';
|
|||||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||||
import { ResizePanel } from '@affine/component/resize-panel';
|
import { ResizePanel } from '@affine/component/resize-panel';
|
||||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
import { CollectionService } from '@affine/core/modules/collection';
|
|
||||||
import type { PageService } from '@blocksuite/blocks';
|
import type { PageService } from '@blocksuite/blocks';
|
||||||
import {
|
import {
|
||||||
BookmarkService,
|
BookmarkService,
|
||||||
@ -42,17 +41,13 @@ import { HubIsland } from '../../../components/affine/hub-island';
|
|||||||
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
|
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
|
||||||
import { ImagePreviewModal } from '../../../components/image-preview';
|
import { ImagePreviewModal } from '../../../components/image-preview';
|
||||||
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
||||||
import {
|
|
||||||
createTagFilter,
|
|
||||||
useCollectionManager,
|
|
||||||
} from '../../../components/page-list';
|
|
||||||
import { TrashPageFooter } from '../../../components/pure/trash-page-footer';
|
import { TrashPageFooter } from '../../../components/pure/trash-page-footer';
|
||||||
import { TopTip } from '../../../components/top-tip';
|
import { TopTip } from '../../../components/top-tip';
|
||||||
import { useRegisterBlocksuiteEditorCommands } from '../../../hooks/affine/use-register-blocksuite-editor-commands';
|
import { useRegisterBlocksuiteEditorCommands } from '../../../hooks/affine/use-register-blocksuite-editor-commands';
|
||||||
import { usePageDocumentTitle } from '../../../hooks/use-global-state';
|
import { usePageDocumentTitle } from '../../../hooks/use-global-state';
|
||||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
import { CurrentPageService } from '../../../modules/page';
|
import { CurrentPageService } from '../../../modules/page';
|
||||||
import { performanceRenderLogger, WorkspaceSubPath } from '../../../shared';
|
import { performanceRenderLogger } from '../../../shared';
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
import * as styles from './detail-page.css';
|
import * as styles from './detail-page.css';
|
||||||
import { DetailPageHeader, RightSidebarHeader } from './detail-page-header';
|
import { DetailPageHeader, RightSidebarHeader } from './detail-page-header';
|
||||||
@ -117,7 +112,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const page = useService(Page);
|
const page = useService(Page);
|
||||||
const pageRecordList = useService(PageRecordList);
|
const pageRecordList = useService(PageRecordList);
|
||||||
const currentPageId = page.id;
|
const currentPageId = page.id;
|
||||||
const { openPage, jumpToSubPath } = useNavigateHelper();
|
const { openPage, jumpToTag } = useNavigateHelper();
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||||
|
|
||||||
@ -127,8 +122,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
|
|
||||||
const isInTrash = pageMeta?.trash;
|
const isInTrash = pageMeta?.trash;
|
||||||
|
|
||||||
const collectionService = useService(CollectionService);
|
|
||||||
const { setTemporaryFilter } = useCollectionManager(collectionService);
|
|
||||||
const mode = useLiveData(page.mode);
|
const mode = useLiveData(page.mode);
|
||||||
useRegisterBlocksuiteEditorCommands();
|
useRegisterBlocksuiteEditorCommands();
|
||||||
const title = useLiveData(page.title);
|
const title = useLiveData(page.title);
|
||||||
@ -191,9 +184,8 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||||
return openPage(blockSuiteWorkspace.id, pageId);
|
return openPage(blockSuiteWorkspace.id, pageId);
|
||||||
});
|
});
|
||||||
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
|
const disposeTagClick = editor.slots.tagClicked.on(({ tagId }) => {
|
||||||
jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
|
jumpToTag(currentWorkspace.id, tagId);
|
||||||
setTemporaryFilter([createTagFilter(tagId)]);
|
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
dispose.dispose();
|
dispose.dispose();
|
||||||
@ -201,14 +193,13 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
page,
|
|
||||||
mode,
|
|
||||||
pageRecordList,
|
|
||||||
openPage,
|
|
||||||
blockSuiteWorkspace.id,
|
blockSuiteWorkspace.id,
|
||||||
jumpToSubPath,
|
|
||||||
currentWorkspace.id,
|
currentWorkspace.id,
|
||||||
setTemporaryFilter,
|
jumpToTag,
|
||||||
|
mode,
|
||||||
|
openPage,
|
||||||
|
page,
|
||||||
|
pageRecordList,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import { TagListHeader, useTagMetas } from '@affine/core/components/page-list';
|
|
||||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
|
||||||
import { useService, Workspace } from '@toeverything/infra';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AllPage } from './all-page/all-page';
|
|
||||||
import { AllPageHeader } from './all-page/all-page-header';
|
|
||||||
import { EmptyPageList } from './page-list-empty';
|
|
||||||
|
|
||||||
export const loader: LoaderFunction = async args => {
|
|
||||||
if (!args.params.tagId) {
|
|
||||||
return redirect('/404');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = function TagPage() {
|
|
||||||
const params = useParams();
|
|
||||||
const currentWorkspace = useService(Workspace);
|
|
||||||
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
|
||||||
const { tagUsageCounts } = useTagMetas(
|
|
||||||
currentWorkspace.blockSuiteWorkspace,
|
|
||||||
pageMetas
|
|
||||||
);
|
|
||||||
const isEmpty = useMemo(() => {
|
|
||||||
if (params.tagId) {
|
|
||||||
return tagUsageCounts[params.tagId] === 0;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, [params.tagId, tagUsageCounts]);
|
|
||||||
|
|
||||||
return isEmpty ? (
|
|
||||||
<>
|
|
||||||
<AllPageHeader
|
|
||||||
workspace={currentWorkspace.blockSuiteWorkspace}
|
|
||||||
showCreateNew={false}
|
|
||||||
isDefaultFilter={true}
|
|
||||||
activeFilter={'tags'}
|
|
||||||
/>
|
|
||||||
<EmptyPageList
|
|
||||||
type="all"
|
|
||||||
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
|
||||||
heading={<TagListHeader />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<AllPage activeFilter="tags" />
|
|
||||||
);
|
|
||||||
};
|
|
23
packages/frontend/core/src/pages/workspace/tag/header.tsx
Normal file
23
packages/frontend/core/src/pages/workspace/tag/header.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
|
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
|
||||||
|
export const TagDetailHeader = () => {
|
||||||
|
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header
|
||||||
|
right={
|
||||||
|
<div
|
||||||
|
className={styles.headerRightWindows}
|
||||||
|
data-is-windows-desktop={isWindowsDesktop}
|
||||||
|
>
|
||||||
|
{isWindowsDesktop ? <WindowsAppControls /> : null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
center={<WorkspaceModeFilterTab activeFilter={'tags'} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
63
packages/frontend/core/src/pages/workspace/tag/index.tsx
Normal file
63
packages/frontend/core/src/pages/workspace/tag/index.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||||
|
import {
|
||||||
|
PageListHeader,
|
||||||
|
useTagMetas,
|
||||||
|
VirtualizedPageList,
|
||||||
|
} from '@affine/core/components/page-list';
|
||||||
|
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { Workspace } from '@toeverything/infra';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PageNotFound } from '../../404';
|
||||||
|
import * as styles from '../all-page/all-page.css';
|
||||||
|
import { EmptyPageList } from '../page-list-empty';
|
||||||
|
import { TagDetailHeader } from './header';
|
||||||
|
|
||||||
|
export const TagDetail = ({ tagId }: { tagId?: string }) => {
|
||||||
|
const currentWorkspace = useService(Workspace);
|
||||||
|
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||||
|
|
||||||
|
const { tags, filterPageMetaByTag } = useTagMetas(
|
||||||
|
currentWorkspace.blockSuiteWorkspace,
|
||||||
|
pageMetas
|
||||||
|
);
|
||||||
|
const tagPageMetas = useMemo(() => {
|
||||||
|
if (tagId) {
|
||||||
|
return filterPageMetaByTag(tagId);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [filterPageMetaByTag, tagId]);
|
||||||
|
|
||||||
|
const currentTag = useMemo(
|
||||||
|
() => tags.find(tag => tag.id === tagId),
|
||||||
|
[tagId, tags]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentTag) {
|
||||||
|
return <PageNotFound />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<TagDetailHeader />
|
||||||
|
{tagPageMetas.length > 0 ? (
|
||||||
|
<VirtualizedPageList tag={currentTag} listItem={tagPageMetas} />
|
||||||
|
) : (
|
||||||
|
<EmptyPageList
|
||||||
|
type="all"
|
||||||
|
heading={<PageListHeader />}
|
||||||
|
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<HubIsland />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
return <TagDetail tagId={params.tagId} />;
|
||||||
|
};
|
@ -1,7 +1,6 @@
|
|||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||||
import {
|
import {
|
||||||
currentCollectionAtom,
|
|
||||||
type ListItem,
|
type ListItem,
|
||||||
ListTableHeader,
|
ListTableHeader,
|
||||||
PageListItemRenderer,
|
PageListItemRenderer,
|
||||||
@ -19,11 +18,8 @@ import { assertExists } from '@blocksuite/global/utils';
|
|||||||
import { DeleteIcon } from '@blocksuite/icons';
|
import { DeleteIcon } from '@blocksuite/icons';
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
import type { PageMeta } from '@blocksuite/store';
|
||||||
import { Workspace } from '@toeverything/infra';
|
import { Workspace } from '@toeverything/infra';
|
||||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
|
||||||
import { useService } from '@toeverything/infra/di';
|
import { useService } from '@toeverything/infra/di';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { type LoaderFunction } from 'react-router-dom';
|
|
||||||
import { NIL } from 'uuid';
|
|
||||||
|
|
||||||
import { EmptyPageList } from './page-list-empty';
|
import { EmptyPageList } from './page-list-empty';
|
||||||
import * as styles from './trash-page.css';
|
import * as styles from './trash-page.css';
|
||||||
@ -50,27 +46,15 @@ const TrashHeader = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loader: LoaderFunction = async () => {
|
|
||||||
// to fix the bug that the trash page list is not updated when route from collection to trash
|
|
||||||
// but it's not a good solution, the page will jitter when collection and trash are switched between each other.
|
|
||||||
// TODO: fix this bug
|
|
||||||
|
|
||||||
const rootStore = getCurrentStore();
|
|
||||||
rootStore.set(currentCollectionAtom, NIL);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TrashPage = () => {
|
export const TrashPage = () => {
|
||||||
const currentWorkspace = useService(Workspace);
|
const currentWorkspace = useService(Workspace);
|
||||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||||
assertExists(blockSuiteWorkspace);
|
assertExists(blockSuiteWorkspace);
|
||||||
|
|
||||||
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||||
const filteredPageMetas = useFilteredPageMetas(
|
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
|
||||||
'trash',
|
trash: true,
|
||||||
pageMetas,
|
});
|
||||||
currentWorkspace
|
|
||||||
);
|
|
||||||
|
|
||||||
const { restoreFromTrash, permanentlyDeletePage } =
|
const { restoreFromTrash, permanentlyDeletePage } =
|
||||||
useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||||
|
@ -15,9 +15,17 @@ export const routes = [
|
|||||||
path: 'all',
|
path: 'all',
|
||||||
lazy: () => import('./pages/workspace/all-page/all-page'),
|
lazy: () => import('./pages/workspace/all-page/all-page'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'collection',
|
||||||
|
lazy: () => import('./pages/workspace/all-collection'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'collection/:collectionId',
|
path: 'collection/:collectionId',
|
||||||
lazy: () => import('./pages/workspace/collection'),
|
lazy: () => import('./pages/workspace/collection/index'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tag',
|
||||||
|
lazy: () => import('./pages/workspace/all-tag'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tag/:tagId',
|
path: 'tag/:tagId',
|
||||||
|
@ -123,6 +123,7 @@ test('allow creation of filters by tags', async ({ page }) => {
|
|||||||
await createPageWithTag(page, { title: 'Page A', tags: ['Page A'] });
|
await createPageWithTag(page, { title: 'Page A', tags: ['Page A'] });
|
||||||
await createPageWithTag(page, { title: 'Page B', tags: ['Page B'] });
|
await createPageWithTag(page, { title: 'Page B', tags: ['Page B'] });
|
||||||
await clickSideBarAllPageButton(page);
|
await clickSideBarAllPageButton(page);
|
||||||
|
await createFirstFilter(page, 'Tags');
|
||||||
await checkFilterName(page, 'is not empty');
|
await checkFilterName(page, 'is not empty');
|
||||||
expect(await getPagesCount(page)).toBe(pagesWithTagsCount + 2);
|
expect(await getPagesCount(page)).toBe(pagesWithTagsCount + 2);
|
||||||
await changeFilter(page, 'contains all');
|
await changeFilter(page, 'contains all');
|
||||||
|
Loading…
Reference in New Issue
Block a user