import { config } from '@affine/env'; import { editorContainerModuleAtom } from '@affine/jotai'; import type { BlockHub } from '@blocksuite/blocks'; import type { EditorContainer } from '@blocksuite/editor'; import { assertExists } from '@blocksuite/global/utils'; import type { Page } from '@blocksuite/store'; import { Skeleton } from '@mui/material'; import { useAtomValue } from 'jotai'; import type { CSSProperties, ReactElement } from 'react'; import { lazy, memo, Suspense, useCallback, useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; import type { FallbackProps } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary'; import { blockSuiteEditorHeaderStyle, blockSuiteEditorStyle, } from './index.css'; export type EditorProps = { page: Page; mode: 'page' | 'edgeless'; onInit: (page: Page, editor: Readonly) => void; onLoad?: (page: Page, editor: EditorContainer) => () => void; style?: CSSProperties; }; export type ErrorBoundaryProps = { onReset?: () => void; }; declare global { // eslint-disable-next-line no-var var currentPage: Page | undefined; // eslint-disable-next-line no-var var currentEditor: EditorContainer | undefined; } const ImagePreviewModal = lazy(() => import('../image-preview-modal').then(module => ({ default: module.ImagePreviewModal, })) ); const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { const { onLoad, page, mode, style, onInit } = props; const JotaiEditorContainer = useAtomValue( editorContainerModuleAtom ) as typeof EditorContainer; assertExists(page, 'page should not be null'); const editorRef = useRef(null); const blockHubRef = useRef(null); if (editorRef.current === null) { editorRef.current = new JotaiEditorContainer(); editorRef.current.autofocus = true; globalThis.currentEditor = editorRef.current; } const editor = editorRef.current; assertExists(editorRef, 'editorRef.current should not be null'); if (editor.mode !== mode) { editor.mode = mode; } useEffect(() => { if (editor.page !== page) { editor.page = page; if (page.root === null) { onInit(page, editor); } } }, [editor, page, onInit]); useEffect(() => { if (editor.page && onLoad) { const disposes = [] as ((() => void) | undefined)[]; disposes.push(onLoad?.(page, editor)); return () => { disposes .filter((dispose): dispose is () => void => !!dispose) .forEach(dispose => dispose()); }; } return; }, [editor, editor.page, page, onLoad]); const ref = useRef(null); useEffect(() => { const editor = editorRef.current; assertExists(editor); const container = ref.current; if (!container) { return; } if (page.awarenessStore.getFlag('enable_block_hub')) { editor .createBlockHub() .then(blockHub => { if (blockHubRef.current) { blockHubRef.current.remove(); } blockHubRef.current = blockHub; const toolWrapper = document.querySelector('#toolWrapper'); if (!toolWrapper) { console.warn( 'toolWrapper not found, block hub feature will not be available.' ); } else { toolWrapper.appendChild(blockHub); } }) .catch(err => { console.error(err); }); } container.appendChild(editor); return () => { blockHubRef.current?.remove(); container.removeChild(editor); }; }, [editor, page]); // issue: https://github.com/toeverything/AFFiNE/issues/2004 const className = `editor-wrapper ${editor.mode}-mode`; return (
); }; const BlockSuiteErrorFallback = ( props: FallbackProps & ErrorBoundaryProps ): ReactElement => { return (

Sorry.. there was an error

{props.error.message}
); }; export const BlockSuiteFallback = memo(function BlockSuiteFallback() { return (
); }); export const BlockSuiteEditor = memo(function BlockSuiteEditor( props: EditorProps & ErrorBoundaryProps ): ReactElement { return ( ( ), [props.onReset] )} > }> {config.enableImagePreviewModal && props.page && ( {createPortal( , document.body )} )} ); }); BlockSuiteEditor.displayName = 'BlockSuiteEditor';