refactor(component): adjust active editor atom (#5457)
before: set global `blocksuiteEditorAtom` state in `<BlocksuiteEditorImpl />` after: Rename `blocksuiteEditorAtom` to `activeBlocksuiteEditorAtom` And move the logic of setting this atom to `<PageDetailEditor />`. benefit: * make BlocksuiteEditor pure * keep @toeverything/component clear * Clarify the purpose of `activeBlocksuiteEditorAtom`
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { AffineEditorContainer } from '@blocksuite/presets';
|
import { AffineEditorContainer } from '@blocksuite/presets';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { useBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { use } from 'foxact/use';
|
import { use } from 'foxact/use';
|
||||||
import type { CSSProperties, ReactElement } from 'react';
|
import type { CSSProperties, ReactElement } from 'react';
|
||||||
import {
|
import {
|
||||||
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
Suspense,
|
Suspense,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -137,104 +137,115 @@ function usePageRoot(page: Page) {
|
|||||||
return page.root;
|
return page.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockSuiteEditorImpl = ({
|
const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
|
||||||
mode,
|
(
|
||||||
page,
|
{
|
||||||
className,
|
mode,
|
||||||
defaultSelectedBlockId,
|
page,
|
||||||
onLoadEditor,
|
className,
|
||||||
onModeChange,
|
defaultSelectedBlockId,
|
||||||
style,
|
onLoadEditor,
|
||||||
}: EditorProps): ReactElement => {
|
onModeChange,
|
||||||
usePageRoot(page);
|
style,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
): ReactElement => {
|
||||||
|
usePageRoot(page);
|
||||||
|
|
||||||
const [, setEditorContainer] = useBlocksuiteEditor();
|
assertExists(page, 'page should not be null');
|
||||||
assertExists(page, 'page should not be null');
|
const editorRef = useRef<AffineEditorContainer | null>(null);
|
||||||
const editorRef = useRef<AffineEditorContainer | null>(null);
|
if (editorRef.current === null) {
|
||||||
if (editorRef.current === null) {
|
editorRef.current = new AffineEditorContainer();
|
||||||
editorRef.current = new AffineEditorContainer();
|
editorRef.current.autofocus = true;
|
||||||
editorRef.current.autofocus = true;
|
}
|
||||||
}
|
const editor = editorRef.current;
|
||||||
const editor = editorRef.current;
|
assertExists(editorRef, 'editorRef.current should not be null');
|
||||||
assertExists(editorRef, 'editorRef.current should not be null');
|
|
||||||
|
|
||||||
if (editor.mode !== mode) {
|
if (editor.mode !== mode) {
|
||||||
editor.mode = mode;
|
editor.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editor.page !== page) {
|
if (editor.page !== page) {
|
||||||
editor.page = page;
|
editor.page = page;
|
||||||
editor.docSpecs = editorSpecs.docModeSpecs;
|
editor.docSpecs = editorSpecs.docModeSpecs;
|
||||||
editor.edgelessSpecs = editorSpecs.edgelessModeSpecs;
|
editor.edgelessSpecs = editorSpecs.edgelessModeSpecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
if (ref) {
|
||||||
if (editor) {
|
if (typeof ref === 'function') {
|
||||||
const disposes: (() => void)[] = [];
|
ref(editor);
|
||||||
const disposeModeSwitch = editor.slots.pageModeSwitched.on(mode => {
|
} else {
|
||||||
onModeChange?.(mode);
|
ref.current = editor;
|
||||||
});
|
|
||||||
disposes.push(() => disposeModeSwitch?.dispose());
|
|
||||||
if (onLoadEditor) {
|
|
||||||
disposes.push(onLoadEditor(editor));
|
|
||||||
}
|
}
|
||||||
return () => {
|
|
||||||
disposes.forEach(dispose => dispose());
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}, [editor, onModeChange, onLoadEditor]);
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
useLayoutEffect(() => {
|
||||||
|
if (editor) {
|
||||||
useEffect(() => {
|
const disposes: (() => void)[] = [];
|
||||||
const container = containerRef.current;
|
const disposeModeSwitch = editor.slots.pageModeSwitched.on(mode => {
|
||||||
if (!container) {
|
onModeChange?.(mode);
|
||||||
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',
|
|
||||||
});
|
});
|
||||||
const selectManager = editor.root?.selection;
|
disposes.push(() => disposeModeSwitch?.dispose());
|
||||||
if (!blockElement.path.length || !selectManager) {
|
if (onLoadEditor) {
|
||||||
return;
|
disposes.push(onLoadEditor(editor));
|
||||||
}
|
}
|
||||||
const newSelection = selectManager.getInstance('block', {
|
return () => {
|
||||||
path: blockElement.path,
|
disposes.forEach(dispose => dispose());
|
||||||
});
|
};
|
||||||
selectManager.set([newSelection]);
|
}
|
||||||
});
|
return;
|
||||||
}
|
}, [editor, onModeChange, onLoadEditor]);
|
||||||
}, [editor, blockElement]);
|
|
||||||
|
|
||||||
// issue: https://github.com/toeverything/AFFiNE/issues/2004
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
return (
|
|
||||||
<div
|
useEffect(() => {
|
||||||
data-testid={`editor-${page.id}`}
|
const container = containerRef.current;
|
||||||
className={clsx(`editor-wrapper ${editor.mode}-mode`, className)}
|
if (!container) {
|
||||||
style={style}
|
return;
|
||||||
ref={containerRef}
|
}
|
||||||
/>
|
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 (
|
||||||
|
<div
|
||||||
|
data-testid={`editor-${page.id}`}
|
||||||
|
className={clsx(`editor-wrapper ${editor.mode}-mode`, className)}
|
||||||
|
style={style}
|
||||||
|
ref={containerRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
BlockSuiteEditorImpl.displayName = 'BlockSuiteEditorImpl';
|
||||||
|
|
||||||
export const EditorLoading = memo(function EditorLoading() {
|
export const EditorLoading = memo(function EditorLoading() {
|
||||||
return (
|
return (
|
||||||
@@ -249,14 +260,16 @@ export const EditorLoading = memo(function EditorLoading() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BlockSuiteEditor = memo(function BlockSuiteEditor(
|
export const BlockSuiteEditor = memo(
|
||||||
props: EditorProps
|
forwardRef<AffineEditorContainer, EditorProps>(
|
||||||
): ReactElement {
|
function BlockSuiteEditor(props, ref): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<EditorLoading />}>
|
<Suspense fallback={<EditorLoading />}>
|
||||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
<BlockSuiteEditorImpl key={props.page.id} ref={ref} {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
BlockSuiteEditor.displayName = 'BlockSuiteEditor';
|
BlockSuiteEditor.displayName = 'BlockSuiteEditor';
|
||||||
|
|||||||
@@ -3,12 +3,21 @@ import './page-detail-editor.css';
|
|||||||
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
|
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
|
||||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||||
import type { Page, Workspace } from '@blocksuite/store';
|
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 { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||||
import { fontStyleOptions } from '@toeverything/infra/atom';
|
import { fontStyleOptions } from '@toeverything/infra/atom';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { CSSProperties } from 'react';
|
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 { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { type PageMode, pageSettingFamily } from '../atoms';
|
import { type PageMode, pageSettingFamily } from '../atoms';
|
||||||
@@ -115,6 +124,13 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
|||||||
[onLoad, page]
|
[onLoad, page]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [, setActiveBlocksuiteEditor] = useActiveBlocksuiteEditor();
|
||||||
|
const editor = useRef<AffineEditorContainer>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveBlocksuiteEditor(editor.current);
|
||||||
|
}, [setActiveBlocksuiteEditor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
className={clsx(styles.editor, {
|
className={clsx(styles.editor, {
|
||||||
@@ -131,6 +147,7 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
|||||||
onModeChange={setEditorMode}
|
onModeChange={setEditorMode}
|
||||||
defaultSelectedBlockId={blockId}
|
defaultSelectedBlockId={blockId}
|
||||||
onLoadEditor={onLoadEditor}
|
onLoadEditor={onLoadEditor}
|
||||||
|
ref={editor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { AiIcon } from '@blocksuite/icons';
|
import { AiIcon } from '@blocksuite/icons';
|
||||||
import { CopilotPanel } from '@blocksuite/presets';
|
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 { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import type { EditorExtension } from '../types';
|
import type { EditorExtension } from '../types';
|
||||||
@@ -10,7 +10,7 @@ import * as styles from './outline.css';
|
|||||||
// A wrapper for CopilotPanel
|
// A wrapper for CopilotPanel
|
||||||
const EditorCopilotPanel = () => {
|
const EditorCopilotPanel = () => {
|
||||||
const copilotPanelRef = useRef<CopilotPanel | null>(null);
|
const copilotPanelRef = useRef<CopilotPanel | null>(null);
|
||||||
const [editor] = useBlocksuiteEditor();
|
const [editor] = useActiveBlocksuiteEditor();
|
||||||
|
|
||||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||||
if (container) {
|
if (container) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { FrameIcon } from '@blocksuite/icons';
|
import { FrameIcon } from '@blocksuite/icons';
|
||||||
import { FramePanel } from '@blocksuite/presets';
|
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 { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import type { EditorExtension } from '../types';
|
import type { EditorExtension } from '../types';
|
||||||
@@ -11,7 +11,7 @@ import * as styles from './frame.css';
|
|||||||
const EditorFramePanel = () => {
|
const EditorFramePanel = () => {
|
||||||
const framePanelRef = useRef<FramePanel | null>(null);
|
const framePanelRef = useRef<FramePanel | null>(null);
|
||||||
|
|
||||||
const [editor] = useBlocksuiteEditor();
|
const [editor] = useActiveBlocksuiteEditor();
|
||||||
|
|
||||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||||
if (container) {
|
if (container) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { TocIcon } from '@blocksuite/icons';
|
import { TocIcon } from '@blocksuite/icons';
|
||||||
import { TOCPanel } from '@blocksuite/presets';
|
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 { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
import type { EditorExtension } from '../types';
|
import type { EditorExtension } from '../types';
|
||||||
@@ -10,7 +10,7 @@ import * as styles from './outline.css';
|
|||||||
// A wrapper for TOCNotesPanel
|
// A wrapper for TOCNotesPanel
|
||||||
const EditorOutline = () => {
|
const EditorOutline = () => {
|
||||||
const tocPanelRef = useRef<TOCPanel | null>(null);
|
const tocPanelRef = useRef<TOCPanel | null>(null);
|
||||||
const [editor] = useBlocksuiteEditor();
|
const [editor] = useActiveBlocksuiteEditor();
|
||||||
|
|
||||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||||
if (container) {
|
if (container) {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||||
import { atom, type SetStateAction, useAtom } from 'jotai';
|
import { atom, type SetStateAction, useAtom } from 'jotai';
|
||||||
|
|
||||||
const editorContainerAtom = atom<AffineEditorContainer | null>(null);
|
const activeEditorContainerAtom = atom<AffineEditorContainer | null>(null);
|
||||||
|
|
||||||
export function useBlocksuiteEditor(): [
|
export function useActiveBlocksuiteEditor(): [
|
||||||
AffineEditorContainer | null,
|
AffineEditorContainer | null,
|
||||||
React.Dispatch<SetStateAction<AffineEditorContainer | null>>,
|
React.Dispatch<SetStateAction<AffineEditorContainer | null>>,
|
||||||
] {
|
] {
|
||||||
const [editorContainer, setEditorContainer] = useAtom(editorContainerAtom);
|
const [editorContainer, setEditorContainer] = useAtom(
|
||||||
|
activeEditorContainerAtom
|
||||||
|
);
|
||||||
|
|
||||||
return [editorContainer, setEditorContainer];
|
return [editorContainer, setEditorContainer];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user