From 28a1ac47726b4d3d89ebb48f9d67dd11a1efedbd Mon Sep 17 00:00:00 2001 From: Daniel Dybing Date: Sat, 20 Dec 2025 07:45:40 +0100 Subject: [PATCH] feat(core): focus on text body when opening journal (#14122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to issue https://github.com/toeverything/AFFiNE/issues/14094 This PR makes it so that focus is put on the input body when loading a journal. A check is made when loading the document whether it is a normal document or a journal document. If it is a journal document, the last noteblock in the document is focused on. This does not change how the title is focused on normal documents. This makes it more effortless to use the journal, as you don't have to click on the body of the journal after opening/creating it. ## Summary by CodeRabbit * **New Features** * Improved editor focus for journal documents: when opening or switching to a journal the cursor now auto-positions to the end of the last note entry (or the input area) after a short, smooth delay for faster typing and reliable focus behavior. * **Bug Fixes** * Added safeguards and error handling to make automatic focus more robust across load and editor states. ✏️ Tip: You can customize this high-level summary in your review settings. --- .../workspace/detail-page/detail-page.tsx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx index 532da1329..1178dcf30 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx @@ -22,6 +22,7 @@ import { DocService } from '@affine/core/modules/doc'; import { EditorService } from '@affine/core/modules/editor'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { GlobalContextService } from '@affine/core/modules/global-context'; +import { JournalService } from '@affine/core/modules/journal'; import { PeekViewService } from '@affine/core/modules/peek-view'; import { RecentDocsService } from '@affine/core/modules/quicksearch'; import { @@ -38,6 +39,8 @@ import { ServerFeature } from '@affine/graphql'; import track from '@affine/track'; import { DisposableGroup } from '@blocksuite/affine/global/disposable'; import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference'; +import { focusBlockEnd } from '@blocksuite/affine/shared/commands'; +import { getLastNoteBlock } from '@blocksuite/affine/shared/utils'; import { AiIcon, CommentIcon, @@ -184,10 +187,37 @@ const DetailPageImpl = memo(function DetailPageImpl() { useRegisterBlocksuiteEditorCommands(editor, isActiveView); + const journalService = useService(JournalService); + const isJournal = !!useLiveData(journalService.journalDate$(doc.id)); + const onLoad = useCallback( (editorContainer: AffineEditorContainer) => { const std = editorContainer.std; const disposable = new DisposableGroup(); + + // Check if journal and handle accordingly to set focus on input block. + if (isJournal) { + const rafId = requestAnimationFrame(() => { + try { + if (!editorContainer.isConnected) return; + const page = editorContainer.page; + const note = getLastNoteBlock(page); + const std = editorContainer.std; + if (note) { + const lastBlock = note.lastChild(); + if (lastBlock) { + const focusBlock = std.view.getBlock(lastBlock.id) ?? undefined; + std.command.exec(focusBlockEnd, { focusBlock, force: true }); + return; + } + } + std.command.exec(focusBlockEnd, { force: true }); + } catch (error) { + console.error('Failed to focus journal body', error); + } + }); + disposable.add(() => cancelAnimationFrame(rafId)); + } if (std) { const refNodeSlots = std.getOptional(RefNodeSlotsProvider); if (refNodeSlots) { @@ -265,7 +295,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { disposable.dispose(); }; }, - [editor, workbench, peekView] + [editor, workbench, peekView, isJournal] ); const [hasScrollTop, setHasScrollTop] = useState(false);