mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
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:
@@ -1,4 +1,3 @@
|
||||
import { Schema } from '@blocksuite/store';
|
||||
import {
|
||||
createAutoIncrementIdGenerator,
|
||||
TestWorkspace,
|
||||
@@ -9,19 +8,23 @@ import { effects } from '../effects.js';
|
||||
import { TestEditorContainer } from './test-editor.js';
|
||||
import {
|
||||
type HeadingBlockModel,
|
||||
HeadingBlockSchema,
|
||||
NoteBlockSchema,
|
||||
RootBlockSchema,
|
||||
HeadingBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
import { testSpecs } from './test-spec.js';
|
||||
|
||||
effects();
|
||||
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
HeadingBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([RootBlockSchema, NoteBlockSchema, HeadingBlockSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
function wait(time: number) {
|
||||
@@ -33,7 +36,7 @@ describe('editor host', () => {
|
||||
const collection = new TestWorkspace(createTestOptions());
|
||||
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
const rootId = doc.addBlock('test:page');
|
||||
const noteId = doc.addBlock('test:note', {}, rootId);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export const RootBlockSchema = defineBlockSchema({
|
||||
flavour: 'test:page',
|
||||
@@ -15,6 +19,8 @@ export const RootBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
|
||||
|
||||
export class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof RootBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -30,6 +36,8 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
|
||||
|
||||
export class NoteBlockModel extends BlockModel<
|
||||
ReturnType<(typeof NoteBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -47,6 +55,9 @@ export const HeadingBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const HeadingBlockSchemaExtension =
|
||||
BlockSchemaExtension(HeadingBlockSchema);
|
||||
|
||||
export class HeadingBlockModel extends BlockModel<
|
||||
ReturnType<(typeof HeadingBlockSchema)['model']['props']>
|
||||
> {}
|
||||
|
||||
@@ -142,7 +142,7 @@ export class BlockStdScope {
|
||||
|
||||
getTransformer(middlewares: TransformerMiddleware[] = []) {
|
||||
return new Transformer({
|
||||
schema: this.workspace.schema,
|
||||
schema: this.store.schema,
|
||||
blobCRUD: this.workspace.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => this.workspace.createDoc({ id }),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { computed, effect } from '@preact/signals-core';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { BlockSchemaExtension } from '../extension/schema.js';
|
||||
import {
|
||||
Block,
|
||||
BlockModel,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
internalPrimitives,
|
||||
} from '../model/block/index.js';
|
||||
import type { YBlock } from '../model/block/types.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
|
||||
@@ -27,6 +27,7 @@ const pageSchema = defineBlockSchema({
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
const pageSchemaExtension = BlockSchemaExtension(pageSchema);
|
||||
|
||||
const tableSchema = defineBlockSchema({
|
||||
flavour: 'table',
|
||||
@@ -39,6 +40,7 @@ const tableSchema = defineBlockSchema({
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
const tableSchemaExtension = BlockSchemaExtension(tableSchema);
|
||||
|
||||
const flatTableSchema = defineBlockSchema({
|
||||
flavour: 'flat-table',
|
||||
@@ -54,6 +56,8 @@ const flatTableSchema = defineBlockSchema({
|
||||
isFlatData: true,
|
||||
},
|
||||
});
|
||||
const flatTableSchemaExtension = BlockSchemaExtension(flatTableSchema);
|
||||
|
||||
class RootModel extends BlockModel<
|
||||
ReturnType<(typeof pageSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -66,9 +70,7 @@ class FlatTableModel extends BlockModel<
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([pageSchema, tableSchema, flatTableSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const defaultDocId = 'doc:home';
|
||||
@@ -76,7 +78,14 @@ function createTestDoc(docId = defaultDocId) {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({
|
||||
id: docId,
|
||||
extensions: [
|
||||
pageSchemaExtension,
|
||||
tableSchemaExtension,
|
||||
flatTableSchemaExtension,
|
||||
],
|
||||
});
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -4,29 +4,20 @@ import type { Slot } from '@blocksuite/global/utils';
|
||||
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import type { BlockModel, BlockSchemaType, DocMeta, Store } from '../index.js';
|
||||
import { Schema } from '../index.js';
|
||||
import type { BlockModel, DocMeta, Store } from '../index.js';
|
||||
import { Text } from '../reactive/text.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import {
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
import { assertExists } from './test-utils-dom.js';
|
||||
|
||||
export const BlockSchemas = [
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
NoteBlockSchema,
|
||||
] as BlockSchemaType[];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const defaultDocId = 'doc:home';
|
||||
@@ -58,11 +49,20 @@ function createRoot(doc: Store) {
|
||||
return doc.root;
|
||||
}
|
||||
|
||||
const extensions = [
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestDoc(docId = defaultDocId) {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({
|
||||
id: docId,
|
||||
extensions,
|
||||
});
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
@@ -113,13 +113,6 @@ describe('basic', () => {
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
workspaceVersion: 2,
|
||||
pageVersion: 2,
|
||||
blockVersions: {
|
||||
'affine:note': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
},
|
||||
},
|
||||
spaces: {
|
||||
[spaceId]: {
|
||||
@@ -155,6 +148,7 @@ describe('basic', () => {
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({
|
||||
id: 'space:0',
|
||||
extensions,
|
||||
});
|
||||
|
||||
const readyCallback = vi.fn();
|
||||
@@ -181,6 +175,7 @@ describe('basic', () => {
|
||||
const collection2 = new TestWorkspace(options);
|
||||
const doc = collection.createDoc({
|
||||
id: 'space:0',
|
||||
extensions,
|
||||
});
|
||||
doc.load(() => {
|
||||
doc.addBlock('affine:page', {
|
||||
@@ -209,7 +204,9 @@ describe('basic', () => {
|
||||
// apply doc update
|
||||
const update = encodeStateAsUpdate(doc.spaceDoc);
|
||||
expect(collection2.docs.size).toBe(1);
|
||||
const doc2 = collection2.getDoc('space:0');
|
||||
const doc2 = collection2.getDoc('space:0', {
|
||||
extensions,
|
||||
});
|
||||
assertExists(doc2);
|
||||
applyUpdate(doc2.spaceDoc, update);
|
||||
expect(serializCollection(collection2.doc)['spaces']).toEqual({
|
||||
|
||||
@@ -2,31 +2,28 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import type { BlockModel, Store } from '../model/index.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import {
|
||||
DividerBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
DividerBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
type RootBlockModel,
|
||||
RootBlockSchema,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
|
||||
const BlockSchemas = [
|
||||
RootBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
DividerBlockSchema,
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
DividerBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
test('trigger props updated', () => {
|
||||
@@ -34,7 +31,7 @@ test('trigger props updated', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -94,7 +91,7 @@ test('stash and pop', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -164,7 +161,7 @@ test('always get latest value in onChange', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -210,11 +207,12 @@ test('query', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc1 = collection.createDoc({ id: 'home' });
|
||||
const doc1 = collection.createDoc({ id: 'home', extensions });
|
||||
doc1.load();
|
||||
const doc2 = collection.getDoc('home');
|
||||
const doc2 = collection.getDoc('home', { extensions });
|
||||
|
||||
const doc3 = collection.getDoc('home', {
|
||||
extensions,
|
||||
query: {
|
||||
mode: 'loose',
|
||||
match: [
|
||||
@@ -247,10 +245,10 @@ test('local readonly', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc1 = collection.createDoc({ id: 'home' });
|
||||
const doc1 = collection.createDoc({ id: 'home', extensions });
|
||||
doc1.load();
|
||||
const doc2 = collection.getDoc('home', { readonly: true });
|
||||
const doc3 = collection.getDoc('home', { readonly: false });
|
||||
const doc2 = collection.getDoc('home', { readonly: true, extensions });
|
||||
const doc3 = collection.getDoc('home', { readonly: false, extensions });
|
||||
|
||||
expect(doc1.readonly).toBeFalsy();
|
||||
expect(doc2?.readonly).toBeTruthy();
|
||||
@@ -276,7 +274,7 @@ describe('move blocks', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
const pageId = doc.addBlock('affine:page');
|
||||
const page = doc.getBlock(pageId)!.model;
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import { literal } from 'lit/static-html.js';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { BlockSchemaExtension } from '../extension/schema.js';
|
||||
import { defineBlockSchema } from '../model/block/zod.js';
|
||||
// import some blocks
|
||||
import { SchemaValidateError } from '../schema/error.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import {
|
||||
DividerBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
DividerBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const TestCustomNoteBlockSchema = defineBlockSchema({
|
||||
@@ -35,6 +33,10 @@ const TestCustomNoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const TestCustomNoteBlockSchemaExtension = BlockSchemaExtension(
|
||||
TestCustomNoteBlockSchema
|
||||
);
|
||||
|
||||
const TestInvalidNoteBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:note-invalid-block-video',
|
||||
props: internal => ({
|
||||
@@ -48,14 +50,18 @@ const TestInvalidNoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const BlockSchemas = [
|
||||
RootBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
DividerBlockSchema,
|
||||
TestCustomNoteBlockSchema,
|
||||
TestInvalidNoteBlockSchema,
|
||||
const TestInvalidNoteBlockSchemaExtension = BlockSchemaExtension(
|
||||
TestInvalidNoteBlockSchema
|
||||
);
|
||||
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
DividerBlockSchemaExtension,
|
||||
TestCustomNoteBlockSchemaExtension,
|
||||
TestInvalidNoteBlockSchemaExtension,
|
||||
];
|
||||
|
||||
const defaultDocId = 'doc0';
|
||||
@@ -63,7 +69,7 @@ function createTestDoc(docId = defaultDocId) {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({ id: docId, extensions });
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BlockSchemaExtension } from '../extension/schema.js';
|
||||
import { BlockModel, defineBlockSchema } from '../model/index.js';
|
||||
|
||||
export const RootBlockSchema = defineBlockSchema({
|
||||
@@ -14,6 +15,8 @@ export const RootBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
|
||||
|
||||
export class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof RootBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -42,6 +45,8 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
|
||||
|
||||
export const ParagraphBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:paragraph',
|
||||
props: internal => ({
|
||||
@@ -60,6 +65,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const ParagraphBlockSchemaExtension =
|
||||
BlockSchemaExtension(ParagraphBlockSchema);
|
||||
|
||||
export const ListBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:list',
|
||||
props: internal => ({
|
||||
@@ -80,6 +88,8 @@ export const ListBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
|
||||
|
||||
export const DividerBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:divider',
|
||||
metadata: {
|
||||
@@ -88,3 +98,6 @@ export const DividerBlockSchema = defineBlockSchema({
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
|
||||
export const DividerBlockSchemaExtension =
|
||||
BlockSchemaExtension(DividerBlockSchema);
|
||||
|
||||
@@ -2,10 +2,10 @@ import { expect, test } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { MemoryBlobCRUD } from '../adapter/index.js';
|
||||
import { BlockSchemaExtension } from '../extension/schema.js';
|
||||
import { BlockModel } from '../model/block/block-model.js';
|
||||
import { defineBlockSchema } from '../model/block/zod.js';
|
||||
import { Text } from '../reactive/index.js';
|
||||
import { Schema } from '../schema/index.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
|
||||
@@ -39,15 +39,16 @@ const docSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const docSchemaExtension = BlockSchemaExtension(docSchema);
|
||||
class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof docSchema)['model']['props']>
|
||||
> {}
|
||||
|
||||
const extensions = [docSchemaExtension];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([docSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const transformer = new BaseBlockTransformer(new Map());
|
||||
@@ -58,7 +59,7 @@ test('model to snapshot', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
doc.addBlock('page');
|
||||
const rootModel = doc.root as RootBlockModel;
|
||||
@@ -75,7 +76,7 @@ test('snapshot to model', async () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
doc.addBlock('page');
|
||||
const rootModel = doc.root as RootBlockModel;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './extension';
|
||||
export * from './schema';
|
||||
export * from './selection';
|
||||
export * from './store-extension';
|
||||
|
||||
20
blocksuite/framework/store/src/extension/schema.ts
Normal file
20
blocksuite/framework/store/src/extension/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createIdentifier } from '@blocksuite/global/di';
|
||||
|
||||
import type { BlockSchemaType } from '../model/block/zod';
|
||||
import type { ExtensionType } from './extension';
|
||||
|
||||
export const BlockSchemaIdentifier =
|
||||
createIdentifier<BlockSchemaType>('BlockSchema');
|
||||
|
||||
export function BlockSchemaExtension(
|
||||
blockSchema: BlockSchemaType
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(
|
||||
BlockSchemaIdentifier(blockSchema.model.flavour),
|
||||
() => blockSchema
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Slot } from '@blocksuite/global/utils';
|
||||
import type * as Y from 'yjs';
|
||||
|
||||
import type { Schema } from '../schema/schema.js';
|
||||
import type { AwarenessStore } from '../yjs/awareness.js';
|
||||
import type { YBlock } from './block/types.js';
|
||||
import type { Query } from './store/query.js';
|
||||
@@ -18,7 +17,6 @@ export type YBlocks = Y.Map<YBlock>;
|
||||
export interface Doc {
|
||||
readonly id: string;
|
||||
get meta(): DocMeta | undefined;
|
||||
get schema(): Schema;
|
||||
|
||||
remove(): void;
|
||||
load(initFn?: () => void): void;
|
||||
|
||||
@@ -4,8 +4,11 @@ import { type Disposable, Slot } from '@blocksuite/global/utils';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
|
||||
import type { ExtensionType } from '../../extension/extension.js';
|
||||
import { StoreSelectionExtension } from '../../extension/index.js';
|
||||
import type { Schema } from '../../schema/index.js';
|
||||
import {
|
||||
BlockSchemaIdentifier,
|
||||
StoreSelectionExtension,
|
||||
} from '../../extension/index.js';
|
||||
import { Schema } from '../../schema/index.js';
|
||||
import {
|
||||
Block,
|
||||
type BlockModel,
|
||||
@@ -20,7 +23,6 @@ import { type Query, runQuery } from './query.js';
|
||||
import { syncBlockProps } from './utils.js';
|
||||
|
||||
export type StoreOptions = {
|
||||
schema: Schema;
|
||||
doc: Doc;
|
||||
id?: string;
|
||||
readonly?: boolean;
|
||||
@@ -298,14 +300,7 @@ export class Store {
|
||||
return this._doc.withoutTransact.bind(this._doc);
|
||||
}
|
||||
|
||||
constructor({
|
||||
schema,
|
||||
doc,
|
||||
readonly,
|
||||
query,
|
||||
provider,
|
||||
extensions,
|
||||
}: StoreOptions) {
|
||||
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
|
||||
const container = new Container();
|
||||
container.addImpl(StoreIdentifier, () => this);
|
||||
|
||||
@@ -331,8 +326,11 @@ export class Store {
|
||||
yBlockUpdated: this._doc.slots.yBlockUpdated,
|
||||
};
|
||||
|
||||
this._crud = new DocCRUD(this._yBlocks, doc.schema);
|
||||
this._schema = schema;
|
||||
this._schema = new Schema();
|
||||
this._provider.getAll(BlockSchemaIdentifier).forEach(schema => {
|
||||
this._schema.register([schema]);
|
||||
});
|
||||
this._crud = new DocCRUD(this._yBlocks, this._schema);
|
||||
if (readonly !== undefined) {
|
||||
this._readonly.value = readonly;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { Slot } from '@blocksuite/global/utils';
|
||||
|
||||
import type { Workspace } from './workspace.js';
|
||||
|
||||
export type Tag = {
|
||||
id: string;
|
||||
value: string;
|
||||
@@ -38,8 +36,6 @@ export interface WorkspaceMeta {
|
||||
get name(): string | undefined;
|
||||
setName(name: string): void;
|
||||
|
||||
hasVersion: boolean;
|
||||
writeVersion(workspace: Workspace): void;
|
||||
get docs(): unknown[] | undefined;
|
||||
initialize(): void;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { BlobEngine } from '@blocksuite/sync';
|
||||
import type { Awareness } from 'y-protocols/awareness.js';
|
||||
import type * as Y from 'yjs';
|
||||
|
||||
import type { Schema } from '../schema/schema.js';
|
||||
import type { IdGenerator } from '../utils/id-generator.js';
|
||||
import type { AwarenessStore } from '../yjs/awareness.js';
|
||||
import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js';
|
||||
@@ -19,7 +18,6 @@ export interface Workspace {
|
||||
readonly onLoadDoc?: (doc: Y.Doc) => void;
|
||||
readonly onLoadAwareness?: (awareness: Awareness) => void;
|
||||
|
||||
get schema(): Schema;
|
||||
get doc(): Y.Doc;
|
||||
get docs(): Map<string, Doc>;
|
||||
|
||||
|
||||
@@ -162,10 +162,6 @@ export class TestDoc implements Doc {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return this.workspace.schema;
|
||||
}
|
||||
|
||||
get spaceDoc() {
|
||||
return this._ySpaceDoc;
|
||||
}
|
||||
@@ -189,13 +185,6 @@ export class TestDoc implements Doc {
|
||||
return (readonly?.toString() as 'true' | 'false') ?? 'false';
|
||||
}
|
||||
|
||||
private _handleVersion() {
|
||||
// Initialization from empty yDoc, indicating that the document is new.
|
||||
if (!this.workspace.meta.hasVersion) {
|
||||
this.workspace.meta.writeVersion(this.workspace);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleYBlockAdd(id: string) {
|
||||
this.slots.yBlockUpdated.emit({ type: 'add', id });
|
||||
}
|
||||
@@ -306,7 +295,6 @@ export class TestDoc implements Doc {
|
||||
|
||||
const doc = new Store({
|
||||
doc: this,
|
||||
schema: this.workspace.schema,
|
||||
readonly,
|
||||
query,
|
||||
provider,
|
||||
@@ -327,10 +315,6 @@ export class TestDoc implements Doc {
|
||||
|
||||
this._ySpaceDoc.load();
|
||||
|
||||
if ((this.workspace.meta.docs?.length ?? 0) <= 1) {
|
||||
this._handleVersion();
|
||||
}
|
||||
|
||||
this._initYBlocks();
|
||||
|
||||
this._yBlocks.forEach((_, id) => {
|
||||
|
||||
@@ -4,20 +4,13 @@ import type * as Y from 'yjs';
|
||||
import type {
|
||||
DocMeta,
|
||||
DocsPropertiesMeta,
|
||||
Workspace,
|
||||
WorkspaceMeta,
|
||||
} from '../model/index.js';
|
||||
import { createYProxy } from '../reactive/proxy.js';
|
||||
|
||||
const COLLECTION_VERSION = 2;
|
||||
const PAGE_VERSION = 2;
|
||||
|
||||
type DocCollectionMetaState = {
|
||||
pages?: unknown[];
|
||||
properties?: DocsPropertiesMeta;
|
||||
workspaceVersion?: number;
|
||||
pageVersion?: number;
|
||||
blockVersions?: Record<string, number>;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
@@ -68,10 +61,6 @@ export class TestMeta implements WorkspaceMeta {
|
||||
return this._proxy.avatar;
|
||||
}
|
||||
|
||||
get blockVersions() {
|
||||
return this._proxy.blockVersions;
|
||||
}
|
||||
|
||||
get docMetas() {
|
||||
if (!this._proxy.pages) {
|
||||
return [] as DocMeta[];
|
||||
@@ -83,21 +72,10 @@ export class TestMeta implements WorkspaceMeta {
|
||||
return this._proxy.pages;
|
||||
}
|
||||
|
||||
get hasVersion() {
|
||||
if (!this.blockVersions || !this.pageVersion || !this.workspaceVersion) {
|
||||
return false;
|
||||
}
|
||||
return Object.keys(this.blockVersions).length > 0;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._proxy.name;
|
||||
}
|
||||
|
||||
get pageVersion() {
|
||||
return this._proxy.pageVersion;
|
||||
}
|
||||
|
||||
get properties(): DocsPropertiesMeta {
|
||||
const meta = this._proxy.properties;
|
||||
if (!meta) {
|
||||
@@ -110,10 +88,6 @@ export class TestMeta implements WorkspaceMeta {
|
||||
return meta;
|
||||
}
|
||||
|
||||
get workspaceVersion() {
|
||||
return this._proxy.workspaceVersion;
|
||||
}
|
||||
|
||||
get yDocs() {
|
||||
return this._yMap.get('pages') as unknown as Y.Array<unknown>;
|
||||
}
|
||||
@@ -232,33 +206,4 @@ export class TestMeta implements WorkspaceMeta {
|
||||
this._proxy.properties = meta;
|
||||
this.docMetaUpdated.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Only for doc initialization
|
||||
*/
|
||||
writeVersion(collection: Workspace) {
|
||||
const { blockVersions, pageVersion, workspaceVersion } = this._proxy;
|
||||
|
||||
if (!workspaceVersion) {
|
||||
this._proxy.workspaceVersion = COLLECTION_VERSION;
|
||||
} else {
|
||||
console.error('Workspace version is already set');
|
||||
}
|
||||
|
||||
if (!pageVersion) {
|
||||
this._proxy.pageVersion = PAGE_VERSION;
|
||||
} else {
|
||||
console.error('Doc version is already set');
|
||||
}
|
||||
|
||||
if (!blockVersions) {
|
||||
const _versions: Record<string, number> = {};
|
||||
collection.schema.flavourSchemaMap.forEach((schema, flavour) => {
|
||||
_versions[flavour] = schema.version;
|
||||
});
|
||||
this._proxy.blockVersions = _versions;
|
||||
} else {
|
||||
console.error('Block versions is already set');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,12 @@ import type {
|
||||
Workspace,
|
||||
WorkspaceMeta,
|
||||
} from '../model/index.js';
|
||||
import type { Schema } from '../schema/index.js';
|
||||
import { type IdGenerator, nanoid } from '../utils/id-generator.js';
|
||||
import { AwarenessStore } from '../yjs/index.js';
|
||||
import { TestDoc } from './test-doc.js';
|
||||
import { TestMeta } from './test-meta.js';
|
||||
|
||||
export type DocCollectionOptions = {
|
||||
schema: Schema;
|
||||
id?: string;
|
||||
idGenerator?: IdGenerator;
|
||||
docSources?: {
|
||||
@@ -47,8 +45,6 @@ export type DocCollectionOptions = {
|
||||
* Do not use this in production
|
||||
*/
|
||||
export class TestWorkspace implements Workspace {
|
||||
protected readonly _schema: Schema;
|
||||
|
||||
storeExtensions: ExtensionType[] = [];
|
||||
|
||||
readonly awarenessStore: AwarenessStore;
|
||||
@@ -79,13 +75,8 @@ export class TestWorkspace implements Workspace {
|
||||
return this.blockCollections;
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return this._schema;
|
||||
}
|
||||
|
||||
constructor({
|
||||
id,
|
||||
schema,
|
||||
idGenerator,
|
||||
awarenessSources = [],
|
||||
docSources = {
|
||||
@@ -94,9 +85,7 @@ export class TestWorkspace implements Workspace {
|
||||
blobSources = {
|
||||
main: new MemoryBlobSource(),
|
||||
},
|
||||
}: DocCollectionOptions) {
|
||||
this._schema = schema;
|
||||
|
||||
}: DocCollectionOptions = {}) {
|
||||
this.id = id || '';
|
||||
this.doc = new Y.Doc({ guid: id });
|
||||
this.awarenessStore = new AwarenessStore(new Awareness(this.doc));
|
||||
@@ -165,7 +154,12 @@ export class TestWorkspace implements Workspace {
|
||||
* will be created in the doc simultaneously.
|
||||
*/
|
||||
createDoc(options: CreateBlocksOptions = {}) {
|
||||
const { id: docId = this.idGenerator(), query, readonly } = options;
|
||||
const {
|
||||
id: docId = this.idGenerator(),
|
||||
query,
|
||||
readonly,
|
||||
extensions,
|
||||
} = options;
|
||||
if (this._hasDoc(docId)) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.DocCollectionError,
|
||||
@@ -184,6 +178,7 @@ export class TestWorkspace implements Workspace {
|
||||
id: docId,
|
||||
query,
|
||||
readonly,
|
||||
extensions,
|
||||
}) as Store;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user