feat: allow collections to be added to favorites (#6288)

This commit is contained in:
pengx17
2024-03-29 04:04:17 +00:00
parent 5490944d04
commit 35af526eb2
9 changed files with 119 additions and 68 deletions
@@ -40,13 +40,12 @@ export const root = style({
paddingLeft: '4px',
paddingRight: '4px',
},
'&[data-type="collection-list-item"][data-collapsible="false"][data-active="true"],&[data-type="reference-page"][data-collapsible="false"][data-active="true"], &[data-type="reference-page"][data-collapsible="false"]:hover, &[data-type="collection-list-item"][data-collapsible="false"]:hover':
{
width: 'calc(100% + 8px)',
transform: 'translateX(-8px)',
paddingLeft: '20px',
paddingRight: '12px',
},
'&[data-collapsible="false"]:is([data-active="true"], :hover)': {
width: 'calc(100% + 8px)',
transform: 'translateX(-8px)',
paddingLeft: '20px',
paddingRight: '12px',
},
[`${linkItemRoot}:first-of-type &`]: {
marginTop: '0px',
},
@@ -1,10 +1,5 @@
import { Button, FlexWrapper, Menu } from '@affine/component';
import type {
Collection,
DeleteCollectionInfo,
Filter,
PropertiesMeta,
} from '@affine/env/filter';
import type { Collection, Filter, PropertiesMeta } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { FilterIcon } from '@blocksuite/icons';
@@ -16,20 +11,14 @@ import type { AllPageListConfig } from './edit-collection/edit-collection';
export const CollectionPageListOperationsMenu = ({
collection,
allPageListConfig,
userInfo,
}: {
collection: Collection;
allPageListConfig: AllPageListConfig;
userInfo: DeleteCollectionInfo;
}) => {
const t = useAFFiNEI18N();
return (
<FlexWrapper alignItems="center">
<CollectionOperations
info={userInfo}
collection={collection}
config={allPageListConfig}
>
<CollectionOperations collection={collection} config={allPageListConfig}>
<Button
className={styles.filterMenuTrigger}
type="default"
@@ -1,16 +1,20 @@
import type { MenuItemProps } from '@affine/component';
import { Menu, MenuIcon, MenuItem } from '@affine/component';
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
import { Workbench } from '@affine/core/modules/workbench';
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
import { FavoriteItemsAdapter } from '@affine/core/modules/workspace';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
DeleteIcon,
EditIcon,
FavoritedIcon,
FavoriteIcon,
FilterIcon,
SplitViewIcon,
} from '@blocksuite/icons';
import { useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import type { PropsWithChildren, ReactElement } from 'react';
import { useCallback, useMemo } from 'react';
@@ -25,15 +29,14 @@ import {
export const CollectionOperations = ({
collection,
config,
info,
openRenameModal,
children,
}: PropsWithChildren<{
info: DeleteCollectionInfo;
collection: Collection;
config: AllPageListConfig;
openRenameModal?: () => void;
}>) => {
const deleteInfo = useDeleteCollectionInfo();
const { appSettings } = useAppSettingHelper();
const service = useService(CollectionService);
const workbench = useService(Workbench);
@@ -76,6 +79,19 @@ export const CollectionOperations = ({
workbench.openCollection(collection.id, { at: 'tail' });
}, [collection.id, workbench]);
const favAdapter = useService(FavoriteItemsAdapter);
const onToggleFavoritePage = useCallback(() => {
favAdapter.toggle(collection.id, 'collection');
}, [favAdapter, collection.id]);
const favorite = useLiveData(
useMemo(
() => favAdapter.isFavorite$(collection.id, 'collection'),
[collection.id, favAdapter]
)
);
const actions = useMemo<
Array<
| {
@@ -109,6 +125,21 @@ export const CollectionOperations = ({
name: t['com.affine.collection.menu.edit'](),
click: showEdit,
},
{
icon: (
<MenuIcon>
{favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)}
</MenuIcon>
),
name: favorite
? t['com.affine.favoritePageOperation.remove']()
: t['com.affine.favoritePageOperation.add'](),
click: onToggleFavoritePage,
},
...(appSettings.enableMultiView
? [
{
@@ -133,7 +164,7 @@ export const CollectionOperations = ({
),
name: t['Delete'](),
click: () => {
service.deleteCollection(info, collection.id);
service.deleteCollection(deleteInfo, collection.id);
},
type: 'danger',
},
@@ -142,10 +173,12 @@ export const CollectionOperations = ({
t,
showEditName,
showEdit,
favorite,
onToggleFavoritePage,
appSettings.enableMultiView,
openCollectionSplitView,
service,
info,
deleteInfo,
collection.id,
]
);
@@ -8,10 +8,10 @@ import {
} from '@affine/core/components/page-list';
import { CollectionService } from '@affine/core/modules/collection';
import { FavoriteItemsAdapter } from '@affine/core/modules/workspace';
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { MoreHorizontalIcon, ViewLayersIcon } from '@blocksuite/icons';
import type { DocCollection, DocMeta } from '@blocksuite/store';
import type { DocCollection } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useService } from '@toeverything/infra';
@@ -27,17 +27,16 @@ import type { CollectionsListProps } from '../index';
import { Page } from './page';
import * as styles from './styles.css';
const CollectionRenderer = ({
export const CollectionSidebarNavItem = ({
collection,
pages,
docCollection,
info,
className,
}: {
collection: Collection;
pages: DocMeta[];
docCollection: DocCollection;
info: DeleteCollectionInfo;
className?: string;
}) => {
const pages = useBlockSuiteDocMeta(docCollection);
const [collapsed, setCollapsed] = useState(true);
const [open, setOpen] = useState(false);
const collectionService = useService(CollectionService);
@@ -112,7 +111,12 @@ const CollectionRenderer = ({
}, []);
return (
<Collapsible.Root open={!collapsed} ref={setNodeRef}>
<Collapsible.Root
open={!collapsed}
ref={setNodeRef}
data-testid="collection-"
className={className}
>
<SidebarMenuLinkItem
data-testid="collection-item"
data-type="collection-list-item"
@@ -127,7 +131,6 @@ const CollectionRenderer = ({
style={{ display: 'flex', alignItems: 'center' }}
>
<CollectionOperations
info={info}
collection={collection}
config={config}
openRenameModal={handleOpen}
@@ -174,12 +177,11 @@ const CollectionRenderer = ({
};
export const CollectionsList = ({
docCollection: workspace,
info,
onCreate,
}: CollectionsListProps) => {
const metas = useBlockSuiteDocMeta(workspace);
const collections = useLiveData(useService(CollectionService).collections$);
const t = useAFFiNEI18N();
if (collections.length === 0) {
return (
<div className={styles.emptyCollectionWrapper}>
@@ -204,11 +206,9 @@ export const CollectionsList = ({
<div data-testid="collections" className={styles.wrapper}>
{collections.map(view => {
return (
<CollectionRenderer
info={info}
<CollectionSidebarNavItem
key={view.id}
collection={view}
pages={metas}
docCollection={workspace}
/>
);
@@ -1,4 +1,5 @@
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { CollectionService } from '@affine/core/modules/collection';
import { FavoriteItemsAdapter } from '@affine/core/modules/workspace';
import type { DocMeta } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
@@ -6,6 +7,7 @@ import { useLiveData, useService } from '@toeverything/infra';
import { useMemo } from 'react';
import { getDropItemId } from '../../../../hooks/affine/use-sidebar-drag';
import { CollectionSidebarNavItem } from '../collections';
import type { FavoriteListProps } from '../index';
import EmptyItem from './empty-item';
import { FavouritePage } from './favourite-page';
@@ -18,18 +20,10 @@ export const FavoriteList = ({
}: FavoriteListProps) => {
const metas = useBlockSuiteDocMeta(workspace);
const favAdapter = useService(FavoriteItemsAdapter);
const collections = useLiveData(useService(CollectionService).collections$);
const dropItemId = getDropItemId('favorites');
const favourites = useLiveData(
favAdapter.favorites$.map(favourites =>
favourites.filter(fav => {
const meta = metas.find(m => m.id === fav.id);
return meta && !meta.trash;
})
)
);
const metaMapping = useMemo(
const docMetaMapping = useMemo(
() =>
metas.reduce(
(acc, meta) => {
@@ -41,6 +35,17 @@ export const FavoriteList = ({
[metas]
);
const favourites = useLiveData(
favAdapter.orderedFavorites$.map(favs => {
return favs.filter(fav => {
if (fav.type === 'doc') {
return !!docMetaMapping[fav.id] && !docMetaMapping[fav.id].trash;
}
return true;
});
})
);
const { setNodeRef, isOver } = useDroppable({
id: dropItemId,
});
@@ -52,17 +57,32 @@ export const FavoriteList = ({
ref={setNodeRef}
data-over={isOver}
>
{favourites.map((pageMeta, index) => {
return (
<FavouritePage
key={`${pageMeta}-${index}`}
metaMapping={metaMapping}
pageId={pageMeta.id}
// memo?
parentIds={emptyPageIdSet}
docCollection={workspace}
/>
);
{favourites.map(item => {
if (item.type === 'collection') {
const collection = collections.find(c => c.id === item.id);
if (collection) {
return (
<CollectionSidebarNavItem
key={item.id}
className={styles.favItemWrapper}
docCollection={workspace}
collection={collection}
/>
);
}
} else if (item.type === 'doc' && !docMetaMapping[item.id].trash) {
return (
<FavouritePage
key={item.id}
metaMapping={docMetaMapping}
pageId={item.id}
// memo?
parentIds={emptyPageIdSet}
docCollection={workspace}
/>
);
}
return null;
})}
{favourites.length === 0 && <EmptyItem />}
</div>
@@ -1,4 +1,3 @@
import type { DeleteCollectionInfo } from '@affine/env/filter';
import type { DocCollection } from '@blocksuite/store';
export type FavoriteListProps = {
@@ -7,6 +6,5 @@ export type FavoriteListProps = {
export type CollectionsListProps = {
docCollection: DocCollection;
info: DeleteCollectionInfo;
onCreate?: () => void;
};
@@ -14,7 +14,6 @@ import type { HTMLAttributes, ReactElement } from 'react';
import { forwardRef, useCallback, useEffect } from 'react';
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
import { useDeleteCollectionInfo } from '../../hooks/affine/use-delete-collection-info';
import { getDropItemId } from '../../hooks/affine/use-sidebar-drag';
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
@@ -163,7 +162,6 @@ export const RootAppSidebar = ({
console.error(err);
});
}, [docCollection.id, collection, navigateHelper, open]);
const userInfo = useDeleteCollectionInfo();
const allPageActive = currentPath === '/all';
@@ -226,7 +224,6 @@ export const RootAppSidebar = ({
</CategoryDivider>
<CollectionsList
docCollection={docCollection}
info={userInfo}
onCreate={handleCreateCollection}
/>
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
@@ -1,3 +1,4 @@
import type { DeleteCollectionInfo } from '@affine/env/filter';
import { useMemo } from 'react';
import { useSession } from './use-current-user';
@@ -5,7 +6,7 @@ import { useSession } from './use-current-user';
export const useDeleteCollectionInfo = () => {
const { user } = useSession();
return useMemo(
return useMemo<DeleteCollectionInfo | null>(
() => (user ? { userName: user.name, userId: user.id } : null),
[user]
);
@@ -168,8 +168,22 @@ export class FavoriteItemsAdapter {
this.getItems().filter(i => i.value)
);
orderedFavorites$ = this.adapter.properties$.map(() => {
const seen = new Set<string>();
return this.sorter.getOrderedItems().filter(item => {
const key = FavoriteItemsAdapter.getFavItemKey(item.id, item.type);
if (seen.has(key) || !item.value) {
return null;
}
seen.add(key);
return item;
});
});
getItems() {
return Object.values(this.adapter.favorites ?? {});
return Object.entries(this.adapter.favorites ?? {})
.filter(([k]) => k.includes(':'))
.map(([, v]) => v);
}
get favorites() {