refactor: clean all pages component (#2176)

Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
Whitewater
2023-05-04 20:59:16 -07:00
committed by GitHub
parent 2c49c774af
commit 84b36c1d35
32 changed files with 772 additions and 504 deletions

View File

@@ -1,31 +0,0 @@
import { MenuItem } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CopyIcon } from '@blocksuite/icons';
import { useCallback } from 'react';
//
import { toast } from '../../../utils';
import type { CommonMenuItemProps } from './types';
export const CopyLink = ({ onItemClick, onSelect }: CommonMenuItemProps) => {
const t = useAFFiNEI18N();
const copyUrl = useCallback(() => {
navigator.clipboard.writeText(window.location.href);
toast(t['Copied link to clipboard']());
}, [t]);
return (
<MenuItem
data-testid="copy-link"
onClick={() => {
copyUrl();
onItemClick?.();
onSelect?.();
}}
icon={<CopyIcon />}
>
{t['Copy Link']()}
</MenuItem>
);
};

View File

@@ -1,58 +0,0 @@
import { MenuItem, styled } from '@affine/component';
import type { PublicLinkDisableProps } from '@affine/component/share-menu';
import { PublicLinkDisableModal } from '@affine/component/share-menu';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ShareIcon } from '@blocksuite/icons';
import type { CommonMenuItemProps } from './types';
const StyledMenuItem = styled(MenuItem)(({ theme }) => {
return {
div: {
color: theme.palette.error.main,
svg: {
color: theme.palette.error.main,
},
},
':hover': {
div: {
color: theme.palette.error.main,
svg: {
color: theme.palette.error.main,
},
},
},
};
});
export const DisablePublicSharing = ({
onSelect,
onItemClick,
testId,
}: CommonMenuItemProps) => {
const t = useAFFiNEI18N();
return (
<>
<StyledMenuItem
data-testid={testId}
onClick={() => {
onItemClick?.();
onSelect?.();
}}
style={{ color: 'red' }}
icon={<ShareIcon />}
>
{t['Disable Public Sharing']()}
</StyledMenuItem>
</>
);
};
const DisablePublicSharingModal = ({
page,
open,
onClose,
}: PublicLinkDisableProps) => {
return <PublicLinkDisableModal page={page} open={open} onClose={onClose} />;
};
DisablePublicSharing.DisablePublicSharingModal = DisablePublicSharingModal;

View File

@@ -1,73 +0,0 @@
import { Menu, MenuItem } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContentParser } from '@blocksuite/blocks/content-parser';
import {
ArrowRightSmallIcon,
ExportIcon,
ExportToHtmlIcon,
ExportToMarkdownIcon,
} from '@blocksuite/icons';
import { useRef } from 'react';
import type { CommonMenuItemProps } from './types';
export const Export = ({
onSelect,
onItemClick,
}: CommonMenuItemProps<{ type: 'markdown' | 'html' }>) => {
const t = useAFFiNEI18N();
const contentParserRef = useRef<ContentParser>();
return (
<Menu
width={248}
placement="left"
trigger="click"
content={
<>
<MenuItem
data-testid="export-to-html"
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.onExportHtml();
onSelect?.({ type: 'html' });
}}
icon={<ExportToHtmlIcon />}
>
{t['Export to HTML']()}
</MenuItem>
<MenuItem
data-testid="export-to-markdown"
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.onExportMarkdown();
onSelect?.({ type: 'markdown' });
}}
icon={<ExportToMarkdownIcon />}
>
{t['Export to Markdown']()}
</MenuItem>
</>
}
>
<MenuItem
data-testid="export-menu"
icon={<ExportIcon />}
endIcon={<ArrowRightSmallIcon />}
onClick={e => {
e.stopPropagation();
onItemClick?.();
}}
>
{t.Export()}
</MenuItem>
</Menu>
);
};

View File

@@ -1,60 +0,0 @@
import { MenuItem } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon, MoveToIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useRef, useState } from 'react';
import type { BlockSuiteWorkspace } from '../../../shared';
import { PinboardMenu } from '../pinboard';
import type { CommonMenuItemProps } from './types';
export type MoveToProps = CommonMenuItemProps<{
dragId: string;
dropId: string;
}> & {
metas: PageMeta[];
currentMeta: PageMeta;
blockSuiteWorkspace: BlockSuiteWorkspace;
};
/**
* @deprecated
*/
export const MoveTo = ({
metas,
currentMeta,
blockSuiteWorkspace,
onSelect,
onItemClick,
}: MoveToProps) => {
const t = useAFFiNEI18N();
const ref = useRef<HTMLButtonElement>(null);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const open = anchorEl !== null;
return (
<>
<MenuItem
ref={ref}
onClick={e => {
e.stopPropagation();
setAnchorEl(ref.current);
onItemClick?.();
}}
icon={<MoveToIcon />}
endIcon={<ArrowRightSmallIcon />}
data-testid="move-to-menu-item"
>
{t['Move to']()}
</MenuItem>
<PinboardMenu
anchorEl={anchorEl}
open={open}
placement="left"
metas={metas}
currentMeta={currentMeta}
blockSuiteWorkspace={blockSuiteWorkspace}
onPinboardClick={onSelect}
/>
</>
);
};

View File

@@ -1,53 +0,0 @@
import type { ConfirmProps } from '@affine/component';
import { Confirm, MenuItem } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DeleteTemporarilyIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import type { CommonMenuItemProps } from './types';
export const MoveToTrash = ({
onSelect,
onItemClick,
testId,
}: CommonMenuItemProps) => {
const t = useAFFiNEI18N();
return (
<>
<MenuItem
data-testid={testId}
onClick={() => {
onItemClick?.();
onSelect?.();
}}
icon={<DeleteTemporarilyIcon />}
>
{t['Move to Trash']()}
</MenuItem>
</>
);
};
const ConfirmModal = ({
meta,
...confirmModalProps
}: {
meta: PageMeta;
} & ConfirmProps) => {
const t = useAFFiNEI18N();
return (
<Confirm
title={t['Delete page?']()}
content={t['will be moved to Trash']({
title: meta.title || 'Untitled',
})}
confirmText={t.Delete()}
confirmType="danger"
{...confirmModalProps}
/>
);
};
MoveToTrash.ConfirmModal = ConfirmModal;

View File

@@ -1,5 +0,0 @@
export * from './CopyLink';
export * from './DisablePublicSharing';
export * from './Export';
export * from './MoveTo';
export * from './MoveToTrash';

View File

@@ -1,7 +0,0 @@
export type CommonMenuItemProps<SelectParams = undefined> = {
// onItemClick is triggered when the item is clicked, sometimes after item click, it still has some internal logic to run(like popover a new menu), so we need to have a separate callback for that
onItemClick?: () => void;
// onSelect is triggered when the item is selected, it's the final callback for the item click
onSelect?: (params?: SelectParams) => void;
testId?: string;
};

View File

@@ -1,4 +1,5 @@
import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component';
import { CopyLink, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
MoreVerticalIcon,
@@ -13,7 +14,6 @@ import { useMemo, useRef, useState } from 'react';
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils';
import { CopyLink, MoveToTrash } from '../../operation-menu-items';
import { PinboardMenu } from '../pinboard-menu/';
import { StyledOperationButton } from '../styles';
@@ -152,7 +152,7 @@ export const OperationButton = ({
/>
<MoveToTrash.ConfirmModal
open={confirmModalOpen}
meta={currentMeta}
title={currentMeta.title}
onConfirm={() => {
toast(t['Moved to Trash']());
removeToTrash(currentMeta.id);

View File

@@ -0,0 +1,5 @@
import { style } from '@vanilla-extract/css';
export const pageListEmptyStyle = style({
height: 'calc(100% - 52px)',
});

View File

@@ -1,35 +1,172 @@
import { Empty } from '@affine/component';
import type { ListData, TrashListData } from '@affine/component/page-list';
import { PageList, PageListTrashView } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import { useAtomValue } from 'jotai';
import type React from 'react';
import { useMemo } from 'react';
import { workspacePreferredModeAtom } from '../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import type { BlockSuiteWorkspace } from '../../../shared';
import PageList from './page-list';
import { toast } from '../../../utils';
import { pageListEmptyStyle } from './index.css';
export type BlockSuitePageListProps = {
blockSuiteWorkspace: BlockSuiteWorkspace;
listType: 'all' | 'trash' | 'favorite' | 'shared' | 'public';
isPublic?: true;
onOpenPage: (pageId: string, newTab?: boolean) => void;
};
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;
},
favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
};
dayjs.extend(localizedFormat);
const formatDate = (date?: number | unknown) => {
const dateStr =
typeof date === 'number' ? dayjs(date).format('YYYY-MM-DD HH:mm') : '--';
return dateStr;
};
const PageListEmpty = (props: {
listType: BlockSuitePageListProps['listType'];
}) => {
const { listType } = props;
const t = useAFFiNEI18N();
const getEmptyDescription = () => {
if (listType === 'all') {
return t['emptyAllPages']();
}
if (listType === 'favorite') {
return t['emptyFavorite']();
}
if (listType === 'trash') {
return t['emptyTrash']();
}
if (listType === 'shared') {
return t['emptySharedPages']();
}
};
return (
<div className={pageListEmptyStyle}>
<Empty description={getEmptyDescription()} />
</div>
);
};
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
blockSuiteWorkspace,
onOpenPage,
listType,
isPublic = false,
}) => {
return (
<PageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onOpenPage}
listType="all"
/>
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
const {
toggleFavorite,
removeToTrash,
restoreFromTrash,
permanentlyDeletePage,
cancelPublicPage,
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const t = useAFFiNEI18N();
const list = useMemo(
() => pageMetas.filter(pageMeta => filter[listType](pageMeta, pageMetas)),
[pageMetas, listType]
);
};
const record = useAtomValue(workspacePreferredModeAtom);
if (list.length === 0) {
return <PageListEmpty listType={listType} />;
}
if (listType === 'trash') {
const pageList: TrashListData[] = list.map(pageMeta => {
return {
icon:
record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />,
pageId: pageMeta.id,
title: pageMeta.title,
favorite: !!pageMeta.favorite,
createDate: formatDate(pageMeta.createDate),
updatedDate: formatDate(pageMeta.updatedDate),
onClickPage: () => onOpenPage(pageMeta.id),
onClickRestore: () => {
restoreFromTrash(pageMeta.id);
},
onRestorePage: () => {
restoreFromTrash(pageMeta.id);
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
},
onPermanentlyDeletePage: () => {
permanentlyDeletePage(pageMeta.id);
toast(t['Permanently deleted']());
},
};
});
return <PageListTrashView list={pageList} />;
}
const pageList: ListData[] = list.map(pageMeta => {
return {
icon:
record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />,
pageId: pageMeta.id,
title: pageMeta.title,
favorite: !!pageMeta.favorite,
isPublicPage: !!pageMeta.isPublic,
createDate: formatDate(pageMeta.createDate),
updatedDate: formatDate(pageMeta.updatedDate),
onClickPage: () => onOpenPage(pageMeta.id),
onOpenPageInNewTab: () => onOpenPage(pageMeta.id, true),
onClickRestore: () => {
restoreFromTrash(pageMeta.id);
},
removeToTrash: () => {
removeToTrash(pageMeta.id);
toast(t['Successfully deleted']());
},
onRestorePage: () => {
restoreFromTrash(pageMeta.id);
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
},
bookmarkPage: () => {
toggleFavorite(pageMeta.id);
toast(
pageMeta.favorite
? t['Removed from Favorites']()
: t['Added to Favorites']()
);
},
onDisablePublicSharing: () => {
cancelPublicPage(pageMeta.id);
toast('Successfully disabled', {
portal: document.body,
});
},
};
});
export const BlockSuitePublicPageList: React.FC<BlockSuitePageListProps> = ({
blockSuiteWorkspace,
onOpenPage,
}) => {
return (
<PageList
isPublic={true}
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onOpenPage}
isPublicWorkspace={isPublic}
list={pageList}
listType={listType}
/>
);
};

View File

@@ -1,30 +0,0 @@
import type { TableCellProps } from '@affine/component';
import { TableCell } from '@affine/component';
import type { PageMeta } from '@blocksuite/store';
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import React from 'react';
dayjs.extend(localizedFormat);
export const DateCell = ({
pageMeta,
dateKey,
backupKey = 'updatedDate',
...props
}: {
pageMeta: PageMeta;
dateKey: keyof PageMeta;
backupKey?: keyof PageMeta;
} & Omit<TableCellProps, 'children'>) => {
const value = pageMeta[dateKey] ?? pageMeta[backupKey];
return (
<TableCell ellipsis={true} {...props}>
{typeof value === 'number'
? dayjs(value).format('YYYY-MM-DD HH:mm')
: '--'}
</TableCell>
);
};
export default DateCell;

View File

@@ -1,30 +0,0 @@
import { Empty } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import React from 'react';
export const PageListEmpty = (props: { listType?: string }) => {
const { listType } = props;
const t = useAFFiNEI18N();
const getEmptyDescription = () => {
if (listType === 'all') {
return t['emptyAllPages']();
}
if (listType === 'favorite') {
return t['emptyFavorite']();
}
if (listType === 'trash') {
return t['emptyTrash']();
}
if (listType === 'shared') {
return t['emptySharedPages']();
}
};
return (
<div style={{ height: 'calc(100% - 52px)' }}>
<Empty description={getEmptyDescription()} />
</div>
);
};
export default PageListEmpty;

View File

@@ -1,198 +0,0 @@
import {
Confirm,
FlexWrapper,
IconButton,
Menu,
MenuItem,
Tooltip,
} from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
DeletePermanentlyIcon,
FavoritedIcon,
FavoriteIcon,
MoreVerticalIcon,
OpenInNewIcon,
ResetIcon,
} from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store';
import type React from 'react';
import { useState } from 'react';
import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils';
import {
DisablePublicSharing,
MoveToTrash,
} from '../../../affine/operation-menu-items';
export type OperationCellProps = {
pageMeta: PageMeta;
metas: PageMeta[];
blockSuiteWorkspace: BlockSuiteWorkspace;
onOpenPageInNewTab: (pageId: string) => void;
onToggleFavoritePage: (pageId: string) => void;
onToggleTrashPage: (pageId: string, isTrash: boolean) => void;
};
export const OperationCell: React.FC<OperationCellProps> = ({
pageMeta,
blockSuiteWorkspace,
onOpenPageInNewTab,
onToggleFavoritePage,
onToggleTrashPage,
}) => {
const { id, favorite, isPublic } = pageMeta;
const t = useAFFiNEI18N();
const [open, setOpen] = useState(false);
const [openDisableShared, setOpenDisableShared] = useState(false);
const page = blockSuiteWorkspace.getPage(id);
assertExists(page);
const OperationMenu = (
<>
{isPublic && (
<DisablePublicSharing
testId="disable-public-sharing"
onItemClick={() => {
setOpenDisableShared(true);
}}
/>
)}
<MenuItem
onClick={() => {
onToggleFavoritePage(id);
toast(
favorite ? t['Removed from Favorites']() : t['Added to Favorites']()
);
}}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)
}
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem>
{!environment.isDesktop && (
<MenuItem
onClick={() => {
onOpenPageInNewTab(id);
}}
icon={<OpenInNewIcon />}
>
{t['Open in new tab']()}
</MenuItem>
)}
{!pageMeta.isRootPinboard && (
<MoveToTrash
testId="move-to-trash"
onItemClick={() => {
setOpen(true);
}}
/>
)}
</>
);
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={OperationMenu}
placement="bottom"
disablePortal={true}
trigger="click"
>
<IconButton data-testid="page-list-operation-button">
<MoreVerticalIcon />
</IconButton>
</Menu>
</FlexWrapper>
<MoveToTrash.ConfirmModal
open={open}
meta={pageMeta}
onConfirm={() => {
onToggleTrashPage(id, true);
toast(t['Moved to Trash']());
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
onCancel={() => {
setOpen(false);
}}
/>
<DisablePublicSharing.DisablePublicSharingModal
page={page}
open={openDisableShared}
onClose={() => {
setOpenDisableShared(false);
}}
/>
</>
);
};
export type TrashOperationCellProps = {
pageMeta: PageMeta;
onPermanentlyDeletePage: (pageId: string) => void;
onRestorePage: (pageId: string) => void;
onOpenPage: (pageId: string) => void;
};
export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
pageMeta,
onPermanentlyDeletePage,
onRestorePage,
}) => {
const { id, title } = pageMeta;
const t = useAFFiNEI18N();
const [open, setOpen] = useState(false);
return (
<FlexWrapper>
<Tooltip content={t['Restore it']()} placement="top-start">
<IconButton
style={{ marginRight: '12px' }}
onClick={() => {
onRestorePage(id);
toast(t['restored']({ title: title || 'Untitled' }));
}}
>
<ResetIcon />
</IconButton>
</Tooltip>
<Tooltip content={t['Delete permanently']()} placement="top-start">
<IconButton
onClick={() => {
setOpen(true);
}}
>
<DeletePermanentlyIcon />
</IconButton>
</Tooltip>
<Confirm
title={t['Delete permanently?']()}
content={t['TrashButtonGroupDescription']()}
confirmText={t['Delete']()}
confirmType="danger"
open={open}
onConfirm={() => {
onPermanentlyDeletePage(id);
toast(t['Permanently deleted']());
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
onCancel={() => {
setOpen(false);
}}
/>
</FlexWrapper>
);
};

View File

@@ -1,252 +0,0 @@
import {
Content,
IconButton,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Tooltip,
} from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
EdgelessIcon,
FavoritedIcon,
FavoriteIcon,
PageIcon,
} from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useMediaQuery, useTheme } from '@mui/material';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import type React from 'react';
import { useMemo } from 'react';
import { workspacePreferredModeAtom } from '../../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
import type { BlockSuiteWorkspace } from '../../../../shared';
import { toast } from '../../../../utils';
import DateCell from './DateCell';
import Empty from './Empty';
import { OperationCell, TrashOperationCell } from './OperationCell';
import {
StyledTableContainer,
StyledTableRow,
StyledTitleLink,
StyledTitleWrapper,
} from './styles';
export type FavoriteTagProps = {
pageMeta: PageMeta;
onClick: () => void;
};
const FavoriteTag: React.FC<FavoriteTagProps> = ({
pageMeta: { favorite },
onClick,
}) => {
const t = useAFFiNEI18N();
return (
<Tooltip
content={favorite ? t['Favorited']() : t['Favorite']()}
placement="top-start"
>
<IconButton
iconSize={[20, 20]}
onClick={e => {
e.stopPropagation();
onClick();
toast(
favorite ? t['Removed from Favorites']() : t['Added to Favorites']()
);
}}
style={{
color: favorite
? 'var(--affine-primary-color)'
: 'var(--affine-icon-color)',
}}
className={favorite ? '' : 'favorite-button'}
>
{favorite ? (
<FavoritedIcon data-testid="favorited-icon" />
) : (
<FavoriteIcon />
)}
</IconButton>
</Tooltip>
);
};
type PageListProps = {
blockSuiteWorkspace: BlockSuiteWorkspace;
isPublic?: boolean;
listType?: 'all' | 'trash' | 'favorite' | 'shared';
onClickPage: (pageId: string, newTab?: boolean) => void;
};
const filter = {
all: (pageMeta: PageMeta) => !pageMeta.trash,
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
return !parentMeta?.trash && pageMeta.trash;
},
favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
};
export const PageList: React.FC<PageListProps> = ({
blockSuiteWorkspace,
isPublic = false,
listType,
onClickPage,
}) => {
const pageList = useBlockSuitePageMeta(blockSuiteWorkspace);
const helper = usePageMetaHelper(blockSuiteWorkspace);
const { removeToTrash, restoreFromTrash } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const t = useAFFiNEI18N();
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.up('sm'));
const isTrash = listType === 'trash';
const isShared = listType === 'shared';
const record = useAtomValue(workspacePreferredModeAtom);
const list = useMemo(
() =>
pageList.filter(pageMeta =>
filter[listType ?? 'all'](pageMeta, pageList)
),
[pageList, listType]
);
if (list.length === 0) {
return <Empty listType={listType} />;
}
return (
<StyledTableContainer>
<Table>
<TableHead>
<TableRow>
{matches && (
<>
<TableCell proportion={0.5}>{t['Title']()}</TableCell>
<TableCell proportion={0.2}>{t['Created']()}</TableCell>
<TableCell proportion={0.2}>
{isTrash
? t['Moved to Trash']()
: isShared
? 'Shared'
: t['Updated']()}
</TableCell>
<TableCell proportion={0.1}></TableCell>
</>
)}
</TableRow>
</TableHead>
<TableBody>
{list.map((pageMeta, index) => {
return (
<StyledTableRow
data-testid={`page-list-item-${pageMeta.id}`}
key={`${pageMeta.id}-${index}`}
>
<TableCell
onClick={() => {
onClickPage(pageMeta.id);
}}
>
<StyledTitleWrapper>
<StyledTitleLink>
{record[pageMeta.id] === 'edgeless' ? (
<EdgelessIcon />
) : (
<PageIcon />
)}
<Content ellipsis={true} color="inherit">
{pageMeta.title || t['Untitled']()}
</Content>
</StyledTitleLink>
{listType && !isTrash && (
<FavoriteTag
onClick={() => {
helper.setPageMeta(pageMeta.id, {
favorite: !pageMeta.favorite,
});
}}
pageMeta={pageMeta}
/>
)}
</StyledTitleWrapper>
</TableCell>
{matches && (
<>
<DateCell
pageMeta={pageMeta}
dateKey="createDate"
onClick={() => {
onClickPage(pageMeta.id);
}}
/>
<DateCell
pageMeta={pageMeta}
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
backupKey={isTrash ? 'trashDate' : 'createDate'}
onClick={() => {
onClickPage(pageMeta.id);
}}
/>
{!isPublic && (
<TableCell
style={{ padding: 0 }}
data-testid={`more-actions-${pageMeta.id}`}
>
{isTrash ? (
<TrashOperationCell
pageMeta={pageMeta}
onPermanentlyDeletePage={pageId => {
blockSuiteWorkspace.removePage(pageId);
}}
onRestorePage={() => {
restoreFromTrash(pageMeta.id);
}}
onOpenPage={pageId => {
onClickPage(pageId, false);
}}
/>
) : (
<OperationCell
pageMeta={pageMeta}
metas={pageList}
blockSuiteWorkspace={blockSuiteWorkspace}
onOpenPageInNewTab={pageId => {
onClickPage(pageId, true);
}}
onToggleFavoritePage={(pageId: string) => {
helper.setPageMeta(pageId, {
favorite: !pageMeta.favorite,
});
}}
onToggleTrashPage={(pageId, isTrash) => {
if (isTrash) {
removeToTrash(pageId);
} else {
restoreFromTrash(pageId);
}
}}
/>
)}
</TableCell>
)}
</>
)}
</StyledTableRow>
);
})}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default PageList;

View File

@@ -1,55 +0,0 @@
import { displayFlex, styled } from '@affine/component';
import { TableRow } from '@affine/component';
export const StyledTableContainer = styled('div')(({ theme }) => {
return {
height: 'calc(100vh - 52px)',
padding: '78px 72px',
maxWidth: '100%',
overflowY: 'auto',
[theme.breakpoints.down('md')]: {
padding: '12px 24px',
},
};
});
export const StyledTitleWrapper = styled('div')(() => {
return {
...displayFlex('flex-start', 'center'),
a: {
color: 'inherit',
},
'a:visited': {
color: 'unset',
},
'a:hover': {
color: 'var(--affine-primary-color)',
},
};
});
export const StyledTitleLink = styled('div')(() => {
return {
maxWidth: '80%',
marginRight: '18px',
...displayFlex('flex-start', 'center'),
color: 'var(--affine-text-primary-color)',
'>svg': {
fontSize: '24px',
marginRight: '12px',
color: 'var(--affine-icon-color)',
},
};
});
export const StyledTableRow = styled(TableRow)(() => {
return {
cursor: 'pointer',
'.favorite-button': {
display: 'none',
},
'&:hover': {
'.favorite-button': {
display: 'flex',
},
},
};
});

View File

@@ -1,5 +1,6 @@
// fixme(himself65): refactor this file
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
import { Export, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
EdgelessIcon,
@@ -22,7 +23,6 @@ import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suit
import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { toast } from '../../../../utils';
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
import {
StyledHorizontalDivider,
@@ -152,7 +152,7 @@ const PageMenu = () => {
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}
meta={pageMeta}
title={pageMeta.title}
onConfirm={() => {
removeToTrash(pageMeta.id);
toast(t['Moved to Trash']());

View File

@@ -15,6 +15,32 @@ export function useBlockSuiteMetaHelper(
useReferenceLinkHelper(blockSuiteWorkspace);
const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
const addToFavorite = useCallback(
(pageId: string) => {
setPageMeta(pageId, {
favorite: true,
});
},
[setPageMeta]
);
const removeFromFavorite = useCallback(
(pageId: string) => {
setPageMeta(pageId, {
favorite: false,
});
},
[setPageMeta]
);
const toggleFavorite = useCallback(
(pageId: string) => {
const { favorite } = getPageMeta(pageId) ?? {};
setPageMeta(pageId, {
favorite: !favorite,
});
},
[getPageMeta, setPageMeta]
);
const removeToTrash = useCallback(
(pageId: string, isRoot = true) => {
const parentMeta = metas.find(m => m.subpageIds?.includes(pageId));
@@ -58,8 +84,47 @@ export function useBlockSuiteMetaHelper(
[addReferenceLink, getPageMeta, setPageMeta]
);
const permanentlyDeletePage = useCallback(
(pageId: string) => {
blockSuiteWorkspace.removePage(pageId);
},
[blockSuiteWorkspace]
);
/**
* see {@link useBlockSuiteWorkspacePageIsPublic}
*/
const publicPage = useCallback(
(pageId: string) => {
setPageMeta(pageId, {
isPublic: true,
});
},
[setPageMeta]
);
/**
* see {@link useBlockSuiteWorkspacePageIsPublic}
*/
const cancelPublicPage = useCallback(
(pageId: string) => {
setPageMeta(pageId, {
isPublic: false,
});
},
[setPageMeta]
);
return {
publicPage,
cancelPublicPage,
addToFavorite,
removeFromFavorite,
toggleFavorite,
removeToTrash,
restoreFromTrash,
permanentlyDeletePage,
};
}

View File

@@ -9,7 +9,7 @@ import { Typography } from '@mui/material';
import type React from 'react';
import { useEffect, useMemo, useState } from 'react';
import PageList from '../../components/blocksuite/block-suite-page-list/page-list';
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
import { StyledPage, StyledWrapper } from '../../layouts/styles';
import { toast } from '../../utils';
@@ -57,9 +57,10 @@ const BroadcastPage: React.FC = () => {
>
Create Page
</Button>
<PageList
<BlockSuitePageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={() => {
listType="all"
onOpenPage={() => {
toast('do nothing');
}}
/>

View File

@@ -1,4 +1,5 @@
import { Breadcrumbs, IconButton, ListSkeleton } from '@affine/component';
import { StyledTableContainer } from '@affine/component/page-list';
import { SearchIcon } from '@blocksuite/icons';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
@@ -13,7 +14,6 @@ import {
publicWorkspaceIdAtom,
} from '../../atoms/public-workspace';
import { QueryParamError } from '../../components/affine/affine-error-eoundary';
import { StyledTableContainer } from '../../components/blocksuite/block-suite-page-list/page-list/styles';
import { WorkspaceAvatar } from '../../components/pure/footer';
import { PageLoading } from '../../components/pure/loading';
import {
@@ -23,9 +23,9 @@ import {
import type { NextPageWithLayout } from '../../shared';
import { NavContainer, StyledBreadcrumbs } from './[workspaceId]/[pageId]';
const BlockSuitePublicPageList = lazy(() =>
const BlockSuitePageList = lazy(() =>
import('../../components/blocksuite/block-suite-page-list').then(module => ({
default: module.BlockSuitePublicPageList,
default: module.BlockSuitePageList,
}))
);
@@ -79,7 +79,9 @@ const ListPageInner: React.FC<{
</StyledTableContainer>
}
>
<BlockSuitePublicPageList
<BlockSuitePageList
listType="public"
isPublic={true}
onOpenPage={handleClickPage}
blockSuiteWorkspace={blockSuiteWorkspace}
/>

View File

@@ -5,7 +5,7 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import PageList from '../../../components/blocksuite/block-suite-page-list/page-list';
import { BlockSuitePageList } from '../../../components/blocksuite/block-suite-page-list';
import { PageLoading } from '../../../components/pure/loading';
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
@@ -50,9 +50,9 @@ const FavouritePage: NextPageWithLayout = () => {
>
{t['Favorites']()}
</WorkspaceTitle>
<PageList
<BlockSuitePageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onClickPage}
onOpenPage={onClickPage}
listType="favorite"
/>
</>

View File

@@ -5,7 +5,7 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import { useCallback } from 'react';
import PageList from '../../../components/blocksuite/block-suite-page-list/page-list';
import { BlockSuitePageList } from '../../../components/blocksuite/block-suite-page-list';
import { PageLoading } from '../../../components/pure/loading';
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
@@ -50,9 +50,9 @@ const SharedPages: NextPageWithLayout = () => {
>
{t['Shared Pages']()}
</WorkspaceTitle>
<PageList
<BlockSuitePageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onClickPage}
onOpenPage={onClickPage}
listType="shared"
/>
</>

View File

@@ -5,7 +5,7 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import PageList from '../../../components/blocksuite/block-suite-page-list/page-list';
import { BlockSuitePageList } from '../../../components/blocksuite/block-suite-page-list';
import { PageLoading } from '../../../components/pure/loading';
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
@@ -53,9 +53,9 @@ const TrashPage: NextPageWithLayout = () => {
>
{t['Trash']()}
</WorkspaceTitle>
<PageList
<BlockSuitePageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onClickPage}
onOpenPage={onClickPage}
listType="trash"
/>
</>

View File

@@ -304,6 +304,7 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
return (
<BlockSuitePageList
listType="all"
onOpenPage={onOpenPage}
blockSuiteWorkspace={blockSuiteWorkspace}
/>

View File

@@ -78,6 +78,7 @@ export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
return (
<BlockSuitePageList
listType="all"
onOpenPage={onOpenPage}
blockSuiteWorkspace={blockSuiteWorkspace}
/>