mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
feat: support header right slot
This commit is contained in:
@@ -17,7 +17,7 @@ export const EditorHeader = () => {
|
||||
const [title, setTitle] = useState('');
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const { editor } = useAppState();
|
||||
const pageMeta = useCurrentPageMeta();
|
||||
const { trash: isTrash = false } = useCurrentPageMeta() || {};
|
||||
const onPropsUpdated = usePropsUpdated();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,16 +34,22 @@ export const EditorHeader = () => {
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<Header
|
||||
rightItems={
|
||||
isTrash
|
||||
? ['trashButtonGroup']
|
||||
: ['syncUser', 'themeModeSwitch', 'editorOptionMenu']
|
||||
}
|
||||
>
|
||||
{title && (
|
||||
<StyledTitle
|
||||
onMouseEnter={() => {
|
||||
if (pageMeta?.trash) return;
|
||||
if (isTrash) return;
|
||||
|
||||
setIsHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (pageMeta?.trash) return;
|
||||
if (isTrash) return;
|
||||
|
||||
setIsHover(false);
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
ExportIcon,
|
||||
ExportToHtmlIcon,
|
||||
ExportToMarkdownIcon,
|
||||
FavouritedIcon,
|
||||
FavouritesIcon,
|
||||
MoreVerticalIcon,
|
||||
PaperIcon,
|
||||
TrashIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { toast } from '@/ui/toast';
|
||||
|
||||
const PopoverContent = () => {
|
||||
const { editor } = useAppState();
|
||||
const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
|
||||
const { changePageMode } = usePageHelper();
|
||||
const { confirm } = useConfirm();
|
||||
const {
|
||||
mode = 'page',
|
||||
id = '',
|
||||
favorite = false,
|
||||
title = '',
|
||||
} = useCurrentPageMeta() || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
toggleFavoritePage(id);
|
||||
toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites');
|
||||
}}
|
||||
icon={favorite ? <FavouritedIcon /> : <FavouritesIcon />}
|
||||
>
|
||||
{favorite ? 'Remove' : 'Add'} to favourites
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PaperIcon />}
|
||||
onClick={() => {
|
||||
changePageMode(id, mode === 'page' ? 'edgeless' : 'page');
|
||||
}}
|
||||
>
|
||||
Convert to {mode === 'page' ? 'Edgeless' : 'Page'}
|
||||
</MenuItem>
|
||||
<Menu
|
||||
placement="left-start"
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
editor && editor.contentParser.onExportHtml();
|
||||
}}
|
||||
icon={<ExportToHtmlIcon />}
|
||||
>
|
||||
Export to HTML
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
editor && editor.contentParser.onExportMarkdown();
|
||||
}}
|
||||
icon={<ExportToMarkdownIcon />}
|
||||
>
|
||||
Export to Markdown
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuItem icon={<ExportIcon />} isDir={true}>
|
||||
Export
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Delete page?',
|
||||
content: `${title || 'Untitled'} will be moved to Trash`,
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
}).then(confirm => {
|
||||
confirm && toggleDeletePage(id);
|
||||
toast('Moved to Trash');
|
||||
});
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const EditorOptionMenu = () => {
|
||||
return (
|
||||
<Menu content={<PopoverContent />} placement="bottom-end">
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorOptionMenu;
|
||||
+3
-1
@@ -3,7 +3,7 @@ import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { IconButton } from '@/ui/button';
|
||||
|
||||
export const SyncIcon = () => {
|
||||
export const SyncUser = () => {
|
||||
const { triggerLoginModal } = useModal();
|
||||
const appState = useAppState();
|
||||
|
||||
@@ -17,3 +17,5 @@ export const SyncIcon = () => {
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default SyncUser;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Button } from '@/ui/button';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
|
||||
export const TrashButtonGroup = () => {
|
||||
const { permanentlyDeletePage } = usePageHelper();
|
||||
const { currentWorkspaceId } = useAppState();
|
||||
const { toggleDeletePage } = usePageHelper();
|
||||
const { confirm } = useConfirm();
|
||||
const router = useRouter();
|
||||
const { id = '' } = useCurrentPageMeta() || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
style={{ marginRight: '24px' }}
|
||||
onClick={() => {
|
||||
toggleDeletePage(id);
|
||||
}}
|
||||
>
|
||||
Restore it
|
||||
</Button>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Permanently delete',
|
||||
content:
|
||||
"Once deleted, you can't undo this action. Do you confirm?",
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
router.push(`/workspace/${currentWorkspaceId}/all`);
|
||||
permanentlyDeletePage(id);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete permanently
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrashButtonGroup;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { PropsWithChildren, useState } from 'react';
|
||||
import React, { PropsWithChildren, ReactNode, useState } from 'react';
|
||||
import {
|
||||
StyledHeader,
|
||||
StyledHeaderRightSide,
|
||||
@@ -6,106 +6,12 @@ import {
|
||||
StyledBrowserWarning,
|
||||
StyledCloseButton,
|
||||
} from './styles';
|
||||
import {
|
||||
ExportIcon,
|
||||
EdgelessIcon,
|
||||
PaperIcon,
|
||||
ExportToHtmlIcon,
|
||||
ExportToMarkdownIcon,
|
||||
MoreVerticalIcon,
|
||||
FavouritesIcon,
|
||||
FavouritedIcon,
|
||||
TrashIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import ThemeModeSwitch from '@/components/theme-mode-switch';
|
||||
import { IconButton, Button } from '@/ui/button';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { getWarningMessage, shouldShowWarning } from './utils';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { SyncIcon } from './sync-icon';
|
||||
import { toast } from '@/ui/toast';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
const PopoverContent = () => {
|
||||
const { editor } = useAppState();
|
||||
const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
|
||||
const { changePageMode } = usePageHelper();
|
||||
const { confirm } = useConfirm();
|
||||
const {
|
||||
mode = 'page',
|
||||
id = '',
|
||||
favorite = false,
|
||||
title = '',
|
||||
} = useCurrentPageMeta() || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
toggleFavoritePage(id);
|
||||
toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites');
|
||||
}}
|
||||
icon={favorite ? <FavouritedIcon /> : <FavouritesIcon />}
|
||||
>
|
||||
{favorite ? 'Remove' : 'Add'} to favourites
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PaperIcon />}
|
||||
onClick={() => {
|
||||
changePageMode(id, mode === 'page' ? 'edgeless' : 'page');
|
||||
}}
|
||||
>
|
||||
Convert to {mode === 'page' ? 'Edgeless' : 'Page'}
|
||||
</MenuItem>
|
||||
<Menu
|
||||
placement="left-start"
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
editor && editor.contentParser.onExportHtml();
|
||||
}}
|
||||
icon={<ExportToHtmlIcon />}
|
||||
>
|
||||
Export to HTML
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
editor && editor.contentParser.onExportMarkdown();
|
||||
}}
|
||||
icon={<ExportToMarkdownIcon />}
|
||||
>
|
||||
Export to Markdown
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuItem icon={<ExportIcon />} isDir={true}>
|
||||
Export
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Delete page?',
|
||||
content: `${title || 'Untitled'} will be moved to Trash`,
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
}).then(confirm => {
|
||||
confirm && toggleDeletePage(id);
|
||||
toast('Moved to Trash');
|
||||
});
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
import EditorOptionMenu from './header-right-items/editor-option-menu';
|
||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
||||
import SyncUser from './header-right-items/sync-user';
|
||||
|
||||
const BrowserWarning = ({
|
||||
show,
|
||||
@@ -124,67 +30,23 @@ const BrowserWarning = ({
|
||||
);
|
||||
};
|
||||
|
||||
const HeaderRight = () => {
|
||||
const { permanentlyDeletePage } = usePageHelper();
|
||||
const { currentWorkspaceId } = useAppState();
|
||||
const { toggleDeletePage } = usePageHelper();
|
||||
const { confirm } = useConfirm();
|
||||
const router = useRouter();
|
||||
const currentPageMeta = useCurrentPageMeta();
|
||||
const isTrash = !!currentPageMeta?.trash;
|
||||
type HeaderRightItemNames =
|
||||
| 'editorOptionMenu'
|
||||
| 'trashButtonGroup'
|
||||
| 'themeModeSwitch'
|
||||
| 'syncUser';
|
||||
|
||||
if (isTrash) {
|
||||
const { id } = currentPageMeta;
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
style={{ marginRight: '24px' }}
|
||||
onClick={() => {
|
||||
toggleDeletePage(id);
|
||||
}}
|
||||
>
|
||||
Restore it
|
||||
</Button>
|
||||
<Button
|
||||
bold={true}
|
||||
shape="round"
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
confirm({
|
||||
title: 'Permanently delete',
|
||||
content:
|
||||
"Once deleted, you can't undo this action. Do you confirm?",
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
}).then(confirm => {
|
||||
if (confirm) {
|
||||
router.push(`/workspace/${currentWorkspaceId}/all`);
|
||||
permanentlyDeletePage(id);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete permanently
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<SyncIcon />
|
||||
<ThemeModeSwitch />
|
||||
<Menu content={<PopoverContent />} placement="bottom-end">
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
const HeaderRightItems: Record<HeaderRightItemNames, ReactNode> = {
|
||||
editorOptionMenu: <EditorOptionMenu />,
|
||||
trashButtonGroup: <TrashButtonGroup />,
|
||||
themeModeSwitch: <ThemeModeSwitch />,
|
||||
syncUser: <SyncUser />,
|
||||
};
|
||||
|
||||
export const Header = ({ children }: PropsWithChildren<{}>) => {
|
||||
export const Header = ({
|
||||
rightItems = ['syncUser'],
|
||||
children,
|
||||
}: PropsWithChildren<{ rightItems?: HeaderRightItemNames[] }>) => {
|
||||
const [showWarning, setShowWarning] = useState(shouldShowWarning());
|
||||
return (
|
||||
<StyledHeaderContainer hasWarning={showWarning}>
|
||||
@@ -197,7 +59,9 @@ export const Header = ({ children }: PropsWithChildren<{}>) => {
|
||||
<StyledHeader hasWarning={showWarning}>
|
||||
{children}
|
||||
<StyledHeaderRightSide>
|
||||
<HeaderRight />
|
||||
{rightItems.map(itemName => {
|
||||
return HeaderRightItems[itemName];
|
||||
})}
|
||||
</StyledHeaderRightSide>
|
||||
</StyledHeader>
|
||||
</StyledHeaderContainer>
|
||||
|
||||
Reference in New Issue
Block a user