feat(editor): schema extension (#10447)

1. **Major Architectural Change: Schema Management**
   - Moved from `workspace.schema` to `store.schema` throughout the codebase
   - Removed schema property from Workspace and Doc interfaces
   - Added `BlockSchemaExtension` pattern across multiple block types

2. **Block Schema Extensions Added**
   - Added new `BlockSchemaExtension` to numerous block types including:
     - DataView, Surface, Attachment, Bookmark, Code
     - Database, Divider, EdgelessText, Embed blocks (Figma, Github, HTML, etc.)
     - Frame, Image, Latex, List, Note, Paragraph
     - Root, Surface Reference, Table blocks

3. **Import/Export System Updates**
   - Updated import functions to accept `schema` parameter:
     - `importHTMLToDoc`
     - `importHTMLZip`
     - `importMarkdownToDoc`
     - `importMarkdownZip`
     - `importNotionZip`
   - Modified export functions to use new schema pattern

4. **Test Infrastructure Updates**
   - Updated test files to use new schema extensions
   - Modified test document creation to include schema extensions
   - Removed direct schema registration in favor of extensions

5. **Service Layer Changes**
   - Updated various services to use `getAFFiNEWorkspaceSchema()`
   - Modified transformer initialization to use document schema
   - Updated collection initialization patterns

6. **Version Management**
   - Removed version-related properties and methods from:
     - `WorkspaceMetaImpl`
     - `TestMeta`
     - `DocImpl`
   - Removed `blockVersions` and `workspaceVersion/pageVersion`

7. **Store and Extension Updates**
   - Added new store extensions and adapters
   - Updated store initialization patterns
   - Added new schema-related functionality in store extension

This PR represents a significant architectural shift in how schemas are managed, moving from a workspace-centric to a store-centric approach, while introducing a more extensible block schema system through `BlockSchemaExtension`. The changes touch multiple layers of the application including core functionality, services, testing infrastructure, and import/export capabilities.
This commit is contained in:
Saul-Mirone
2025-02-26 11:31:28 +00:00
parent 2732b96d00
commit ce87dcf58e
95 changed files with 655 additions and 490 deletions
@@ -2,7 +2,11 @@ import {
type GfxCommonBlockProps,
GfxCompatible,
} from '@blocksuite/affine/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/affine/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/affine/store';
type AIChatProps = {
messages: string; // JSON string of ChatMessage[]
@@ -33,4 +37,7 @@ export const AIChatBlockSchema = defineBlockSchema({
},
});
export const AIChatBlockSchemaExtension =
BlockSchemaExtension(AIChatBlockSchema);
export class AIChatBlockModel extends GfxCompatible<AIChatProps>(BlockModel) {}
@@ -214,7 +214,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
if (this._answers.length > 0) {
const latestAnswer = this._answers.pop();
this._answers = [];
const schema = this.schema ?? this.host?.std.store.workspace.schema;
const schema = this.schema ?? this.host?.std.store.schema;
let provider: ServiceProvider;
if (this.host) {
provider = this.host.std.provider;
@@ -1,3 +1,5 @@
import { SpecProvider } from '@blocksuite/affine/blocks';
import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block';
import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block';
import {
@@ -10,6 +12,7 @@ import {
} from './blocks/ai-chat-block/components/chat-images';
import { ImagePlaceholder } from './blocks/ai-chat-block/components/image-placeholder';
import { UserInfo } from './blocks/ai-chat-block/components/user-info';
import { AIChatBlockSchemaExtension } from './blocks/ai-chat-block/model';
import { ChatPanel } from './chat-panel';
import { ActionWrapper } from './chat-panel/actions/action-wrapper';
import { ChatText } from './chat-panel/actions/chat-text';
@@ -126,4 +129,6 @@ export function registerAIEffects() {
'edgeless-copilot-toolbar-entry',
EdgelessCopilotToolbarEntry
);
SpecProvider._.extendSpec('store', [AIChatBlockSchemaExtension]);
}
@@ -1,9 +1,8 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std';
import { SpecProvider } from '@blocksuite/affine/blocks';
import { AffineSchemas } from '@blocksuite/affine/blocks/schemas';
import { WithDisposable } from '@blocksuite/affine/global/utils';
import { Schema, type Store } from '@blocksuite/affine/store';
import type { Store } from '@blocksuite/affine/store';
import { css, html, LitElement, nothing } from 'lit';
import { property, query } from 'lit/decorators.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
@@ -216,9 +215,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
override connectedCallback(): void {
super.connectedCallback();
const schema = new Schema().register(AffineSchemas);
const collection = new WorkspaceImpl({
schema,
id: 'SLIDES_PREVIEW',
});
collection.meta.initialize();
@@ -4,7 +4,6 @@ import {
MarkdownInlineToDeltaAdapterExtensions,
} from '@blocksuite/affine/blocks';
import { Container } from '@blocksuite/affine/global/di';
import { Schema } from '@blocksuite/affine/store';
import { TestWorkspace } from '@blocksuite/affine/store/test';
import { describe, expect, test } from 'vitest';
@@ -29,7 +28,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
- Text D
- Text E
`;
const collection = new TestWorkspace({ schema: new Schema() });
const collection = new TestWorkspace();
collection.meta.initialize();
const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider);
@@ -67,7 +66,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
- Text D
- Text E
`;
const collection = new TestWorkspace({ schema: new Schema() });
const collection = new TestWorkspace();
collection.meta.initialize();
const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider);
@@ -99,7 +98,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
test('empty case', () => {
const markdown = '';
const collection = new TestWorkspace({ schema: new Schema() });
const collection = new TestWorkspace();
collection.meta.initialize();
const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider);
@@ -99,7 +99,6 @@ export class MiniMindmapPreview extends WithDisposable(LitElement) {
const collection = new WorkspaceImpl({
id: 'MINI_MINDMAP_TEMPORARY',
schema,
});
collection.meta.initialize();
const doc = collection.createDoc({ id: 'doc:home' }).load();
@@ -237,7 +236,7 @@ export const markdownToMindmap = (
) => {
let result: Node | null = null;
const transformer = new Transformer({
schema: doc.workspace.schema,
schema: doc.schema,
blobCRUD: doc.workspace.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
@@ -197,7 +197,7 @@ function getNoteBlockModels(doc: Store) {
async function getTransformer(doc: Store) {
return new Transformer({
schema: doc.workspace.schema,
schema: doc.schema,
blobCRUD: doc.workspace.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
@@ -4,6 +4,7 @@ import { AppSidebarService } from '@affine/core/modules/app-sidebar';
import { DocsService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { type DocMode } from '@blocksuite/affine/blocks';
import type { Workspace } from '@blocksuite/affine/store';
import { useServices } from '@toeverything/infra';
@@ -110,6 +111,7 @@ export const usePageHelper = (docCollection: Workspace) => {
};
showImportModal({
collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
onSuccess,
onFail: message => {
reject(new Error(message));
@@ -81,7 +81,7 @@ export async function getContentFromSlice(
type: 'markdown' | 'plain-text' = 'markdown'
) {
const transformer = new Transformer({
schema: host.std.store.workspace.schema,
schema: host.std.store.schema,
blobCRUD: host.std.store.workspace.blobSync,
docCRUD: {
create: (id: string) => host.std.store.workspace.createDoc({ id }),
@@ -114,7 +114,7 @@ export const markdownToSnapshot = async (
host: EditorHost
) => {
const transformer = new Transformer({
schema: host.std.store.workspace.schema,
schema: host.std.store.schema,
blobCRUD: host.std.store.workspace.blobSync,
docCRUD: {
create: (id: string) => host.std.store.workspace.createDoc({ id }),
@@ -174,12 +174,10 @@ export async function markDownToDoc(
middlewares?: TransformerMiddleware[]
) {
// Should not create a new doc in the original collection
const collection = new WorkspaceImpl({
schema,
});
const collection = new WorkspaceImpl();
collection.meta.initialize();
const transformer = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),