diff --git a/packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx b/packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx index 31d153d4c5..537e51d5c2 100644 --- a/packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx +++ b/packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx @@ -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( MenuItem.displayName = 'MenuItem'; export const MenuLinkItem = React.forwardRef( - ({ to, linkComponent: LinkComponent = Link, ...props }, ref) => { + ({ to, linkComponent: LinkComponent = WorkbenchLink, ...props }, ref) => { return ( {/* The element rendered by Link does not generate display box due to `display: contents` style */} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index 35b5e69ece..6b7a62899d 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -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, ]); 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 d2ca4d548f..05a026e79b 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 @@ -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) => { 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?.(); 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 dea28f58df..4a0929dba3 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 @@ -65,8 +65,13 @@ export const PageListHeader = () => { + // 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} >
{t['New Page']()}
diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-new-page-button.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-new-page-button.tsx index 7d76d29b9c..1b0666f7ff 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-new-page-button.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-new-page-button.tsx @@ -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 (
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 3f6dd0b0ef..4910df3209 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -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 => { />
- } - active={allPageActive} - to={pathGenerator.all(currentWorkspaceId)} - > + } active={allPageActive} to={'/all'}> {t['com.affine.workspaceSubPath.all']()} 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 3fcd3c7f01..514903100e 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 @@ -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 = ({ } > {t['com.affine.journal.app-sidebar-title']()} diff --git a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx index 9d5e70f1cd..0f30111f37 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx @@ -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={} active={trashActive || draggedOver} - linkComponent={WorkbenchLink} to={'/trash'} > diff --git a/packages/frontend/core/src/hooks/use-journal.ts b/packages/frontend/core/src/hooks/use-journal.ts index c419988569..c51520969c 100644 --- a/packages/frontend/core/src/hooks/use-journal.ts +++ b/packages/frontend/core/src/hooks/use-journal.ts @@ -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 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( () => ({ 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 4ed7db8019..76a87772a5 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 @@ -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( ( 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 91190c8fe9..61be4cd4f0 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 @@ -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} > diff --git a/tests/affine-local/e2e/local-first-collections-items.spec.ts b/tests/affine-local/e2e/local-first-collections-items.spec.ts index 1d7ca4b489..6c9f4ce38a 100644 --- a/tests/affine-local/e2e/local-first-collections-items.spec.ts +++ b/tests/affine-local/e2e/local-first-collections-items.spec.ts @@ -38,6 +38,9 @@ const createAndPinCollection = async ( await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('test page'); + // fixme: remove this timeout. looks like an issue with useBindWorkbenchToBrowserRouter? + await page.waitForTimeout(500); + await page.getByTestId('all-pages').click(); const cell = page.getByTestId('page-list-item-title').getByText('test page'); @@ -67,7 +70,7 @@ test('Show collections items in sidebar', async ({ page }) => { const collections = page.getByTestId('explorer-collections'); await collections.getByTestId('category-divider-collapse-button').click(); const items = collections.locator('[data-testid^="explorer-collection-"]'); - expect(await items.count()).toBe(1); + await expect(items).toHaveCount(1); const first = items.first(); expect(await first.textContent()).toBe('test collection'); await first.getByTestId('explorer-collapsed-button').click(); @@ -87,7 +90,7 @@ test('Show collections items in sidebar', async ({ page }) => { const deleteCollection = page.getByText('Delete'); await deleteCollection.click(); await page.waitForTimeout(50); - expect(await items.count()).toBe(0); + await expect(items).toHaveCount(0); await createAndPinCollection(page); expect(await items.count()).toBe(1); await clickSideBarAllPageButton(page);