feat: refactor trash, page would delete from its parent's subpageIds after move to trash (#1871)

This commit is contained in:
Qi
2023-04-12 01:14:57 -05:00
committed by GitHub
parent ba4a2fc9d2
commit af04c1b889
12 changed files with 119 additions and 82 deletions
@@ -2,7 +2,7 @@ import { MenuItem } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { ArrowRightSmallIcon, MoveToIcon } from '@blocksuite/icons'; import { ArrowRightSmallIcon, MoveToIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store'; import type { PageMeta } from '@blocksuite/store';
import { useMemo, useRef, useState } from 'react'; import { useRef, useState } from 'react';
import type { BlockSuiteWorkspace } from '../../../shared'; import type { BlockSuiteWorkspace } from '../../../shared';
import { PinboardMenu } from '../pinboard'; import { PinboardMenu } from '../pinboard';
@@ -47,10 +47,7 @@ export const MoveTo = ({
anchorEl={anchorEl} anchorEl={anchorEl}
open={open} open={open}
placement="left-start" placement="left-start"
metas={useMemo( metas={metas}
() => metas.filter(m => !m.trash && m.id !== currentMeta.id),
[metas, currentMeta]
)}
currentMeta={currentMeta} currentMeta={currentMeta}
blockSuiteWorkspace={blockSuiteWorkspace} blockSuiteWorkspace={blockSuiteWorkspace}
onPinboardClick={onSelect} onPinboardClick={onSelect}
@@ -3,7 +3,7 @@ import { Input, PureMenu, TreeView } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { RemoveIcon, SearchIcon } from '@blocksuite/icons'; import { RemoveIcon, SearchIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store'; import type { PageMeta } from '@blocksuite/store';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { usePageMetaHelper } from '../../../../hooks/use-page-meta'; import { usePageMetaHelper } from '../../../../hooks/use-page-meta';
import { usePinboardData } from '../../../../hooks/use-pinboard-data'; import { usePinboardData } from '../../../../hooks/use-pinboard-data';
@@ -29,13 +29,17 @@ export type PinboardMenuProps = {
} & PureMenuProps; } & PureMenuProps;
export const PinboardMenu = ({ export const PinboardMenu = ({
metas, metas: propsMetas,
currentMeta, currentMeta,
blockSuiteWorkspace, blockSuiteWorkspace,
showRemovePinboard = false, showRemovePinboard = false,
onPinboardClick, onPinboardClick,
...pureMenuProps ...pureMenuProps
}: PinboardMenuProps) => { }: PinboardMenuProps) => {
const metas = useMemo(
() => propsMetas.filter(m => m.id !== currentMeta.id),
[currentMeta.id, propsMetas]
);
const { t } = useTranslation(); const { t } = useTranslation();
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
@@ -10,7 +10,7 @@ import type { PageMeta } from '@blocksuite/store';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { usePageMetaHelper } from '../../../../hooks/use-page-meta'; import { useMetaHelper } from '../../../../hooks/affine/use-meta-helper';
import type { BlockSuiteWorkspace } from '../../../../shared'; import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils'; import { toast } from '../../../../utils';
import { CopyLink, MoveToTrash } from '../../operation-menu-items'; import { CopyLink, MoveToTrash } from '../../operation-menu-items';
@@ -49,7 +49,7 @@ export const OperationButton = ({
const [pinboardMenuOpen, setPinboardMenuOpen] = useState(false); const [pinboardMenuOpen, setPinboardMenuOpen] = useState(false);
const [confirmModalOpen, setConfirmModalOpen] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const menuIndex = useMemo(() => modalIndex + 1, [modalIndex]); const menuIndex = useMemo(() => modalIndex + 1, [modalIndex]);
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { removeToTrash } = useMetaHelper(blockSuiteWorkspace);
return ( return (
<MuiClickAwayListener <MuiClickAwayListener
@@ -151,10 +151,7 @@ export const OperationButton = ({
meta={currentMeta} meta={currentMeta}
onConfirm={() => { onConfirm={() => {
toast(t('Moved to Trash')); toast(t('Moved to Trash'));
setPageMeta(currentMeta.id, { removeToTrash(currentMeta.id);
trash: true,
trashDate: +new Date(),
});
onDelete(); onDelete();
}} }}
onCancel={() => { onCancel={() => {
@@ -9,7 +9,6 @@ import {
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { import {
DeletePermanentlyIcon, DeletePermanentlyIcon,
DeleteTemporarilyIcon,
FavoritedIcon, FavoritedIcon,
FavoriteIcon, FavoriteIcon,
MoreVerticalIcon, MoreVerticalIcon,
@@ -22,7 +21,7 @@ import { useState } from 'react';
import type { BlockSuiteWorkspace } from '../../../../shared'; import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils'; import { toast } from '../../../../utils';
import { MoveTo } from '../../../affine/operation-menu-items'; import { MoveTo, MoveToTrash } from '../../../affine/operation-menu-items';
export type OperationCellProps = { export type OperationCellProps = {
pageMeta: PageMeta; pageMeta: PageMeta;
@@ -30,7 +29,7 @@ export type OperationCellProps = {
blockSuiteWorkspace: BlockSuiteWorkspace; blockSuiteWorkspace: BlockSuiteWorkspace;
onOpenPageInNewTab: (pageId: string) => void; onOpenPageInNewTab: (pageId: string) => void;
onToggleFavoritePage: (pageId: string) => void; onToggleFavoritePage: (pageId: string) => void;
onToggleTrashPage: (pageId: string) => void; onToggleTrashPage: (pageId: string, isTrash: boolean) => void;
}; };
export const OperationCell: React.FC<OperationCellProps> = ({ export const OperationCell: React.FC<OperationCellProps> = ({
@@ -74,15 +73,12 @@ export const OperationCell: React.FC<OperationCellProps> = ({
/> />
)} )}
{!pageMeta.isRootPinboard && ( {!pageMeta.isRootPinboard && (
<MenuItem <MoveToTrash
data-testid="move-to-trash" testId="move-to-trash"
onClick={() => { onItemClick={() => {
setOpen(true); setOpen(true);
}} }}
icon={<DeleteTemporarilyIcon />} />
>
{t('Move to Trash')}
</MenuItem>
)} )}
</> </>
); );
@@ -100,16 +96,11 @@ export const OperationCell: React.FC<OperationCellProps> = ({
</IconButton> </IconButton>
</Menu> </Menu>
</FlexWrapper> </FlexWrapper>
<Confirm <MoveToTrash.ConfirmModal
open={open} open={open}
title={t('Delete page?')} meta={pageMeta}
content={t('will be moved to Trash', {
title: pageMeta.title || 'Untitled',
})}
confirmText={t('Delete')}
confirmType="danger"
onConfirm={() => { onConfirm={() => {
onToggleTrashPage(id); onToggleTrashPage(id, true);
toast(t('Deleted')); toast(t('Deleted'));
setOpen(false); setOpen(false);
}} }}
@@ -19,9 +19,10 @@ import type { PageMeta } from '@blocksuite/store';
import { useMediaQuery, useTheme } from '@mui/material'; import { useMediaQuery, useTheme } from '@mui/material';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import type React from 'react'; import type React from 'react';
import { useCallback, useMemo } from 'react'; import { useMemo } from 'react';
import { workspacePreferredModeAtom } from '../../../../atoms'; import { workspacePreferredModeAtom } from '../../../../atoms';
import { useMetaHelper } from '../../../../hooks/affine/use-meta-helper';
import { import {
usePageMeta, usePageMeta,
usePageMetaHelper, usePageMetaHelper,
@@ -101,6 +102,8 @@ export const PageList: React.FC<PageListProps> = ({
}) => { }) => {
const pageList = usePageMeta(blockSuiteWorkspace); const pageList = usePageMeta(blockSuiteWorkspace);
const helper = usePageMetaHelper(blockSuiteWorkspace); const helper = usePageMetaHelper(blockSuiteWorkspace);
const { removeToTrash, restoreFromTrash } =
useMetaHelper(blockSuiteWorkspace);
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.up('sm')); const matches = useMediaQuery(theme.breakpoints.up('sm'));
@@ -113,20 +116,6 @@ export const PageList: React.FC<PageListProps> = ({
), ),
[pageList, listType] [pageList, listType]
); );
const restorePage = useCallback(
(pageMeta: PageMeta, allMetas: PageMeta[]) => {
helper.setPageMeta(pageMeta.id, {
trash: false,
});
allMetas
.filter(m => pageMeta?.subpageIds.includes(m.id))
.forEach(m => {
restorePage(m, allMetas);
});
},
[helper]
);
if (list.length === 0) { if (list.length === 0) {
return <Empty listType={listType} />; return <Empty listType={listType} />;
} }
@@ -212,7 +201,7 @@ export const PageList: React.FC<PageListProps> = ({
blockSuiteWorkspace.removePage(pageId); blockSuiteWorkspace.removePage(pageId);
}} }}
onRestorePage={() => { onRestorePage={() => {
restorePage(pageMeta, pageList); restoreFromTrash(pageMeta.id);
}} }}
onOpenPage={pageId => { onOpenPage={pageId => {
onClickPage(pageId, false); onClickPage(pageId, false);
@@ -231,11 +220,12 @@ export const PageList: React.FC<PageListProps> = ({
favorite: !pageMeta.favorite, favorite: !pageMeta.favorite,
}); });
}} }}
onToggleTrashPage={() => { onToggleTrashPage={(pageId, isTrash) => {
helper.setPageMeta(pageMeta.id, { if (isTrash) {
trash: !pageMeta.trash, removeToTrash(pageId);
trashDate: +new Date(), } else {
}); restoreFromTrash(pageId);
}
}} }}
/> />
)} )}
@@ -14,6 +14,7 @@ import { useAtom } from 'jotai';
import { useState } from 'react'; import { useState } from 'react';
import { workspacePreferredModeAtom } from '../../../../atoms'; import { workspacePreferredModeAtom } from '../../../../atoms';
import { useMetaHelper } from '../../../../hooks/affine/use-meta-helper';
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id'; import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { import {
@@ -47,7 +48,7 @@ export const EditorOptionMenu = () => {
const { favorite } = pageMeta; const { favorite } = pageMeta;
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false); const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useMetaHelper(blockSuiteWorkspace);
const EditMenu = ( const EditMenu = (
<> <>
<MenuItem <MenuItem
@@ -118,11 +119,8 @@ export const EditorOptionMenu = () => {
open={openConfirm} open={openConfirm}
meta={pageMeta} meta={pageMeta}
onConfirm={() => { onConfirm={() => {
removeToTrash(pageMeta.id);
toast(t('Moved to Trash')); toast(t('Moved to Trash'));
setPageMeta(pageMeta.id, {
trash: true,
trashDate: +new Date(),
});
}} }}
onCancel={() => { onCancel={() => {
setOpenConfirm(false); setOpenConfirm(false);
@@ -4,12 +4,10 @@ import { assertExists } from '@blocksuite/store';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import { useMetaHelper } from '../../../../hooks/affine/use-meta-helper';
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id'; import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { import { usePageMeta } from '../../../../hooks/use-page-meta';
usePageMeta,
usePageMetaHelper,
} from '../../../../hooks/use-page-meta';
export const TrashButtonGroup = () => { export const TrashButtonGroup = () => {
// fixme(himself65): remove these hooks ASAP // fixme(himself65): remove these hooks ASAP
@@ -22,12 +20,12 @@ export const TrashButtonGroup = () => {
meta => meta.id === pageId meta => meta.id === pageId
); );
assertExists(pageMeta); assertExists(pageMeta);
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
// const { restoreFromTrash } = useMetaHelper(blockSuiteWorkspace);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { t } = useTranslation();
return ( return (
<> <>
<Button <Button
@@ -35,7 +33,7 @@ export const TrashButtonGroup = () => {
shape="round" shape="round"
style={{ marginRight: '24px' }} style={{ marginRight: '24px' }}
onClick={() => { onClick={() => {
setPageMeta(pageId, { trash: false }); restoreFromTrash(pageId);
}} }}
> >
{t('Restore it')} {t('Restore it')}
@@ -34,7 +34,7 @@ export const Pinboard = ({
); );
const { data } = usePinboardData({ const { data } = usePinboardData({
metas: allMetas.filter(meta => !meta.trash), metas: allMetas,
pinboardRender: PinboardRender, pinboardRender: PinboardRender,
blockSuiteWorkspace: blockSuiteWorkspace, blockSuiteWorkspace: blockSuiteWorkspace,
onClick: handlePinboardClick, onClick: handlePinboardClick,
@@ -0,0 +1,68 @@
import { useCallback } from 'react';
import type { BlockSuiteWorkspace } from '../../shared';
import { usePageMeta, usePageMetaHelper } from '../use-page-meta';
export function useMetaHelper(blockSuiteWorkspace: BlockSuiteWorkspace | null) {
const { setPageMeta, getPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const metas = usePageMeta(blockSuiteWorkspace);
const removeToTrash = useCallback(
(pageId: string, isRoot = true) => {
const parentMeta = metas.find(m => m.subpageIds?.includes(pageId));
const { subpageIds = [] } = getPageMeta(pageId) ?? {};
subpageIds.forEach(id => {
removeToTrash(id, false);
});
setPageMeta(pageId, {
trash: true,
trashDate: +new Date(),
trashRelate: isRoot ? parentMeta?.id : undefined,
});
// Just the trash root need delete its id from parent
if (parentMeta && isRoot) {
const deleteIndex = parentMeta.subpageIds.findIndex(
id => id === pageId
);
const newSubpageIds = [...parentMeta.subpageIds];
newSubpageIds.splice(deleteIndex, 1);
setPageMeta(parentMeta.id, {
subpageIds: newSubpageIds,
});
}
},
[getPageMeta, metas, setPageMeta]
);
const restoreFromTrash = useCallback(
(pageId: string) => {
const { subpageIds = [], trashRelate } = getPageMeta(pageId) ?? {};
if (trashRelate) {
const parentMeta = metas.find(m => m.id === trashRelate);
parentMeta &&
setPageMeta(parentMeta.id, {
subpageIds: [...parentMeta.subpageIds, pageId],
});
}
setPageMeta(pageId, {
trash: false,
trashDate: undefined,
trashRelate: undefined,
});
subpageIds.forEach(id => {
restoreFromTrash(id);
});
},
[getPageMeta, metas, setPageMeta]
);
return {
removeToTrash,
restoreFromTrash,
};
}
+2
View File
@@ -9,6 +9,8 @@ declare module '@blocksuite/store' {
interface PageMeta { interface PageMeta {
favorite?: boolean; favorite?: boolean;
subpageIds: string[]; subpageIds: string[];
// If a page remove to trash, and it is a subpage, it will remove from its parent `subpageIds`, 'trashRelate' is use for save it parent
trashRelate?: string;
trash?: boolean; trash?: boolean;
trashDate?: number; trashDate?: number;
// whether to create the page with the default template // whether to create the page with the default template
+6 -15
View File
@@ -5,6 +5,7 @@ import { nanoid } from '@blocksuite/store';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { BlockSuiteWorkspace } from '../shared'; import type { BlockSuiteWorkspace } from '../shared';
import { useMetaHelper } from './affine/use-meta-helper';
import { useBlockSuiteWorkspaceHelper } from './use-blocksuite-workspace-helper'; import { useBlockSuiteWorkspaceHelper } from './use-blocksuite-workspace-helper';
import { usePageMetaHelper } from './use-page-meta'; import { usePageMetaHelper } from './use-page-meta';
import type { NodeRenderProps } from './use-pinboard-data'; import type { NodeRenderProps } from './use-pinboard-data';
@@ -32,8 +33,9 @@ export function usePinboardHandler({
onDrop?: TreeViewProps<NodeRenderProps>['onDrop']; onDrop?: TreeViewProps<NodeRenderProps>['onDrop'];
}) { }) {
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const { getPageMeta, setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const { removeToTrash: removeToTrashHelper } =
useMetaHelper(blockSuiteWorkspace);
// Just need handle add operation, delete check is handled in blockSuite's reference link // Just need handle add operation, delete check is handled in blockSuite's reference link
const addReferenceLink = useCallback( const addReferenceLink = useCallback(
(pageId: string, referenceId: string) => { (pageId: string, referenceId: string) => {
@@ -71,21 +73,10 @@ export function usePinboardHandler({
const deletePin = useCallback( const deletePin = useCallback(
(deleteId: string) => { (deleteId: string) => {
const removeToTrash = (currentMeta: PageMeta) => { removeToTrashHelper(deleteId);
const { subpageIds = [] } = currentMeta;
setPageMeta(currentMeta.id, {
trash: true,
trashDate: +new Date(),
});
subpageIds.forEach(id => {
const subCurrentMeta = getPageMeta(id);
subCurrentMeta && removeToTrash(subCurrentMeta);
});
};
removeToTrash(metas.find(m => m.id === deleteId)!);
onDelete?.(deleteId); onDelete?.(deleteId);
}, },
[metas, getPageMeta, onDelete, setPageMeta] [removeToTrashHelper, onDelete]
); );
const dropPin = useCallback( const dropPin = useCallback(
+2 -1
View File
@@ -184,6 +184,7 @@ test.describe('PinBoard interaction', () => {
test('search pinboard', async ({ page }) => { test('search pinboard', async ({ page }) => {
const rootPinboardMeta = await initHomePageWithPinboard(page); const rootPinboardMeta = await initHomePageWithPinboard(page);
await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test1'); await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test1');
await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test2');
const childMeta = (await getMetas(page)).find(m => m.title === 'test1'); const childMeta = (await getMetas(page)).find(m => m.title === 'test1');
await openPinboardPageOperationMenu(page, childMeta?.id ?? ''); await openPinboardPageOperationMenu(page, childMeta?.id ?? '');
@@ -193,7 +194,7 @@ test.describe('PinBoard interaction', () => {
await page.fill('[data-testid="pinboard-menu-search"]', '111'); await page.fill('[data-testid="pinboard-menu-search"]', '111');
expect(await page.locator('[alt="no result"]').count()).toEqual(1); expect(await page.locator('[alt="no result"]').count()).toEqual(1);
await page.fill('[data-testid="pinboard-menu-search"]', 'test1'); await page.fill('[data-testid="pinboard-menu-search"]', 'test2');
expect( expect(
await page.locator('[data-testid="pinboard-search-result"]').count() await page.locator('[data-testid="pinboard-search-result"]').count()
).toEqual(1); ).toEqual(1);