mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
refactor: header options menu (#3615)
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
"@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/icons": "^2.1.29",
|
||||
"@blocksuite/icons": "^2.1.30",
|
||||
"@blocksuite/lit": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.8",
|
||||
"@toeverything/components": "^0.0.10",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
|
||||
@@ -5,12 +5,15 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
EditIcon,
|
||||
FavoritedIcon,
|
||||
FavoriteIcon,
|
||||
ImportIcon,
|
||||
MoreVerticalIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
@@ -24,6 +27,8 @@ import { pageSettingFamily } from '../../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { toast } from '../../../../utils';
|
||||
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
|
||||
import { usePageHelper } from '../../block-suite-page-list/utils';
|
||||
import { LanguageMenu } from './language-menu';
|
||||
import { MenuThemeModeSwitch } from './theme-mode-switch';
|
||||
const CommonMenu = () => {
|
||||
@@ -52,7 +57,12 @@ const CommonMenu = () => {
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
const PageMenu = () => {
|
||||
|
||||
type PageMenuProps = {
|
||||
rename?: () => void;
|
||||
};
|
||||
|
||||
export const PageMenu = ({ rename }: PageMenuProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
@@ -71,6 +81,7 @@ const PageMenu = () => {
|
||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const [openConfirm, setOpenConfirm] = useState(false);
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const { importFile } = usePageHelper(blockSuiteWorkspace);
|
||||
const handleFavorite = useCallback(() => {
|
||||
setPageMeta(pageId, { favorite: !favorite });
|
||||
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
|
||||
@@ -90,12 +101,32 @@ const PageMenu = () => {
|
||||
toast(t['Moved to Trash']());
|
||||
setOpenConfirm(false);
|
||||
}, [pageMeta.id, removeToTrash, t]);
|
||||
|
||||
const menuItemStyle = {
|
||||
padding: '4px 12px',
|
||||
};
|
||||
const EditMenu = (
|
||||
<>
|
||||
<MenuItem
|
||||
icon={<EditIcon />}
|
||||
data-testid="editor-option-menu-rename"
|
||||
onClick={rename}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Rename']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={handleSwitchMode}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onClick={handleFavorite}
|
||||
style={menuItemStyle}
|
||||
icon={
|
||||
favorite ? (
|
||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||
@@ -106,15 +137,34 @@ const PageMenu = () => {
|
||||
>
|
||||
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={handleSwitchMode}
|
||||
{/* {TODO: add tag and duplicate function support} */}
|
||||
{/* <MenuItem
|
||||
icon={<TagsIcon />}
|
||||
data-testid="editor-option-menu-add-tag"
|
||||
onClick={() => {}}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
{t['com.affine.header.option.add-tag']()}
|
||||
</MenuItem> */}
|
||||
<Divider />
|
||||
{/* <MenuItem
|
||||
icon={<DuplicateIcon />}
|
||||
data-testid="editor-option-menu-duplicate"
|
||||
onClick={() => {}}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.header.option.duplicate']()}
|
||||
</MenuItem> */}
|
||||
<MenuItem
|
||||
icon={<ImportIcon />}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={importFile}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['Import']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<Divider />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
@@ -132,10 +182,15 @@ const PageMenu = () => {
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
menuStyles={{
|
||||
borderRadius: '8px',
|
||||
padding: '8px',
|
||||
background: 'var(--affine-background-overlay-panel-color)',
|
||||
}}
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu">
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
<div>
|
||||
<HeaderDropDownButton />
|
||||
</div>
|
||||
</Menu>
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={openConfirm}
|
||||
|
||||
@@ -60,10 +60,8 @@ interface HeaderItem {
|
||||
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
||||
[HeaderRightItemName.EditorOptionMenu]: {
|
||||
Component: EditorOptionMenu,
|
||||
availableWhen: (_, currentPage, { isPublic }) => {
|
||||
return (
|
||||
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
|
||||
);
|
||||
availableWhen: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { openQuickSearchModalAtom } from '../../../atoms';
|
||||
import { QuickSearchButton } from '../../pure/quick-search-button';
|
||||
import { EditorModeSwitch } from './editor-mode-switch';
|
||||
import type { BaseHeaderProps } from './header';
|
||||
import { Header } from './header';
|
||||
import { PageMenu } from './header-right-items/editor-option-menu';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface WorkspaceHeaderProps
|
||||
@@ -26,14 +23,12 @@ export const BlockSuiteEditorHeader = (
|
||||
): ReactElement => {
|
||||
const { workspace, currentPage, children, isPublic } = props;
|
||||
// fixme(himself65): remove this atom and move it to props
|
||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
|
||||
meta => meta.id === currentPage?.id
|
||||
);
|
||||
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (isEditable) {
|
||||
setIsEditable(!isEditable);
|
||||
@@ -45,7 +40,14 @@ export const BlockSuiteEditorHeader = (
|
||||
setIsEditable(!isEditable);
|
||||
}
|
||||
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
handleClick();
|
||||
}
|
||||
},
|
||||
[handleClick]
|
||||
);
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta?.title;
|
||||
@@ -76,16 +78,8 @@ export const BlockSuiteEditorHeader = (
|
||||
defaultValue={pageMeta?.title}
|
||||
onBlur={handleClick}
|
||||
ref={inputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
data-testid="save-edit-button"
|
||||
style={{
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span data-testid="title-edit-button" onClick={handleClick}>
|
||||
@@ -94,11 +88,7 @@ export const BlockSuiteEditorHeader = (
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.searchArrowWrapper}>
|
||||
<QuickSearchButton
|
||||
onClick={() => {
|
||||
setOpenQuickSearch(true);
|
||||
}}
|
||||
/>
|
||||
<PageMenu rename={handleClick} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -205,6 +205,7 @@ export const searchArrowWrapper = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: '4px',
|
||||
});
|
||||
|
||||
export const pageListTitleWrapper = style({
|
||||
|
||||
@@ -14,21 +14,19 @@ const StyledIconButtonWithAnimate = styled(IconButton)(() => {
|
||||
svg: {
|
||||
transform: 'translateY(3px)',
|
||||
},
|
||||
'::after': {
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
},
|
||||
backgroundColor: 'transparent !important',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// fixme(himself65): need to refactor
|
||||
export const QuickSearchButton = ({
|
||||
export const HeaderDropDownButton = ({
|
||||
onClick,
|
||||
...props
|
||||
}: Omit<IconButtonProps, 'children'>) => {
|
||||
return (
|
||||
<StyledIconButtonWithAnimate
|
||||
data-testid="header-quickSearchButton"
|
||||
data-testid="header-dropDownButton"
|
||||
{...props}
|
||||
onClick={e => {
|
||||
onClick?.(e);
|
||||
@@ -1,45 +1,19 @@
|
||||
import { RadioButton, RadioButtonGroup } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactNode } from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
allPageModeSelectAtom,
|
||||
openQuickSearchModalAtom,
|
||||
} from '../../../atoms';
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
|
||||
import { Header } from '../../blocksuite/workspace-header/header';
|
||||
import * as styles from '../../blocksuite/workspace-header/styles.css';
|
||||
import { QuickSearchButton } from '../quick-search-button';
|
||||
|
||||
export interface WorkspaceTitleProps
|
||||
extends React.PropsWithChildren<HeaderProps> {
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
export const WorkspaceTitle = ({
|
||||
icon,
|
||||
children,
|
||||
...props
|
||||
}: WorkspaceTitleProps) => {
|
||||
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
|
||||
return (
|
||||
<Header {...props}>
|
||||
<div className={styles.pageListTitleWrapper}>
|
||||
<div className={styles.pageListTitleIcon}>{icon}</div>
|
||||
{children}
|
||||
<QuickSearchButton
|
||||
onClick={() => {
|
||||
setOpenQuickSearch(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [value, setMode] = useAtom(allPageModeSelectAtom);
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { WorkspaceHeaderProps } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -16,14 +14,13 @@ import { useGetPageInfoById } from '../hooks/use-get-page-info';
|
||||
import { useWorkspace } from '../hooks/use-workspace';
|
||||
import { BlockSuiteEditorHeader } from './blocksuite/workspace-header';
|
||||
import { filterContainerStyle } from './filter-container.css';
|
||||
import { WorkspaceModeFilterTab, WorkspaceTitle } from './pure/workspace-title';
|
||||
import { WorkspaceModeFilterTab } from './pure/workspace-title';
|
||||
|
||||
export function WorkspaceHeader({
|
||||
currentWorkspaceId,
|
||||
currentEntry,
|
||||
}: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement {
|
||||
const setting = useCollectionManager(currentWorkspaceId);
|
||||
const t = useAFFiNEI18N();
|
||||
const saveToCollection = useCallback(
|
||||
async (collection: Collection) => {
|
||||
await setting.saveCollection(collection);
|
||||
@@ -89,17 +86,6 @@ export function WorkspaceHeader({
|
||||
{filterContainer}
|
||||
</>
|
||||
);
|
||||
} else if (currentEntry.subPath === WorkspaceSubPath.SETTING) {
|
||||
return (
|
||||
<WorkspaceTitle
|
||||
workspace={currentWorkspace}
|
||||
currentPage={null}
|
||||
isPublic={false}
|
||||
icon={<SettingsIcon />}
|
||||
>
|
||||
{t['Workspace Settings']()}
|
||||
</WorkspaceTitle>
|
||||
);
|
||||
} else if (
|
||||
currentEntry.subPath === WorkspaceSubPath.SHARED ||
|
||||
currentEntry.subPath === WorkspaceSubPath.TRASH
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/icons": "^2.1.29",
|
||||
"@blocksuite/icons": "^2.1.30",
|
||||
"@blocksuite/lit": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly",
|
||||
"react": "18.2.0",
|
||||
|
||||
Reference in New Issue
Block a user