diff --git a/.eslintrc.js b/.eslintrc.js
index 227bcac1d0..c96058a4ed 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -247,7 +247,8 @@ const config = {
'react-hooks/exhaustive-deps': [
'warn',
{
- additionalHooks: '(useAsyncCallback|useDraggable|useDropTarget)',
+ additionalHooks:
+ '(useAsyncCallback|useCatchEventCallback|useDraggable|useDropTarget)',
},
],
},
diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx
index ec146a10ff..66921a0a0c 100644
--- a/packages/frontend/core/src/components/affine/reference-link/index.tsx
+++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx
@@ -106,7 +106,6 @@ export function AffinePageReference({
e.preventDefault();
e.stopPropagation();
peekView.open(ref.current).catch(console.error);
- return false; // means this click is handled
}
if (isInPeekView) {
peekView.close();
diff --git a/packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx b/packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx
index 369c623d0f..ff2e756acc 100644
--- a/packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx
+++ b/packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx
@@ -29,6 +29,7 @@ export function AddPageButton({
style={style}
className={clsx([styles.root, className])}
onClick={onClick}
+ onAuxClick={onClick}
>
diff --git a/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx b/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx
index 2282d85694..c9a5f95dfa 100644
--- a/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx
+++ b/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx
@@ -1,13 +1,14 @@
import { Checkbox, useDraggable } from '@affine/component';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
+import { stopPropagation } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import type { ForwardedRef, PropsWithChildren } from 'react';
import { forwardRef, useCallback, useMemo } from 'react';
import { selectionStateAtom, useAtom } from '../scoped-atoms';
import type { CollectionListItemProps, PageListItemProps } from '../types';
-import { ColWrapper, stopPropagation } from '../utils';
+import { ColWrapper } from '../utils';
import * as styles from './collection-list-item.css';
const ListTitleCell = ({
diff --git a/packages/frontend/core/src/components/page-list/components/new-page-button.tsx b/packages/frontend/core/src/components/page-list/components/new-page-button.tsx
index 05a026e79b..0c909b80e4 100644
--- a/packages/frontend/core/src/components/page-list/components/new-page-button.tsx
+++ b/packages/frontend/core/src/components/page-list/components/new-page-button.tsx
@@ -35,6 +35,7 @@ export const CreateNewPagePopup = ({
desc={t['com.affine.write_with_a_blank_page']()}
right={}
onClick={createNewPage}
+ onAuxClick={createNewPage}
data-testid="new-page-button-in-all-page"
/>
}
onClick={createNewEdgeless}
+ onAuxClick={createNewEdgeless}
data-testid="new-edgeless-button-in-all-page"
/>
{importFile ? (
@@ -117,6 +119,7 @@ export const NewPageButton = ({
setOpen(open => !open), [])}
>
{children}
diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx
index 4a0929dba3..564671d022 100644
--- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx
+++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx
@@ -10,6 +10,7 @@ import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { track } from '@affine/core/mixpanel';
import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag';
+import { isNewTabTrigger } from '@affine/core/utils';
import type { Collection } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
import {
@@ -66,12 +67,9 @@ export const PageListHeader = () => {
size="small"
testId="new-page-button-trigger"
onCreateEdgeless={e =>
- // todo: abstract this for ctrl check
- createEdgeless(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
- }
- onCreatePage={e =>
- createPage(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
+ createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
}
+ onCreatePage={e => createPage(isNewTabTrigger(e) ? 'new-tab' : true)}
onImportFile={onImportFile}
>
{t['New Page']()}
diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx
index 9b93cca417..2675a47f3f 100644
--- a/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx
+++ b/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx
@@ -1,6 +1,7 @@
import { Checkbox, Tooltip, useDraggable } from '@affine/component';
import { TagService } from '@affine/core/modules/tag';
import type { AffineDNDData } from '@affine/core/types/dnd';
+import { stopPropagation } from '@affine/core/utils';
import { i18nTime } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
import type { ForwardedRef, PropsWithChildren } from 'react';
@@ -15,7 +16,7 @@ import {
} from '../scoped-atoms';
import type { PageListItemProps } from '../types';
import { useAllDocDisplayProperties } from '../use-all-doc-display-properties';
-import { ColWrapper, stopPropagation } from '../utils';
+import { ColWrapper } from '../utils';
import * as styles from './page-list-item.css';
import { PageTags } from './page-tags';
@@ -310,18 +311,18 @@ const PageListItemWrapper = forwardRef(
if (!selectionState.selectable) {
return;
}
- stopPropagation(e);
+ e.stopPropagation();
const currentIndex = pageIds.indexOf(pageId);
if (e.shiftKey) {
+ e.preventDefault();
if (!selectionState.selectionActive) {
setSelectionActive(true);
setAnchorIndex(currentIndex);
onClick?.();
- return false;
+ } else {
+ handleShiftClick(currentIndex);
}
- handleShiftClick(currentIndex);
- return false;
} else {
setAnchorIndex(undefined);
setRangeIds([]);
diff --git a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx
index a903a32dac..e064248cd6 100644
--- a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx
+++ b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx
@@ -1,13 +1,13 @@
import { Menu } from '@affine/component';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import type { Tag } from '@affine/core/modules/tag';
+import { stopPropagation } from '@affine/core/utils';
import { CloseIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc';
import { LiveData, useLiveData } from '@toeverything/infra';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
import { useMemo } from 'react';
-import { stopPropagation } from '../utils';
import * as styles from './page-tags.css';
export interface PageTagsProps {
diff --git a/packages/frontend/core/src/components/page-list/operation-cell.tsx b/packages/frontend/core/src/components/page-list/operation-cell.tsx
index 48078a3fd2..a61c7a5ed0 100644
--- a/packages/frontend/core/src/components/page-list/operation-cell.tsx
+++ b/packages/frontend/core/src/components/page-list/operation-cell.tsx
@@ -9,6 +9,7 @@ import {
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
+import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { track } from '@affine/core/mixpanel';
import { FavoriteService } from '@affine/core/modules/favorite';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
@@ -277,18 +278,30 @@ export const TrashOperationCell = ({
const t = useI18n();
const { openConfirmModal } = useConfirmModal();
- const onConfirmPermanentlyDelete = useCallback(() => {
- openConfirmModal({
- title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
- description: t['com.affine.trashOperation.deleteDescription'](),
- cancelText: t['Cancel'](),
- confirmText: t['com.affine.trashOperation.delete'](),
- confirmButtonOptions: {
- variant: 'error',
- },
- onConfirm: onPermanentlyDeletePage,
- });
- }, [onPermanentlyDeletePage, openConfirmModal, t]);
+ const onConfirmPermanentlyDelete = useCatchEventCallback(
+ e => {
+ e.preventDefault();
+ openConfirmModal({
+ title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
+ description: t['com.affine.trashOperation.deleteDescription'](),
+ cancelText: t['Cancel'](),
+ confirmText: t['com.affine.trashOperation.delete'](),
+ confirmButtonOptions: {
+ variant: 'error',
+ },
+ onConfirm: onPermanentlyDeletePage,
+ });
+ },
+ [onPermanentlyDeletePage, openConfirmModal, t]
+ );
+
+ const handleRestorePage = useCatchEventCallback(
+ e => {
+ e.preventDefault();
+ onRestorePage();
+ },
+ [onRestorePage]
+ );
return (
@@ -297,9 +310,7 @@ export const TrashOperationCell = ({
tooltipOptions={tooltipSideTop}
data-testid="restore-page-button"
style={{ marginRight: '12px' }}
- onClick={() => {
- onRestorePage();
- }}
+ onClick={handleRestorePage}
size="20"
>
diff --git a/packages/frontend/core/src/components/page-list/page-header.tsx b/packages/frontend/core/src/components/page-list/page-header.tsx
index 84bee49bb0..562ef57156 100644
--- a/packages/frontend/core/src/components/page-list/page-header.tsx
+++ b/packages/frontend/core/src/components/page-list/page-header.tsx
@@ -1,5 +1,6 @@
import type { CheckboxProps } from '@affine/component';
import { Checkbox } from '@affine/component';
+import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import { useI18n } from '@affine/i18n';
import { MultiSelectIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx';
@@ -19,7 +20,6 @@ import {
useAtomValue,
} from './scoped-atoms';
import type { HeaderColDef, ListItem } from './types';
-import { stopPropagation } from './utils';
// the checkbox on the header has three states:
// when list selectable = true, the checkbox will be presented
@@ -28,23 +28,19 @@ import { stopPropagation } from './utils';
const ListHeaderCheckbox = () => {
const [selectionState, setSelectionState] = useAtom(selectionStateAtom);
const items = useAtomValue(itemsAtom);
- const onActivateSelection: MouseEventHandler = useCallback(
- e => {
- stopPropagation(e);
- setSelectionState(true);
- },
- [setSelectionState]
- );
+ const onActivateSelection: MouseEventHandler = useCatchEventCallback(() => {
+ setSelectionState(true);
+ }, [setSelectionState]);
const handlers = useAtomValue(listHandlersAtom);
- const onChange: NonNullable = useCallback(
- (e, checked) => {
- stopPropagation(e);
- handlers.onSelectedIdsChange?.(
- checked ? (items ?? []).map(i => i.id) : []
- );
- },
- [handlers, items]
- );
+ const onChange: NonNullable =
+ useCatchEventCallback(
+ (_e, checked) => {
+ handlers.onSelectedIdsChange?.(
+ checked ? (items ?? []).map(i => i.id) : []
+ );
+ },
+ [handlers, items]
+ );
if (!selectionState.selectable) {
return null;
diff --git a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx
index 92b3c0ec07..7cc6c84a68 100644
--- a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx
+++ b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx
@@ -1,13 +1,14 @@
import { Checkbox, useDraggable } from '@affine/component';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
+import { stopPropagation } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import type { ForwardedRef, PropsWithChildren } from 'react';
import { forwardRef, useCallback, useMemo } from 'react';
import { selectionStateAtom, useAtom } from '../scoped-atoms';
import type { TagListItemProps } from '../types';
-import { ColWrapper, stopPropagation } from '../utils';
+import { ColWrapper } from '../utils';
import * as styles from './tag-list-item.css';
const TagListTitleCell = ({
diff --git a/packages/frontend/core/src/components/page-list/utils.tsx b/packages/frontend/core/src/components/page-list/utils.tsx
index f91ce95054..1a63f2fe36 100644
--- a/packages/frontend/core/src/components/page-list/utils.tsx
+++ b/packages/frontend/core/src/components/page-list/utils.tsx
@@ -1,5 +1,4 @@
import clsx from 'clsx';
-import type { BaseSyntheticEvent } from 'react';
import { forwardRef } from 'react';
import * as styles from './list.css';
@@ -58,14 +57,6 @@ export const betweenDaysAgo = (
return !withinDaysAgo(date, days0) && withinDaysAgo(date, days1);
};
-export function stopPropagation(event: BaseSyntheticEvent) {
- event.stopPropagation();
- event.preventDefault();
-}
-export function stopPropagationWithoutPrevent(event: BaseSyntheticEvent) {
- event.stopPropagation();
-}
-
// credit: https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js
export function shallowEqual(objA: any, objB: any) {
if (Object.is(objA, objB)) {
diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx
index 1203e99c96..214bb0fb3f 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx
+++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx
@@ -9,6 +9,7 @@ import {
} from '@affine/core/modules/explorer';
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
+import { isNewTabTrigger } from '@affine/core/utils';
import { events } from '@affine/electron-api';
import { useI18n } from '@affine/i18n';
import { AllDocsIcon, SettingsIcon } from '@blocksuite/icons/rc';
@@ -85,9 +86,7 @@ export const RootAppSidebar = (): ReactElement => {
const onClickNewPage = useAsyncCallback(
async (e?: MouseEvent) => {
- const page = pageHelper.createPage(
- e?.ctrlKey || e?.metaKey ? 'new-tab' : true
- );
+ const page = pageHelper.createPage(isNewTabTrigger(e) ? 'new-tab' : true);
page.load();
track.$.navigationPanel.$.createDoc();
},
diff --git a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx
index 514903100e..37f3fc5346 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx
+++ b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx
@@ -1,13 +1,15 @@
+import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
import {
useJournalInfoHelper,
useJournalRouteHelper,
} from '@affine/core/hooks/use-journal';
import { WorkbenchService } from '@affine/core/modules/workbench';
import type { DocCollection } from '@affine/core/shared';
+import { isNewTabTrigger } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
-import { type MouseEvent, useCallback } from 'react';
+import { type MouseEvent } from 'react';
import { MenuItem } from '../app-sidebar';
@@ -27,9 +29,9 @@ export const AppSidebarJournalButton = ({
location.pathname.split('/')[1]
);
- const handleOpenToday = useCallback(
+ const handleOpenToday = useCatchEventCallback(
(e: MouseEvent) => {
- openToday(e.ctrlKey || e.metaKey);
+ openToday(isNewTabTrigger(e));
},
[openToday]
);
@@ -48,6 +50,7 @@ export const AppSidebarJournalButton = ({
data-testid="slider-bar-journals-button"
active={isJournal}
onClick={handleOpenToday}
+ onAuxClick={handleOpenToday}
icon={}
>
{t['com.affine.journal.app-sidebar-title']()}
diff --git a/packages/frontend/core/src/hooks/use-catch-event-hook.ts b/packages/frontend/core/src/hooks/use-catch-event-hook.ts
index 0d71f4ddbe..3040e72ce9 100644
--- a/packages/frontend/core/src/hooks/use-catch-event-hook.ts
+++ b/packages/frontend/core/src/hooks/use-catch-event-hook.ts
@@ -2,14 +2,17 @@ import { type DependencyList, type SyntheticEvent } from 'react';
import { useAsyncCallback } from './affine-async-hooks';
-export const useCatchEventCallback = (
- cb: (e: E) => void | Promise,
+export const useCatchEventCallback = <
+ E extends SyntheticEvent,
+ Args extends any[],
+>(
+ cb: (e: E, ...args: Args) => void | Promise,
deps: DependencyList
) => {
return useAsyncCallback(
- async (e: E) => {
+ async (e: E, ...args: Args) => {
e.stopPropagation();
- await cb(e);
+ await cb(e, ...args);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
deps
diff --git a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx
index eeea7ef3f9..f59ebe3f3d 100644
--- a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx
+++ b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx
@@ -15,6 +15,7 @@ import {
} from '@affine/core/modules/favorite';
import { WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
+import { isNewTabTrigger } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc';
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
@@ -81,7 +82,7 @@ export const ExplorerFavorites = () => {
favoriteService.favoriteList.indexAt('before')
);
workbenchService.workbench.openDoc(newDoc.id, {
- at: e.ctrlKey || e.metaKey ? 'new-tab' : 'active',
+ at: isNewTabTrigger(e) ? 'new-tab' : 'active',
});
explorerSection.setCollapsed(false);
},
@@ -173,6 +174,7 @@ export const ExplorerFavorites = () => {
data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc}
+ onAuxClick={handleCreateNewFavoriteDoc}
size="16"
tooltip={t[
'com.affine.rootAppSidebar.explorer.fav-section-add-tooltip'
diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx
index 16eec059de..0287a56cda 100644
--- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx
+++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx
@@ -1,5 +1,6 @@
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
+import { isNewTabTrigger } from '@affine/core/utils';
import { useLiveData, useService } from '@toeverything/infra';
import { type To } from 'history';
import { forwardRef, type MouseEvent } from 'react';
@@ -11,7 +12,7 @@ export const WorkbenchLink = forwardRef<
React.PropsWithChildren<
{
to: To;
- onClick?: (e: MouseEvent) => boolean | void; // return false to stop propagation
+ onClick?: (e: MouseEvent) => void;
} & React.HTMLProps
>
>(function WorkbenchLink({ to, onClick, ...other }, ref) {
@@ -23,26 +24,33 @@ export const WorkbenchLink = forwardRef<
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
const handleClick = useCatchEventCallback(
async (event: React.MouseEvent) => {
- event.preventDefault();
- if (onClick?.(event) === false) {
+ onClick?.(event);
+ if (event.defaultPrevented) {
return;
}
-
const at = (() => {
- if (event.ctrlKey || event.metaKey) {
+ if (isNewTabTrigger(event)) {
return event.altKey && appSettings.enableMultiView
? 'tail'
: 'new-tab';
}
return 'active';
})();
-
workbench.open(to, { at });
+ event.preventDefault();
},
[appSettings.enableMultiView, onClick, to, workbench]
);
// eslint suspicious runtime error
// eslint-disable-next-line react/no-danger-with-children
- return ;
+ return (
+
+ );
});
diff --git a/packages/frontend/core/src/pages/workspace/all-page/all-page-header.tsx b/packages/frontend/core/src/pages/workspace/all-page/all-page-header.tsx
index 61be4cd4f0..5a215384a8 100644
--- a/packages/frontend/core/src/pages/workspace/all-page/all-page-header.tsx
+++ b/packages/frontend/core/src/pages/workspace/all-page/all-page-header.tsx
@@ -8,6 +8,7 @@ import { Header } from '@affine/core/components/pure/header';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
+import { isNewTabTrigger } from '@affine/core/utils';
import type { Filter } from '@affine/env/filter';
import { PlusIcon } from '@blocksuite/icons/rc';
import { useService, WorkspaceService } from '@toeverything/infra';
@@ -60,10 +61,10 @@ export const AllPageHeader = ({
!showCreateNew && styles.headerCreateNewButtonHidden
)}
onCreateEdgeless={e =>
- createEdgeless(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
+ createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
}
onCreatePage={e =>
- createPage(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
+ createPage(isNewTabTrigger(e) ? 'new-tab' : true)
}
onImportFile={onImportFile}
>
diff --git a/packages/frontend/core/src/utils/event.ts b/packages/frontend/core/src/utils/event.ts
new file mode 100644
index 0000000000..71786d1698
--- /dev/null
+++ b/packages/frontend/core/src/utils/event.ts
@@ -0,0 +1,14 @@
+import type { BaseSyntheticEvent } from 'react';
+
+export function stopPropagation(event: BaseSyntheticEvent) {
+ event.stopPropagation();
+}
+
+export function stopEvent(event: BaseSyntheticEvent) {
+ event.stopPropagation();
+ event.preventDefault();
+}
+
+export function isNewTabTrigger(event?: React.MouseEvent) {
+ return event ? event.ctrlKey || event.metaKey || event.button === 1 : false;
+}
diff --git a/packages/frontend/core/src/utils/index.ts b/packages/frontend/core/src/utils/index.ts
index eaf45a6fa2..464497ff62 100644
--- a/packages/frontend/core/src/utils/index.ts
+++ b/packages/frontend/core/src/utils/index.ts
@@ -1,4 +1,5 @@
export * from './create-emotion-cache';
+export * from './event';
export * from './fractional-indexing';
export * from './popup';
export * from './string2color';
diff --git a/tests/affine-local/e2e/local-first-favorite-page.spec.ts b/tests/affine-local/e2e/local-first-favorite-page.spec.ts
index fca397cb26..4d8b06cf3e 100644
--- a/tests/affine-local/e2e/local-first-favorite-page.spec.ts
+++ b/tests/affine-local/e2e/local-first-favorite-page.spec.ts
@@ -10,7 +10,7 @@ import {
import { waitForLogMessage } from '@affine-test/kit/utils/utils';
import { expect } from '@playwright/test';
-test('New a page and open it ,then favorite it', async ({
+test('New a page and open it, then favorite it', async ({
page,
workspace,
}) => {
diff --git a/tests/affine-local/e2e/local-first-openpage-newtab.spec.ts b/tests/affine-local/e2e/local-first-openpage-newtab.spec.ts
index 0f2ae2036e..c66ebb087d 100644
--- a/tests/affine-local/e2e/local-first-openpage-newtab.spec.ts
+++ b/tests/affine-local/e2e/local-first-openpage-newtab.spec.ts
@@ -8,7 +8,7 @@ import {
} from '@affine-test/kit/utils/page-logic';
import { expect } from '@playwright/test';
-test('click btn bew page and open in tab', async ({ page, workspace }) => {
+test('click btn new page and open in tab', async ({ page, workspace }) => {
await openHomePage(page);
await waitForEditorLoad(page);
await clickNewPageButton(page);
@@ -31,3 +31,54 @@ test('click btn bew page and open in tab', async ({ page, workspace }) => {
expect(currentWorkspace.meta.flavour).toContain('local');
});
+
+test('switch between new page and all page', async ({ page }) => {
+ await openHomePage(page);
+ await waitForEditorLoad(page);
+
+ const title = 'this is a new page';
+
+ await clickNewPageButton(page, title);
+ await page.getByTestId('all-pages').click();
+
+ const cell = page.getByTestId('page-list-item').getByText(title);
+ await expect(cell).toBeVisible();
+
+ await cell.click();
+ await expect(getBlockSuiteEditorTitle(page)).toHaveText(title);
+
+ await page.getByTestId('all-pages').click();
+ await expect(cell).toBeVisible();
+});
+
+test('ctrl click all page and open in new tab', async ({ page }) => {
+ await openHomePage(page);
+ await waitForEditorLoad(page);
+
+ const [newTabPage] = await Promise.all([
+ page.waitForEvent('popup'),
+ page.getByTestId('all-pages').click({
+ modifiers: ['ControlOrMeta'],
+ }),
+ ]);
+
+ await expect(newTabPage).toHaveURL(/\/all/, {
+ timeout: 15000,
+ });
+});
+
+test('mid click all page and open in new tab', async ({ page }) => {
+ await openHomePage(page);
+ await waitForEditorLoad(page);
+
+ const [newTabPage] = await Promise.all([
+ page.waitForEvent('popup'),
+ page.getByTestId('all-pages').click({
+ button: 'middle',
+ }),
+ ]);
+
+ await expect(newTabPage).toHaveURL(/\/all/, {
+ timeout: 15000,
+ });
+});