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

View File

@@ -5,7 +5,11 @@ import {
type InsertToPosition, type InsertToPosition,
} from '@blocksuite/affine-shared/utils'; } from '@blocksuite/affine-shared/utils';
import type { DataViewDataType } from '@blocksuite/data-view'; import type { DataViewDataType } from '@blocksuite/data-view';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
type Props = { type Props = {
title: string; title: string;
@@ -93,3 +97,6 @@ export const DataViewBlockSchema = defineBlockSchema({
return new DataViewBlockModel(); return new DataViewBlockModel();
}, },
}); });
export const DataViewBlockSchemaExtension =
BlockSchemaExtension(DataViewBlockSchema);

View File

@@ -374,7 +374,7 @@ export class EdgelessClipboardController extends PageClipboard {
const elementsRawData = JSON.parse(mayBeSurfaceDataJson); const elementsRawData = JSON.parse(mayBeSurfaceDataJson);
const { snapshot, blobs } = elementsRawData; const { snapshot, blobs } = elementsRawData;
const job = new Transformer({ const job = new Transformer({
schema: this.std.workspace.schema, schema: this.std.store.schema,
blobCRUD: this.std.workspace.blobSync, blobCRUD: this.std.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => this.std.workspace.createDoc({ id }), create: (id: string) => this.std.workspace.createDoc({ id }),
@@ -1378,7 +1378,7 @@ export async function prepareClipboardData(
std: BlockStdScope std: BlockStdScope
) { ) {
const job = new Transformer({ const job = new Transformer({
schema: std.workspace.schema, schema: std.store.schema,
blobCRUD: std.workspace.blobSync, blobCRUD: std.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }), create: (id: string) => std.workspace.createDoc({ id }),

View File

@@ -91,7 +91,7 @@ export class TemplateJob {
constructor({ model, type, middlewares }: TemplateJobConfig) { constructor({ model, type, middlewares }: TemplateJobConfig) {
this.job = new Transformer({ this.job = new Transformer({
schema: model.doc.workspace.schema, schema: model.doc.schema,
blobCRUD: model.doc.workspace.blobSync, blobCRUD: model.doc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => model.doc.workspace.createDoc({ id }), create: (id: string) => model.doc.workspace.createDoc({ id }),
@@ -320,8 +320,7 @@ export class TemplateJob {
from: Record<string, Record<string, unknown>>, from: Record<string, Record<string, unknown>>,
to: Y.Map<Y.Map<unknown>> to: Y.Map<Y.Map<unknown>>
) { ) {
const schema = const schema = this.model.doc.schema.get('affine:surface');
this.model.doc.workspace.schema.flavourSchemaMap.get('affine:surface');
const surfaceTransformer = schema?.transformer?.( const surfaceTransformer = schema?.transformer?.(
new Map() new Map()
) as SurfaceBlockTransformer; ) as SurfaceBlockTransformer;

View File

@@ -41,7 +41,7 @@ export function getSortedCloneElements(elements: GfxModel[]) {
export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) { export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) {
elements = sortEdgelessElements(elements); elements = sortEdgelessElements(elements);
const job = new Transformer({ const job = new Transformer({
schema: std.workspace.schema, schema: std.store.schema,
blobCRUD: std.workspace.blobSync, blobCRUD: std.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }), create: (id: string) => std.workspace.createDoc({ id }),

View File

@@ -8,19 +8,21 @@ import {
import { SpecProvider } from '@blocksuite/affine-shared/utils'; import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store'; import type { Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store'; import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js'; import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = { type ImportHTMLToDocOptions = {
collection: Workspace; collection: Workspace;
schema: Schema;
html: string; html: string;
fileName?: string; fileName?: string;
}; };
type ImportHTMLZipOptions = { type ImportHTMLZipOptions = {
collection: Workspace; collection: Workspace;
schema: Schema;
imported: Blob; imported: Blob;
}; };
@@ -87,18 +89,20 @@ async function exportDoc(doc: Store) {
* *
* @param options - The import options. * @param options - The import options.
* @param options.collection - The target doc collection. * @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.html - The HTML content to import. * @param options.html - The HTML content to import.
* @param options.fileName - Optional filename for the imported doc. * @param options.fileName - Optional filename for the imported doc.
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails. * @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails.
*/ */
async function importHTMLToDoc({ async function importHTMLToDoc({
collection, collection,
schema,
html, html,
fileName, fileName,
}: ImportHTMLToDocOptions) { }: ImportHTMLToDocOptions) {
const provider = getProvider(); const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),
@@ -127,10 +131,15 @@ async function importHTMLToDoc({
* *
* @param options - The import options. * @param options - The import options.
* @param options.collection - The target doc collection. * @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.imported - The zip file as a Blob. * @param options.imported - The zip file as a Blob.
* @returns A Promise that resolves to an array of IDs of the newly created docs. * @returns A Promise that resolves to an array of IDs of the newly created docs.
*/ */
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) { async function importHTMLZip({
collection,
schema,
imported,
}: ImportHTMLZipOptions) {
const provider = getProvider(); const provider = getProvider();
const unzip = new Unzip(); const unzip = new Unzip();
await unzip.load(imported); await unzip.load(imported);
@@ -161,7 +170,7 @@ async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
htmlBlobs.map(async ([fileName, blob]) => { htmlBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, ''); const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -9,7 +9,7 @@ import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils'; import { assertExists, sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store'; import type { Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store'; import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js'; import { createAssetsArchive, download, Unzip } from './utils.js';
@@ -31,12 +31,14 @@ type ImportMarkdownToBlockOptions = {
type ImportMarkdownToDocOptions = { type ImportMarkdownToDocOptions = {
collection: Workspace; collection: Workspace;
schema: Schema;
markdown: string; markdown: string;
fileName?: string; fileName?: string;
}; };
type ImportMarkdownZipOptions = { type ImportMarkdownZipOptions = {
collection: Workspace; collection: Workspace;
schema: Schema;
imported: Blob; imported: Blob;
}; };
@@ -143,18 +145,20 @@ async function importMarkdownToBlock({
* Imports Markdown content into a new doc within a collection. * Imports Markdown content into a new doc within a collection.
* @param options Object containing import options * @param options Object containing import options
* @param options.collection The target doc collection * @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.markdown The Markdown content to import * @param options.markdown The Markdown content to import
* @param options.fileName Optional filename for the imported doc * @param options.fileName Optional filename for the imported doc
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails * @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails
*/ */
async function importMarkdownToDoc({ async function importMarkdownToDoc({
collection, collection,
schema,
markdown, markdown,
fileName, fileName,
}: ImportMarkdownToDocOptions) { }: ImportMarkdownToDocOptions) {
const provider = getProvider(); const provider = getProvider();
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),
@@ -182,11 +186,13 @@ async function importMarkdownToDoc({
* Imports a zip file containing Markdown files and assets into a collection. * Imports a zip file containing Markdown files and assets into a collection.
* @param options Object containing import options * @param options Object containing import options
* @param options.collection The target doc collection * @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.imported The zip file as a Blob * @param options.imported The zip file as a Blob
* @returns A Promise that resolves to an array of IDs of the newly created docs * @returns A Promise that resolves to an array of IDs of the newly created docs
*/ */
async function importMarkdownZip({ async function importMarkdownZip({
collection, collection,
schema,
imported, imported,
}: ImportMarkdownZipOptions) { }: ImportMarkdownZipOptions) {
const provider = getProvider(); const provider = getProvider();
@@ -219,7 +225,7 @@ async function importMarkdownZip({
markdownBlobs.map(async ([fileName, blob]) => { markdownBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, ''); const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -3,12 +3,18 @@ import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils'; import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di'; import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store'; import {
extMimeMap,
type Schema,
Transformer,
type Workspace,
} from '@blocksuite/store';
import { Unzip } from './utils.js'; import { Unzip } from './utils.js';
type ImportNotionZipOptions = { type ImportNotionZipOptions = {
collection: Workspace; collection: Workspace;
schema: Schema;
imported: Blob; imported: Blob;
}; };
@@ -26,6 +32,7 @@ function getProvider() {
* *
* @param options - The options for importing. * @param options - The options for importing.
* @param options.collection - The BlockSuite document collection. * @param options.collection - The BlockSuite document collection.
* @param options.schema - The schema of the BlockSuite document collection.
* @param options.imported - The imported zip file as a Blob. * @param options.imported - The imported zip file as a Blob.
* *
* @returns A promise that resolves to an object containing: * @returns A promise that resolves to an object containing:
@@ -36,6 +43,7 @@ function getProvider() {
*/ */
async function importNotionZip({ async function importNotionZip({
collection, collection,
schema,
imported, imported,
}: ImportNotionZipOptions) { }: ImportNotionZipOptions) {
const provider = getProvider(); const provider = getProvider();
@@ -117,7 +125,7 @@ async function importNotionZip({
} }
const pagePromises = Array.from(pagePaths).map(async path => { const pagePromises = Array.from(pagePaths).map(async path => {
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -3,15 +3,19 @@ import {
titleMiddleware, titleMiddleware,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store'; import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store'; import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js'; import { download, Unzip, Zip } from '../transformers/utils.js';
async function exportDocs(collection: Workspace, docs: Store[]) { async function exportDocs(
collection: Workspace,
schema: Schema,
docs: Store[]
) {
const zip = new Zip(); const zip = new Zip();
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),
@@ -70,7 +74,11 @@ async function exportDocs(collection: Workspace, docs: Store[]) {
return download(downloadBlob, `${collection.id}.bs.zip`); return download(downloadBlob, `${collection.id}.bs.zip`);
} }
async function importDocs(collection: Workspace, imported: Blob) { async function importDocs(
collection: Workspace,
schema: Schema,
imported: Blob
) {
const unzip = new Unzip(); const unzip = new Unzip();
await unzip.load(imported); await unzip.load(imported);
@@ -98,7 +106,7 @@ async function importDocs(collection: Workspace, imported: Blob) {
} }
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -233,6 +233,7 @@ export function createNewDocMenuGroup(
}; };
showImportModal({ showImportModal({
collection: doc.workspace, collection: doc.workspace,
schema: doc.schema,
onSuccess, onSuccess,
onFail, onFail,
}); });

View File

@@ -8,7 +8,7 @@ import {
} from '@blocksuite/affine-components/icons'; } from '@blocksuite/affine-components/icons';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils'; import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils'; import { WithDisposable } from '@blocksuite/global/utils';
import type { Workspace } from '@blocksuite/store'; import type { Schema, Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit'; import { html, LitElement, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js'; import { query, state } from 'lit/decorators.js';
@@ -31,6 +31,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
constructor( constructor(
private readonly collection: Workspace, private readonly collection: Workspace,
private readonly schema: Schema,
private readonly onSuccess?: OnSuccessHandler, private readonly onSuccess?: OnSuccessHandler,
private readonly onFail?: OnFailHandler, private readonly onFail?: OnFailHandler,
private readonly abortController = new AbortController() private readonly abortController = new AbortController()
@@ -63,6 +64,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
} }
const pageId = await HtmlTransformer.importHTMLToDoc({ const pageId = await HtmlTransformer.importHTMLToDoc({
collection: this.collection, collection: this.collection,
schema: this.schema,
html: text, html: text,
fileName, fileName,
}); });
@@ -93,6 +95,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
} }
const pageId = await MarkdownTransformer.importMarkdownToDoc({ const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: this.collection, collection: this.collection,
schema: this.schema,
markdown: text, markdown: text,
fileName, fileName,
}); });
@@ -117,6 +120,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
const { entryId, pageIds, isWorkspaceFile, hasMarkdown } = const { entryId, pageIds, isWorkspaceFile, hasMarkdown } =
await NotionHtmlTransformer.importNotionZip({ await NotionHtmlTransformer.importNotionZip({
collection: this.collection, collection: this.collection,
schema: this.schema,
imported: file, imported: file,
}); });
needLoading && this.abortController.abort(); needLoading && this.abortController.abort();

View File

@@ -1,4 +1,4 @@
import type { Workspace } from '@blocksuite/store'; import type { Schema, Workspace } from '@blocksuite/store';
import { import {
ImportDoc, ImportDoc,
@@ -7,12 +7,14 @@ import {
} from './import-doc.js'; } from './import-doc.js';
export function showImportModal({ export function showImportModal({
schema,
collection, collection,
onSuccess, onSuccess,
onFail, onFail,
container = document.body, container = document.body,
abortController = new AbortController(), abortController = new AbortController(),
}: { }: {
schema: Schema;
collection: Workspace; collection: Workspace;
onSuccess?: OnSuccessHandler; onSuccess?: OnSuccessHandler;
onFail?: OnFailHandler; onFail?: OnFailHandler;
@@ -22,6 +24,7 @@ export function showImportModal({
}) { }) {
const importDoc = new ImportDoc( const importDoc = new ImportDoc(
collection, collection,
schema,
onSuccess, onSuccess,
onFail, onFail,
abortController abortController

View File

@@ -50,7 +50,11 @@ export {
} from './adapters/index.js'; } from './adapters/index.js';
export type { SurfaceContext } from './surface-block.js'; export type { SurfaceContext } from './surface-block.js';
export { SurfaceBlockComponent } from './surface-block.js'; export { SurfaceBlockComponent } from './surface-block.js';
export { SurfaceBlockModel, SurfaceBlockSchema } from './surface-model.js'; export {
SurfaceBlockModel,
SurfaceBlockSchema,
SurfaceBlockSchemaExtension,
} from './surface-model.js';
export type { SurfaceBlockService } from './surface-service.js'; export type { SurfaceBlockService } from './surface-service.js';
export { export {
EdgelessSurfaceBlockSpec, EdgelessSurfaceBlockSpec,

View File

@@ -5,7 +5,7 @@ import type {
import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx'; import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx';
import { SurfaceBlockModel as BaseSurfaceModel } from '@blocksuite/block-std/gfx'; import { SurfaceBlockModel as BaseSurfaceModel } from '@blocksuite/block-std/gfx';
import { DisposableGroup } from '@blocksuite/global/utils'; import { DisposableGroup } from '@blocksuite/global/utils';
import { defineBlockSchema } from '@blocksuite/store'; import { BlockSchemaExtension, defineBlockSchema } from '@blocksuite/store';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { elementsCtorMap } from './element-model/index.js'; import { elementsCtorMap } from './element-model/index.js';
@@ -36,6 +36,9 @@ export const SurfaceBlockSchema = defineBlockSchema({
toModel: () => new SurfaceBlockModel(), toModel: () => new SurfaceBlockModel(),
}); });
export const SurfaceBlockSchemaExtension =
BlockSchemaExtension(SurfaceBlockSchema);
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void; export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;
export class SurfaceBlockModel extends BaseSurfaceModel { export class SurfaceBlockModel extends BaseSurfaceModel {

View File

@@ -3,7 +3,11 @@ import type {
GfxElementGeometry, GfxElementGeometry,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx'; import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { EmbedCardStyle } from '../../utils/index.js'; import type { EmbedCardStyle } from '../../utils/index.js';
import { AttachmentBlockTransformer } from './attachment-transformer.js'; import { AttachmentBlockTransformer } from './attachment-transformer.js';
@@ -86,6 +90,10 @@ export const AttachmentBlockSchema = defineBlockSchema({
toModel: () => new AttachmentBlockModel(), toModel: () => new AttachmentBlockModel(),
}); });
export const AttachmentBlockSchemaExtension = BlockSchemaExtension(
AttachmentBlockSchema
);
export class AttachmentBlockModel export class AttachmentBlockModel
extends GfxCompatible<AttachmentBlockProps>(BlockModel) extends GfxCompatible<AttachmentBlockProps>(BlockModel)
implements GfxElementGeometry {} implements GfxElementGeometry {}

View File

@@ -3,7 +3,11 @@ import type {
GfxElementGeometry, GfxElementGeometry,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx'; import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js'; import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js';
@@ -54,6 +58,9 @@ export const BookmarkBlockSchema = defineBlockSchema({
toModel: () => new BookmarkBlockModel(), toModel: () => new BookmarkBlockModel(),
}); });
export const BookmarkBlockSchemaExtension =
BlockSchemaExtension(BookmarkBlockSchema);
export class BookmarkBlockModel export class BookmarkBlockModel
extends GfxCompatible<BookmarkBlockProps>(BlockModel) extends GfxCompatible<BookmarkBlockProps>(BlockModel)
implements GfxElementGeometry {} implements GfxElementGeometry {}

View File

@@ -1,4 +1,9 @@
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
interface CodeBlockProps { interface CodeBlockProps {
text: Text; text: Text;
@@ -30,6 +35,8 @@ export const CodeBlockSchema = defineBlockSchema({
toModel: () => new CodeBlockModel(), toModel: () => new CodeBlockModel(),
}); });
export const CodeBlockSchemaExtension = BlockSchemaExtension(CodeBlockSchema);
export class CodeBlockModel extends BlockModel<CodeBlockProps> { export class CodeBlockModel extends BlockModel<CodeBlockProps> {
override text!: Text; override text!: Text;
} }

View File

@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store'; import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { Column, SerializedCells, ViewBasicDataType } from './types.js'; import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
@@ -28,3 +32,6 @@ export const DatabaseBlockSchema = defineBlockSchema({
}, },
toModel: () => new DatabaseBlockModel(), toModel: () => new DatabaseBlockModel(),
}); });
export const DatabaseBlockSchemaExtension =
BlockSchemaExtension(DatabaseBlockSchema);

View File

@@ -1,4 +1,8 @@
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export const DividerBlockSchema = defineBlockSchema({ export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider', flavour: 'affine:divider',
@@ -15,3 +19,6 @@ type Props = {
}; };
export class DividerBlockModel extends BlockModel<Props> {} export class DividerBlockModel extends BlockModel<Props> {}
export const DividerBlockSchemaExtension =
BlockSchemaExtension(DividerBlockSchema);

View File

@@ -3,7 +3,11 @@ import type {
GfxElementGeometry, GfxElementGeometry,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx'; import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { z } from 'zod'; import { z } from 'zod';
import { import {
@@ -76,6 +80,10 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
}, },
}); });
export const EdgelessTextBlockSchemaExtension = BlockSchemaExtension(
EdgelessTextBlockSchema
);
export class EdgelessTextBlockModel export class EdgelessTextBlockModel
extends GfxCompatible<EdgelessTextProps>(BlockModel) extends GfxCompatible<EdgelessTextProps>(BlockModel)
implements GfxElementGeometry {} implements GfxElementGeometry {}

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedFigmaBlockProps, type EmbedFigmaBlockProps,
@@ -20,3 +22,7 @@ export const EmbedFigmaBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedFigmaModel(), toModel: () => new EmbedFigmaModel(),
props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps, props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps,
}); });
export const EmbedFigmaBlockSchemaExtension = BlockSchemaExtension(
EmbedFigmaBlockSchema
);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedGithubBlockProps, type EmbedGithubBlockProps,
@@ -29,3 +31,7 @@ export const EmbedGithubBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedGithubModel(), toModel: () => new EmbedGithubModel(),
props: (): EmbedGithubBlockProps => defaultEmbedGithubProps, props: (): EmbedGithubBlockProps => defaultEmbedGithubProps,
}); });
export const EmbedGithubBlockSchemaExtension = BlockSchemaExtension(
EmbedGithubBlockSchema
);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedHtmlBlockProps, type EmbedHtmlBlockProps,
@@ -18,3 +20,6 @@ export const EmbedHtmlBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedHtmlModel(), toModel: () => new EmbedHtmlModel(),
props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps, props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps,
}); });
export const EmbedHtmlBlockSchemaExtension =
BlockSchemaExtension(EmbedHtmlBlockSchema);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedLinkedDocBlockProps, type EmbedLinkedDocBlockProps,
@@ -20,3 +22,7 @@ export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLinkedDocModel(), toModel: () => new EmbedLinkedDocModel(),
props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps, props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps,
}); });
export const EmbedLinkedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedLinkedDocBlockSchema
);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedLoomBlockProps, type EmbedLoomBlockProps,
@@ -22,3 +24,6 @@ export const EmbedLoomBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLoomModel(), toModel: () => new EmbedLoomModel(),
props: (): EmbedLoomBlockProps => defaultEmbedLoomProps, props: (): EmbedLoomBlockProps => defaultEmbedLoomProps,
}); });
export const EmbedLoomBlockSchemaExtension =
BlockSchemaExtension(EmbedLoomBlockSchema);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedSyncedDocBlockProps, type EmbedSyncedDocBlockProps,
@@ -21,3 +23,7 @@ export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedSyncedDocModel(), toModel: () => new EmbedSyncedDocModel(),
props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps, props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps,
}); });
export const EmbedSyncedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedSyncedDocBlockSchema
);

View File

@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js'; import { createEmbedBlockSchema } from '../../../utils/index.js';
import { import {
type EmbedYoutubeBlockProps, type EmbedYoutubeBlockProps,
@@ -25,3 +27,7 @@ export const EmbedYoutubeBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedYoutubeModel(), toModel: () => new EmbedYoutubeModel(),
props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps, props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps,
}); });
export const EmbedYoutubeBlockSchemaExtension = BlockSchemaExtension(
EmbedYoutubeBlockSchema
);

View File

@@ -15,7 +15,12 @@ import {
hasDescendantElementImpl, hasDescendantElementImpl,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils'; import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
import { z } from 'zod'; import { z } from 'zod';
import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js'; import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js';
@@ -57,6 +62,8 @@ export const FrameBlockSchema = defineBlockSchema({
}, },
}); });
export const FrameBlockSchemaExtension = BlockSchemaExtension(FrameBlockSchema);
export class FrameBlockModel export class FrameBlockModel
extends GfxCompatible<FrameBlockProps>(BlockModel) extends GfxCompatible<FrameBlockProps>(BlockModel)
implements GfxElementGeometry, GfxGroupCompatibleInterface implements GfxElementGeometry, GfxGroupCompatibleInterface

View File

@@ -3,7 +3,11 @@ import type {
GfxElementGeometry, GfxElementGeometry,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx'; import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { ImageBlockTransformer } from './image-transformer.js'; import { ImageBlockTransformer } from './image-transformer.js';
@@ -40,6 +44,8 @@ export const ImageBlockSchema = defineBlockSchema({
toModel: () => new ImageBlockModel(), toModel: () => new ImageBlockModel(),
}); });
export const ImageBlockSchemaExtension = BlockSchemaExtension(ImageBlockSchema);
export class ImageBlockModel export class ImageBlockModel
extends GfxCompatible<ImageBlockProps>(BlockModel) extends GfxCompatible<ImageBlockProps>(BlockModel)
implements GfxElementGeometry {} implements GfxElementGeometry {}

View File

@@ -3,7 +3,11 @@ import {
GfxCompatible, GfxCompatible,
type GfxElementGeometry, type GfxElementGeometry,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type LatexProps = { export type LatexProps = {
latex: string; latex: string;
@@ -34,6 +38,8 @@ export const LatexBlockSchema = defineBlockSchema({
}, },
}); });
export const LatexBlockSchemaExtension = BlockSchemaExtension(LatexBlockSchema);
export class LatexBlockModel export class LatexBlockModel
extends GfxCompatible<LatexProps>(BlockModel) extends GfxCompatible<LatexProps>(BlockModel)
implements GfxElementGeometry {} implements GfxElementGeometry {}

View File

@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store'; import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
// `toggle` type has been deprecated, do not use it // `toggle` type has been deprecated, do not use it
export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle'; export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
@@ -38,6 +42,8 @@ export const ListBlockSchema = defineBlockSchema({
toModel: () => new ListBlockModel(), toModel: () => new ListBlockModel(),
}); });
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
export class ListBlockModel extends BlockModel<ListProps> { export class ListBlockModel extends BlockModel<ListProps> {
override text!: Text; override text!: Text;
} }

View File

@@ -4,7 +4,11 @@ import type {
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx'; import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils'; import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { z } from 'zod'; import { z } from 'zod';
import { import {
@@ -21,6 +25,7 @@ import {
StrokeStyleSchema, StrokeStyleSchema,
} from '../../consts/note'; } from '../../consts/note';
import { type Color, ColorSchema, DefaultTheme } from '../../themes'; import { type Color, ColorSchema, DefaultTheme } from '../../themes';
import { TableModelFlavour } from '../table';
export const NoteZodSchema = z export const NoteZodSchema = z
.object({ .object({
@@ -47,7 +52,6 @@ export const NoteZodSchema = z
}, },
}, },
}); });
import { TableModelFlavour } from '../table';
export const NoteBlockSchema = defineBlockSchema({ export const NoteBlockSchema = defineBlockSchema({
flavour: 'affine:note', flavour: 'affine:note',
@@ -92,6 +96,7 @@ export const NoteBlockSchema = defineBlockSchema({
}, },
}); });
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export type NoteProps = { export type NoteProps = {
background: Color; background: Color;
displayMode: NoteDisplayMode; displayMode: NoteDisplayMode;

View File

@@ -1,4 +1,9 @@
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
export type ParagraphType = export type ParagraphType =
| 'text' | 'text'
@@ -37,6 +42,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
toModel: () => new ParagraphBlockModel(), toModel: () => new ParagraphBlockModel(),
}); });
export const ParagraphBlockSchemaExtension =
BlockSchemaExtension(ParagraphBlockSchema);
export class ParagraphBlockModel extends BlockModel<ParagraphProps> { export class ParagraphBlockModel extends BlockModel<ParagraphProps> {
override text!: Text; override text!: Text;

View File

@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store'; import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type RootBlockProps = { export type RootBlockProps = {
title: Text; title: Text;
@@ -51,3 +55,5 @@ export const RootBlockSchema = defineBlockSchema({
}, },
toModel: () => new RootBlockModel(), toModel: () => new RootBlockModel(),
}); });
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);

View File

@@ -1,4 +1,8 @@
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type SurfaceRefProps = { export type SurfaceRefProps = {
reference: string; reference: string;
@@ -21,4 +25,8 @@ export const SurfaceRefBlockSchema = defineBlockSchema({
toModel: () => new SurfaceRefBlockModel(), toModel: () => new SurfaceRefBlockModel(),
}); });
export const SurfaceRefBlockSchemaExtension = BlockSchemaExtension(
SurfaceRefBlockSchema
);
export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {} export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {}

View File

@@ -1,6 +1,10 @@
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
import type { Text } from '@blocksuite/store'; import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type TableCell = { export type TableCell = {
text: Text; text: Text;
@@ -56,3 +60,5 @@ export const TableBlockSchema = defineBlockSchema({
}, },
toModel: () => new TableBlockModel(), toModel: () => new TableBlockModel(),
}); });
export const TableBlockSchemaExtension = BlockSchemaExtension(TableBlockSchema);

View File

@@ -4,8 +4,11 @@ import type { SliceSnapshot } from '@blocksuite/store';
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { createJob } from '../utils/create-job.js'; import { createJob } from '../utils/create-job.js';
import { getProvider } from '../utils/get-provider.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js'; import { nanoidReplacement } from '../utils/nanoid-replacement.js';
getProvider();
describe('notion-text to snapshot', () => { describe('notion-text to snapshot', () => {
test('basic', () => { test('basic', () => {
const notionText = const notionText =

View File

@@ -11,39 +11,37 @@ import {
type Cell, type Cell,
type Column, type Column,
type DatabaseBlockModel, type DatabaseBlockModel,
DatabaseBlockSchema, DatabaseBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
RootBlockSchema, RootBlockSchemaExtension,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets'; import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
import type { BlockModel, Store } from '@blocksuite/store'; import type { BlockModel, Store } from '@blocksuite/store';
import { Schema, Text } from '@blocksuite/store'; import { Text } from '@blocksuite/store';
import { import {
createAutoIncrementIdGenerator, createAutoIncrementIdGenerator,
TestWorkspace, TestWorkspace,
} from '@blocksuite/store/test'; } from '@blocksuite/store/test';
import { beforeEach, describe, expect, test } from 'vitest'; import { beforeEach, describe, expect, test } from 'vitest';
const AffineSchemas = [ const extensions = [
RootBlockSchema, RootBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
DatabaseBlockSchema, DatabaseBlockSchemaExtension,
]; ];
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register(AffineSchemas);
return { id: 'test-collection', idGenerator, schema };
} }
function createTestDoc(docId = 'doc0') { function createTestDoc(docId = 'doc0') {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: docId }); const doc = collection.createDoc({ id: docId, extensions });
doc.load(); doc.load();
return doc; return doc;
} }

View File

@@ -1,5 +1,5 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image'; import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { import {
Schema, Schema,
Transformer, Transformer,
@@ -26,8 +26,8 @@ export function createJob(middlewares?: TransformerMiddleware[]) {
const testMiddlewares = middlewares ?? []; const testMiddlewares = middlewares ?? [];
testMiddlewares.push(defaultImageProxyMiddleware); testMiddlewares.push(defaultImageProxyMiddleware);
const schema = new Schema().register(AffineSchemas); const schema = new Schema().register(AffineSchemas);
const docCollection = new TestWorkspace({ schema }); const docCollection = new TestWorkspace();
docCollection.storeExtensions = [FeatureFlagService]; docCollection.storeExtensions = SpecProvider._.getSpec('store').value;
docCollection.meta.initialize(); docCollection.meta.initialize();
return new Transformer({ return new Transformer({
schema, schema,

View File

@@ -2,15 +2,12 @@ import { AttachmentBlockSpec } from '@blocksuite/affine-block-attachment';
import { BookmarkBlockSpec } from '@blocksuite/affine-block-bookmark'; import { BookmarkBlockSpec } from '@blocksuite/affine-block-bookmark';
import { CodeBlockSpec } from '@blocksuite/affine-block-code'; import { CodeBlockSpec } from '@blocksuite/affine-block-code';
import { DataViewBlockSpec } from '@blocksuite/affine-block-data-view'; import { DataViewBlockSpec } from '@blocksuite/affine-block-data-view';
import { import { DatabaseBlockSpec } from '@blocksuite/affine-block-database';
DatabaseBlockSpec,
DatabaseSelectionExtension,
} from '@blocksuite/affine-block-database';
import { DividerBlockSpec } from '@blocksuite/affine-block-divider'; import { DividerBlockSpec } from '@blocksuite/affine-block-divider';
import { EdgelessTextBlockSpec } from '@blocksuite/affine-block-edgeless-text'; import { EdgelessTextBlockSpec } from '@blocksuite/affine-block-edgeless-text';
import { EmbedExtensions } from '@blocksuite/affine-block-embed'; import { EmbedExtensions } from '@blocksuite/affine-block-embed';
import { FrameBlockSpec } from '@blocksuite/affine-block-frame'; import { FrameBlockSpec } from '@blocksuite/affine-block-frame';
import { ImageBlockSpec, ImageStoreSpec } from '@blocksuite/affine-block-image'; import { ImageBlockSpec } from '@blocksuite/affine-block-image';
import { LatexBlockSpec } from '@blocksuite/affine-block-latex'; import { LatexBlockSpec } from '@blocksuite/affine-block-latex';
import { ListBlockSpec } from '@blocksuite/affine-block-list'; import { ListBlockSpec } from '@blocksuite/affine-block-list';
import { import {
@@ -26,43 +23,19 @@ import {
EdgelessSurfaceRefBlockSpec, EdgelessSurfaceRefBlockSpec,
PageSurfaceRefBlockSpec, PageSurfaceRefBlockSpec,
} from '@blocksuite/affine-block-surface-ref'; } from '@blocksuite/affine-block-surface-ref';
import { import { TableBlockSpec } from '@blocksuite/affine-block-table';
TableBlockSpec,
TableSelectionExtension,
} from '@blocksuite/affine-block-table';
import { import {
RefNodeSlotsExtension, RefNodeSlotsExtension,
RichTextExtensions, RichTextExtensions,
} from '@blocksuite/affine-components/rich-text'; } from '@blocksuite/affine-components/rich-text';
import {
HighlightSelectionExtension,
ImageSelectionExtension,
} from '@blocksuite/affine-shared/selection';
import { import {
DefaultOpenDocExtension, DefaultOpenDocExtension,
DocDisplayMetaService, DocDisplayMetaService,
EditPropsStore, EditPropsStore,
FeatureFlagService,
FileSizeLimitService,
FontLoaderService, FontLoaderService,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import {
BlockSelectionExtension,
CursorSelectionExtension,
SurfaceSelectionExtension,
TextSelectionExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store'; import type { ExtensionType } from '@blocksuite/store';
import {
AdapterFactoryExtensions,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
} from '../adapters/extension.js';
export const CommonBlockSpecs: ExtensionType[] = [ export const CommonBlockSpecs: ExtensionType[] = [
DocDisplayMetaService, DocDisplayMetaService,
RefNodeSlotsExtension, RefNodeSlotsExtension,
@@ -82,7 +55,6 @@ export const CommonBlockSpecs: ExtensionType[] = [
ParagraphBlockSpec, ParagraphBlockSpec,
DefaultOpenDocExtension, DefaultOpenDocExtension,
FontLoaderService, FontLoaderService,
AdapterFactoryExtensions,
].flat(); ].flat();
export const PageFirstPartyBlockSpecs: ExtensionType[] = [ export const PageFirstPartyBlockSpecs: ExtensionType[] = [
@@ -101,24 +73,3 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
FrameBlockSpec, FrameBlockSpec,
EdgelessTextBlockSpec, EdgelessTextBlockSpec,
].flat(); ].flat();
export const StoreExtensions: ExtensionType[] = [
BlockSelectionExtension,
TextSelectionExtension,
SurfaceSelectionExtension,
CursorSelectionExtension,
HighlightSelectionExtension,
ImageSelectionExtension,
DatabaseSelectionExtension,
TableSelectionExtension,
FeatureFlagService,
LinkPreviewerService,
FileSizeLimitService,
ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
].flat();

View File

@@ -1,3 +1,4 @@
export * from './common.js'; export * from './common.js';
export * from './editor-specs.js'; export * from './editor-specs.js';
export * from './preview-specs.js'; export * from './preview-specs.js';
export * from './store.js';

View File

@@ -1,6 +1,5 @@
import { SpecProvider } from '@blocksuite/affine-shared/utils'; import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { StoreExtensions } from './common.js';
import { import {
EdgelessEditorBlockSpecs, EdgelessEditorBlockSpecs,
PageEditorBlockSpecs, PageEditorBlockSpecs,
@@ -9,6 +8,7 @@ import {
PreviewEdgelessEditorBlockSpecs, PreviewEdgelessEditorBlockSpecs,
PreviewPageEditorBlockSpecs, PreviewPageEditorBlockSpecs,
} from './preview-specs.js'; } from './preview-specs.js';
import { StoreExtensions } from './store.js';
export function registerSpecs() { export function registerSpecs() {
SpecProvider._.addSpec('store', StoreExtensions); SpecProvider._.addSpec('store', StoreExtensions);

View File

@@ -0,0 +1,101 @@
import { DataViewBlockSchemaExtension } from '@blocksuite/affine-block-data-view';
import { DatabaseSelectionExtension } from '@blocksuite/affine-block-database';
import { ImageStoreSpec } from '@blocksuite/affine-block-image';
import { SurfaceBlockSchemaExtension } from '@blocksuite/affine-block-surface';
import { TableSelectionExtension } from '@blocksuite/affine-block-table';
import {
AttachmentBlockSchemaExtension,
BookmarkBlockSchemaExtension,
CodeBlockSchemaExtension,
DatabaseBlockSchemaExtension,
DividerBlockSchemaExtension,
EdgelessTextBlockSchemaExtension,
EmbedFigmaBlockSchemaExtension,
EmbedGithubBlockSchemaExtension,
EmbedHtmlBlockSchemaExtension,
EmbedLinkedDocBlockSchemaExtension,
EmbedLoomBlockSchemaExtension,
EmbedSyncedDocBlockSchemaExtension,
EmbedYoutubeBlockSchemaExtension,
FrameBlockSchemaExtension,
ImageBlockSchemaExtension,
LatexBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
SurfaceRefBlockSchemaExtension,
TableBlockSchemaExtension,
} from '@blocksuite/affine-model';
import {
HighlightSelectionExtension,
ImageSelectionExtension,
} from '@blocksuite/affine-shared/selection';
import {
FeatureFlagService,
FileSizeLimitService,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services';
import {
BlockSelectionExtension,
CursorSelectionExtension,
SurfaceSelectionExtension,
TextSelectionExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import {
AdapterFactoryExtensions,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
} from '../adapters/extension.js';
export const StoreExtensions: ExtensionType[] = [
CodeBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
DividerBlockSchemaExtension,
ImageBlockSchemaExtension,
SurfaceBlockSchemaExtension,
BookmarkBlockSchemaExtension,
FrameBlockSchemaExtension,
DatabaseBlockSchemaExtension,
SurfaceRefBlockSchemaExtension,
DataViewBlockSchemaExtension,
AttachmentBlockSchemaExtension,
EmbedSyncedDocBlockSchemaExtension,
EmbedLinkedDocBlockSchemaExtension,
EmbedHtmlBlockSchemaExtension,
EmbedGithubBlockSchemaExtension,
EmbedFigmaBlockSchemaExtension,
EmbedLoomBlockSchemaExtension,
EmbedYoutubeBlockSchemaExtension,
EdgelessTextBlockSchemaExtension,
LatexBlockSchemaExtension,
TableBlockSchemaExtension,
BlockSelectionExtension,
TextSelectionExtension,
SurfaceSelectionExtension,
CursorSelectionExtension,
HighlightSelectionExtension,
ImageSelectionExtension,
DatabaseSelectionExtension,
TableSelectionExtension,
FeatureFlagService,
LinkPreviewerService,
FileSizeLimitService,
ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
AdapterFactoryExtensions,
].flat();

View File

@@ -1,4 +1,3 @@
import { Schema } from '@blocksuite/store';
import { import {
createAutoIncrementIdGenerator, createAutoIncrementIdGenerator,
TestWorkspace, TestWorkspace,
@@ -9,19 +8,23 @@ import { effects } from '../effects.js';
import { TestEditorContainer } from './test-editor.js'; import { TestEditorContainer } from './test-editor.js';
import { import {
type HeadingBlockModel, type HeadingBlockModel,
HeadingBlockSchema, HeadingBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
RootBlockSchema, RootBlockSchemaExtension,
} from './test-schema.js'; } from './test-schema.js';
import { testSpecs } from './test-spec.js'; import { testSpecs } from './test-spec.js';
effects(); effects();
const extensions = [
RootBlockSchemaExtension,
NoteBlockSchemaExtension,
HeadingBlockSchemaExtension,
];
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register([RootBlockSchema, NoteBlockSchema, HeadingBlockSchema]);
return { id: 'test-collection', idGenerator, schema };
} }
function wait(time: number) { function wait(time: number) {
@@ -33,7 +36,7 @@ describe('editor host', () => {
const collection = new TestWorkspace(createTestOptions()); const collection = new TestWorkspace(createTestOptions());
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
const rootId = doc.addBlock('test:page'); const rootId = doc.addBlock('test:page');
const noteId = doc.addBlock('test:note', {}, rootId); const noteId = doc.addBlock('test:note', {}, rootId);

View File

@@ -1,4 +1,8 @@
import { BlockModel, defineBlockSchema } from '@blocksuite/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export const RootBlockSchema = defineBlockSchema({ export const RootBlockSchema = defineBlockSchema({
flavour: 'test:page', flavour: 'test:page',
@@ -15,6 +19,8 @@ export const RootBlockSchema = defineBlockSchema({
}, },
}); });
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
export class RootBlockModel extends BlockModel< export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']> ReturnType<(typeof RootBlockSchema)['model']['props']>
> {} > {}
@@ -30,6 +36,8 @@ export const NoteBlockSchema = defineBlockSchema({
}, },
}); });
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export class NoteBlockModel extends BlockModel< export class NoteBlockModel extends BlockModel<
ReturnType<(typeof NoteBlockSchema)['model']['props']> ReturnType<(typeof NoteBlockSchema)['model']['props']>
> {} > {}
@@ -47,6 +55,9 @@ export const HeadingBlockSchema = defineBlockSchema({
}, },
}); });
export const HeadingBlockSchemaExtension =
BlockSchemaExtension(HeadingBlockSchema);
export class HeadingBlockModel extends BlockModel< export class HeadingBlockModel extends BlockModel<
ReturnType<(typeof HeadingBlockSchema)['model']['props']> ReturnType<(typeof HeadingBlockSchema)['model']['props']>
> {} > {}

View File

@@ -142,7 +142,7 @@ export class BlockStdScope {
getTransformer(middlewares: TransformerMiddleware[] = []) { getTransformer(middlewares: TransformerMiddleware[] = []) {
return new Transformer({ return new Transformer({
schema: this.workspace.schema, schema: this.store.schema,
blobCRUD: this.workspace.blobSync, blobCRUD: this.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => this.workspace.createDoc({ id }), create: (id: string) => this.workspace.createDoc({ id }),

View File

@@ -2,6 +2,7 @@ import { computed, effect } from '@preact/signals-core';
import { describe, expect, test, vi } from 'vitest'; import { describe, expect, test, vi } from 'vitest';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { BlockSchemaExtension } from '../extension/schema.js';
import { import {
Block, Block,
BlockModel, BlockModel,
@@ -9,7 +10,6 @@ import {
internalPrimitives, internalPrimitives,
} from '../model/block/index.js'; } from '../model/block/index.js';
import type { YBlock } from '../model/block/types.js'; import type { YBlock } from '../model/block/types.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js'; import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js'; import { TestWorkspace } from '../test/test-workspace.js';
@@ -27,6 +27,7 @@ const pageSchema = defineBlockSchema({
version: 1, version: 1,
}, },
}); });
const pageSchemaExtension = BlockSchemaExtension(pageSchema);
const tableSchema = defineBlockSchema({ const tableSchema = defineBlockSchema({
flavour: 'table', flavour: 'table',
@@ -39,6 +40,7 @@ const tableSchema = defineBlockSchema({
version: 1, version: 1,
}, },
}); });
const tableSchemaExtension = BlockSchemaExtension(tableSchema);
const flatTableSchema = defineBlockSchema({ const flatTableSchema = defineBlockSchema({
flavour: 'flat-table', flavour: 'flat-table',
@@ -54,6 +56,8 @@ const flatTableSchema = defineBlockSchema({
isFlatData: true, isFlatData: true,
}, },
}); });
const flatTableSchemaExtension = BlockSchemaExtension(flatTableSchema);
class RootModel extends BlockModel< class RootModel extends BlockModel<
ReturnType<(typeof pageSchema)['model']['props']> ReturnType<(typeof pageSchema)['model']['props']>
> {} > {}
@@ -66,9 +70,7 @@ class FlatTableModel extends BlockModel<
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register([pageSchema, tableSchema, flatTableSchema]);
return { id: 'test-collection', idGenerator, schema };
} }
const defaultDocId = 'doc:home'; const defaultDocId = 'doc:home';
@@ -76,7 +78,14 @@ function createTestDoc(docId = defaultDocId) {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: docId }); const doc = collection.createDoc({
id: docId,
extensions: [
pageSchemaExtension,
tableSchemaExtension,
flatTableSchemaExtension,
],
});
doc.load(); doc.load();
return doc; return doc;
} }

View File

@@ -4,29 +4,20 @@ import type { Slot } from '@blocksuite/global/utils';
import { assert, beforeEach, describe, expect, it, vi } from 'vitest'; import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs'; import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
import type { BlockModel, BlockSchemaType, DocMeta, Store } from '../index.js'; import type { BlockModel, DocMeta, Store } from '../index.js';
import { Schema } from '../index.js';
import { Text } from '../reactive/text.js'; import { Text } from '../reactive/text.js';
import { createAutoIncrementIdGenerator } from '../test/index.js'; import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js'; import { TestWorkspace } from '../test/test-workspace.js';
import { import {
NoteBlockSchema, NoteBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
RootBlockSchema, RootBlockSchemaExtension,
} from './test-schema.js'; } from './test-schema.js';
import { assertExists } from './test-utils-dom.js'; import { assertExists } from './test-utils-dom.js';
export const BlockSchemas = [
ParagraphBlockSchema,
RootBlockSchema,
NoteBlockSchema,
] as BlockSchemaType[];
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
} }
const defaultDocId = 'doc:home'; const defaultDocId = 'doc:home';
@@ -58,11 +49,20 @@ function createRoot(doc: Store) {
return doc.root; return doc.root;
} }
const extensions = [
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
];
function createTestDoc(docId = defaultDocId) { function createTestDoc(docId = defaultDocId) {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: docId }); const doc = collection.createDoc({
id: docId,
extensions,
});
doc.load(); doc.load();
return doc; return doc;
} }
@@ -113,13 +113,6 @@ describe('basic', () => {
tags: [], tags: [],
}, },
], ],
workspaceVersion: 2,
pageVersion: 2,
blockVersions: {
'affine:note': 1,
'affine:page': 2,
'affine:paragraph': 1,
},
}, },
spaces: { spaces: {
[spaceId]: { [spaceId]: {
@@ -155,6 +148,7 @@ describe('basic', () => {
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ const doc = collection.createDoc({
id: 'space:0', id: 'space:0',
extensions,
}); });
const readyCallback = vi.fn(); const readyCallback = vi.fn();
@@ -181,6 +175,7 @@ describe('basic', () => {
const collection2 = new TestWorkspace(options); const collection2 = new TestWorkspace(options);
const doc = collection.createDoc({ const doc = collection.createDoc({
id: 'space:0', id: 'space:0',
extensions,
}); });
doc.load(() => { doc.load(() => {
doc.addBlock('affine:page', { doc.addBlock('affine:page', {
@@ -209,7 +204,9 @@ describe('basic', () => {
// apply doc update // apply doc update
const update = encodeStateAsUpdate(doc.spaceDoc); const update = encodeStateAsUpdate(doc.spaceDoc);
expect(collection2.docs.size).toBe(1); expect(collection2.docs.size).toBe(1);
const doc2 = collection2.getDoc('space:0'); const doc2 = collection2.getDoc('space:0', {
extensions,
});
assertExists(doc2); assertExists(doc2);
applyUpdate(doc2.spaceDoc, update); applyUpdate(doc2.spaceDoc, update);
expect(serializCollection(collection2.doc)['spaces']).toEqual({ expect(serializCollection(collection2.doc)['spaces']).toEqual({

View File

@@ -2,31 +2,28 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import * as Y from 'yjs'; import * as Y from 'yjs';
import type { BlockModel, Store } from '../model/index.js'; import type { BlockModel, Store } from '../model/index.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js'; import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js'; import { TestWorkspace } from '../test/test-workspace.js';
import { import {
DividerBlockSchema, DividerBlockSchemaExtension,
ListBlockSchema, ListBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
type RootBlockModel, type RootBlockModel,
RootBlockSchema, RootBlockSchemaExtension,
} from './test-schema.js'; } from './test-schema.js';
const BlockSchemas = [ const extensions = [
RootBlockSchema, RootBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
ListBlockSchema, ListBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
DividerBlockSchema, DividerBlockSchemaExtension,
]; ];
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
} }
test('trigger props updated', () => { test('trigger props updated', () => {
@@ -34,7 +31,7 @@ test('trigger props updated', () => {
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
doc.addBlock('affine:page'); doc.addBlock('affine:page');
@@ -94,7 +91,7 @@ test('stash and pop', () => {
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
doc.addBlock('affine:page'); doc.addBlock('affine:page');
@@ -164,7 +161,7 @@ test('always get latest value in onChange', () => {
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
doc.addBlock('affine:page'); doc.addBlock('affine:page');
@@ -210,11 +207,12 @@ test('query', () => {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc1 = collection.createDoc({ id: 'home' }); const doc1 = collection.createDoc({ id: 'home', extensions });
doc1.load(); doc1.load();
const doc2 = collection.getDoc('home'); const doc2 = collection.getDoc('home', { extensions });
const doc3 = collection.getDoc('home', { const doc3 = collection.getDoc('home', {
extensions,
query: { query: {
mode: 'loose', mode: 'loose',
match: [ match: [
@@ -247,10 +245,10 @@ test('local readonly', () => {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc1 = collection.createDoc({ id: 'home' }); const doc1 = collection.createDoc({ id: 'home', extensions });
doc1.load(); doc1.load();
const doc2 = collection.getDoc('home', { readonly: true }); const doc2 = collection.getDoc('home', { readonly: true, extensions });
const doc3 = collection.getDoc('home', { readonly: false }); const doc3 = collection.getDoc('home', { readonly: false, extensions });
expect(doc1.readonly).toBeFalsy(); expect(doc1.readonly).toBeFalsy();
expect(doc2?.readonly).toBeTruthy(); expect(doc2?.readonly).toBeTruthy();
@@ -276,7 +274,7 @@ describe('move blocks', () => {
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
const pageId = doc.addBlock('affine:page'); const pageId = doc.addBlock('affine:page');
const page = doc.getBlock(pageId)!.model; const page = doc.getBlock(pageId)!.model;

View File

@@ -1,25 +1,23 @@
import { literal } from 'lit/static-html.js'; import { literal } from 'lit/static-html.js';
import { describe, expect, it, vi } from 'vitest'; import { describe, expect, it, vi } from 'vitest';
import { BlockSchemaExtension } from '../extension/schema.js';
import { defineBlockSchema } from '../model/block/zod.js'; import { defineBlockSchema } from '../model/block/zod.js';
// import some blocks // import some blocks
import { SchemaValidateError } from '../schema/error.js'; import { SchemaValidateError } from '../schema/error.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js'; import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js'; import { TestWorkspace } from '../test/test-workspace.js';
import { import {
DividerBlockSchema, DividerBlockSchemaExtension,
ListBlockSchema, ListBlockSchemaExtension,
NoteBlockSchema, NoteBlockSchemaExtension,
ParagraphBlockSchema, ParagraphBlockSchemaExtension,
RootBlockSchema, RootBlockSchemaExtension,
} from './test-schema.js'; } from './test-schema.js';
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
} }
const TestCustomNoteBlockSchema = defineBlockSchema({ const TestCustomNoteBlockSchema = defineBlockSchema({
@@ -35,6 +33,10 @@ const TestCustomNoteBlockSchema = defineBlockSchema({
}, },
}); });
const TestCustomNoteBlockSchemaExtension = BlockSchemaExtension(
TestCustomNoteBlockSchema
);
const TestInvalidNoteBlockSchema = defineBlockSchema({ const TestInvalidNoteBlockSchema = defineBlockSchema({
flavour: 'affine:note-invalid-block-video', flavour: 'affine:note-invalid-block-video',
props: internal => ({ props: internal => ({
@@ -48,14 +50,18 @@ const TestInvalidNoteBlockSchema = defineBlockSchema({
}, },
}); });
const BlockSchemas = [ const TestInvalidNoteBlockSchemaExtension = BlockSchemaExtension(
RootBlockSchema, TestInvalidNoteBlockSchema
ParagraphBlockSchema, );
ListBlockSchema,
NoteBlockSchema, const extensions = [
DividerBlockSchema, RootBlockSchemaExtension,
TestCustomNoteBlockSchema, ParagraphBlockSchemaExtension,
TestInvalidNoteBlockSchema, ListBlockSchemaExtension,
NoteBlockSchemaExtension,
DividerBlockSchemaExtension,
TestCustomNoteBlockSchemaExtension,
TestInvalidNoteBlockSchemaExtension,
]; ];
const defaultDocId = 'doc0'; const defaultDocId = 'doc0';
@@ -63,7 +69,7 @@ function createTestDoc(docId = defaultDocId) {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: docId }); const doc = collection.createDoc({ id: docId, extensions });
doc.load(); doc.load();
return doc; return doc;
} }

View File

@@ -1,3 +1,4 @@
import { BlockSchemaExtension } from '../extension/schema.js';
import { BlockModel, defineBlockSchema } from '../model/index.js'; import { BlockModel, defineBlockSchema } from '../model/index.js';
export const RootBlockSchema = defineBlockSchema({ export const RootBlockSchema = defineBlockSchema({
@@ -14,6 +15,8 @@ export const RootBlockSchema = defineBlockSchema({
}, },
}); });
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
export class RootBlockModel extends BlockModel< export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']> ReturnType<(typeof RootBlockSchema)['model']['props']>
> {} > {}
@@ -42,6 +45,8 @@ export const NoteBlockSchema = defineBlockSchema({
}, },
}); });
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export const ParagraphBlockSchema = defineBlockSchema({ export const ParagraphBlockSchema = defineBlockSchema({
flavour: 'affine:paragraph', flavour: 'affine:paragraph',
props: internal => ({ props: internal => ({
@@ -60,6 +65,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
}, },
}); });
export const ParagraphBlockSchemaExtension =
BlockSchemaExtension(ParagraphBlockSchema);
export const ListBlockSchema = defineBlockSchema({ export const ListBlockSchema = defineBlockSchema({
flavour: 'affine:list', flavour: 'affine:list',
props: internal => ({ props: internal => ({
@@ -80,6 +88,8 @@ export const ListBlockSchema = defineBlockSchema({
}, },
}); });
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
export const DividerBlockSchema = defineBlockSchema({ export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider', flavour: 'affine:divider',
metadata: { metadata: {
@@ -88,3 +98,6 @@ export const DividerBlockSchema = defineBlockSchema({
children: [], children: [],
}, },
}); });
export const DividerBlockSchemaExtension =
BlockSchemaExtension(DividerBlockSchema);

View File

@@ -2,10 +2,10 @@ import { expect, test } from 'vitest';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { MemoryBlobCRUD } from '../adapter/index.js'; import { MemoryBlobCRUD } from '../adapter/index.js';
import { BlockSchemaExtension } from '../extension/schema.js';
import { BlockModel } from '../model/block/block-model.js'; import { BlockModel } from '../model/block/block-model.js';
import { defineBlockSchema } from '../model/block/zod.js'; import { defineBlockSchema } from '../model/block/zod.js';
import { Text } from '../reactive/index.js'; import { Text } from '../reactive/index.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js'; import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js'; import { TestWorkspace } from '../test/test-workspace.js';
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js'; import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
@@ -39,15 +39,16 @@ const docSchema = defineBlockSchema({
}, },
}); });
const docSchemaExtension = BlockSchemaExtension(docSchema);
class RootBlockModel extends BlockModel< class RootBlockModel extends BlockModel<
ReturnType<(typeof docSchema)['model']['props']> ReturnType<(typeof docSchema)['model']['props']>
> {} > {}
const extensions = [docSchemaExtension];
function createTestOptions() { function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator(); const idGenerator = createAutoIncrementIdGenerator();
const schema = new Schema(); return { id: 'test-collection', idGenerator };
schema.register([docSchema]);
return { id: 'test-collection', idGenerator, schema };
} }
const transformer = new BaseBlockTransformer(new Map()); const transformer = new BaseBlockTransformer(new Map());
@@ -58,7 +59,7 @@ test('model to snapshot', () => {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
doc.addBlock('page'); doc.addBlock('page');
const rootModel = doc.root as RootBlockModel; const rootModel = doc.root as RootBlockModel;
@@ -75,7 +76,7 @@ test('snapshot to model', async () => {
const options = createTestOptions(); const options = createTestOptions();
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'home' }); const doc = collection.createDoc({ id: 'home', extensions });
doc.load(); doc.load();
doc.addBlock('page'); doc.addBlock('page');
const rootModel = doc.root as RootBlockModel; const rootModel = doc.root as RootBlockModel;

View File

@@ -1,3 +1,4 @@
export * from './extension'; export * from './extension';
export * from './schema';
export * from './selection'; export * from './selection';
export * from './store-extension'; export * from './store-extension';

View 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
);
},
};
}

View File

@@ -1,7 +1,6 @@
import type { Slot } from '@blocksuite/global/utils'; import type { Slot } from '@blocksuite/global/utils';
import type * as Y from 'yjs'; import type * as Y from 'yjs';
import type { Schema } from '../schema/schema.js';
import type { AwarenessStore } from '../yjs/awareness.js'; import type { AwarenessStore } from '../yjs/awareness.js';
import type { YBlock } from './block/types.js'; import type { YBlock } from './block/types.js';
import type { Query } from './store/query.js'; import type { Query } from './store/query.js';
@@ -18,7 +17,6 @@ export type YBlocks = Y.Map<YBlock>;
export interface Doc { export interface Doc {
readonly id: string; readonly id: string;
get meta(): DocMeta | undefined; get meta(): DocMeta | undefined;
get schema(): Schema;
remove(): void; remove(): void;
load(initFn?: () => void): void; load(initFn?: () => void): void;

View File

@@ -4,8 +4,11 @@ import { type Disposable, Slot } from '@blocksuite/global/utils';
import { computed, signal } from '@preact/signals-core'; import { computed, signal } from '@preact/signals-core';
import type { ExtensionType } from '../../extension/extension.js'; import type { ExtensionType } from '../../extension/extension.js';
import { StoreSelectionExtension } from '../../extension/index.js'; import {
import type { Schema } from '../../schema/index.js'; BlockSchemaIdentifier,
StoreSelectionExtension,
} from '../../extension/index.js';
import { Schema } from '../../schema/index.js';
import { import {
Block, Block,
type BlockModel, type BlockModel,
@@ -20,7 +23,6 @@ import { type Query, runQuery } from './query.js';
import { syncBlockProps } from './utils.js'; import { syncBlockProps } from './utils.js';
export type StoreOptions = { export type StoreOptions = {
schema: Schema;
doc: Doc; doc: Doc;
id?: string; id?: string;
readonly?: boolean; readonly?: boolean;
@@ -298,14 +300,7 @@ export class Store {
return this._doc.withoutTransact.bind(this._doc); return this._doc.withoutTransact.bind(this._doc);
} }
constructor({ constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
schema,
doc,
readonly,
query,
provider,
extensions,
}: StoreOptions) {
const container = new Container(); const container = new Container();
container.addImpl(StoreIdentifier, () => this); container.addImpl(StoreIdentifier, () => this);
@@ -331,8 +326,11 @@ export class Store {
yBlockUpdated: this._doc.slots.yBlockUpdated, yBlockUpdated: this._doc.slots.yBlockUpdated,
}; };
this._crud = new DocCRUD(this._yBlocks, doc.schema); this._schema = new Schema();
this._schema = schema; this._provider.getAll(BlockSchemaIdentifier).forEach(schema => {
this._schema.register([schema]);
});
this._crud = new DocCRUD(this._yBlocks, this._schema);
if (readonly !== undefined) { if (readonly !== undefined) {
this._readonly.value = readonly; this._readonly.value = readonly;
} }

View File

@@ -1,7 +1,5 @@
import type { Slot } from '@blocksuite/global/utils'; import type { Slot } from '@blocksuite/global/utils';
import type { Workspace } from './workspace.js';
export type Tag = { export type Tag = {
id: string; id: string;
value: string; value: string;
@@ -38,8 +36,6 @@ export interface WorkspaceMeta {
get name(): string | undefined; get name(): string | undefined;
setName(name: string): void; setName(name: string): void;
hasVersion: boolean;
writeVersion(workspace: Workspace): void;
get docs(): unknown[] | undefined; get docs(): unknown[] | undefined;
initialize(): void; initialize(): void;

View File

@@ -3,7 +3,6 @@ import type { BlobEngine } from '@blocksuite/sync';
import type { Awareness } from 'y-protocols/awareness.js'; import type { Awareness } from 'y-protocols/awareness.js';
import type * as Y from 'yjs'; import type * as Y from 'yjs';
import type { Schema } from '../schema/schema.js';
import type { IdGenerator } from '../utils/id-generator.js'; import type { IdGenerator } from '../utils/id-generator.js';
import type { AwarenessStore } from '../yjs/awareness.js'; import type { AwarenessStore } from '../yjs/awareness.js';
import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js'; import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js';
@@ -19,7 +18,6 @@ export interface Workspace {
readonly onLoadDoc?: (doc: Y.Doc) => void; readonly onLoadDoc?: (doc: Y.Doc) => void;
readonly onLoadAwareness?: (awareness: Awareness) => void; readonly onLoadAwareness?: (awareness: Awareness) => void;
get schema(): Schema;
get doc(): Y.Doc; get doc(): Y.Doc;
get docs(): Map<string, Doc>; get docs(): Map<string, Doc>;

View File

@@ -162,10 +162,6 @@ export class TestDoc implements Doc {
return this._ready; return this._ready;
} }
get schema() {
return this.workspace.schema;
}
get spaceDoc() { get spaceDoc() {
return this._ySpaceDoc; return this._ySpaceDoc;
} }
@@ -189,13 +185,6 @@ export class TestDoc implements Doc {
return (readonly?.toString() as 'true' | 'false') ?? 'false'; 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) { private _handleYBlockAdd(id: string) {
this.slots.yBlockUpdated.emit({ type: 'add', id }); this.slots.yBlockUpdated.emit({ type: 'add', id });
} }
@@ -306,7 +295,6 @@ export class TestDoc implements Doc {
const doc = new Store({ const doc = new Store({
doc: this, doc: this,
schema: this.workspace.schema,
readonly, readonly,
query, query,
provider, provider,
@@ -327,10 +315,6 @@ export class TestDoc implements Doc {
this._ySpaceDoc.load(); this._ySpaceDoc.load();
if ((this.workspace.meta.docs?.length ?? 0) <= 1) {
this._handleVersion();
}
this._initYBlocks(); this._initYBlocks();
this._yBlocks.forEach((_, id) => { this._yBlocks.forEach((_, id) => {

View File

@@ -4,20 +4,13 @@ import type * as Y from 'yjs';
import type { import type {
DocMeta, DocMeta,
DocsPropertiesMeta, DocsPropertiesMeta,
Workspace,
WorkspaceMeta, WorkspaceMeta,
} from '../model/index.js'; } from '../model/index.js';
import { createYProxy } from '../reactive/proxy.js'; import { createYProxy } from '../reactive/proxy.js';
const COLLECTION_VERSION = 2;
const PAGE_VERSION = 2;
type DocCollectionMetaState = { type DocCollectionMetaState = {
pages?: unknown[]; pages?: unknown[];
properties?: DocsPropertiesMeta; properties?: DocsPropertiesMeta;
workspaceVersion?: number;
pageVersion?: number;
blockVersions?: Record<string, number>;
name?: string; name?: string;
avatar?: string; avatar?: string;
}; };
@@ -68,10 +61,6 @@ export class TestMeta implements WorkspaceMeta {
return this._proxy.avatar; return this._proxy.avatar;
} }
get blockVersions() {
return this._proxy.blockVersions;
}
get docMetas() { get docMetas() {
if (!this._proxy.pages) { if (!this._proxy.pages) {
return [] as DocMeta[]; return [] as DocMeta[];
@@ -83,21 +72,10 @@ export class TestMeta implements WorkspaceMeta {
return this._proxy.pages; return this._proxy.pages;
} }
get hasVersion() {
if (!this.blockVersions || !this.pageVersion || !this.workspaceVersion) {
return false;
}
return Object.keys(this.blockVersions).length > 0;
}
get name() { get name() {
return this._proxy.name; return this._proxy.name;
} }
get pageVersion() {
return this._proxy.pageVersion;
}
get properties(): DocsPropertiesMeta { get properties(): DocsPropertiesMeta {
const meta = this._proxy.properties; const meta = this._proxy.properties;
if (!meta) { if (!meta) {
@@ -110,10 +88,6 @@ export class TestMeta implements WorkspaceMeta {
return meta; return meta;
} }
get workspaceVersion() {
return this._proxy.workspaceVersion;
}
get yDocs() { get yDocs() {
return this._yMap.get('pages') as unknown as Y.Array<unknown>; 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._proxy.properties = meta;
this.docMetaUpdated.emit(); 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');
}
}
} }

View File

@@ -21,14 +21,12 @@ import type {
Workspace, Workspace,
WorkspaceMeta, WorkspaceMeta,
} from '../model/index.js'; } from '../model/index.js';
import type { Schema } from '../schema/index.js';
import { type IdGenerator, nanoid } from '../utils/id-generator.js'; import { type IdGenerator, nanoid } from '../utils/id-generator.js';
import { AwarenessStore } from '../yjs/index.js'; import { AwarenessStore } from '../yjs/index.js';
import { TestDoc } from './test-doc.js'; import { TestDoc } from './test-doc.js';
import { TestMeta } from './test-meta.js'; import { TestMeta } from './test-meta.js';
export type DocCollectionOptions = { export type DocCollectionOptions = {
schema: Schema;
id?: string; id?: string;
idGenerator?: IdGenerator; idGenerator?: IdGenerator;
docSources?: { docSources?: {
@@ -47,8 +45,6 @@ export type DocCollectionOptions = {
* Do not use this in production * Do not use this in production
*/ */
export class TestWorkspace implements Workspace { export class TestWorkspace implements Workspace {
protected readonly _schema: Schema;
storeExtensions: ExtensionType[] = []; storeExtensions: ExtensionType[] = [];
readonly awarenessStore: AwarenessStore; readonly awarenessStore: AwarenessStore;
@@ -79,13 +75,8 @@ export class TestWorkspace implements Workspace {
return this.blockCollections; return this.blockCollections;
} }
get schema() {
return this._schema;
}
constructor({ constructor({
id, id,
schema,
idGenerator, idGenerator,
awarenessSources = [], awarenessSources = [],
docSources = { docSources = {
@@ -94,9 +85,7 @@ export class TestWorkspace implements Workspace {
blobSources = { blobSources = {
main: new MemoryBlobSource(), main: new MemoryBlobSource(),
}, },
}: DocCollectionOptions) { }: DocCollectionOptions = {}) {
this._schema = schema;
this.id = id || ''; this.id = id || '';
this.doc = new Y.Doc({ guid: id }); this.doc = new Y.Doc({ guid: id });
this.awarenessStore = new AwarenessStore(new Awareness(this.doc)); this.awarenessStore = new AwarenessStore(new Awareness(this.doc));
@@ -165,7 +154,12 @@ export class TestWorkspace implements Workspace {
* will be created in the doc simultaneously. * will be created in the doc simultaneously.
*/ */
createDoc(options: CreateBlocksOptions = {}) { createDoc(options: CreateBlocksOptions = {}) {
const { id: docId = this.idGenerator(), query, readonly } = options; const {
id: docId = this.idGenerator(),
query,
readonly,
extensions,
} = options;
if (this._hasDoc(docId)) { if (this._hasDoc(docId)) {
throw new BlockSuiteError( throw new BlockSuiteError(
ErrorCode.DocCollectionError, ErrorCode.DocCollectionError,
@@ -184,6 +178,7 @@ export class TestWorkspace implements Workspace {
id: docId, id: docId,
query, query,
readonly, readonly,
extensions,
}) as Store; }) as Store;
} }

View File

@@ -1,5 +1,10 @@
import { type SurfaceBlockModel, ZipTransformer } from '@blocksuite/blocks'; import {
AffineSchemas,
type SurfaceBlockModel,
ZipTransformer,
} from '@blocksuite/blocks';
import type { PointLocation } from '@blocksuite/global/utils'; import type { PointLocation } from '@blocksuite/global/utils';
import { Schema } from '@blocksuite/store';
import { beforeEach, expect, test } from 'vitest'; import { beforeEach, expect, test } from 'vitest';
import { wait } from '../utils/common.js'; import { wait } from '../utils/common.js';
@@ -25,6 +30,8 @@ const skipFields = new Set(['_lastXYWH']);
const snapshotTest = async (snapshotUrl: string, elementsCount: number) => { const snapshotTest = async (snapshotUrl: string, elementsCount: number) => {
const transformer = ZipTransformer; const transformer = ZipTransformer;
const schema = new Schema();
schema.register(AffineSchemas);
const snapshotFile = await fetch(snapshotUrl) const snapshotFile = await fetch(snapshotUrl)
.then(res => res.blob()) .then(res => res.blob())
@@ -32,8 +39,10 @@ const snapshotTest = async (snapshotUrl: string, elementsCount: number) => {
console.error(e); console.error(e);
throw e; throw e;
}); });
const [newDoc] = await transformer.importDocs( const [newDoc] = await transformer.importDocs(
window.editor.doc.workspace, window.editor.doc.workspace,
schema,
snapshotFile snapshotFile
); );

View File

@@ -1,6 +1,7 @@
import { replaceIdMiddleware } from '@blocksuite/blocks'; import { AffineSchemas, replaceIdMiddleware } from '@blocksuite/blocks';
import { import {
type DocSnapshot, type DocSnapshot,
Schema,
Transformer, Transformer,
type Workspace, type Workspace,
} from '@blocksuite/store'; } from '@blocksuite/store';
@@ -10,7 +11,7 @@ export async function importFromSnapshot(
snapshot: DocSnapshot snapshot: DocSnapshot
) { ) {
const job = new Transformer({ const job = new Transformer({
schema: collection.schema, schema: new Schema().register(AffineSchemas),
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -238,7 +238,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _exportFile(config: AdapterConfig) { private async _exportFile(config: AdapterConfig) {
const doc = this.editor.doc; const doc = this.editor.doc;
const job = new Transformer({ const job = new Transformer({
schema: this.collection.schema, schema: doc.schema,
blobCRUD: this.collection.blobSync, blobCRUD: this.collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => this.collection.createDoc({ id }), create: (id: string) => this.collection.createDoc({ id }),
@@ -325,6 +325,7 @@ export class StarterDebugMenu extends ShadowlessElement {
private async _exportSnapshot() { private async _exportSnapshot() {
await ZipTransformer.exportDocs( await ZipTransformer.exportDocs(
this.collection, this.collection,
this.editor.doc.schema,
Array.from(this.collection.docs.values()).map(collection => Array.from(this.collection.docs.values()).map(collection =>
collection.getStore() collection.getStore()
) )
@@ -346,6 +347,7 @@ export class StarterDebugMenu extends ShadowlessElement {
const fileName = file.name.split('.').slice(0, -1).join('.'); const fileName = file.name.split('.').slice(0, -1).join('.');
const pageId = await HtmlTransformer.importHTMLToDoc({ const pageId = await HtmlTransformer.importHTMLToDoc({
collection: this.collection, collection: this.collection,
schema: this.editor.doc.schema,
html: text, html: text,
fileName, fileName,
}); });
@@ -369,6 +371,7 @@ export class StarterDebugMenu extends ShadowlessElement {
if (!file) return; if (!file) return;
const result = await HtmlTransformer.importHTMLZip({ const result = await HtmlTransformer.importHTMLZip({
collection: this.collection, collection: this.collection,
schema: this.editor.doc.schema,
imported: file, imported: file,
}); });
if (!this.editor.host) return; if (!this.editor.host) return;
@@ -396,6 +399,7 @@ export class StarterDebugMenu extends ShadowlessElement {
const fileName = file.name.split('.').slice(0, -1).join('.'); const fileName = file.name.split('.').slice(0, -1).join('.');
const pageId = await MarkdownTransformer.importMarkdownToDoc({ const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: this.collection, collection: this.collection,
schema: this.editor.doc.schema,
markdown: text, markdown: text,
fileName, fileName,
}); });
@@ -419,6 +423,7 @@ export class StarterDebugMenu extends ShadowlessElement {
if (!file) return; if (!file) return;
const result = await MarkdownTransformer.importMarkdownZip({ const result = await MarkdownTransformer.importMarkdownZip({
collection: this.collection, collection: this.collection,
schema: this.editor.doc.schema,
imported: file, imported: file,
}); });
if (!this.editor.host) return; if (!this.editor.host) return;
@@ -438,8 +443,9 @@ export class StarterDebugMenu extends ShadowlessElement {
multiple: false, multiple: false,
}); });
if (!file) return; if (!file) return;
const doc = this.editor.doc;
const job = new Transformer({ const job = new Transformer({
schema: this.collection.schema, schema: doc.schema,
blobCRUD: this.collection.blobSync, blobCRUD: this.collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => this.collection.createDoc({ id }), create: (id: string) => this.collection.createDoc({ id }),
@@ -465,6 +471,7 @@ export class StarterDebugMenu extends ShadowlessElement {
if (!file) return; if (!file) return;
const result = await NotionHtmlTransformer.importNotionZip({ const result = await NotionHtmlTransformer.importNotionZip({
collection: this.collection, collection: this.collection,
schema: this.editor.doc.schema,
imported: file, imported: file,
}); });
if (!this.editor.host) return; if (!this.editor.host) return;
@@ -488,7 +495,11 @@ export class StarterDebugMenu extends ShadowlessElement {
return; return;
} }
try { try {
const docs = await ZipTransformer.importDocs(this.collection, file); const docs = await ZipTransformer.importDocs(
this.collection,
this.editor.doc.schema,
file
);
for (const doc of docs) { for (const doc of docs) {
if (doc) { if (doc) {
const noteBlock = window.doc.getBlockByFlavour('affine:note'); const noteBlock = window.doc.getBlockByFlavour('affine:note');

View File

@@ -1,10 +1,8 @@
import { AffineSchemas, SpecProvider } from '@blocksuite/blocks'; import { SpecProvider } from '@blocksuite/blocks';
import { Schema } from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test'; import { TestWorkspace } from '@blocksuite/store/test';
export function createEmptyDoc() { export function createEmptyDoc() {
const schema = new Schema().register(AffineSchemas); const collection = new TestWorkspace();
const collection = new TestWorkspace({ schema });
collection.storeExtensions = SpecProvider._.getSpec('store').value; collection.storeExtensions = SpecProvider._.getSpec('store').value;
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc(); const doc = collection.createDoc();

View File

@@ -1,5 +1,5 @@
import { ZipTransformer } from '@blocksuite/blocks'; import { AffineSchemas, ZipTransformer } from '@blocksuite/blocks';
import { Text, type Workspace } from '@blocksuite/store'; import { Schema, Text, type Workspace } from '@blocksuite/store';
export async function affineSnapshot(collection: Workspace, id: string) { export async function affineSnapshot(collection: Workspace, id: string) {
const doc = collection.createDoc({ id }); const doc = collection.createDoc({ id });
@@ -13,7 +13,9 @@ export async function affineSnapshot(collection: Workspace, id: string) {
const path = '/apps/starter/data/snapshots/affine-default.zip'; const path = '/apps/starter/data/snapshots/affine-default.zip';
const response = await fetch(path); const response = await fetch(path);
const file = await response.blob(); const file = await response.blob();
await ZipTransformer.importDocs(collection, file); const schema = new Schema();
schema.register(AffineSchemas);
await ZipTransformer.importDocs(collection, schema, file);
} }
affineSnapshot.id = 'affine-snapshot'; affineSnapshot.id = 'affine-snapshot';

View File

@@ -49,7 +49,6 @@ export function createStarterDocCollection() {
const options: DocCollectionOptions = { const options: DocCollectionOptions = {
id: collectionId, id: collectionId,
schema,
idGenerator, idGenerator,
awarenessSources: [new BroadcastChannelAwarenessSource(id)], awarenessSources: [new BroadcastChannelAwarenessSource(id)],
docSources, docSources,
@@ -63,7 +62,7 @@ export function createStarterDocCollection() {
window.collection = collection; window.collection = collection;
window.blockSchemas = AffineSchemas; window.blockSchemas = AffineSchemas;
window.job = new Transformer({ window.job = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -66,33 +66,6 @@ export const defaultStore = {
tags: [], tags: [],
}, },
], ],
blockVersions: {
'affine:paragraph': 1,
'affine:page': 2,
'affine:database': 3,
'affine:data-view': 1,
'affine:list': 1,
'affine:note': 1,
'affine:divider': 1,
'affine:embed-youtube': 1,
'affine:embed-figma': 1,
'affine:embed-github': 1,
'affine:embed-loom': 1,
'affine:embed-html': 1,
'affine:embed-linked-doc': 1,
'affine:embed-synced-doc': 1,
'affine:image': 1,
'affine:latex': 1,
'affine:frame': 1,
'affine:code': 1,
'affine:surface': 5,
'affine:bookmark': 1,
'affine:attachment': 1,
'affine:surface-ref': 1,
'affine:edgeless-text': 1,
},
workspaceVersion: 2,
pageVersion: 2,
}, },
spaces: { spaces: {
'doc:home': { 'doc:home': {

View File

@@ -166,7 +166,7 @@ framework.impl(AIButtonProvider, {
const blockSuiteDoc = doc.blockSuiteDoc; const blockSuiteDoc = doc.blockSuiteDoc;
const transformer = new Transformer({ const transformer = new Transformer({
schema: blockSuiteDoc.workspace.schema, schema: blockSuiteDoc.schema,
blobCRUD: blockSuiteDoc.workspace.blobSync, blobCRUD: blockSuiteDoc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => blockSuiteDoc.workspace.createDoc({ id }), create: (id: string) => blockSuiteDoc.workspace.createDoc({ id }),

View File

@@ -224,7 +224,7 @@ const frameworkProvider = framework.provider();
const blockSuiteDoc = doc.blockSuiteDoc; const blockSuiteDoc = doc.blockSuiteDoc;
const transformer = new Transformer({ const transformer = new Transformer({
schema: blockSuiteDoc.workspace.schema, schema: blockSuiteDoc.schema,
blobCRUD: blockSuiteDoc.workspace.blobSync, blobCRUD: blockSuiteDoc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => blockSuiteDoc.workspace.createDoc({ id }), create: (id: string) => blockSuiteDoc.workspace.createDoc({ id }),

View File

@@ -2,7 +2,11 @@ import {
type GfxCommonBlockProps, type GfxCommonBlockProps,
GfxCompatible, GfxCompatible,
} from '@blocksuite/affine/block-std/gfx'; } from '@blocksuite/affine/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/affine/store'; import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/affine/store';
type AIChatProps = { type AIChatProps = {
messages: string; // JSON string of ChatMessage[] 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) {} export class AIChatBlockModel extends GfxCompatible<AIChatProps>(BlockModel) {}

View File

@@ -214,7 +214,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
if (this._answers.length > 0) { if (this._answers.length > 0) {
const latestAnswer = this._answers.pop(); const latestAnswer = this._answers.pop();
this._answers = []; this._answers = [];
const schema = this.schema ?? this.host?.std.store.workspace.schema; const schema = this.schema ?? this.host?.std.store.schema;
let provider: ServiceProvider; let provider: ServiceProvider;
if (this.host) { if (this.host) {
provider = this.host.std.provider; provider = this.host.std.provider;

View File

@@ -1,3 +1,5 @@
import { SpecProvider } from '@blocksuite/affine/blocks';
import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block'; import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block';
import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block'; import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block';
import { import {
@@ -10,6 +12,7 @@ import {
} from './blocks/ai-chat-block/components/chat-images'; } from './blocks/ai-chat-block/components/chat-images';
import { ImagePlaceholder } from './blocks/ai-chat-block/components/image-placeholder'; import { ImagePlaceholder } from './blocks/ai-chat-block/components/image-placeholder';
import { UserInfo } from './blocks/ai-chat-block/components/user-info'; import { UserInfo } from './blocks/ai-chat-block/components/user-info';
import { AIChatBlockSchemaExtension } from './blocks/ai-chat-block/model';
import { ChatPanel } from './chat-panel'; import { ChatPanel } from './chat-panel';
import { ActionWrapper } from './chat-panel/actions/action-wrapper'; import { ActionWrapper } from './chat-panel/actions/action-wrapper';
import { ChatText } from './chat-panel/actions/chat-text'; import { ChatText } from './chat-panel/actions/chat-text';
@@ -126,4 +129,6 @@ export function registerAIEffects() {
'edgeless-copilot-toolbar-entry', 'edgeless-copilot-toolbar-entry',
EdgelessCopilotToolbarEntry EdgelessCopilotToolbarEntry
); );
SpecProvider._.extendSpec('store', [AIChatBlockSchemaExtension]);
} }

View File

@@ -1,9 +1,8 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace'; import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std'; import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std';
import { SpecProvider } from '@blocksuite/affine/blocks'; import { SpecProvider } from '@blocksuite/affine/blocks';
import { AffineSchemas } from '@blocksuite/affine/blocks/schemas';
import { WithDisposable } from '@blocksuite/affine/global/utils'; 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 { css, html, LitElement, nothing } from 'lit';
import { property, query } from 'lit/decorators.js'; import { property, query } from 'lit/decorators.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js'; import { createRef, type Ref, ref } from 'lit/directives/ref.js';
@@ -216,9 +215,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
override connectedCallback(): void { override connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
const schema = new Schema().register(AffineSchemas);
const collection = new WorkspaceImpl({ const collection = new WorkspaceImpl({
schema,
id: 'SLIDES_PREVIEW', id: 'SLIDES_PREVIEW',
}); });
collection.meta.initialize(); collection.meta.initialize();

View File

@@ -4,7 +4,6 @@ import {
MarkdownInlineToDeltaAdapterExtensions, MarkdownInlineToDeltaAdapterExtensions,
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
import { Container } from '@blocksuite/affine/global/di'; import { Container } from '@blocksuite/affine/global/di';
import { Schema } from '@blocksuite/affine/store';
import { TestWorkspace } from '@blocksuite/affine/store/test'; import { TestWorkspace } from '@blocksuite/affine/store/test';
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
@@ -29,7 +28,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
- Text D - Text D
- Text E - Text E
`; `;
const collection = new TestWorkspace({ schema: new Schema() }); const collection = new TestWorkspace();
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc(); const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider); const nodes = markdownToMindmap(markdown, doc, provider);
@@ -67,7 +66,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
- Text D - Text D
- Text E - Text E
`; `;
const collection = new TestWorkspace({ schema: new Schema() }); const collection = new TestWorkspace();
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc(); const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider); const nodes = markdownToMindmap(markdown, doc, provider);
@@ -99,7 +98,7 @@ describe('markdownToMindmap: convert markdown list to a mind map tree', () => {
test('empty case', () => { test('empty case', () => {
const markdown = ''; const markdown = '';
const collection = new TestWorkspace({ schema: new Schema() }); const collection = new TestWorkspace();
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc(); const doc = collection.createDoc();
const nodes = markdownToMindmap(markdown, doc, provider); const nodes = markdownToMindmap(markdown, doc, provider);

View File

@@ -99,7 +99,6 @@ export class MiniMindmapPreview extends WithDisposable(LitElement) {
const collection = new WorkspaceImpl({ const collection = new WorkspaceImpl({
id: 'MINI_MINDMAP_TEMPORARY', id: 'MINI_MINDMAP_TEMPORARY',
schema,
}); });
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc({ id: 'doc:home' }).load(); const doc = collection.createDoc({ id: 'doc:home' }).load();
@@ -237,7 +236,7 @@ export const markdownToMindmap = (
) => { ) => {
let result: Node | null = null; let result: Node | null = null;
const transformer = new Transformer({ const transformer = new Transformer({
schema: doc.workspace.schema, schema: doc.schema,
blobCRUD: doc.workspace.blobSync, blobCRUD: doc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }), create: (id: string) => doc.workspace.createDoc({ id }),

View File

@@ -197,7 +197,7 @@ function getNoteBlockModels(doc: Store) {
async function getTransformer(doc: Store) { async function getTransformer(doc: Store) {
return new Transformer({ return new Transformer({
schema: doc.workspace.schema, schema: doc.schema,
blobCRUD: doc.workspace.blobSync, blobCRUD: doc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }), create: (id: string) => doc.workspace.createDoc({ id }),

View File

@@ -4,6 +4,7 @@ import { AppSidebarService } from '@affine/core/modules/app-sidebar';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { type DocMode } from '@blocksuite/affine/blocks'; import { type DocMode } from '@blocksuite/affine/blocks';
import type { Workspace } from '@blocksuite/affine/store'; import type { Workspace } from '@blocksuite/affine/store';
import { useServices } from '@toeverything/infra'; import { useServices } from '@toeverything/infra';
@@ -110,6 +111,7 @@ export const usePageHelper = (docCollection: Workspace) => {
}; };
showImportModal({ showImportModal({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
onSuccess, onSuccess,
onFail: message => { onFail: message => {
reject(new Error(message)); reject(new Error(message));

View File

@@ -81,7 +81,7 @@ export async function getContentFromSlice(
type: 'markdown' | 'plain-text' = 'markdown' type: 'markdown' | 'plain-text' = 'markdown'
) { ) {
const transformer = new Transformer({ const transformer = new Transformer({
schema: host.std.store.workspace.schema, schema: host.std.store.schema,
blobCRUD: host.std.store.workspace.blobSync, blobCRUD: host.std.store.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => host.std.store.workspace.createDoc({ id }), create: (id: string) => host.std.store.workspace.createDoc({ id }),
@@ -114,7 +114,7 @@ export const markdownToSnapshot = async (
host: EditorHost host: EditorHost
) => { ) => {
const transformer = new Transformer({ const transformer = new Transformer({
schema: host.std.store.workspace.schema, schema: host.std.store.schema,
blobCRUD: host.std.store.workspace.blobSync, blobCRUD: host.std.store.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => host.std.store.workspace.createDoc({ id }), create: (id: string) => host.std.store.workspace.createDoc({ id }),
@@ -174,12 +174,10 @@ export async function markDownToDoc(
middlewares?: TransformerMiddleware[] middlewares?: TransformerMiddleware[]
) { ) {
// Should not create a new doc in the original collection // Should not create a new doc in the original collection
const collection = new WorkspaceImpl({ const collection = new WorkspaceImpl();
schema,
});
collection.meta.initialize(); collection.meta.initialize();
const transformer = new Transformer({ const transformer = new Transformer({
schema: collection.schema, schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -2,7 +2,6 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-
import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page'; import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page';
import { FetchService, GraphQLService } from '@affine/core/modules/cloud'; import { FetchService, GraphQLService } from '@affine/core/modules/cloud';
import { import {
getAFFiNEWorkspaceSchema,
type WorkspaceFlavourProvider, type WorkspaceFlavourProvider,
WorkspaceService, WorkspaceService,
WorkspacesService, WorkspacesService,
@@ -131,7 +130,6 @@ const getOrCreateShellWorkspace = (
return Promise.resolve([]); return Promise.resolve([]);
}, },
}, },
schema: getAFFiNEWorkspaceSchema(),
}); });
docCollectionMap.set(workspaceId, docCollection); docCollectionMap.set(workspaceId, docCollection);
docCollection.doc.emit('sync', [true, docCollection.doc]); docCollection.doc.emit('sync', [true, docCollection.doc]);

View File

@@ -4,6 +4,7 @@ import {
resolveGlobalLoadingEventAtom, resolveGlobalLoadingEventAtom,
} from '@affine/component/global-loading'; } from '@affine/component/global-loading';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace/global-schema';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import type { BlockStdScope } from '@blocksuite/affine/block-std'; import type { BlockStdScope } from '@blocksuite/affine/block-std';
@@ -59,7 +60,7 @@ async function exportDoc(
config: AdapterConfig config: AdapterConfig
) { ) {
const transformer = new Transformer({ const transformer = new Transformer({
schema: doc.workspace.schema, schema: getAFFiNEWorkspaceSchema(),
blobCRUD: doc.workspace.blobSync, blobCRUD: doc.workspace.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }), create: (id: string) => doc.workspace.createDoc({ id }),
@@ -148,7 +149,11 @@ async function exportHandler({
await exportToMarkdown(page, editorRoot?.std); await exportToMarkdown(page, editorRoot?.std);
return; return;
case 'snapshot': case 'snapshot':
await ZipTransformer.exportDocs(page.workspace, [page]); await ZipTransformer.exportDocs(
page.workspace,
getAFFiNEWorkspaceSchema(),
[page]
);
return; return;
case 'pdf': case 'pdf':
await printToPdf(editorContainer); await printToPdf(editorContainer);

View File

@@ -3,9 +3,9 @@
*/ */
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
import { AffineSchemas } from '@blocksuite/affine/blocks/schemas'; import { StoreExtensions } from '@blocksuite/affine/blocks';
import { assertExists } from '@blocksuite/affine/global/utils'; import { assertExists } from '@blocksuite/affine/global/utils';
import { Schema, type Store, Text } from '@blocksuite/affine/store'; import { type Store, Text } from '@blocksuite/affine/store';
import { TestWorkspace } from '@blocksuite/affine/store/test'; import { TestWorkspace } from '@blocksuite/affine/store/test';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
@@ -14,12 +14,11 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import { useBlockSuitePagePreview } from '../use-block-suite-page-preview'; import { useBlockSuitePagePreview } from '../use-block-suite-page-preview';
let docCollection: TestWorkspace; let docCollection: TestWorkspace;
const schema = new Schema(); const extensions = StoreExtensions;
schema.register(AffineSchemas);
beforeEach(async () => { beforeEach(async () => {
vi.useFakeTimers({ toFake: ['requestIdleCallback'] }); vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
docCollection = new TestWorkspace({ id: 'test', schema }); docCollection = new TestWorkspace({ id: 'test' });
docCollection.meta.initialize(); docCollection.meta.initialize();
const initPage = async (page: Store) => { const initPage = async (page: Store) => {
page.load(); page.load();
@@ -31,7 +30,7 @@ beforeEach(async () => {
const frameId = page.addBlock('affine:note', {}, pageBlockId); const frameId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, frameId); page.addBlock('affine:paragraph', {}, frameId);
}; };
await initPage(docCollection.createDoc({ id: 'page0' })); await initPage(docCollection.createDoc({ id: 'page0', extensions }));
}); });
describe('useBlockSuitePagePreview', () => { describe('useBlockSuitePagePreview', () => {

View File

@@ -26,7 +26,10 @@ import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands'; import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
import { QuickSearchContainer } from '@affine/core/modules/quicksearch'; import { QuickSearchContainer } from '@affine/core/modules/quicksearch';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace'; import {
getAFFiNEWorkspaceSchema,
WorkspaceService,
} from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track'; import track from '@affine/track';
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks'; import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
@@ -74,6 +77,7 @@ export const WorkspaceSideEffects = () => {
throwIfAborted(abort); throwIfAborted(abort);
const [doc] = await ZipTransformer.importDocs( const [doc] = await ZipTransformer.importDocs(
currentWorkspace.docCollection, currentWorkspace.docCollection,
getAFFiNEWorkspaceSchema(),
templateBlob templateBlob
); );
if (doc) { if (doc) {

View File

@@ -5,7 +5,10 @@ import type {
WORKSPACE_DIALOG_SCHEMA, WORKSPACE_DIALOG_SCHEMA,
} from '@affine/core/modules/dialogs'; } from '@affine/core/modules/dialogs';
import { UrlService } from '@affine/core/modules/url'; import { UrlService } from '@affine/core/modules/url';
import { WorkspaceService } from '@affine/core/modules/workspace'; import {
getAFFiNEWorkspaceSchema,
WorkspaceService,
} from '@affine/core/modules/workspace';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track'; import track from '@affine/track';
@@ -141,6 +144,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
const fileName = file.name.split('.').slice(0, -1).join('.'); const fileName = file.name.split('.').slice(0, -1).join('.');
const docId = await MarkdownTransformer.importMarkdownToDoc({ const docId = await MarkdownTransformer.importMarkdownToDoc({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
markdown: text, markdown: text,
fileName, fileName,
}); });
@@ -159,6 +163,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
} }
const docIds = await MarkdownTransformer.importMarkdownZip({ const docIds = await MarkdownTransformer.importMarkdownZip({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
imported: file, imported: file,
}); });
return { return {
@@ -178,6 +183,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
const fileName = file.name.split('.').slice(0, -1).join('.'); const fileName = file.name.split('.').slice(0, -1).join('.');
const docId = await HtmlTransformer.importHTMLToDoc({ const docId = await HtmlTransformer.importHTMLToDoc({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
html: text, html: text,
fileName, fileName,
}); });
@@ -197,6 +203,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
const { entryId, pageIds, isWorkspaceFile } = const { entryId, pageIds, isWorkspaceFile } =
await NotionHtmlTransformer.importNotionZip({ await NotionHtmlTransformer.importNotionZip({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
imported: file, imported: file,
}); });
return { return {
@@ -212,7 +219,13 @@ const importConfigs: Record<ImportType, ImportConfig> = {
if (Array.isArray(file)) { if (Array.isArray(file)) {
throw new Error('Expected a single zip file for snapshot import'); throw new Error('Expected a single zip file for snapshot import');
} }
const docIds = (await ZipTransformer.importDocs(docCollection, file)) const docIds = (
await ZipTransformer.importDocs(
docCollection,
getAFFiNEWorkspaceSchema(),
file
)
)
.filter(doc => doc !== undefined) .filter(doc => doc !== undefined)
.map(doc => doc.id); .map(doc => doc.id);

View File

@@ -1,7 +1,7 @@
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace'; import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
import { AffineSchemas } from '@blocksuite/affine/blocks';
import type { DocSnapshot, Store } from '@blocksuite/affine/store'; import type { DocSnapshot, Store } from '@blocksuite/affine/store';
import { Schema, Transformer } from '@blocksuite/affine/store'; import { Transformer } from '@blocksuite/affine/store';
const getCollection = (() => { const getCollection = (() => {
let collection: WorkspaceImpl | null = null; let collection: WorkspaceImpl | null = null;
@@ -9,9 +9,7 @@ const getCollection = (() => {
if (collection) { if (collection) {
return collection; return collection;
} }
const schema = new Schema(); collection = new WorkspaceImpl({});
schema.register(AffineSchemas);
collection = new WorkspaceImpl({ schema });
collection.meta.initialize(); collection.meta.initialize();
return collection; return collection;
}; };
@@ -86,7 +84,7 @@ async function initDoc(name: DocName) {
const snapshot = (await loaders[name]()) as DocSnapshot; const snapshot = (await loaders[name]()) as DocSnapshot;
const collection = await getCollection(); const collection = await getCollection();
const transformer = new Transformer({ const transformer = new Transformer({
schema: collection.schema, schema: getAFFiNEWorkspaceSchema(),
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -10,6 +10,7 @@ import { DndService } from '@affine/core/modules/dnd/services';
import { GlobalContextService } from '@affine/core/modules/global-context'; import { GlobalContextService } from '@affine/core/modules/global-context';
import { OpenInAppGuard } from '@affine/core/modules/open-in-app'; import { OpenInAppGuard } from '@affine/core/modules/open-in-app';
import { import {
getAFFiNEWorkspaceSchema,
type Workspace, type Workspace,
type WorkspaceMetadata, type WorkspaceMetadata,
WorkspacesService, WorkspacesService,
@@ -279,6 +280,7 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
window.exportWorkspaceSnapshot = async (docs?: string[]) => { window.exportWorkspaceSnapshot = async (docs?: string[]) => {
await ZipTransformer.exportDocs( await ZipTransformer.exportDocs(
workspace.docCollection, workspace.docCollection,
getAFFiNEWorkspaceSchema(),
Array.from(workspace.docCollection.docs.values()) Array.from(workspace.docCollection.docs.values())
.filter(doc => (docs ? docs.includes(doc.id) : true)) .filter(doc => (docs ? docs.includes(doc.id) : true))
.map(doc => doc.getStore()) .map(doc => doc.getStore())
@@ -294,6 +296,7 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
const blob = new Blob([file], { type: 'application/zip' }); const blob = new Blob([file], { type: 'application/zip' });
const newDocs = await ZipTransformer.importDocs( const newDocs = await ZipTransformer.importDocs(
workspace.docCollection, workspace.docCollection,
getAFFiNEWorkspaceSchema(),
blob blob
); );
console.log( console.log(

View File

@@ -16,6 +16,7 @@ import {
initDocFromProps, initDocFromProps,
} from '../../../blocksuite/initialization'; } from '../../../blocksuite/initialization';
import type { DocProperties } from '../../db'; import type { DocProperties } from '../../db';
import { getAFFiNEWorkspaceSchema } from '../../workspace';
import type { Doc } from '../entities/doc'; import type { Doc } from '../entities/doc';
import { DocPropertyList } from '../entities/property-list'; import { DocPropertyList } from '../entities/property-list';
import { DocRecordList } from '../entities/record-list'; import { DocRecordList } from '../entities/record-list';
@@ -202,7 +203,7 @@ export class DocsService extends Service {
const collection = this.store.getBlocksuiteCollection(); const collection = this.store.getBlocksuiteCollection();
const transformer = new Transformer({ const transformer = new Transformer({
schema: collection.schema, schema: getAFFiNEWorkspaceSchema(),
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => collection.createDoc({ id }), create: (id: string) => collection.createDoc({ id }),

View File

@@ -116,7 +116,6 @@ const bookmarkFlavours = new Set([
const markdownPreviewDocCollection = new WorkspaceImpl({ const markdownPreviewDocCollection = new WorkspaceImpl({
id: 'indexer', id: 'indexer',
schema: blocksuiteSchema,
}); });
function generateMarkdownPreviewBuilder( function generateMarkdownPreviewBuilder(
@@ -190,7 +189,7 @@ function generateMarkdownPreviewBuilder(
const provider = container.provider(); const provider = container.provider();
const markdownAdapter = new MarkdownAdapter( const markdownAdapter = new MarkdownAdapter(
new Transformer({ new Transformer({
schema: markdownPreviewDocCollection.schema, schema: getAFFiNEWorkspaceSchema(),
blobCRUD: markdownPreviewDocCollection.blobSync, blobCRUD: markdownPreviewDocCollection.blobSync,
docCRUD: { docCRUD: {
create: (id: string) => markdownPreviewDocCollection.createDoc({ id }), create: (id: string) => markdownPreviewDocCollection.createDoc({ id }),

View File

@@ -2,7 +2,11 @@ import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
import { Service } from '@toeverything/infra'; import { Service } from '@toeverything/infra';
import { DocsService } from '../../doc'; import { DocsService } from '../../doc';
import type { WorkspaceMetadata, WorkspacesService } from '../../workspace'; import {
getAFFiNEWorkspaceSchema,
type WorkspaceMetadata,
type WorkspacesService,
} from '../../workspace';
export class ImportTemplateService extends Service { export class ImportTemplateService extends Service {
constructor(private readonly workspacesService: WorkspacesService) { constructor(private readonly workspacesService: WorkspacesService) {
@@ -21,6 +25,7 @@ export class ImportTemplateService extends Service {
await workspace.engine.doc.waitForDocReady(workspace.id); // wait for root doc ready await workspace.engine.doc.waitForDocReady(workspace.id); // wait for root doc ready
const [importedDoc] = await ZipTransformer.importDocs( const [importedDoc] = await ZipTransformer.importDocs(
workspace.docCollection, workspace.docCollection,
getAFFiNEWorkspaceSchema(),
new Blob([docBinary], { new Blob([docBinary], {
type: 'application/zip', type: 'application/zip',
}) })

View File

@@ -53,13 +53,12 @@ import {
WorkspaceServerService, WorkspaceServerService,
} from '../../cloud'; } from '../../cloud';
import type { GlobalState } from '../../storage'; import type { GlobalState } from '../../storage';
import { import type {
getAFFiNEWorkspaceSchema, Workspace,
type Workspace, WorkspaceFlavourProvider,
type WorkspaceFlavourProvider, WorkspaceFlavoursProvider,
type WorkspaceFlavoursProvider, WorkspaceMetadata,
type WorkspaceMetadata, WorkspaceProfileInfo,
type WorkspaceProfileInfo,
} from '../../workspace'; } from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impls/workspace'; import { WorkspaceImpl } from '../../workspace/impls/workspace';
import { getWorkspaceProfileWorker } from './out-worker'; import { getWorkspaceProfileWorker } from './out-worker';
@@ -163,7 +162,6 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
const docCollection = new WorkspaceImpl({ const docCollection = new WorkspaceImpl({
id: workspaceId, id: workspaceId,
schema: getAFFiNEWorkspaceSchema(),
blobSource: { blobSource: {
get: async key => { get: async key => {
const record = await blobStorage.get(key); const record = await blobStorage.get(key);

View File

@@ -32,12 +32,11 @@ import { Observable } from 'rxjs';
import { type Doc as YDoc, encodeStateAsUpdate } from 'yjs'; import { type Doc as YDoc, encodeStateAsUpdate } from 'yjs';
import { DesktopApiService } from '../../desktop-api'; import { DesktopApiService } from '../../desktop-api';
import { import type {
getAFFiNEWorkspaceSchema, WorkspaceFlavourProvider,
type WorkspaceFlavourProvider, WorkspaceFlavoursProvider,
type WorkspaceFlavoursProvider, WorkspaceMetadata,
type WorkspaceMetadata, WorkspaceProfileInfo,
type WorkspaceProfileInfo,
} from '../../workspace'; } from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impls/workspace'; import { WorkspaceImpl } from '../../workspace/impls/workspace';
import { getWorkspaceProfileWorker } from './out-worker'; import { getWorkspaceProfileWorker } from './out-worker';
@@ -145,7 +144,6 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
const docCollection = new WorkspaceImpl({ const docCollection = new WorkspaceImpl({
id: id, id: id,
schema: getAFFiNEWorkspaceSchema(),
blobSource: { blobSource: {
get: async key => { get: async key => {
const record = await blobStorage.get(key); const record = await blobStorage.get(key);

View File

@@ -3,7 +3,6 @@ import { Entity, LiveData } from '@toeverything/infra';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js'; import type { Awareness } from 'y-protocols/awareness.js';
import { getAFFiNEWorkspaceSchema } from '../global-schema';
import { WorkspaceImpl } from '../impls/workspace'; import { WorkspaceImpl } from '../impls/workspace';
import type { WorkspaceScope } from '../scopes/workspace'; import type { WorkspaceScope } from '../scopes/workspace';
import { WorkspaceEngineService } from '../services/engine'; import { WorkspaceEngineService } from '../services/engine';
@@ -51,7 +50,6 @@ export class Workspace extends Entity {
name: 'blob', name: 'blob',
readonly: false, readonly: false,
}, },
schema: getAFFiNEWorkspaceSchema(),
onLoadDoc: doc => this.engine.doc.connectDoc(doc), onLoadDoc: doc => this.engine.doc.connectDoc(doc),
onLoadAwareness: awareness => onLoadAwareness: awareness =>
this.engine.awareness.connectAwareness(awareness), this.engine.awareness.connectAwareness(awareness),

View File

@@ -147,10 +147,6 @@ export class DocImpl implements Doc {
return this._ready; return this._ready;
} }
get schema() {
return this.workspace.schema;
}
get spaceDoc() { get spaceDoc() {
return this._ySpaceDoc; return this._ySpaceDoc;
} }
@@ -174,13 +170,6 @@ export class DocImpl implements Doc {
return (readonly?.toString() as 'true' | 'false') ?? 'false'; 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) { private _handleYBlockAdd(id: string) {
this.slots.yBlockUpdated.emit({ type: 'add', id }); this.slots.yBlockUpdated.emit({ type: 'add', id });
} }
@@ -296,7 +285,6 @@ export class DocImpl implements Doc {
const doc = new Store({ const doc = new Store({
doc: this, doc: this,
schema: this.workspace.schema,
readonly, readonly,
query, query,
provider, provider,
@@ -316,10 +304,6 @@ export class DocImpl implements Doc {
this.spaceDoc.load(); this.spaceDoc.load();
this.workspace.onLoadDoc?.(this.spaceDoc); this.workspace.onLoadDoc?.(this.spaceDoc);
if ((this.workspace.meta.docs?.length ?? 0) <= 1) {
this._handleVersion();
}
this._initYBlocks(); this._initYBlocks();
this._yBlocks.forEach((_, id) => { this._yBlocks.forEach((_, id) => {

View File

@@ -3,20 +3,13 @@ import {
createYProxy, createYProxy,
type DocMeta, type DocMeta,
type DocsPropertiesMeta, type DocsPropertiesMeta,
type Workspace,
type WorkspaceMeta, type WorkspaceMeta,
} from '@blocksuite/affine/store'; } from '@blocksuite/affine/store';
import type * as Y from 'yjs'; import type * as Y from 'yjs';
const COLLECTION_VERSION = 2;
const PAGE_VERSION = 2;
type MetaState = { type MetaState = {
pages?: unknown[]; pages?: unknown[];
properties?: DocsPropertiesMeta; properties?: DocsPropertiesMeta;
workspaceVersion?: number;
pageVersion?: number;
blockVersions?: Record<string, number>;
name?: string; name?: string;
avatar?: string; avatar?: string;
}; };
@@ -102,25 +95,6 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
return this._proxy.pages; return this._proxy.pages;
} }
get hasVersion() {
if (!this._blockVersions || !this._pageVersion || !this._workspaceVersion) {
return false;
}
return Object.keys(this._blockVersions).length > 0;
}
private get _blockVersions() {
return this._proxy.blockVersions;
}
private get _pageVersion() {
return this._proxy.pageVersion;
}
private get _workspaceVersion() {
return this._proxy.workspaceVersion;
}
get yDocs() { get yDocs() {
return this._yMap.get('pages') as unknown as Y.Array<unknown>; return this._yMap.get('pages') as unknown as Y.Array<unknown>;
} }
@@ -220,33 +194,4 @@ export class WorkspaceMetaImpl implements WorkspaceMeta {
}); });
}, this._doc.clientID); }, this._doc.clientID);
} }
/**
* @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');
}
}
} }

View File

@@ -10,7 +10,6 @@ import {
type GetBlocksOptions, type GetBlocksOptions,
type IdGenerator, type IdGenerator,
nanoid, nanoid,
type Schema,
type Store, type Store,
type Workspace, type Workspace,
type WorkspaceMeta, type WorkspaceMeta,
@@ -28,15 +27,12 @@ import { WorkspaceMetaImpl } from './meta';
type WorkspaceOptions = { type WorkspaceOptions = {
id?: string; id?: string;
schema: Schema;
blobSource?: BlobSource; blobSource?: BlobSource;
onLoadDoc?: (doc: Y.Doc) => void; onLoadDoc?: (doc: Y.Doc) => void;
onLoadAwareness?: (awareness: Awareness) => void; onLoadAwareness?: (awareness: Awareness) => void;
}; };
export class WorkspaceImpl implements Workspace { export class WorkspaceImpl implements Workspace {
protected readonly _schema: Schema;
readonly awarenessStore: AwarenessStore; readonly awarenessStore: AwarenessStore;
readonly blobSync: BlobEngine; readonly blobSync: BlobEngine;
@@ -61,22 +57,15 @@ export class WorkspaceImpl implements Workspace {
return this.blockCollections; return this.blockCollections;
} }
get schema() {
return this._schema;
}
readonly onLoadDoc?: (doc: Y.Doc) => void; readonly onLoadDoc?: (doc: Y.Doc) => void;
readonly onLoadAwareness?: (awareness: Awareness) => void; readonly onLoadAwareness?: (awareness: Awareness) => void;
constructor({ constructor({
id, id,
schema,
blobSource, blobSource,
onLoadDoc, onLoadDoc,
onLoadAwareness, onLoadAwareness,
}: WorkspaceOptions) { }: WorkspaceOptions = {}) {
this._schema = schema;
this.id = id || ''; this.id = id || '';
this.doc = new Y.Doc({ guid: id }); this.doc = new Y.Doc({ guid: id });
this.awarenessStore = new AwarenessStore(new Awareness(this.doc)); this.awarenessStore = new AwarenessStore(new Awareness(this.doc));

View File

@@ -7,7 +7,10 @@ import onboardingUrl from '@affine/templates/onboarding.zip';
import { ZipTransformer } from '@blocksuite/affine/blocks'; import { ZipTransformer } from '@blocksuite/affine/blocks';
import { DocsService } from '../modules/doc'; import { DocsService } from '../modules/doc';
import type { WorkspacesService } from '../modules/workspace'; import {
getAFFiNEWorkspaceSchema,
type WorkspacesService,
} from '../modules/workspace';
export async function buildShowcaseWorkspace( export async function buildShowcaseWorkspace(
workspacesService: WorkspacesService, workspacesService: WorkspacesService,
@@ -19,7 +22,11 @@ export async function buildShowcaseWorkspace(
docCollection.meta.setName(workspaceName); docCollection.meta.setName(workspaceName);
const blob = await (await fetch(onboardingUrl)).blob(); const blob = await (await fetch(onboardingUrl)).blob();
await ZipTransformer.importDocs(docCollection, blob); await ZipTransformer.importDocs(
docCollection,
getAFFiNEWorkspaceSchema(),
blob
);
}); });
const { workspace, dispose } = workspacesService.open({ metadata: meta }); const { workspace, dispose } = workspacesService.open({ metadata: meta });