feat(editor): add embed option config extension (#10540)

This PR implements a significant refactoring of the embed block services across multiple components (Figma, GitHub, Loom, and YouTube) in the BlockSuite codebase. Here are the key changes:

1. **Architecture Change**:
   - Moves from a dynamic registration pattern to a more declarative configuration approach
   - Replaces `EmbedOptionProvider.registerEmbedBlockOptions()` calls with a new `EmbedOptionConfig` factory function

2. **Service Refactoring**:
   - For each embed block type (Figma, GitHub, Loom, YouTube):
     - Separates configuration from service logic
     - Creates new `EmbedBlockOptionConfig` constants using the new `EmbedOptionConfig` factory
     - Removes the `mounted()` lifecycle hook from services where it was only used for registration

3. **Core Changes**:
   - In `embed-option-service.ts`:
     - Introduces new `EmbedOptionConfigIdentifier` for dependency injection
     - Adds `EmbedOptionConfig` factory function for creating embed configurations
     - Updates `EmbedOptionService` constructor to automatically register configurations
     - Modifies DI setup to include `StdIdentifier` dependency

4. **Spec Updates**:
   - Updates all block specs to include the new configuration objects
   - Maintains existing functionality while using the new pattern

The main benefit of this refactoring is:
- More declarative configuration approach
- Better separation of concerns
- Reduced runtime registration code
- More predictable initialization of embed options
This commit is contained in:
Saul-Mirone
2025-03-01 16:40:18 +00:00
parent 7527d36547
commit 6353e72078
10 changed files with 72 additions and 64 deletions

View File

@@ -10,13 +10,9 @@ import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
import { EmbedBlockComponent } from '../common/embed-block-element.js'; import { EmbedBlockComponent } from '../common/embed-block-element.js';
import type { EmbedFigmaBlockService } from './embed-figma-service.js';
import { FigmaIcon, styles } from './styles.js'; import { FigmaIcon, styles } from './styles.js';
export class EmbedFigmaBlockComponent extends EmbedBlockComponent< export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaModel> {
EmbedFigmaModel,
EmbedFigmaBlockService
> {
static override styles = styles; static override styles = styles;
override _cardStyle: (typeof EmbedFigmaStyles)[number] = 'figma'; override _cardStyle: (typeof EmbedFigmaStyles)[number] = 'figma';

View File

@@ -2,22 +2,13 @@ import {
EmbedFigmaBlockSchema, EmbedFigmaBlockSchema,
EmbedFigmaStyles, EmbedFigmaStyles,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services'; import { EmbedOptionConfig } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
import { figmaUrlRegex } from './embed-figma-model.js'; import { figmaUrlRegex } from './embed-figma-model.js';
export class EmbedFigmaBlockService extends BlockService { export const EmbedFigmaBlockOptionConfig = EmbedOptionConfig({
static override readonly flavour = EmbedFigmaBlockSchema.model.flavour; flavour: EmbedFigmaBlockSchema.model.flavour,
urlRegex: figmaUrlRegex,
override mounted() { styles: EmbedFigmaStyles,
super.mounted(); viewType: 'embed',
});
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: figmaUrlRegex,
styles: EmbedFigmaStyles,
viewType: 'embed',
});
}
}

View File

@@ -3,15 +3,15 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js'; import { literal } from 'lit/static-html.js';
import { EmbedFigmaBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedFigmaBlockAdapterExtensions } from './adapters/extension.js';
import { EmbedFigmaBlockService } from './embed-figma-service.js'; import { EmbedFigmaBlockOptionConfig } from './embed-figma-service.js';
export const EmbedFigmaBlockSpec: ExtensionType[] = [ export const EmbedFigmaBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-figma'), FlavourExtension('affine:embed-figma'),
EmbedFigmaBlockService,
BlockViewExtension('affine:embed-figma', model => { BlockViewExtension('affine:embed-figma', model => {
return model.parent?.flavour === 'affine:surface' return model.parent?.flavour === 'affine:surface'
? literal`affine-embed-edgeless-figma-block` ? literal`affine-embed-edgeless-figma-block`
: literal`affine-embed-figma-block`; : literal`affine-embed-figma-block`;
}), }),
EmbedFigmaBlockAdapterExtensions, EmbedFigmaBlockAdapterExtensions,
EmbedFigmaBlockOptionConfig,
].flat(); ].flat();

View File

@@ -4,7 +4,7 @@ import {
EmbedGithubStyles, EmbedGithubStyles,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
EmbedOptionProvider, EmbedOptionConfig,
LinkPreviewerService, LinkPreviewerService,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std'; import { BlockService } from '@blocksuite/block-std';
@@ -26,15 +26,11 @@ export class EmbedGithubBlockService extends BlockService {
signal signal
); );
}; };
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: githubUrlRegex,
styles: EmbedGithubStyles,
viewType: 'card',
});
}
} }
export const EmbedGithubBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedGithubBlockSchema.model.flavour,
urlRegex: githubUrlRegex,
styles: EmbedGithubStyles,
viewType: 'card',
});

View File

@@ -3,7 +3,10 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js'; import { literal } from 'lit/static-html.js';
import { EmbedGithubBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedGithubBlockAdapterExtensions } from './adapters/extension.js';
import { EmbedGithubBlockService } from './embed-github-service.js'; import {
EmbedGithubBlockOptionConfig,
EmbedGithubBlockService,
} from './embed-github-service.js';
export const EmbedGithubBlockSpec: ExtensionType[] = [ export const EmbedGithubBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-github'), FlavourExtension('affine:embed-github'),
@@ -14,4 +17,5 @@ export const EmbedGithubBlockSpec: ExtensionType[] = [
: literal`affine-embed-github-block`; : literal`affine-embed-github-block`;
}), }),
EmbedGithubBlockAdapterExtensions, EmbedGithubBlockAdapterExtensions,
EmbedGithubBlockOptionConfig,
].flat(); ].flat();

View File

@@ -3,7 +3,7 @@ import {
type EmbedLoomModel, type EmbedLoomModel,
EmbedLoomStyles, EmbedLoomStyles,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services'; import { EmbedOptionConfig } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std'; import { BlockService } from '@blocksuite/block-std';
import { loomUrlRegex } from './embed-loom-model.js'; import { loomUrlRegex } from './embed-loom-model.js';
@@ -15,15 +15,11 @@ export class EmbedLoomBlockService extends BlockService {
queryUrlData = (embedLoomModel: EmbedLoomModel, signal?: AbortSignal) => { queryUrlData = (embedLoomModel: EmbedLoomModel, signal?: AbortSignal) => {
return queryEmbedLoomData(embedLoomModel, signal); return queryEmbedLoomData(embedLoomModel, signal);
}; };
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: loomUrlRegex,
styles: EmbedLoomStyles,
viewType: 'embed',
});
}
} }
export const EmbedLoomBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedLoomBlockSchema.model.flavour,
urlRegex: loomUrlRegex,
styles: EmbedLoomStyles,
viewType: 'embed',
});

View File

@@ -3,7 +3,10 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js'; import { literal } from 'lit/static-html.js';
import { EmbedLoomBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedLoomBlockAdapterExtensions } from './adapters/extension.js';
import { EmbedLoomBlockService } from './embed-loom-service.js'; import {
EmbedLoomBlockOptionConfig,
EmbedLoomBlockService,
} from './embed-loom-service.js';
export const EmbedLoomBlockSpec: ExtensionType[] = [ export const EmbedLoomBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-loom'), FlavourExtension('affine:embed-loom'),
@@ -14,4 +17,5 @@ export const EmbedLoomBlockSpec: ExtensionType[] = [
: literal`affine-embed-loom-block`; : literal`affine-embed-loom-block`;
}), }),
EmbedLoomBlockAdapterExtensions, EmbedLoomBlockAdapterExtensions,
EmbedLoomBlockOptionConfig,
].flat(); ].flat();

View File

@@ -4,7 +4,7 @@ import {
EmbedYoutubeStyles, EmbedYoutubeStyles,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
EmbedOptionProvider, EmbedOptionConfig,
LinkPreviewerService, LinkPreviewerService,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std'; import { BlockService } from '@blocksuite/block-std';
@@ -25,15 +25,11 @@ export class EmbedYoutubeBlockService extends BlockService {
signal signal
); );
}; };
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: youtubeUrlRegex,
styles: EmbedYoutubeStyles,
viewType: 'embed',
});
}
} }
export const EmbedYoutubeBlockOptionConfig = EmbedOptionConfig({
flavour: EmbedYoutubeBlockSchema.model.flavour,
urlRegex: youtubeUrlRegex,
styles: EmbedYoutubeStyles,
viewType: 'embed',
});

View File

@@ -3,7 +3,10 @@ import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js'; import { literal } from 'lit/static-html.js';
import { EmbedYoutubeBlockAdapterExtensions } from './adapters/extension.js'; import { EmbedYoutubeBlockAdapterExtensions } from './adapters/extension.js';
import { EmbedYoutubeBlockService } from './embed-youtube-service.js'; import {
EmbedYoutubeBlockOptionConfig,
EmbedYoutubeBlockService,
} from './embed-youtube-service.js';
export const EmbedYoutubeBlockSpec: ExtensionType[] = [ export const EmbedYoutubeBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-youtube'), FlavourExtension('affine:embed-youtube'),
@@ -14,4 +17,5 @@ export const EmbedYoutubeBlockSpec: ExtensionType[] = [
: literal`affine-embed-youtube-block`; : literal`affine-embed-youtube-block`;
}), }),
EmbedYoutubeBlockAdapterExtensions, EmbedYoutubeBlockAdapterExtensions,
EmbedYoutubeBlockOptionConfig,
].flat(); ].flat();

View File

@@ -1,7 +1,8 @@
import type { EmbedCardStyle } from '@blocksuite/affine-model'; import type { EmbedCardStyle } from '@blocksuite/affine-model';
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
import type { Container } from '@blocksuite/global/di'; import type { Container } from '@blocksuite/global/di';
import { createIdentifier } from '@blocksuite/global/di'; import { createIdentifier } from '@blocksuite/global/di';
import { Extension } from '@blocksuite/store'; import { Extension, type ExtensionType } from '@blocksuite/store';
export type EmbedOptions = { export type EmbedOptions = {
flavour: string; flavour: string;
@@ -19,12 +20,32 @@ export const EmbedOptionProvider = createIdentifier<EmbedOptionProvider>(
'AffineEmbedOptionProvider' 'AffineEmbedOptionProvider'
); );
export const EmbedOptionConfigIdentifier = createIdentifier<EmbedOptions>(
'AffineEmbedOptionConfig'
);
export const EmbedOptionConfig = (options: EmbedOptions): ExtensionType => {
return {
setup: di => {
di.addImpl(EmbedOptionConfigIdentifier(options.flavour), options);
},
};
};
export class EmbedOptionService export class EmbedOptionService
extends Extension extends Extension
implements EmbedOptionProvider implements EmbedOptionProvider
{ {
private readonly _embedBlockRegistry = new Set<EmbedOptions>(); private readonly _embedBlockRegistry = new Set<EmbedOptions>();
constructor(readonly std: BlockStdScope) {
super();
const configs = this.std.provider.getAll(EmbedOptionConfigIdentifier);
configs.forEach(value => {
this.registerEmbedBlockOptions(value);
});
}
getEmbedBlockOptions = (url: string): EmbedOptions | null => { getEmbedBlockOptions = (url: string): EmbedOptions | null => {
const entries = this._embedBlockRegistry.entries(); const entries = this._embedBlockRegistry.entries();
for (const [options] of entries) { for (const [options] of entries) {
@@ -39,6 +60,6 @@ export class EmbedOptionService
}; };
static override setup(di: Container) { static override setup(di: Container) {
di.addImpl(EmbedOptionProvider, EmbedOptionService); di.addImpl(EmbedOptionProvider, EmbedOptionService, [StdIdentifier]);
} }
} }