diff --git a/packages/frontend/component/src/components/block-suite-editor/index.tsx b/packages/frontend/component/src/components/block-suite-editor/index.tsx index 4b5fdbe92c..04d35085ff 100644 --- a/packages/frontend/component/src/components/block-suite-editor/index.tsx +++ b/packages/frontend/component/src/components/block-suite-editor/index.tsx @@ -1,11 +1,11 @@ import { assertExists } from '@blocksuite/global/utils'; import { AffineEditorContainer } from '@blocksuite/presets'; import type { Page } from '@blocksuite/store'; -import { useBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; import clsx from 'clsx'; import { use } from 'foxact/use'; import type { CSSProperties, ReactElement } from 'react'; import { + forwardRef, memo, Suspense, useEffect, @@ -137,104 +137,115 @@ function usePageRoot(page: Page) { return page.root; } -const BlockSuiteEditorImpl = ({ - mode, - page, - className, - defaultSelectedBlockId, - onLoadEditor, - onModeChange, - style, -}: EditorProps): ReactElement => { - usePageRoot(page); +const BlockSuiteEditorImpl = forwardRef( + ( + { + mode, + page, + className, + defaultSelectedBlockId, + onLoadEditor, + onModeChange, + style, + }, + ref + ): ReactElement => { + usePageRoot(page); - const [, setEditorContainer] = useBlocksuiteEditor(); - assertExists(page, 'page should not be null'); - const editorRef = useRef(null); - if (editorRef.current === null) { - editorRef.current = new AffineEditorContainer(); - editorRef.current.autofocus = true; - } - const editor = editorRef.current; - assertExists(editorRef, 'editorRef.current should not be null'); + assertExists(page, 'page should not be null'); + const editorRef = useRef(null); + if (editorRef.current === null) { + editorRef.current = new AffineEditorContainer(); + editorRef.current.autofocus = true; + } + const editor = editorRef.current; + assertExists(editorRef, 'editorRef.current should not be null'); - if (editor.mode !== mode) { - editor.mode = mode; - } + if (editor.mode !== mode) { + editor.mode = mode; + } - if (editor.page !== page) { - editor.page = page; - editor.docSpecs = editorSpecs.docModeSpecs; - editor.edgelessSpecs = editorSpecs.edgelessModeSpecs; - } + if (editor.page !== page) { + editor.page = page; + editor.docSpecs = editorSpecs.docModeSpecs; + editor.edgelessSpecs = editorSpecs.edgelessModeSpecs; + } - useLayoutEffect(() => { - if (editor) { - const disposes: (() => void)[] = []; - const disposeModeSwitch = editor.slots.pageModeSwitched.on(mode => { - onModeChange?.(mode); - }); - disposes.push(() => disposeModeSwitch?.dispose()); - if (onLoadEditor) { - disposes.push(onLoadEditor(editor)); + if (ref) { + if (typeof ref === 'function') { + ref(editor); + } else { + ref.current = editor; } - return () => { - disposes.forEach(dispose => dispose()); - }; } - return; - }, [editor, onModeChange, onLoadEditor]); - const containerRef = useRef(null); - - useEffect(() => { - const container = containerRef.current; - if (!container) { - return; - } - container.append(editor); - setEditorContainer(editor); - return () => { - editor.remove(); - setEditorContainer(null); - }; - }, [editor, setEditorContainer]); - - const blockElement = useBlockElementById( - containerRef.current, - defaultSelectedBlockId - ); - - useEffect(() => { - if (blockElement) { - requestIdleCallback(() => { - blockElement.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center', + useLayoutEffect(() => { + if (editor) { + const disposes: (() => void)[] = []; + const disposeModeSwitch = editor.slots.pageModeSwitched.on(mode => { + onModeChange?.(mode); }); - const selectManager = editor.root?.selection; - if (!blockElement.path.length || !selectManager) { - return; + disposes.push(() => disposeModeSwitch?.dispose()); + if (onLoadEditor) { + disposes.push(onLoadEditor(editor)); } - const newSelection = selectManager.getInstance('block', { - path: blockElement.path, - }); - selectManager.set([newSelection]); - }); - } - }, [editor, blockElement]); + return () => { + disposes.forEach(dispose => dispose()); + }; + } + return; + }, [editor, onModeChange, onLoadEditor]); - // issue: https://github.com/toeverything/AFFiNE/issues/2004 - return ( -
- ); -}; + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (!container) { + return; + } + container.append(editor); + return () => { + editor.remove(); + }; + }, [editor]); + + const blockElement = useBlockElementById( + containerRef.current, + defaultSelectedBlockId + ); + + useEffect(() => { + if (blockElement) { + requestIdleCallback(() => { + blockElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center', + }); + const selectManager = editor.root?.selection; + if (!blockElement.path.length || !selectManager) { + return; + } + const newSelection = selectManager.getInstance('block', { + path: blockElement.path, + }); + selectManager.set([newSelection]); + }); + } + }, [editor, blockElement]); + + // issue: https://github.com/toeverything/AFFiNE/issues/2004 + return ( +
+ ); + } +); +BlockSuiteEditorImpl.displayName = 'BlockSuiteEditorImpl'; export const EditorLoading = memo(function EditorLoading() { return ( @@ -249,14 +260,16 @@ export const EditorLoading = memo(function EditorLoading() { ); }); -export const BlockSuiteEditor = memo(function BlockSuiteEditor( - props: EditorProps -): ReactElement { - return ( - }> - - - ); -}); +export const BlockSuiteEditor = memo( + forwardRef( + function BlockSuiteEditor(props, ref): ReactElement { + return ( + }> + + + ); + } + ) +); BlockSuiteEditor.displayName = 'BlockSuiteEditor'; diff --git a/packages/frontend/core/src/components/page-detail-editor.tsx b/packages/frontend/core/src/components/page-detail-editor.tsx index 3a0545d511..6924a35ed1 100644 --- a/packages/frontend/core/src/components/page-detail-editor.tsx +++ b/packages/frontend/core/src/components/page-detail-editor.tsx @@ -3,12 +3,21 @@ import './page-detail-editor.css'; import { assertExists, DisposableGroup } from '@blocksuite/global/utils'; import type { AffineEditorContainer } from '@blocksuite/presets'; import type { Page, Workspace } from '@blocksuite/store'; +import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page'; import { fontStyleOptions } from '@toeverything/infra/atom'; import clsx from 'clsx'; import { useAtomValue } from 'jotai'; import type { CSSProperties } from 'react'; -import { memo, Suspense, useCallback, useMemo, useState } from 'react'; +import { + memo, + Suspense, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useLocation } from 'react-router-dom'; import { type PageMode, pageSettingFamily } from '../atoms'; @@ -115,6 +124,13 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({ [onLoad, page] ); + const [, setActiveBlocksuiteEditor] = useActiveBlocksuiteEditor(); + const editor = useRef(null); + + useEffect(() => { + setActiveBlocksuiteEditor(editor.current); + }, [setActiveBlocksuiteEditor]); + return ( ); }); diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx index 01f1068cec..8e5833c93c 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx @@ -1,7 +1,7 @@ import { assertExists } from '@blocksuite/global/utils'; import { AiIcon } from '@blocksuite/icons'; import { CopilotPanel } from '@blocksuite/presets'; -import { useBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; +import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; import { useCallback, useRef } from 'react'; import type { EditorExtension } from '../types'; @@ -10,7 +10,7 @@ import * as styles from './outline.css'; // A wrapper for CopilotPanel const EditorCopilotPanel = () => { const copilotPanelRef = useRef(null); - const [editor] = useBlocksuiteEditor(); + const [editor] = useActiveBlocksuiteEditor(); const onRefChange = useCallback((container: HTMLDivElement | null) => { if (container) { diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/frame.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/frame.tsx index dc9263e141..06e73dacfb 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/frame.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/frame.tsx @@ -1,7 +1,7 @@ import { assertExists } from '@blocksuite/global/utils'; import { FrameIcon } from '@blocksuite/icons'; import { FramePanel } from '@blocksuite/presets'; -import { useBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; +import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; import { useCallback, useRef } from 'react'; import type { EditorExtension } from '../types'; @@ -11,7 +11,7 @@ import * as styles from './frame.css'; const EditorFramePanel = () => { const framePanelRef = useRef(null); - const [editor] = useBlocksuiteEditor(); + const [editor] = useActiveBlocksuiteEditor(); const onRefChange = useCallback((container: HTMLDivElement | null) => { if (container) { diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/outline.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/outline.tsx index cae97db2a3..002adb2dca 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/outline.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/outline.tsx @@ -1,7 +1,7 @@ import { assertExists } from '@blocksuite/global/utils'; import { TocIcon } from '@blocksuite/icons'; import { TOCPanel } from '@blocksuite/presets'; -import { useBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; +import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor'; import { useCallback, useRef } from 'react'; import type { EditorExtension } from '../types'; @@ -10,7 +10,7 @@ import * as styles from './outline.css'; // A wrapper for TOCNotesPanel const EditorOutline = () => { const tocPanelRef = useRef(null); - const [editor] = useBlocksuiteEditor(); + const [editor] = useActiveBlocksuiteEditor(); const onRefChange = useCallback((container: HTMLDivElement | null) => { if (container) { diff --git a/packages/frontend/hooks/src/use-block-suite-editor.ts b/packages/frontend/hooks/src/use-block-suite-editor.ts index f48dcbd57f..68d9ff82fb 100644 --- a/packages/frontend/hooks/src/use-block-suite-editor.ts +++ b/packages/frontend/hooks/src/use-block-suite-editor.ts @@ -1,13 +1,15 @@ import type { AffineEditorContainer } from '@blocksuite/presets'; import { atom, type SetStateAction, useAtom } from 'jotai'; -const editorContainerAtom = atom(null); +const activeEditorContainerAtom = atom(null); -export function useBlocksuiteEditor(): [ +export function useActiveBlocksuiteEditor(): [ AffineEditorContainer | null, React.Dispatch>, ] { - const [editorContainer, setEditorContainer] = useAtom(editorContainerAtom); + const [editorContainer, setEditorContainer] = useAtom( + activeEditorContainerAtom + ); return [editorContainer, setEditorContainer]; }