feat(editor): root block extension (#12063)

Closes: BS-3202

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

- **New Features**
  - Introduced new root block store and view extensions, enhancing modularity and integration options for root block functionality.
  - Exported the EdgelessLocker class for broader usage.

- **Improvements**
  - Updated export paths for root block modules, enabling clearer and more flexible module access.
  - Enhanced view extension setup to better tailor user interface and interactions based on context.

- **Removals**
  - Removed legacy migrating store and view extension logic, streamlining extension management and reducing unused code.
  - Removed multiple deprecated block specifications and common extension collections to simplify the codebase.
  - Deleted AI page root block specification, retaining only lifecycle watcher functionality.

- **Chores**
  - Updated dependencies and project references to support new extension loader integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-04-29 09:15:51 +00:00
parent b5edd7a6bb
commit d6ab958e15
22 changed files with 138 additions and 246 deletions

View File

@@ -21,7 +21,7 @@
"@blocksuite/affine-block-paragraph": "workspace:*",
"@blocksuite/affine-block-surface": "workspace:*",
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-fragment-doc-title": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-gfx-brush": "workspace:*",
"@blocksuite/affine-gfx-connector": "workspace:*",
"@blocksuite/affine-gfx-group": "workspace:*",
@@ -35,8 +35,6 @@
"@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
"@blocksuite/affine-widget-slash-menu": "workspace:*",
"@blocksuite/affine-widget-toolbar": "workspace:*",
"@blocksuite/data-view": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.12",
@@ -47,22 +45,20 @@
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"@types/lodash-es": "^4.17.12",
"@types/mdast": "^4.0.4",
"@vanilla-extract/css": "^1.17.0",
"dompurify": "^3.2.4",
"fflate": "^0.8.2",
"html2canvas": "^1.4.1",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
"simple-xml-to-json": "^1.2.2",
"yjs": "^13.6.21",
"zod": "^3.23.8"
},
"exports": {
".": "./src/index.ts",
"./effects": "./src/effects.ts"
"./effects": "./src/effects.ts",
"./store": "./src/store.ts",
"./view": "./src/view.ts"
},
"files": [
"src",

View File

@@ -1,19 +0,0 @@
import { NoteBlockSchema } from '@blocksuite/affine-model';
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
import { BlockFlavourIdentifier, FlavourExtension } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store';
import { RootBlockAdapterExtensions } from '../adapters/extension';
import { builtinToolbarConfig } from '../configs/toolbar';
import { fallbackKeymap } from '../keyboard/keymap';
export const CommonSpecs: ExtensionType[] = [
FlavourExtension('affine:page'),
...RootBlockAdapterExtensions,
fallbackKeymap,
ToolbarModuleExtension({
id: BlockFlavourIdentifier(NoteBlockSchema.model.flavour),
config: builtinToolbarConfig,
}),
];

View File

@@ -1,15 +0,0 @@
import type { ExtensionType } from '@blocksuite/store';
import { EdgelessElementToolbarExtension } from './configs/toolbar';
import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
import { AltCloneExtension } from './interact-extensions/clone-ext.js';
export const EdgelessBuiltInManager: ExtensionType[] = [
AltCloneExtension,
EdgelessElementToolbarExtension,
].flat();
export const EdgelessBuiltInSpecs: ExtensionType[] = [
EdgelessRootBlockSpec,
EdgelessBuiltInManager,
].flat();

View File

@@ -1,19 +1,10 @@
import { ViewportElementExtension } from '@blocksuite/affine-shared/services';
import {
BlockViewExtension,
LifeCycleWatcher,
WidgetViewExtension,
} from '@blocksuite/std';
import { LifeCycleWatcher, WidgetViewExtension } from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import type { ExtensionType } from '@blocksuite/store';
import { literal, unsafeStatic } from 'lit/static-html.js';
import { CommonSpecs } from '../common-specs/index.js';
import { EdgelessClipboardController } from './clipboard/clipboard.js';
import { NOTE_SLICER_WIDGET } from './components/note-slicer/index.js';
import { EDGELESS_DRAGGING_AREA_WIDGET } from './components/rects/edgeless-dragging-area-rect.js';
import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selected-rect.js';
import { EdgelessRootService } from './edgeless-root-service.js';
export const edgelessDraggingAreaWidget = WidgetViewExtension(
'affine:page',
@@ -31,7 +22,7 @@ export const edgelessSelectedRectWidget = WidgetViewExtension(
literal`${unsafeStatic(EDGELESS_SELECTED_RECT_WIDGET)}`
);
class EdgelessLocker extends LifeCycleWatcher {
export class EdgelessLocker extends LifeCycleWatcher {
static override key = 'edgeless-locker';
override mounted() {
@@ -39,24 +30,3 @@ class EdgelessLocker extends LifeCycleWatcher {
viewport.locked = true;
}
}
const EdgelessCommonExtension: ExtensionType[] = [
CommonSpecs,
EdgelessRootService,
ViewportElementExtension('.affine-edgeless-viewport'),
].flat();
export const EdgelessRootBlockSpec: ExtensionType[] = [
...EdgelessCommonExtension,
BlockViewExtension('affine:page', literal`affine-edgeless-root`),
edgelessDraggingAreaWidget,
noteSlicerWidget,
edgelessSelectedRectWidget,
EdgelessClipboardController,
];
export const PreviewEdgelessRootBlockSpec: ExtensionType[] = [
...EdgelessCommonExtension,
BlockViewExtension('affine:page', literal`affine-edgeless-root-preview`),
EdgelessLocker,
];

View File

@@ -5,12 +5,9 @@ import type * as ShapeEffect from '@blocksuite/affine-gfx-shape';
export * from './adapters';
export * from './clipboard/index.js';
export * from './common-specs/index.js';
export * from './edgeless/edgeless-builtin-spec.js';
export * from './edgeless/edgeless-root-spec.js';
export * from './edgeless/index.js';
export * from './page/page-root-block.js';
export * from './page/page-root-spec.js';
export * from './preview/preview-root-block.js';
export * from './types.js';
export * from './utils/index.js';

View File

@@ -1,23 +0,0 @@
import { ViewportElementExtension } from '@blocksuite/affine-shared/services';
import { BlockViewExtension } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { PageClipboard } from '../clipboard/page-clipboard.js';
import { CommonSpecs } from '../common-specs/index.js';
const PageCommonExtension: ExtensionType[] = [
CommonSpecs,
ViewportElementExtension('.affine-page-viewport'),
].flat();
export const PageRootBlockSpec: ExtensionType[] = [
...PageCommonExtension,
BlockViewExtension('affine:page', literal`affine-page-root`),
PageClipboard,
].flat();
export const PreviewPageRootBlockSpec: ExtensionType[] = [
...PageCommonExtension,
BlockViewExtension('affine:page', literal`affine-preview-root`),
];

View File

@@ -0,0 +1,17 @@
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { RootBlockSchemaExtension } from '@blocksuite/affine-model';
import { RootBlockAdapterExtensions } from './adapters/extension';
export class RootStoreExtension extends StoreExtensionProvider {
override name = 'affine-root-block';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(RootBlockSchemaExtension);
context.register(RootBlockAdapterExtensions);
}
}

View File

@@ -0,0 +1,101 @@
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { NoteBlockSchema } from '@blocksuite/affine-model';
import {
ToolbarModuleExtension,
ViewportElementExtension,
} from '@blocksuite/affine-shared/services';
import {
BlockFlavourIdentifier,
BlockViewExtension,
FlavourExtension,
} from '@blocksuite/std';
import { literal } from 'lit/static-html.js';
import { PageClipboard, ReadOnlyClipboard } from './clipboard';
import { builtinToolbarConfig } from './configs/toolbar';
import { EdgelessClipboardController, EdgelessRootService } from './edgeless';
import { EdgelessElementToolbarExtension } from './edgeless/configs/toolbar';
import {
edgelessDraggingAreaWidget,
EdgelessLocker,
edgelessSelectedRectWidget,
noteSlicerWidget,
} from './edgeless/edgeless-root-spec';
import { AltCloneExtension } from './edgeless/interact-extensions/clone-ext';
import { effects } from './effects';
import { fallbackKeymap } from './keyboard/keymap';
export class RootViewExtension extends ViewExtensionProvider {
override name = 'affine-root-block';
override effect(): void {
super.effect();
effects();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register([
FlavourExtension('affine:page'),
fallbackKeymap,
ToolbarModuleExtension({
id: BlockFlavourIdentifier(NoteBlockSchema.model.flavour),
config: builtinToolbarConfig,
}),
]);
if (
context.scope === 'preview-page' ||
context.scope === 'preview-edgeless'
) {
context.register(ReadOnlyClipboard);
}
if (this.isEdgeless(context.scope)) {
this._setupEdgeless(context);
return;
}
this._setupPage(context);
}
private readonly _setupPage = (context: ViewExtensionContext) => {
context.register(ViewportElementExtension('.affine-page-viewport'));
if (context.scope === 'preview-page') {
context.register(
BlockViewExtension('affine:page', literal`affine-preview-root`)
);
return;
}
context.register(
BlockViewExtension('affine:page', literal`affine-page-root`)
);
context.register(PageClipboard);
};
private readonly _setupEdgeless = (context: ViewExtensionContext) => {
context.register([
EdgelessRootService,
ViewportElementExtension('.affine-edgeless-viewport'),
]);
if (context.scope === 'preview-edgeless') {
context.register([
BlockViewExtension(
'affine:page',
literal`affine-edgeless-root-preview`
),
EdgelessLocker,
]);
return;
}
context.register([
BlockViewExtension('affine:page', literal`affine-edgeless-root`),
edgelessDraggingAreaWidget,
noteSlicerWidget,
edgelessSelectedRectWidget,
EdgelessClipboardController,
AltCloneExtension,
]);
context.register(EdgelessElementToolbarExtension);
};
}

View File

@@ -18,7 +18,7 @@
{ "path": "../paragraph" },
{ "path": "../surface" },
{ "path": "../../components" },
{ "path": "../../fragments/doc-title" },
{ "path": "../../ext-loader" },
{ "path": "../../gfx/brush" },
{ "path": "../../gfx/connector" },
{ "path": "../../gfx/group" },
@@ -32,8 +32,6 @@
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../widgets/edgeless-toolbar" },
{ "path": "../../widgets/slash-menu" },
{ "path": "../../widgets/toolbar" },
{ "path": "../../data-view" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/std" },