refactor: header options menu (#3615)

This commit is contained in:
JimmFly
2023-08-09 01:14:24 +08:00
committed by GitHub
parent 7d16a8348c
commit 4e84b9a121
21 changed files with 183 additions and 128 deletions
+2 -2
View File
@@ -22,7 +22,7 @@
"@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/global": "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/lit": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly",
"@dnd-kit/core": "^6.0.8", "@dnd-kit/core": "^6.0.8",
@@ -33,7 +33,7 @@
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.2", "@mui/material": "^5.14.2",
"@react-hookz/web": "^23.1.0", "@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.8", "@toeverything/components": "^0.0.10",
"async-call-rpc": "^6.3.1", "async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0", "cmdk": "^0.2.0",
"css-spring": "^4.1.0", "css-spring": "^4.1.0",
@@ -5,12 +5,15 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { import {
EdgelessIcon, EdgelessIcon,
EditIcon,
FavoritedIcon, FavoritedIcon,
FavoriteIcon, FavoriteIcon,
ImportIcon,
MoreVerticalIcon, MoreVerticalIcon,
PageIcon, PageIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button'; import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import { import {
useBlockSuitePageMeta, useBlockSuitePageMeta,
usePageMetaHelper, usePageMetaHelper,
@@ -24,6 +27,8 @@ import { pageSettingFamily } from '../../../../atoms';
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 { toast } from '../../../../utils'; 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 { LanguageMenu } from './language-menu';
import { MenuThemeModeSwitch } from './theme-mode-switch'; import { MenuThemeModeSwitch } from './theme-mode-switch';
const CommonMenu = () => { const CommonMenu = () => {
@@ -52,7 +57,12 @@ const CommonMenu = () => {
</FlexWrapper> </FlexWrapper>
); );
}; };
const PageMenu = () => {
type PageMenuProps = {
rename?: () => void;
};
export const PageMenu = ({ rename }: PageMenuProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP // fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace(); const [workspace] = useCurrentWorkspace();
@@ -71,6 +81,7 @@ const PageMenu = () => {
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false); const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace);
const handleFavorite = useCallback(() => { const handleFavorite = useCallback(() => {
setPageMeta(pageId, { favorite: !favorite }); setPageMeta(pageId, { favorite: !favorite });
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']()); toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
@@ -90,12 +101,32 @@ const PageMenu = () => {
toast(t['Moved to Trash']()); toast(t['Moved to Trash']());
setOpenConfirm(false); setOpenConfirm(false);
}, [pageMeta.id, removeToTrash, t]); }, [pageMeta.id, removeToTrash, t]);
const menuItemStyle = {
padding: '4px 12px',
};
const EditMenu = ( 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 <MenuItem
data-testid="editor-option-menu-favorite" data-testid="editor-option-menu-favorite"
onClick={handleFavorite} onClick={handleFavorite}
style={menuItemStyle}
icon={ icon={
favorite ? ( favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} /> <FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
@@ -106,15 +137,34 @@ const PageMenu = () => {
> >
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()} {favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem> </MenuItem>
<MenuItem {/* {TODO: add tag and duplicate function support} */}
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />} {/* <MenuItem
data-testid="editor-option-menu-edgeless" icon={<TagsIcon />}
onClick={handleSwitchMode} data-testid="editor-option-menu-add-tag"
onClick={() => {}}
style={menuItemStyle}
> >
{t['Convert to ']()} {t['com.affine.header.option.add-tag']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()} </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> </MenuItem>
<Export /> <Export />
<Divider />
<MoveToTrash <MoveToTrash
data-testid="editor-option-menu-delete" data-testid="editor-option-menu-delete"
onItemClick={() => { onItemClick={() => {
@@ -132,10 +182,15 @@ const PageMenu = () => {
placement="bottom-end" placement="bottom-end"
disablePortal={true} disablePortal={true}
trigger="click" trigger="click"
menuStyles={{
borderRadius: '8px',
padding: '8px',
background: 'var(--affine-background-overlay-panel-color)',
}}
> >
<IconButton data-testid="editor-option-menu"> <div>
<MoreVerticalIcon /> <HeaderDropDownButton />
</IconButton> </div>
</Menu> </Menu>
<MoveToTrash.ConfirmModal <MoveToTrash.ConfirmModal
open={openConfirm} open={openConfirm}
@@ -60,10 +60,8 @@ interface HeaderItem {
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = { const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
[HeaderRightItemName.EditorOptionMenu]: { [HeaderRightItemName.EditorOptionMenu]: {
Component: EditorOptionMenu, Component: EditorOptionMenu,
availableWhen: (_, currentPage, { isPublic }) => { availableWhen: () => {
return ( return false;
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
);
}, },
}, },
}; };
@@ -1,18 +1,15 @@
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { Button } from '@toeverything/components/button';
import { import {
useBlockSuitePageMeta, useBlockSuitePageMeta,
usePageMetaHelper, usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta'; } from '@toeverything/hooks/use-block-suite-page-meta';
import { useSetAtom } from 'jotai';
import type { HTMLAttributes, ReactElement, ReactNode } from 'react'; import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
import { useCallback, useRef, useState } 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 { EditorModeSwitch } from './editor-mode-switch';
import type { BaseHeaderProps } from './header'; import type { BaseHeaderProps } from './header';
import { Header } from './header'; import { Header } from './header';
import { PageMenu } from './header-right-items/editor-option-menu';
import * as styles from './styles.css'; import * as styles from './styles.css';
export interface WorkspaceHeaderProps export interface WorkspaceHeaderProps
@@ -26,14 +23,12 @@ export const BlockSuiteEditorHeader = (
): ReactElement => { ): ReactElement => {
const { workspace, currentPage, children, isPublic } = props; const { workspace, currentPage, children, isPublic } = props;
// fixme(himself65): remove this atom and move it to props // fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find( const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id meta => meta.id === currentPage?.id
); );
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace); const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
const [isEditable, setIsEditable] = useState(false); const [isEditable, setIsEditable] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (isEditable) { if (isEditable) {
setIsEditable(!isEditable); setIsEditable(!isEditable);
@@ -45,7 +40,14 @@ export const BlockSuiteEditorHeader = (
setIsEditable(!isEditable); setIsEditable(!isEditable);
} }
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]); }, [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); const headerRef = useRef<HTMLDivElement>(null);
assertExists(pageMeta); assertExists(pageMeta);
const title = pageMeta?.title; const title = pageMeta?.title;
@@ -76,16 +78,8 @@ export const BlockSuiteEditorHeader = (
defaultValue={pageMeta?.title} defaultValue={pageMeta?.title}
onBlur={handleClick} onBlur={handleClick}
ref={inputRef} ref={inputRef}
onKeyDown={handleKeyDown}
/> />
<Button
onClick={handleClick}
data-testid="save-edit-button"
style={{
marginLeft: '12px',
}}
>
Save
</Button>
</div> </div>
) : ( ) : (
<span data-testid="title-edit-button" onClick={handleClick}> <span data-testid="title-edit-button" onClick={handleClick}>
@@ -94,11 +88,7 @@ export const BlockSuiteEditorHeader = (
)} )}
</div> </div>
<div className={styles.searchArrowWrapper}> <div className={styles.searchArrowWrapper}>
<QuickSearchButton <PageMenu rename={handleClick} />
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -205,6 +205,7 @@ export const searchArrowWrapper = style({
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
marginLeft: '4px',
}); });
export const pageListTitleWrapper = style({ export const pageListTitleWrapper = style({
@@ -14,21 +14,19 @@ const StyledIconButtonWithAnimate = styled(IconButton)(() => {
svg: { svg: {
transform: 'translateY(3px)', transform: 'translateY(3px)',
}, },
'::after': { backgroundColor: 'transparent !important',
background: 'var(--affine-background-primary-color)',
},
}, },
}; };
}); });
// fixme(himself65): need to refactor // fixme(himself65): need to refactor
export const QuickSearchButton = ({ export const HeaderDropDownButton = ({
onClick, onClick,
...props ...props
}: Omit<IconButtonProps, 'children'>) => { }: Omit<IconButtonProps, 'children'>) => {
return ( return (
<StyledIconButtonWithAnimate <StyledIconButtonWithAnimate
data-testid="header-quickSearchButton" data-testid="header-dropDownButton"
{...props} {...props}
onClick={e => { onClick={e => {
onClick?.(e); onClick?.(e);
@@ -1,45 +1,19 @@
import { RadioButton, RadioButtonGroup } from '@affine/component'; import { RadioButton, RadioButtonGroup } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useSetAtom } from 'jotai';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type React from 'react'; import type React from 'react';
import { import { allPageModeSelectAtom } from '../../../atoms';
allPageModeSelectAtom,
openQuickSearchModalAtom,
} from '../../../atoms';
import type { HeaderProps } from '../../blocksuite/workspace-header/header'; import type { HeaderProps } from '../../blocksuite/workspace-header/header';
import { Header } from '../../blocksuite/workspace-header/header'; import { Header } from '../../blocksuite/workspace-header/header';
import * as styles from '../../blocksuite/workspace-header/styles.css'; import * as styles from '../../blocksuite/workspace-header/styles.css';
import { QuickSearchButton } from '../quick-search-button';
export interface WorkspaceTitleProps export interface WorkspaceTitleProps
extends React.PropsWithChildren<HeaderProps> { extends React.PropsWithChildren<HeaderProps> {
icon?: ReactNode; 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) => { export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [value, setMode] = useAtom(allPageModeSelectAtom); const [value, setMode] = useAtom(allPageModeSelectAtom);
+1 -15
View File
@@ -7,8 +7,6 @@ import {
import type { Collection } from '@affine/env/filter'; import type { Collection } from '@affine/env/filter';
import type { WorkspaceHeaderProps } from '@affine/env/workspace'; import type { WorkspaceHeaderProps } from '@affine/env/workspace';
import { WorkspaceFlavour, WorkspaceSubPath } 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 type { ReactElement } from 'react';
import { useCallback } 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 { useWorkspace } from '../hooks/use-workspace';
import { BlockSuiteEditorHeader } from './blocksuite/workspace-header'; import { BlockSuiteEditorHeader } from './blocksuite/workspace-header';
import { filterContainerStyle } from './filter-container.css'; import { filterContainerStyle } from './filter-container.css';
import { WorkspaceModeFilterTab, WorkspaceTitle } from './pure/workspace-title'; import { WorkspaceModeFilterTab } from './pure/workspace-title';
export function WorkspaceHeader({ export function WorkspaceHeader({
currentWorkspaceId, currentWorkspaceId,
currentEntry, currentEntry,
}: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement { }: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement {
const setting = useCollectionManager(currentWorkspaceId); const setting = useCollectionManager(currentWorkspaceId);
const t = useAFFiNEI18N();
const saveToCollection = useCallback( const saveToCollection = useCallback(
async (collection: Collection) => { async (collection: Collection) => {
await setting.saveCollection(collection); await setting.saveCollection(collection);
@@ -89,17 +86,6 @@ export function WorkspaceHeader({
{filterContainer} {filterContainer}
</> </>
); );
} else if (currentEntry.subPath === WorkspaceSubPath.SETTING) {
return (
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPublic={false}
icon={<SettingsIcon />}
>
{t['Workspace Settings']()}
</WorkspaceTitle>
);
} else if ( } else if (
currentEntry.subPath === WorkspaceSubPath.SHARED || currentEntry.subPath === WorkspaceSubPath.SHARED ||
currentEntry.subPath === WorkspaceSubPath.TRASH currentEntry.subPath === WorkspaceSubPath.TRASH
+1 -1
View File
@@ -34,7 +34,7 @@
"@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/global": "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/lit": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly",
"react": "18.2.0", "react": "18.2.0",
+2 -2
View File
@@ -36,7 +36,7 @@
"@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-toast": "^1.1.4",
"@toeverything/hooks": "workspace:*", "@toeverything/hooks": "workspace:*",
"@toeverything/theme": "^0.7.9", "@toeverything/theme": "^0.7.11",
"@vanilla-extract/dynamic": "^2.0.3", "@vanilla-extract/dynamic": "^2.0.3",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
@@ -54,7 +54,7 @@
"@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/blocks": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/editor": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/global": "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/lit": "0.0.0-20230807164933-9f6fb698-nightly",
"@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly", "@blocksuite/store": "0.0.0-20230807164933-9f6fb698-nightly",
"@types/react": "^18.2.17", "@types/react": "^18.2.17",
@@ -17,6 +17,10 @@ import { Menu, MenuItem } from '../../..';
import { getContentParser } from './get-content-parser'; import { getContentParser } from './get-content-parser';
import type { CommonMenuItemProps } from './types'; import type { CommonMenuItemProps } from './types';
const MenuItemStyle = {
padding: '4px 12px',
};
export const ExportToPdfMenuItem = ({ export const ExportToPdfMenuItem = ({
onSelect, onSelect,
}: CommonMenuItemProps<{ type: 'pdf' }>) => { }: CommonMenuItemProps<{ type: 'pdf' }>) => {
@@ -83,6 +87,7 @@ export const ExportToPdfMenuItem = ({
data-testid="export-to-pdf" data-testid="export-to-pdf"
onClick={onClickDownloadPDF} onClick={onClickDownloadPDF}
icon={<ExportToPdfIcon />} icon={<ExportToPdfIcon />}
style={MenuItemStyle}
> >
{t['Export to PDF']()} {t['Export to PDF']()}
</MenuItem> </MenuItem>
@@ -128,6 +133,7 @@ export const ExportToHtmlMenuItem = ({
data-testid="export-to-html" data-testid="export-to-html"
onClick={onClickExportHtml} onClick={onClickExportHtml}
icon={<ExportToHtmlIcon />} icon={<ExportToHtmlIcon />}
style={MenuItemStyle}
> >
{t['Export to HTML']()} {t['Export to HTML']()}
</MenuItem> </MenuItem>
@@ -176,6 +182,7 @@ export const ExportToPngMenuItem = ({
data-testid="export-to-png" data-testid="export-to-png"
onClick={onClickDownloadPNG} onClick={onClickDownloadPNG}
icon={<ExportToPngIcon />} icon={<ExportToPngIcon />}
style={MenuItemStyle}
> >
{t['Export to PNG']()} {t['Export to PNG']()}
</MenuItem> </MenuItem>
@@ -222,6 +229,7 @@ export const ExportToMarkdownMenuItem = ({
data-testid="export-to-markdown" data-testid="export-to-markdown"
onClick={onClickExportMarkdown} onClick={onClickExportMarkdown}
icon={<ExportToMarkdownIcon />} icon={<ExportToMarkdownIcon />}
style={MenuItemStyle}
> >
{t['Export to Markdown']()} {t['Export to Markdown']()}
</MenuItem> </MenuItem>
@@ -236,8 +244,8 @@ export const Export = ({
return ( return (
<Menu <Menu
width={248} width={248}
placement="left"
trigger="click" trigger="click"
placement="right-start"
content={ content={
<> <>
<ExportToPdfMenuItem></ExportToPdfMenuItem> <ExportToPdfMenuItem></ExportToPdfMenuItem>
@@ -246,11 +254,17 @@ export const Export = ({
<ExportToMarkdownMenuItem></ExportToMarkdownMenuItem> <ExportToMarkdownMenuItem></ExportToMarkdownMenuItem>
</> </>
} }
menuStyles={{
borderRadius: '8px',
padding: '8px',
background: 'var(--affine-background-overlay-panel-color)',
}}
> >
<MenuItem <MenuItem
data-testid="export-menu" data-testid="export-menu"
icon={<ExportIcon />} icon={<ExportIcon />}
endIcon={<ArrowRightSmallIcon />} endIcon={<ArrowRightSmallIcon />}
style={{ padding: '4px 12px' }}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
onItemClick?.(); onItemClick?.();
@@ -1,6 +1,7 @@
import { globalStyle, style } from '@vanilla-extract/css'; import { globalStyle, style } from '@vanilla-extract/css';
export const moveToTrashStyle = style({ export const moveToTrashStyle = style({
padding: '4px 12px',
':hover': { ':hover': {
backgroundColor: 'var(--affine-background-error-color)', backgroundColor: 'var(--affine-background-error-color)',
color: 'var(--affine-error-color)', color: 'var(--affine-error-color)',
+3 -1
View File
@@ -428,5 +428,7 @@
"Choose your font style": "Choose your font style", "Choose your font style": "Choose your font style",
"Switch": "Switch", "Switch": "Switch",
"com.affine.settings.workspace.publish.local-tooltip": "Enable AFFiNE Cloud to publish this Workspace", "com.affine.settings.workspace.publish.local-tooltip": "Enable AFFiNE Cloud to publish this Workspace",
"com.affine.settings.workspace.member.local-tooltip": "Enable AFFiNE Cloud to collaborate with others" "com.affine.settings.workspace.member.local-tooltip": "Enable AFFiNE Cloud to collaborate with others",
"com.affine.header.option.add-tag": "Add Tag",
"com.affine.header.option.duplicate": "Duplicate"
} }
+1 -1
View File
@@ -20,7 +20,7 @@
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",
"@affine/sdk": "workspace:*", "@affine/sdk": "workspace:*",
"@blocksuite/icons": "^2.1.29", "@blocksuite/icons": "^2.1.30",
"foxact": "^0.2.17", "foxact": "^0.2.17",
"link-preview-js": "^3.0.4" "link-preview-js": "^3.0.4"
}, },
+1 -1
View File
@@ -16,7 +16,7 @@
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",
"@affine/sdk": "workspace:*", "@affine/sdk": "workspace:*",
"@toeverything/components": "^0.0.8", "@toeverything/components": "^0.0.10",
"idb": "^7.1.1", "idb": "^7.1.1",
"langchain": "^0.0.118", "langchain": "^0.0.118",
"marked": "^7.0.1", "marked": "^7.0.1",
+2 -2
View File
@@ -17,8 +17,8 @@
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",
"@affine/sdk": "workspace:*", "@affine/sdk": "workspace:*",
"@blocksuite/icons": "^2.1.29", "@blocksuite/icons": "^2.1.30",
"@toeverything/components": "^0.0.8" "@toeverything/components": "^0.0.10"
}, },
"devDependencies": { "devDependencies": {
"@affine/plugin-cli": "workspace:*" "@affine/plugin-cli": "workspace:*"
+3 -3
View File
@@ -16,9 +16,9 @@
"dependencies": { "dependencies": {
"@affine/component": "workspace:*", "@affine/component": "workspace:*",
"@affine/sdk": "workspace:*", "@affine/sdk": "workspace:*",
"@blocksuite/icons": "^2.1.29", "@blocksuite/icons": "^2.1.30",
"@toeverything/components": "^0.0.8", "@toeverything/components": "^0.0.10",
"@toeverything/theme": "^0.7.9", "@toeverything/theme": "^0.7.11",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"foxact": "^0.2.17", "foxact": "^0.2.17",
"react-error-boundary": "^4.0.10", "react-error-boundary": "^4.0.10",
@@ -70,7 +70,7 @@ test('Able to insert the title of an untitled page', async ({ page }) => {
await titleBarTextContent.click({ delay: 100 }); await titleBarTextContent.click({ delay: 100 });
const titleContent = await page.getByTestId('title-content'); const titleContent = await page.getByTestId('title-content');
await titleContent.fill('test'); await titleContent.fill('test');
await page.getByTestId('save-edit-button').click({ delay: 100 }); await titleContent.blur();
expect(await titleBarTextContent.textContent()).toBe('test'); expect(await titleBarTextContent.textContent()).toBe('test');
}); });
@@ -81,11 +81,11 @@ test('Able to edit the title of an existing page', async ({ page }) => {
await titleBarTextContent.click({ delay: 100 }); await titleBarTextContent.click({ delay: 100 });
const titleContent = await page.getByTestId('title-content'); const titleContent = await page.getByTestId('title-content');
await titleContent.fill('test'); await titleContent.fill('test');
await page.getByTestId('save-edit-button').click({ delay: 100 }); await titleContent.blur();
expect(await titleBarTextContent.textContent()).toBe('test'); expect(await titleBarTextContent.textContent()).toBe('test');
await titleBarTextContent.click({ delay: 100 }); await titleBarTextContent.click({ delay: 100 });
await titleContent.fill('Sample text 2'); await titleContent.fill('Sample text 2');
await page.getByTestId('save-edit-button').click({ delay: 100 }); await titleContent.blur();
expect(await titleBarTextContent.textContent()).toBe('Sample text 2'); expect(await titleBarTextContent.textContent()).toBe('Sample text 2');
}); });
@@ -98,10 +98,28 @@ test('Clearing out the title bar will remove the page title', async ({
await titleBarTextContent.click({ delay: 100 }); await titleBarTextContent.click({ delay: 100 });
const titleContent = await page.getByTestId('title-content'); const titleContent = await page.getByTestId('title-content');
await titleContent.fill('test'); await titleContent.fill('test');
await page.getByTestId('save-edit-button').click({ delay: 100 }); await titleContent.blur();
expect(await titleBarTextContent.textContent()).toBe('test'); expect(await titleBarTextContent.textContent()).toBe('test');
await titleBarTextContent.click({ delay: 100 }); await titleBarTextContent.click({ delay: 100 });
await titleContent.fill(''); await titleContent.fill('');
await page.getByTestId('save-edit-button').click({ delay: 100 }); await titleContent.blur();
expect(await titleBarTextContent.textContent()).toBe('Untitled');
});
test('Rename by editor header items, save with shortcut', async ({ page }) => {
await openHomePage(page);
await waitEditorLoad(page);
await clickPageMoreActions(page);
const menusRenameItem = page.getByTestId('editor-option-menu-rename');
await menusRenameItem.click({ delay: 100 });
const titleBarTextContent = page.getByTestId('title-edit-button');
const titleContent = page.getByTestId('title-content');
await titleContent.fill('test');
await page.keyboard.press('Enter');
expect(await titleBarTextContent.textContent()).toBe('test');
await clickPageMoreActions(page);
await menusRenameItem.click({ delay: 100 });
await titleContent.fill('');
await page.keyboard.press('Escape');
expect(await titleBarTextContent.textContent()).toBe('Untitled'); expect(await titleBarTextContent.textContent()).toBe('Untitled');
}); });
@@ -0,0 +1,18 @@
import { test } from '@affine-test/kit/playwright';
import { openHomePage } from '@affine-test/kit/utils/load-page';
import {
clickPageMoreActions,
waitEditorLoad,
} from '@affine-test/kit/utils/page-logic';
import { expect } from '@playwright/test';
test('Click import page item', async ({ page }) => {
await openHomePage(page);
await waitEditorLoad(page);
await clickPageMoreActions(page);
const importItem = page.getByTestId('editor-option-menu-import');
await importItem.click();
const importModal = page.locator('import-page');
expect(await importModal.isVisible()).toBe(true);
});
+1 -1
View File
@@ -46,6 +46,6 @@ export const createLinkedPage = async (page: Page, pageName?: string) => {
export async function clickPageMoreActions(page: Page) { export async function clickPageMoreActions(page: Page) {
return page return page
.getByTestId('editor-header-items') .getByTestId('editor-header-items')
.getByTestId('editor-option-menu') .getByTestId('header-dropDownButton')
.click(); .click();
} }
+25 -25
View File
@@ -96,7 +96,7 @@ __metadata:
"@affine/component": "workspace:*" "@affine/component": "workspace:*"
"@affine/plugin-cli": "workspace:*" "@affine/plugin-cli": "workspace:*"
"@affine/sdk": "workspace:*" "@affine/sdk": "workspace:*"
"@blocksuite/icons": ^2.1.29 "@blocksuite/icons": ^2.1.30
foxact: ^0.2.17 foxact: ^0.2.17
link-preview-js: ^3.0.4 link-preview-js: ^3.0.4
languageName: unknown languageName: unknown
@@ -129,7 +129,7 @@ __metadata:
"@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/global": 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/lit": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly
"@dnd-kit/core": ^6.0.8 "@dnd-kit/core": ^6.0.8
@@ -148,7 +148,7 @@ __metadata:
"@radix-ui/react-scroll-area": ^1.0.4 "@radix-ui/react-scroll-area": ^1.0.4
"@radix-ui/react-toast": ^1.1.4 "@radix-ui/react-toast": ^1.1.4
"@toeverything/hooks": "workspace:*" "@toeverything/hooks": "workspace:*"
"@toeverything/theme": ^0.7.9 "@toeverything/theme": ^0.7.11
"@types/react": ^18.2.17 "@types/react": ^18.2.17
"@types/react-datepicker": ^4.15.0 "@types/react-datepicker": ^4.15.0
"@types/react-dnd": ^3.0.2 "@types/react-dnd": ^3.0.2
@@ -186,7 +186,7 @@ __metadata:
"@affine/component": "workspace:*" "@affine/component": "workspace:*"
"@affine/plugin-cli": "workspace:*" "@affine/plugin-cli": "workspace:*"
"@affine/sdk": "workspace:*" "@affine/sdk": "workspace:*"
"@toeverything/components": ^0.0.8 "@toeverything/components": ^0.0.10
"@types/marked": ^5.0.1 "@types/marked": ^5.0.1
idb: ^7.1.1 idb: ^7.1.1
jotai: ^2.2.2 jotai: ^2.2.2
@@ -220,7 +220,7 @@ __metadata:
"@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/global": 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/lit": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly
"@dnd-kit/core": ^6.0.8 "@dnd-kit/core": ^6.0.8
@@ -236,7 +236,7 @@ __metadata:
"@sentry/webpack-plugin": ^2.5.0 "@sentry/webpack-plugin": ^2.5.0
"@svgr/webpack": ^8.0.1 "@svgr/webpack": ^8.0.1
"@swc/core": ^1.3.74 "@swc/core": ^1.3.74
"@toeverything/components": ^0.0.8 "@toeverything/components": ^0.0.10
"@types/webpack-env": ^1.18.1 "@types/webpack-env": ^1.18.1
async-call-rpc: ^6.3.1 async-call-rpc: ^6.3.1
cmdk: ^0.2.0 cmdk: ^0.2.0
@@ -403,8 +403,8 @@ __metadata:
"@affine/component": "workspace:*" "@affine/component": "workspace:*"
"@affine/plugin-cli": "workspace:*" "@affine/plugin-cli": "workspace:*"
"@affine/sdk": "workspace:*" "@affine/sdk": "workspace:*"
"@blocksuite/icons": ^2.1.29 "@blocksuite/icons": ^2.1.30
"@toeverything/components": ^0.0.8 "@toeverything/components": ^0.0.10
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -429,9 +429,9 @@ __metadata:
"@affine/component": "workspace:*" "@affine/component": "workspace:*"
"@affine/plugin-cli": "workspace:*" "@affine/plugin-cli": "workspace:*"
"@affine/sdk": "workspace:*" "@affine/sdk": "workspace:*"
"@blocksuite/icons": ^2.1.29 "@blocksuite/icons": ^2.1.30
"@toeverything/components": ^0.0.8 "@toeverything/components": ^0.0.10
"@toeverything/theme": ^0.7.9 "@toeverything/theme": ^0.7.11
clsx: ^2.0.0 clsx: ^2.0.0
foxact: ^0.2.17 foxact: ^0.2.17
react-error-boundary: ^4.0.10 react-error-boundary: ^4.0.10
@@ -641,7 +641,7 @@ __metadata:
"@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/blocks": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/editor": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/global": 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/lit": 0.0.0-20230807164933-9f6fb698-nightly
"@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly "@blocksuite/store": 0.0.0-20230807164933-9f6fb698-nightly
"@storybook/addon-actions": ^7.1.1 "@storybook/addon-actions": ^7.1.1
@@ -3421,13 +3421,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@blocksuite/icons@npm:^2.1.26, @blocksuite/icons@npm:^2.1.29": "@blocksuite/icons@npm:^2.1.30":
version: 2.1.29 version: 2.1.30
resolution: "@blocksuite/icons@npm:2.1.29" resolution: "@blocksuite/icons@npm:2.1.30"
peerDependencies: peerDependencies:
"@types/react": ^18.0.25 "@types/react": ^18.0.25
react: ^18.2.0 react: ^18.2.0
checksum: f853a709fc74aa79c4deb8d19d5554f0c18f5cafdef617338fdbc85fcc9b45e7adfa79ec27cf01e2dc6af6cb278efececfbac5da1cbc6cfe031d8f1abe73607b checksum: 4c1aaf56304f5dfa3f330a6f8c6cc2526393dd5aae06940c7291755515e7051054357943426af519d6144a99e283170fe9f20f575ebd9344c480b4343a690648
languageName: node languageName: node
linkType: hard linkType: hard
@@ -11250,17 +11250,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@toeverything/components@npm:^0.0.8": "@toeverything/components@npm:^0.0.10":
version: 0.0.8 version: 0.0.10
resolution: "@toeverything/components@npm:0.0.8" resolution: "@toeverything/components@npm:0.0.10"
dependencies: dependencies:
"@blocksuite/icons": ^2.1.26 "@blocksuite/icons": ^2.1.30
peerDependencies: peerDependencies:
"@radix-ui/react-avatar": ^1 "@radix-ui/react-avatar": ^1
clsx: ^2 clsx: ^2
react: ^18 react: ^18
react-dom: ^18 react-dom: ^18
checksum: 9c955d3c46729397e92031cc6a5ba0f15eb8a67893862b4c6c017c87fff2ab8e9ee8505695b0da571f2af35ad6545f643fc34f657f1c9d1913648796971540d9 checksum: b1e15d5a03e6e83ca6dfa4794b23047ea7dba66cb333246be01ac96b5871ed16c20c455931ebe9dd38d1fb98793acee9510e6e2ad8d5ffba7956d2df24d887dd
languageName: node languageName: node
linkType: hard linkType: hard
@@ -11321,10 +11321,10 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@toeverything/theme@npm:^0.7.9": "@toeverything/theme@npm:^0.7.11":
version: 0.7.9 version: 0.7.11
resolution: "@toeverything/theme@npm:0.7.9" resolution: "@toeverything/theme@npm:0.7.11"
checksum: ac7eeea49f9099e34e8aacbd327ef5e48cc9fc02c32a20309da3dbcb4363c8dda3d2c887cf0e8eb8bb0fa2c50e73f123332f7009d2926961ef7ecbbb921a856d checksum: 4676ff7714248aa5e5224a7ee96d40ec30e05f4a8c91a31b36ec4efd244c23ffd7953768d15d0f5b7d560579db9e98f629cebb85afcb92f0db654f99c2edc908
languageName: node languageName: node
linkType: hard linkType: hard