mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor: delete page style (#4347)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -1,18 +1,21 @@
|
|||||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
|
import { DeleteIcon, ResetIcon } from '@blocksuite/icons';
|
||||||
import { Button } from '@toeverything/components/button';
|
import { Button } from '@toeverything/components/button';
|
||||||
import { ConfirmModal } from '@toeverything/components/modal';
|
import { ConfirmModal } from '@toeverything/components/modal';
|
||||||
|
import { Tooltip } from '@toeverything/components/tooltip';
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
import { currentPageIdAtom } from '@toeverything/infra/atom';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { useAppSetting } from '../../../atoms/settings';
|
||||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||||
import { toast } from '../../../utils';
|
import { toast } from '../../../utils';
|
||||||
import { buttonContainer, group } from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export const TrashButtonGroup = () => {
|
export const TrashButtonGroup = () => {
|
||||||
// fixme(himself65): remove these hooks ASAP
|
// fixme(himself65): remove these hooks ASAP
|
||||||
@@ -26,57 +29,124 @@ export const TrashButtonGroup = () => {
|
|||||||
);
|
);
|
||||||
assertExists(pageMeta);
|
assertExists(pageMeta);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
const [appSettings] = useAppSetting();
|
||||||
const { jumpToSubPath } = useNavigateHelper();
|
const { jumpToSubPath } = useNavigateHelper();
|
||||||
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||||
|
const restoreRef = useRef(null);
|
||||||
|
const deleteRef = useRef(null);
|
||||||
|
const hintTextRef = useRef(null);
|
||||||
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
|
const hintText =
|
||||||
|
'This page has been moved to the trash, you can either restore or permanently delete it.';
|
||||||
|
useEffect(() => {
|
||||||
|
const currentRef = wrapperRef.current;
|
||||||
|
|
||||||
|
if (!currentRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (!currentRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperWidth = currentRef?.offsetWidth || 0;
|
||||||
|
setWidth(wrapperWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(handleResize);
|
||||||
|
resizeObserver.observe(currentRef);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.unobserve(currentRef);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={group}>
|
<div ref={wrapperRef} style={{ width: '100%' }}>
|
||||||
<div className={buttonContainer}>
|
<div
|
||||||
<Button
|
className={styles.deleteHintContainer}
|
||||||
data-testid="page-restore-button"
|
style={{
|
||||||
type="primary"
|
width: `${width}px`,
|
||||||
onClick={() => {
|
|
||||||
restoreFromTrash(pageId);
|
|
||||||
toast(
|
|
||||||
t['com.affine.toastMessage.restored']({
|
|
||||||
title: pageMeta.title || 'Untitled',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
{t['com.affine.trashOperation.restoreIt']()}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className={buttonContainer}>
|
|
||||||
<Button
|
|
||||||
type="error"
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
{t['com.affine.trashOperation.deletePermanently']()}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<ConfirmModal
|
|
||||||
title={t['com.affine.trashOperation.delete.title']()}
|
|
||||||
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
|
||||||
description={t['com.affine.trashOperation.delete.description']()}
|
|
||||||
confirmButtonOptions={{
|
|
||||||
type: 'error',
|
|
||||||
children: t['com.affine.trashOperation.delete'](),
|
|
||||||
}}
|
}}
|
||||||
open={open}
|
data-has-background={!appSettings.clientBorder}
|
||||||
onConfirm={useCallback(() => {
|
>
|
||||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
<Tooltip
|
||||||
blockSuiteWorkspace.removePage(pageId);
|
content={hintText}
|
||||||
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
portalOptions={{
|
||||||
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])}
|
container: hintTextRef.current,
|
||||||
onOpenChange={setOpen}
|
}}
|
||||||
/>
|
options={{ style: { whiteSpace: 'break-spaces' } }}
|
||||||
|
>
|
||||||
|
<div ref={hintTextRef} className={styles.deleteHintText}>
|
||||||
|
{hintText}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div className={styles.group}>
|
||||||
|
<Tooltip
|
||||||
|
content={t['com.affine.trashOperation.restoreIt']()}
|
||||||
|
portalOptions={{
|
||||||
|
container: restoreRef.current,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
ref={restoreRef}
|
||||||
|
data-testid="page-restore-button"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
restoreFromTrash(pageId);
|
||||||
|
toast(
|
||||||
|
t['com.affine.toastMessage.restored']({
|
||||||
|
title: pageMeta.title || 'Untitled',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className={styles.buttonContainer}
|
||||||
|
>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<ResetIcon />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
content={t['com.affine.trashOperation.deletePermanently']()}
|
||||||
|
portalOptions={{ container: deleteRef.current }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
ref={deleteRef}
|
||||||
|
type="error"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
style={{ color: 'var(--affine-pure-white)' }}
|
||||||
|
className={styles.buttonContainer}
|
||||||
|
>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
title={t['com.affine.trashOperation.delete.title']()}
|
||||||
|
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
||||||
|
description={t['com.affine.trashOperation.delete.description']()}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
type: 'error',
|
||||||
|
children: t['com.affine.trashOperation.delete'](),
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
onConfirm={useCallback(() => {
|
||||||
|
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||||
|
blockSuiteWorkspace.removePage(pageId);
|
||||||
|
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
||||||
|
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,46 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const group = style({
|
export const group = style({
|
||||||
width: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '100px',
|
|
||||||
left: '0',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '24px',
|
gap: '16px',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
});
|
||||||
|
export const deleteHintContainer = style({
|
||||||
|
position: 'fixed',
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
|
padding: '14px 20px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
bottom: '0',
|
||||||
|
gap: '16px',
|
||||||
|
backgroundColor: 'var(--affine-background-primary-color)',
|
||||||
|
borderTop: '1px solid var(--affine-border-color)',
|
||||||
|
selectors: {
|
||||||
|
'&[data-has-background="false"]': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderTop: 'none',
|
||||||
|
padding: '14px 0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const deleteHintText = style({
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: '500',
|
||||||
|
lineHeight: '24px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
export const buttonContainer = style({
|
export const buttonContainer = style({
|
||||||
boxShadow: 'var(--affine-float-button-shadow-2)',
|
color: 'var(--affine-pure-white)',
|
||||||
borderRadius: '8px',
|
padding: '8px 18px',
|
||||||
|
fontSize: '20px',
|
||||||
|
height: '36px',
|
||||||
|
});
|
||||||
|
export const icon = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignContent: 'center',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||||
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
@@ -228,7 +229,10 @@ export const WorkspaceLayoutInner = ({
|
|||||||
const [appSetting] = useAppSetting();
|
const [appSetting] = useAppSetting();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pageId } = useParams();
|
const { pageId } = useParams();
|
||||||
|
const pageMeta = useBlockSuitePageMeta(
|
||||||
|
currentWorkspace.blockSuiteWorkspace
|
||||||
|
).find(meta => meta.id === pageId);
|
||||||
|
const inTrashPage = pageMeta?.trash ?? false;
|
||||||
const setMainContainer = useSetAtom(mainContainerAtom);
|
const setMainContainer = useSetAtom(mainContainerAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -262,9 +266,10 @@ export const WorkspaceLayoutInner = ({
|
|||||||
<MainContainer
|
<MainContainer
|
||||||
ref={setMainContainer}
|
ref={setMainContainer}
|
||||||
padding={appSetting.clientBorder}
|
padding={appSetting.clientBorder}
|
||||||
|
inTrashPage={inTrashPage}
|
||||||
>
|
>
|
||||||
{incompatible ? <MigrationFallback /> : children}
|
{incompatible ? <MigrationFallback /> : children}
|
||||||
<ToolContainer>
|
<ToolContainer inTrashPage={inTrashPage}>
|
||||||
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
|
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
|
||||||
<HelpIsland showList={pageId ? undefined : showList} />
|
<HelpIsland showList={pageId ? undefined : showList} />
|
||||||
</ToolContainer>
|
</ToolContainer>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const sidebarSwitch = style({
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: '32px',
|
width: '32px',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
fontSize: '24px',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ export const mainContainerStyle = style({
|
|||||||
'&[data-show-padding="true"][data-is-macos="true"]': {
|
'&[data-show-padding="true"][data-is-macos="true"]': {
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
},
|
},
|
||||||
|
'&[data-in-trash-page="true"]': {
|
||||||
|
marginBottom: '66px',
|
||||||
|
},
|
||||||
|
'&[data-in-trash-page="true"][data-show-padding="true"]': {
|
||||||
|
marginBottom: '66px',
|
||||||
|
},
|
||||||
'&[data-show-padding="true"]:before': {
|
'&[data-show-padding="true"]:before': {
|
||||||
content: '""',
|
content: '""',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -151,4 +157,20 @@ export const toolStyle = style({
|
|||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
selectors: {
|
||||||
|
'&[data-in-trash-page="true"]': {
|
||||||
|
bottom: '70px',
|
||||||
|
'@media': {
|
||||||
|
[breakpoints.down('md', true)]: {
|
||||||
|
bottom: '80px',
|
||||||
|
},
|
||||||
|
[breakpoints.down('sm', true)]: {
|
||||||
|
bottom: '85px',
|
||||||
|
},
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ export const AppContainer = ({
|
|||||||
export interface MainContainerProps extends HTMLAttributes<HTMLDivElement> {
|
export interface MainContainerProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
className?: string;
|
className?: string;
|
||||||
padding?: boolean;
|
padding?: boolean;
|
||||||
|
inTrashPage?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MainContainer = forwardRef<
|
export const MainContainer = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
PropsWithChildren<MainContainerProps>
|
PropsWithChildren<MainContainerProps>
|
||||||
>(function MainContainer(
|
>(function MainContainer(
|
||||||
{ className, padding, children, ...props },
|
{ className, padding, inTrashPage, children, ...props },
|
||||||
ref
|
ref
|
||||||
): ReactElement {
|
): ReactElement {
|
||||||
return (
|
return (
|
||||||
@@ -50,6 +51,7 @@ export const MainContainer = forwardRef<
|
|||||||
className={clsx(mainContainerStyle, className)}
|
className={clsx(mainContainerStyle, className)}
|
||||||
data-is-macos={environment.isDesktop && environment.isMacOs}
|
data-is-macos={environment.isDesktop && environment.isMacOs}
|
||||||
data-show-padding={!!padding}
|
data-show-padding={!!padding}
|
||||||
|
data-in-trash-page={!!inTrashPage}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -59,8 +61,14 @@ export const MainContainer = forwardRef<
|
|||||||
|
|
||||||
MainContainer.displayName = 'MainContainer';
|
MainContainer.displayName = 'MainContainer';
|
||||||
|
|
||||||
export const ToolContainer = (props: PropsWithChildren): ReactElement => {
|
export const ToolContainer = (
|
||||||
return <div className={toolStyle}>{props.children}</div>;
|
props: PropsWithChildren & { inTrashPage: boolean }
|
||||||
|
): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className={toolStyle} data-in-trash-page={!!props.inTrashPage}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceFallback = (): ReactElement => {
|
export const WorkspaceFallback = (): ReactElement => {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function useIsTinyScreen({
|
|||||||
resizeObserver.observe(mainContainer);
|
resizeObserver.observe(mainContainer);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.unobserve(mainContainer);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
centerDom,
|
centerDom,
|
||||||
|
|||||||
@@ -60,7 +60,12 @@ export const HeaderItem = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size="large"
|
||||||
ref={setContainer}
|
ref={setContainer}
|
||||||
|
style={{
|
||||||
|
width: '32px',
|
||||||
|
fontSize: '24px',
|
||||||
|
}}
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user