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:
@@ -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';
|
||||||
|
|||||||
@@ -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',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user