mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
refactor: replace editor container with editor host (#10328)
### TL;DR Refactored editor access to use `EditorHost` instead of `AffineEditorContainer` and updated mode access through `DocModeProvider`. ### What changed? - Changed editor property types from `AffineEditorContainer` to `EditorHost` across multiple components - Updated mode access to use `DocModeProvider` service instead of direct editor mode access - Modified editor references to use `editor.host` where appropriate - Updated scroll and highlight utilities to work with `EditorHost` ### How to test? 1. Open a document in both page and edgeless modes 2. Verify outline panel functionality works as expected 3. Test outline viewer navigation and highlighting 4. Confirm mobile outline menu operates correctly 5. Check that frame panel and TOC features work in all modes ### Why make this change? This change improves architectural consistency by using `EditorHost` directly and accessing mode through the proper service provider. This makes the code more maintainable and follows better dependency practices by using the correct abstraction levels.
This commit is contained in:
@@ -22,7 +22,7 @@ export class CustomOutlinePanel extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
private _renderPanel() {
|
||||
return html`<affine-outline-panel
|
||||
.editor=${this.editor}
|
||||
.editor=${this.editor.host}
|
||||
.fitPadding=${[50, 360, 50, 50]}
|
||||
></affine-outline-panel>`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class CustomOutlineViewer extends WithDisposable(LitElement) {
|
||||
|
||||
private _renderViewer() {
|
||||
return html`<affine-outline-viewer
|
||||
.editor=${this.editor}
|
||||
.editor=${this.editor.host}
|
||||
.toggleOutlinePanel=${this.toggleOutlinePanel}
|
||||
></affine-outline-viewer>`;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { effects } from '@blocksuite/affine-block-note/effects';
|
||||
import { ShadowlessElement, SurfaceSelection } from '@blocksuite/block-std';
|
||||
import {
|
||||
changeNoteDisplayMode,
|
||||
DocModeProvider,
|
||||
matchModels,
|
||||
NoteBlockModel,
|
||||
NoteDisplayMode,
|
||||
@@ -224,7 +225,9 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
|
||||
private _watchSelectedNotes() {
|
||||
return effect(() => {
|
||||
const { std, doc, mode } = this.editor;
|
||||
const { std, doc } = this.editor;
|
||||
const docModeService = this.editor.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
if (mode !== 'edgeless') return;
|
||||
|
||||
const currSelectedNotes = std.selection
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import type { ParagraphBlockModel, Signal } from '@blocksuite/blocks';
|
||||
import {
|
||||
AttachmentIcon,
|
||||
@@ -22,8 +23,6 @@ import {
|
||||
import { createContext } from '@lit/context';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
|
||||
const _16px = { width: '16px', height: '16px' };
|
||||
|
||||
const paragraphIconMap: Record<
|
||||
@@ -85,7 +84,7 @@ export const headingKeys = new Set(
|
||||
export const outlineSettingsKey = 'outlinePanelSettings';
|
||||
|
||||
export type TocContext = {
|
||||
editor$: Signal<AffineEditorContainer>;
|
||||
editor$: Signal<EditorHost>;
|
||||
enableSorting$: Signal<boolean>;
|
||||
showIcons$: Signal<boolean>;
|
||||
fitPadding$: Signal<number[]>;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
|
||||
import {
|
||||
type EditorHost,
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
} from '@blocksuite/block-std';
|
||||
import {
|
||||
DocModeProvider,
|
||||
matchModels,
|
||||
NoteDisplayMode,
|
||||
ParagraphBlockModel,
|
||||
@@ -14,7 +19,6 @@ import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import { getHeadingBlocksFromDoc } from './utils/query.js';
|
||||
import {
|
||||
observeActiveHeadingDuringScroll,
|
||||
@@ -162,8 +166,9 @@ export class MobileOutlineMenu extends SignalWatcher(
|
||||
};
|
||||
|
||||
override render() {
|
||||
if (this.editor.doc.root === null || this.editor.mode === 'edgeless')
|
||||
return nothing;
|
||||
const docModeService = this.editor.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
if (this.editor.doc.root === null || mode === 'edgeless') return nothing;
|
||||
|
||||
const headingBlocks = getHeadingBlocksFromDoc(
|
||||
this.editor.doc,
|
||||
@@ -182,7 +187,7 @@ export class MobileOutlineMenu extends SignalWatcher(
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
accessor editor!: EditorHost;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
type EditorHost,
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/block-std';
|
||||
import { DocModeProvider } from '@blocksuite/blocks';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { provide } from '@lit/context';
|
||||
import { effect, signal } from '@preact/signals-core';
|
||||
import { html, type PropertyValues } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import { outlineSettingsKey, type TocContext, tocContext } from './config.js';
|
||||
import * as styles from './outline-panel.css';
|
||||
|
||||
@@ -21,6 +22,12 @@ export const AFFINE_OUTLINE_PANEL = 'affine-outline-panel';
|
||||
export class OutlinePanel extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
private _getEditorMode(host: EditorHost) {
|
||||
const docModeService = host.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
return mode;
|
||||
}
|
||||
|
||||
private _setContext() {
|
||||
this._context = {
|
||||
editor$: signal(this.editor),
|
||||
@@ -39,7 +46,7 @@ export class OutlinePanel extends SignalWatcher(
|
||||
}
|
||||
|
||||
const editor = this._context.editor$.value;
|
||||
if (editor.mode === 'edgeless') {
|
||||
if (this._getEditorMode(editor) === 'edgeless') {
|
||||
this._context.enableSorting$.value = true;
|
||||
} else if (settings) {
|
||||
this._context.enableSorting$.value = settings.enableSorting;
|
||||
@@ -51,7 +58,8 @@ export class OutlinePanel extends SignalWatcher(
|
||||
private _watchSettingsChange() {
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
if (this._context.editor$.value.mode === 'edgeless') return;
|
||||
if (this._getEditorMode(this._context.editor$.value) === 'edgeless')
|
||||
return;
|
||||
|
||||
const showPreviewIcon = this._context.showIcons$.value;
|
||||
const enableNotesSorting = this._context.enableSorting$.value;
|
||||
@@ -84,7 +92,7 @@ export class OutlinePanel extends SignalWatcher(
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.editor.host) return;
|
||||
if (!this.editor) return;
|
||||
|
||||
return html`
|
||||
<affine-outline-panel-header></affine-outline-panel-header>
|
||||
@@ -97,7 +105,7 @@ export class OutlinePanel extends SignalWatcher(
|
||||
private accessor _context!: TocContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
accessor editor!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor fitPadding!: number[];
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import {
|
||||
type EditorHost,
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/block-std';
|
||||
import { NoteDisplayMode, scrollbarStyle } from '@blocksuite/blocks';
|
||||
import {
|
||||
DocModeProvider,
|
||||
NoteDisplayMode,
|
||||
scrollbarStyle,
|
||||
} from '@blocksuite/blocks';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { TocIcon } from '@blocksuite/icons/lit';
|
||||
import { provide } from '@lit/context';
|
||||
@@ -13,7 +18,6 @@ import { property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import { type TocContext, tocContext } from './config.js';
|
||||
import { getHeadingBlocksFromDoc } from './utils/query.js';
|
||||
import {
|
||||
@@ -219,8 +223,9 @@ export class OutlineViewer extends SignalWatcher(
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.editor.doc.root === null || this.editor.mode === 'edgeless')
|
||||
return nothing;
|
||||
const docModeService = this.editor.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
if (this.editor.doc.root === null || mode === 'edgeless') return nothing;
|
||||
|
||||
const headingBlocks = getHeadingBlocksFromDoc(
|
||||
this.editor.doc,
|
||||
@@ -308,7 +313,7 @@ export class OutlineViewer extends SignalWatcher(
|
||||
private accessor _showViewer: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
accessor editor!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor toggleOutlinePanel: (() => void) | null = null;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import { getDocTitleByEditorHost, NoteDisplayMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
DocModeProvider,
|
||||
getDocTitleByEditorHost,
|
||||
NoteDisplayMode,
|
||||
} from '@blocksuite/blocks';
|
||||
import { clamp, DisposableGroup } from '@blocksuite/global/utils';
|
||||
|
||||
import type { AffineEditorContainer } from '../../../editors/editor-container.js';
|
||||
import { getHeadingBlocksFromDoc } from './query.js';
|
||||
|
||||
export function scrollToBlock(editor: AffineEditorContainer, blockId: string) {
|
||||
const { host, mode } = editor;
|
||||
if (mode === 'edgeless' || !host) return;
|
||||
export function scrollToBlock(host: EditorHost, blockId: string) {
|
||||
const docModeService = host.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
if (mode === 'edgeless') return;
|
||||
|
||||
if (editor.doc.root?.id === blockId) {
|
||||
const docTitle = getDocTitleByEditorHost(host);
|
||||
@@ -49,12 +53,11 @@ export function isBlockBeforeViewportCenter(
|
||||
}
|
||||
|
||||
export const observeActiveHeadingDuringScroll = (
|
||||
getEditor: () => AffineEditorContainer, // workaround for editor changed
|
||||
getEditor: () => EditorHost, // workaround for editor changed
|
||||
update: (activeHeading: string | null) => void
|
||||
) => {
|
||||
const handler = () => {
|
||||
const { host } = getEditor();
|
||||
if (!host) return;
|
||||
const host = getEditor();
|
||||
|
||||
const headings = getHeadingBlocksFromDoc(
|
||||
host.doc,
|
||||
@@ -81,13 +84,10 @@ export const observeActiveHeadingDuringScroll = (
|
||||
let highlightMask: HTMLDivElement | null = null;
|
||||
let highlightTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function highlightBlock(editor: AffineEditorContainer, blockId: string) {
|
||||
function highlightBlock(host: EditorHost, blockId: string) {
|
||||
const emptyClear = () => {};
|
||||
|
||||
const { host } = editor;
|
||||
if (!host) return emptyClear;
|
||||
|
||||
if (editor.doc.root?.id === blockId) return emptyClear;
|
||||
if (host.doc.root?.id === blockId) return emptyClear;
|
||||
|
||||
const rootComponent = host.querySelector('affine-page-root');
|
||||
if (!rootComponent) return emptyClear;
|
||||
@@ -152,11 +152,11 @@ function highlightBlock(editor: AffineEditorContainer, blockId: string) {
|
||||
// this function is useful when the scroll need smooth animation
|
||||
let highlightIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||
export async function scrollToBlockWithHighlight(
|
||||
editor: AffineEditorContainer,
|
||||
host: EditorHost,
|
||||
blockId: string,
|
||||
timeout = 3000
|
||||
) {
|
||||
scrollToBlock(editor, blockId);
|
||||
scrollToBlock(host, blockId);
|
||||
|
||||
let timeCount = 0;
|
||||
|
||||
@@ -173,10 +173,9 @@ export async function scrollToBlockWithHighlight(
|
||||
return;
|
||||
}
|
||||
|
||||
const { host } = editor;
|
||||
const block = host?.view.getBlock(blockId);
|
||||
const block = host.view.getBlock(blockId);
|
||||
|
||||
if (!host || !block || timeCount > timeout) {
|
||||
if (!block || timeCount > timeout) {
|
||||
clearInterval(highlightIntervalId);
|
||||
resolve(() => {});
|
||||
return;
|
||||
@@ -194,7 +193,7 @@ export async function scrollToBlockWithHighlight(
|
||||
clearInterval(highlightIntervalId);
|
||||
|
||||
// highlight block
|
||||
resolve(highlightBlock(editor, blockId));
|
||||
resolve(highlightBlock(host, blockId));
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { OutlineViewer } from '@blocksuite/affine/presets';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
@@ -9,7 +9,7 @@ export const EditorOutlineViewer = ({
|
||||
show,
|
||||
openOutlinePanel,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
editor: EditorHost | null;
|
||||
show: boolean;
|
||||
openOutlinePanel?: () => void;
|
||||
}) => {
|
||||
|
||||
@@ -310,7 +310,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
/>
|
||||
</Scrollable.Root>
|
||||
<EditorOutlineViewer
|
||||
editor={editorContainer}
|
||||
editor={editorContainer?.host ?? null}
|
||||
show={mode === 'page' && !isSideBarOpen}
|
||||
openOutlinePanel={openOutlinePanel}
|
||||
/>
|
||||
@@ -350,7 +350,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
<ViewSidebarTab tabId="outline" icon={<TocIcon />}>
|
||||
<Scrollable.Root className={styles.sidebarScrollArea}>
|
||||
<Scrollable.Viewport>
|
||||
<EditorOutlinePanel editor={editorContainer} />
|
||||
<EditorOutlinePanel editor={editorContainer?.host ?? null} />
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
@@ -359,7 +359,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
<ViewSidebarTab tabId="frame" icon={<FrameIcon />}>
|
||||
<Scrollable.Root className={styles.sidebarScrollArea}>
|
||||
<Scrollable.Viewport>
|
||||
<EditorFramePanel editor={editorContainer} />
|
||||
<EditorFramePanel editor={editorContainer?.host ?? null} />
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { FramePanel } from '@blocksuite/affine/blocks';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import * as styles from './frame.css';
|
||||
|
||||
// A wrapper for FramePanel
|
||||
export const EditorFramePanel = ({
|
||||
editor,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
}) => {
|
||||
export const EditorFramePanel = ({ editor }: { editor: EditorHost | null }) => {
|
||||
const framePanelRef = useRef<FramePanel | null>(null);
|
||||
|
||||
const onRefChange = useCallback(
|
||||
(container: HTMLDivElement | null) => {
|
||||
if (editor?.host && container && container.children.length === 0) {
|
||||
if (editor && container && container.children.length === 0) {
|
||||
framePanelRef.current = new FramePanel();
|
||||
framePanelRef.current.host = editor.host;
|
||||
framePanelRef.current.host = editor;
|
||||
framePanelRef.current.fitPadding = [20, 20, 20, 20];
|
||||
container.append(framePanelRef.current);
|
||||
}
|
||||
},
|
||||
[editor?.host]
|
||||
[editor]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor?.host && framePanelRef.current) {
|
||||
framePanelRef.current.host = editor.host;
|
||||
if (editor && framePanelRef.current) {
|
||||
framePanelRef.current.host = editor;
|
||||
}
|
||||
}, [editor?.host]);
|
||||
}, [editor]);
|
||||
|
||||
return <div className={styles.root} ref={onRefChange} />;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { OutlinePanel } from '@blocksuite/affine/presets';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as styles from './outline.css';
|
||||
export const EditorOutlinePanel = ({
|
||||
editor,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
editor: EditorHost | null;
|
||||
}) => {
|
||||
const outlinePanelRef = useRef<OutlinePanel | null>(null);
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ const SharePageInner = ({
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
<EditorOutlineViewer
|
||||
editor={editorContainer}
|
||||
editor={editorContainer?.host ?? null}
|
||||
show={publishMode === 'page'}
|
||||
/>
|
||||
{!BUILD_CONFIG.isElectron && <SharePageFooter />}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
type AffineEditorContainer,
|
||||
MobileOutlineMenu,
|
||||
} from '@blocksuite/affine/presets';
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { MobileOutlineMenu } from '@blocksuite/affine/presets';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const MobileTocMenu = ({
|
||||
editor,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
}) => {
|
||||
export const MobileTocMenu = ({ editor }: { editor: EditorHost | null }) => {
|
||||
const outlineMenuRef = useRef<MobileOutlineMenu | null>(null);
|
||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||
if (container) {
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ export const PageHeaderMenuButton = () => {
|
||||
title={t['com.affine.header.menu.toc']()}
|
||||
items={
|
||||
<div className={styles.outlinePanel}>
|
||||
<MobileTocMenu editor={editorContainer} />
|
||||
<MobileTocMenu editor={editorContainer?.host ?? null} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -179,7 +179,7 @@ function DocPeekPreviewEditor({
|
||||
</Scrollable.Root>
|
||||
{!BUILD_CONFIG.isMobileEdition && !BUILD_CONFIG.isMobileWeb ? (
|
||||
<EditorOutlineViewer
|
||||
editor={editorElement}
|
||||
editor={editorElement?.host ?? null}
|
||||
show={mode === 'page'}
|
||||
openOutlinePanel={openOutlinePanel}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user