mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-19 19:16:29 +08:00
refactor: clean all pages component (#2176)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './all-page';
|
||||
export * from './operation-cell';
|
||||
export * from './operation-menu-items';
|
||||
export * from './styles';
|
||||
@@ -0,0 +1,172 @@
|
||||
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 React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||
|
||||
export type OperationCellProps = {
|
||||
title: string;
|
||||
favorite: boolean;
|
||||
isPublic: boolean;
|
||||
onOpenPageInNewTab: () => void;
|
||||
onToggleFavoritePage: () => void;
|
||||
onRemoveToTrash: () => void;
|
||||
onDisablePublicSharing: () => void;
|
||||
};
|
||||
|
||||
export const OperationCell: React.FC<OperationCellProps> = ({
|
||||
title,
|
||||
favorite,
|
||||
isPublic,
|
||||
onOpenPageInNewTab,
|
||||
onToggleFavoritePage,
|
||||
onRemoveToTrash,
|
||||
onDisablePublicSharing,
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openDisableShared, setOpenDisableShared] = useState(false);
|
||||
|
||||
const OperationMenu = (
|
||||
<>
|
||||
{isPublic && (
|
||||
<DisablePublicSharing
|
||||
testId="disable-public-sharing"
|
||||
onItemClick={() => {
|
||||
setOpenDisableShared(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={onToggleFavoritePage}
|
||||
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} icon={<OpenInNewIcon />}>
|
||||
{t['Open in new tab']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
<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}
|
||||
title={title}
|
||||
onConfirm={() => {
|
||||
onRemoveToTrash();
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
<DisablePublicSharing.DisablePublicSharingModal
|
||||
onConfirmDisable={onDisablePublicSharing}
|
||||
open={openDisableShared}
|
||||
onClose={() => {
|
||||
setOpenDisableShared(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export type TrashOperationCellProps = {
|
||||
onPermanentlyDeletePage: () => void;
|
||||
onRestorePage: () => void;
|
||||
onOpenPage: () => void;
|
||||
};
|
||||
|
||||
export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
onPermanentlyDeletePage,
|
||||
onRestorePage,
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<FlexWrapper>
|
||||
<Tooltip content={t['Restore it']()} placement="top-start">
|
||||
<IconButton
|
||||
style={{ marginRight: '12px' }}
|
||||
onClick={() => {
|
||||
onRestorePage();
|
||||
}}
|
||||
>
|
||||
<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();
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MenuItem, toast } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CopyIcon } from '@blocksuite/icons';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { MenuItem, styled } from '@affine/component';
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DisablePublicSharing.DisablePublicSharingModal = PublicLinkDisableModal;
|
||||
@@ -0,0 +1,73 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
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 { 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 = ({
|
||||
title,
|
||||
...confirmModalProps
|
||||
}: {
|
||||
title: string;
|
||||
} & ConfirmProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
title={t['Delete page?']()}
|
||||
content={t['will be moved to Trash']({
|
||||
title: title || 'Untitled',
|
||||
})}
|
||||
confirmText={t.Delete()}
|
||||
confirmType="danger"
|
||||
{...confirmModalProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
MoveToTrash.ConfirmModal = ConfirmModal;
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './CopyLink';
|
||||
export * from './DisablePublicSharing';
|
||||
export * from './Export';
|
||||
// export * from './MoveTo';
|
||||
export * from './MoveToTrash';
|
||||
@@ -0,0 +1,7 @@
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
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': {
|
||||
visibility: 'hidden',
|
||||
},
|
||||
'&:hover': {
|
||||
'.favorite-button': {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -56,6 +56,12 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
||||
navigator.clipboard.writeText(sharingUrl);
|
||||
toast(t['Copied link to clipboard']());
|
||||
}, [sharingUrl, t]);
|
||||
const onDisablePublic = useCallback(() => {
|
||||
setIsPublic(false);
|
||||
toast('Successfully disabled', {
|
||||
portal: document.body,
|
||||
});
|
||||
}, [setIsPublic]);
|
||||
|
||||
return (
|
||||
<div className={menuItemStyle}>
|
||||
@@ -104,8 +110,8 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
||||
{t['Disable Public Link']()}
|
||||
</StyledDisableButton>
|
||||
<PublicLinkDisableModal
|
||||
page={props.currentPage}
|
||||
open={showDisable}
|
||||
onConfirmDisable={onDisablePublic}
|
||||
onClose={() => {
|
||||
setShowDisable(false);
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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 {
|
||||
StyledButton,
|
||||
StyledButtonContent,
|
||||
@@ -14,25 +11,17 @@ import {
|
||||
} from './style';
|
||||
|
||||
export type PublicLinkDisableProps = {
|
||||
page: Page;
|
||||
open: boolean;
|
||||
onConfirmDisable: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const PublicLinkDisableModal = ({
|
||||
page,
|
||||
open,
|
||||
onConfirmDisable,
|
||||
onClose,
|
||||
}: PublicLinkDisableProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [, setIsPublic] = useBlockSuiteWorkspacePageIsPublic(page);
|
||||
const handleDisable = useCallback(() => {
|
||||
setIsPublic(false);
|
||||
toast('Successfully disabled', {
|
||||
portal: document.body,
|
||||
});
|
||||
onClose();
|
||||
}, [onClose, setIsPublic]);
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
@@ -47,7 +36,10 @@ export const PublicLinkDisableModal = ({
|
||||
<StyledButton onClick={onClose}>{t['Cancel']()}</StyledButton>
|
||||
<StyledDangerButton
|
||||
data-testid="disable-public-link-confirm-button"
|
||||
onClick={handleDisable}
|
||||
onClick={() => {
|
||||
onConfirmDisable();
|
||||
onClose();
|
||||
}}
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Disable']()}
|
||||
|
||||
Reference in New Issue
Block a user