refactor(editor): optimize extension register and effects (#10406)

Key Changes:

1. **Code Reorganization and Consolidation**
   - Created new centralized extension management through new files:
     - `enableEditorExtension` in `extensions/entry/enable-editor.ts`
     - `enablePreviewExtension` in `extensions/entry/enable-preview.ts`
   - Removed several spec-related files that are now consolidated:
     - Removed `specs/edgeless.ts`
     - Removed `specs/page.ts`
     - Removed `specs/preview.ts`

2. **Template Management**
   - Added new `register-templates.ts` file to handle template registration
   - Moved template registration logic from `specs/edgeless.ts` to this new file
   - Templates now include both edgeless and sticker templates

3. **Extension Management Changes**
   - Simplified extension enabling process through new centralized functions
   - `enableEditorExtension` now handles both page and edgeless modes
   - `enablePreviewExtension` consolidates preview-related extensions
   - Removed duplicate code for extension management

4. **Preview Functionality Updates**
   - Streamlined preview spec management
   - Consolidated footnote configuration
   - Improved theme and preview extension handling

5. **Dependencies and Effects**
   - Updated how effects are registered and managed
   - Simplified initialization process in `index.ts`
   - More organized approach to handling framework providers

The main theme of this PR appears to be code consolidation and simplification, moving from multiple specialized files to more centralized, reusable extension management. This should make the codebase more maintainable and reduce duplication while keeping the same functionality.

The changes primarily affect the editor's extension system, preview functionality, and template management, making these systems more modular and easier to maintain.
This commit is contained in:
Saul-Mirone 2025-02-24 10:37:59 +00:00
parent 0e581c915c
commit 6289981fd1
No known key found for this signature in database
GPG Key ID: 0D941B4A9125B742
13 changed files with 89 additions and 192 deletions

1
.github/CODEOWNERS vendored
View File

@ -1 +1,2 @@
/blocksuite/ @toeverything/blocksuite-core
/packages/frontend/core/src/blocksuite @toeverything/blocksuite-core

View File

@ -36,12 +36,12 @@ import {
AffineSharedPageReference,
} from '../../components/affine/reference-link';
import { LitTextRenderer } from '../ai/components/text-renderer';
import { enableEditorExtension } from '../extensions/entry/enable-editor';
import {
patchReferenceRenderer,
type ReferenceReactRenderer,
} from '../extensions/reference-renderer';
import * as styles from './bi-directional-link-panel.css';
import { createPageModeSpecs } from './specs/page';
const PREFIX = 'bi-directional-link-panel-collapse:';
@ -144,7 +144,7 @@ const usePreviewExtensions = () => {
}, [workspaceService]);
const extensions = useMemo(() => {
const specs = createPageModeSpecs(framework);
const specs = enableEditorExtension(framework, 'page');
specs.extend([patchReferenceRenderer(reactToLit, referenceRenderer)]);
return specs.value;
}, [reactToLit, referenceRenderer, framework]);

View File

@ -2,14 +2,12 @@ import { registerAIEffects } from '@affine/core/blocksuite/ai/effects';
import { effects as editorEffects } from '@affine/core/blocksuite/editors';
import { effects as bsEffects } from '@blocksuite/affine/effects';
import { effects as edgelessEffects } from './specs/edgeless';
import { effects as patchEffects } from './specs/preview';
import { registerTemplates } from './register-templates';
bsEffects();
patchEffects();
editorEffects();
edgelessEffects();
registerAIEffects();
registerTemplates();
export * from './blocksuite-editor';
export * from './blocksuite-editor-container';

View File

@ -51,7 +51,9 @@ import { patchDocModeService } from '../extensions/doc-mode-service';
import { patchDocUrlExtensions } from '../extensions/doc-url';
import { EdgelessClipboardWatcher } from '../extensions/edgeless-clipboard';
import { patchForClipboardInElectron } from '../extensions/electron-clipboard';
import { enableEditorExtension } from '../extensions/entry/enable-editor';
import { enableMobileExtension } from '../extensions/entry/enable-mobile';
import { enablePreviewExtension } from '../extensions/entry/enable-preview';
import { patchForEdgelessNoteConfig } from '../extensions/note-config';
import { patchNotificationService } from '../extensions/notification-service';
import { patchOpenDocExtension } from '../extensions/open-doc';
@ -64,9 +66,6 @@ import {
import { patchSideBarService } from '../extensions/side-bar-service';
import { BiDirectionalLinkPanel } from './bi-directional-link-panel';
import { BlocksuiteEditorJournalDocTitle } from './journal-doc-title';
import { createEdgelessModeSpecs } from './specs/edgeless';
import { createPageModeSpecs } from './specs/page';
import { extendEdgelessPreviewSpec } from './specs/preview';
import { StarterBar } from './starter-bar';
import * as styles from './styles.css';
@ -125,16 +124,13 @@ const usePatchSpecs = (mode: DocMode) => {
}, [workspaceService]);
useMemo(() => {
extendEdgelessPreviewSpec(framework);
enablePreviewExtension(framework);
}, [framework]);
const confirmModal = useConfirmModal();
const patchedSpecs = useMemo(() => {
const builder =
mode === 'edgeless'
? createEdgelessModeSpecs(framework)
: createPageModeSpecs(framework);
const builder = enableEditorExtension(framework, mode);
builder.extend(
[

View File

@ -0,0 +1,15 @@
import { builtInTemplates as builtInEdgelessTemplates } from '@affine/templates/edgeless';
import { builtInTemplates as builtInStickersTemplates } from '@affine/templates/stickers';
import {
EdgelessTemplatePanel,
type TemplateManager,
} from '@blocksuite/affine/blocks';
export function registerTemplates() {
EdgelessTemplatePanel.templates.extend(
builtInStickersTemplates as TemplateManager
);
EdgelessTemplatePanel.templates.extend(
builtInEdgelessTemplates as TemplateManager
);
}

View File

@ -1,26 +0,0 @@
import { enableAIExtension } from '@affine/core/blocksuite/ai';
import { enableAffineExtension } from '@affine/core/blocksuite/extensions';
import { builtInTemplates as builtInEdgelessTemplates } from '@affine/templates/edgeless';
import { builtInTemplates as builtInStickersTemplates } from '@affine/templates/stickers';
import type { SpecBuilder, TemplateManager } from '@blocksuite/affine/blocks';
import { EdgelessTemplatePanel, SpecProvider } from '@blocksuite/affine/blocks';
import { type FrameworkProvider } from '@toeverything/infra';
export function createEdgelessModeSpecs(
framework: FrameworkProvider
): SpecBuilder {
const edgelessSpec = SpecProvider._.getSpec('edgeless');
enableAffineExtension(edgelessSpec, framework);
enableAIExtension(edgelessSpec, framework);
return edgelessSpec;
}
export function effects() {
EdgelessTemplatePanel.templates.extend(
builtInStickersTemplates as TemplateManager
);
EdgelessTemplatePanel.templates.extend(
builtInEdgelessTemplates as TemplateManager
);
}

View File

@ -1,11 +0,0 @@
import { enableAIExtension } from '@affine/core/blocksuite/ai';
import { enableAffineExtension } from '@affine/core/blocksuite/extensions';
import { type SpecBuilder, SpecProvider } from '@blocksuite/affine/blocks';
import { type FrameworkProvider } from '@toeverything/infra';
export function createPageModeSpecs(framework: FrameworkProvider): SpecBuilder {
const pageSpec = SpecProvider._.getSpec('page');
enableAffineExtension(pageSpec, framework);
enableAIExtension(pageSpec, framework);
return pageSpec;
}

View File

@ -0,0 +1,16 @@
import { enableAIExtension } from '@affine/core/blocksuite/ai';
import { enableAffineExtension } from '@affine/core/blocksuite/extensions';
import type { SpecBuilder } from '@blocksuite/affine/blocks';
import { SpecProvider } from '@blocksuite/affine/blocks';
import { type FrameworkProvider } from '@toeverything/infra';
export function enableEditorExtension(
framework: FrameworkProvider,
mode: 'edgeless' | 'page'
): SpecBuilder {
const spec = SpecProvider._.getSpec(mode);
enableAffineExtension(spec, framework);
enableAIExtension(spec, framework);
return spec;
}

View File

@ -1,4 +1,3 @@
import { AIChatBlockSpec } from '@affine/core/blocksuite/ai/blocks';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import { AppThemeService } from '@affine/core/modules/theme';
import {
@ -9,8 +8,8 @@ import {
import {
ColorScheme,
createSignalFromObservable,
FootNoteNodeConfigExtension,
type Signal,
type SpecBuilder,
SpecProvider,
type ThemeExtension,
ThemeExtensionIdentifier,
@ -20,30 +19,13 @@ import type { ExtensionType } from '@blocksuite/affine/store';
import type { FrameworkProvider } from '@toeverything/infra';
import type { Observable } from 'rxjs';
import { buildDocDisplayMetaExtension } from '../../extensions/display-meta';
import { getFontConfigExtension } from '../../extensions/font-config';
import { patchPeekViewService } from '../../extensions/peek-view-service';
import { getThemeExtension } from '../../extensions/theme';
import { AIChatBlockSpec } from '../../ai/blocks';
import { buildDocDisplayMetaExtension } from '../display-meta';
import { getFontConfigExtension } from '../font-config';
import { patchPeekViewService } from '../peek-view-service';
import { getThemeExtension } from '../theme';
const CustomSpecs: ExtensionType[] = [
AIChatBlockSpec,
getFontConfigExtension(),
].flat();
function patchPreviewSpec(
id: 'preview:edgeless' | 'preview:page',
specs: ExtensionType[]
) {
const specProvider = SpecProvider._;
specProvider.extendSpec(id, specs);
}
export function effects() {
// Patch edgeless preview spec for blocksuite surface-ref and embed-synced-doc
patchPreviewSpec('preview:edgeless', CustomSpecs);
}
export function getPagePreviewThemeExtension(framework: FrameworkProvider) {
function getPagePreviewThemeExtension(framework: FrameworkProvider) {
class AffinePagePreviewThemeExtension
extends LifeCycleWatcher
implements ThemeExtension
@ -98,33 +80,42 @@ export function getPagePreviewThemeExtension(framework: FrameworkProvider) {
return AffinePagePreviewThemeExtension;
}
export function createPageModePreviewSpecs(
framework: FrameworkProvider
): SpecBuilder {
// Disable hover effect for footnote node in center peek preview mode
const footnodeConfig = FootNoteNodeConfigExtension({
disableHoverEffect: true,
});
const fontConfig = getFontConfigExtension();
let _framework: FrameworkProvider;
let _previewExtensions: ExtensionType[];
export function enablePreviewExtension(framework: FrameworkProvider): void {
if (_framework === framework && _previewExtensions) {
return;
}
const specProvider = SpecProvider._;
const pagePreviewSpec = specProvider.getSpec('preview:page');
// Enable theme extension, doc display meta extension and peek view service
if (_previewExtensions) {
_previewExtensions.forEach(extension => {
specProvider.omitSpec('preview:page', extension);
specProvider.omitSpec('preview:edgeless', extension);
});
}
_framework = framework;
const peekViewService = framework.get(PeekViewService);
pagePreviewSpec.extend([
_previewExtensions = [
...AIChatBlockSpec,
footnodeConfig,
fontConfig,
getThemeExtension(framework),
getPagePreviewThemeExtension(framework),
buildDocDisplayMetaExtension(framework),
patchPeekViewService(peekViewService),
]);
return pagePreviewSpec;
}
];
export const extendEdgelessPreviewSpec = (function () {
let _extension: ExtensionType;
let _framework: FrameworkProvider;
return function (framework: FrameworkProvider) {
if (framework === _framework && _extension) {
return _extension;
} else {
_extension && SpecProvider._.omitSpec('preview:edgeless', _extension);
_extension = getThemeExtension(framework);
_framework = framework;
SpecProvider._.extendSpec('preview:edgeless', [_extension]);
return _extension;
}
};
})();
specProvider.extendSpec('preview:page', _previewExtensions);
specProvider.extendSpec('preview:edgeless', _previewExtensions);
}

View File

@ -1,35 +1,24 @@
import { Skeleton } from '@affine/component';
import type { EditorSettingSchema } from '@affine/core/modules/editor-setting';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { AppThemeService } from '@affine/core/modules/theme';
import type { EditorHost } from '@blocksuite/affine/block-std';
import {
BlockServiceIdentifier,
BlockStdScope,
LifeCycleWatcher,
StdIdentifier,
} from '@blocksuite/affine/block-std';
import {
GfxControllerIdentifier,
type GfxPrimitiveElementModel,
} from '@blocksuite/affine/block-std/gfx';
import type { ThemeExtension } from '@blocksuite/affine/blocks';
import {
ColorScheme,
createSignalFromObservable,
EdgelessCRUDIdentifier,
SpecProvider,
ThemeExtensionIdentifier,
} from '@blocksuite/affine/blocks';
import type { Container } from '@blocksuite/affine/global/di';
import { Bound } from '@blocksuite/affine/global/utils';
import type { Block, Store } from '@blocksuite/affine/store';
import type { Signal } from '@preact/signals-core';
import type { FrameworkProvider } from '@toeverything/infra';
import { useFramework } from '@toeverything/infra';
import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef } from 'react';
import type { Observable } from 'rxjs';
import { map, pairwise } from 'rxjs';
import {
@ -91,10 +80,7 @@ export const EdgelessSnapshot = (props: Props) => {
const editorHost = new BlockStdScope({
store: doc,
extensions: [
...SpecProvider._.getSpec('preview:edgeless').value,
getThemeExtension(framework),
],
extensions: SpecProvider._.getSpec('preview:edgeless').value,
}).render();
docRef.current = doc;
editorHostRef.current?.remove();
@ -128,7 +114,7 @@ export const EdgelessSnapshot = (props: Props) => {
// append to dom node
wrapperRef.current.append(editorHost);
}, [docName, firstUpdate, framework, updateElements]);
}, [docName, firstUpdate, updateElements]);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -172,58 +158,3 @@ export const EdgelessSnapshot = (props: Props) => {
</div>
);
};
function getThemeExtension(framework: FrameworkProvider) {
class AffineThemeExtension
extends LifeCycleWatcher
implements ThemeExtension
{
static override readonly key = 'affine-settings-theme';
private readonly theme: Signal<ColorScheme>;
protected readonly disposables: (() => void)[] = [];
static override setup(di: Container) {
super.setup(di);
di.override(ThemeExtensionIdentifier, AffineThemeExtension, [
StdIdentifier,
]);
}
constructor(std: BlockStdScope) {
super(std);
const theme$: Observable<ColorScheme> = framework
.get(AppThemeService)
.appTheme.theme$.map(theme => {
return theme === ColorScheme.Dark
? ColorScheme.Dark
: ColorScheme.Light;
});
const { signal, cleanup } = createSignalFromObservable<ColorScheme>(
theme$,
ColorScheme.Light
);
this.theme = signal;
this.disposables.push(cleanup);
}
getAppTheme() {
return this.theme;
}
getEdgelessTheme() {
return this.theme;
}
override unmounted() {
this.dispose();
}
dispose() {
this.disposables.forEach(dispose => dispose());
}
}
return AffineThemeExtension;
}

View File

@ -1,6 +1,5 @@
import { ChatPanel } from '@affine/core/blocksuite/ai';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { createPageModePreviewSpecs } from '@affine/core/blocksuite/block-suite-editor/specs/preview';
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { DocSearchMenuService } from '@affine/core/modules/doc-search-menu/services';
@ -8,8 +7,8 @@ import { WorkspaceService } from '@affine/core/modules/workspace';
import {
createSignalFromObservable,
DocModeProvider,
FootNoteNodeConfigExtension,
RefNodeSlotsProvider,
SpecProvider,
} from '@blocksuite/affine/blocks';
import { useFramework } from '@toeverything/infra';
import { forwardRef, useEffect, useRef } from 'react';
@ -21,11 +20,6 @@ export interface SidebarTabProps {
onLoad?: ((component: HTMLElement) => void) | null;
}
// Disable hover effect for footnote node in chat panel
const FOOTNOTE_CONFIG = FootNoteNodeConfigExtension({
disableHoverEffect: true,
});
// A wrapper for CopilotPanel
export const EditorChatPanel = forwardRef(function EditorChatPanel(
{ editor, onLoad }: SidebarTabProps,
@ -90,8 +84,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
);
},
};
const previewSpecBuilder = createPageModePreviewSpecs(framework);
previewSpecBuilder.extend([FOOTNOTE_CONFIG]);
const previewSpecBuilder = SpecProvider._.getSpec('preview:page');
chatPanelRef.current.previewSpecBuilder = previewSpecBuilder;
} else {
chatPanelRef.current.host = editor.host;

View File

@ -5,10 +5,10 @@ import {
type MonitorGetFeedback,
type toExternalData,
} from '@affine/component';
import { createPageModeSpecs } from '@affine/core/blocksuite/block-suite-editor/specs/page';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { BlockStdScope } from '@blocksuite/affine/block-std';
import {
DNDAPIExtension,
DndApiExtensionIdentifier,
type DragBlockPayload,
} from '@blocksuite/affine/blocks';
@ -163,7 +163,7 @@ export class DndService extends Service {
const std = new BlockStdScope({
store: doc,
extensions: createPageModeSpecs(this.framework).value,
extensions: [DNDAPIExtension],
});
const dndAPI = std.get(DndApiExtensionIdentifier);
return dndAPI;

View File

@ -1,10 +1,9 @@
import { toReactNode } from '@affine/component';
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/ai';
import type { AIChatBlockModel } from '@affine/core/blocksuite/ai/blocks/ai-chat-block/model/ai-chat-model';
import { createPageModePreviewSpecs } from '@affine/core/blocksuite/block-suite-editor/specs/preview';
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
import type { EditorHost } from '@blocksuite/affine/block-std';
import { FootNoteNodeConfigExtension } from '@blocksuite/affine/blocks';
import { SpecProvider } from '@blocksuite/affine/blocks';
import { useFramework } from '@toeverything/infra';
import { useMemo } from 'react';
@ -13,11 +12,6 @@ export type AIChatBlockPeekViewProps = {
host: EditorHost;
};
// Disable hover effect for footnote node in center peek preview mode
const FOOTNOTE_CONFIG = FootNoteNodeConfigExtension({
disableHoverEffect: true,
});
export const AIChatBlockPeekView = ({
model,
host,
@ -25,8 +19,7 @@ export const AIChatBlockPeekView = ({
const framework = useFramework();
const searchService = framework.get(AINetworkSearchService);
return useMemo(() => {
const previewSpecBuilder = createPageModePreviewSpecs(framework);
previewSpecBuilder.extend([FOOTNOTE_CONFIG]);
const previewSpecBuilder = SpecProvider._.getSpec('preview:page');
const networkSearchConfig = {
visible: searchService.visible,
enabled: searchService.enabled,
@ -39,5 +32,5 @@ export const AIChatBlockPeekView = ({
networkSearchConfig
);
return toReactNode(template);
}, [framework, model, host, searchService]);
}, [model, host, searchService]);
};