refactor: clean all pages component (#2176)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
@@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component';
|
import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component';
|
||||||
|
import { CopyLink, MoveToTrash } from '@affine/component/page-list';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import {
|
import {
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
@@ -13,7 +14,6 @@ import { useMemo, useRef, useState } from 'react';
|
|||||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-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 { PinboardMenu } from '../pinboard-menu/';
|
import { PinboardMenu } from '../pinboard-menu/';
|
||||||
import { StyledOperationButton } from '../styles';
|
import { StyledOperationButton } from '../styles';
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ export const OperationButton = ({
|
|||||||
/>
|
/>
|
||||||
<MoveToTrash.ConfirmModal
|
<MoveToTrash.ConfirmModal
|
||||||
open={confirmModalOpen}
|
open={confirmModalOpen}
|
||||||
meta={currentMeta}
|
title={currentMeta.title}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
toast(t['Moved to Trash']());
|
toast(t['Moved to Trash']());
|
||||||
removeToTrash(currentMeta.id);
|
removeToTrash(currentMeta.id);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const pageListEmptyStyle = style({
|
||||||
|
height: 'calc(100% - 52px)',
|
||||||
|
});
|
||||||
@@ -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 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 type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
import PageList from './page-list';
|
import { toast } from '../../../utils';
|
||||||
|
import { pageListEmptyStyle } from './index.css';
|
||||||
|
|
||||||
export type BlockSuitePageListProps = {
|
export type BlockSuitePageListProps = {
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||||
|
listType: 'all' | 'trash' | 'favorite' | 'shared' | 'public';
|
||||||
|
isPublic?: true;
|
||||||
onOpenPage: (pageId: string, newTab?: boolean) => void;
|
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> = ({
|
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
|
||||||
blockSuiteWorkspace,
|
blockSuiteWorkspace,
|
||||||
onOpenPage,
|
onOpenPage,
|
||||||
|
listType,
|
||||||
|
isPublic = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||||
<PageList
|
const {
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
toggleFavorite,
|
||||||
onClickPage={onOpenPage}
|
removeToTrash,
|
||||||
listType="all"
|
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 (
|
return (
|
||||||
<PageList
|
<PageList
|
||||||
isPublic={true}
|
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
|
||||||
onClickPage={onOpenPage}
|
onClickPage={onOpenPage}
|
||||||
|
isPublicWorkspace={isPublic}
|
||||||
|
list={pageList}
|
||||||
|
listType={listType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// fixme(himself65): refactor this file
|
// fixme(himself65): refactor this file
|
||||||
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
|
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
|
||||||
|
import { Export, MoveToTrash } from '@affine/component/page-list';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import {
|
import {
|
||||||
EdgelessIcon,
|
EdgelessIcon,
|
||||||
@@ -22,7 +23,6 @@ import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suit
|
|||||||
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 { toast } from '../../../../utils';
|
import { toast } from '../../../../utils';
|
||||||
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
|
|
||||||
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
||||||
import {
|
import {
|
||||||
StyledHorizontalDivider,
|
StyledHorizontalDivider,
|
||||||
@@ -152,7 +152,7 @@ const PageMenu = () => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
<MoveToTrash.ConfirmModal
|
<MoveToTrash.ConfirmModal
|
||||||
open={openConfirm}
|
open={openConfirm}
|
||||||
meta={pageMeta}
|
title={pageMeta.title}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
removeToTrash(pageMeta.id);
|
removeToTrash(pageMeta.id);
|
||||||
toast(t['Moved to Trash']());
|
toast(t['Moved to Trash']());
|
||||||
|
|||||||
@@ -15,6 +15,32 @@ export function useBlockSuiteMetaHelper(
|
|||||||
useReferenceLinkHelper(blockSuiteWorkspace);
|
useReferenceLinkHelper(blockSuiteWorkspace);
|
||||||
const metas = useBlockSuitePageMeta(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(
|
const removeToTrash = useCallback(
|
||||||
(pageId: string, isRoot = true) => {
|
(pageId: string, isRoot = true) => {
|
||||||
const parentMeta = metas.find(m => m.subpageIds?.includes(pageId));
|
const parentMeta = metas.find(m => m.subpageIds?.includes(pageId));
|
||||||
@@ -58,8 +84,47 @@ export function useBlockSuiteMetaHelper(
|
|||||||
[addReferenceLink, getPageMeta, setPageMeta]
|
[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 {
|
return {
|
||||||
|
publicPage,
|
||||||
|
cancelPublicPage,
|
||||||
|
|
||||||
|
addToFavorite,
|
||||||
|
removeFromFavorite,
|
||||||
|
toggleFavorite,
|
||||||
|
|
||||||
removeToTrash,
|
removeToTrash,
|
||||||
restoreFromTrash,
|
restoreFromTrash,
|
||||||
|
permanentlyDeletePage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Typography } from '@mui/material';
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useEffect, useMemo, useState } 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 { StyledPage, StyledWrapper } from '../../layouts/styles';
|
||||||
import { toast } from '../../utils';
|
import { toast } from '../../utils';
|
||||||
|
|
||||||
@@ -57,9 +57,10 @@ const BroadcastPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Create Page
|
Create Page
|
||||||
</Button>
|
</Button>
|
||||||
<PageList
|
<BlockSuitePageList
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
onClickPage={() => {
|
listType="all"
|
||||||
|
onOpenPage={() => {
|
||||||
toast('do nothing');
|
toast('do nothing');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Breadcrumbs, IconButton, ListSkeleton } from '@affine/component';
|
import { Breadcrumbs, IconButton, ListSkeleton } from '@affine/component';
|
||||||
|
import { StyledTableContainer } from '@affine/component/page-list';
|
||||||
import { SearchIcon } from '@blocksuite/icons';
|
import { SearchIcon } from '@blocksuite/icons';
|
||||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
publicWorkspaceIdAtom,
|
publicWorkspaceIdAtom,
|
||||||
} from '../../atoms/public-workspace';
|
} from '../../atoms/public-workspace';
|
||||||
import { QueryParamError } from '../../components/affine/affine-error-eoundary';
|
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 { WorkspaceAvatar } from '../../components/pure/footer';
|
||||||
import { PageLoading } from '../../components/pure/loading';
|
import { PageLoading } from '../../components/pure/loading';
|
||||||
import {
|
import {
|
||||||
@@ -23,9 +23,9 @@ import {
|
|||||||
import type { NextPageWithLayout } from '../../shared';
|
import type { NextPageWithLayout } from '../../shared';
|
||||||
import { NavContainer, StyledBreadcrumbs } from './[workspaceId]/[pageId]';
|
import { NavContainer, StyledBreadcrumbs } from './[workspaceId]/[pageId]';
|
||||||
|
|
||||||
const BlockSuitePublicPageList = lazy(() =>
|
const BlockSuitePageList = lazy(() =>
|
||||||
import('../../components/blocksuite/block-suite-page-list').then(module => ({
|
import('../../components/blocksuite/block-suite-page-list').then(module => ({
|
||||||
default: module.BlockSuitePublicPageList,
|
default: module.BlockSuitePageList,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -79,7 +79,9 @@ const ListPageInner: React.FC<{
|
|||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<BlockSuitePublicPageList
|
<BlockSuitePageList
|
||||||
|
listType="public"
|
||||||
|
isPublic={true}
|
||||||
onOpenPage={handleClickPage}
|
onOpenPage={handleClickPage}
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Head from 'next/head';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { useCallback } from 'react';
|
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 { PageLoading } from '../../../components/pure/loading';
|
||||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
@@ -50,9 +50,9 @@ const FavouritePage: NextPageWithLayout = () => {
|
|||||||
>
|
>
|
||||||
{t['Favorites']()}
|
{t['Favorites']()}
|
||||||
</WorkspaceTitle>
|
</WorkspaceTitle>
|
||||||
<PageList
|
<BlockSuitePageList
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
onClickPage={onClickPage}
|
onOpenPage={onClickPage}
|
||||||
listType="favorite"
|
listType="favorite"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Head from 'next/head';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useCallback } from 'react';
|
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 { PageLoading } from '../../../components/pure/loading';
|
||||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
@@ -50,9 +50,9 @@ const SharedPages: NextPageWithLayout = () => {
|
|||||||
>
|
>
|
||||||
{t['Shared Pages']()}
|
{t['Shared Pages']()}
|
||||||
</WorkspaceTitle>
|
</WorkspaceTitle>
|
||||||
<PageList
|
<BlockSuitePageList
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
onClickPage={onClickPage}
|
onOpenPage={onClickPage}
|
||||||
listType="shared"
|
listType="shared"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Head from 'next/head';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { useCallback } from 'react';
|
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 { PageLoading } from '../../../components/pure/loading';
|
||||||
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
import { WorkspaceTitle } from '../../../components/pure/workspace-title';
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
@@ -53,9 +53,9 @@ const TrashPage: NextPageWithLayout = () => {
|
|||||||
>
|
>
|
||||||
{t['Trash']()}
|
{t['Trash']()}
|
||||||
</WorkspaceTitle>
|
</WorkspaceTitle>
|
||||||
<PageList
|
<BlockSuitePageList
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
onClickPage={onClickPage}
|
onOpenPage={onClickPage}
|
||||||
listType="trash"
|
listType="trash"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
|
|||||||
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
||||||
return (
|
return (
|
||||||
<BlockSuitePageList
|
<BlockSuitePageList
|
||||||
|
listType="all"
|
||||||
onOpenPage={onOpenPage}
|
onOpenPage={onOpenPage}
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
|
|||||||
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
||||||
return (
|
return (
|
||||||
<BlockSuitePageList
|
<BlockSuitePageList
|
||||||
|
listType="all"
|
||||||
onOpenPage={onOpenPage}
|
onOpenPage={onOpenPage}
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
356
packages/component/src/components/page-list/all-page.tsx
Normal file
356
packages/component/src/components/page-list/all-page.tsx
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
import type { IconButtonProps, TableCellProps } from '@affine/component';
|
||||||
|
import {
|
||||||
|
Content,
|
||||||
|
IconButton,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Tooltip,
|
||||||
|
} from '@affine/component';
|
||||||
|
import { OperationCell, TrashOperationCell } from '@affine/component/page-list';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons';
|
||||||
|
import { useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
StyledTableContainer,
|
||||||
|
StyledTableRow,
|
||||||
|
StyledTitleLink,
|
||||||
|
StyledTitleWrapper,
|
||||||
|
} from './styles';
|
||||||
|
|
||||||
|
export type FavoriteTagProps = {
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
const FavoriteTag = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
FavoriteTagProps & Omit<IconButtonProps, 'children'>
|
||||||
|
>(({ active, onClick, ...props }, ref) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={active ? t['Favorited']() : t['Favorite']()}
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
ref={ref}
|
||||||
|
iconSize={[20, 20]}
|
||||||
|
style={{
|
||||||
|
color: active
|
||||||
|
? 'var(--affine-primary-color)'
|
||||||
|
: 'var(--affine-icon-color)',
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick?.(e);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{active ? (
|
||||||
|
<FavoritedIcon data-testid="favorited-icon" />
|
||||||
|
) : (
|
||||||
|
<FavoriteIcon />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PageListProps = {
|
||||||
|
isPublicWorkspace?: boolean;
|
||||||
|
list: ListData[];
|
||||||
|
listType: 'all' | 'favorite' | 'shared' | 'public';
|
||||||
|
onClickPage: (pageId: string, newTab?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TitleCell = ({
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
suffix,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
icon: JSX.Element;
|
||||||
|
text: string;
|
||||||
|
suffix?: JSX.Element;
|
||||||
|
} & TableCellProps) => {
|
||||||
|
return (
|
||||||
|
<TableCell {...props}>
|
||||||
|
<StyledTitleWrapper>
|
||||||
|
<StyledTitleLink>
|
||||||
|
{icon}
|
||||||
|
<Content ellipsis={true} color="inherit">
|
||||||
|
{text}
|
||||||
|
</Content>
|
||||||
|
</StyledTitleLink>
|
||||||
|
{suffix}
|
||||||
|
</StyledTitleWrapper>
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListData = {
|
||||||
|
pageId: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
title: string;
|
||||||
|
favorite: boolean;
|
||||||
|
createDate: string;
|
||||||
|
updatedDate?: string;
|
||||||
|
trashDate?: string;
|
||||||
|
isPublicPage: boolean;
|
||||||
|
onClickPage: () => void;
|
||||||
|
onOpenPageInNewTab: () => void;
|
||||||
|
bookmarkPage: () => void;
|
||||||
|
removeToTrash: () => void;
|
||||||
|
onDisablePublicSharing: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageList: React.FC<PageListProps> = ({
|
||||||
|
isPublicWorkspace = false,
|
||||||
|
list,
|
||||||
|
listType,
|
||||||
|
}) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const isShared = listType === 'shared';
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
if (isSmallDevices) {
|
||||||
|
return <PageListMobileView list={list} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListHead = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell proportion={0.5}>{t['Title']()}</TableCell>
|
||||||
|
<TableCell proportion={0.2}>{t['Created']()}</TableCell>
|
||||||
|
<TableCell proportion={0.2}>
|
||||||
|
{isShared
|
||||||
|
? // TODO add i18n
|
||||||
|
'Shared'
|
||||||
|
: t['Updated']()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell proportion={0.1}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListItems = list.map(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
pageId,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
isPublicPage,
|
||||||
|
favorite,
|
||||||
|
createDate,
|
||||||
|
updatedDate,
|
||||||
|
onClickPage,
|
||||||
|
bookmarkPage,
|
||||||
|
onOpenPageInNewTab,
|
||||||
|
removeToTrash,
|
||||||
|
onDisablePublicSharing,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<StyledTableRow
|
||||||
|
data-testid={`page-list-item-${pageId}`}
|
||||||
|
key={`${pageId}-${index}`}
|
||||||
|
>
|
||||||
|
<TitleCell
|
||||||
|
icon={icon}
|
||||||
|
text={title || t['Untitled']()}
|
||||||
|
suffix={
|
||||||
|
<FavoriteTag
|
||||||
|
className={favorite ? '' : 'favorite-button'}
|
||||||
|
onClick={bookmarkPage}
|
||||||
|
active={!!favorite}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={onClickPage}
|
||||||
|
/>
|
||||||
|
<TableCell ellipsis={true} onClick={onClickPage}>
|
||||||
|
{createDate}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell ellipsis={true} onClick={onClickPage}>
|
||||||
|
{updatedDate ?? createDate}
|
||||||
|
</TableCell>
|
||||||
|
{!isPublicWorkspace && (
|
||||||
|
<TableCell
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
data-testid={`more-actions-${pageId}`}
|
||||||
|
>
|
||||||
|
<OperationCell
|
||||||
|
title={title}
|
||||||
|
favorite={favorite}
|
||||||
|
isPublic={isPublicPage}
|
||||||
|
onOpenPageInNewTab={onOpenPageInNewTab}
|
||||||
|
onToggleFavoritePage={bookmarkPage}
|
||||||
|
onRemoveToTrash={removeToTrash}
|
||||||
|
onDisablePublicSharing={onDisablePublicSharing}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</StyledTableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledTableContainer>
|
||||||
|
<Table>
|
||||||
|
<ListHead />
|
||||||
|
<TableBody>{ListItems}</TableBody>
|
||||||
|
</Table>
|
||||||
|
</StyledTableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TrashListHead = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell proportion={0.5}>{t['Title']()}</TableCell>
|
||||||
|
<TableCell proportion={0.2}>{t['Created']()}</TableCell>
|
||||||
|
<TableCell proportion={0.2}>{t['Moved to Trash']()}</TableCell>
|
||||||
|
<TableCell proportion={0.1}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TrashListData = {
|
||||||
|
pageId: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
title: string;
|
||||||
|
favorite: boolean;
|
||||||
|
createDate: string;
|
||||||
|
updatedDate?: string;
|
||||||
|
trashDate?: string;
|
||||||
|
// isPublic: boolean;
|
||||||
|
onClickPage: () => void;
|
||||||
|
onRestorePage: () => void;
|
||||||
|
onPermanentlyDeletePage: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageListTrashView: React.FC<{
|
||||||
|
list: TrashListData[];
|
||||||
|
}> = ({ list }) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
if (isSmallDevices) {
|
||||||
|
const mobileList = list.map(({ pageId, icon, title, onClickPage }) => ({
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
pageId,
|
||||||
|
onClickPage,
|
||||||
|
}));
|
||||||
|
return <PageListMobileView list={mobileList} />;
|
||||||
|
}
|
||||||
|
const ListItems = list.map(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
pageId,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
createDate,
|
||||||
|
trashDate,
|
||||||
|
onClickPage,
|
||||||
|
onPermanentlyDeletePage,
|
||||||
|
onRestorePage,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<StyledTableRow
|
||||||
|
data-testid={`page-list-item-${pageId}`}
|
||||||
|
key={`${pageId}-${index}`}
|
||||||
|
>
|
||||||
|
<TitleCell
|
||||||
|
icon={icon}
|
||||||
|
text={title || t['Untitled']()}
|
||||||
|
onClick={onClickPage}
|
||||||
|
/>
|
||||||
|
<TableCell ellipsis={true} onClick={onClickPage}>
|
||||||
|
{createDate}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell ellipsis={true} onClick={onClickPage}>
|
||||||
|
{trashDate}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
data-testid={`more-actions-${pageId}`}
|
||||||
|
>
|
||||||
|
<TrashOperationCell
|
||||||
|
onPermanentlyDeletePage={onPermanentlyDeletePage}
|
||||||
|
onRestorePage={onRestorePage}
|
||||||
|
onOpenPage={onClickPage}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledTableContainer>
|
||||||
|
<Table>
|
||||||
|
<TrashListHead />
|
||||||
|
<TableBody>{ListItems}</TableBody>
|
||||||
|
</Table>
|
||||||
|
</StyledTableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageListMobileView: React.FC<{
|
||||||
|
list: {
|
||||||
|
pageId: string;
|
||||||
|
title: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
onClickPage: () => void;
|
||||||
|
}[];
|
||||||
|
}> = ({ list }) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const ListItems = list.map(({ pageId, title, icon, onClickPage }, index) => {
|
||||||
|
return (
|
||||||
|
<StyledTableRow
|
||||||
|
data-testid={`page-list-item-${pageId}`}
|
||||||
|
key={`${pageId}-${index}`}
|
||||||
|
>
|
||||||
|
<TableCell onClick={onClickPage}>
|
||||||
|
<StyledTitleWrapper>
|
||||||
|
<StyledTitleLink>
|
||||||
|
{icon}
|
||||||
|
<Content ellipsis={true} color="inherit">
|
||||||
|
{title || t['Untitled']()}
|
||||||
|
</Content>
|
||||||
|
</StyledTitleLink>
|
||||||
|
</StyledTitleWrapper>
|
||||||
|
</TableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledTableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableBody>{ListItems}</TableBody>
|
||||||
|
</Table>
|
||||||
|
</StyledTableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageList;
|
||||||
4
packages/component/src/components/page-list/index.tsx
Normal file
4
packages/component/src/components/page-list/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './all-page';
|
||||||
|
export * from './operation-cell';
|
||||||
|
export * from './operation-menu-items';
|
||||||
|
export * from './styles';
|
||||||
@@ -15,42 +15,34 @@ import {
|
|||||||
OpenInNewIcon,
|
OpenInNewIcon,
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import { assertExists } from '@blocksuite/store';
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||||
import { toast } from '../../../../utils';
|
|
||||||
import {
|
|
||||||
DisablePublicSharing,
|
|
||||||
MoveToTrash,
|
|
||||||
} from '../../../affine/operation-menu-items';
|
|
||||||
|
|
||||||
export type OperationCellProps = {
|
export type OperationCellProps = {
|
||||||
pageMeta: PageMeta;
|
title: string;
|
||||||
metas: PageMeta[];
|
favorite: boolean;
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
isPublic: boolean;
|
||||||
onOpenPageInNewTab: (pageId: string) => void;
|
onOpenPageInNewTab: () => void;
|
||||||
onToggleFavoritePage: (pageId: string) => void;
|
onToggleFavoritePage: () => void;
|
||||||
onToggleTrashPage: (pageId: string, isTrash: boolean) => void;
|
onRemoveToTrash: () => void;
|
||||||
|
onDisablePublicSharing: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OperationCell: React.FC<OperationCellProps> = ({
|
export const OperationCell: React.FC<OperationCellProps> = ({
|
||||||
pageMeta,
|
title,
|
||||||
blockSuiteWorkspace,
|
favorite,
|
||||||
|
isPublic,
|
||||||
onOpenPageInNewTab,
|
onOpenPageInNewTab,
|
||||||
onToggleFavoritePage,
|
onToggleFavoritePage,
|
||||||
onToggleTrashPage,
|
onRemoveToTrash,
|
||||||
|
onDisablePublicSharing,
|
||||||
}) => {
|
}) => {
|
||||||
const { id, favorite, isPublic } = pageMeta;
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [openDisableShared, setOpenDisableShared] = useState(false);
|
const [openDisableShared, setOpenDisableShared] = useState(false);
|
||||||
|
|
||||||
const page = blockSuiteWorkspace.getPage(id);
|
|
||||||
assertExists(page);
|
|
||||||
|
|
||||||
const OperationMenu = (
|
const OperationMenu = (
|
||||||
<>
|
<>
|
||||||
{isPublic && (
|
{isPublic && (
|
||||||
@@ -62,12 +54,7 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={onToggleFavoritePage}
|
||||||
onToggleFavoritePage(id);
|
|
||||||
toast(
|
|
||||||
favorite ? t['Removed from Favorites']() : t['Added to Favorites']()
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
icon={
|
icon={
|
||||||
favorite ? (
|
favorite ? (
|
||||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||||
@@ -79,23 +66,16 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
|||||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!environment.isDesktop && (
|
{!environment.isDesktop && (
|
||||||
<MenuItem
|
<MenuItem onClick={onOpenPageInNewTab} icon={<OpenInNewIcon />}>
|
||||||
onClick={() => {
|
|
||||||
onOpenPageInNewTab(id);
|
|
||||||
}}
|
|
||||||
icon={<OpenInNewIcon />}
|
|
||||||
>
|
|
||||||
{t['Open in new tab']()}
|
{t['Open in new tab']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{!pageMeta.isRootPinboard && (
|
<MoveToTrash
|
||||||
<MoveToTrash
|
testId="move-to-trash"
|
||||||
testId="move-to-trash"
|
onItemClick={() => {
|
||||||
onItemClick={() => {
|
setOpen(true);
|
||||||
setOpen(true);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
@@ -114,10 +94,9 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
|||||||
</FlexWrapper>
|
</FlexWrapper>
|
||||||
<MoveToTrash.ConfirmModal
|
<MoveToTrash.ConfirmModal
|
||||||
open={open}
|
open={open}
|
||||||
meta={pageMeta}
|
title={title}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
onToggleTrashPage(id, true);
|
onRemoveToTrash();
|
||||||
toast(t['Moved to Trash']());
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -128,7 +107,7 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DisablePublicSharing.DisablePublicSharingModal
|
<DisablePublicSharing.DisablePublicSharingModal
|
||||||
page={page}
|
onConfirmDisable={onDisablePublicSharing}
|
||||||
open={openDisableShared}
|
open={openDisableShared}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpenDisableShared(false);
|
setOpenDisableShared(false);
|
||||||
@@ -139,18 +118,15 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TrashOperationCellProps = {
|
export type TrashOperationCellProps = {
|
||||||
pageMeta: PageMeta;
|
onPermanentlyDeletePage: () => void;
|
||||||
onPermanentlyDeletePage: (pageId: string) => void;
|
onRestorePage: () => void;
|
||||||
onRestorePage: (pageId: string) => void;
|
onOpenPage: () => void;
|
||||||
onOpenPage: (pageId: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||||
pageMeta,
|
|
||||||
onPermanentlyDeletePage,
|
onPermanentlyDeletePage,
|
||||||
onRestorePage,
|
onRestorePage,
|
||||||
}) => {
|
}) => {
|
||||||
const { id, title } = pageMeta;
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
return (
|
return (
|
||||||
@@ -159,8 +135,7 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
style={{ marginRight: '12px' }}
|
style={{ marginRight: '12px' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onRestorePage(id);
|
onRestorePage();
|
||||||
toast(t['restored']({ title: title || 'Untitled' }));
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ResetIcon />
|
<ResetIcon />
|
||||||
@@ -182,8 +157,7 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
|||||||
confirmType="danger"
|
confirmType="danger"
|
||||||
open={open}
|
open={open}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
onPermanentlyDeletePage(id);
|
onPermanentlyDeletePage();
|
||||||
toast(t['Permanently deleted']());
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { MenuItem } from '@affine/component';
|
import { MenuItem, toast } from '@affine/component';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { CopyIcon } from '@blocksuite/icons';
|
import { CopyIcon } from '@blocksuite/icons';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
//
|
|
||||||
import { toast } from '../../../utils';
|
|
||||||
import type { CommonMenuItemProps } from './types';
|
import type { CommonMenuItemProps } from './types';
|
||||||
|
|
||||||
export const CopyLink = ({ onItemClick, onSelect }: CommonMenuItemProps) => {
|
export const CopyLink = ({ onItemClick, onSelect }: CommonMenuItemProps) => {
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { MenuItem, styled } from '@affine/component';
|
import { MenuItem, styled } from '@affine/component';
|
||||||
import type { PublicLinkDisableProps } from '@affine/component/share-menu';
|
|
||||||
import { PublicLinkDisableModal } from '@affine/component/share-menu';
|
import { PublicLinkDisableModal } from '@affine/component/share-menu';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { ShareIcon } from '@blocksuite/icons';
|
import { ShareIcon } from '@blocksuite/icons';
|
||||||
@@ -47,12 +46,4 @@ export const DisablePublicSharing = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DisablePublicSharingModal = ({
|
DisablePublicSharing.DisablePublicSharingModal = PublicLinkDisableModal;
|
||||||
page,
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
}: PublicLinkDisableProps) => {
|
|
||||||
return <PublicLinkDisableModal page={page} open={open} onClose={onClose} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
DisablePublicSharing.DisablePublicSharingModal = DisablePublicSharingModal;
|
|
||||||
@@ -2,7 +2,6 @@ import type { ConfirmProps } from '@affine/component';
|
|||||||
import { Confirm, MenuItem } from '@affine/component';
|
import { Confirm, MenuItem } from '@affine/component';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { DeleteTemporarilyIcon } from '@blocksuite/icons';
|
import { DeleteTemporarilyIcon } from '@blocksuite/icons';
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
|
|
||||||
import type { CommonMenuItemProps } from './types';
|
import type { CommonMenuItemProps } from './types';
|
||||||
|
|
||||||
@@ -30,10 +29,10 @@ export const MoveToTrash = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ConfirmModal = ({
|
const ConfirmModal = ({
|
||||||
meta,
|
title,
|
||||||
...confirmModalProps
|
...confirmModalProps
|
||||||
}: {
|
}: {
|
||||||
meta: PageMeta;
|
title: string;
|
||||||
} & ConfirmProps) => {
|
} & ConfirmProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ const ConfirmModal = ({
|
|||||||
<Confirm
|
<Confirm
|
||||||
title={t['Delete page?']()}
|
title={t['Delete page?']()}
|
||||||
content={t['will be moved to Trash']({
|
content={t['will be moved to Trash']({
|
||||||
title: meta.title || 'Untitled',
|
title: title || 'Untitled',
|
||||||
})}
|
})}
|
||||||
confirmText={t.Delete()}
|
confirmText={t.Delete()}
|
||||||
confirmType="danger"
|
confirmType="danger"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export * from './CopyLink';
|
export * from './CopyLink';
|
||||||
export * from './DisablePublicSharing';
|
export * from './DisablePublicSharing';
|
||||||
export * from './Export';
|
export * from './Export';
|
||||||
export * from './MoveTo';
|
// export * from './MoveTo';
|
||||||
export * from './MoveToTrash';
|
export * from './MoveToTrash';
|
||||||
@@ -44,11 +44,11 @@ export const StyledTableRow = styled(TableRow)(() => {
|
|||||||
return {
|
return {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
'.favorite-button': {
|
'.favorite-button': {
|
||||||
display: 'none',
|
visibility: 'hidden',
|
||||||
},
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
'.favorite-button': {
|
'.favorite-button': {
|
||||||
display: 'flex',
|
visibility: 'visible',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -56,6 +56,12 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
|||||||
navigator.clipboard.writeText(sharingUrl);
|
navigator.clipboard.writeText(sharingUrl);
|
||||||
toast(t['Copied link to clipboard']());
|
toast(t['Copied link to clipboard']());
|
||||||
}, [sharingUrl, t]);
|
}, [sharingUrl, t]);
|
||||||
|
const onDisablePublic = useCallback(() => {
|
||||||
|
setIsPublic(false);
|
||||||
|
toast('Successfully disabled', {
|
||||||
|
portal: document.body,
|
||||||
|
});
|
||||||
|
}, [setIsPublic]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={menuItemStyle}>
|
<div className={menuItemStyle}>
|
||||||
@@ -104,8 +110,8 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
|||||||
{t['Disable Public Link']()}
|
{t['Disable Public Link']()}
|
||||||
</StyledDisableButton>
|
</StyledDisableButton>
|
||||||
<PublicLinkDisableModal
|
<PublicLinkDisableModal
|
||||||
page={props.currentPage}
|
|
||||||
open={showDisable}
|
open={showDisable}
|
||||||
|
onConfirmDisable={onDisablePublic}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowDisable(false);
|
setShowDisable(false);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-block-suite-workspace-page-is-public';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { Modal, ModalCloseButton, toast } from '../../..';
|
import { Modal, ModalCloseButton } from '../../..';
|
||||||
import {
|
import {
|
||||||
StyledButton,
|
StyledButton,
|
||||||
StyledButtonContent,
|
StyledButtonContent,
|
||||||
@@ -14,25 +11,17 @@ import {
|
|||||||
} from './style';
|
} from './style';
|
||||||
|
|
||||||
export type PublicLinkDisableProps = {
|
export type PublicLinkDisableProps = {
|
||||||
page: Page;
|
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
onConfirmDisable: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PublicLinkDisableModal = ({
|
export const PublicLinkDisableModal = ({
|
||||||
page,
|
|
||||||
open,
|
open,
|
||||||
|
onConfirmDisable,
|
||||||
onClose,
|
onClose,
|
||||||
}: PublicLinkDisableProps) => {
|
}: PublicLinkDisableProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [, setIsPublic] = useBlockSuiteWorkspacePageIsPublic(page);
|
|
||||||
const handleDisable = useCallback(() => {
|
|
||||||
setIsPublic(false);
|
|
||||||
toast('Successfully disabled', {
|
|
||||||
portal: document.body,
|
|
||||||
});
|
|
||||||
onClose();
|
|
||||||
}, [onClose, setIsPublic]);
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Modal open={open} onClose={onClose}>
|
||||||
<StyledModalWrapper>
|
<StyledModalWrapper>
|
||||||
@@ -47,7 +36,10 @@ export const PublicLinkDisableModal = ({
|
|||||||
<StyledButton onClick={onClose}>{t['Cancel']()}</StyledButton>
|
<StyledButton onClick={onClose}>{t['Cancel']()}</StyledButton>
|
||||||
<StyledDangerButton
|
<StyledDangerButton
|
||||||
data-testid="disable-public-link-confirm-button"
|
data-testid="disable-public-link-confirm-button"
|
||||||
onClick={handleDisable}
|
onClick={() => {
|
||||||
|
onConfirmDisable();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
style={{ marginLeft: '24px' }}
|
style={{ marginLeft: '24px' }}
|
||||||
>
|
>
|
||||||
{t['Disable']()}
|
{t['Disable']()}
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ export const Close: StoryFn = () => {
|
|||||||
Close.play = async ({ canvasElement }) => {
|
Close.play = async ({ canvasElement }) => {
|
||||||
const element = within(canvasElement);
|
const element = within(canvasElement);
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
await element.getByTestId('change-log-close-button').click();
|
element.getByTestId('change-log-close-button').click();
|
||||||
};
|
};
|
||||||
|
|||||||
89
packages/component/src/stories/PageList.stories.tsx
Normal file
89
packages/component/src/stories/PageList.stories.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { PageIcon } from '@blocksuite/icons';
|
||||||
|
import type { StoryFn } from '@storybook/react';
|
||||||
|
|
||||||
|
import { AffineLoading } from '../components/affine-loading';
|
||||||
|
import type {
|
||||||
|
PageListProps,
|
||||||
|
TrashListData,
|
||||||
|
} from '../components/page-list/all-page';
|
||||||
|
import { PageListTrashView } from '../components/page-list/all-page';
|
||||||
|
import PageList from '../components/page-list/all-page';
|
||||||
|
import type { OperationCellProps } from '../components/page-list/operation-cell';
|
||||||
|
import { OperationCell } from '../components/page-list/operation-cell';
|
||||||
|
import { toast } from '../ui/toast';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'AFFiNE/PageList',
|
||||||
|
component: AffineLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AffineOperationCell: StoryFn<OperationCellProps> = ({
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
<OperationCell {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
AffineOperationCell.args = {
|
||||||
|
title: 'Example Page',
|
||||||
|
favorite: false,
|
||||||
|
isPublic: true,
|
||||||
|
onToggleFavoritePage: () => toast('Toggle favorite page'),
|
||||||
|
onDisablePublicSharing: () => toast('Disable public sharing'),
|
||||||
|
onOpenPageInNewTab: () => toast('Open page in new tab'),
|
||||||
|
onRemoveToTrash: () => toast('Remove to trash'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AffineAllPageList: StoryFn<PageListProps> = ({ ...props }) => (
|
||||||
|
<div>
|
||||||
|
<PageList {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
AffineAllPageList.args = {
|
||||||
|
isPublicWorkspace: false,
|
||||||
|
listType: 'all',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
pageId: '1',
|
||||||
|
favorite: false,
|
||||||
|
icon: <PageIcon />,
|
||||||
|
isPublicPage: true,
|
||||||
|
title: 'Example Page',
|
||||||
|
updatedDate: '2021-01-01',
|
||||||
|
createDate: '2021-01-01',
|
||||||
|
trashDate: '2021-01-01',
|
||||||
|
bookmarkPage: () => toast('Bookmark page'),
|
||||||
|
onClickPage: () => toast('Click page'),
|
||||||
|
onDisablePublicSharing: () => toast('Disable public sharing'),
|
||||||
|
onOpenPageInNewTab: () => toast('Open page in new tab'),
|
||||||
|
removeToTrash: () => toast('Remove to trash'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AffineTrashPageList: StoryFn<{
|
||||||
|
list: TrashListData[];
|
||||||
|
}> = ({ ...props }) => (
|
||||||
|
<div>
|
||||||
|
<PageListTrashView {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
AffineTrashPageList.args = {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
pageId: '1',
|
||||||
|
favorite: false,
|
||||||
|
icon: <PageIcon />,
|
||||||
|
title: 'Example Page',
|
||||||
|
updatedDate: '2021-01-01',
|
||||||
|
createDate: '2021-01-01',
|
||||||
|
trashDate: '2021-01-01',
|
||||||
|
onClickPage: () => toast('Click page'),
|
||||||
|
onPermanentlyDeletePage: () => toast('Permanently delete page'),
|
||||||
|
onRestorePage: () => toast('Restore page'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -5,8 +5,11 @@ import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
|||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { expect } from '@storybook/jest';
|
import { expect } from '@storybook/jest';
|
||||||
import type { StoryFn } from '@storybook/react';
|
import type { StoryFn } from '@storybook/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { PublicLinkDisableModal } from '../components/share-menu/disable-public-link';
|
||||||
import { ShareMenu } from '../components/share-menu/ShareMenu';
|
import { ShareMenu } from '../components/share-menu/ShareMenu';
|
||||||
|
import { StyledDisableButton } from '../components/share-menu/styles';
|
||||||
import toast from '../ui/toast/toast';
|
import toast from '../ui/toast/toast';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -36,9 +39,9 @@ const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
|||||||
WorkspaceFlavour.LOCAL
|
WorkspaceFlavour.LOCAL
|
||||||
);
|
);
|
||||||
|
|
||||||
initPage(blockSuiteWorkspace.createPage('page0'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
|
||||||
initPage(blockSuiteWorkspace.createPage('page1'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
||||||
initPage(blockSuiteWorkspace.createPage('page2'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
||||||
|
|
||||||
const localWorkspace: LocalWorkspace = {
|
const localWorkspace: LocalWorkspace = {
|
||||||
id: 'test-workspace',
|
id: 'test-workspace',
|
||||||
@@ -103,3 +106,22 @@ export const AffineBasic: StoryFn = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DisableModal: StoryFn = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledDisableButton onClick={() => setOpen(!open)}>
|
||||||
|
Disable Public Link
|
||||||
|
</StyledDisableButton>
|
||||||
|
<PublicLinkDisableModal
|
||||||
|
open={open}
|
||||||
|
onConfirmDisable={() => {
|
||||||
|
toast('Disabled');
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,3 @@ export default {
|
|||||||
export const Basic: StoryFn = () => {
|
export const Basic: StoryFn = () => {
|
||||||
return <Switch />;
|
return <Switch />;
|
||||||
};
|
};
|
||||||
Basic.args = {
|
|
||||||
logoSrc: '/imgs/affine-text-logo.png',
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user