feat(editor): replace spec provider with extension manager (#11861)

Closes: BS-3273
This commit is contained in:
Saul-Mirone
2025-04-22 07:40:41 +00:00
parent 8fdb00e0ab
commit 6d6504e2af
51 changed files with 623 additions and 177 deletions

View File

@@ -208,7 +208,9 @@
"./model": "./src/model/index.ts", "./model": "./src/model/index.ts",
"./sync": "./src/sync/index.ts", "./sync": "./src/sync/index.ts",
"./adapters": "./src/adapters/index.ts", "./adapters": "./src/adapters/index.ts",
"./extensions": "./src/extensions/index.ts" "./extensions": "./src/extensions/index.ts",
"./extensions/store": "./src/extensions/store.ts",
"./extensions/view": "./src/extensions/view.ts"
}, },
"files": [ "files": [
"src", "src",

View File

@@ -0,0 +1,153 @@
import { AttachmentBlockSpec } from '@blocksuite/affine-block-attachment';
import { BookmarkBlockSpec } from '@blocksuite/affine-block-bookmark';
import { CalloutBlockSpec } from '@blocksuite/affine-block-callout';
import { CodeBlockSpec } from '@blocksuite/affine-block-code';
import { DataViewBlockSpec } from '@blocksuite/affine-block-data-view';
import { DatabaseBlockSpec } from '@blocksuite/affine-block-database';
import { DividerBlockSpec } from '@blocksuite/affine-block-divider';
import { EdgelessTextBlockSpec } from '@blocksuite/affine-block-edgeless-text';
import { EmbedExtensions } from '@blocksuite/affine-block-embed';
import { FrameBlockSpec } from '@blocksuite/affine-block-frame';
import { ImageBlockSpec } from '@blocksuite/affine-block-image';
import { LatexBlockSpec } from '@blocksuite/affine-block-latex';
import { ListBlockSpec } from '@blocksuite/affine-block-list';
import {
EdgelessNoteBlockSpec,
NoteBlockSpec,
} from '@blocksuite/affine-block-note';
import { ParagraphBlockSpec } from '@blocksuite/affine-block-paragraph';
import {
EdgelessBuiltInSpecs,
PageRootBlockSpec,
PreviewEdgelessRootBlockSpec,
PreviewPageRootBlockSpec,
ReadOnlyClipboard,
} from '@blocksuite/affine-block-root';
import {
EdgelessSurfaceBlockAdapterExtensions,
EdgelessSurfaceBlockSpec,
PageSurfaceBlockSpec,
SurfaceBlockAdapterExtensions,
} from '@blocksuite/affine-block-surface';
import {
EdgelessSurfaceRefBlockSpec,
PageSurfaceRefBlockSpec,
} from '@blocksuite/affine-block-surface-ref';
import { TableBlockSpec } from '@blocksuite/affine-block-table';
import {
brushToMarkdownAdapterMatcher,
brushToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-brush';
import {
connectorToMarkdownAdapterMatcher,
connectorToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-connector';
import {
groupToMarkdownAdapterMatcher,
groupToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-group';
import {
mindmapToMarkdownAdapterMatcher,
mindmapToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-mindmap';
import {
shapeToMarkdownAdapterMatcher,
shapeToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-shape';
import {
textToMarkdownAdapterMatcher,
textToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-text';
import { inlinePresetExtensions } from '@blocksuite/affine-inline-preset';
import {
DefaultOpenDocExtension,
DocDisplayMetaService,
EditPropsStore,
FontLoaderService,
} from '@blocksuite/affine-shared/services';
import type { ExtensionType } from '@blocksuite/store';
const elementToPlainTextAdapterMatchers = [
groupToPlainTextAdapterMatcher,
shapeToPlainTextAdapterMatcher,
connectorToPlainTextAdapterMatcher,
brushToPlainTextAdapterMatcher,
textToPlainTextAdapterMatcher,
mindmapToPlainTextAdapterMatcher,
];
const elementToMarkdownAdapterMatchers = [
groupToMarkdownAdapterMatcher,
shapeToMarkdownAdapterMatcher,
connectorToMarkdownAdapterMatcher,
brushToMarkdownAdapterMatcher,
textToMarkdownAdapterMatcher,
mindmapToMarkdownAdapterMatcher,
];
const CommonBlockSpecs: ExtensionType[] = [
inlinePresetExtensions,
DocDisplayMetaService,
EditPropsStore,
LatexBlockSpec,
ListBlockSpec,
DatabaseBlockSpec,
TableBlockSpec,
DataViewBlockSpec,
DividerBlockSpec,
BookmarkBlockSpec,
EmbedExtensions,
AttachmentBlockSpec,
CodeBlockSpec,
ImageBlockSpec,
ParagraphBlockSpec,
DefaultOpenDocExtension,
FontLoaderService,
CalloutBlockSpec,
FrameBlockSpec,
elementToPlainTextAdapterMatchers,
elementToMarkdownAdapterMatchers,
].flat();
const PageFirstPartyBlockSpecs: ExtensionType[] = [
CommonBlockSpecs,
NoteBlockSpec,
PageSurfaceBlockSpec,
PageSurfaceRefBlockSpec,
...SurfaceBlockAdapterExtensions,
].flat();
const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
CommonBlockSpecs,
EdgelessNoteBlockSpec,
EdgelessSurfaceBlockSpec,
EdgelessSurfaceRefBlockSpec,
EdgelessTextBlockSpec,
...EdgelessSurfaceBlockAdapterExtensions,
].flat();
export const MigratingEdgelessEditorBlockSpecs: ExtensionType[] = [
EdgelessBuiltInSpecs,
EdgelessFirstPartyBlockSpecs,
].flat();
export const MigratingPageEditorBlockSpecs: ExtensionType[] = [
PageRootBlockSpec,
PageFirstPartyBlockSpecs,
].flat();
export const MigratingPreviewEdgelessEditorBlockSpecs: ExtensionType[] = [
PreviewEdgelessRootBlockSpec,
EdgelessFirstPartyBlockSpecs,
ReadOnlyClipboard,
].flat();
export const MigratingPreviewPageEditorBlockSpecs: ExtensionType[] = [
PreviewPageRootBlockSpec,
PageFirstPartyBlockSpecs,
ReadOnlyClipboard,
].flat();

View File

@@ -4,6 +4,10 @@ import { EmbedIframeConfigExtensions } from '@blocksuite/affine-block-embed';
import { ImageStoreSpec } from '@blocksuite/affine-block-image'; import { ImageStoreSpec } from '@blocksuite/affine-block-image';
import { SurfaceBlockSchemaExtension } from '@blocksuite/affine-block-surface'; import { SurfaceBlockSchemaExtension } from '@blocksuite/affine-block-surface';
import { TableSelectionExtension } from '@blocksuite/affine-block-table'; import { TableSelectionExtension } from '@blocksuite/affine-block-table';
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { import {
AttachmentBlockSchemaExtension, AttachmentBlockSchemaExtension,
BookmarkBlockSchemaExtension, BookmarkBlockSchemaExtension,
@@ -110,3 +114,12 @@ export const StoreExtensions: ExtensionType[] = [
EmbedIframeConfigExtensions, EmbedIframeConfigExtensions,
EmbedIframeService, EmbedIframeService,
].flat(); ].flat();
export class MigratingStoreExtension extends StoreExtensionProvider {
override name = 'migrating';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(StoreExtensions);
}
}

View File

@@ -0,0 +1,42 @@
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { effects } from '../effects';
import {
MigratingEdgelessEditorBlockSpecs,
MigratingPageEditorBlockSpecs,
MigratingPreviewEdgelessEditorBlockSpecs,
MigratingPreviewPageEditorBlockSpecs,
} from './migrating';
export class MigratingViewExtension extends ViewExtensionProvider {
override name = 'migrating';
override effect() {
super.effect();
effects();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
const scope = context.scope;
if (scope === 'preview-page') {
context.register(MigratingPreviewPageEditorBlockSpecs);
return;
}
if (scope === 'preview-edgeless') {
context.register(MigratingPreviewEdgelessEditorBlockSpecs);
return;
}
if (scope === 'page' || scope === 'mobile-page') {
context.register(MigratingPageEditorBlockSpecs);
return;
}
if (scope === 'edgeless' || scope === 'mobile-edgeless') {
context.register(MigratingEdgelessEditorBlockSpecs);
return;
}
}
}

View File

@@ -1,4 +1,5 @@
import { getSurfaceBlock } from '@blocksuite/affine-block-surface'; import { getSurfaceBlock } from '@blocksuite/affine-block-surface';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import { import {
type DocMode, type DocMode,
ImageBlockModel, ImageBlockModel,
@@ -9,7 +10,7 @@ import {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts'; import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts';
import { NotificationProvider } from '@blocksuite/affine-shared/services'; import { NotificationProvider } from '@blocksuite/affine-shared/services';
import { matchModels, SpecProvider } from '@blocksuite/affine-shared/utils'; import { matchModels } from '@blocksuite/affine-shared/utils';
import { BlockStdScope, EditorLifeCycleExtension } from '@blocksuite/std'; import { BlockStdScope, EditorLifeCycleExtension } from '@blocksuite/std';
import { import {
type BlockModel, type BlockModel,
@@ -202,10 +203,13 @@ async function renderNoteContent(
match: ids.map(id => ({ id, viewType: 'display' })), match: ids.map(id => ({ id, viewType: 'display' })),
}; };
const previewDoc = doc.doc.getStore({ query }); const previewDoc = doc.doc.getStore({ query });
const previewSpec = SpecProvider._.getSpec('preview:page'); const std = card.host.std;
const previewSpec = std
.get(ViewExtensionManagerIdentifier)
.get('preview-page');
const previewStd = new BlockStdScope({ const previewStd = new BlockStdScope({
store: previewDoc, store: previewDoc,
extensions: previewSpec.value, extensions: previewSpec,
}); });
const previewTemplate = previewStd.render(); const previewTemplate = previewStd.render();
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();

View File

@@ -70,7 +70,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
<div class="affine-page-viewport" data-theme=${appTheme}> <div class="affine-page-viewport" data-theme=${appTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('preview:page'), extensions: this._buildPreviewSpec('preview-page'),
}).render()} }).render()}
</div> </div>
`, `,
@@ -81,7 +81,7 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}> <div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('preview:edgeless'), extensions: this._buildPreviewSpec('preview-edgeless'),
}).render()} }).render()}
</div> </div>
`, `,

View File

@@ -1,4 +1,5 @@
import { Peekable } from '@blocksuite/affine-components/peek'; import { Peekable } from '@blocksuite/affine-components/peek';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import { import {
type DocLinkClickedEvent, type DocLinkClickedEvent,
RefNodeSlotsProvider, RefNodeSlotsProvider,
@@ -20,10 +21,7 @@ import {
ThemeExtensionIdentifier, ThemeExtensionIdentifier,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { import { cloneReferenceInfo } from '@blocksuite/affine-shared/utils';
cloneReferenceInfo,
SpecProvider,
} from '@blocksuite/affine-shared/utils';
import { Bound, getCommonBound } from '@blocksuite/global/gfx'; import { Bound, getCommonBound } from '@blocksuite/global/gfx';
import { import {
BlockSelection, BlockSelection,
@@ -113,9 +111,10 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
], ],
}; };
protected _buildPreviewSpec = (name: 'preview:page' | 'preview:edgeless') => { protected _buildPreviewSpec = (name: 'preview-page' | 'preview-edgeless') => {
const nextDepth = this.depth + 1; const nextDepth = this.depth + 1;
const previewSpecBuilder = SpecProvider._.getSpec(name); const viewExtensionManager = this.std.get(ViewExtensionManagerIdentifier);
const previewSpec = viewExtensionManager.get(name);
const currentDisposables = this.disposables; const currentDisposables = this.disposables;
const editorSetting = this.std.getOptional(EditorSettingProvider) ?? { const editorSetting = this.std.getOptional(EditorSettingProvider) ?? {
setting$: signal(GeneralSettingSchema.parse({})), setting$: signal(GeneralSettingSchema.parse({})),
@@ -157,13 +156,11 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
} }
} }
previewSpecBuilder.extend([ return previewSpec.concat([
EmbedSyncedDocWatcher, EmbedSyncedDocWatcher,
GfxViewportInitializer, GfxViewportInitializer,
EditorSettingExtension(editorSetting), EditorSettingExtension(editorSetting),
]); ]);
return previewSpecBuilder.value;
}; };
protected _renderSyncedView = () => { protected _renderSyncedView = () => {
@@ -204,7 +201,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
<div class="affine-page-viewport" data-theme=${appTheme}> <div class="affine-page-viewport" data-theme=${appTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('preview:page'), extensions: this._buildPreviewSpec('preview-page'),
}).render()} }).render()}
</div> </div>
`, `,
@@ -215,7 +212,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
<div class="affine-edgeless-viewport" data-theme=${edgelessTheme}> <div class="affine-edgeless-viewport" data-theme=${edgelessTheme}>
${new BlockStdScope({ ${new BlockStdScope({
store: syncedDoc, store: syncedDoc,
extensions: this._buildPreviewSpec('preview:edgeless'), extensions: this._buildPreviewSpec('preview-edgeless'),
}).render()} }).render()}
</div> </div>
`, `,

View File

@@ -13,6 +13,7 @@
"@blocksuite/affine-block-frame": "workspace:*", "@blocksuite/affine-block-frame": "workspace:*",
"@blocksuite/affine-block-surface": "workspace:*", "@blocksuite/affine-block-surface": "workspace:*",
"@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-inline-reference": "workspace:*", "@blocksuite/affine-inline-reference": "workspace:*",
"@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-shared": "workspace:*", "@blocksuite/affine-shared": "workspace:*",

View File

@@ -1,4 +1,5 @@
import type { CanvasRenderer } from '@blocksuite/affine-block-surface'; import type { CanvasRenderer } from '@blocksuite/affine-block-surface';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import type { NoteBlockModel } from '@blocksuite/affine-model'; import type { NoteBlockModel } from '@blocksuite/affine-model';
import { import {
DefaultTheme, DefaultTheme,
@@ -10,7 +11,6 @@ import {
EDGELESS_BLOCK_CHILD_PADDING, EDGELESS_BLOCK_CHILD_PADDING,
} from '@blocksuite/affine-shared/consts'; } from '@blocksuite/affine-shared/consts';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { deserializeXYWH } from '@blocksuite/global/gfx'; import { deserializeXYWH } from '@blocksuite/global/gfx';
import { WithDisposable } from '@blocksuite/global/lit'; import { WithDisposable } from '@blocksuite/global/lit';
import { import {
@@ -122,10 +122,12 @@ export class SurfaceRefNotePortal extends WithDisposable(ShadowlessElement) {
query: this.query, query: this.query,
readonly: true, readonly: true,
}); });
const previewSpec = SpecProvider._.getSpec('preview:page'); const previewSpec = this.host.std
.get(ViewExtensionManagerIdentifier)
.get('preview-page');
return new BlockStdScope({ return new BlockStdScope({
store: doc, store: doc,
extensions: previewSpec.value.slice(), extensions: previewSpec,
}).render(); }).render();
} }

View File

@@ -6,6 +6,7 @@ import {
import type { BlockCaptionEditor } from '@blocksuite/affine-components/caption'; import type { BlockCaptionEditor } from '@blocksuite/affine-components/caption';
import { whenHover } from '@blocksuite/affine-components/hover'; import { whenHover } from '@blocksuite/affine-components/hover';
import { Peekable } from '@blocksuite/affine-components/peek'; import { Peekable } from '@blocksuite/affine-components/peek';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import { RefNodeSlotsProvider } from '@blocksuite/affine-inline-reference'; import { RefNodeSlotsProvider } from '@blocksuite/affine-inline-reference';
import { import {
FrameBlockModel, FrameBlockModel,
@@ -20,10 +21,7 @@ import {
ViewportElementExtension, ViewportElementExtension,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
requestConnectedFrame,
SpecProvider,
} from '@blocksuite/affine-shared/utils';
import { DisposableGroup } from '@blocksuite/global/disposable'; import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { import {
@@ -46,7 +44,7 @@ import {
type GfxModel, type GfxModel,
GfxPrimitiveElementModel, GfxPrimitiveElementModel,
} from '@blocksuite/std/gfx'; } from '@blocksuite/std/gfx';
import type { BaseSelection, Store } from '@blocksuite/store'; import type { BaseSelection, ExtensionType, Store } from '@blocksuite/store';
import { effect, signal } from '@preact/signals-core'; import { effect, signal } from '@preact/signals-core';
import { css, html, nothing } from 'lit'; import { css, html, nothing } from 'lit';
import { query } from 'lit/decorators.js'; import { query } from 'lit/decorators.js';
@@ -114,9 +112,18 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
private _previewDoc: Store | null = null; private _previewDoc: Store | null = null;
private readonly _previewSpec = SpecProvider._.getSpec( private _runtimePreviewExt: ExtensionType[] = [];
'preview:edgeless'
).extend([ViewportElementExtension('.ref-viewport')]); private get _viewExtensionManager() {
return this.std.get(ViewExtensionManagerIdentifier);
}
private get _previewSpec() {
return [
...this._viewExtensionManager.get('preview-edgeless'),
ViewportElementExtension('.ref-viewport'),
];
}
private _referencedModel: GfxModel | null = null; private _referencedModel: GfxModel | null = null;
@@ -338,7 +345,7 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
} }
} }
this._previewSpec.extend([SurfaceRefViewportWatcher]); this._runtimePreviewExt = [SurfaceRefViewportWatcher];
} }
private _initHover() { private _initHover() {
@@ -366,7 +373,7 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
private _renderRefContent(referencedModel: GfxModel) { private _renderRefContent(referencedModel: GfxModel) {
const [, , w, h] = deserializeXYWH(referencedModel.xywh); const [, , w, h] = deserializeXYWH(referencedModel.xywh);
const _previewSpec = this._previewSpec.value; const _previewSpec = this._previewSpec.concat(this._runtimePreviewExt);
return html`<div class="ref-content"> return html`<div class="ref-content">
<div <div

View File

@@ -10,6 +10,7 @@
{ "path": "../frame" }, { "path": "../frame" },
{ "path": "../surface" }, { "path": "../surface" },
{ "path": "../../components" }, { "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../inlines/reference" }, { "path": "../../inlines/reference" },
{ "path": "../../model" }, { "path": "../../model" },
{ "path": "../../shared" }, { "path": "../../shared" },

View File

@@ -54,14 +54,17 @@ describe('multiple scopes', () => {
class ViewExt1 extends ViewExtensionProvider { class ViewExt1 extends ViewExtensionProvider {
override name = 'ViewExt1'; override name = 'ViewExt1';
override setup(context: ViewExtensionContext) { constructor() {
super.setup(context); super();
setup1();
}
override setup(context: ViewExtensionContext, option?: { foo: number }) {
super.setup(context, option);
if (context.scope === 'page') { if (context.scope === 'page') {
setup1();
context.register(Ext2); context.register(Ext2);
} }
if (context.scope === 'edgeless') { if (context.scope === 'edgeless') {
setup2();
context.register(Ext3); context.register(Ext3);
} }
} }
@@ -69,6 +72,11 @@ describe('multiple scopes', () => {
class ViewExt2 extends ViewExtensionProvider { class ViewExt2 extends ViewExtensionProvider {
override name = 'ViewExt2'; override name = 'ViewExt2';
constructor() {
super();
setup2();
}
override setup(context: ViewExtensionContext) { override setup(context: ViewExtensionContext) {
super.setup(context); super.setup(context);
if (context.scope === 'page') { if (context.scope === 'page') {
@@ -87,7 +95,7 @@ describe('multiple scopes', () => {
expect(edgelessExtensions).toEqual([Ext3, Ext5]); expect(edgelessExtensions).toEqual([Ext3, Ext5]);
}); });
it('should setup be cached', () => { it('should cache provider instances', () => {
manager.get('page'); manager.get('page');
manager.get('edgeless'); manager.get('edgeless');
expect(setup1).toHaveBeenCalledTimes(1); expect(setup1).toHaveBeenCalledTimes(1);

View File

@@ -84,16 +84,17 @@ export class ExtensionManager<Scope extends string> {
/** /**
* Retrieves all extensions registered for a specific scope. * Retrieves all extensions registered for a specific scope.
* If the scope hasn't been built yet, it triggers the build process. * It triggers the build process.
* *
* @param scope - The scope to retrieve extensions for * @param scope - The scope to retrieve extensions for
* @returns An array of extensions registered for the specified scope * @returns An array of extensions registered for the specified scope
* @throws {BlockSuiteError} If the scope is not found * @throws {BlockSuiteError} If the scope is not found
*/ */
get(scope: Scope) { get(scope: Scope) {
if (!this._extensions.has(scope)) { if (this._extensions.has(scope)) {
this._build(scope); this._extensions.delete(scope);
} }
this._build(scope);
const extensionSet = this._extensions.get(scope); const extensionSet = this._extensions.get(scope);
if (!extensionSet) { if (!extensionSet) {
throw new BlockSuiteError( throw new BlockSuiteError(
@@ -117,14 +118,19 @@ export class ExtensionManager<Scope extends string> {
provider: typeof BaseExtensionProvider<Scope, T>, provider: typeof BaseExtensionProvider<Scope, T>,
options: ((prev: T | undefined) => T | undefined) | T | undefined options: ((prev: T | undefined) => T | undefined) | T | undefined
) { ) {
const prev = this._providerOptions.get(provider);
let config: T | undefined; let config: T | undefined;
if (typeof options === 'function') { if (typeof options === 'function') {
const prev = this._providerOptions.get(provider);
config = (options as (prev: unknown) => T)(prev); config = (options as (prev: unknown) => T)(prev);
} else { } else {
config = options; config = options;
} }
if (prev === config) {
return;
}
if (config === undefined) { if (config === undefined) {
this._providerOptions.delete(provider); this._providerOptions.delete(provider);
} else { } else {

View File

@@ -13,6 +13,7 @@
"@blocksuite/affine-block-frame": "workspace:*", "@blocksuite/affine-block-frame": "workspace:*",
"@blocksuite/affine-block-surface": "workspace:*", "@blocksuite/affine-block-surface": "workspace:*",
"@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*", "@blocksuite/affine-shared": "workspace:*",

View File

@@ -1,10 +1,10 @@
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import type { FrameBlockModel } from '@blocksuite/affine-model'; import type { FrameBlockModel } from '@blocksuite/affine-model';
import { import {
DocModeExtension, DocModeExtension,
DocModeProvider, DocModeProvider,
ViewportElementExtension, ViewportElementExtension,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { DisposableGroup } from '@blocksuite/global/disposable'; import { DisposableGroup } from '@blocksuite/global/disposable';
import { Bound, deserializeXYWH } from '@blocksuite/global/gfx'; import { Bound, deserializeXYWH } from '@blocksuite/global/gfx';
import { WithDisposable } from '@blocksuite/global/lit'; import { WithDisposable } from '@blocksuite/global/lit';
@@ -15,13 +15,12 @@ import {
ShadowlessElement, ShadowlessElement,
} from '@blocksuite/std'; } from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx'; import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import { type Query, type Store } from '@blocksuite/store'; import { type ExtensionType, type Query, type Store } from '@blocksuite/store';
import { css, html, nothing, type PropertyValues } from 'lit'; import { css, html, nothing, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js'; import { property, query, state } from 'lit/decorators.js';
import { guard } from 'lit/directives/guard.js'; import { guard } from 'lit/directives/guard.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
const DEFAULT_PREVIEW_CONTAINER_WIDTH = 280; const DEFAULT_PREVIEW_CONTAINER_WIDTH = 280;
const DEFAULT_PREVIEW_CONTAINER_HEIGHT = 166; const DEFAULT_PREVIEW_CONTAINER_HEIGHT = 166;
@@ -86,7 +85,15 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _previewDoc: Store | null = null; private _previewDoc: Store | null = null;
private readonly _previewSpec = SpecProvider._.getSpec('preview:edgeless'); private _runtimePreviewExt: ExtensionType[] = [];
private get _viewExtensionManager() {
return this.std.get(ViewExtensionManagerIdentifier);
}
private get _previewSpec() {
return this._viewExtensionManager.get('preview-edgeless');
}
private readonly _updateFrameViewportWH = () => { private readonly _updateFrameViewportWH = () => {
const [, , w, h] = deserializeXYWH(this.frame.xywh); const [, , w, h] = deserializeXYWH(this.frame.xywh);
@@ -143,11 +150,11 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
} }
const docModeService = this.std.get(DocModeProvider); const docModeService = this.std.get(DocModeProvider);
this._previewSpec.extend([ this._runtimePreviewExt = [
ViewportElementExtension('.frame-preview-viewport'), ViewportElementExtension('.frame-preview-viewport'),
FramePreviewWatcher, FramePreviewWatcher,
DocModeExtension(docModeService), DocModeExtension(docModeService),
]); ];
} }
private _refreshViewport() { private _refreshViewport() {
@@ -163,7 +170,7 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _renderSurfaceContent() { private _renderSurfaceContent() {
const { width, height } = this.frameViewportWH; const { width, height } = this.frameViewportWH;
const _previewSpec = this._previewSpec.value; const _previewSpec = this._previewSpec.concat(this._runtimePreviewExt);
return html`<div return html`<div
class="frame-preview-surface-container" class="frame-preview-surface-container"
style=${styleMap({ style=${styleMap({

View File

@@ -10,6 +10,7 @@
{ "path": "../../blocks/frame" }, { "path": "../../blocks/frame" },
{ "path": "../../blocks/surface" }, { "path": "../../blocks/surface" },
{ "path": "../../components" }, { "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../model" }, { "path": "../../model" },
{ "path": "../../rich-text" }, { "path": "../../rich-text" },
{ "path": "../../shared" }, { "path": "../../shared" },

View File

@@ -35,7 +35,7 @@ export function EditorSettingExtension(
): ExtensionType { ): ExtensionType {
return { return {
setup: di => { setup: di => {
di.addImpl(EditorSettingProvider, () => service); di.override(EditorSettingProvider, () => service);
}, },
}; };
} }

View File

@@ -17,6 +17,7 @@
"@blocksuite/affine-block-paragraph": "workspace:*", "@blocksuite/affine-block-paragraph": "workspace:*",
"@blocksuite/affine-block-surface": "workspace:*", "@blocksuite/affine-block-surface": "workspace:*",
"@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-shared": "workspace:*", "@blocksuite/affine-shared": "workspace:*",
"@blocksuite/global": "workspace:*", "@blocksuite/global": "workspace:*",

View File

@@ -1,10 +1,10 @@
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
import { import {
DocModeExtension, DocModeExtension,
DocModeProvider, DocModeProvider,
EditorSettingExtension, EditorSettingExtension,
EditorSettingProvider, EditorSettingProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { BlockStdScope, BlockViewIdentifier } from '@blocksuite/std'; import { BlockStdScope, BlockViewIdentifier } from '@blocksuite/std';
import type { import type {
BlockModel, BlockModel,
@@ -69,7 +69,9 @@ export class PreviewHelper {
const editorSetting = std.get(EditorSettingProvider); const editorSetting = std.get(EditorSettingProvider);
const query = this._calculateQuery(blockIds as string[]); const query = this._calculateQuery(blockIds as string[]);
const store = widget.doc.doc.getStore({ query }); const store = widget.doc.doc.getStore({ query });
const previewSpec = SpecProvider._.getSpec('preview:page'); let previewSpec = widget.std
.get(ViewExtensionManagerIdentifier)
.get('preview-page');
const settingSignal = signal({ ...editorSetting.setting$.peek() }); const settingSignal = signal({ ...editorSetting.setting$.peek() });
const extensions = [ const extensions = [
DocModeExtension(docModeService), DocModeExtension(docModeService),
@@ -99,7 +101,7 @@ export class PreviewHelper {
} as ExtensionType, } as ExtensionType,
]; ];
previewSpec.extend(extensions); previewSpec = previewSpec.concat(extensions);
settingSignal.value = { settingSignal.value = {
...settingSignal.value, ...settingSignal.value,
@@ -108,7 +110,7 @@ export class PreviewHelper {
const previewStd = new BlockStdScope({ const previewStd = new BlockStdScope({
store, store,
extensions: previewSpec.value, extensions: previewSpec,
}); });
let width: number = 500; let width: number = 500;

View File

@@ -14,6 +14,7 @@
{ "path": "../../blocks/paragraph" }, { "path": "../../blocks/paragraph" },
{ "path": "../../blocks/surface" }, { "path": "../../blocks/surface" },
{ "path": "../../components" }, { "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../model" }, { "path": "../../model" },
{ "path": "../../shared" }, { "path": "../../shared" },
{ "path": "../../../framework/global" }, { "path": "../../../framework/global" },

View File

@@ -28,7 +28,9 @@
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./effects": "./src/effects.ts" "./effects": "./src/effects.ts",
"./store": "./src/store.ts",
"./view": "./src/view.ts"
}, },
"files": [ "files": [
"src", "src",

View File

@@ -1,13 +1,7 @@
import '@toeverything/theme/style.css'; import '@toeverything/theme/style.css';
import '@toeverything/theme/fonts.css'; import '@toeverything/theme/fonts.css';
import { effects as blocksEffects } from '@blocksuite/affine/effects'; import { registerStoreSpecs } from '@blocksuite/affine/extensions';
import {
EdgelessEditorBlockSpecs,
PageEditorBlockSpecs,
registerStoreSpecs,
StoreExtensions,
} from '@blocksuite/affine/extensions';
import type { DocMode } from '@blocksuite/affine/model'; import type { DocMode } from '@blocksuite/affine/model';
import { AffineSchemas } from '@blocksuite/affine/schemas'; import { AffineSchemas } from '@blocksuite/affine/schemas';
import { import {
@@ -27,11 +21,17 @@ import {
import { effects } from '../../effects.js'; import { effects } from '../../effects.js';
import { TestAffineEditorContainer } from '../../index.js'; import { TestAffineEditorContainer } from '../../index.js';
import { getTestStoreManager } from '../../store.js';
import { getTestViewManager } from '../../view.js';
// FIXME: used for test import/export
registerStoreSpecs(); registerStoreSpecs();
blocksEffects(); const storeManager = getTestStoreManager();
const viewManager = getTestViewManager();
effects(); effects();
const storeExtensions = storeManager.get('store');
export function getRenderer() { export function getRenderer() {
return editor.std.get( return editor.std.get(
ViewportTurboRendererIdentifier ViewportTurboRendererIdentifier
@@ -85,12 +85,12 @@ async function createEditor(
editor.doc = doc; editor.doc = doc;
editor.mode = mode; editor.mode = mode;
editor.pageSpecs = [ editor.pageSpecs = [
...PageEditorBlockSpecs, ...viewManager.get('page'),
FontConfigExtension(CommunityCanvasTextFonts), FontConfigExtension(CommunityCanvasTextFonts),
...extensions, ...extensions,
]; ];
editor.edgelessSpecs = [ editor.edgelessSpecs = [
...EdgelessEditorBlockSpecs, ...viewManager.get('edgeless'),
FontConfigExtension(CommunityCanvasTextFonts), FontConfigExtension(CommunityCanvasTextFonts),
...extensions, ...extensions,
]; ];
@@ -123,7 +123,7 @@ export async function setupEditor(
extensions: ExtensionType[] = [] extensions: ExtensionType[] = []
) { ) {
const collection = new TestWorkspace(createCollectionOptions()); const collection = new TestWorkspace(createCollectionOptions());
collection.storeExtensions = StoreExtensions; collection.storeExtensions = storeExtensions;
collection.meta.initialize(); collection.meta.initialize();
window.collection = collection; window.collection = collection;

View File

@@ -0,0 +1,7 @@
import { StoreExtensionManager } from '@blocksuite/affine/ext-loader';
import { MigratingStoreExtension } from '@blocksuite/affine/extensions/store';
export function getTestStoreManager() {
const manager = new StoreExtensionManager([MigratingStoreExtension]);
return manager;
}

View File

@@ -0,0 +1,7 @@
import { ViewExtensionManager } from '@blocksuite/affine/ext-loader';
import { MigratingViewExtension } from '@blocksuite/affine/extensions/view';
export function getTestViewManager() {
const manager = new ViewExtensionManager([MigratingViewExtension]);
return manager;
}

View File

@@ -2,7 +2,6 @@ import '../../style.css';
import * as databaseBlocks from '@blocksuite/affine/blocks/database'; import * as databaseBlocks from '@blocksuite/affine/blocks/database';
import * as noteBlocks from '@blocksuite/affine/blocks/note'; import * as noteBlocks from '@blocksuite/affine/blocks/note';
import { effects as blocksEffects } from '@blocksuite/affine/effects';
import { registerStoreSpecs } from '@blocksuite/affine/extensions'; import { registerStoreSpecs } from '@blocksuite/affine/extensions';
import * as globalUtils from '@blocksuite/affine/global/utils'; import * as globalUtils from '@blocksuite/affine/global/utils';
import * as services from '@blocksuite/affine/shared/services'; import * as services from '@blocksuite/affine/shared/services';
@@ -10,7 +9,8 @@ import * as blockStd from '@blocksuite/affine/std';
import * as store from '@blocksuite/affine/store'; import * as store from '@blocksuite/affine/store';
import * as affineModel from '@blocksuite/affine-model'; import * as affineModel from '@blocksuite/affine-model';
import * as editor from '@blocksuite/integration-test'; import * as editor from '@blocksuite/integration-test';
import { effects as presetsEffects } from '@blocksuite/integration-test/effects'; import { effects as itEffects } from '@blocksuite/integration-test/effects';
import { getTestStoreManager } from '@blocksuite/integration-test/store';
import { setupEdgelessTemplate } from '../_common/setup.js'; import { setupEdgelessTemplate } from '../_common/setup.js';
import { effects as commentEffects } from '../comment/effects.js'; import { effects as commentEffects } from '../comment/effects.js';
@@ -22,8 +22,8 @@ import { mountDefaultDocEditor } from './utils/setup-playground';
import { prepareTestApp } from './utils/test'; import { prepareTestApp } from './utils/test';
registerStoreSpecs(); registerStoreSpecs();
blocksEffects(); itEffects();
presetsEffects(); const storeManager = getTestStoreManager();
commentEffects(); commentEffects();
async function main() { async function main() {
@@ -34,7 +34,7 @@ async function main() {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search);
const room = params.get('room') ?? Math.random().toString(16).slice(2, 8); const room = params.get('room') ?? Math.random().toString(16).slice(2, 8);
const isE2E = room.startsWith('playwright'); const isE2E = room.startsWith('playwright');
const collection = createStarterDocCollection(); const collection = createStarterDocCollection(storeManager);
if (isE2E) { if (isE2E) {
Object.defineProperty(window, '$blocksuite', { Object.defineProperty(window, '$blocksuite', {

View File

@@ -1,5 +1,5 @@
import type { StoreExtensionManager } from '@blocksuite/affine/ext-loader';
import { AffineSchemas } from '@blocksuite/affine/schemas'; import { AffineSchemas } from '@blocksuite/affine/schemas';
import { SpecProvider } from '@blocksuite/affine/shared/utils';
import { nanoid, Schema, Transformer } from '@blocksuite/affine/store'; import { nanoid, Schema, Transformer } from '@blocksuite/affine/store';
import { import {
createAutoIncrementIdGenerator, createAutoIncrementIdGenerator,
@@ -23,7 +23,9 @@ const room = params.get('room');
const isE2E = room?.startsWith('playwright'); const isE2E = room?.startsWith('playwright');
const blobSourceArgs = (params.get('blobSource') ?? '').split(','); const blobSourceArgs = (params.get('blobSource') ?? '').split(',');
export function createStarterDocCollection() { export function createStarterDocCollection(
storeExtensionManager: StoreExtensionManager
) {
const collectionId = room ?? 'starter'; const collectionId = room ?? 'starter';
const schema = new Schema(); const schema = new Schema();
schema.register(AffineSchemas); schema.register(AffineSchemas);
@@ -56,7 +58,7 @@ export function createStarterDocCollection() {
blobSources, blobSources,
}; };
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.storeExtensions = SpecProvider._.getSpec('store').value; collection.storeExtensions = storeExtensionManager.get('store');
collection.start(); collection.start();
// debug info // debug info

View File

@@ -1,7 +1,3 @@
import {
EdgelessEditorBlockSpecs,
PageEditorBlockSpecs,
} from '@blocksuite/affine/extensions';
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference'; import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
import { import {
CommunityCanvasTextFonts, CommunityCanvasTextFonts,
@@ -13,6 +9,7 @@ import {
} from '@blocksuite/affine/shared/services'; } from '@blocksuite/affine/shared/services';
import type { ExtensionType, Store, Workspace } from '@blocksuite/affine/store'; import type { ExtensionType, Store, Workspace } from '@blocksuite/affine/store';
import { type TestAffineEditorContainer } from '@blocksuite/integration-test'; import { type TestAffineEditorContainer } from '@blocksuite/integration-test';
import { getTestViewManager } from '@blocksuite/integration-test/view';
import { import {
mockDocModeService, mockDocModeService,
@@ -20,6 +17,8 @@ import {
mockParseDocUrlService, mockParseDocUrlService,
} from '../../_common/mock-services'; } from '../../_common/mock-services';
const viewManager = getTestViewManager();
export function getTestCommonExtensions( export function getTestCommonExtensions(
editor: TestAffineEditorContainer editor: TestAffineEditorContainer
): ExtensionType[] { ): ExtensionType[] {
@@ -48,8 +47,8 @@ export function createTestEditor(store: Store, workspace: Workspace) {
editor.doc = store; editor.doc = store;
const defaultExtensions = getTestCommonExtensions(editor); const defaultExtensions = getTestCommonExtensions(editor);
editor.pageSpecs = [...PageEditorBlockSpecs, ...defaultExtensions]; editor.pageSpecs = [...viewManager.get('page'), ...defaultExtensions];
editor.edgelessSpecs = [...EdgelessEditorBlockSpecs, ...defaultExtensions]; editor.edgelessSpecs = [...viewManager.get('edgeless'), ...defaultExtensions];
editor.std editor.std
.get(RefNodeSlotsProvider) .get(RefNodeSlotsProvider)

View File

@@ -3,10 +3,9 @@ import {
DocModeProvider, DocModeProvider,
FeatureFlagService, FeatureFlagService,
} from '@blocksuite/affine/shared/services'; } from '@blocksuite/affine/shared/services';
import { type SpecBuilder } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { BaseSelection } from '@blocksuite/affine/store'; import type { BaseSelection, ExtensionType } from '@blocksuite/affine/store';
import { ArrowDownBigIcon as ArrowDownIcon } from '@blocksuite/icons/lit'; import { ArrowDownBigIcon as ArrowDownIcon } from '@blocksuite/icons/lit';
import { css, html, nothing, type PropertyValues } from 'lit'; import { css, html, nothing, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js'; import { property, query, state } from 'lit/decorators.js';
@@ -157,7 +156,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
accessor updateContext!: (context: Partial<ChatContextValue>) => void; accessor updateContext!: (context: Partial<ChatContextValue>) => void;
@property({ attribute: false }) @property({ attribute: false })
accessor previewSpecBuilder!: SpecBuilder; accessor extensions!: ExtensionType[];
@query('.chat-panel-messages-container') @query('.chat-panel-messages-container')
accessor messagesContainer: HTMLDivElement | null = null; accessor messagesContainer: HTMLDivElement | null = null;
@@ -271,7 +270,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
.isLast=${isLast} .isLast=${isLast}
.status=${isLast ? status : 'idle'} .status=${isLast ? status : 'idle'}
.error=${isLast ? error : null} .error=${isLast ? error : null}
.previewSpecBuilder=${this.previewSpecBuilder} .extensions=${this.extensions}
.getSessionId=${this.getSessionId} .getSessionId=${this.getSessionId}
.retry=${() => this.retry()} .retry=${() => this.retry()}
></chat-message-assistant>`; ></chat-message-assistant>`;

View File

@@ -1,7 +1,7 @@
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import type { SpecBuilder } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import { html } from 'lit'; import { html } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
@@ -18,13 +18,13 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
accessor state: 'finished' | 'generating' = 'finished'; accessor state: 'finished' | 'generating' = 'finished';
@property({ attribute: false }) @property({ attribute: false })
accessor previewSpecBuilder!: SpecBuilder; accessor extensions!: ExtensionType[];
protected override render() { protected override render() {
const { text, host } = this; const { text, host } = this;
return html`${createTextRenderer(host, { return html`${createTextRenderer(host, {
customHeading: true, customHeading: true,
extensions: this.previewSpecBuilder.value, extensions: this.extensions,
})(text, this.state)}`; })(text, this.state)}`;
} }
} }

View File

@@ -2,10 +2,9 @@ import './chat-panel-messages';
import type { ContextEmbedStatus } from '@affine/graphql'; import type { ContextEmbedStatus } from '@affine/graphql';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import type { SpecBuilder } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { Store } from '@blocksuite/affine/store'; import type { ExtensionType, Store } from '@blocksuite/affine/store';
import { HelpIcon } from '@blocksuite/icons/lit'; import { HelpIcon } from '@blocksuite/icons/lit';
import { type Signal, signal } from '@preact/signals-core'; import { type Signal, signal } from '@preact/signals-core';
import { css, html, type PropertyValues } from 'lit'; import { css, html, type PropertyValues } from 'lit';
@@ -208,7 +207,7 @@ export class ChatPanel extends SignalWatcher(
accessor docDisplayConfig!: DocDisplayConfig; accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor previewSpecBuilder!: SpecBuilder; accessor extensions!: ExtensionType[];
@state() @state()
accessor isLoading = false; accessor isLoading = false;
@@ -401,7 +400,7 @@ export class ChatPanel extends SignalWatcher(
.updateContext=${this.updateContext} .updateContext=${this.updateContext}
.host=${this.host} .host=${this.host}
.isLoading=${this.isLoading} .isLoading=${this.isLoading}
.previewSpecBuilder=${this.previewSpecBuilder} .extensions=${this.extensions}
></chat-panel-messages> ></chat-panel-messages>
<ai-chat-composer <ai-chat-composer
.host=${this.host} .host=${this.host}

View File

@@ -5,6 +5,7 @@ import { WithDisposable } from '@blocksuite/affine/global/lit';
import { isInsidePageEditor } from '@blocksuite/affine/shared/utils'; import { isInsidePageEditor } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import { css, html, nothing } from 'lit'; import { css, html, nothing } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
@@ -44,7 +45,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
accessor error: AIError | null = null; accessor error: AIError | null = null;
@property({ attribute: false }) @property({ attribute: false })
accessor previewSpecBuilder: any; accessor extensions!: ExtensionType[];
@property({ attribute: false }) @property({ attribute: false })
accessor getSessionId!: () => Promise<string | undefined>; accessor getSessionId!: () => Promise<string | undefined>;
@@ -90,7 +91,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
.host=${host} .host=${host}
.text=${item.content} .text=${item.content}
.state=${state} .state=${state}
.previewSpecBuilder=${this.previewSpecBuilder} .extensions=${this.extensions}
></chat-content-rich-text> ></chat-content-rich-text>
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing} ${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
${this.renderEditorActions()} ${this.renderEditorActions()}

View File

@@ -43,6 +43,18 @@ import type {
AffineAIPanelWidgetConfig, AffineAIPanelWidgetConfig,
} from '../widgets/ai-panel/type'; } from '../widgets/ai-panel/type';
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
...PageEditorBlockSpecs,
{
setup: di => {
di.override(
BlockViewIdentifier('affine:page'),
() => literal`affine-page-root`
);
},
},
];
const customHeadingStyles = css` const customHeadingStyles = css`
.custom-heading { .custom-heading {
.h1 { .h1 {
@@ -91,18 +103,6 @@ export type TextRendererOptions = {
testId?: string; testId?: string;
}; };
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
...PageEditorBlockSpecs,
{
setup: di => {
di.override(
BlockViewIdentifier('affine:page'),
() => literal`affine-page-root`
);
},
},
];
// todo: refactor it for more general purpose usage instead of AI only? // todo: refactor it for more general purpose usage instead of AI only?
export class TextRenderer extends WithDisposable(ShadowlessElement) { export class TextRenderer extends WithDisposable(ShadowlessElement) {
static override styles = css` static override styles = css`

View File

@@ -7,7 +7,7 @@ import type { ExtensionType } from '@blocksuite/affine/store';
import { setupCodeToolbarAIEntry } from '../entries/code-toolbar/setup-code-toolbar'; import { setupCodeToolbarAIEntry } from '../entries/code-toolbar/setup-code-toolbar';
class AICodeBlockWatcher extends LifeCycleWatcher { export class AICodeBlockWatcher extends LifeCycleWatcher {
static override key = 'ai-code-block-watcher'; static override key = 'ai-code-block-watcher';
override mounted() { override mounted() {

View File

@@ -47,7 +47,7 @@ export function createAIEdgelessRootBlockSpec(
]; ];
} }
function getAIEdgelessRootWatcher(framework: FrameworkProvider) { export function getAIEdgelessRootWatcher(framework: FrameworkProvider) {
class AIEdgelessRootWatcher extends LifeCycleWatcher { class AIEdgelessRootWatcher extends LifeCycleWatcher {
static override key = 'ai-edgeless-root-watcher'; static override key = 'ai-edgeless-root-watcher';

View File

@@ -16,7 +16,7 @@ import {
} from '../widgets/ai-panel/ai-panel'; } from '../widgets/ai-panel/ai-panel';
import { AiSlashMenuConfigExtension } from './ai-slash-menu'; import { AiSlashMenuConfigExtension } from './ai-slash-menu';
function getAIPageRootWatcher(framework: FrameworkProvider) { export function getAIPageRootWatcher(framework: FrameworkProvider) {
class AIPageRootWatcher extends LifeCycleWatcher { class AIPageRootWatcher extends LifeCycleWatcher {
static override key = 'ai-page-root-watcher'; static override key = 'ai-page-root-watcher';

View File

@@ -1,6 +1,6 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace'; import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { SpecProvider } from '@blocksuite/affine/shared/utils';
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/std'; import { BlockStdScope, type EditorHost } from '@blocksuite/affine/std';
import type { Store } from '@blocksuite/affine/store'; import type { Store } from '@blocksuite/affine/store';
import { css, html, LitElement, nothing } from 'lit'; import { css, html, LitElement, nothing } from 'lit';
@@ -91,6 +91,12 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
}); });
} }
protected _getExtensions() {
return this.host.std
.get(ViewExtensionManagerIdentifier)
.get('preview-edgeless');
}
protected override render() { protected override render() {
return html`<style> return html`<style>
.slides-container { .slides-container {
@@ -205,7 +211,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
> >
${new BlockStdScope({ ${new BlockStdScope({
store: this._doc, store: this._doc,
extensions: SpecProvider._.getSpec('preview:edgeless').value, extensions: this._getExtensions(),
}).render()} }).render()}
</div> </div>
<div class="mask"></div> <div class="mask"></div>

View File

@@ -4,12 +4,13 @@ import {
EdgelessCRUDIdentifier, EdgelessCRUDIdentifier,
getSurfaceBlock, getSurfaceBlock,
} from '@blocksuite/affine/blocks/surface'; } from '@blocksuite/affine/blocks/surface';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
import { ConnectorMode } from '@blocksuite/affine/model'; import { ConnectorMode } from '@blocksuite/affine/model';
import { import {
DocModeProvider, DocModeProvider,
TelemetryProvider, TelemetryProvider,
} from '@blocksuite/affine/shared/services'; } from '@blocksuite/affine/shared/services';
import type { Signal, SpecBuilder } from '@blocksuite/affine/shared/utils'; import type { Signal } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { signal } from '@preact/signals-core'; import { signal } from '@preact/signals-core';
import { html, LitElement, nothing } from 'lit'; import { html, LitElement, nothing } from 'lit';
@@ -437,9 +438,11 @@ export class AIChatBlockPeekView extends LitElement {
override connectedCallback() { override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this._textRendererOptions = { const extensions = this.host.std
extensions: this.previewSpecBuilder.value, .get(ViewExtensionManagerIdentifier)
}; .get('preview-page');
this._textRendererOptions = { extensions };
this._historyMessages = this._deserializeHistoryChatMessages( this._historyMessages = this._deserializeHistoryChatMessages(
this.historyMessagesString this.historyMessagesString
); );
@@ -529,9 +532,6 @@ export class AIChatBlockPeekView extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
accessor host!: EditorHost; accessor host!: EditorHost;
@property({ attribute: false })
accessor previewSpecBuilder!: SpecBuilder;
@property({ attribute: false }) @property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig; accessor networkSearchConfig!: AINetworkSearchConfig;
@@ -566,7 +566,6 @@ declare global {
export const AIChatBlockPeekViewTemplate = ( export const AIChatBlockPeekViewTemplate = (
blockModel: AIChatBlockModel, blockModel: AIChatBlockModel,
host: EditorHost, host: EditorHost,
previewSpecBuilder: SpecBuilder,
docDisplayConfig: DocDisplayConfig, docDisplayConfig: DocDisplayConfig,
searchMenuConfig: SearchMenuConfig, searchMenuConfig: SearchMenuConfig,
networkSearchConfig: AINetworkSearchConfig networkSearchConfig: AINetworkSearchConfig
@@ -574,7 +573,6 @@ export const AIChatBlockPeekViewTemplate = (
return html`<ai-chat-block-peek-view return html`<ai-chat-block-peek-view
.blockModel=${blockModel} .blockModel=${blockModel}
.host=${host} .host=${host}
.previewSpecBuilder=${previewSpecBuilder}
.networkSearchConfig=${networkSearchConfig} .networkSearchConfig=${networkSearchConfig}
.docDisplayConfig=${docDisplayConfig} .docDisplayConfig=${docDisplayConfig}
.searchMenuConfig=${searchMenuConfig} .searchMenuConfig=${searchMenuConfig}

View File

@@ -41,11 +41,11 @@ import {
AffineSharedPageReference, AffineSharedPageReference,
} from '../../components/affine/reference-link'; } from '../../components/affine/reference-link';
import { LitTextRenderer } from '../ai/components/text-renderer'; import { LitTextRenderer } from '../ai/components/text-renderer';
import { enableEditorExtension } from '../extensions/entry/enable-editor';
import { import {
patchReferenceRenderer, patchReferenceRenderer,
type ReferenceReactRenderer, type ReferenceReactRenderer,
} from '../extensions/reference-renderer'; } from '../extensions/reference-renderer';
import { getViewManager } from '../manager/migrating-view';
import * as styles from './bi-directional-link-panel.css'; import * as styles from './bi-directional-link-panel.css';
const PREFIX = 'bi-directional-link-panel-collapse:'; const PREFIX = 'bi-directional-link-panel-collapse:';
@@ -163,9 +163,9 @@ const usePreviewExtensions = () => {
const enableAI = useEnableAI(); const enableAI = useEnableAI();
const extensions = useMemo(() => { const extensions = useMemo(() => {
const specs = enableEditorExtension(framework, 'page', enableAI); const manager = getViewManager(framework, enableAI);
specs.extend([patchReferenceRenderer(reactToLit, referenceRenderer)]); const specs = manager.get('preview-page');
return specs.value; return [...specs, patchReferenceRenderer(reactToLit, referenceRenderer)];
}, [reactToLit, referenceRenderer, framework, enableAI]); }, [reactToLit, referenceRenderer, framework, enableAI]);
return [extensions, portals] as const; return [extensions, portals] as const;

View File

@@ -1,10 +1,11 @@
import { registerAIEffects } from '@affine/core/blocksuite/ai/effects'; import { registerAIEffects } from '@affine/core/blocksuite/ai/effects';
import { editorEffects } from '@affine/core/blocksuite/editors'; import { editorEffects } from '@affine/core/blocksuite/editors';
import { effects as bsEffects } from '@blocksuite/affine/effects'; import type * as EffectType from '@blocksuite/affine/effects';
declare type _GLOBAL_ = typeof EffectType;
import { registerTemplates } from './register-templates'; import { registerTemplates } from './register-templates';
bsEffects();
editorEffects(); editorEffects();
registerAIEffects(); registerAIEffects();
registerTemplates(); registerTemplates();

View File

@@ -62,7 +62,6 @@ import { EdgelessClipboardAIChatConfig } from '../extensions/edgeless-clipboard'
import { patchForClipboardInElectron } from '../extensions/electron-clipboard'; import { patchForClipboardInElectron } from '../extensions/electron-clipboard';
import { enableEditorExtension } from '../extensions/entry/enable-editor'; import { enableEditorExtension } from '../extensions/entry/enable-editor';
import { enableMobileExtension } from '../extensions/entry/enable-mobile'; import { enableMobileExtension } from '../extensions/entry/enable-mobile';
import { enablePreviewExtension } from '../extensions/entry/enable-preview';
import { patchForEdgelessNoteConfig } from '../extensions/note-config'; import { patchForEdgelessNoteConfig } from '../extensions/note-config';
import { patchNotificationService } from '../extensions/notification-service'; import { patchNotificationService } from '../extensions/notification-service';
import { patchOpenDocExtension } from '../extensions/open-doc'; import { patchOpenDocExtension } from '../extensions/open-doc';
@@ -140,10 +139,6 @@ const usePatchSpecs = (mode: DocMode) => {
}; };
}, [workspaceService]); }, [workspaceService]);
useMemo(() => {
enablePreviewExtension(framework);
}, [framework]);
const confirmModal = useConfirmModal(); const confirmModal = useConfirmModal();
const enableAI = useEnableAI(); const enableAI = useEnableAI();
@@ -161,9 +156,9 @@ const usePatchSpecs = (mode: DocMode) => {
); );
const patchedSpecs = useMemo(() => { const patchedSpecs = useMemo(() => {
const builder = enableEditorExtension(framework, mode, enableAI); let extensions = enableEditorExtension(framework, mode, enableAI);
builder.extend( extensions = extensions.concat(
[ [
patchReferenceRenderer(reactToLit, referenceRenderer), patchReferenceRenderer(reactToLit, referenceRenderer),
patchForEdgelessNoteConfig(framework, reactToLit, insidePeekView), patchForEdgelessNoteConfig(framework, reactToLit, insidePeekView),
@@ -190,17 +185,20 @@ const usePatchSpecs = (mode: DocMode) => {
); );
if (enablePDFEmbedPreview) { if (enablePDFEmbedPreview) {
builder.extend([patchForPDFEmbedView(reactToLit)]); extensions = extensions.concat([patchForPDFEmbedView(reactToLit)]);
} }
if (BUILD_CONFIG.isMobileEdition) { if (BUILD_CONFIG.isMobileEdition) {
enableMobileExtension(builder, framework); extensions = enableMobileExtension(extensions, framework);
}
if (BUILD_CONFIG.isElectron) {
builder.extend([patchForClipboardInElectron(framework)].flat());
} }
return builder.value; if (BUILD_CONFIG.isElectron) {
extensions = extensions.concat(
[patchForClipboardInElectron(framework)].flat()
);
}
return extensions;
}, [ }, [
framework, framework,
mode, mode,

View File

@@ -1,19 +1,13 @@
import { enableAIExtension } from '@affine/core/blocksuite/ai'; import type { ExtensionType } from '@blocksuite/affine/store';
import { enableAffineExtension } from '@affine/core/blocksuite/extensions';
import {
type SpecBuilder,
SpecProvider,
} from '@blocksuite/affine/shared/utils';
import { type FrameworkProvider } from '@toeverything/infra'; import { type FrameworkProvider } from '@toeverything/infra';
import { getViewManager } from '../../manager/migrating-view';
export function enableEditorExtension( export function enableEditorExtension(
framework: FrameworkProvider, framework: FrameworkProvider,
mode: 'edgeless' | 'page', mode: 'edgeless' | 'page',
enableAI: boolean enableAI: boolean
): SpecBuilder { ): ExtensionType[] {
const spec = SpecProvider._.getSpec(mode); const manager = getViewManager(framework, enableAI);
enableAffineExtension(spec, framework); return manager.get(mode);
enableAIExtension(spec, framework, enableAI);
return spec;
} }

View File

@@ -11,7 +11,6 @@ import {
VirtualKeyboardProvider as BSVirtualKeyboardProvider, VirtualKeyboardProvider as BSVirtualKeyboardProvider,
type VirtualKeyboardProviderWithAction, type VirtualKeyboardProviderWithAction,
} from '@blocksuite/affine/shared/services'; } from '@blocksuite/affine/shared/services';
import type { SpecBuilder } from '@blocksuite/affine/shared/utils';
import { type BlockStdScope, LifeCycleWatcher } from '@blocksuite/affine/std'; import { type BlockStdScope, LifeCycleWatcher } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store'; import type { ExtensionType } from '@blocksuite/affine/store';
import { SlashMenuExtension } from '@blocksuite/affine/widgets/slash-menu'; import { SlashMenuExtension } from '@blocksuite/affine/widgets/slash-menu';
@@ -112,16 +111,26 @@ function KeyboardToolbarExtension(framework: FrameworkProvider): ExtensionType {
} }
export function enableMobileExtension( export function enableMobileExtension(
specBuilder: SpecBuilder, extensions: ExtensionType[],
framework: FrameworkProvider framework: FrameworkProvider
): void { ): ExtensionType[] {
specBuilder.omit(codeToolbarWidget); const next = extensions.filter(extension => {
specBuilder.omit(toolbarWidget); if (extension === codeToolbarWidget) {
specBuilder.omit(SlashMenuExtension); return false;
specBuilder.extend([ }
if (extension === toolbarWidget) {
return false;
}
if (extension === SlashMenuExtension) {
return false;
}
return true;
});
next.push(
MobileSpecsPatches, MobileSpecsPatches,
KeyboardToolbarExtension(framework), KeyboardToolbarExtension(framework),
mobileParagraphConfig, mobileParagraphConfig,
mobileCodeConfig, mobileCodeConfig
]); );
return next;
} }

View File

@@ -27,7 +27,7 @@ import { getFontConfigExtension } from '../font-config';
import { patchPeekViewService } from '../peek-view-service'; import { patchPeekViewService } from '../peek-view-service';
import { getThemeExtension } from '../theme'; import { getThemeExtension } from '../theme';
function getPagePreviewThemeExtension(framework: FrameworkProvider) { export function getPagePreviewThemeExtension(framework: FrameworkProvider) {
class AffinePagePreviewThemeExtension class AffinePagePreviewThemeExtension
extends LifeCycleWatcher extends LifeCycleWatcher
implements ThemeExtension implements ThemeExtension

View File

@@ -0,0 +1,26 @@
import {
type StoreExtensionContext,
StoreExtensionManager,
StoreExtensionProvider,
} from '@blocksuite/affine/ext-loader';
import { MigratingStoreExtension } from '@blocksuite/affine/extensions/store';
import { AIChatBlockSchemaExtension } from '../ai/blocks/ai-chat-block/model';
class MigratingAffineStoreExtension extends StoreExtensionProvider {
override name = 'affine-store-extensions';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(AIChatBlockSchemaExtension);
}
}
const manager = new StoreExtensionManager([
MigratingAffineStoreExtension,
MigratingStoreExtension,
]);
export function getStoreManager() {
return manager;
}

View File

@@ -0,0 +1,144 @@
import { PeekViewService } from '@affine/core/modules/peek-view';
import { ParagraphBlockConfigExtension } from '@blocksuite/affine/blocks/paragraph';
import {
type ViewExtensionContext,
ViewExtensionManager,
ViewExtensionProvider,
} from '@blocksuite/affine/ext-loader';
import { MigratingViewExtension } from '@blocksuite/affine/extensions/view';
import { ToolbarModuleExtension } from '@blocksuite/affine/shared/services';
import { BlockFlavourIdentifier } from '@blocksuite/affine/std';
import { FrameworkProvider } from '@toeverything/infra';
import { z } from 'zod';
import { toolbarAIEntryConfig } from '../ai';
import { AIChatBlockSpec } from '../ai/blocks';
import { AITranscriptionBlockSpec } from '../ai/blocks/ai-chat-block/ai-transcription-block';
import { edgelessToolbarAIEntryConfig } from '../ai/entries/edgeless';
import { imageToolbarAIEntryConfig } from '../ai/entries/image-toolbar/setup-image-toolbar';
import { AICodeBlockWatcher } from '../ai/extensions/ai-code';
import { getAIEdgelessRootWatcher } from '../ai/extensions/ai-edgeless-root';
import { getAIPageRootWatcher } from '../ai/extensions/ai-page-root';
import { AiSlashMenuConfigExtension } from '../ai/extensions/ai-slash-menu';
import { CopilotTool } from '../ai/tool/copilot-tool';
import { aiPanelWidget } from '../ai/widgets/ai-panel/ai-panel';
import { edgelessCopilotWidget } from '../ai/widgets/edgeless-copilot';
import { buildDocDisplayMetaExtension } from '../extensions/display-meta';
import { getEditorConfigExtension } from '../extensions/editor-config';
import { getPagePreviewThemeExtension } from '../extensions/entry/enable-preview';
import { getFontConfigExtension } from '../extensions/font-config';
import { patchPeekViewService } from '../extensions/peek-view-service';
import { getTelemetryExtension } from '../extensions/telemetry';
import { getThemeExtension } from '../extensions/theme';
const optionsSchema = z.object({
enableAI: z.boolean().optional(),
framework: z.instanceof(FrameworkProvider),
});
class MigratingAffineViewExtension extends ViewExtensionProvider<
z.infer<typeof optionsSchema>
> {
override name = 'affine-view-extensions';
override schema = optionsSchema;
override setup(
context: ViewExtensionContext,
options?: z.infer<typeof optionsSchema>
) {
super.setup(context);
const { framework, enableAI } = options || {};
if (framework) {
context.register([
getThemeExtension(framework),
getFontConfigExtension(),
buildDocDisplayMetaExtension(framework),
]);
if (context.scope === 'page' || context.scope === 'edgeless') {
context.register(getTelemetryExtension());
context.register(getEditorConfigExtension(framework));
}
if (
context.scope === 'preview-page' ||
context.scope === 'preview-edgeless'
) {
context.register(getPagePreviewThemeExtension(framework));
context.register(patchPeekViewService(framework.get(PeekViewService)));
}
if (enableAI) {
context.register(AIChatBlockSpec);
context.register(AITranscriptionBlockSpec);
context.register(
[
AICodeBlockWatcher,
ToolbarModuleExtension({
id: BlockFlavourIdentifier('custom:affine:image'),
config: imageToolbarAIEntryConfig(),
}),
ParagraphBlockConfigExtension({
getPlaceholder: model => {
const placeholders = {
text: "Type '/' for commands, 'space' for AI",
h1: 'Heading 1',
h2: 'Heading 2',
h3: 'Heading 3',
h4: 'Heading 4',
h5: 'Heading 5',
h6: 'Heading 6',
quote: '',
};
return placeholders[model.props.type];
},
}),
].flat()
);
if (context.scope === 'edgeless') {
context.register([
CopilotTool,
aiPanelWidget,
edgelessCopilotWidget,
getAIEdgelessRootWatcher(framework),
// In note
ToolbarModuleExtension({
id: BlockFlavourIdentifier('custom:affine:note'),
config: toolbarAIEntryConfig(),
}),
ToolbarModuleExtension({
id: BlockFlavourIdentifier('custom:affine:surface:*'),
config: edgelessToolbarAIEntryConfig(),
}),
AiSlashMenuConfigExtension(),
]);
}
if (context.scope === 'page') {
context.register([
aiPanelWidget,
getAIPageRootWatcher(framework),
ToolbarModuleExtension({
id: BlockFlavourIdentifier('custom:affine:note'),
config: toolbarAIEntryConfig(),
}),
AiSlashMenuConfigExtension(),
]);
}
}
}
}
}
const manager = new ViewExtensionManager([
MigratingViewExtension,
MigratingAffineViewExtension,
]);
export function getViewManager(
framework: FrameworkProvider,
enableAI: boolean
) {
manager.configure(MigratingAffineViewExtension, {
framework,
enableAI,
});
return manager;
}

View File

@@ -1,10 +1,10 @@
import { Skeleton } from '@affine/component'; import { Skeleton } from '@affine/component';
import { getViewManager } from '@affine/core/blocksuite/manager/migrating-view';
import type { EditorSettingSchema } from '@affine/core/modules/editor-setting'; import type { EditorSettingSchema } from '@affine/core/modules/editor-setting';
import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { EdgelessCRUDIdentifier } from '@blocksuite/affine/blocks/surface'; import { EdgelessCRUDIdentifier } from '@blocksuite/affine/blocks/surface';
import { Bound } from '@blocksuite/affine/global/gfx'; import { Bound } from '@blocksuite/affine/global/gfx';
import { ViewportElementExtension } from '@blocksuite/affine/shared/services'; import { ViewportElementExtension } from '@blocksuite/affine/shared/services';
import { SpecProvider } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { BlockStdScope } from '@blocksuite/affine/std'; import { BlockStdScope } from '@blocksuite/affine/std';
import { import {
@@ -14,7 +14,7 @@ import {
import type { Block, Store } from '@blocksuite/affine/store'; import type { Block, Store } from '@blocksuite/affine/store';
import { useFramework } from '@toeverything/infra'; import { useFramework } from '@toeverything/infra';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { map, pairwise } from 'rxjs'; import { map, pairwise } from 'rxjs';
import { import {
@@ -55,6 +55,13 @@ export const EdgelessSnapshot = (props: Props) => {
const framework = useFramework(); const framework = useFramework();
const { editorSetting } = framework.get(EditorSettingService); const { editorSetting } = framework.get(EditorSettingService);
const extensions = useMemo(() => {
const manager = getViewManager(framework, false);
return manager
.get('preview-edgeless')
.concat([ViewportElementExtension('.ref-viewport')]);
}, [framework]);
const updateElements = useCallback(() => { const updateElements = useCallback(() => {
const editorHost = editorHostRef.current; const editorHost = editorHostRef.current;
const doc = docRef.current; const doc = docRef.current;
@@ -76,9 +83,7 @@ export const EdgelessSnapshot = (props: Props) => {
const editorHost = new BlockStdScope({ const editorHost = new BlockStdScope({
store: doc, store: doc,
extensions: SpecProvider._.getSpec('preview:edgeless').extend([ extensions,
ViewportElementExtension('.ref-viewport'),
]).value,
}).render(); }).render();
docRef.current = doc; docRef.current = doc;
editorHostRef.current?.remove(); editorHostRef.current?.remove();
@@ -115,7 +120,7 @@ export const EdgelessSnapshot = (props: Props) => {
// append to dom node // append to dom node
wrapperRef.current.append(editorHost); wrapperRef.current.append(editorHost);
}, [docName, firstUpdate, updateElements]); }, [docName, extensions, firstUpdate, updateElements]);
useEffect(() => { useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@@ -2,12 +2,10 @@ import { ChatPanel } from '@affine/core/blocksuite/ai';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor'; import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config'; import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference'; import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
import { DocModeProvider } from '@blocksuite/affine/shared/services'; import { DocModeProvider } from '@blocksuite/affine/shared/services';
import { import { createSignalFromObservable } from '@blocksuite/affine/shared/utils';
createSignalFromObservable,
SpecProvider,
} from '@blocksuite/affine/shared/utils';
import { useFramework } from '@toeverything/infra'; import { useFramework } from '@toeverything/infra';
import { forwardRef, useEffect, useRef } from 'react'; import { forwardRef, useEffect, useRef } from 'react';
@@ -69,8 +67,9 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
chatPanelRef.current.docDisplayConfig = docDisplayConfig; chatPanelRef.current.docDisplayConfig = docDisplayConfig;
chatPanelRef.current.searchMenuConfig = searchMenuConfig; chatPanelRef.current.searchMenuConfig = searchMenuConfig;
chatPanelRef.current.networkSearchConfig = networkSearchConfig; chatPanelRef.current.networkSearchConfig = networkSearchConfig;
chatPanelRef.current.previewSpecBuilder = chatPanelRef.current.extensions = editor.host.std
SpecProvider._.getSpec('preview:page'); .get(ViewExtensionManagerIdentifier)
.get('preview-page');
containerRef.current?.append(chatPanelRef.current); containerRef.current?.append(chatPanelRef.current);
} else { } else {

View File

@@ -2,7 +2,6 @@ import { toReactNode } from '@affine/component';
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/ai'; import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/ai';
import type { AIChatBlockModel } from '@affine/core/blocksuite/ai/blocks/ai-chat-block/model/ai-chat-model'; import type { AIChatBlockModel } from '@affine/core/blocksuite/ai/blocks/ai-chat-block/model/ai-chat-model';
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config'; import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
import { SpecProvider } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { useMemo } from 'react'; import { useMemo } from 'react';
@@ -18,11 +17,9 @@ export const AIChatBlockPeekView = ({
const { docDisplayConfig, searchMenuConfig, networkSearchConfig } = const { docDisplayConfig, searchMenuConfig, networkSearchConfig } =
useAIChatConfig(); useAIChatConfig();
return useMemo(() => { return useMemo(() => {
const previewSpecBuilder = SpecProvider._.getSpec('preview:page');
const template = AIChatBlockPeekViewTemplate( const template = AIChatBlockPeekViewTemplate(
model, model,
host, host,
previewSpecBuilder,
docDisplayConfig, docDisplayConfig,
searchMenuConfig, searchMenuConfig,
networkSearchConfig networkSearchConfig

View File

@@ -1,4 +1,4 @@
import { SpecProvider } from '@blocksuite/affine/shared/utils'; import { getStoreManager } from '@affine/core/blocksuite/manager/migrating-store';
import { import {
AwarenessStore, AwarenessStore,
type Doc, type Doc,
@@ -294,11 +294,9 @@ export class DocImpl implements Doc {
return this._storeMap.get(key) as Store; return this._storeMap.get(key) as Store;
} }
const storeExtensions = SpecProvider._.getSpec('store'); const storeExtensions = getStoreManager().get('store');
const extensionSet = new Set( const extensionSet = new Set(
storeExtensions.value storeExtensions.concat(extensions ?? []).concat(this.storeExtensions)
.concat(extensions ?? [])
.concat(this.storeExtensions)
); );
const doc = new Store({ const doc = new Store({

View File

@@ -388,6 +388,7 @@ export const PackageList = [
'blocksuite/affine/blocks/frame', 'blocksuite/affine/blocks/frame',
'blocksuite/affine/blocks/surface', 'blocksuite/affine/blocks/surface',
'blocksuite/affine/components', 'blocksuite/affine/components',
'blocksuite/affine/ext-loader',
'blocksuite/affine/inlines/reference', 'blocksuite/affine/inlines/reference',
'blocksuite/affine/model', 'blocksuite/affine/model',
'blocksuite/affine/shared', 'blocksuite/affine/shared',
@@ -465,6 +466,7 @@ export const PackageList = [
'blocksuite/affine/blocks/frame', 'blocksuite/affine/blocks/frame',
'blocksuite/affine/blocks/surface', 'blocksuite/affine/blocks/surface',
'blocksuite/affine/components', 'blocksuite/affine/components',
'blocksuite/affine/ext-loader',
'blocksuite/affine/model', 'blocksuite/affine/model',
'blocksuite/affine/rich-text', 'blocksuite/affine/rich-text',
'blocksuite/affine/shared', 'blocksuite/affine/shared',
@@ -754,6 +756,7 @@ export const PackageList = [
'blocksuite/affine/blocks/paragraph', 'blocksuite/affine/blocks/paragraph',
'blocksuite/affine/blocks/surface', 'blocksuite/affine/blocks/surface',
'blocksuite/affine/components', 'blocksuite/affine/components',
'blocksuite/affine/ext-loader',
'blocksuite/affine/model', 'blocksuite/affine/model',
'blocksuite/affine/shared', 'blocksuite/affine/shared',
'blocksuite/framework/global', 'blocksuite/framework/global',

View File

@@ -2875,6 +2875,7 @@ __metadata:
"@blocksuite/affine-block-frame": "workspace:*" "@blocksuite/affine-block-frame": "workspace:*"
"@blocksuite/affine-block-surface": "workspace:*" "@blocksuite/affine-block-surface": "workspace:*"
"@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-ext-loader": "workspace:*"
"@blocksuite/affine-inline-reference": "workspace:*" "@blocksuite/affine-inline-reference": "workspace:*"
"@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-shared": "workspace:*" "@blocksuite/affine-shared": "workspace:*"
@@ -3027,6 +3028,7 @@ __metadata:
"@blocksuite/affine-block-frame": "workspace:*" "@blocksuite/affine-block-frame": "workspace:*"
"@blocksuite/affine-block-surface": "workspace:*" "@blocksuite/affine-block-surface": "workspace:*"
"@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-ext-loader": "workspace:*"
"@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-rich-text": "workspace:*"
"@blocksuite/affine-shared": "workspace:*" "@blocksuite/affine-shared": "workspace:*"
@@ -3598,6 +3600,7 @@ __metadata:
"@blocksuite/affine-block-paragraph": "workspace:*" "@blocksuite/affine-block-paragraph": "workspace:*"
"@blocksuite/affine-block-surface": "workspace:*" "@blocksuite/affine-block-surface": "workspace:*"
"@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-ext-loader": "workspace:*"
"@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-shared": "workspace:*" "@blocksuite/affine-shared": "workspace:*"
"@blocksuite/global": "workspace:*" "@blocksuite/global": "workspace:*"