feat: allow opening new tab for some navigation buttons (#7764)

fix AF-1010
This commit is contained in:
pengx17
2024-08-07 06:43:10 +00:00
parent e1087a0c7b
commit ae9381c36d
12 changed files with 111 additions and 111 deletions

View File

@@ -1,8 +1,8 @@
import { WorkbenchLink } from '@affine/core/modules/workbench';
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx';
import React from 'react';
import type { To } from 'react-router-dom';
import { Link } from 'react-router-dom';
import * as styles from './index.css';
@@ -90,7 +90,7 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
MenuItem.displayName = 'MenuItem';
export const MenuLinkItem = React.forwardRef<HTMLDivElement, MenuLinkItemProps>(
({ to, linkComponent: LinkComponent = Link, ...props }, ref) => {
({ to, linkComponent: LinkComponent = WorkbenchLink, ...props }, ref) => {
return (
<LinkComponent to={to} className={styles.linkItemRoot}>
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}

View File

@@ -1,18 +1,14 @@
import { toast } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useDocCollectionHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
import { WorkspaceSubPath } from '@affine/core/shared';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { DocsService, initEmptyPage, useService } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import type { DocCollection } from '../../../shared';
export const usePageHelper = (docCollection: DocCollection) => {
const { openPage, jumpToSubPath } = useNavigateHelper();
const workbench = useService(WorkbenchService).workbench;
const { createDoc } = useDocCollectionHelper(docCollection);
const { setDocMeta } = useDocMetaHelper(docCollection);
const docRecordList = useService(DocsService).list;
const isPreferredEdgeless = useCallback(
@@ -22,18 +18,21 @@ export const usePageHelper = (docCollection: DocCollection) => {
);
const createPageAndOpen = useCallback(
(mode?: 'page' | 'edgeless', open?: boolean) => {
(mode?: 'page' | 'edgeless', open?: boolean | 'new-tab') => {
const page = createDoc();
initEmptyPage(page);
docRecordList.doc$(page.id).value?.setMode(mode || 'page');
if (open !== false) openPage(docCollection.id, page.id);
if (open !== false)
workbench.openDoc(page.id, {
at: open === 'new-tab' ? 'new-tab' : 'active',
});
return page;
},
[docCollection.id, createDoc, openPage, docRecordList]
[createDoc, docRecordList, workbench]
);
const createEdgelessAndOpen = useCallback(
(open?: boolean) => {
(open?: boolean | 'new-tab') => {
return createPageAndOpen('edgeless', open);
},
[createPageAndOpen]
@@ -59,7 +58,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
}.`
);
if (options.isWorkspaceFile) {
jumpToSubPath(docCollection.id, WorkspaceSubPath.ALL);
workbench.openAll();
return;
}
@@ -67,7 +66,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
return;
}
const pageId = pageIds[0];
openPage(docCollection.id, pageId);
workbench.openDoc(pageId);
};
showImportModal({
collection: docCollection,
@@ -78,47 +77,20 @@ export const usePageHelper = (docCollection: DocCollection) => {
});
return await promise;
},
[docCollection, openPage, jumpToSubPath]
);
const createLinkedPageAndOpen = useAsyncCallback(
async (pageId: string) => {
const page = createPageAndOpen();
page.load();
const parentPage = docCollection.getDoc(pageId);
if (parentPage) {
parentPage.load();
const text = parentPage.Text.fromDelta([
{
insert: ' ',
attributes: {
reference: {
type: 'LinkedPage',
pageId: page.id,
},
},
},
]);
const [frame] = parentPage.getBlockByFlavour('affine:note');
frame && parentPage.addBlock('affine:paragraph', { text }, frame.id);
setDocMeta(page.id, {});
}
},
[docCollection, createPageAndOpen, setDocMeta]
[docCollection, workbench]
);
return useMemo(() => {
return {
isPreferredEdgeless,
createPage: createPageAndOpen,
createPage: (open?: boolean | 'new-tab') =>
createPageAndOpen('page', open),
createEdgeless: createEdgelessAndOpen,
importFile: importFileAndOpen,
createLinkedPage: createLinkedPageAndOpen,
};
}, [
isPreferredEdgeless,
createEdgelessAndOpen,
createLinkedPageAndOpen,
createPageAndOpen,
importFileAndOpen,
]);

View File

@@ -3,14 +3,14 @@ import { BlockCard } from '@affine/component/card/block-card';
import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n';
import { EdgelessIcon, ImportIcon, PageIcon } from '@blocksuite/icons/rc';
import type { PropsWithChildren } from 'react';
import type { MouseEvent, PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';
import { menuContent } from './new-page-button.css';
type NewPageButtonProps = {
createNewPage: () => void;
createNewEdgeless: () => void;
createNewPage: (e?: MouseEvent) => void;
createNewEdgeless: (e?: MouseEvent) => void;
importFile?: () => void;
size?: 'small' | 'default';
};
@@ -67,19 +67,26 @@ export const NewPageButton = ({
}: PropsWithChildren<NewPageButtonProps>) => {
const [open, setOpen] = useState(false);
const handleCreateNewPage = useCallback(() => {
createNewPage();
setOpen(false);
track.allDocs.header.actions.createDoc();
}, [createNewPage]);
const handleCreateNewPage: NewPageButtonProps['createNewPage'] = useCallback(
e => {
createNewPage(e);
setOpen(false);
track.allDocs.header.actions.createDoc();
},
[createNewPage]
);
const handleCreateNewEdgeless = useCallback(() => {
createNewEdgeless();
setOpen(false);
track.allDocs.header.actions.createDoc({
mode: 'edgeless',
});
}, [createNewEdgeless]);
const handleCreateNewEdgeless: NewPageButtonProps['createNewEdgeless'] =
useCallback(
e => {
createNewEdgeless(e);
setOpen(false);
track.allDocs.header.actions.createDoc({
mode: 'edgeless',
});
},
[createNewEdgeless]
);
const handleImportFile = useCallback(() => {
importFile?.();

View File

@@ -65,8 +65,13 @@ export const PageListHeader = () => {
<PageListNewPageButton
size="small"
testId="new-page-button-trigger"
onCreateEdgeless={createEdgeless}
onCreatePage={createPage}
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)
}
onImportFile={onImportFile}
>
<div className={styles.buttonText}>{t['New Page']()}</div>

View File

@@ -1,4 +1,4 @@
import type { PropsWithChildren } from 'react';
import type { MouseEvent, PropsWithChildren } from 'react';
import { NewPageButton } from '../components/new-page-button';
import * as styles from './page-list-new-page-button.css';
@@ -15,9 +15,9 @@ export const PageListNewPageButton = ({
className?: string;
size?: 'small' | 'default';
testId?: string;
onCreatePage: () => void;
onCreateEdgeless: () => void;
onImportFile?: () => void;
onCreatePage: (e?: MouseEvent) => void;
onCreateEdgeless: (e?: MouseEvent) => void;
onImportFile?: (e?: MouseEvent) => void;
}>) => {
return (
<div className={className} data-testid={testId}>

View File

@@ -9,7 +9,6 @@ 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 { pathGenerator } from '@affine/core/shared';
import { apis, events } from '@affine/electron-api';
import { useI18n } from '@affine/i18n';
import { AllDocsIcon, SettingsIcon } from '@blocksuite/icons/rc';
@@ -69,7 +68,6 @@ export type RootAppSidebarProps = {
*/
export const RootAppSidebar = (): ReactElement => {
const currentWorkspace = useService(WorkspaceService).workspace;
const currentWorkspaceId = currentWorkspace.id;
const { appSettings } = useAppSettingHelper();
const docCollection = currentWorkspace.docCollection;
const t = useI18n();
@@ -88,15 +86,13 @@ export const RootAppSidebar = (): ReactElement => {
const onClickNewPage = useAsyncCallback(
async (e?: MouseEvent) => {
const page = pageHelper.createPage('page', false);
const page = pageHelper.createPage(
e?.ctrlKey || e?.metaKey ? 'new-tab' : true
);
page.load();
track.$.navigationPanel.$.createDoc();
workbench.openDoc(page.id, {
at: e?.ctrlKey || e?.metaKey ? 'new-tab' : 'active',
});
},
[pageHelper, workbench]
[pageHelper]
);
useEffect(() => {
if (environment.isDesktop) {
@@ -145,11 +141,7 @@ export const RootAppSidebar = (): ReactElement => {
/>
<AddPageButton onClick={onClickNewPage} />
</div>
<MenuLinkItem
icon={<AllDocsIcon />}
active={allPageActive}
to={pathGenerator.all(currentWorkspaceId)}
>
<MenuLinkItem icon={<AllDocsIcon />} active={allPageActive} to={'/all'}>
<span data-testid="all-pages">
{t['com.affine.workspaceSubPath.all']()}
</span>

View File

@@ -7,6 +7,7 @@ import type { DocCollection } from '@affine/core/shared';
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 { MenuItem } from '../app-sidebar';
@@ -26,6 +27,13 @@ export const AppSidebarJournalButton = ({
location.pathname.split('/')[1]
);
const handleOpenToday = useCallback(
(e: MouseEvent) => {
openToday(e.ctrlKey || e.metaKey);
},
[openToday]
);
const Icon =
isJournal && journalDate
? journalDate.isBefore(new Date(), 'day')
@@ -39,7 +47,7 @@ export const AppSidebarJournalButton = ({
<MenuItem
data-testid="slider-bar-journals-button"
active={isJournal}
onClick={openToday}
onClick={handleOpenToday}
icon={<Icon />}
>
{t['com.affine.journal.app-sidebar-title']()}

View File

@@ -3,7 +3,6 @@ import {
useConfirmModal,
useDropTarget,
} from '@affine/component';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n';
import {
@@ -62,7 +61,6 @@ export const TrashButton = () => {
ref={dropTargetRef}
icon={<AnimatedDeleteIcon closed={draggedOver} />}
active={trashActive || draggedOver}
linkComponent={WorkbenchLink}
to={'/trash'}
>
<span data-testid="trash-page">

View File

@@ -1,12 +1,12 @@
import { i18nTime } from '@affine/i18n';
import { initEmptyPage } from '@toeverything/infra';
import { initEmptyPage, useService } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';
import { WorkbenchService } from '../modules/workbench';
import type { DocCollection } from '../shared';
import { useCurrentWorkspacePropertiesAdapter } from './use-affine-adapter';
import { useDocCollectionHelper } from './use-block-suite-workspace-helper';
import { useNavigateHelper } from './use-navigate-helper';
type MaybeDate = Date | string | number;
export const JOURNAL_DATE_FORMAT = 'YYYY-MM-DD';
@@ -153,26 +153,32 @@ export const useJournalHelper = (docCollection: DocCollection) => {
// split useJournalRouteHelper since it requires a <Route /> context, which may not work in lit
export const useJournalRouteHelper = (docCollection: DocCollection) => {
const navigateHelper = useNavigateHelper();
const { getJournalByDate } = useJournalHelper(docCollection);
const workbench = useService(WorkbenchService).workbench;
/**
* open journal by date, create one if not exist
*/
const openJournal = useCallback(
(maybeDate: MaybeDate) => {
(maybeDate: MaybeDate, newTab?: boolean) => {
const page = getJournalByDate(maybeDate);
navigateHelper.openPage(docCollection.id, page.id);
workbench.openDoc(page.id, {
at: newTab ? 'new-tab' : 'active',
});
return page.id;
},
[getJournalByDate, navigateHelper, docCollection.id]
[getJournalByDate, workbench]
);
/**
* open today's journal
*/
const openToday = useCallback(() => {
const date = dayjs().format(JOURNAL_DATE_FORMAT);
openJournal(date);
}, [openJournal]);
const openToday = useCallback(
(newTab?: boolean) => {
const date = dayjs().format(JOURNAL_DATE_FORMAT);
return openJournal(date, newTab);
},
[openJournal]
);
return useMemo(
() => ({

View File

@@ -20,7 +20,7 @@ import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc';
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
import { type MouseEventHandler, useCallback, useMemo } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section';
@@ -88,21 +88,26 @@ export const ExplorerFavorites = () => {
[]
);
const handleCreateNewFavoriteDoc = useCallback(() => {
const newDoc = docsService.createDoc();
favoriteService.favoriteList.add(
'doc',
newDoc.id,
favoriteService.favoriteList.indexAt('before')
);
workbenchService.workbench.openDoc(newDoc.id);
explorerSection.setCollapsed(false);
}, [
docsService,
explorerSection,
favoriteService.favoriteList,
workbenchService.workbench,
]);
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
e => {
const newDoc = docsService.createDoc();
favoriteService.favoriteList.add(
'doc',
newDoc.id,
favoriteService.favoriteList.indexAt('before')
);
workbenchService.workbench.openDoc(newDoc.id, {
at: e.ctrlKey || e.metaKey ? 'new-tab' : 'active',
});
explorerSection.setCollapsed(false);
},
[
docsService,
explorerSection,
favoriteService.favoriteList,
workbenchService.workbench,
]
);
const handleOnChildrenDrop = useCallback(
(

View File

@@ -59,8 +59,12 @@ export const AllPageHeader = ({
styles.headerCreateNewButton,
!showCreateNew && styles.headerCreateNewButtonHidden
)}
onCreateEdgeless={createEdgeless}
onCreatePage={createPage}
onCreateEdgeless={e =>
createEdgeless(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
}
onCreatePage={e =>
createPage(e?.metaKey || e?.ctrlKey ? 'new-tab' : true)
}
onImportFile={onImportFile}
>
<PlusIcon />