diff --git a/packages/frontend/core/src/mobile/components/app-tabs/create.tsx b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx
new file mode 100644
index 000000000..657541363
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx
@@ -0,0 +1,51 @@
+import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
+import { JournalService } from '@affine/core/modules/journal';
+import { WorkbenchService } from '@affine/core/modules/workbench';
+import track from '@affine/track';
+import { EditIcon } from '@blocksuite/icons/rc';
+import {
+ DocsService,
+ useLiveData,
+ useServices,
+ WorkspaceService,
+} from '@toeverything/infra';
+import { useCallback } from 'react';
+
+import { tabItem } from './styles.css';
+
+export const AppTabCreate = () => {
+ const { docsService, workbenchService, workspaceService, journalService } =
+ useServices({
+ DocsService,
+ WorkbenchService,
+ WorkspaceService,
+ JournalService,
+ });
+ const workbench = workbenchService.workbench;
+ const currentWorkspace = workspaceService.workspace;
+ const location = useLiveData(workbench.location$);
+ const pageHelper = usePageHelper(currentWorkspace.docCollection);
+
+ const maybeDocId = location.pathname.split('/')[1].split('?')[0];
+ const doc = useLiveData(docsService.list.doc$(maybeDocId));
+ const journalDate = useLiveData(journalService.journalDate$(maybeDocId));
+ const isActive = !!doc && !journalDate;
+
+ const createPage = useCallback(() => {
+ if (isActive) return;
+ pageHelper.createPage(undefined, true);
+ track.$.navigationPanel.$.createDoc();
+ }, [isActive, pageHelper]);
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/core/src/mobile/components/app-tabs/index.tsx b/packages/frontend/core/src/mobile/components/app-tabs/index.tsx
index 9e3507f81..cd8f19b3b 100644
--- a/packages/frontend/core/src/mobile/components/app-tabs/index.tsx
+++ b/packages/frontend/core/src/mobile/components/app-tabs/index.tsx
@@ -3,13 +3,14 @@ import {
WorkbenchLink,
WorkbenchService,
} from '@affine/core/modules/workbench';
-import { AllDocsIcon, MobileHomeIcon, SearchIcon } from '@blocksuite/icons/rc';
+import { AllDocsIcon, MobileHomeIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import React from 'react';
import type { Location } from 'react-router-dom';
import { VirtualKeyboardService } from '../../modules/virtual-keyboard/services/virtual-keyboard';
+import { AppTabCreate } from './create';
import { AppTabJournal } from './journal';
import * as styles from './styles.css';
@@ -48,9 +49,8 @@ const routes: Route[] = [
node: ,
},
{
- key: 'search',
- to: '/search',
- Icon: SearchIcon,
+ key: 'new',
+ node: ,
},
];
diff --git a/packages/frontend/core/src/mobile/components/index.ts b/packages/frontend/core/src/mobile/components/index.ts
index 18dd7f3f6..81bb7db8a 100644
--- a/packages/frontend/core/src/mobile/components/index.ts
+++ b/packages/frontend/core/src/mobile/components/index.ts
@@ -1,5 +1,6 @@
export * from './app-tabs';
export * from './doc-card';
+export * from './navigation-back';
export * from './page-header';
export * from './rename';
export * from './search-input';
diff --git a/packages/frontend/core/src/mobile/components/navigation-back/index.tsx b/packages/frontend/core/src/mobile/components/navigation-back/index.tsx
new file mode 100644
index 000000000..b29efe9f9
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/navigation-back/index.tsx
@@ -0,0 +1,54 @@
+import {
+ IconButton,
+ type IconButtonProps,
+ useIsInsideModal,
+} from '@affine/component';
+import { ArrowLeftSmallIcon, CloseIcon } from '@blocksuite/icons/rc';
+import { useService } from '@toeverything/infra';
+import { useCallback, useEffect, useMemo } from 'react';
+
+import { NavigationGestureService } from '../../modules/navigation-gesture';
+
+export interface NavigationBackButtonProps extends IconButtonProps {
+ backAction?: () => void;
+}
+
+/**
+ * A button to control the back behavior of the mobile app, as well as manage navigation gesture
+ */
+export const NavigationBackButton = ({
+ backAction,
+ style: propsStyle,
+ ...otherProps
+}: NavigationBackButtonProps) => {
+ const navigationGesture = useService(NavigationGestureService);
+ const isInsideModal = useIsInsideModal();
+
+ const handleRouteBack = useCallback(() => {
+ backAction ? backAction() : history.back();
+ }, [backAction]);
+
+ useEffect(() => {
+ if (isInsideModal) return;
+
+ const prev = navigationGesture.enabled$.value;
+ navigationGesture.setEnabled(true);
+
+ return () => {
+ navigationGesture.setEnabled(prev);
+ };
+ }, [isInsideModal, navigationGesture]);
+
+ const style = useMemo(() => ({ padding: 10, ...propsStyle }), [propsStyle]);
+
+ return (
+ : }
+ data-testid="page-header-back"
+ {...otherProps}
+ />
+ );
+};
diff --git a/packages/frontend/core/src/mobile/components/page-header/index.tsx b/packages/frontend/core/src/mobile/components/page-header/index.tsx
index 2ff0ce6a9..70ae57ed4 100644
--- a/packages/frontend/core/src/mobile/components/page-header/index.tsx
+++ b/packages/frontend/core/src/mobile/components/page-header/index.tsx
@@ -1,16 +1,8 @@
-import { IconButton, SafeArea, useIsInsideModal } from '@affine/component';
-import { ArrowLeftSmallIcon, CloseIcon } from '@blocksuite/icons/rc';
-import { useService } from '@toeverything/infra';
+import { SafeArea } from '@affine/component';
import clsx from 'clsx';
-import {
- forwardRef,
- type HtmlHTMLAttributes,
- type ReactNode,
- useCallback,
- useEffect,
-} from 'react';
+import { forwardRef, type HtmlHTMLAttributes, type ReactNode } from 'react';
-import { NavigationGestureService } from '../../modules/navigation-gesture';
+import { NavigationBackButton } from '../navigation-back';
import * as styles from './styles.css';
export interface PageHeaderProps
@@ -74,24 +66,6 @@ export const PageHeader = forwardRef(
},
ref
) {
- const navigationGesture = useService(NavigationGestureService);
- const isInsideModal = useIsInsideModal();
-
- useEffect(() => {
- if (isInsideModal) return;
-
- const prev = navigationGesture.enabled$.value;
- navigationGesture.setEnabled(!!back);
-
- return () => {
- navigationGesture.setEnabled(prev);
- };
- }, [back, isInsideModal, navigationGesture]);
-
- const handleRouteBack = useCallback(() => {
- backAction ? backAction() : history.back();
- }, [backAction]);
-
return (
<>
(
className={clsx(styles.prefix, prefixClassName)}
style={prefixStyle}
>
- {back ? (
- : }
- data-testid="page-header-back"
- />
- ) : null}
+ {back ? : null}
{prefix}
diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx
index ae0f7823a..67f65d143 100644
--- a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx
+++ b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx
@@ -169,7 +169,7 @@ const DetailPageImpl = () => {
editor.bindEditorContainer(
editorContainer,
- null,
+ (editorContainer as any).docTitle, // set from proxy
scrollViewportRef.current
);
@@ -272,9 +272,7 @@ const MobileDetailPage = ({
) : null}
- {date ? (
-
- ) : null}
+
);
diff --git a/packages/frontend/core/src/mobile/pages/workspace/search.tsx b/packages/frontend/core/src/mobile/pages/workspace/search.tsx
index 8c379f9d3..5219545a2 100644
--- a/packages/frontend/core/src/mobile/pages/workspace/search.tsx
+++ b/packages/frontend/core/src/mobile/pages/workspace/search.tsx
@@ -1,10 +1,15 @@
-import { SafeArea, useThemeColorV2 } from '@affine/component';
+import {
+ SafeArea,
+ startScopedViewTransition,
+ useThemeColorV2,
+} from '@affine/component';
import { CollectionService } from '@affine/core/modules/collection';
import {
type QuickSearchItem,
QuickSearchTagIcon,
} from '@affine/core/modules/quicksearch';
import { TagService } from '@affine/core/modules/tag';
+import { sleep } from '@blocksuite/affine/global/utils';
import { ViewLayersIcon } from '@blocksuite/icons/rc';
import {
LiveData,
@@ -14,7 +19,12 @@ import {
} from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
-import { AppTabs, SearchInput, SearchResLabel } from '../../components';
+import {
+ NavigationBackButton,
+ SearchInput,
+ SearchResLabel,
+} from '../../components';
+import { searchVTScope } from '../../components/search-input/style.css';
import { MobileSearchService } from '../../modules/search';
import { SearchResults } from '../../views/search/search-results';
import * as styles from '../../views/search/style.css';
@@ -133,11 +143,20 @@ export const Component = () => {
]
);
+ const transitionBack = useCallback(() => {
+ startScopedViewTransition(searchVTScope, async () => {
+ history.back();
+ await sleep(10);
+ });
+ }, []);
+
return (
<>
+
{
{searchInput ? : }
-
>
);
};
diff --git a/packages/frontend/core/src/mobile/views/search/style.css.ts b/packages/frontend/core/src/mobile/views/search/style.css.ts
index 3123021be..d1628904a 100644
--- a/packages/frontend/core/src/mobile/views/search/style.css.ts
+++ b/packages/frontend/core/src/mobile/views/search/style.css.ts
@@ -4,6 +4,14 @@ import { style } from '@vanilla-extract/css';
export const searchHeader = style({
padding: 16,
+ paddingLeft: 8,
+ display: 'flex',
+ alignItems: 'center',
+ gap: 4,
+});
+export const searchInput = style({
+ width: 0,
+ flex: 1,
});
export const resTitle = style([
diff --git a/packages/frontend/core/src/modules/editor/entities/editor.ts b/packages/frontend/core/src/modules/editor/entities/editor.ts
index dd5921a33..7955e196c 100644
--- a/packages/frontend/core/src/modules/editor/entities/editor.ts
+++ b/packages/frontend/core/src/modules/editor/entities/editor.ts
@@ -229,7 +229,15 @@ export class Editor extends Entity {
const title = docTitle?.querySelector<
HTMLElement & { inlineEditor: InlineEditor | null }
>('rich-text');
- title?.inlineEditor?.focusEnd();
+ // Only focus on the title when it's empty on mobile edition.
+ if (BUILD_CONFIG.isMobileEdition) {
+ const titleText = this.doc.title$.value;
+ if (!titleText?.length) {
+ title?.inlineEditor?.focusEnd();
+ }
+ } else {
+ title?.inlineEditor?.focusEnd();
+ }
} else {
const selection = editorContainer.host?.std.selection;
diff --git a/tests/affine-mobile/e2e/home.spec.ts b/tests/affine-mobile/e2e/home.spec.ts
index c6591fdcb..1f4b4cfe2 100644
--- a/tests/affine-mobile/e2e/home.spec.ts
+++ b/tests/affine-mobile/e2e/home.spec.ts
@@ -13,7 +13,8 @@ test('app tabs is visible', async ({ page }) => {
await expect(tabs.getByRole('tab', { name: 'home' })).toBeVisible();
await expect(tabs.getByRole('tab', { name: 'all' })).toBeVisible();
- await expect(tabs.getByRole('tab', { name: 'search' })).toBeVisible();
+ await expect(tabs.getByRole('tab', { name: 'journal' })).toBeVisible();
+ await expect(tabs.getByRole('tab', { name: 'new' })).toBeVisible();
});
test('recent docs', async ({ page }) => {
@@ -49,15 +50,3 @@ test('all tab', async ({ page }) => {
const todayDocs = page.getByTestId('doc-card');
expect(await todayDocs.count()).toBeGreaterThan(0);
});
-
-test('search tab', async ({ page }) => {
- const searchTab = page
- .locator('#app-tabs')
- .getByRole('tab', { name: 'search' });
- await expect(searchTab).toBeVisible();
-
- await searchTab.click();
-
- const searchInput = page.getByTestId('search-header').getByRole('textbox');
- await expect(searchInput).toBeVisible();
-});