mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 03:56:23 +08:00
refactor(core): refactor collection to use new filter system (#12228)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new Collection entity and store with reactive management and real-time updates. - Added reactive favorite and shared filters with expanded filtering options. - **Refactor** - Overhauled collection and filtering logic for better performance and maintainability. - Replaced legacy filtering UI and logic with a streamlined, service-driven rules system. - Updated collection components to use reactive data streams and simplified props. - Simplified collection creation by delegating ID generation and instantiation to the service layer. - Removed deprecated hooks and replaced state-based filtering with observable-driven filtering. - **Bug Fixes** - Improved accuracy and consistency of tag and favorite filtering in collections. - **Chores** - Removed deprecated and unused filter-related files, types, components, and styles to reduce complexity. - Cleaned up imports and removed unused code across multiple components. - **Documentation** - Corrected inline documentation for improved clarity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+30
-72
@@ -5,26 +5,17 @@ import {
|
||||
MenuItem,
|
||||
toast,
|
||||
} from '@affine/component';
|
||||
import { filterPage } from '@affine/core/components/page-list';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import {
|
||||
type Collection,
|
||||
CollectionService,
|
||||
} from '@affine/core/modules/collection';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { ShareDocsListService } from '@affine/core/modules/share-doc';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { PublicDocMode } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import { FilterMinusIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -71,6 +62,7 @@ export const NavigationPanelCollectionNode = ({
|
||||
|
||||
const collectionService = useService(CollectionService);
|
||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||
const name = useLiveData(collection?.name$);
|
||||
|
||||
const dndData = useMemo(() => {
|
||||
return {
|
||||
@@ -89,11 +81,10 @@ export const NavigationPanelCollectionNode = ({
|
||||
|
||||
const handleRename = useCallback(
|
||||
(name: string) => {
|
||||
if (collection && collection.name !== name) {
|
||||
collectionService.updateCollection(collectionId, () => ({
|
||||
...collection,
|
||||
if (collection && collection.name$.value !== name) {
|
||||
collectionService.updateCollection(collectionId, {
|
||||
name,
|
||||
}));
|
||||
});
|
||||
|
||||
track.$.navigationPanel.organize.renameOrganizeItem({
|
||||
type: 'collection',
|
||||
@@ -109,10 +100,10 @@ export const NavigationPanelCollectionNode = ({
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
if (collection.allowList.includes(docId)) {
|
||||
if (collection.allowList$.value.includes(docId)) {
|
||||
toast(t['com.affine.collection.addPage.alreadyExists']());
|
||||
} else {
|
||||
collectionService.addPageToCollection(collection.id, docId);
|
||||
collectionService.addDocToCollection(collection.id, docId);
|
||||
}
|
||||
},
|
||||
[collection, collectionService, t]
|
||||
@@ -210,7 +201,7 @@ export const NavigationPanelCollectionNode = ({
|
||||
return (
|
||||
<NavigationPanelTreeNode
|
||||
icon={CollectionIcon}
|
||||
name={collection.name || t['Untitled']()}
|
||||
name={name || t['Untitled']()}
|
||||
dndData={dndData}
|
||||
onDrop={handleDropOnCollection}
|
||||
renameable
|
||||
@@ -237,84 +228,51 @@ const NavigationPanelCollectionNodeChildren = ({
|
||||
collection: Collection;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const {
|
||||
docsService,
|
||||
compatibleFavoriteItemsAdapter,
|
||||
shareDocsListService,
|
||||
collectionService,
|
||||
} = useServices({
|
||||
DocsService,
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
ShareDocsListService,
|
||||
const { collectionService } = useServices({
|
||||
CollectionService,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// TODO(@eyhn): loading & error UI
|
||||
shareDocsListService.shareDocs?.revalidate();
|
||||
}, [shareDocsListService]);
|
||||
|
||||
const docMetas = useLiveData(
|
||||
useMemo(
|
||||
() =>
|
||||
LiveData.computed(get => {
|
||||
return get(docsService.list.docs$).map(
|
||||
doc => get(doc.meta$) as DocMeta
|
||||
);
|
||||
}),
|
||||
[docsService]
|
||||
)
|
||||
const allowList = useLiveData(
|
||||
collection.allowList$.map(list => new Set(list))
|
||||
);
|
||||
const favourites = useLiveData(compatibleFavoriteItemsAdapter.favorites$);
|
||||
const allowList = useMemo(
|
||||
() => new Set(collection.allowList),
|
||||
[collection.allowList]
|
||||
);
|
||||
const shareDocs = useLiveData(shareDocsListService.shareDocs?.list$);
|
||||
|
||||
const handleRemoveFromAllowList = useCallback(
|
||||
(id: string) => {
|
||||
track.$.navigationPanel.collections.removeOrganizeItem({ type: 'doc' });
|
||||
collectionService.deletePageFromCollection(collection.id, id);
|
||||
collectionService.removeDocFromCollection(collection.id, id);
|
||||
toast(t['com.affine.collection.removePage.success']());
|
||||
},
|
||||
[collection.id, collectionService, t]
|
||||
);
|
||||
|
||||
const filtered = docMetas.filter(meta => {
|
||||
if (meta.trash) return false;
|
||||
const publicMode = shareDocs?.find(d => d.id === meta.id)?.mode;
|
||||
const pageData = {
|
||||
meta: meta as DocMeta,
|
||||
publicMode:
|
||||
publicMode === PublicDocMode.Edgeless
|
||||
? ('edgeless' as const)
|
||||
: publicMode === PublicDocMode.Page
|
||||
? ('page' as const)
|
||||
: undefined,
|
||||
favorite: favourites.some(fav => fav.id === meta.id),
|
||||
};
|
||||
return filterPage(collection, pageData);
|
||||
});
|
||||
const [filteredDocIds, setFilteredDocIds] = useState<string[]>([]);
|
||||
|
||||
return filtered.map(doc => (
|
||||
useEffect(() => {
|
||||
const subscription = collection.watch().subscribe(docIds => {
|
||||
setFilteredDocIds(docIds);
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, [collection]);
|
||||
|
||||
return filteredDocIds.map(docId => (
|
||||
<NavigationPanelDocNode
|
||||
key={doc.id}
|
||||
docId={doc.id}
|
||||
key={docId}
|
||||
docId={docId}
|
||||
reorderable={false}
|
||||
location={{
|
||||
at: 'navigation-panel:collection:filtered-docs',
|
||||
collectionId: collection.id,
|
||||
}}
|
||||
operations={
|
||||
allowList
|
||||
allowList.has(docId)
|
||||
? [
|
||||
{
|
||||
index: 99,
|
||||
view: (
|
||||
<MenuItem
|
||||
prefixIcon={<FilterMinusIcon />}
|
||||
onClick={() => handleRemoveFromAllowList(doc.id)}
|
||||
onClick={() => handleRemoveFromAllowList(docId)}
|
||||
>
|
||||
{t['Remove special filter']()}
|
||||
</MenuItem>
|
||||
|
||||
+3
-5
@@ -5,7 +5,6 @@ import {
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||
import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
@@ -42,7 +41,6 @@ export const useNavigationPanelCollectionNodeOperations = (
|
||||
CollectionService,
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
});
|
||||
const deleteInfo = useDeleteCollectionInfo();
|
||||
|
||||
const { createPage } = usePageHelper(
|
||||
workspaceService.workspace.docCollection
|
||||
@@ -59,7 +57,7 @@ export const useNavigationPanelCollectionNodeOperations = (
|
||||
|
||||
const createAndAddDocument = useCallback(() => {
|
||||
const newDoc = createPage();
|
||||
collectionService.addPageToCollection(collectionId, newDoc.id);
|
||||
collectionService.addDocToCollection(collectionId, newDoc.id);
|
||||
track.$.navigationPanel.collections.createDoc();
|
||||
track.$.navigationPanel.collections.addDocToCollection({
|
||||
control: 'button',
|
||||
@@ -100,11 +98,11 @@ export const useNavigationPanelCollectionNodeOperations = (
|
||||
}, [collectionId, workbenchService.workbench]);
|
||||
|
||||
const handleDeleteCollection = useCallback(() => {
|
||||
collectionService.deleteCollection(deleteInfo, collectionId);
|
||||
collectionService.deleteCollection(collectionId);
|
||||
track.$.navigationPanel.organize.deleteOrganizeItem({
|
||||
type: 'collection',
|
||||
});
|
||||
}, [collectionId, collectionService, deleteInfo]);
|
||||
}, [collectionId, collectionService]);
|
||||
|
||||
const handleShowEdit = useCallback(() => {
|
||||
onOpenEdit();
|
||||
|
||||
+2
-5
@@ -1,5 +1,4 @@
|
||||
import { IconButton, usePromptModal } from '@affine/component';
|
||||
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
@@ -7,7 +6,6 @@ import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
@@ -46,8 +44,7 @@ export const NavigationPanelCollections = () => {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
const id = nanoid();
|
||||
collectionService.addCollection(createEmptyCollection(id, { name }));
|
||||
const id = collectionService.createCollection({ name });
|
||||
track.$.navigationPanel.organize.createOrganizeItem({
|
||||
type: 'collection',
|
||||
});
|
||||
@@ -84,7 +81,7 @@ export const NavigationPanelCollections = () => {
|
||||
<NavigationPanelTreeRoot
|
||||
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
|
||||
>
|
||||
{collections.map(collection => (
|
||||
{Array.from(collections.values()).map(collection => (
|
||||
<NavigationPanelCollectionNode
|
||||
key={collection.id}
|
||||
collectionId={collection.id}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, RadioGroup } from '@affine/component';
|
||||
import { useAllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config';
|
||||
import { SelectPage } from '@affine/core/components/page-list/docs/select-page';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { CollectionInfo } from '@affine/core/modules/collection';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
@@ -12,10 +12,10 @@ export type EditCollectionMode = 'page' | 'rule';
|
||||
|
||||
export interface EditCollectionProps {
|
||||
onConfirmText?: string;
|
||||
init: Collection;
|
||||
init: CollectionInfo;
|
||||
mode?: EditCollectionMode;
|
||||
onCancel: () => void;
|
||||
onConfirm: (collection: Collection) => void;
|
||||
onConfirm: (collection: CollectionInfo) => void;
|
||||
}
|
||||
|
||||
export const EditCollection = ({
|
||||
@@ -27,9 +27,9 @@ export const EditCollection = ({
|
||||
}: EditCollectionProps) => {
|
||||
const t = useI18n();
|
||||
const config = useAllPageListConfig();
|
||||
const [value, onChange] = useState<Collection>(init);
|
||||
const [value, onChange] = useState<CollectionInfo>(init);
|
||||
const [mode, setMode] = useState<'page' | 'rule'>(
|
||||
initMode ?? (init.filterList.length === 0 ? 'page' : 'rule')
|
||||
initMode ?? (init.rules.filters.length === 0 ? 'page' : 'rule')
|
||||
);
|
||||
const isNameEmpty = useMemo(() => value.name.trim().length === 0, [value]);
|
||||
const onSaveCollection = useCallback(() => {
|
||||
@@ -40,10 +40,10 @@ export const EditCollection = ({
|
||||
const reset = useCallback(() => {
|
||||
onChange({
|
||||
...value,
|
||||
filterList: init.filterList,
|
||||
rules: init.rules,
|
||||
allowList: init.allowList,
|
||||
});
|
||||
}, [init.allowList, init.filterList, value]);
|
||||
}, [init, value]);
|
||||
const onIdsChange = useCallback(
|
||||
(ids: string[]) => {
|
||||
onChange({ ...value, allowList: ids });
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import {
|
||||
type CollectionInfo,
|
||||
CollectionService,
|
||||
} from '@affine/core/modules/collection';
|
||||
import type { DialogComponentProps } from '@affine/core/modules/dialogs';
|
||||
import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
@@ -18,17 +20,18 @@ export const CollectionEditorDialog = ({
|
||||
const collectionService = useService(CollectionService);
|
||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||
const onConfirmOnCollection = useCallback(
|
||||
(collection: Collection) => {
|
||||
collectionService.updateCollection(collection.id, () => collection);
|
||||
(collection: CollectionInfo) => {
|
||||
collectionService.updateCollection(collection.id, collection);
|
||||
close();
|
||||
},
|
||||
[close, collectionService]
|
||||
);
|
||||
const info = useLiveData(collection?.info$);
|
||||
const onCancel = useCallback(() => {
|
||||
close();
|
||||
}, [close]);
|
||||
|
||||
if (!collection) {
|
||||
if (!collection || !info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -50,7 +53,7 @@ export const CollectionEditorDialog = ({
|
||||
>
|
||||
<EditCollection
|
||||
onConfirmText={t['com.affine.editCollection.save']()}
|
||||
init={collection}
|
||||
init={info}
|
||||
mode={mode}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirmOnCollection}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { Button, IconButton, Tooltip } from '@affine/component';
|
||||
import { Filters } from '@affine/core/components/filter';
|
||||
import type { AllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config';
|
||||
import {
|
||||
AffineShapeIcon,
|
||||
FilterList,
|
||||
filterPageByRules,
|
||||
List,
|
||||
type ListItem,
|
||||
ListScrollContainer,
|
||||
} from '@affine/core/components/page-list';
|
||||
import type { CollectionInfo } from '@affine/core/modules/collection';
|
||||
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import {
|
||||
@@ -19,11 +18,11 @@ import {
|
||||
PageIcon,
|
||||
ToggleRightIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import clsx from 'clsx';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import * as styles from './edit-collection.css';
|
||||
|
||||
@@ -35,8 +34,8 @@ export const RulesMode = ({
|
||||
switchMode,
|
||||
allPageListConfig,
|
||||
}: {
|
||||
collection: Collection;
|
||||
updateCollection: (collection: Collection) => void;
|
||||
collection: CollectionInfo;
|
||||
updateCollection: (collection: CollectionInfo) => void;
|
||||
reset: () => void;
|
||||
buttons: ReactNode;
|
||||
switchMode: ReactNode;
|
||||
@@ -44,30 +43,50 @@ export const RulesMode = ({
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const [showPreview, setShowPreview] = useState(true);
|
||||
const allowListPages: DocMeta[] = [];
|
||||
const rulesPages: DocMeta[] = [];
|
||||
const docsService = useService(DocsService);
|
||||
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
|
||||
const favorites = useLiveData(favAdapter.favorites$);
|
||||
allPageListConfig.allPages.forEach(meta => {
|
||||
if (meta.trash) {
|
||||
return;
|
||||
}
|
||||
const pageData = {
|
||||
meta,
|
||||
publicMode: allPageListConfig.getPublicMode(meta.id),
|
||||
favorite: favorites.some(f => f.id === meta.id),
|
||||
const collectionRulesService = useService(CollectionRulesService);
|
||||
const [rulesPageIds, setRulesPageIds] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = collectionRulesService
|
||||
.watch(
|
||||
collection.rules.filters.length > 0
|
||||
? [
|
||||
...collection.rules.filters,
|
||||
{
|
||||
type: 'system',
|
||||
key: 'trash',
|
||||
method: 'is',
|
||||
value: 'false',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
.subscribe(rules => {
|
||||
setRulesPageIds(rules.groups.flatMap(group => group.items));
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
if (
|
||||
collection.filterList.length &&
|
||||
filterPageByRules(collection.filterList, [], pageData)
|
||||
) {
|
||||
rulesPages.push(meta);
|
||||
}
|
||||
if (collection.allowList.includes(meta.id)) {
|
||||
allowListPages.push(meta);
|
||||
}
|
||||
});
|
||||
}, [collection, collectionRulesService]);
|
||||
|
||||
const rulesPages = useMemo(() => {
|
||||
return allPageListConfig.allPages.filter(meta => {
|
||||
return rulesPageIds.includes(meta.id);
|
||||
});
|
||||
}, [allPageListConfig.allPages, rulesPageIds]);
|
||||
|
||||
const allowListPages = useMemo(() => {
|
||||
return allPageListConfig.allPages.filter(meta => {
|
||||
return (
|
||||
collection.allowList.includes(meta.id) &&
|
||||
!rulesPageIds.includes(meta.id)
|
||||
);
|
||||
});
|
||||
}, [allPageListConfig.allPages, collection.allowList, rulesPageIds]);
|
||||
|
||||
const [expandInclude, setExpandInclude] = useState(
|
||||
collection.allowList.length > 0
|
||||
);
|
||||
@@ -113,13 +132,17 @@ export const RulesMode = ({
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<FilterList
|
||||
propertiesMeta={allPageListConfig.docCollection.meta.properties}
|
||||
value={collection.filterList}
|
||||
onChange={useCallback(
|
||||
filterList => updateCollection({ ...collection, filterList }),
|
||||
[collection, updateCollection]
|
||||
)}
|
||||
<Filters
|
||||
filters={collection.rules.filters}
|
||||
onChange={filters => {
|
||||
updateCollection({
|
||||
...collection,
|
||||
rules: {
|
||||
...collection.rules,
|
||||
filters,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className={styles.rulesContainerLeftContentInclude}>
|
||||
{collection.allowList.length > 0 ? (
|
||||
@@ -215,7 +238,7 @@ export const RulesMode = ({
|
||||
></List>
|
||||
) : (
|
||||
<RulesEmpty
|
||||
noRules={collection.filterList.length === 0}
|
||||
noRules={collection.rules.filters.length === 0}
|
||||
fullHeight={allowListPages.length === 0}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,14 +2,16 @@ import { Modal, toast } from '@affine/component';
|
||||
import {
|
||||
collectionHeaderColsDef,
|
||||
CollectionListItemRenderer,
|
||||
type CollectionMeta,
|
||||
FavoriteTag,
|
||||
type ListItem,
|
||||
ListTableHeader,
|
||||
VirtualizedList,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { SelectorLayout } from '@affine/core/components/page-list/selector/selector-layout';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import {
|
||||
type CollectionMeta,
|
||||
CollectionService,
|
||||
} from '@affine/core/modules/collection';
|
||||
import type { DialogComponentProps } from '@affine/core/modules/dialogs';
|
||||
import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
@@ -52,22 +54,15 @@ export const CollectionSelectorDialog = ({
|
||||
const collectionService = useService(CollectionService);
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
|
||||
const collections = useLiveData(collectionService.collections$);
|
||||
const collections = useLiveData(collectionService.collectionMetas$);
|
||||
const [selection, setSelection] = useState(selectedCollectionIds);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const collectionMetas = useMemo(() => {
|
||||
const collectionsList: CollectionMeta[] = collections
|
||||
.map(collection => {
|
||||
return {
|
||||
...collection,
|
||||
title: collection.name,
|
||||
};
|
||||
})
|
||||
.filter(meta => {
|
||||
const reg = new RegExp(keyword, 'i');
|
||||
return reg.test(meta.title);
|
||||
});
|
||||
const collectionsList: CollectionMeta[] = collections.filter(meta => {
|
||||
const reg = new RegExp(keyword, 'i');
|
||||
return reg.test(meta.title);
|
||||
});
|
||||
return collectionsList;
|
||||
}, [collections, keyword]);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { usePromptModal } from '@affine/component';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import type { CollectionMeta } from '@affine/core/components/page-list';
|
||||
import {
|
||||
CollectionListHeader,
|
||||
createEmptyCollection,
|
||||
VirtualizedCollectionList,
|
||||
} from '@affine/core/components/page-list';
|
||||
import {
|
||||
@@ -13,8 +11,7 @@ import {
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { CollectionService } from '../../../../modules/collection';
|
||||
import { ViewBody, ViewHeader } from '../../../../modules/workbench';
|
||||
@@ -31,16 +28,6 @@ export const AllCollection = () => {
|
||||
const collectionService = useService(CollectionService);
|
||||
const collections = useLiveData(collectionService.collections$);
|
||||
|
||||
const collectionMetas = useMemo(() => {
|
||||
const collectionsList: CollectionMeta[] = collections.map(collection => {
|
||||
return {
|
||||
...collection,
|
||||
title: collection.name,
|
||||
};
|
||||
});
|
||||
return collectionsList;
|
||||
}, [collections]);
|
||||
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const { openPromptModal } = usePromptModal();
|
||||
|
||||
@@ -62,8 +49,7 @@ export const AllCollection = () => {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
const id = nanoid();
|
||||
collectionService.addCollection(createEmptyCollection(id, { name }));
|
||||
const id = collectionService.createCollection({ name });
|
||||
navigateHelper.jumpToCollection(currentWorkspace.id, id);
|
||||
},
|
||||
});
|
||||
@@ -87,10 +73,8 @@ export const AllCollection = () => {
|
||||
</ViewHeader>
|
||||
<ViewBody>
|
||||
<div className={styles.body}>
|
||||
{collectionMetas.length > 0 ? (
|
||||
{collections.size > 0 ? (
|
||||
<VirtualizedCollectionList
|
||||
collections={collections}
|
||||
collectionMetas={collectionMetas}
|
||||
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
|
||||
handleCreateCollection={handleCreateCollection}
|
||||
/>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { filterContainerStyle } from '../../../../components/filter-container.css';
|
||||
import { useNavigateHelper } from '../../../../components/hooks/use-navigate-helper';
|
||||
import {
|
||||
FilterList,
|
||||
SaveAsCollectionButton,
|
||||
} from '../../../../components/page-list';
|
||||
|
||||
export const FilterContainer = ({
|
||||
filters,
|
||||
onChangeFilters,
|
||||
}: {
|
||||
filters: Filter[];
|
||||
onChangeFilters: (filters: Filter[]) => void;
|
||||
}) => {
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const collectionService = useService(CollectionService);
|
||||
const saveToCollection = useCallback(
|
||||
(collection: Collection) => {
|
||||
collectionService.addCollection({
|
||||
...collection,
|
||||
filterList: filters,
|
||||
});
|
||||
navigateHelper.jumpToCollection(currentWorkspace.id, collection.id);
|
||||
},
|
||||
[collectionService, filters, navigateHelper, currentWorkspace.id]
|
||||
);
|
||||
|
||||
if (!filters.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={filterContainerStyle}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<FilterList
|
||||
propertiesMeta={currentWorkspace.docCollection.meta.properties}
|
||||
value={filters}
|
||||
onChange={onChangeFilters}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{filters.length > 0 ? (
|
||||
<SaveAsCollectionButton onConfirm={saveToCollection} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
||||
import {
|
||||
AllPageListOperationsMenu,
|
||||
PageDisplayMenu,
|
||||
PageListNewPageButton,
|
||||
} from '@affine/core/components/page-list';
|
||||
@@ -10,7 +9,6 @@ import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { inferOpenMode } from '@affine/core/utils';
|
||||
import type { Filter } from '@affine/env/filter';
|
||||
import { track } from '@affine/track';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import { useServices } from '@toeverything/infra';
|
||||
@@ -21,12 +19,8 @@ import * as styles from './all-page.css';
|
||||
|
||||
export const AllPageHeader = ({
|
||||
showCreateNew,
|
||||
filters,
|
||||
onChangeFilters,
|
||||
}: {
|
||||
showCreateNew: boolean;
|
||||
filters: Filter[];
|
||||
onChangeFilters: (filters: Filter[]) => void;
|
||||
}) => {
|
||||
const { workspaceService, workspaceDialogService, workbenchService } =
|
||||
useServices({
|
||||
@@ -90,11 +84,6 @@ export const AllPageHeader = ({
|
||||
>
|
||||
<PlusIcon />
|
||||
</PageListNewPageButton>
|
||||
<AllPageListOperationsMenu
|
||||
filterList={filters}
|
||||
onChangeFilterList={onChangeFilters}
|
||||
propertiesMeta={workspace.docCollection.meta.properties}
|
||||
/>
|
||||
<PageDisplayMenu />
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
PageListHeader,
|
||||
useFilteredPageMetas,
|
||||
VirtualizedPageList,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { IntegrationService } from '@affine/core/modules/integration';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { Filter } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
useIsActiveView,
|
||||
@@ -23,7 +21,6 @@ import {
|
||||
import { AllDocSidebarTabs } from '../layouts/all-doc-sidebar-tabs';
|
||||
import { EmptyPageList } from '../page-list-empty';
|
||||
import * as styles from './all-page.css';
|
||||
import { FilterContainer } from './all-page-filter';
|
||||
import { AllPageHeader } from './all-page-header';
|
||||
|
||||
export const AllPage = () => {
|
||||
@@ -37,10 +34,10 @@ export const AllPage = () => {
|
||||
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||
const importing = useLiveData(integrationService.importing$);
|
||||
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
const filteredPageMetas = useFilteredPageMetas(pageMetas, {
|
||||
filters: filters,
|
||||
});
|
||||
const filteredPageMetas = useMemo(
|
||||
() => pageMetas.filter(page => !page.trash),
|
||||
[pageMetas]
|
||||
);
|
||||
|
||||
const isActiveView = useIsActiveView();
|
||||
|
||||
@@ -66,20 +63,14 @@ export const AllPage = () => {
|
||||
<ViewTitle title={t['All pages']()} />
|
||||
<ViewIcon icon="allDocs" />
|
||||
<ViewHeader>
|
||||
<AllPageHeader
|
||||
showCreateNew={!hideHeaderCreateNew}
|
||||
filters={filters}
|
||||
onChangeFilters={setFilters}
|
||||
/>
|
||||
<AllPageHeader showCreateNew={!hideHeaderCreateNew} />
|
||||
</ViewHeader>
|
||||
<ViewBody>
|
||||
<div className={styles.body}>
|
||||
<FilterContainer filters={filters} onChangeFilters={setFilters} />
|
||||
{filteredPageMetas.length > 0 ? (
|
||||
<VirtualizedPageList
|
||||
disableMultiDelete={!isAdmin && !isOwner}
|
||||
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
|
||||
filters={filters}
|
||||
/>
|
||||
) : (
|
||||
<EmptyPageList type="all" heading={<PageListHeader />} />
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { EmptyCollectionDetail } from '@affine/core/components/affine/empty/collection-detail';
|
||||
import { VirtualizedPageList } from '@affine/core/components/page-list';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import {
|
||||
type Collection,
|
||||
CollectionService,
|
||||
} from '@affine/core/modules/collection';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { ViewLayersIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
ViewIcon,
|
||||
ViewTitle,
|
||||
} from '../../../../modules/workbench';
|
||||
import { PageNotFound } from '../../404';
|
||||
import { AllDocSidebarTabs } from '../layouts/all-doc-sidebar-tabs';
|
||||
import { CollectionDetailHeader } from './header';
|
||||
|
||||
@@ -68,30 +70,16 @@ export const Component = function CollectionPage() {
|
||||
GlobalContextService,
|
||||
});
|
||||
const globalContext = globalContextService.globalContext;
|
||||
|
||||
const collections = useLiveData(collectionService.collections$);
|
||||
const navigate = useNavigateHelper();
|
||||
const t = useI18n();
|
||||
const params = useParams();
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
const collection = collections.find(v => v.id === params.collectionId);
|
||||
const collection = useLiveData(
|
||||
params.collectionId
|
||||
? collectionService.collection$(params.collectionId)
|
||||
: null
|
||||
);
|
||||
const name = useLiveData(collection?.name$);
|
||||
const isActiveView = useIsActiveView();
|
||||
|
||||
const notifyCollectionDeleted = useCallback(() => {
|
||||
navigate.jumpToPage(workspace.id, 'all');
|
||||
const collection = collectionService.collectionsTrash$.value.find(
|
||||
v => v.collection.id === params.collectionId
|
||||
);
|
||||
let text = 'Collection does not exist';
|
||||
if (collection) {
|
||||
if (collection.userId) {
|
||||
text = `${collection.collection.name} has been deleted by ${collection.userName}`;
|
||||
} else {
|
||||
text = `${collection.collection.name} has been deleted`;
|
||||
}
|
||||
}
|
||||
return notify.error({ title: text });
|
||||
}, [collectionService, navigate, params.collectionId, workspace.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActiveView && collection) {
|
||||
globalContext.collectionId.set(collection.id);
|
||||
@@ -105,25 +93,22 @@ export const Component = function CollectionPage() {
|
||||
return;
|
||||
}, [collection, globalContext, isActiveView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!collection) {
|
||||
notifyCollectionDeleted();
|
||||
}
|
||||
}, [collection, notifyCollectionDeleted]);
|
||||
const info = useLiveData(collection?.info$);
|
||||
|
||||
if (!collection) {
|
||||
return null;
|
||||
return <PageNotFound />;
|
||||
}
|
||||
const inner = isEmptyCollection(collection) ? (
|
||||
<Placeholder collection={collection} />
|
||||
) : (
|
||||
<CollectionDetail collection={collection} />
|
||||
);
|
||||
const inner =
|
||||
info?.allowList.length === 0 && info?.rules.filters.length === 0 ? (
|
||||
<Placeholder collection={collection} />
|
||||
) : (
|
||||
<CollectionDetail collection={collection} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewIcon icon="collection" />
|
||||
<ViewTitle title={collection.name} />
|
||||
<ViewTitle title={name ?? t['Untitled']()} />
|
||||
<AllDocSidebarTabs />
|
||||
{inner}
|
||||
</>
|
||||
@@ -134,6 +119,7 @@ const Placeholder = ({ collection }: { collection: Collection }) => {
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
const t = useI18n();
|
||||
const name = useLiveData(collection?.name$);
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspace.id);
|
||||
@@ -176,7 +162,7 @@ const Placeholder = ({ collection }: { collection: Collection }) => {
|
||||
['WebkitAppRegion' as string]: 'no-drag',
|
||||
}}
|
||||
>
|
||||
{collection.name}
|
||||
{name ?? t['Untitled']()}
|
||||
</div>
|
||||
<div style={{ flex: 1 }} />
|
||||
</div>
|
||||
@@ -190,9 +176,3 @@ const Placeholder = ({ collection }: { collection: Collection }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const isEmptyCollection = (collection: Collection) => {
|
||||
return (
|
||||
collection.allowList.length === 0 && collection.filterList.length === 0
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
useFilteredPageMetas,
|
||||
VirtualizedTrashList,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { VirtualizedTrashList } from '@affine/core/components/page-list';
|
||||
import { Header } from '@affine/core/components/pure/header';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { DeleteIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect } from 'react';
|
||||
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
useIsActiveView,
|
||||
@@ -43,11 +41,15 @@ export const TrashPage = () => {
|
||||
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
|
||||
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||
const docCollection = currentWorkspace.docCollection;
|
||||
const docsService = useService(DocsService);
|
||||
const allTrashPageIds = useLiveData(
|
||||
LiveData.from(docsService.allTrashDocIds$(), [])
|
||||
);
|
||||
|
||||
const pageMetas = useBlockSuiteDocMeta(docCollection);
|
||||
const filteredPageMetas = useFilteredPageMetas(pageMetas, {
|
||||
trash: true,
|
||||
});
|
||||
const filteredPageMetas = useMemo(() => {
|
||||
return pageMetas.filter(page => allTrashPageIds.includes(page.id));
|
||||
}, [pageMetas, allTrashPageIds]);
|
||||
|
||||
const isActiveView = useIsActiveView();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user