mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-26 02:04:43 +03:00
parent
fd6e198295
commit
7fca13076a
@ -247,7 +247,8 @@ const config = {
|
|||||||
'react-hooks/exhaustive-deps': [
|
'react-hooks/exhaustive-deps': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
additionalHooks: '(useAsyncCallback|useDraggable|useDropTarget)',
|
additionalHooks:
|
||||||
|
'(useAsyncCallback|useCatchEventCallback|useDraggable|useDropTarget)',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -106,7 +106,6 @@ export function AffinePageReference({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
peekView.open(ref.current).catch(console.error);
|
peekView.open(ref.current).catch(console.error);
|
||||||
return false; // means this click is handled
|
|
||||||
}
|
}
|
||||||
if (isInPeekView) {
|
if (isInPeekView) {
|
||||||
peekView.close();
|
peekView.close();
|
||||||
|
@ -29,6 +29,7 @@ export function AddPageButton({
|
|||||||
style={style}
|
style={style}
|
||||||
className={clsx([styles.root, className])}
|
className={clsx([styles.root, className])}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
onAuxClick={onClick}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Checkbox, useDraggable } from '@affine/component';
|
import { Checkbox, useDraggable } from '@affine/component';
|
||||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
|
import { stopPropagation } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { ForwardedRef, PropsWithChildren } from 'react';
|
import type { ForwardedRef, PropsWithChildren } from 'react';
|
||||||
import { forwardRef, useCallback, useMemo } from 'react';
|
import { forwardRef, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { selectionStateAtom, useAtom } from '../scoped-atoms';
|
import { selectionStateAtom, useAtom } from '../scoped-atoms';
|
||||||
import type { CollectionListItemProps, PageListItemProps } from '../types';
|
import type { CollectionListItemProps, PageListItemProps } from '../types';
|
||||||
import { ColWrapper, stopPropagation } from '../utils';
|
import { ColWrapper } from '../utils';
|
||||||
import * as styles from './collection-list-item.css';
|
import * as styles from './collection-list-item.css';
|
||||||
|
|
||||||
const ListTitleCell = ({
|
const ListTitleCell = ({
|
||||||
|
@ -35,6 +35,7 @@ export const CreateNewPagePopup = ({
|
|||||||
desc={t['com.affine.write_with_a_blank_page']()}
|
desc={t['com.affine.write_with_a_blank_page']()}
|
||||||
right={<PageIcon width={20} height={20} />}
|
right={<PageIcon width={20} height={20} />}
|
||||||
onClick={createNewPage}
|
onClick={createNewPage}
|
||||||
|
onAuxClick={createNewPage}
|
||||||
data-testid="new-page-button-in-all-page"
|
data-testid="new-page-button-in-all-page"
|
||||||
/>
|
/>
|
||||||
<BlockCard
|
<BlockCard
|
||||||
@ -42,6 +43,7 @@ export const CreateNewPagePopup = ({
|
|||||||
desc={t['com.affine.draw_with_a_blank_whiteboard']()}
|
desc={t['com.affine.draw_with_a_blank_whiteboard']()}
|
||||||
right={<EdgelessIcon width={20} height={20} />}
|
right={<EdgelessIcon width={20} height={20} />}
|
||||||
onClick={createNewEdgeless}
|
onClick={createNewEdgeless}
|
||||||
|
onAuxClick={createNewEdgeless}
|
||||||
data-testid="new-edgeless-button-in-all-page"
|
data-testid="new-edgeless-button-in-all-page"
|
||||||
/>
|
/>
|
||||||
{importFile ? (
|
{importFile ? (
|
||||||
@ -117,6 +119,7 @@ export const NewPageButton = ({
|
|||||||
<DropdownButton
|
<DropdownButton
|
||||||
size={size}
|
size={size}
|
||||||
onClick={handleCreateNewPage}
|
onClick={handleCreateNewPage}
|
||||||
|
onAuxClick={handleCreateNewPage}
|
||||||
onClickDropDown={useCallback(() => setOpen(open => !open), [])}
|
onClickDropDown={useCallback(() => setOpen(open => !open), [])}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -10,6 +10,7 @@ import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
|||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
import type { Tag } from '@affine/core/modules/tag';
|
||||||
import { TagService } 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 type { Collection } from '@affine/env/filter';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
@ -66,12 +67,9 @@ export const PageListHeader = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
testId="new-page-button-trigger"
|
testId="new-page-button-trigger"
|
||||||
onCreateEdgeless={e =>
|
onCreateEdgeless={e =>
|
||||||
// todo: abstract this for ctrl check
|
createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
|
||||||
createEdgeless(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
|
|
||||||
}
|
|
||||||
onCreatePage={e =>
|
|
||||||
createPage(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
|
|
||||||
}
|
}
|
||||||
|
onCreatePage={e => createPage(isNewTabTrigger(e) ? 'new-tab' : true)}
|
||||||
onImportFile={onImportFile}
|
onImportFile={onImportFile}
|
||||||
>
|
>
|
||||||
<div className={styles.buttonText}>{t['New Page']()}</div>
|
<div className={styles.buttonText}>{t['New Page']()}</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Checkbox, Tooltip, useDraggable } from '@affine/component';
|
import { Checkbox, Tooltip, useDraggable } from '@affine/component';
|
||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
|
import { stopPropagation } from '@affine/core/utils';
|
||||||
import { i18nTime } from '@affine/i18n';
|
import { i18nTime } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import type { ForwardedRef, PropsWithChildren } from 'react';
|
import type { ForwardedRef, PropsWithChildren } from 'react';
|
||||||
@ -15,7 +16,7 @@ import {
|
|||||||
} from '../scoped-atoms';
|
} from '../scoped-atoms';
|
||||||
import type { PageListItemProps } from '../types';
|
import type { PageListItemProps } from '../types';
|
||||||
import { useAllDocDisplayProperties } from '../use-all-doc-display-properties';
|
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 * as styles from './page-list-item.css';
|
||||||
import { PageTags } from './page-tags';
|
import { PageTags } from './page-tags';
|
||||||
|
|
||||||
@ -310,18 +311,18 @@ const PageListItemWrapper = forwardRef(
|
|||||||
if (!selectionState.selectable) {
|
if (!selectionState.selectable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopPropagation(e);
|
e.stopPropagation();
|
||||||
const currentIndex = pageIds.indexOf(pageId);
|
const currentIndex = pageIds.indexOf(pageId);
|
||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
if (!selectionState.selectionActive) {
|
if (!selectionState.selectionActive) {
|
||||||
setSelectionActive(true);
|
setSelectionActive(true);
|
||||||
setAnchorIndex(currentIndex);
|
setAnchorIndex(currentIndex);
|
||||||
onClick?.();
|
onClick?.();
|
||||||
return false;
|
} else {
|
||||||
|
handleShiftClick(currentIndex);
|
||||||
}
|
}
|
||||||
handleShiftClick(currentIndex);
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
setAnchorIndex(undefined);
|
setAnchorIndex(undefined);
|
||||||
setRangeIds([]);
|
setRangeIds([]);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Menu } from '@affine/component';
|
import { Menu } from '@affine/component';
|
||||||
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
import type { Tag } from '@affine/core/modules/tag';
|
||||||
|
import { stopPropagation } from '@affine/core/utils';
|
||||||
import { CloseIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc';
|
import { CloseIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc';
|
||||||
import { LiveData, useLiveData } from '@toeverything/infra';
|
import { LiveData, useLiveData } from '@toeverything/infra';
|
||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { stopPropagation } from '../utils';
|
|
||||||
import * as styles from './page-tags.css';
|
import * as styles from './page-tags.css';
|
||||||
|
|
||||||
export interface PageTagsProps {
|
export interface PageTagsProps {
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-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 { 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 { track } from '@affine/core/mixpanel';
|
||||||
import { FavoriteService } from '@affine/core/modules/favorite';
|
import { FavoriteService } from '@affine/core/modules/favorite';
|
||||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
|
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||||
@ -277,18 +278,30 @@ export const TrashOperationCell = ({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
|
||||||
const onConfirmPermanentlyDelete = useCallback(() => {
|
const onConfirmPermanentlyDelete = useCatchEventCallback(
|
||||||
openConfirmModal({
|
e => {
|
||||||
title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
|
e.preventDefault();
|
||||||
description: t['com.affine.trashOperation.deleteDescription'](),
|
openConfirmModal({
|
||||||
cancelText: t['Cancel'](),
|
title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
|
||||||
confirmText: t['com.affine.trashOperation.delete'](),
|
description: t['com.affine.trashOperation.deleteDescription'](),
|
||||||
confirmButtonOptions: {
|
cancelText: t['Cancel'](),
|
||||||
variant: 'error',
|
confirmText: t['com.affine.trashOperation.delete'](),
|
||||||
},
|
confirmButtonOptions: {
|
||||||
onConfirm: onPermanentlyDeletePage,
|
variant: 'error',
|
||||||
});
|
},
|
||||||
}, [onPermanentlyDeletePage, openConfirmModal, t]);
|
onConfirm: onPermanentlyDeletePage,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onPermanentlyDeletePage, openConfirmModal, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRestorePage = useCatchEventCallback(
|
||||||
|
e => {
|
||||||
|
e.preventDefault();
|
||||||
|
onRestorePage();
|
||||||
|
},
|
||||||
|
[onRestorePage]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColWrapper flex={1}>
|
<ColWrapper flex={1}>
|
||||||
@ -297,9 +310,7 @@ export const TrashOperationCell = ({
|
|||||||
tooltipOptions={tooltipSideTop}
|
tooltipOptions={tooltipSideTop}
|
||||||
data-testid="restore-page-button"
|
data-testid="restore-page-button"
|
||||||
style={{ marginRight: '12px' }}
|
style={{ marginRight: '12px' }}
|
||||||
onClick={() => {
|
onClick={handleRestorePage}
|
||||||
onRestorePage();
|
|
||||||
}}
|
|
||||||
size="20"
|
size="20"
|
||||||
>
|
>
|
||||||
<ResetIcon />
|
<ResetIcon />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { CheckboxProps } from '@affine/component';
|
import type { CheckboxProps } from '@affine/component';
|
||||||
import { Checkbox } from '@affine/component';
|
import { Checkbox } from '@affine/component';
|
||||||
|
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { MultiSelectIcon } from '@blocksuite/icons/rc';
|
import { MultiSelectIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@ -19,7 +20,6 @@ import {
|
|||||||
useAtomValue,
|
useAtomValue,
|
||||||
} from './scoped-atoms';
|
} from './scoped-atoms';
|
||||||
import type { HeaderColDef, ListItem } from './types';
|
import type { HeaderColDef, ListItem } from './types';
|
||||||
import { stopPropagation } from './utils';
|
|
||||||
|
|
||||||
// the checkbox on the header has three states:
|
// the checkbox on the header has three states:
|
||||||
// when list selectable = true, the checkbox will be presented
|
// when list selectable = true, the checkbox will be presented
|
||||||
@ -28,23 +28,19 @@ import { stopPropagation } from './utils';
|
|||||||
const ListHeaderCheckbox = () => {
|
const ListHeaderCheckbox = () => {
|
||||||
const [selectionState, setSelectionState] = useAtom(selectionStateAtom);
|
const [selectionState, setSelectionState] = useAtom(selectionStateAtom);
|
||||||
const items = useAtomValue(itemsAtom);
|
const items = useAtomValue(itemsAtom);
|
||||||
const onActivateSelection: MouseEventHandler = useCallback(
|
const onActivateSelection: MouseEventHandler = useCatchEventCallback(() => {
|
||||||
e => {
|
setSelectionState(true);
|
||||||
stopPropagation(e);
|
}, [setSelectionState]);
|
||||||
setSelectionState(true);
|
|
||||||
},
|
|
||||||
[setSelectionState]
|
|
||||||
);
|
|
||||||
const handlers = useAtomValue(listHandlersAtom);
|
const handlers = useAtomValue(listHandlersAtom);
|
||||||
const onChange: NonNullable<CheckboxProps['onChange']> = useCallback(
|
const onChange: NonNullable<CheckboxProps['onChange']> =
|
||||||
(e, checked) => {
|
useCatchEventCallback(
|
||||||
stopPropagation(e);
|
(_e, checked) => {
|
||||||
handlers.onSelectedIdsChange?.(
|
handlers.onSelectedIdsChange?.(
|
||||||
checked ? (items ?? []).map(i => i.id) : []
|
checked ? (items ?? []).map(i => i.id) : []
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[handlers, items]
|
[handlers, items]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!selectionState.selectable) {
|
if (!selectionState.selectable) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Checkbox, useDraggable } from '@affine/component';
|
import { Checkbox, useDraggable } from '@affine/component';
|
||||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
|
import { stopPropagation } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { ForwardedRef, PropsWithChildren } from 'react';
|
import type { ForwardedRef, PropsWithChildren } from 'react';
|
||||||
import { forwardRef, useCallback, useMemo } from 'react';
|
import { forwardRef, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { selectionStateAtom, useAtom } from '../scoped-atoms';
|
import { selectionStateAtom, useAtom } from '../scoped-atoms';
|
||||||
import type { TagListItemProps } from '../types';
|
import type { TagListItemProps } from '../types';
|
||||||
import { ColWrapper, stopPropagation } from '../utils';
|
import { ColWrapper } from '../utils';
|
||||||
import * as styles from './tag-list-item.css';
|
import * as styles from './tag-list-item.css';
|
||||||
|
|
||||||
const TagListTitleCell = ({
|
const TagListTitleCell = ({
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { BaseSyntheticEvent } from 'react';
|
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
import * as styles from './list.css';
|
import * as styles from './list.css';
|
||||||
@ -58,14 +57,6 @@ export const betweenDaysAgo = (
|
|||||||
return !withinDaysAgo(date, days0) && withinDaysAgo(date, days1);
|
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
|
// credit: https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js
|
||||||
export function shallowEqual(objA: any, objB: any) {
|
export function shallowEqual(objA: any, objB: any) {
|
||||||
if (Object.is(objA, objB)) {
|
if (Object.is(objA, objB)) {
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from '@affine/core/modules/explorer';
|
} from '@affine/core/modules/explorer';
|
||||||
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
||||||
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { events } from '@affine/electron-api';
|
import { events } from '@affine/electron-api';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { AllDocsIcon, SettingsIcon } from '@blocksuite/icons/rc';
|
import { AllDocsIcon, SettingsIcon } from '@blocksuite/icons/rc';
|
||||||
@ -85,9 +86,7 @@ export const RootAppSidebar = (): ReactElement => {
|
|||||||
|
|
||||||
const onClickNewPage = useAsyncCallback(
|
const onClickNewPage = useAsyncCallback(
|
||||||
async (e?: MouseEvent) => {
|
async (e?: MouseEvent) => {
|
||||||
const page = pageHelper.createPage(
|
const page = pageHelper.createPage(isNewTabTrigger(e) ? 'new-tab' : true);
|
||||||
e?.ctrlKey || e?.metaKey ? 'new-tab' : true
|
|
||||||
);
|
|
||||||
page.load();
|
page.load();
|
||||||
track.$.navigationPanel.$.createDoc();
|
track.$.navigationPanel.$.createDoc();
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
||||||
import {
|
import {
|
||||||
useJournalInfoHelper,
|
useJournalInfoHelper,
|
||||||
useJournalRouteHelper,
|
useJournalRouteHelper,
|
||||||
} from '@affine/core/hooks/use-journal';
|
} from '@affine/core/hooks/use-journal';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import type { DocCollection } from '@affine/core/shared';
|
import type { DocCollection } from '@affine/core/shared';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons/rc';
|
import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type MouseEvent, useCallback } from 'react';
|
import { type MouseEvent } from 'react';
|
||||||
|
|
||||||
import { MenuItem } from '../app-sidebar';
|
import { MenuItem } from '../app-sidebar';
|
||||||
|
|
||||||
@ -27,9 +29,9 @@ export const AppSidebarJournalButton = ({
|
|||||||
location.pathname.split('/')[1]
|
location.pathname.split('/')[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenToday = useCallback(
|
const handleOpenToday = useCatchEventCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
openToday(e.ctrlKey || e.metaKey);
|
openToday(isNewTabTrigger(e));
|
||||||
},
|
},
|
||||||
[openToday]
|
[openToday]
|
||||||
);
|
);
|
||||||
@ -48,6 +50,7 @@ export const AppSidebarJournalButton = ({
|
|||||||
data-testid="slider-bar-journals-button"
|
data-testid="slider-bar-journals-button"
|
||||||
active={isJournal}
|
active={isJournal}
|
||||||
onClick={handleOpenToday}
|
onClick={handleOpenToday}
|
||||||
|
onAuxClick={handleOpenToday}
|
||||||
icon={<Icon />}
|
icon={<Icon />}
|
||||||
>
|
>
|
||||||
{t['com.affine.journal.app-sidebar-title']()}
|
{t['com.affine.journal.app-sidebar-title']()}
|
||||||
|
@ -2,14 +2,17 @@ import { type DependencyList, type SyntheticEvent } from 'react';
|
|||||||
|
|
||||||
import { useAsyncCallback } from './affine-async-hooks';
|
import { useAsyncCallback } from './affine-async-hooks';
|
||||||
|
|
||||||
export const useCatchEventCallback = <E extends SyntheticEvent>(
|
export const useCatchEventCallback = <
|
||||||
cb: (e: E) => void | Promise<void>,
|
E extends SyntheticEvent,
|
||||||
|
Args extends any[],
|
||||||
|
>(
|
||||||
|
cb: (e: E, ...args: Args) => void | Promise<void>,
|
||||||
deps: DependencyList
|
deps: DependencyList
|
||||||
) => {
|
) => {
|
||||||
return useAsyncCallback(
|
return useAsyncCallback(
|
||||||
async (e: E) => {
|
async (e: E, ...args: Args) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await cb(e);
|
await cb(e, ...args);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
deps
|
deps
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
} from '@affine/core/modules/favorite';
|
} from '@affine/core/modules/favorite';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||||
@ -81,7 +82,7 @@ export const ExplorerFavorites = () => {
|
|||||||
favoriteService.favoriteList.indexAt('before')
|
favoriteService.favoriteList.indexAt('before')
|
||||||
);
|
);
|
||||||
workbenchService.workbench.openDoc(newDoc.id, {
|
workbenchService.workbench.openDoc(newDoc.id, {
|
||||||
at: e.ctrlKey || e.metaKey ? 'new-tab' : 'active',
|
at: isNewTabTrigger(e) ? 'new-tab' : 'active',
|
||||||
});
|
});
|
||||||
explorerSection.setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
},
|
},
|
||||||
@ -173,6 +174,7 @@ export const ExplorerFavorites = () => {
|
|||||||
data-event-props="$.navigationPanel.favorites.createDoc"
|
data-event-props="$.navigationPanel.favorites.createDoc"
|
||||||
data-event-args-control="addFavorite"
|
data-event-args-control="addFavorite"
|
||||||
onClick={handleCreateNewFavoriteDoc}
|
onClick={handleCreateNewFavoriteDoc}
|
||||||
|
onAuxClick={handleCreateNewFavoriteDoc}
|
||||||
size="16"
|
size="16"
|
||||||
tooltip={t[
|
tooltip={t[
|
||||||
'com.affine.rootAppSidebar.explorer.fav-section-add-tooltip'
|
'com.affine.rootAppSidebar.explorer.fav-section-add-tooltip'
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||||
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type To } from 'history';
|
import { type To } from 'history';
|
||||||
import { forwardRef, type MouseEvent } from 'react';
|
import { forwardRef, type MouseEvent } from 'react';
|
||||||
@ -11,7 +12,7 @@ export const WorkbenchLink = forwardRef<
|
|||||||
React.PropsWithChildren<
|
React.PropsWithChildren<
|
||||||
{
|
{
|
||||||
to: To;
|
to: To;
|
||||||
onClick?: (e: MouseEvent) => boolean | void; // return false to stop propagation
|
onClick?: (e: MouseEvent) => void;
|
||||||
} & React.HTMLProps<HTMLAnchorElement>
|
} & React.HTMLProps<HTMLAnchorElement>
|
||||||
>
|
>
|
||||||
>(function WorkbenchLink({ to, onClick, ...other }, ref) {
|
>(function WorkbenchLink({ to, onClick, ...other }, ref) {
|
||||||
@ -23,26 +24,33 @@ export const WorkbenchLink = forwardRef<
|
|||||||
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
||||||
const handleClick = useCatchEventCallback(
|
const handleClick = useCatchEventCallback(
|
||||||
async (event: React.MouseEvent<HTMLAnchorElement>) => {
|
async (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||||
event.preventDefault();
|
onClick?.(event);
|
||||||
if (onClick?.(event) === false) {
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const at = (() => {
|
const at = (() => {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (isNewTabTrigger(event)) {
|
||||||
return event.altKey && appSettings.enableMultiView
|
return event.altKey && appSettings.enableMultiView
|
||||||
? 'tail'
|
? 'tail'
|
||||||
: 'new-tab';
|
: 'new-tab';
|
||||||
}
|
}
|
||||||
return 'active';
|
return 'active';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
workbench.open(to, { at });
|
workbench.open(to, { at });
|
||||||
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
[appSettings.enableMultiView, onClick, to, workbench]
|
[appSettings.enableMultiView, onClick, to, workbench]
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint suspicious runtime error
|
// eslint suspicious runtime error
|
||||||
// eslint-disable-next-line react/no-danger-with-children
|
// eslint-disable-next-line react/no-danger-with-children
|
||||||
return <a {...other} ref={ref} href={link} onClick={handleClick} />;
|
return (
|
||||||
|
<a
|
||||||
|
{...other}
|
||||||
|
ref={ref}
|
||||||
|
href={link}
|
||||||
|
onClick={handleClick}
|
||||||
|
onAuxClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@ import { Header } from '@affine/core/components/pure/header';
|
|||||||
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
|
||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import type { Filter } from '@affine/env/filter';
|
import type { Filter } from '@affine/env/filter';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||||
@ -60,10 +61,10 @@ export const AllPageHeader = ({
|
|||||||
!showCreateNew && styles.headerCreateNewButtonHidden
|
!showCreateNew && styles.headerCreateNewButtonHidden
|
||||||
)}
|
)}
|
||||||
onCreateEdgeless={e =>
|
onCreateEdgeless={e =>
|
||||||
createEdgeless(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
|
createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
|
||||||
}
|
}
|
||||||
onCreatePage={e =>
|
onCreatePage={e =>
|
||||||
createPage(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
|
createPage(isNewTabTrigger(e) ? 'new-tab' : true)
|
||||||
}
|
}
|
||||||
onImportFile={onImportFile}
|
onImportFile={onImportFile}
|
||||||
>
|
>
|
||||||
|
14
packages/frontend/core/src/utils/event.ts
Normal file
14
packages/frontend/core/src/utils/event.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from './create-emotion-cache';
|
export * from './create-emotion-cache';
|
||||||
|
export * from './event';
|
||||||
export * from './fractional-indexing';
|
export * from './fractional-indexing';
|
||||||
export * from './popup';
|
export * from './popup';
|
||||||
export * from './string2color';
|
export * from './string2color';
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { waitForLogMessage } from '@affine-test/kit/utils/utils';
|
import { waitForLogMessage } from '@affine-test/kit/utils/utils';
|
||||||
import { expect } from '@playwright/test';
|
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,
|
page,
|
||||||
workspace,
|
workspace,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '@affine-test/kit/utils/page-logic';
|
} from '@affine-test/kit/utils/page-logic';
|
||||||
import { expect } from '@playwright/test';
|
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 openHomePage(page);
|
||||||
await waitForEditorLoad(page);
|
await waitForEditorLoad(page);
|
||||||
await clickNewPageButton(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');
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user