refactor(editor): config the extension provider directly (#12252)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Added new export paths to improve module accessibility for foundation store and view components.
	- Introduced new extension points for telemetry, font configuration, link preview cache, and peek view services in the view extension foundation.

- **Improvements**
	- Enhanced flexibility by allowing optional configuration of placeholders in paragraph view extensions.
	- Switched to runtime schema validation for font configuration, increasing reliability.
	- Streamlined service registration for peek view providers.

- **Refactor**
	- Centralized and simplified extension management by removing redundant extension files and consolidating setup logic.
	- Updated internal integration of telemetry, font, and peek view services for improved maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-05-13 11:09:21 +00:00
parent 39942a0d04
commit 4ebeb530e0
13 changed files with 126 additions and 96 deletions
+3 -1
View File
@@ -274,7 +274,9 @@
"./model": "./src/model/index.ts", "./model": "./src/model/index.ts",
"./sync": "./src/sync/index.ts", "./sync": "./src/sync/index.ts",
"./extensions/store": "./src/extensions/store.ts", "./extensions/store": "./src/extensions/store.ts",
"./extensions/view": "./src/extensions/view.ts" "./extensions/view": "./src/extensions/view.ts",
"./foundation/store": "./src/foundation/store.ts",
"./foundation/view": "./src/foundation/view.ts"
}, },
"files": [ "files": [
"src", "src",
@@ -0,0 +1 @@
export * from '@blocksuite/affine-foundation/store';
@@ -0,0 +1 @@
export * from '@blocksuite/affine-foundation/view';
@@ -28,10 +28,9 @@ import { z } from 'zod';
import { effects } from './effects'; import { effects } from './effects';
const optionsSchema = z.object({ const optionsSchema = z.object({
getPlaceholder: z getPlaceholder: z.optional(
.function() z.function().args(z.instanceof(ParagraphBlockModel)).returns(z.string())
.args(z.instanceof(ParagraphBlockModel)) ),
.returns(z.string()),
}); });
export class ParagraphViewExtension extends ViewExtensionProvider< export class ParagraphViewExtension extends ViewExtensionProvider<
@@ -50,7 +49,7 @@ export class ParagraphViewExtension extends ViewExtensionProvider<
context: ViewExtensionContext, context: ViewExtensionContext,
options?: z.infer<typeof optionsSchema> options?: z.infer<typeof optionsSchema>
) { ) {
super.setup(context); super.setup(context, options);
const getPlaceholder = const getPlaceholder =
options?.getPlaceholder ?? (model => placeholders[model.props.type]); options?.getPlaceholder ?? (model => placeholders[model.props.type]);
@@ -10,7 +10,7 @@ export const PeekViewProvider = createIdentifier<PeekViewService>(
export function PeekViewExtension(service: PeekViewService): ExtensionType { export function PeekViewExtension(service: PeekViewService): ExtensionType {
return { return {
setup: di => { setup: di => {
di.addImpl(PeekViewProvider, () => service); di.override(PeekViewProvider, () => service);
}, },
}; };
} }
+44 -3
View File
@@ -1,4 +1,8 @@
import { FileDropExtension } from '@blocksuite/affine-components/drop-indicator'; import { FileDropExtension } from '@blocksuite/affine-components/drop-indicator';
import {
PeekViewExtension,
type PeekViewService,
} from '@blocksuite/affine-components/peek';
import { import {
type ViewExtensionContext, type ViewExtensionContext,
ViewExtensionProvider, ViewExtensionProvider,
@@ -12,28 +16,49 @@ import {
EditPropsStore, EditPropsStore,
EmbedOptionService, EmbedOptionService,
FileSizeLimitService, FileSizeLimitService,
FontConfigExtension,
fontConfigSchema,
FontLoaderService, FontLoaderService,
LinkPreviewCache, LinkPreviewCache,
LinkPreviewCacheConfigSchema,
LinkPreviewCacheExtension,
LinkPreviewService, LinkPreviewService,
PageViewportServiceExtension, PageViewportServiceExtension,
TelemetryExtension,
type TelemetryService,
ThemeService, ThemeService,
ToolbarRegistryExtension, ToolbarRegistryExtension,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { InteractivityManager, ToolController } from '@blocksuite/std/gfx'; import { InteractivityManager, ToolController } from '@blocksuite/std/gfx';
import { z } from 'zod';
import { clipboardConfigs } from './clipboard'; import { clipboardConfigs } from './clipboard';
import { effects } from './effects'; import { effects } from './effects';
export class FoundationViewExtension extends ViewExtensionProvider { const optionsSchema = z.object({
linkPreviewCacheConfig: z.optional(LinkPreviewCacheConfigSchema),
fontConfig: z.optional(z.array(fontConfigSchema)),
telemetry: z.optional(z.custom<TelemetryService>()),
peekView: z.optional(z.custom<PeekViewService>()),
});
export type FoundationViewExtensionOptions = z.infer<typeof optionsSchema>;
export class FoundationViewExtension extends ViewExtensionProvider<FoundationViewExtensionOptions> {
override name = 'foundation'; override name = 'foundation';
override schema = optionsSchema;
override effect() { override effect() {
super.effect(); super.effect();
effects(); effects();
} }
override setup(context: ViewExtensionContext) { override setup(
super.setup(context); context: ViewExtensionContext,
options?: FoundationViewExtensionOptions
) {
super.setup(context, options);
context.register([ context.register([
DocDisplayMetaService, DocDisplayMetaService,
EditPropsStore, EditPropsStore,
@@ -56,5 +81,21 @@ export class FoundationViewExtension extends ViewExtensionProvider {
if (this.isEdgeless(context.scope)) { if (this.isEdgeless(context.scope)) {
context.register([InteractivityManager, ToolController]); context.register([InteractivityManager, ToolController]);
} }
const fontConfig = options?.fontConfig;
if (fontConfig) {
context.register(FontConfigExtension(fontConfig));
}
const linkPreviewCacheConfig = options?.linkPreviewCacheConfig;
if (linkPreviewCacheConfig) {
context.register(LinkPreviewCacheExtension(linkPreviewCacheConfig));
}
const telemetry = options?.telemetry;
if (telemetry) {
context.register(TelemetryExtension(telemetry));
}
const peekView = options?.peekView;
if (peekView) {
context.register(PeekViewExtension(peekView));
}
} }
} }
@@ -1,11 +1,14 @@
import { FontFamily, FontStyle, FontWeight } from '@blocksuite/affine-model'; import { FontFamily, FontStyle, FontWeight } from '@blocksuite/affine-model';
import { z } from 'zod';
export interface FontConfig { export const fontConfigSchema = z.object({
font: string; font: z.string(),
weight: string; weight: z.string(),
url: string; url: z.string(),
style: string; style: z.string(),
} });
export type FontConfig = z.infer<typeof fontConfigSchema>;
export const AffineCanvasTextFonts: FontConfig[] = [ export const AffineCanvasTextFonts: FontConfig[] = [
// Inter, https://fonts.cdnfonts.com/css/inter?styles=29139,29134,29135,29136,29140,29141 // Inter, https://fonts.cdnfonts.com/css/inter?styles=29139,29134,29135,29136,29140,29141
@@ -1,4 +1,5 @@
import { createIdentifier } from '@blocksuite/global/di'; import { createIdentifier } from '@blocksuite/global/di';
import type { ExtensionType } from '@blocksuite/store';
import type { OutDatabaseAllEvents } from './database.js'; import type { OutDatabaseAllEvents } from './database.js';
import type { LinkToolbarEvents } from './link.js'; import type { LinkToolbarEvents } from './link.js';
@@ -46,3 +47,13 @@ export interface TelemetryService {
export const TelemetryProvider = createIdentifier<TelemetryService>( export const TelemetryProvider = createIdentifier<TelemetryService>(
'AffineTelemetryService' 'AffineTelemetryService'
); );
export const TelemetryExtension = (
service: TelemetryService
): ExtensionType => {
return {
setup: di => {
di.override(TelemetryProvider, () => service);
},
};
};
@@ -1,13 +0,0 @@
import {
AffineCanvasTextFonts,
FontConfigExtension,
} from '@blocksuite/affine/shared/services';
export function getFontConfigExtension() {
return FontConfigExtension(
AffineCanvasTextFonts.map(font => ({
...font,
url: environment.publicPath + 'fonts/' + font.url.split('/').pop(),
}))
);
}
@@ -1,36 +0,0 @@
import type { PeekViewService } from '@affine/core/modules/peek-view';
import { DebugLogger } from '@affine/debug';
import type {
PeekOptions,
PeekViewService as BSPeekViewService,
} from '@blocksuite/affine/components/peek';
import { PeekViewExtension } from '@blocksuite/affine/components/peek';
import type { TemplateResult } from 'lit';
const logger = new DebugLogger('affine::patch-peek-view-service');
export function patchPeekViewService(service: PeekViewService) {
return PeekViewExtension({
peek: (
element: {
target: HTMLElement;
docId: string;
blockIds?: string[];
template?: TemplateResult;
},
options?: PeekOptions
) => {
logger.debug('center peek', element);
const { template, target, ...props } = element;
return service.peekView.open(
{
element: target,
docRef: props,
},
template,
options?.abortSignal
);
},
} satisfies BSPeekViewService);
}
@@ -1,21 +0,0 @@
import { mixpanel } from '@affine/track';
import {
type TelemetryEventMap,
TelemetryProvider,
} from '@blocksuite/affine/shared/services';
import type { ExtensionType } from '@blocksuite/affine/store';
export function getTelemetryExtension(): ExtensionType {
return {
setup: di => {
di.addImpl(TelemetryProvider, () => ({
track: <T extends keyof TelemetryEventMap>(
eventName: T,
props: TelemetryEventMap[T]
) => {
mixpanel.track(eventName as string, props as Record<string, unknown>);
},
}));
},
};
}
@@ -1,9 +1,5 @@
import { buildDocDisplayMetaExtension } from '@affine/core/blocksuite/extensions/display-meta'; import { buildDocDisplayMetaExtension } from '@affine/core/blocksuite/extensions/display-meta';
import { patchFileSizeLimitExtension } from '@affine/core/blocksuite/extensions/file-size-limit'; import { patchFileSizeLimitExtension } from '@affine/core/blocksuite/extensions/file-size-limit';
import { getFontConfigExtension } from '@affine/core/blocksuite/extensions/font-config';
import { patchPeekViewService } from '@affine/core/blocksuite/extensions/peek-view-service';
import { getTelemetryExtension } from '@affine/core/blocksuite/extensions/telemetry';
import { PeekViewService } from '@affine/core/modules/peek-view';
import { import {
type ViewExtensionContext, type ViewExtensionContext,
ViewExtensionProvider, ViewExtensionProvider,
@@ -29,12 +25,7 @@ export class AffineCommonViewExtension extends ViewExtensionProvider<
super.setup(context, options); super.setup(context, options);
const { framework } = options || {}; const { framework } = options || {};
if (framework) { if (framework) {
context.register(patchPeekViewService(framework.get(PeekViewService))); context.register(buildDocDisplayMetaExtension(framework));
context.register([
getFontConfigExtension(),
buildDocDisplayMetaExtension(framework),
]);
context.register(getTelemetryExtension());
if (context.scope === 'edgeless' || context.scope === 'page') { if (context.scope === 'edgeless' || context.scope === 'page') {
context.register(patchFileSizeLimitExtension(framework)); context.register(patchFileSizeLimitExtension(framework));
} }
@@ -19,12 +19,22 @@ import {
AffineEditorViewExtension, AffineEditorViewExtension,
type AffineEditorViewOptions, type AffineEditorViewOptions,
} from '@affine/core/blocksuite/manager/editor-view'; } from '@affine/core/blocksuite/manager/editor-view';
import { PeekViewService } from '@affine/core/modules/peek-view';
import { DebugLogger } from '@affine/debug';
import { mixpanel } from '@affine/track';
import { DatabaseViewExtension } from '@blocksuite/affine/blocks/database/view'; import { DatabaseViewExtension } from '@blocksuite/affine/blocks/database/view';
import { ParagraphViewExtension } from '@blocksuite/affine/blocks/paragraph/view'; import { ParagraphViewExtension } from '@blocksuite/affine/blocks/paragraph/view';
import type {
PeekOptions,
PeekViewService as BSPeekViewService,
} from '@blocksuite/affine/components/peek';
import { ViewExtensionManager } from '@blocksuite/affine/ext-loader'; import { ViewExtensionManager } from '@blocksuite/affine/ext-loader';
import { getInternalViewExtensions } from '@blocksuite/affine/extensions/view'; import { getInternalViewExtensions } from '@blocksuite/affine/extensions/view';
import { FoundationViewExtension } from '@blocksuite/affine/foundation/view';
import { AffineCanvasTextFonts } from '@blocksuite/affine/shared/services';
import { LinkedDocViewExtension } from '@blocksuite/affine/widgets/linked-doc/view'; import { LinkedDocViewExtension } from '@blocksuite/affine/widgets/linked-doc/view';
import type { FrameworkProvider } from '@toeverything/infra'; import type { FrameworkProvider } from '@toeverything/infra';
import type { TemplateResult } from 'lit';
import { CodeBlockPreviewViewExtension } from './code-block-preview'; import { CodeBlockPreviewViewExtension } from './code-block-preview';
@@ -50,6 +60,8 @@ type Configure = {
value: ViewExtensionManager; value: ViewExtensionManager;
}; };
const peekViewLogger = new DebugLogger('affine::patch-peek-view-service');
class ViewProvider { class ViewProvider {
static instance: ViewProvider | null = null; static instance: ViewProvider | null = null;
static getInstance() { static getInstance() {
@@ -129,6 +141,45 @@ class ViewProvider {
}; };
private readonly _configureCommon = (framework?: FrameworkProvider) => { private readonly _configureCommon = (framework?: FrameworkProvider) => {
const peekViewService = framework?.get(PeekViewService);
this._manager.configure(FoundationViewExtension, {
telemetry: {
track: (eventName, props) => {
mixpanel.track(eventName, props);
},
},
fontConfig: AffineCanvasTextFonts.map(font => ({
...font,
url: environment.publicPath + 'fonts/' + font.url.split('/').pop(),
})),
peekView: !peekViewService
? undefined
: ({
peek: (
element: {
target: HTMLElement;
docId: string;
blockIds?: string[];
template?: TemplateResult;
},
options?: PeekOptions
) => {
peekViewLogger.debug('center peek', element);
const { template, target, ...props } = element;
return peekViewService.peekView.open(
{
element: target,
docRef: props,
},
template,
options?.abortSignal
);
},
} satisfies BSPeekViewService),
});
this._manager.configure(AffineCommonViewExtension, { this._manager.configure(AffineCommonViewExtension, {
framework, framework,
}); });