diff --git a/blocksuite/affine/block-embed/src/common/render-linked-doc.ts b/blocksuite/affine/block-embed/src/common/render-linked-doc.ts index 23b3559a66787..6c96c6dd995c3 100644 --- a/blocksuite/affine/block-embed/src/common/render-linked-doc.ts +++ b/blocksuite/affine/block-embed/src/common/render-linked-doc.ts @@ -13,7 +13,6 @@ import { type BlockModel, type Blocks, type BlockSnapshot, - BlockViewType, type DraftModel, type Query, Slice, @@ -194,7 +193,7 @@ async function renderNoteContent( }); const query: Query = { mode: 'strict', - match: ids.map(id => ({ id, viewType: BlockViewType.Display })), + match: ids.map(id => ({ id, viewType: 'display' })), }; const previewDoc = doc.doc.getBlocks({ query }); const previewSpec = SpecProvider.getInstance().getSpec('page:preview'); diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts index 5cad7e9278156..738ff45d36bed 100644 --- a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts @@ -25,12 +25,7 @@ import { } from '@blocksuite/block-std'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils'; -import { - BlockViewType, - type GetBlocksOptions, - type Query, - Text, -} from '@blocksuite/store'; +import { type GetBlocksOptions, type Query, Text } from '@blocksuite/store'; import { computed } from '@preact/signals-core'; import { html, type PropertyValues } from 'lit'; import { query, state } from 'lit/decorators.js'; @@ -106,7 +101,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent ({ id, - viewType: BlockViewType.Display, + viewType: 'display', })), }; this.query = query; diff --git a/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts b/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts index a2fa5b4e40323..cce841e068b5a 100644 --- a/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts +++ b/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts @@ -5,7 +5,7 @@ import { type DndEventState, } from '@blocksuite/block-std'; import { Point } from '@blocksuite/global/utils'; -import { BlockViewType, type Query } from '@blocksuite/store'; +import type { BlockViewType, Query } from '@blocksuite/store'; import { DragPreview } from '../components/drag-preview.js'; import type { AffineDragHandleWidget } from '../drag-handle.js'; @@ -24,7 +24,7 @@ export class PreviewHelper { const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map( id => ({ id, - viewType: BlockViewType.Display, + viewType: 'display', }) ); @@ -33,7 +33,7 @@ export class PreviewHelper { let parent: string | null = block; do { if (!selectedIds.includes(parent)) { - ids.push({ viewType: BlockViewType.Bypass, id: parent }); + ids.push({ viewType: 'bypass', id: parent }); } parent = this.widget.doc.getParent(parent)?.id ?? null; } while (parent && !ids.map(({ id }) => id).includes(parent)); @@ -43,7 +43,7 @@ export class PreviewHelper { const addChildren = (id: string) => { const children = this.widget.doc.getBlock(id)?.model.children ?? []; children.forEach(child => { - ids.push({ viewType: BlockViewType.Display, id: child.id }); + ids.push({ viewType: 'display', id: child.id }); addChildren(child.id); }); }; diff --git a/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts b/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts index 38b310708658d..630be03302108 100644 --- a/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts +++ b/blocksuite/blocks/src/root-block/edgeless/components/frame/frame-preview.ts @@ -12,7 +12,7 @@ import { DisposableGroup, WithDisposable, } from '@blocksuite/global/utils'; -import { type Blocks, BlockViewType, type Query } from '@blocksuite/store'; +import { type Blocks, type Query } from '@blocksuite/store'; import { css, html, nothing, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; @@ -71,7 +71,7 @@ export class FramePreview extends WithDisposable(ShadowlessElement) { match: [ { flavour: 'affine:frame', - viewType: BlockViewType.Hidden, + viewType: 'hidden', }, ], }; diff --git a/blocksuite/framework/block-std/src/view/element/block-component.ts b/blocksuite/framework/block-std/src/view/element/block-component.ts index 9933376d0ed4f..947571862e213 100644 --- a/blocksuite/framework/block-std/src/view/element/block-component.ts +++ b/blocksuite/framework/block-std/src/view/element/block-component.ts @@ -1,6 +1,6 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils'; -import { type BlockModel, Blocks, BlockViewType } from '@blocksuite/store'; +import { type BlockModel, Blocks, type BlockViewType } from '@blocksuite/store'; import { consume, provide } from '@lit/context'; import { computed } from '@preact/signals-core'; import { nothing, type TemplateResult } from 'lit'; @@ -187,9 +187,9 @@ export class BlockComponent< private _renderViewType(content: unknown) { return choose(this.viewType, [ - [BlockViewType.Display, () => content], - [BlockViewType.Hidden, () => nothing], - [BlockViewType.Bypass, () => this.renderChildren(this.model)], + ['display', () => content], + ['hidden', () => nothing], + ['bypass', () => this.renderChildren(this.model)], ]); } @@ -310,7 +310,7 @@ export class BlockComponent< accessor doc!: Blocks; @property({ attribute: false }) - accessor viewType: BlockViewType = BlockViewType.Display; + accessor viewType: BlockViewType = 'display'; @property({ attribute: false, diff --git a/blocksuite/framework/block-std/src/view/element/lit-host.ts b/blocksuite/framework/block-std/src/view/element/lit-host.ts index ac03a80c5ef01..396702611954e 100644 --- a/blocksuite/framework/block-std/src/view/element/lit-host.ts +++ b/blocksuite/framework/block-std/src/view/element/lit-host.ts @@ -4,7 +4,7 @@ import { handleError, } from '@blocksuite/global/exceptions'; import { SignalWatcher, Slot, WithDisposable } from '@blocksuite/global/utils'; -import { type BlockModel, Blocks, BlockViewType } from '@blocksuite/store'; +import { type BlockModel, Blocks } from '@blocksuite/store'; import { createContext, provide } from '@lit/context'; import { css, LitElement, nothing, type TemplateResult } from 'lit'; import { property } from 'lit/decorators.js'; @@ -44,7 +44,7 @@ export class EditorHost extends SignalWatcher( private readonly _renderModel = (model: BlockModel): TemplateResult => { const { flavour } = model; const block = this.doc.getBlock(model.id); - if (!block || block.blockViewType === BlockViewType.Hidden) { + if (!block || block.blockViewType === 'hidden') { return html`${nothing}`; } const schema = this.doc.schema.flavourSchemaMap.get(flavour); diff --git a/blocksuite/framework/store/src/__tests__/block.unit.spec.ts b/blocksuite/framework/store/src/__tests__/block.unit.spec.ts index 3fc0f8af2e59f..720936c1d090f 100644 --- a/blocksuite/framework/store/src/__tests__/block.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/block.unit.spec.ts @@ -3,14 +3,15 @@ import { describe, expect, test, vi } from 'vitest'; import * as Y from 'yjs'; import { + Block, defineBlockSchema, internalPrimitives, - Schema, type SchemaToModel, -} from '../schema/index.js'; -import { Block, type YBlock } from '../store/doc/block/index.js'; +} from '../model/block/index.js'; +import type { YBlock } from '../model/block/types.js'; +import { Schema } from '../schema/index.js'; +import { createAutoIncrementIdGenerator } from '../test/index.js'; import { TestWorkspace } from '../test/test-workspace.js'; -import { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; const pageSchema = defineBlockSchema({ flavour: 'page', diff --git a/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts b/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts index d69e864bdbd04..76f4003ddc1fd 100644 --- a/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts @@ -4,13 +4,11 @@ import type { Slot } from '@blocksuite/global/utils'; import { assert, beforeEach, describe, expect, it, vi } from 'vitest'; import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs'; -import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js'; -import type { BlockModel, Blocks, BlockSchemaType } from '../index.js'; +import type { BlockModel, Blocks, BlockSchemaType, DocMeta } from '../index.js'; import { Schema } from '../index.js'; import { Text } from '../reactive/text.js'; -import type { DocMeta } from '../store/workspace.js'; +import { createAutoIncrementIdGenerator } from '../test/index.js'; import { TestWorkspace } from '../test/test-workspace.js'; -import { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; import { NoteBlockSchema, ParagraphBlockSchema, @@ -115,8 +113,8 @@ describe('basic', () => { tags: [], }, ], - workspaceVersion: COLLECTION_VERSION, - pageVersion: PAGE_VERSION, + workspaceVersion: 2, + pageVersion: 2, blockVersions: { 'affine:note': 1, 'affine:page': 2, diff --git a/blocksuite/framework/store/src/__tests__/doc.unit.spec.ts b/blocksuite/framework/store/src/__tests__/doc.unit.spec.ts index d2b624d0f598c..0dec121105849 100644 --- a/blocksuite/framework/store/src/__tests__/doc.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/doc.unit.spec.ts @@ -2,9 +2,8 @@ import { expect, test, vi } from 'vitest'; import * as Y from 'yjs'; import { Schema } from '../schema/index.js'; -import { BlockViewType } from '../store/index.js'; +import { createAutoIncrementIdGenerator } from '../test/index.js'; import { TestWorkspace } from '../test/test-workspace.js'; -import { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; import { DividerBlockSchema, ListBlockSchema, @@ -220,7 +219,7 @@ test('query', () => { match: [ { flavour: 'affine:list', - viewType: BlockViewType.Hidden, + viewType: 'hidden', }, ], }, @@ -233,14 +232,14 @@ test('query', () => { const paragraph1 = doc1.addBlock('affine:paragraph', {}, note); const list1 = doc1.addBlock('affine:list' as never, {}, note); - expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe(BlockViewType.Display); - expect(doc2?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Display); - expect(doc3?.getBlock(list1)?.blockViewType).toBe(BlockViewType.Hidden); + expect(doc2?.getBlock(paragraph1)?.blockViewType).toBe('display'); + expect(doc2?.getBlock(list1)?.blockViewType).toBe('display'); + expect(doc3?.getBlock(list1)?.blockViewType).toBe('hidden'); const list2 = doc1.addBlock('affine:list' as never, {}, note); - expect(doc2?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Display); - expect(doc3?.getBlock(list2)?.blockViewType).toBe(BlockViewType.Hidden); + expect(doc2?.getBlock(list2)?.blockViewType).toBe('display'); + expect(doc3?.getBlock(list2)?.blockViewType).toBe('hidden'); }); test('local readonly', () => { diff --git a/blocksuite/framework/store/src/__tests__/schema.unit.spec.ts b/blocksuite/framework/store/src/__tests__/schema.unit.spec.ts index ae27a2f5e6a40..e2bdebd24323f 100644 --- a/blocksuite/framework/store/src/__tests__/schema.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/schema.unit.spec.ts @@ -1,12 +1,13 @@ import { literal } from 'lit/static-html.js'; import { describe, expect, it, vi } from 'vitest'; +import type { BlockModel } from '../model/block/block-model.js'; +import { defineBlockSchema } from '../model/block/zod.js'; // import some blocks -import { type BlockModel, defineBlockSchema } from '../schema/base.js'; import { SchemaValidateError } from '../schema/error.js'; import { Schema } from '../schema/index.js'; +import { createAutoIncrementIdGenerator } from '../test/index.js'; import { TestWorkspace } from '../test/test-workspace.js'; -import { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; import { DividerBlockSchema, ListBlockSchema, diff --git a/blocksuite/framework/store/src/__tests__/test-schema.ts b/blocksuite/framework/store/src/__tests__/test-schema.ts index 87349b09d1df4..2a132ddad001f 100644 --- a/blocksuite/framework/store/src/__tests__/test-schema.ts +++ b/blocksuite/framework/store/src/__tests__/test-schema.ts @@ -1,4 +1,4 @@ -import { defineBlockSchema, type SchemaToModel } from '../schema/index.js'; +import { defineBlockSchema, type SchemaToModel } from '../model/index.js'; export const RootBlockSchema = defineBlockSchema({ flavour: 'affine:page', diff --git a/blocksuite/framework/store/src/__tests__/transformer.unit.spec.ts b/blocksuite/framework/store/src/__tests__/transformer.unit.spec.ts index 51067ac912cbb..a5a634ab2f989 100644 --- a/blocksuite/framework/store/src/__tests__/transformer.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/transformer.unit.spec.ts @@ -2,16 +2,13 @@ import { expect, test } from 'vitest'; import * as Y from 'yjs'; import { MemoryBlobCRUD } from '../adapter/index.js'; +import type { BlockModel } from '../model/block/block-model.js'; +import { defineBlockSchema, type SchemaToModel } from '../model/block/zod.js'; import { Text } from '../reactive/index.js'; -import { - type BlockModel, - defineBlockSchema, - Schema, - type SchemaToModel, -} from '../schema/index.js'; +import { Schema } from '../schema/index.js'; +import { createAutoIncrementIdGenerator } from '../test/index.js'; import { TestWorkspace } from '../test/test-workspace.js'; import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js'; -import { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; const docSchema = defineBlockSchema({ flavour: 'page', diff --git a/blocksuite/framework/store/src/adapter/base.ts b/blocksuite/framework/store/src/adapter/base.ts index ee855dc4e3dfb..00c9bf0eef710 100644 --- a/blocksuite/framework/store/src/adapter/base.ts +++ b/blocksuite/framework/store/src/adapter/base.ts @@ -1,8 +1,8 @@ import { BlockSuiteError } from '@blocksuite/global/exceptions'; -import type { Blocks } from '../store/index.js'; +import type { Blocks, DraftModel } from '../model/index.js'; import type { AssetsManager } from '../transformer/assets.js'; -import type { DraftModel, Job, Slice } from '../transformer/index.js'; +import type { Job, Slice } from '../transformer/index.js'; import type { BlockSnapshot, DocSnapshot, diff --git a/blocksuite/framework/store/src/consts.ts b/blocksuite/framework/store/src/consts.ts index 91f85c961e97b..db824f3157fef 100644 --- a/blocksuite/framework/store/src/consts.ts +++ b/blocksuite/framework/store/src/consts.ts @@ -1,7 +1,3 @@ -export const COLLECTION_VERSION = 2; - -export const PAGE_VERSION = 2; - export const SCHEMA_NOT_FOUND_MESSAGE = 'Schema not found. The block flavour may not be registered.'; diff --git a/blocksuite/framework/store/src/index.ts b/blocksuite/framework/store/src/index.ts index 0ad17ed650e53..84b5e248cc2ad 100644 --- a/blocksuite/framework/store/src/index.ts +++ b/blocksuite/framework/store/src/index.ts @@ -2,12 +2,11 @@ /// export * from './adapter/index.js'; +export * from './model/index.js'; export * from './reactive/index.js'; export * from './schema/index.js'; -export * from './store/index.js'; export * from './transformer/index.js'; export { type IdGenerator, nanoid, uuidv4 } from './utils/id-generator.js'; -export * as Utils from './utils/utils.js'; export * from './yjs/index.js'; const env = diff --git a/blocksuite/framework/store/src/model/block/block-model.ts b/blocksuite/framework/store/src/model/block/block-model.ts new file mode 100644 index 0000000000000..61f86ac006b3f --- /dev/null +++ b/blocksuite/framework/store/src/model/block/block-model.ts @@ -0,0 +1,152 @@ +import { type Disposable, Slot } from '@blocksuite/global/utils'; +import { computed, type Signal, signal } from '@preact/signals-core'; + +import type { Text } from '../../reactive/index.js'; +import type { Blocks } from '../blocks/blocks.js'; +import type { YBlock } from './types.js'; +import type { RoleType } from './zod.js'; + +type SignaledProps = Props & { + [P in keyof Props & string as `${P}$`]: Signal; +}; +/** + * The MagicProps function is used to append the props to the class. + * For example: + * + * ```ts + * class MyBlock extends MagicProps()<{ foo: string }> {} + * const myBlock = new MyBlock(); + * // You'll get type checking for the foo prop + * myBlock.foo = 'bar'; + * ``` + */ +function MagicProps(): { + new (): Props; +} { + return class {} as never; +} + +const modelLabel = Symbol('model_label'); + +// @ts-expect-error allow magic props +export class BlockModel< + Props extends object = object, + PropsSignal extends object = SignaledProps, +> extends MagicProps() { + private readonly _children = signal([]); + + /** + * @deprecated use doc instead + */ + page!: Blocks; + + private readonly _childModels = computed(() => { + const value: BlockModel[] = []; + this._children.value.forEach(id => { + const block = this.page.getBlock$(id); + if (block) { + value.push(block.model); + } + }); + return value; + }); + + private readonly _onCreated: Disposable; + + private readonly _onDeleted: Disposable; + + childMap = computed(() => + this._children.value.reduce((map, id, index) => { + map.set(id, index); + return map; + }, new Map()) + ); + + created = new Slot(); + + deleted = new Slot(); + + flavour!: string; + + id!: string; + + isEmpty = computed(() => { + return this._children.value.length === 0; + }); + + keys!: string[]; + + // This is used to avoid https://stackoverflow.com/questions/55886792/infer-typescript-generic-class-type + [modelLabel]: Props = 'type_info_label' as never; + + pop!: (prop: keyof Props & string) => void; + + propsUpdated = new Slot<{ key: string }>(); + + role!: RoleType; + + stash!: (prop: keyof Props & string) => void; + + // text is optional + text?: Text; + + version!: number; + + yBlock!: YBlock; + + get children() { + return this._childModels.value; + } + + get doc() { + return this.page; + } + + set doc(doc: Blocks) { + this.page = doc; + } + + get parent() { + return this.doc.getParent(this); + } + + constructor() { + super(); + this._onCreated = this.created.once(() => { + this._children.value = this.yBlock.get('sys:children').toArray(); + this.yBlock.get('sys:children').observe(event => { + this._children.value = event.target.toArray(); + }); + this.yBlock.observe(event => { + if (event.keysChanged.has('sys:children')) { + this._children.value = this.yBlock.get('sys:children').toArray(); + } + }); + }); + this._onDeleted = this.deleted.once(() => { + this._onCreated.dispose(); + }); + } + + dispose() { + this.created.dispose(); + this.deleted.dispose(); + this.propsUpdated.dispose(); + } + + firstChild(): BlockModel | null { + return this.children[0] || null; + } + + lastChild(): BlockModel | null { + if (!this.children.length) { + return this; + } + return this.children[this.children.length - 1].lastChild(); + } + + [Symbol.dispose]() { + this._onCreated.dispose(); + this._onDeleted.dispose(); + } +} diff --git a/blocksuite/framework/store/src/store/doc/block/index.ts b/blocksuite/framework/store/src/model/block/block.ts similarity index 80% rename from blocksuite/framework/store/src/store/doc/block/index.ts rename to blocksuite/framework/store/src/model/block/block.ts index a449e31c9f951..b4b168507c9b4 100644 --- a/blocksuite/framework/store/src/store/doc/block/index.ts +++ b/blocksuite/framework/store/src/model/block/block.ts @@ -1,15 +1,14 @@ -import type { Schema } from '../../../schema/index.js'; -import { BlockViewType } from '../consts.js'; -import type { Blocks } from '../doc.js'; +import type { Schema } from '../../schema/index.js'; +import type { Blocks } from '../blocks/blocks.js'; import { SyncController } from './sync-controller.js'; import type { BlockOptions, YBlock } from './types.js'; -export * from './types.js'; +export type BlockViewType = 'bypass' | 'display' | 'hidden'; export class Block { private readonly _syncController: SyncController; - blockViewType: BlockViewType = BlockViewType.Display; + blockViewType: BlockViewType = 'display'; get flavour() { return this._syncController.flavour; diff --git a/blocksuite/framework/store/src/transformer/draft.ts b/blocksuite/framework/store/src/model/block/draft.ts similarity index 93% rename from blocksuite/framework/store/src/transformer/draft.ts rename to blocksuite/framework/store/src/model/block/draft.ts index c1a027c370b88..e9e764c609bcb 100644 --- a/blocksuite/framework/store/src/transformer/draft.ts +++ b/blocksuite/framework/store/src/model/block/draft.ts @@ -1,4 +1,4 @@ -import type { BlockModel } from '../schema/base.js'; +import type { BlockModel } from './block-model.js'; type PropsInDraft = 'version' | 'flavour' | 'role' | 'id' | 'keys' | 'text'; diff --git a/blocksuite/framework/store/src/model/block/index.ts b/blocksuite/framework/store/src/model/block/index.ts new file mode 100644 index 0000000000000..c27ff1e9debe7 --- /dev/null +++ b/blocksuite/framework/store/src/model/block/index.ts @@ -0,0 +1,5 @@ +export * from './block.js'; +export * from './block-model.js'; +export * from './draft.js'; +export * from './types.js'; +export * from './zod.js'; diff --git a/blocksuite/framework/store/src/store/doc/block/sync-controller.ts b/blocksuite/framework/store/src/model/block/sync-controller.ts similarity index 97% rename from blocksuite/framework/store/src/store/doc/block/sync-controller.ts rename to blocksuite/framework/store/src/model/block/sync-controller.ts index d063e3e054d4c..963165889143d 100644 --- a/blocksuite/framework/store/src/store/doc/block/sync-controller.ts +++ b/blocksuite/framework/store/src/model/block/sync-controller.ts @@ -9,11 +9,12 @@ import { native2Y, type UnRecord, y2Native, -} from '../../../reactive/index.js'; -import { BlockModel, internalPrimitives } from '../../../schema/base.js'; -import type { Schema } from '../../../schema/schema.js'; -import type { Blocks } from '../doc.js'; +} from '../../reactive/index.js'; +import type { Schema } from '../../schema/schema.js'; +import type { Blocks } from '../blocks/blocks.js'; +import { BlockModel } from './block-model.js'; import type { YBlock } from './types.js'; +import { internalPrimitives } from './zod.js'; /** * @internal diff --git a/blocksuite/framework/store/src/store/doc/block/types.ts b/blocksuite/framework/store/src/model/block/types.ts similarity index 62% rename from blocksuite/framework/store/src/store/doc/block/types.ts rename to blocksuite/framework/store/src/model/block/types.ts index db7bfcb2341fa..7224b4e5d3e64 100644 --- a/blocksuite/framework/store/src/store/doc/block/types.ts +++ b/blocksuite/framework/store/src/model/block/types.ts @@ -1,5 +1,6 @@ import type * as Y from 'yjs'; +import type { BlockModel } from './block-model.js'; import type { Block } from './index.js'; export type YBlock = Y.Map & { @@ -11,3 +12,10 @@ export type YBlock = Y.Map & { export type BlockOptions = { onChange?: (block: Block, key: string, value: unknown) => void; }; + +export type BlockSysProps = { + id: string; + flavour: string; + children?: BlockModel[]; +}; +export type BlockProps = BlockSysProps & Record; diff --git a/blocksuite/framework/store/src/model/block/zod.ts b/blocksuite/framework/store/src/model/block/zod.ts new file mode 100644 index 0000000000000..7e5e5b64fdc55 --- /dev/null +++ b/blocksuite/framework/store/src/model/block/zod.ts @@ -0,0 +1,124 @@ +import type * as Y from 'yjs'; +import { z } from 'zod'; + +import { Boxed, Text } from '../../reactive/index.js'; +import type { BaseBlockTransformer } from '../../transformer/base.js'; +import type { BlockModel } from './block-model.js'; + +const FlavourSchema = z.string(); +const ParentSchema = z.array(z.string()).optional(); +const ContentSchema = z.array(z.string()).optional(); +const role = ['root', 'hub', 'content'] as const; +const RoleSchema = z.enum(role); + +export type RoleType = (typeof role)[number]; + +export interface InternalPrimitives { + Text: (input?: Y.Text | string) => Text; + Boxed: (input: T) => Boxed; +} + +export const internalPrimitives: InternalPrimitives = Object.freeze({ + Text: (input: Y.Text | string = '') => new Text(input), + Boxed: (input: T) => new Boxed(input), +}); + +export const BlockSchema = z.object({ + version: z.number(), + model: z.object({ + role: RoleSchema, + flavour: FlavourSchema, + parent: ParentSchema, + children: ContentSchema, + props: z + .function() + .args(z.custom()) + .returns(z.record(z.any())) + .optional(), + toModel: z.function().args().returns(z.custom()).optional(), + }), + transformer: z + .function() + .args() + .returns(z.custom()) + .optional(), +}); + +export type BlockSchemaType = z.infer; + +export type PropsGetter = ( + internalPrimitives: InternalPrimitives +) => Props; + +export type SchemaToModel< + Schema extends { + model: { + props: PropsGetter; + flavour: string; + }; + }, +> = BlockModel> & + ReturnType & { + flavour: Schema['model']['flavour']; + }; + +export function defineBlockSchema< + Flavour extends string, + Role extends RoleType, + Props extends object, + Metadata extends Readonly<{ + version: number; + role: Role; + parent?: string[]; + children?: string[]; + }>, + Model extends BlockModel, + Transformer extends BaseBlockTransformer, +>(options: { + flavour: Flavour; + metadata: Metadata; + props?: (internalPrimitives: InternalPrimitives) => Props; + toModel?: () => Model; + transformer?: () => Transformer; +}): { + version: number; + model: { + props: PropsGetter; + flavour: Flavour; + } & Metadata; + transformer?: () => Transformer; +}; + +export function defineBlockSchema({ + flavour, + props, + metadata, + toModel, + transformer, +}: { + flavour: string; + metadata: { + version: number; + role: RoleType; + parent?: string[]; + children?: string[]; + }; + props?: (internalPrimitives: InternalPrimitives) => Record; + toModel?: () => BlockModel; + transformer?: () => BaseBlockTransformer; +}): BlockSchemaType { + const schema = { + version: metadata.version, + model: { + role: metadata.role, + parent: metadata.parent, + children: metadata.children, + flavour, + props, + toModel, + }, + transformer, + } satisfies z.infer; + BlockSchema.parse(schema); + return schema; +} diff --git a/blocksuite/framework/store/src/store/doc/doc.ts b/blocksuite/framework/store/src/model/blocks/blocks.ts similarity index 97% rename from blocksuite/framework/store/src/store/doc/doc.ts rename to blocksuite/framework/store/src/model/blocks/blocks.ts index 340d73c9565ba..a0f20f4358419 100644 --- a/blocksuite/framework/store/src/store/doc/doc.ts +++ b/blocksuite/framework/store/src/model/blocks/blocks.ts @@ -2,14 +2,18 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { type Disposable, Slot } from '@blocksuite/global/utils'; import { signal } from '@preact/signals-core'; -import type { BlockModel, Schema } from '../../schema/index.js'; -import type { DraftModel } from '../../transformer/index.js'; -import { syncBlockProps } from '../../utils/utils.js'; -import type { BlockProps, Doc } from '../workspace.js'; -import type { BlockOptions } from './block/index.js'; -import { Block } from './block/index.js'; +import type { Schema } from '../../schema/index.js'; +import { + Block, + type BlockModel, + type BlockOptions, + type BlockProps, + type DraftModel, +} from '../block/index.js'; +import type { Doc } from '../doc.js'; import { DocCRUD } from './crud.js'; import { type Query, runQuery } from './query.js'; +import { syncBlockProps } from './utils.js'; type DocOptions = { schema: Schema; diff --git a/blocksuite/framework/store/src/store/doc/crud.ts b/blocksuite/framework/store/src/model/blocks/crud.ts similarity index 98% rename from blocksuite/framework/store/src/store/doc/crud.ts rename to blocksuite/framework/store/src/model/blocks/crud.ts index 8d1cb5ef406b9..99abe93f871f7 100644 --- a/blocksuite/framework/store/src/store/doc/crud.ts +++ b/blocksuite/framework/store/src/model/blocks/crud.ts @@ -2,12 +2,10 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import * as Y from 'yjs'; import { native2Y } from '../../reactive/index.js'; -import { - type BlockModel, - internalPrimitives, - type Schema, -} from '../../schema/index.js'; -import type { YBlock } from './index.js'; +import type { Schema } from '../../schema/index.js'; +import type { BlockModel } from '../block/block-model.js'; +import type { YBlock } from '../block/types.js'; +import { internalPrimitives } from '../block/zod.js'; export class DocCRUD { get root(): string | null { diff --git a/blocksuite/framework/store/src/model/blocks/index.ts b/blocksuite/framework/store/src/model/blocks/index.ts new file mode 100644 index 0000000000000..c37ae9ebc55e6 --- /dev/null +++ b/blocksuite/framework/store/src/model/blocks/index.ts @@ -0,0 +1,2 @@ +export * from './blocks.js'; +export * from './query.js'; diff --git a/blocksuite/framework/store/src/store/doc/query.ts b/blocksuite/framework/store/src/model/blocks/query.ts similarity index 80% rename from blocksuite/framework/store/src/store/doc/query.ts rename to blocksuite/framework/store/src/model/blocks/query.ts index f661ab27019db..a640ef7790c32 100644 --- a/blocksuite/framework/store/src/store/doc/query.ts +++ b/blocksuite/framework/store/src/model/blocks/query.ts @@ -1,8 +1,6 @@ import isMatch from 'lodash.ismatch'; -import type { BlockModel } from '../../schema/index.js'; -import type { Block } from './block/index.js'; -import { BlockViewType } from './consts.js'; +import type { Block, BlockModel, BlockViewType } from '../block/index.js'; export type QueryMatch = { id?: string; @@ -27,7 +25,7 @@ export function runQuery(query: Query, block: Block) { const blockViewType = getBlockViewType(query, block); block.blockViewType = blockViewType; - if (blockViewType !== BlockViewType.Hidden) { + if (blockViewType !== 'hidden') { const queryMode = query.mode; setAncestorsToDisplayIfHidden(queryMode, block); } @@ -46,8 +44,8 @@ function getBlockViewType(query: Query, block: Block): BlockViewType { }, {} as Record ); - let blockViewType = - queryMode === 'loose' ? BlockViewType.Display : BlockViewType.Hidden; + let blockViewType: BlockViewType = + queryMode === 'loose' ? 'display' : 'hidden'; query.match.some(queryObject => { const { @@ -76,9 +74,8 @@ function setAncestorsToDisplayIfHidden(mode: QueryMode, block: Block) { let parent = doc.getParent(block.model); while (parent) { const parentBlock = doc.getBlock(parent.id); - if (parentBlock && parentBlock.blockViewType === BlockViewType.Hidden) { - parentBlock.blockViewType = - mode === 'include' ? BlockViewType.Display : BlockViewType.Bypass; + if (parentBlock && parentBlock.blockViewType === 'hidden') { + parentBlock.blockViewType = mode === 'include' ? 'display' : 'bypass'; } parent = doc.getParent(parent); } diff --git a/blocksuite/framework/store/src/utils/utils.ts b/blocksuite/framework/store/src/model/blocks/utils.ts similarity index 58% rename from blocksuite/framework/store/src/utils/utils.ts rename to blocksuite/framework/store/src/model/blocks/utils.ts index 1490f94c2a1bd..a2209da8906e7 100644 --- a/blocksuite/framework/store/src/utils/utils.ts +++ b/blocksuite/framework/store/src/model/blocks/utils.ts @@ -1,11 +1,11 @@ import type { z } from 'zod'; -import { SYS_KEYS } from '../consts.js'; -import { native2Y } from '../reactive/index.js'; -import type { BlockModel, BlockSchema } from '../schema/base.js'; -import { internalPrimitives } from '../schema/base.js'; -import type { YBlock } from '../store/doc/block/index.js'; -import type { BlockProps } from '../store/workspace.js'; +import { SYS_KEYS } from '../../consts.js'; +import { native2Y } from '../../reactive/index.js'; +import type { BlockModel } from '../block/block-model.js'; +import type { BlockProps, YBlock } from '../block/types.js'; +import type { BlockSchema } from '../block/zod.js'; +import { internalPrimitives } from '../block/zod.js'; export function syncBlockProps( schema: z.infer, @@ -35,13 +35,3 @@ export function syncBlockProps( model[key] = native2Y(value); }); } - -export const hash = (str: string) => { - return str - .split('') - .reduce( - (prevHash, currVal) => - ((prevHash << 5) - prevHash + currVal.charCodeAt(0)) | 0, - 0 - ); -}; diff --git a/blocksuite/framework/store/src/model/doc.ts b/blocksuite/framework/store/src/model/doc.ts new file mode 100644 index 0000000000000..1ee477756f52b --- /dev/null +++ b/blocksuite/framework/store/src/model/doc.ts @@ -0,0 +1,68 @@ +import type { Slot } from '@blocksuite/global/utils'; +import type * as Y from 'yjs'; + +import type { Schema } from '../schema/schema.js'; +import type { AwarenessStore } from '../yjs/awareness.js'; +import type { YBlock } from './block/types.js'; +import type { Blocks } from './blocks/blocks.js'; +import type { Query } from './blocks/query.js'; +import type { Workspace } from './workspace.js'; +import type { DocMeta } from './workspace-meta.js'; + +export type GetBlocksOptions = { + query?: Query; + readonly?: boolean; +}; +export type CreateBlocksOptions = GetBlocksOptions & { + id?: string; +}; +export type YBlocks = Y.Map; + +export interface Doc { + readonly id: string; + get meta(): DocMeta | undefined; + get schema(): Schema; + + remove(): void; + load(initFn?: () => void): void; + get ready(): boolean; + dispose(): void; + + slots: { + historyUpdated: Slot; + yBlockUpdated: Slot< + | { + type: 'add'; + id: string; + } + | { + type: 'delete'; + id: string; + } + >; + }; + + get history(): Y.UndoManager; + get canRedo(): boolean; + get canUndo(): boolean; + undo(): void; + redo(): void; + resetHistory(): void; + transact(fn: () => void, shouldTransact?: boolean): void; + withoutTransact(fn: () => void): void; + + captureSync(): void; + clear(): void; + getBlocks(options?: GetBlocksOptions): Blocks; + clearQuery(query: Query, readonly?: boolean): void; + + get loaded(): boolean; + get readonly(): boolean; + get awarenessStore(): AwarenessStore; + + get workspace(): Workspace; + + get rootDoc(): Y.Doc; + get spaceDoc(): Y.Doc; + get yBlocks(): Y.Map; +} diff --git a/blocksuite/framework/store/src/model/index.ts b/blocksuite/framework/store/src/model/index.ts new file mode 100644 index 0000000000000..3cd9fba3dadf4 --- /dev/null +++ b/blocksuite/framework/store/src/model/index.ts @@ -0,0 +1,19 @@ +import type { BlockModel } from './block/block-model.js'; + +export * from './block/index.js'; +export * from './blocks/index.js'; +export * from './doc.js'; +export * from './workspace.js'; +export * from './workspace-meta.js'; + +declare global { + namespace BlockSuite { + interface BlockModels {} + + type Flavour = string & keyof BlockModels; + + type ModelProps = Partial< + Model extends BlockModel ? U : never + >; + } +} diff --git a/blocksuite/framework/store/src/model/workspace-meta.ts b/blocksuite/framework/store/src/model/workspace-meta.ts new file mode 100644 index 0000000000000..028ef78dd6197 --- /dev/null +++ b/blocksuite/framework/store/src/model/workspace-meta.ts @@ -0,0 +1,50 @@ +import type { Slot } from '@blocksuite/global/utils'; + +import type { Workspace } from './workspace.js'; + +export type Tag = { + id: string; + value: string; + color: string; +}; +export type DocsPropertiesMeta = { + tags?: { + options: Tag[]; + }; +}; +export interface DocMeta { + id: string; + title: string; + tags: string[]; + createDate: number; + updatedDate?: number; + favorite?: boolean; +} + +export interface WorkspaceMeta { + get docMetas(): DocMeta[]; + + addDocMeta(props: DocMeta, index?: number): void; + getDocMeta(id: string): DocMeta | undefined; + setDocMeta(id: string, props: Partial): void; + removeDocMeta(id: string): void; + + get properties(): DocsPropertiesMeta; + setProperties(meta: DocsPropertiesMeta): void; + + get avatar(): string | undefined; + setAvatar(avatar: string): void; + + get name(): string | undefined; + setName(name: string): void; + + hasVersion: boolean; + writeVersion(workspace: Workspace): void; + get docs(): unknown[] | undefined; + initialize(): void; + + commonFieldsUpdated: Slot; + docMetaAdded: Slot; + docMetaRemoved: Slot; + docMetaUpdated: Slot; +} diff --git a/blocksuite/framework/store/src/model/workspace.ts b/blocksuite/framework/store/src/model/workspace.ts new file mode 100644 index 0000000000000..7df46231c5a19 --- /dev/null +++ b/blocksuite/framework/store/src/model/workspace.ts @@ -0,0 +1,35 @@ +import type { Slot } from '@blocksuite/global/utils'; +import type { BlobEngine, DocEngine } from '@blocksuite/sync'; +import type * as Y from 'yjs'; + +import type { Schema } from '../schema/schema.js'; +import type { IdGenerator } from '../utils/id-generator.js'; +import type { AwarenessStore } from '../yjs/awareness.js'; +import type { Blocks } from './blocks/blocks.js'; +import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js'; +import type { WorkspaceMeta } from './workspace-meta.js'; + +export interface Workspace { + readonly id: string; + readonly meta: WorkspaceMeta; + readonly idGenerator: IdGenerator; + readonly docSync: DocEngine; + readonly blobSync: BlobEngine; + readonly awarenessStore: AwarenessStore; + + get schema(): Schema; + get doc(): Y.Doc; + get docs(): Map; + + slots: { + docListUpdated: Slot; + docCreated: Slot; + docRemoved: Slot; + }; + + createDoc(options?: CreateBlocksOptions): Blocks; + getDoc(docId: string, options?: GetBlocksOptions): Blocks | null; + removeDoc(docId: string): void; + + dispose(): void; +} diff --git a/blocksuite/framework/store/src/schema/base.ts b/blocksuite/framework/store/src/schema/base.ts deleted file mode 100644 index 8d4d51b210c10..0000000000000 --- a/blocksuite/framework/store/src/schema/base.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { type Disposable, Slot } from '@blocksuite/global/utils'; -import type { Signal } from '@preact/signals-core'; -import { computed, signal } from '@preact/signals-core'; -import type * as Y from 'yjs'; -import { z } from 'zod'; - -import { Boxed } from '../reactive/boxed.js'; -import { Text } from '../reactive/text.js'; -import type { YBlock } from '../store/doc/block/index.js'; -import type { Blocks } from '../store/index.js'; -import type { BaseBlockTransformer } from '../transformer/base.js'; - -const FlavourSchema = z.string(); -const ParentSchema = z.array(z.string()).optional(); -const ContentSchema = z.array(z.string()).optional(); -const role = ['root', 'hub', 'content'] as const; -const RoleSchema = z.enum(role); - -export type RoleType = (typeof role)[number]; - -export interface InternalPrimitives { - Text: (input?: Y.Text | string) => Text; - Boxed: (input: T) => Boxed; -} - -export const internalPrimitives: InternalPrimitives = Object.freeze({ - Text: (input: Y.Text | string = '') => new Text(input), - Boxed: (input: T) => new Boxed(input), -}); - -export const BlockSchema = z.object({ - version: z.number(), - model: z.object({ - role: RoleSchema, - flavour: FlavourSchema, - parent: ParentSchema, - children: ContentSchema, - props: z - .function() - .args(z.custom()) - .returns(z.record(z.any())) - .optional(), - toModel: z.function().args().returns(z.custom()).optional(), - }), - transformer: z - .function() - .args() - .returns(z.custom()) - .optional(), -}); - -export type BlockSchemaType = z.infer; - -export type PropsGetter = ( - internalPrimitives: InternalPrimitives -) => Props; - -export type SchemaToModel< - Schema extends { - model: { - props: PropsGetter; - flavour: string; - }; - }, -> = BlockModel> & - ReturnType & { - flavour: Schema['model']['flavour']; - }; - -export function defineBlockSchema< - Flavour extends string, - Role extends RoleType, - Props extends object, - Metadata extends Readonly<{ - version: number; - role: Role; - parent?: string[]; - children?: string[]; - }>, - Model extends BlockModel, - Transformer extends BaseBlockTransformer, ->(options: { - flavour: Flavour; - metadata: Metadata; - props?: (internalPrimitives: InternalPrimitives) => Props; - toModel?: () => Model; - transformer?: () => Transformer; -}): { - version: number; - model: { - props: PropsGetter; - flavour: Flavour; - } & Metadata; - transformer?: () => Transformer; -}; - -export function defineBlockSchema({ - flavour, - props, - metadata, - toModel, - transformer, -}: { - flavour: string; - metadata: { - version: number; - role: RoleType; - parent?: string[]; - children?: string[]; - }; - props?: (internalPrimitives: InternalPrimitives) => Record; - toModel?: () => BlockModel; - transformer?: () => BaseBlockTransformer; -}): BlockSchemaType { - const schema = { - version: metadata.version, - model: { - role: metadata.role, - parent: metadata.parent, - children: metadata.children, - flavour, - props, - toModel, - }, - transformer, - } satisfies z.infer; - BlockSchema.parse(schema); - return schema; -} - -type SignaledProps = Props & { - [P in keyof Props & string as `${P}$`]: Signal; -}; -/** - * The MagicProps function is used to append the props to the class. - * For example: - * - * ```ts - * class MyBlock extends MagicProps()<{ foo: string }> {} - * const myBlock = new MyBlock(); - * // You'll get type checking for the foo prop - * myBlock.foo = 'bar'; - * ``` - */ -function MagicProps(): { - new (): Props; -} { - return class {} as never; -} - -const modelLabel = Symbol('model_label'); - -// @ts-expect-error allow magic props -export class BlockModel< - Props extends object = object, - PropsSignal extends object = SignaledProps, -> extends MagicProps() { - private readonly _children = signal([]); - - /** - * @deprecated use doc instead - */ - page!: Blocks; - - private readonly _childModels = computed(() => { - const value: BlockModel[] = []; - this._children.value.forEach(id => { - const block = this.page.getBlock$(id); - if (block) { - value.push(block.model); - } - }); - return value; - }); - - private readonly _onCreated: Disposable; - - private readonly _onDeleted: Disposable; - - childMap = computed(() => - this._children.value.reduce((map, id, index) => { - map.set(id, index); - return map; - }, new Map()) - ); - - created = new Slot(); - - deleted = new Slot(); - - flavour!: string; - - id!: string; - - isEmpty = computed(() => { - return this._children.value.length === 0; - }); - - keys!: string[]; - - // This is used to avoid https://stackoverflow.com/questions/55886792/infer-typescript-generic-class-type - [modelLabel]: Props = 'type_info_label' as never; - - pop!: (prop: keyof Props & string) => void; - - propsUpdated = new Slot<{ key: string }>(); - - role!: RoleType; - - stash!: (prop: keyof Props & string) => void; - - // text is optional - text?: Text; - - version!: number; - - yBlock!: YBlock; - - get children() { - return this._childModels.value; - } - - get doc() { - return this.page; - } - - set doc(doc: Blocks) { - this.page = doc; - } - - get parent() { - return this.doc.getParent(this); - } - - constructor() { - super(); - this._onCreated = this.created.once(() => { - this._children.value = this.yBlock.get('sys:children').toArray(); - this.yBlock.get('sys:children').observe(event => { - this._children.value = event.target.toArray(); - }); - this.yBlock.observe(event => { - if (event.keysChanged.has('sys:children')) { - this._children.value = this.yBlock.get('sys:children').toArray(); - } - }); - }); - this._onDeleted = this.deleted.once(() => { - this._onCreated.dispose(); - }); - } - - dispose() { - this.created.dispose(); - this.deleted.dispose(); - this.propsUpdated.dispose(); - } - - firstChild(): BlockModel | null { - return this.children[0] || null; - } - - lastChild(): BlockModel | null { - if (!this.children.length) { - return this; - } - return this.children[this.children.length - 1].lastChild(); - } - - [Symbol.dispose]() { - this._onCreated.dispose(); - this._onDeleted.dispose(); - } -} diff --git a/blocksuite/framework/store/src/schema/error.ts b/blocksuite/framework/store/src/schema/error.ts index 6b44cab175ccc..a0874e31838d2 100644 --- a/blocksuite/framework/store/src/schema/error.ts +++ b/blocksuite/framework/store/src/schema/error.ts @@ -1,15 +1,5 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; -export class MigrationError extends BlockSuiteError { - constructor(description: string) { - super( - ErrorCode.MigrationError, - `Migration failed. Please report to https://github.com/toeverything/blocksuite/issues - ${description}` - ); - } -} - export class SchemaValidateError extends BlockSuiteError { constructor(flavour: string, message: string) { super( diff --git a/blocksuite/framework/store/src/schema/index.ts b/blocksuite/framework/store/src/schema/index.ts index 63258f38c7476..38d803af4c7c9 100644 --- a/blocksuite/framework/store/src/schema/index.ts +++ b/blocksuite/framework/store/src/schema/index.ts @@ -1,2 +1 @@ -export * from './base.js'; export { Schema } from './schema.js'; diff --git a/blocksuite/framework/store/src/schema/schema.ts b/blocksuite/framework/store/src/schema/schema.ts index 9b1d04afe2153..8193ebdf82107 100644 --- a/blocksuite/framework/store/src/schema/schema.ts +++ b/blocksuite/framework/store/src/schema/schema.ts @@ -1,8 +1,7 @@ import { minimatch } from 'minimatch'; import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js'; -import type { BlockSchemaType } from './base.js'; -import { BlockSchema } from './base.js'; +import { BlockSchema, type BlockSchemaType } from '../model/index.js'; import { SchemaValidateError } from './error.js'; export class Schema { diff --git a/blocksuite/framework/store/src/store/doc/consts.ts b/blocksuite/framework/store/src/store/doc/consts.ts deleted file mode 100644 index 8a5c58f12f6d1..0000000000000 --- a/blocksuite/framework/store/src/store/doc/consts.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum BlockViewType { - Bypass = 'bypass', - Display = 'display', - Hidden = 'hidden', -} diff --git a/blocksuite/framework/store/src/store/doc/index.ts b/blocksuite/framework/store/src/store/doc/index.ts deleted file mode 100644 index 4b1647494ad05..0000000000000 --- a/blocksuite/framework/store/src/store/doc/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './block/index.js'; -export * from './consts.js'; -export * from './doc.js'; -export * from './query.js'; diff --git a/blocksuite/framework/store/src/store/index.ts b/blocksuite/framework/store/src/store/index.ts deleted file mode 100644 index 2b1968a7543b3..0000000000000 --- a/blocksuite/framework/store/src/store/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './doc/index.js'; -export * from './workspace.js'; diff --git a/blocksuite/framework/store/src/store/workspace.ts b/blocksuite/framework/store/src/store/workspace.ts deleted file mode 100644 index abb87191702b0..0000000000000 --- a/blocksuite/framework/store/src/store/workspace.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { Slot } from '@blocksuite/global/utils'; -import type { BlobEngine, DocEngine } from '@blocksuite/sync'; -import type * as Y from 'yjs'; - -import type { BlockModel } from '../schema/base.js'; -import type { Schema } from '../schema/schema.js'; -import type { IdGenerator } from '../utils/id-generator.js'; -import type { AwarenessStore } from '../yjs/awareness.js'; -import type { YBlock } from './doc/block/types.js'; -import type { Blocks } from './doc/doc.js'; -import type { Query } from './doc/query.js'; - -export type Tag = { - id: string; - value: string; - color: string; -}; -export type DocsPropertiesMeta = { - tags?: { - options: Tag[]; - }; -}; -export interface DocMeta { - id: string; - title: string; - tags: string[]; - createDate: number; - updatedDate?: number; - favorite?: boolean; -} - -export type GetBlocksOptions = { - query?: Query; - readonly?: boolean; -}; -export type CreateBlocksOptions = GetBlocksOptions & { - id?: string; -}; - -export interface WorkspaceMeta { - get docMetas(): DocMeta[]; - - addDocMeta(props: DocMeta, index?: number): void; - getDocMeta(id: string): DocMeta | undefined; - setDocMeta(id: string, props: Partial): void; - removeDocMeta(id: string): void; - - get properties(): DocsPropertiesMeta; - setProperties(meta: DocsPropertiesMeta): void; - - get avatar(): string | undefined; - setAvatar(avatar: string): void; - - get name(): string | undefined; - setName(name: string): void; - - hasVersion: boolean; - writeVersion(workspace: Workspace): void; - get docs(): unknown[] | undefined; - initialize(): void; - - commonFieldsUpdated: Slot; - docMetaAdded: Slot; - docMetaRemoved: Slot; - docMetaUpdated: Slot; -} - -export interface Workspace { - readonly id: string; - readonly meta: WorkspaceMeta; - readonly idGenerator: IdGenerator; - readonly docSync: DocEngine; - readonly blobSync: BlobEngine; - readonly awarenessStore: AwarenessStore; - - get schema(): Schema; - get doc(): Y.Doc; - get docs(): Map; - - slots: { - docListUpdated: Slot; - docCreated: Slot; - docRemoved: Slot; - }; - - createDoc(options?: CreateBlocksOptions): Blocks; - getDoc(docId: string, options?: GetBlocksOptions): Blocks | null; - removeDoc(docId: string): void; - - dispose(): void; -} - -export interface Doc { - readonly id: string; - get meta(): DocMeta | undefined; - get schema(): Schema; - - remove(): void; - load(initFn?: () => void): void; - get ready(): boolean; - dispose(): void; - - slots: { - historyUpdated: Slot; - yBlockUpdated: Slot< - | { - type: 'add'; - id: string; - } - | { - type: 'delete'; - id: string; - } - >; - }; - - get history(): Y.UndoManager; - get canRedo(): boolean; - get canUndo(): boolean; - undo(): void; - redo(): void; - resetHistory(): void; - transact(fn: () => void, shouldTransact?: boolean): void; - withoutTransact(fn: () => void): void; - - captureSync(): void; - clear(): void; - getBlocks(options?: GetBlocksOptions): Blocks; - clearQuery(query: Query, readonly?: boolean): void; - - get loaded(): boolean; - get readonly(): boolean; - get awarenessStore(): AwarenessStore; - - get workspace(): Workspace; - - get rootDoc(): Y.Doc; - get spaceDoc(): Y.Doc; - get yBlocks(): Y.Map; -} - -export interface StackItem { - meta: Map<'selection-state', unknown>; -} - -export type YBlocks = Y.Map; - -/** JSON-serializable properties of a block */ -export type BlockSysProps = { - id: string; - flavour: string; - children?: BlockModel[]; -}; -export type BlockProps = BlockSysProps & Record; - -declare global { - namespace BlockSuite { - interface BlockModels {} - - type Flavour = string & keyof BlockModels; - - type ModelProps = Partial< - Model extends BlockModel ? U : never - >; - } -} diff --git a/blocksuite/framework/store/src/test/index.ts b/blocksuite/framework/store/src/test/index.ts index a15387a2a8c1c..7975e01d8afed 100644 --- a/blocksuite/framework/store/src/test/index.ts +++ b/blocksuite/framework/store/src/test/index.ts @@ -1,4 +1,10 @@ -export { createAutoIncrementIdGenerator } from '../utils/id-generator.js'; +import type { IdGenerator } from '../utils/id-generator.js'; + export * from './test-doc.js'; export * from './test-meta.js'; export * from './test-workspace.js'; + +export function createAutoIncrementIdGenerator(): IdGenerator { + let i = 0; + return () => (i++).toString(); +} diff --git a/blocksuite/framework/store/src/test/test-doc.ts b/blocksuite/framework/store/src/test/test-doc.ts index 6a72073618fec..2af889f745537 100644 --- a/blocksuite/framework/store/src/test/test-doc.ts +++ b/blocksuite/framework/store/src/test/test-doc.ts @@ -2,10 +2,10 @@ import { type Disposable, Slot } from '@blocksuite/global/utils'; import { signal } from '@preact/signals-core'; import * as Y from 'yjs'; -import { Blocks } from '../store/doc/doc.js'; -import type { YBlock } from '../store/doc/index.js'; -import type { Query } from '../store/doc/query.js'; -import type { Doc, GetBlocksOptions, Workspace } from '../store/workspace.js'; +import type { YBlock } from '../model/block/types.js'; +import { Blocks } from '../model/blocks/blocks.js'; +import type { Query } from '../model/blocks/query.js'; +import type { Doc, GetBlocksOptions, Workspace } from '../model/index.js'; import type { AwarenessStore } from '../yjs/index.js'; type DocOptions = { diff --git a/blocksuite/framework/store/src/test/test-meta.ts b/blocksuite/framework/store/src/test/test-meta.ts index b0c056c78cf78..8ec3ee91c235a 100644 --- a/blocksuite/framework/store/src/test/test-meta.ts +++ b/blocksuite/framework/store/src/test/test-meta.ts @@ -1,16 +1,18 @@ import { Slot } from '@blocksuite/global/utils'; import type * as Y from 'yjs'; -import { COLLECTION_VERSION, PAGE_VERSION } from '../consts.js'; -import { createYProxy } from '../reactive/proxy.js'; import type { DocMeta, DocsPropertiesMeta, Workspace, WorkspaceMeta, -} from '../store/workspace.js'; +} from '../model/index.js'; +import { createYProxy } from '../reactive/proxy.js'; + +const COLLECTION_VERSION = 2; +const PAGE_VERSION = 2; -export type DocCollectionMetaState = { +type DocCollectionMetaState = { pages?: unknown[]; properties?: DocsPropertiesMeta; workspaceVersion?: number; diff --git a/blocksuite/framework/store/src/test/test-workspace.ts b/blocksuite/framework/store/src/test/test-workspace.ts index e4a48d3b943a6..9334e186d1b41 100644 --- a/blocksuite/framework/store/src/test/test-workspace.ts +++ b/blocksuite/framework/store/src/test/test-workspace.ts @@ -16,14 +16,14 @@ import merge from 'lodash.merge'; import { Awareness } from 'y-protocols/awareness.js'; import * as Y from 'yjs'; +import type { + Blocks, + CreateBlocksOptions, + GetBlocksOptions, + Workspace, + WorkspaceMeta, +} from '../model/index.js'; import type { Schema } from '../schema/index.js'; -import { - type Blocks, - type CreateBlocksOptions, - type GetBlocksOptions, - type Workspace, - type WorkspaceMeta, -} from '../store/index.js'; import { type IdGenerator, nanoid } from '../utils/id-generator.js'; import { AwarenessStore, type RawAwarenessState } from '../yjs/index.js'; import { TestDoc } from './test-doc.js'; diff --git a/blocksuite/framework/store/src/transformer/base.ts b/blocksuite/framework/store/src/transformer/base.ts index 92c432bc9f075..13de56897f1db 100644 --- a/blocksuite/framework/store/src/transformer/base.ts +++ b/blocksuite/framework/store/src/transformer/base.ts @@ -1,7 +1,10 @@ -import type { BlockModel, InternalPrimitives } from '../schema/index.js'; -import { internalPrimitives } from '../schema/index.js'; +import type { BlockModel } from '../model/block/block-model.js'; +import type { DraftModel } from '../model/block/draft.js'; +import { + type InternalPrimitives, + internalPrimitives, +} from '../model/block/zod.js'; import type { AssetsManager } from './assets.js'; -import type { DraftModel } from './draft.js'; import { fromJSON, toJSON } from './json.js'; import type { BlockSnapshot } from './type.js'; diff --git a/blocksuite/framework/store/src/transformer/index.ts b/blocksuite/framework/store/src/transformer/index.ts index c7d95ff7ce3d8..a823cb6c6306e 100644 --- a/blocksuite/framework/store/src/transformer/index.ts +++ b/blocksuite/framework/store/src/transformer/index.ts @@ -1,6 +1,5 @@ export * from './assets.js'; export * from './base.js'; -export * from './draft.js'; export * from './job.js'; export * from './json.js'; export * from './middleware.js'; diff --git a/blocksuite/framework/store/src/transformer/job.ts b/blocksuite/framework/store/src/transformer/job.ts index bad8d125a160c..f846255025038 100644 --- a/blocksuite/framework/store/src/transformer/job.ts +++ b/blocksuite/framework/store/src/transformer/job.ts @@ -1,11 +1,15 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { nextTick, Slot } from '@blocksuite/global/utils'; -import type { BlockModel, BlockSchemaType, Schema } from '../schema/index.js'; -import type { Blocks } from '../store/index.js'; +import type { + BlockModel, + Blocks, + BlockSchemaType, + DraftModel, +} from '../model/index.js'; +import type { Schema } from '../schema/index.js'; import { AssetsManager } from './assets.js'; import { BaseBlockTransformer } from './base.js'; -import type { DraftModel } from './draft.js'; import type { BeforeExportPayload, BeforeImportPayload, diff --git a/blocksuite/framework/store/src/transformer/middleware.ts b/blocksuite/framework/store/src/transformer/middleware.ts index fbe7945398062..58c69ccf23996 100644 --- a/blocksuite/framework/store/src/transformer/middleware.ts +++ b/blocksuite/framework/store/src/transformer/middleware.ts @@ -1,8 +1,7 @@ import type { Slot } from '@blocksuite/global/utils'; -import type { Blocks } from '../store/index.js'; +import type { Blocks, DraftModel } from '../model/index.js'; import type { AssetsManager } from './assets.js'; -import type { DraftModel } from './draft.js'; import type { Slice } from './slice.js'; import type { BlockSnapshot, diff --git a/blocksuite/framework/store/src/transformer/slice.ts b/blocksuite/framework/store/src/transformer/slice.ts index d20548538085e..c0fc44865bc8e 100644 --- a/blocksuite/framework/store/src/transformer/slice.ts +++ b/blocksuite/framework/store/src/transformer/slice.ts @@ -1,5 +1,4 @@ -import type { Blocks } from '../store/index.js'; -import type { DraftModel } from './draft.js'; +import type { Blocks, DraftModel } from '../model/index.js'; type SliceData = { content: DraftModel[]; diff --git a/blocksuite/framework/store/src/transformer/type.ts b/blocksuite/framework/store/src/transformer/type.ts index 38270e3151f78..ecf1c3fc089b9 100644 --- a/blocksuite/framework/store/src/transformer/type.ts +++ b/blocksuite/framework/store/src/transformer/type.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import type { Blocks } from '../store/doc/doc.js'; -import type { DocMeta, DocsPropertiesMeta } from '../store/workspace.js'; +import type { Blocks } from '../model/blocks/blocks.js'; +import type { DocMeta, DocsPropertiesMeta } from '../model/workspace-meta.js'; export type BlockSnapshot = { type: 'block'; diff --git a/blocksuite/framework/store/src/utils/id-generator.ts b/blocksuite/framework/store/src/utils/id-generator.ts index a97ec922c93e4..227308d6f5315 100644 --- a/blocksuite/framework/store/src/utils/id-generator.ts +++ b/blocksuite/framework/store/src/utils/id-generator.ts @@ -3,11 +3,6 @@ import { nanoid as nanoidGenerator } from 'nanoid'; export type IdGenerator = () => string; -export function createAutoIncrementIdGenerator(): IdGenerator { - let i = 0; - return () => (i++).toString(); -} - export const uuidv4: IdGenerator = () => { return uuidv4IdGenerator(); }; diff --git a/blocksuite/framework/store/src/yjs/awareness.ts b/blocksuite/framework/store/src/yjs/awareness.ts index 09e9d602414de..b1a721ca2b35f 100644 --- a/blocksuite/framework/store/src/yjs/awareness.ts +++ b/blocksuite/framework/store/src/yjs/awareness.ts @@ -5,7 +5,7 @@ import clonedeep from 'lodash.clonedeep'; import merge from 'lodash.merge'; import type { Awareness as YAwareness } from 'y-protocols/awareness.js'; -import type { Doc } from '../store/index.js'; +import type { Doc } from '../model/doc.js'; export interface UserInfo { name: string; @@ -14,25 +14,22 @@ export interface UserInfo { type UserSelection = Array>; // Raw JSON state in awareness CRDT -export type RawAwarenessState = - { - user?: UserInfo; - color?: string; - flags: Flags; - // use v2 to avoid crush on old clients - selectionV2: Record; - }; - -export interface AwarenessEvent< - Flags extends BlockSuiteFlags = BlockSuiteFlags, -> { +export type RawAwarenessState = { + user?: UserInfo; + color?: string; + flags: BlockSuiteFlags; + // use v2 to avoid crush on old clients + selectionV2: Record; +}; + +export interface AwarenessEvent { id: number; type: 'add' | 'update' | 'remove'; - state?: RawAwarenessState; + state?: RawAwarenessState; } -export class AwarenessStore { - private readonly _flags: Signal; +export class AwarenessStore { + private readonly _flags: Signal; private readonly _onAwarenessChange = (diff: { added: number[]; @@ -66,24 +63,24 @@ export class AwarenessStore { }); }; - readonly awareness: YAwareness>; + readonly awareness: YAwareness; readonly slots = { - update: new Slot>(), + update: new Slot(), }; constructor( - awareness: YAwareness>, - defaultFlags: Flags + awareness: YAwareness, + defaultFlags: BlockSuiteFlags ) { - this._flags = signal(defaultFlags); + this._flags = signal(defaultFlags); this.awareness = awareness; this.awareness.on('change', this._onAwarenessChange); this.awareness.setLocalStateField('selectionV2', {}); this._initFlags(defaultFlags); } - private _initFlags(defaultFlags: Flags) { + private _initFlags(defaultFlags: BlockSuiteFlags) { const upstreamFlags = this.awareness.getLocalState()?.flags; const flags = clonedeep(defaultFlags); if (upstreamFlags) { @@ -98,7 +95,7 @@ export class AwarenessStore { this.awareness.destroy(); } - getFlag(field: Key) { + getFlag(field: Key) { return this._flags.value[field]; } @@ -111,7 +108,7 @@ export class AwarenessStore { ); } - getStates(): Map> { + getStates(): Map { return this.awareness.getStates(); } @@ -124,7 +121,10 @@ export class AwarenessStore { } } - setFlag(field: Key, value: Flags[Key]) { + setFlag( + field: Key, + value: BlockSuiteFlags[Key] + ) { const oldFlags = this.awareness.getLocalState()?.flags ?? {}; this.awareness.setLocalStateField('flags', { ...oldFlags, [field]: value }); } @@ -142,6 +142,6 @@ export class AwarenessStore { this.setFlag('readonly', { ...flags, [blockCollection.id]: value, - } as Flags['readonly']); + } as BlockSuiteFlags['readonly']); } } diff --git a/blocksuite/framework/store/src/yjs/utils.ts b/blocksuite/framework/store/src/yjs/utils.ts index ffe764c20f90d..d07cbfa874743 100644 --- a/blocksuite/framework/store/src/yjs/utils.ts +++ b/blocksuite/framework/store/src/yjs/utils.ts @@ -5,3 +5,7 @@ export type SubdocEvent = { removed: Set; added: Set; }; + +export interface StackItem { + meta: Map<'selection-state', unknown>; +} diff --git a/blocksuite/tests-legacy/utils/asserts.ts b/blocksuite/tests-legacy/utils/asserts.ts index 6992b63524e51..9a5a88f642fe8 100644 --- a/blocksuite/tests-legacy/utils/asserts.ts +++ b/blocksuite/tests-legacy/utils/asserts.ts @@ -16,7 +16,6 @@ import { BLOCK_ID_ATTR } from '@blocksuite/block-std'; import { assertExists } from '@blocksuite/global/utils'; import type { InlineRootElement } from '@inline/inline-editor.js'; import { expect, type Locator, type Page } from '@playwright/test'; -import { COLLECTION_VERSION, PAGE_VERSION } from '@store/consts.js'; import type { BlockModel } from '@store/index.js'; import { @@ -88,8 +87,8 @@ export const defaultStore = { 'affine:surface-ref': 1, 'affine:edgeless-text': 1, }, - workspaceVersion: COLLECTION_VERSION, - pageVersion: PAGE_VERSION, + workspaceVersion: 2, + pageVersion: 2, }, spaces: { 'doc:home': { diff --git a/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts b/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts index bb3503a7f0f78..f68b5f1122f41 100644 --- a/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts +++ b/packages/frontend/core/src/blocksuite/presets/_common/components/text-renderer.ts @@ -21,7 +21,6 @@ import { Container, type ServiceProvider } from '@blocksuite/affine/global/di'; import { WithDisposable } from '@blocksuite/affine/global/utils'; import { type Blocks, - BlockViewType, type JobMiddleware, type Query, type Schema, @@ -186,7 +185,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) { 'affine:code', 'affine:list', 'affine:divider', - ].map(flavour => ({ flavour, viewType: BlockViewType.Display })), + ].map(flavour => ({ flavour, viewType: 'display' })), }; private _timer?: ReturnType | null = null;