mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
feat: allow opening new tab for some navigation buttons (#7764)
fix AF-1010
This commit is contained in:
@@ -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 */}
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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?.();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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']()}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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(
|
||||
() => ({
|
||||
|
||||
@@ -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(
|
||||
(
|
||||
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user