mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
feat: new collections (#4530)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
+2
-2
@@ -70,8 +70,8 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
const handleOpenTrashModal = useCallback(() => {
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageId,
|
||||
pageTitle: pageMeta.title,
|
||||
pageIds: [pageId],
|
||||
pageTitles: [pageMeta.title],
|
||||
});
|
||||
}, [pageId, pageMeta.title, setTrashModal]);
|
||||
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
import { Empty } from '@affine/component';
|
||||
import type { ListData, TrashListData } from '@affine/component/page-list';
|
||||
import { PageList, PageListTrashView } from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { type PageMeta, type Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper';
|
||||
import { useGetPageInfoById } from '../../../hooks/use-get-page-info';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { filterPage } from '../../../utils/filter';
|
||||
import { currentCollectionsAtom } from '../../../utils/user-setting';
|
||||
import { emptyDescButton, emptyDescKbd, pageListEmptyStyle } from './index.css';
|
||||
import { usePageHelper } from './utils';
|
||||
|
||||
export interface BlockSuitePageListProps {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
listType: 'all' | 'trash' | 'shared' | 'public';
|
||||
isPublic?: boolean;
|
||||
onOpenPage: (pageId: string, newTab?: boolean) => void;
|
||||
collection?: Collection;
|
||||
}
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
public: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
|
||||
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
|
||||
return !parentMeta?.trash && pageMeta.trash;
|
||||
},
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
interface PagePreviewInnerProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const PagePreviewInner = ({ workspace, pageId }: PagePreviewInnerProps) => {
|
||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||
assertExists(page);
|
||||
const previewAtom = useBlockSuitePagePreview(page);
|
||||
const preview = useAtomValue(previewAtom);
|
||||
return preview;
|
||||
};
|
||||
|
||||
interface PagePreviewProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const PagePreview = ({ workspace, pageId }: PagePreviewProps) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<PagePreviewInner workspace={workspace} pageId={pageId} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
interface PageListEmptyProps {
|
||||
createPage?: ReturnType<typeof usePageHelper>['createPage'];
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
}
|
||||
|
||||
const PageListEmpty = (props: PageListEmptyProps) => {
|
||||
const { listType, createPage } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const onCreatePage = useCallback(() => {
|
||||
createPage?.();
|
||||
}, [createPage]);
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
const createNewPageButton = (
|
||||
<button className={emptyDescButton} onClick={onCreatePage}>
|
||||
New Page
|
||||
</button>
|
||||
);
|
||||
if (environment.isDesktop) {
|
||||
const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N';
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPagesClient">
|
||||
Click on the {createNewPageButton} button Or press
|
||||
<kbd className={emptyDescKbd}>{{ shortcut } as any}</kbd> to create
|
||||
your first page.
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPages">
|
||||
Click on the
|
||||
{createNewPageButton}
|
||||
button to create your first page.
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
if (listType === 'trash') {
|
||||
return t['emptyTrash']();
|
||||
}
|
||||
if (listType === 'shared') {
|
||||
return t['emptySharedPages']();
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={pageListEmptyStyle}>
|
||||
<Empty
|
||||
title={t['com.affine.emptyDesc']()}
|
||||
description={getEmptyDescription()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuitePageList = ({
|
||||
blockSuiteWorkspace,
|
||||
onOpenPage,
|
||||
listType,
|
||||
isPublic = false,
|
||||
collection,
|
||||
}: BlockSuitePageListProps) => {
|
||||
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const {
|
||||
toggleFavorite,
|
||||
restoreFromTrash,
|
||||
permanentlyDeletePage,
|
||||
cancelPublicPage,
|
||||
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const [filterMode] = useAtom(allPageModeSelectAtom);
|
||||
const { createPage, createEdgeless, importFile, isPreferredEdgeless } =
|
||||
usePageHelper(blockSuiteWorkspace);
|
||||
const t = useAFFiNEI18N();
|
||||
const getPageInfo = useGetPageInfoById(blockSuiteWorkspace);
|
||||
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
|
||||
|
||||
const tagOptionMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(blockSuiteWorkspace.meta.properties.tags?.options ?? []).map(v => [
|
||||
v.id,
|
||||
v,
|
||||
])
|
||||
),
|
||||
[blockSuiteWorkspace.meta.properties.tags?.options]
|
||||
);
|
||||
const list = useMemo(
|
||||
() =>
|
||||
pageMetas
|
||||
.filter(pageMeta => {
|
||||
if (filterMode === 'all') {
|
||||
return true;
|
||||
}
|
||||
if (filterMode === 'edgeless') {
|
||||
return isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
if (filterMode === 'page') {
|
||||
return !isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
console.error('unknown filter mode', pageMeta, filterMode);
|
||||
return true;
|
||||
})
|
||||
.filter(pageMeta => {
|
||||
if (!filter[listType](pageMeta, pageMetas)) {
|
||||
return false;
|
||||
}
|
||||
if (!collection) {
|
||||
return true;
|
||||
}
|
||||
return filterPage(collection, pageMeta);
|
||||
}),
|
||||
[pageMetas, filterMode, isPreferredEdgeless, listType, collection]
|
||||
);
|
||||
|
||||
if (listType === 'trash') {
|
||||
const pageList: TrashListData[] = list.map(pageMeta => {
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PageIcon />
|
||||
),
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
createDate: new Date(pageMeta.createDate),
|
||||
trashDate: pageMeta.trashDate
|
||||
? new Date(pageMeta.trashDate)
|
||||
: undefined,
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(
|
||||
t['com.affine.toastMessage.restored']({
|
||||
title: pageMeta.title || 'Untitled',
|
||||
})
|
||||
);
|
||||
},
|
||||
onPermanentlyDeletePage: () => {
|
||||
permanentlyDeletePage(pageMeta.id);
|
||||
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
||||
},
|
||||
};
|
||||
});
|
||||
return (
|
||||
<PageListTrashView
|
||||
list={pageList}
|
||||
fallback={<PageListEmpty listType={listType} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const pageList: ListData[] = list.map(pageMeta => {
|
||||
const page = blockSuiteWorkspace.getPage(pageMeta.id);
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? <EdgelessIcon /> : <PageIcon />,
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
tags:
|
||||
page?.meta.tags?.map(id => tagOptionMap[id]).filter(v => v != null) ??
|
||||
[],
|
||||
favorite: !!pageMeta.favorite,
|
||||
isPublicPage: !!pageMeta.isPublic,
|
||||
createDate: new Date(pageMeta.createDate),
|
||||
updatedDate: new Date(pageMeta.updatedDate ?? pageMeta.createDate),
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onOpenPageInNewTab: () => onOpenPage(pageMeta.id, true),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
removeToTrash: () =>
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageId: pageMeta.id,
|
||||
pageTitle: pageMeta.title,
|
||||
}),
|
||||
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(
|
||||
t['com.affine.toastMessage.restored']({
|
||||
title: pageMeta.title || 'Untitled',
|
||||
})
|
||||
);
|
||||
},
|
||||
bookmarkPage: () => {
|
||||
const status = pageMeta.favorite;
|
||||
toggleFavorite(pageMeta.id);
|
||||
toast(
|
||||
status
|
||||
? t['com.affine.toastMessage.removedFavorites']()
|
||||
: t['com.affine.toastMessage.addedFavorites']()
|
||||
);
|
||||
},
|
||||
onDisablePublicSharing: () => {
|
||||
cancelPublicPage(pageMeta.id);
|
||||
toast('Successfully disabled', {
|
||||
portal: document.body,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<PageList
|
||||
collectionsAtom={currentCollectionsAtom}
|
||||
propertiesMeta={blockSuiteWorkspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
onCreateNewPage={createPage}
|
||||
onCreateNewEdgeless={createEdgeless}
|
||||
onImportFile={importFile}
|
||||
isPublicWorkspace={isPublic}
|
||||
list={pageList}
|
||||
fallback={<PageListEmpty createPage={createPage} listType={listType} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +1,7 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const filterContainerStyle = style({
|
||||
padding: '12px',
|
||||
padding: '0 16px',
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
'::after': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
background: 'var(--affine-border-color)',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
margin: '0 1px',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -25,17 +25,17 @@ import {
|
||||
} from '@toeverything/infra/command';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
openQuickSearchModalAtom,
|
||||
pageSettingsAtom,
|
||||
recentPageIdsBaseAtom,
|
||||
} from '../../../atoms';
|
||||
import { collectionsCRUDAtom } from '../../../atoms/collections';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../../../shared';
|
||||
import { currentCollectionsAtom } from '../../../utils/user-setting';
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
import type { CMDKCommand, CommandContext } from './types';
|
||||
|
||||
@@ -295,7 +295,7 @@ export const collectionToCommand = (
|
||||
collection: Collection,
|
||||
store: ReturnType<typeof getCurrentStore>,
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>,
|
||||
selectCollection: ReturnType<typeof useCollectionManager>['selectCollection'],
|
||||
selectCollection: (id: string) => void,
|
||||
t: ReturnType<typeof useAFFiNEI18N>
|
||||
): CMDKCommand => {
|
||||
const currentWorkspaceId = store.get(currentWorkspaceIdAtom);
|
||||
@@ -329,14 +329,18 @@ export const collectionToCommand = (
|
||||
|
||||
export const useCollectionsCommands = () => {
|
||||
// todo: considering collections for searching pages
|
||||
const { savedCollections, selectCollection } = useCollectionManager(
|
||||
currentCollectionsAtom
|
||||
);
|
||||
const { savedCollections } = useCollectionManager(collectionsCRUDAtom);
|
||||
const store = getCurrentStore();
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
const navigationHelper = useNavigateHelper();
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const selectCollection = useCallback(
|
||||
(id: string) => {
|
||||
navigationHelper.jumpToCollection(workspace.id, id);
|
||||
},
|
||||
[navigationHelper, workspace.id]
|
||||
);
|
||||
return useMemo(() => {
|
||||
let results: CMDKCommand[] = [];
|
||||
if (query.trim() === '') {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { useIsTinyScreen } from '@toeverything/hooks/use-is-tiny-screen';
|
||||
import clsx from 'clsx';
|
||||
import { type Atom, useAtomValue } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
|
||||
import * as style from './style.css';
|
||||
@@ -14,17 +14,18 @@ import { TopTip } from './top-tip';
|
||||
import { WindowsAppControls } from './windows-app-controls';
|
||||
|
||||
interface HeaderPros {
|
||||
left?: ReactElement;
|
||||
right?: ReactElement;
|
||||
center?: ReactElement;
|
||||
left?: ReactNode;
|
||||
right?: ReactNode;
|
||||
center?: ReactNode;
|
||||
mainContainerAtom: Atom<HTMLDivElement | null>;
|
||||
bottomBorder?: boolean;
|
||||
}
|
||||
|
||||
// The Header component is used to solve the following problems
|
||||
// 1. Manage layout issues independently of page or business logic
|
||||
// 2. Dynamic centered middle element (relative to the main-container), when the middle element is detected to collide with the two elements, the line wrapping process is performed
|
||||
export const Header = forwardRef<HTMLDivElement, HeaderPros>(function Header(
|
||||
{ left, center, right, mainContainerAtom },
|
||||
{ left, center, right, mainContainerAtom, bottomBorder },
|
||||
ref
|
||||
) {
|
||||
const sidebarSwitchRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -51,7 +52,7 @@ export const Header = forwardRef<HTMLDivElement, HeaderPros>(function Header(
|
||||
<>
|
||||
<TopTip />
|
||||
<div
|
||||
className={style.header}
|
||||
className={clsx(style.header, bottomBorder && style.bottomBorder)}
|
||||
// data-has-warning={showWarning}
|
||||
data-open={open}
|
||||
data-sidebar-floating={appSidebarFloating}
|
||||
|
||||
@@ -8,7 +8,6 @@ export const header = style({
|
||||
padding: '0 16px',
|
||||
minHeight: '52px',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
zIndex: 2,
|
||||
selectors: {
|
||||
'&[data-sidebar-floating="false"]': {
|
||||
@@ -25,6 +24,10 @@ export const header = style({
|
||||
},
|
||||
} as ComplexStyleRule);
|
||||
|
||||
export const bottomBorder = style({
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
});
|
||||
|
||||
export const headerItem = style({
|
||||
minHeight: '32px',
|
||||
display: 'flex',
|
||||
|
||||
+28
-37
@@ -1,41 +1,41 @@
|
||||
import {
|
||||
EditCollectionModal,
|
||||
createEmptyCollection,
|
||||
useCollectionManager,
|
||||
useEditCollectionName,
|
||||
} from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
|
||||
import { currentCollectionsAtom } from '../../../../utils/user-setting';
|
||||
import { collectionsCRUDAtom } from '../../../../atoms/collections';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
||||
|
||||
type AddCollectionButtonProps = {
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
export const AddCollectionButton = ({
|
||||
workspace,
|
||||
}: AddCollectionButtonProps) => {
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
export const AddCollectionButton = () => {
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const t = useAFFiNEI18N();
|
||||
const [show, showUpdateCollection] = useState(false);
|
||||
const [defaultCollection, setDefaultCollection] = useState<Collection>();
|
||||
const { node, open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.createCollection'](),
|
||||
showTips: true,
|
||||
});
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const handleClick = useCallback(() => {
|
||||
showUpdateCollection(true);
|
||||
setDefaultCollection({
|
||||
id: nanoid(),
|
||||
name: '',
|
||||
pinned: true,
|
||||
filterList: [],
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}, [showUpdateCollection, workspace.id]);
|
||||
|
||||
open('')
|
||||
.then(name => {
|
||||
const id = nanoid();
|
||||
return setting
|
||||
.createCollection(createEmptyCollection(id, { name }))
|
||||
.then(() => {
|
||||
navigateHelper.jumpToCollection(workspace.id, id);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [navigateHelper, open, setting, workspace.id]);
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
@@ -45,16 +45,7 @@ export const AddCollectionButton = ({
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
|
||||
<EditCollectionModal
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
onConfirm={setting.saveCollection}
|
||||
open={show}
|
||||
onOpenChange={showUpdateCollection}
|
||||
title={t['com.affine.editCollection.saveCollection']()}
|
||||
init={defaultCollection}
|
||||
/>
|
||||
{node}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+48
-177
@@ -1,40 +1,29 @@
|
||||
import { MenuItem as CollectionItem } from '@affine/component/app-sidebar';
|
||||
import { AnimatedCollectionsIcon } from '@affine/component';
|
||||
import {
|
||||
EditCollectionModal,
|
||||
MenuItem as SidebarMenuItem,
|
||||
MenuLinkItem as SidebarMenuLinkItem,
|
||||
} from '@affine/component/app-sidebar';
|
||||
import {
|
||||
CollectionOperations,
|
||||
filterPage,
|
||||
stopPropagation,
|
||||
useCollectionManager,
|
||||
useSavedCollections,
|
||||
} from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { GetPageInfoById } from '@affine/env/page-info';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteIcon,
|
||||
FilterIcon,
|
||||
InformationIcon,
|
||||
MoreHorizontalIcon,
|
||||
UnpinIcon,
|
||||
ViewLayersIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { InformationIcon, MoreHorizontalIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import {
|
||||
Menu,
|
||||
MenuIcon,
|
||||
MenuItem,
|
||||
type MenuItemProps,
|
||||
} from '@toeverything/components/menu';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
|
||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
||||
import { filterPage } from '../../../../utils/filter';
|
||||
import { currentCollectionsAtom } from '../../../../utils/user-setting';
|
||||
import { collectionsCRUDAtom } from '../../../../atoms/collections';
|
||||
import { useAllPageListConfig } from '../../../../hooks/affine/use-all-page-list-config';
|
||||
import type { CollectionsListProps } from '../index';
|
||||
import { Page } from './page';
|
||||
import * as styles from './styles.css';
|
||||
@@ -51,111 +40,20 @@ export const processCollectionsDrag = (e: DragEndEvent) => {
|
||||
e.over?.data.current?.addToCollection?.(e.active.data.current?.pageId);
|
||||
}
|
||||
};
|
||||
const CollectionOperations = ({
|
||||
view,
|
||||
showUpdateCollection,
|
||||
setting,
|
||||
}: {
|
||||
view: Collection;
|
||||
showUpdateCollection: () => void;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const actions = useMemo<
|
||||
Array<
|
||||
| {
|
||||
icon: ReactElement;
|
||||
name: string;
|
||||
click: () => void;
|
||||
type?: MenuItemProps['type'];
|
||||
element?: undefined;
|
||||
}
|
||||
| {
|
||||
element: ReactElement;
|
||||
}
|
||||
>
|
||||
>(
|
||||
() => [
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<FilterIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Edit Filter'](),
|
||||
click: showUpdateCollection,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<UnpinIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Unpin'](),
|
||||
click: () => {
|
||||
return setting.updateCollection({
|
||||
...view,
|
||||
pinned: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
element: <div key="divider" className={styles.menuDividerStyle}></div>,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<DeleteIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Delete'](),
|
||||
click: () => {
|
||||
return setting.deleteCollection(view.id);
|
||||
},
|
||||
type: 'danger',
|
||||
},
|
||||
],
|
||||
[setting, showUpdateCollection, t, view]
|
||||
);
|
||||
return (
|
||||
<div style={{ minWidth: 150 }}>
|
||||
{actions.map(action => {
|
||||
if (action.element) {
|
||||
return action.element;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
data-testid="collection-option"
|
||||
key={action.name}
|
||||
type={action.type}
|
||||
preFix={action.icon}
|
||||
onClick={action.click}
|
||||
>
|
||||
{action.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionRenderer = ({
|
||||
collection,
|
||||
pages,
|
||||
workspace,
|
||||
getPageInfo,
|
||||
info,
|
||||
}: {
|
||||
collection: Collection;
|
||||
pages: PageMeta[];
|
||||
workspace: Workspace;
|
||||
getPageInfo: GetPageInfoById;
|
||||
info: DeleteCollectionInfo;
|
||||
}) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
const clickCollection = useCallback(() => {
|
||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
setting.selectCollection(collection.id);
|
||||
}, [jumpToSubPath, workspace.id, setting, collection.id]);
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: `${Collections_DROP_AREA_PREFIX}${collection.id}`,
|
||||
data: {
|
||||
@@ -166,19 +64,15 @@ const CollectionRenderer = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
const config = useAllPageListConfig();
|
||||
const allPagesMeta = useMemo(
|
||||
() => Object.fromEntries(pages.map(v => [v.id, v])),
|
||||
[pages]
|
||||
);
|
||||
const [show, showUpdateCollection] = useState(false);
|
||||
const allowList = useMemo(
|
||||
() => new Set(collection.allowList),
|
||||
[collection.allowList]
|
||||
);
|
||||
const excludeList = useMemo(
|
||||
() => new Set(collection.excludeList),
|
||||
[collection.excludeList]
|
||||
);
|
||||
const removeFromAllowList = useCallback(
|
||||
(id: string) => {
|
||||
return setting.updateCollection({
|
||||
@@ -188,57 +82,41 @@ const CollectionRenderer = ({
|
||||
},
|
||||
[collection, setting]
|
||||
);
|
||||
const addToExcludeList = useCallback(
|
||||
(id: string) => {
|
||||
return setting.updateCollection({
|
||||
...collection,
|
||||
excludeList: [id, ...(collection.excludeList ?? [])],
|
||||
});
|
||||
},
|
||||
[collection, setting]
|
||||
);
|
||||
const pagesToRender = pages.filter(
|
||||
page => filterPage(collection, page) && !page.trash
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname.split('?')[0];
|
||||
const path = `/workspace/${workspace.id}/collection/${collection.id}`;
|
||||
return (
|
||||
<Collapsible.Root open={!collapsed}>
|
||||
<EditCollectionModal
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
init={collection}
|
||||
onConfirm={setting.saveCollection}
|
||||
open={show}
|
||||
onOpenChange={showUpdateCollection}
|
||||
/>
|
||||
<CollectionItem
|
||||
<SidebarMenuLinkItem
|
||||
data-testid="collection-item"
|
||||
data-type="collection-list-item"
|
||||
ref={setNodeRef}
|
||||
onCollapsedChange={setCollapsed}
|
||||
active={isOver}
|
||||
icon={<ViewLayersIcon />}
|
||||
active={isOver || currentPath === path}
|
||||
icon={<AnimatedCollectionsIcon closed={isOver} />}
|
||||
to={path}
|
||||
postfix={
|
||||
<Menu
|
||||
items={
|
||||
<CollectionOperations
|
||||
view={collection}
|
||||
showUpdateCollection={() => showUpdateCollection(true)}
|
||||
setting={setting}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="collection-options"
|
||||
type="plain"
|
||||
withoutHoverStyle
|
||||
<div onClick={stopPropagation}>
|
||||
<CollectionOperations
|
||||
info={info}
|
||||
collection={collection}
|
||||
setting={setting}
|
||||
config={config}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
<IconButton
|
||||
data-testid="collection-options"
|
||||
type="plain"
|
||||
withoutHoverStyle
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</CollectionOperations>
|
||||
</div>
|
||||
}
|
||||
collapsed={pagesToRender.length > 0 ? collapsed : undefined}
|
||||
onClick={clickCollection}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -249,7 +127,7 @@ const CollectionRenderer = ({
|
||||
>
|
||||
<div>{collection.name}</div>
|
||||
</div>
|
||||
</CollectionItem>
|
||||
</SidebarMenuLinkItem>
|
||||
<Collapsible.Content className={styles.collapsibleContent}>
|
||||
<div style={{ marginLeft: 20, marginTop: -4 }}>
|
||||
{pagesToRender.map(page => {
|
||||
@@ -257,8 +135,6 @@ const CollectionRenderer = ({
|
||||
<Page
|
||||
inAllowList={allowList.has(page.id)}
|
||||
removeFromAllowList={removeFromAllowList}
|
||||
inExcludeList={excludeList.has(page.id)}
|
||||
addToExcludeList={addToExcludeList}
|
||||
allPageMeta={allPagesMeta}
|
||||
page={page}
|
||||
key={page.id}
|
||||
@@ -271,32 +147,27 @@ const CollectionRenderer = ({
|
||||
</Collapsible.Root>
|
||||
);
|
||||
};
|
||||
export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
||||
export const CollectionsList = ({ workspace, info }: CollectionsListProps) => {
|
||||
const metas = useBlockSuitePageMeta(workspace);
|
||||
const { savedCollections } = useSavedCollections(currentCollectionsAtom);
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const pinedCollections = useMemo(
|
||||
() => savedCollections.filter(v => v.pinned),
|
||||
[savedCollections]
|
||||
);
|
||||
const { collections } = useSavedCollections(collectionsCRUDAtom);
|
||||
const t = useAFFiNEI18N();
|
||||
if (pinedCollections.length === 0) {
|
||||
if (collections.length === 0) {
|
||||
return (
|
||||
<CollectionItem
|
||||
<SidebarMenuItem
|
||||
data-testid="slider-bar-collection-null-description"
|
||||
icon={<InformationIcon />}
|
||||
disabled
|
||||
>
|
||||
<span>{t['Create a collection']()}</span>
|
||||
</CollectionItem>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div data-testid="collections" className={styles.wrapper}>
|
||||
{pinedCollections.map(view => {
|
||||
{collections.map(view => {
|
||||
return (
|
||||
<CollectionRenderer
|
||||
getPageInfo={getPageInfo}
|
||||
info={info}
|
||||
key={view.id}
|
||||
collection={view}
|
||||
pages={metas}
|
||||
|
||||
+5
-35
@@ -4,7 +4,6 @@ import {
|
||||
DeleteIcon,
|
||||
EdgelessIcon,
|
||||
FilterMinusIcon,
|
||||
FilterUndoIcon,
|
||||
MoreHorizontalIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
@@ -32,25 +31,21 @@ import * as styles from './styles.css';
|
||||
export const PageOperations = ({
|
||||
page,
|
||||
inAllowList,
|
||||
addToExcludeList,
|
||||
removeFromAllowList,
|
||||
inExcludeList,
|
||||
workspace,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
page: PageMeta;
|
||||
inAllowList: boolean;
|
||||
removeFromAllowList: (id: string) => void;
|
||||
inExcludeList: boolean;
|
||||
addToExcludeList: (id: string) => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { setTrashModal } = useTrashModalHelper(workspace);
|
||||
const onClickDelete = useCallback(() => {
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageId: page.id,
|
||||
pageTitle: page.title,
|
||||
pageIds: [page.id],
|
||||
pageTitles: [page.title],
|
||||
});
|
||||
}, [page.id, page.title, setTrashModal]);
|
||||
const actions = useMemo<
|
||||
@@ -79,24 +74,13 @@ export const PageOperations = ({
|
||||
name: t['Remove special filter'](),
|
||||
click: () => removeFromAllowList(page.id),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!inExcludeList
|
||||
? [
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<FilterUndoIcon />
|
||||
</MenuIcon>
|
||||
element: (
|
||||
<div key="divider" className={styles.menuDividerStyle}></div>
|
||||
),
|
||||
name: t['Exclude from filter'](),
|
||||
click: () => addToExcludeList(page.id),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
element: <div key="divider" className={styles.menuDividerStyle}></div>,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
@@ -108,15 +92,7 @@ export const PageOperations = ({
|
||||
type: 'danger',
|
||||
},
|
||||
],
|
||||
[
|
||||
inAllowList,
|
||||
t,
|
||||
inExcludeList,
|
||||
onClickDelete,
|
||||
removeFromAllowList,
|
||||
page.id,
|
||||
addToExcludeList,
|
||||
]
|
||||
[inAllowList, t, onClickDelete, removeFromAllowList, page.id]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
@@ -144,15 +120,11 @@ export const Page = ({
|
||||
workspace,
|
||||
allPageMeta,
|
||||
inAllowList,
|
||||
inExcludeList,
|
||||
removeFromAllowList,
|
||||
addToExcludeList,
|
||||
}: {
|
||||
page: PageMeta;
|
||||
inAllowList: boolean;
|
||||
removeFromAllowList: (id: string) => void;
|
||||
inExcludeList: boolean;
|
||||
addToExcludeList: (id: string) => void;
|
||||
workspace: Workspace;
|
||||
allPageMeta: Record<string, PageMeta>;
|
||||
}) => {
|
||||
@@ -186,8 +158,6 @@ export const Page = ({
|
||||
<PageOperations
|
||||
inAllowList={inAllowList}
|
||||
removeFromAllowList={removeFromAllowList}
|
||||
inExcludeList={inExcludeList}
|
||||
addToExcludeList={addToExcludeList}
|
||||
page={page}
|
||||
workspace={workspace}
|
||||
/>
|
||||
|
||||
+3
@@ -1,6 +1,9 @@
|
||||
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
|
||||
|
||||
export const wrapper = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
userSelect: 'none',
|
||||
// marginLeft:8,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
|
||||
export type FavoriteListProps = {
|
||||
@@ -6,4 +7,5 @@ export type FavoriteListProps = {
|
||||
|
||||
export type CollectionsListProps = {
|
||||
workspace: Workspace;
|
||||
info: DeleteCollectionInfo;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AnimatedDeleteIcon } from '@affine/component';
|
||||
import {
|
||||
AddPageButton,
|
||||
AppSidebar,
|
||||
@@ -10,13 +11,10 @@ import {
|
||||
SidebarContainer,
|
||||
SidebarScrollableContainer,
|
||||
} from '@affine/component/app-sidebar';
|
||||
import { MoveToTrash, useCollectionManager } from '@affine/component/page-list';
|
||||
import { MoveToTrash } from '@affine/component/page-list';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteTemporarilyIcon,
|
||||
FolderIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { FolderIcon, SettingsIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { Menu } from '@toeverything/components/menu';
|
||||
@@ -27,11 +25,12 @@ import { forwardRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import { openWorkspaceListModalAtom } from '../../atoms';
|
||||
import { useHistoryAtom } from '../../atoms/history';
|
||||
import { useAppSettingHelper } from '../../hooks/affine/use-app-setting-helper';
|
||||
import { useDeleteCollectionInfo } from '../../hooks/affine/use-delete-collection-info';
|
||||
import { useGeneralShortcuts } from '../../hooks/affine/use-shortcuts';
|
||||
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../hooks/use-shortcut-commands';
|
||||
import type { AllWorkspace } from '../../shared';
|
||||
import { currentCollectionsAtom } from '../../utils/user-setting';
|
||||
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||
import { AddCollectionButton } from '../pure/workspace-slider-bar/collections/add-collection-button';
|
||||
import { AddFavouriteButton } from '../pure/workspace-slider-bar/favorite/add-favourite-button';
|
||||
@@ -101,7 +100,6 @@ export const RootAppSidebar = ({
|
||||
}: RootAppSidebarProps): ReactElement => {
|
||||
const currentWorkspaceId = currentWorkspace.id;
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { backToAll } = useCollectionManager(currentCollectionsAtom);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
const [openUserWorkspaceList, setOpenUserWorkspaceList] = useAtom(
|
||||
@@ -117,7 +115,7 @@ export const RootAppSidebar = ({
|
||||
|
||||
const { trashModal, setTrashModal, handleOnConfirm } =
|
||||
useTrashModalHelper(blockSuiteWorkspace);
|
||||
const deletePageTitle = trashModal.pageTitle;
|
||||
const deletePageTitles = trashModal.pageTitles;
|
||||
const trashConfirmOpen = trashModal.open;
|
||||
const onTrashConfirmOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
@@ -129,6 +127,10 @@ export const RootAppSidebar = ({
|
||||
[trashModal, setTrashModal]
|
||||
);
|
||||
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const backToAll = useCallback(() => {
|
||||
navigateHelper.jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
|
||||
}, [currentWorkspace.id, navigateHelper]);
|
||||
// Listen to the "New Page" action from the menu
|
||||
useEffect(() => {
|
||||
if (environment.isDesktop) {
|
||||
@@ -166,6 +168,7 @@ export const RootAppSidebar = ({
|
||||
setOpenUserWorkspaceList(false);
|
||||
}, [setOpenUserWorkspaceList]);
|
||||
useRegisterBlocksuiteEditorCommands(router.back, router.forward);
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
return (
|
||||
<>
|
||||
<AppSidebar
|
||||
@@ -183,7 +186,7 @@ export const RootAppSidebar = ({
|
||||
open={trashConfirmOpen}
|
||||
onConfirm={handleOnConfirm}
|
||||
onOpenChange={onTrashConfirmOpenChange}
|
||||
title={deletePageTitle}
|
||||
titles={deletePageTitles}
|
||||
/>
|
||||
<SidebarContainer>
|
||||
<Menu
|
||||
@@ -243,16 +246,16 @@ export const RootAppSidebar = ({
|
||||
</CategoryDivider>
|
||||
<FavoriteList workspace={blockSuiteWorkspace} />
|
||||
<CategoryDivider label={t['com.affine.rootAppSidebar.collections']()}>
|
||||
<AddCollectionButton workspace={blockSuiteWorkspace} />
|
||||
<AddCollectionButton />
|
||||
</CategoryDivider>
|
||||
<CollectionsList workspace={blockSuiteWorkspace} />
|
||||
<CollectionsList workspace={blockSuiteWorkspace} info={userInfo} />
|
||||
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
|
||||
{/* fixme: remove the following spacer */}
|
||||
<div style={{ height: '4px' }} />
|
||||
<RouteMenuLinkItem
|
||||
ref={trashDroppable.setNodeRef}
|
||||
isDraggedOver={trashDroppable.isOver}
|
||||
icon={<DeleteTemporarilyIcon />}
|
||||
icon={<AnimatedDeleteIcon closed={trashDroppable.isOver} />}
|
||||
currentPath={currentPath}
|
||||
path={paths.trash(currentWorkspaceId)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const trashTitle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: '0 8px',
|
||||
fontWeight: 600,
|
||||
});
|
||||
|
||||
export const trashIcon = style({
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: 'var(--affine-font-h-5)',
|
||||
});
|
||||
@@ -1,43 +1,50 @@
|
||||
import {
|
||||
CollectionList,
|
||||
FilterList,
|
||||
SaveCollectionButton,
|
||||
SaveAsCollectionButton,
|
||||
useCollectionManager,
|
||||
} from '@affine/component/page-list';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { PropertiesMeta } from '@affine/env/filter';
|
||||
import type {
|
||||
WorkspaceFlavour,
|
||||
WorkspaceHeaderProps,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DeleteIcon } from '@blocksuite/icons';
|
||||
import { useSetAtom } from 'jotai/react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { collectionsCRUDAtom } from '../atoms/collections';
|
||||
import { appHeaderAtom, mainContainerAtom } from '../atoms/element';
|
||||
import { useGetPageInfoById } from '../hooks/use-get-page-info';
|
||||
import { useAllPageListConfig } from '../hooks/affine/use-all-page-list-config';
|
||||
import { useDeleteCollectionInfo } from '../hooks/affine/use-delete-collection-info';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { useWorkspace } from '../hooks/use-workspace';
|
||||
import { currentCollectionsAtom } from '../utils/user-setting';
|
||||
import { SharePageModal } from './affine/share-page-modal';
|
||||
import { BlockSuiteHeaderTitle } from './blocksuite/block-suite-header-title';
|
||||
import { filterContainerStyle } from './filter-container.css';
|
||||
import { Header } from './pure/header';
|
||||
import { PluginHeader } from './pure/plugin-header';
|
||||
import { WorkspaceModeFilterTab } from './pure/workspace-mode-filter-tab';
|
||||
import * as styles from './workspace-header.css';
|
||||
|
||||
const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||
const currentWorkspace = useWorkspace(workspaceId);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const saveToCollection = useCallback(
|
||||
async (collection: Collection) => {
|
||||
await setting.saveCollection(collection);
|
||||
setting.selectCollection(collection.id);
|
||||
console.log(setting.currentCollection.filterList);
|
||||
await setting.createCollection({
|
||||
...collection,
|
||||
mode: 'rule',
|
||||
filterList: setting.currentCollection.filterList,
|
||||
});
|
||||
navigateHelper.jumpToCollection(workspaceId, collection.id);
|
||||
},
|
||||
[setting]
|
||||
);
|
||||
const getPageInfoById = useGetPageInfoById(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
[setting, navigateHelper, workspaceId]
|
||||
);
|
||||
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
|
||||
return null;
|
||||
@@ -59,16 +66,9 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||
</div>
|
||||
<div>
|
||||
{setting.currentCollection.filterList.length > 0 ? (
|
||||
<SaveCollectionButton
|
||||
propertiesMeta={
|
||||
currentWorkspace.blockSuiteWorkspace.meta
|
||||
.properties as PropertiesMeta
|
||||
}
|
||||
getPageInfo={getPageInfoById}
|
||||
<SaveAsCollectionButton
|
||||
onConfirm={saveToCollection}
|
||||
filterList={setting.currentCollection.filterList}
|
||||
workspaceId={workspaceId}
|
||||
></SaveCollectionButton>
|
||||
></SaveAsCollectionButton>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,14 +78,17 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
|
||||
export function WorkspaceHeader({
|
||||
currentWorkspaceId,
|
||||
currentEntry,
|
||||
rightSlot,
|
||||
}: WorkspaceHeaderProps<WorkspaceFlavour>) {
|
||||
const setAppHeader = useSetAtom(appHeaderAtom);
|
||||
|
||||
const currentWorkspace = useWorkspace(currentWorkspaceId);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
const getPageInfoById = useGetPageInfoById(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
const workspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const config = useAllPageListConfig();
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
// route in all page
|
||||
if (
|
||||
@@ -99,25 +102,24 @@ export function WorkspaceHeader({
|
||||
ref={setAppHeader}
|
||||
left={
|
||||
<CollectionList
|
||||
userInfo={userInfo}
|
||||
allPageListConfig={config}
|
||||
setting={setting}
|
||||
getPageInfo={getPageInfoById}
|
||||
propertiesMeta={
|
||||
currentWorkspace.blockSuiteWorkspace.meta.properties
|
||||
}
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
/>
|
||||
}
|
||||
right={rightSlot}
|
||||
center={<WorkspaceModeFilterTab />}
|
||||
/>
|
||||
{<FilterContainer workspaceId={currentWorkspaceId} />}
|
||||
<FilterContainer workspaceId={currentWorkspaceId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// route in shared or trash
|
||||
// route in shared
|
||||
if (
|
||||
'subPath' in currentEntry &&
|
||||
(currentEntry.subPath === WorkspaceSubPath.SHARED ||
|
||||
currentEntry.subPath === WorkspaceSubPath.TRASH)
|
||||
currentEntry.subPath === WorkspaceSubPath.SHARED
|
||||
) {
|
||||
return (
|
||||
<Header
|
||||
@@ -128,11 +130,28 @@ export function WorkspaceHeader({
|
||||
);
|
||||
}
|
||||
|
||||
// route in trash
|
||||
if (
|
||||
'subPath' in currentEntry &&
|
||||
currentEntry.subPath === WorkspaceSubPath.TRASH
|
||||
) {
|
||||
return (
|
||||
<Header
|
||||
mainContainerAtom={mainContainerAtom}
|
||||
ref={setAppHeader}
|
||||
left={
|
||||
<div className={styles.trashTitle}>
|
||||
<DeleteIcon className={styles.trashIcon} />
|
||||
{t['com.affine.workspaceSubPath.trash']()}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// route in edit page
|
||||
if ('pageId' in currentEntry) {
|
||||
const currentPage = currentWorkspace.blockSuiteWorkspace.getPage(
|
||||
currentEntry.pageId
|
||||
);
|
||||
const currentPage = workspace.getPage(currentEntry.pageId);
|
||||
const sharePageModal = currentPage ? (
|
||||
<SharePageModal workspace={currentWorkspace} page={currentPage} />
|
||||
) : null;
|
||||
@@ -152,6 +171,7 @@ export function WorkspaceHeader({
|
||||
<PluginHeader />
|
||||
</div>
|
||||
}
|
||||
bottomBorder
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user