diff --git a/blocksuite/affine/all/src/__tests__/database/database.unit.spec.ts b/blocksuite/affine/all/src/__tests__/database/database.unit.spec.ts index 96cd19f93e..879b7815f0 100644 --- a/blocksuite/affine/all/src/__tests__/database/database.unit.spec.ts +++ b/blocksuite/affine/all/src/__tests__/database/database.unit.spec.ts @@ -82,7 +82,7 @@ describe('DatabaseManager', () => { noteBlockId ); - const databaseModel = doc.getBlockById( + const databaseModel = doc.getModelById( databaseBlockId ) as DatabaseBlockModel; db = databaseModel; @@ -187,7 +187,7 @@ describe('DatabaseManager', () => { addProperty(db, 'end', column); updateCell(db, modelId, cell); - const model = doc.getBlockById(modelId); + const model = doc.getModelById(modelId); expect(model).not.toBeNull(); diff --git a/blocksuite/affine/blocks/block-database/src/data-source.ts b/blocksuite/affine/blocks/block-database/src/data-source.ts index e899b05663..8a657bcf8f 100644 --- a/blocksuite/affine/blocks/block-database/src/data-source.ts +++ b/blocksuite/affine/blocks/block-database/src/data-source.ts @@ -466,7 +466,7 @@ export class DatabaseBlockDataSource extends DataSourceBase { } rowMove(rowId: string, position: InsertToPosition): void { - const model = this.doc.getBlockById(rowId); + const model = this.doc.getModelById(rowId); if (model) { const index = insertPositionToIndex(position, this._model.children); const target = this._model.children[index]; diff --git a/blocksuite/affine/blocks/block-image/src/utils.ts b/blocksuite/affine/blocks/block-image/src/utils.ts index 6d6e87fada..d233f7e03a 100644 --- a/blocksuite/affine/blocks/block-image/src/utils.ts +++ b/blocksuite/affine/blocks/block-image/src/utils.ts @@ -68,7 +68,7 @@ export async function uploadBlobForImage( } finally { setImageUploaded(blockId); - const imageModel = doc.getBlockById(blockId) as ImageBlockModel | null; + const imageModel = doc.getModelById(blockId) as ImageBlockModel | null; if (sourceId && imageModel) { const props: Partial = { sourceId, diff --git a/blocksuite/affine/blocks/block-note/src/commands/block-type.ts b/blocksuite/affine/blocks/block-note/src/commands/block-type.ts index 0832b1fa82..ab965d006e 100644 --- a/blocksuite/affine/blocks/block-note/src/commands/block-type.ts +++ b/blocksuite/affine/blocks/block-note/src/commands/block-type.ts @@ -84,7 +84,7 @@ export const updateBlockType: Command< if (flavour !== 'affine:code') return; const id = mergeToCodeModel(blockModels); if (!id) return; - const model = doc.getBlockById(id); + const model = doc.getModelById(id); if (!model) return; asyncSetInlineRange(host, model, { index: model.text?.length ?? 0, @@ -115,7 +115,7 @@ export const updateBlockType: Command< nextSiblingId = doc.addBlock('affine:paragraph', {}, parent); } focusTextModel(host.std, nextSiblingId); - const newModel = doc.getBlockById(id); + const newModel = doc.getModelById(id); if (!newModel) { return next({ updatedBlocks: [] }); } @@ -217,7 +217,7 @@ export const updateBlockType: Command< if (!newId) { return; } - const newModel = doc.getBlockById(newId); + const newModel = doc.getModelById(newId); if (newModel) { newModels.push(newModel); } diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts index cdf2a0f62a..1f7e310533 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts @@ -1091,7 +1091,7 @@ export class EdgelessClipboardController extends PageClipboard { const newSelected = [ ...canvasElementIds, ...blockIds.filter(id => { - return isTopLevelBlock(this.doc.getBlockById(id)); + return isTopLevelBlock(this.doc.getModelById(id)); }), ]; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts b/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts index 5efd92f70f..b677fa385c 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts @@ -380,7 +380,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) { this.edgeless ); } else { - const model = doc.getBlockById(id); + const model = doc.getModelById(id); if (!model) { return; } diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/note-slicer/index.ts b/blocksuite/affine/blocks/block-root/src/edgeless/components/note-slicer/index.ts index 1ec6967675..79f27f61bc 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/components/note-slicer/index.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/components/note-slicer/index.ts @@ -182,7 +182,7 @@ export class NoteSlicer extends WidgetComponent< doc.root?.id ); - doc.moveBlocks(resetBlocks, doc.getBlockById(newNoteId) as NoteBlockModel); + doc.moveBlocks(resetBlocks, doc.getModelById(newNoteId) as NoteBlockModel); this._activeSlicerIndex = 0; this._selection.set({ diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/services/template.ts b/blocksuite/affine/blocks/block-root/src/edgeless/services/template.ts index b1abd1ab68..c93da1e63b 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/services/template.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/services/template.ts @@ -188,7 +188,7 @@ export class TemplateJob { if (isMergeBlock) { this._mergeProps( json, - this.model.doc.getBlockById( + this.model.doc.getModelById( this._getMergeBlockId(json) ) as BlockModel ); diff --git a/blocksuite/affine/blocks/block-root/src/page/page-root-block.ts b/blocksuite/affine/blocks/block-root/src/page/page-root-block.ts index e95e6a3ba6..521103a0b0 100644 --- a/blocksuite/affine/blocks/block-root/src/page/page-root-block.ts +++ b/blocksuite/affine/blocks/block-root/src/page/page-root-block.ts @@ -191,7 +191,7 @@ export class PageRootBlockComponent extends BlockComponent< const { doc } = this; const noteId = doc.addBlock('affine:note', {}, doc.root?.id); - return doc.getBlockById(noteId) as NoteBlockModel; + return doc.getModelById(noteId) as NoteBlockModel; } private _getDefaultNoteBlock() { @@ -262,7 +262,7 @@ export class PageRootBlockComponent extends BlockComponent< ); if (!sel) return; let model: BlockModel | null = null; - let current = this.doc.getBlockById(sel.blockId); + let current = this.doc.getModelById(sel.blockId); while (current && !model) { if (current.flavour === 'affine:note') { model = current; @@ -280,7 +280,7 @@ export class PageRootBlockComponent extends BlockComponent< } return; } - const notes = this.doc.getBlockByFlavour('affine:note'); + const notes = this.doc.getModelsByFlavour('affine:note'); const index = notes.indexOf(prevNote); if (index !== 0) return; diff --git a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts index 13907bd003..92400bbefe 100644 --- a/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts +++ b/blocksuite/affine/blocks/block-surface-ref/src/surface-ref-block.ts @@ -281,7 +281,7 @@ export class SurfaceRefBlockComponent extends BlockComponent { diff --git a/blocksuite/affine/blocks/block-surface/src/extensions/crud-extension.ts b/blocksuite/affine/blocks/block-surface/src/extensions/crud-extension.ts index 16ddfb3409..58fdceced3 100644 --- a/blocksuite/affine/blocks/block-surface/src/extensions/crud-extension.ts +++ b/blocksuite/affine/blocks/block-surface/src/extensions/crud-extension.ts @@ -129,7 +129,7 @@ export class EdgelessCRUDExtension extends Extension { return; } - const block = this.std.store.getBlockById(id); + const block = this.std.store.getModelById(id); if (block) { const key = getLastPropsKey(block.flavour, { ...block.yBlock.toJSON(), @@ -145,7 +145,7 @@ export class EdgelessCRUDExtension extends Extension { if (!surface) { return null; } - const el = surface.getElementById(id) ?? this.std.store.getBlockById(id); + const el = surface.getElementById(id) ?? this.std.store.getModelById(id); return el as GfxModel | null; } diff --git a/blocksuite/affine/blocks/block-surface/src/extensions/export-manager/export-manager.ts b/blocksuite/affine/blocks/block-surface/src/extensions/export-manager/export-manager.ts index f6f42ae2df..3c431b11fb 100644 --- a/blocksuite/affine/blocks/block-surface/src/extensions/export-manager/export-manager.ts +++ b/blocksuite/affine/blocks/block-surface/src/extensions/export-manager/export-manager.ts @@ -602,7 +602,7 @@ function getNotesInFrameBound( ): NoteBlockModel[] { const bound = Bound.deserialize(frame.xywh); - return (doc.getBlockByFlavour('affine:note') as NoteBlockModel[]).filter( + return (doc.getModelsByFlavour('affine:note') as NoteBlockModel[]).filter( ele => { const xywh = Bound.deserialize(ele.xywh); diff --git a/blocksuite/affine/blocks/block-surface/src/renderer/elements/mindmap.ts b/blocksuite/affine/blocks/block-surface/src/renderer/elements/mindmap.ts index f8aac70546..4b34192911 100644 --- a/blocksuite/affine/blocks/block-surface/src/renderer/elements/mindmap.ts +++ b/blocksuite/affine/blocks/block-surface/src/renderer/elements/mindmap.ts @@ -32,7 +32,7 @@ export function mindmap( const { connector, outdated } = result; const elementGetter = (id: string) => model.surface.getElementById(id) ?? - (model.surface.doc.getBlockById(id) as GfxModel); + (model.surface.doc.getModelById(id) as GfxModel); if (outdated) { ConnectorPathGenerator.updatePath(connector, null, elementGetter); diff --git a/blocksuite/affine/blocks/block-surface/src/surface-service.ts b/blocksuite/affine/blocks/block-surface/src/surface-service.ts index dd15975e2a..1760a503eb 100644 --- a/blocksuite/affine/blocks/block-surface/src/surface-service.ts +++ b/blocksuite/affine/blocks/block-surface/src/surface-service.ts @@ -25,7 +25,7 @@ export class SurfaceBlockService extends BlockService { const disposable = this.doc.slots.blockUpdated.subscribe(payload => { if (payload.flavour === 'affine:surface') { disposable.unsubscribe(); - const surface = this.doc.getBlockById( + const surface = this.doc.getModelById( payload.id ) as SurfaceBlockModel | null; if (!surface) return; diff --git a/blocksuite/affine/blocks/block-surface/src/utils/add-note.ts b/blocksuite/affine/blocks/block-surface/src/utils/add-note.ts index 672a33d1c9..e8b6a8c5ae 100644 --- a/blocksuite/affine/blocks/block-surface/src/utils/add-note.ts +++ b/blocksuite/affine/blocks/block-surface/src/utils/add-note.ts @@ -100,7 +100,7 @@ export function addNote( noteId ); if (options.collapse && height > NOTE_MIN_HEIGHT) { - const note = doc.getBlockById(noteId) as NoteBlockModel; + const note = doc.getModelById(noteId) as NoteBlockModel; doc.updateBlock(note, () => { note.props.edgeless.collapse = true; note.props.edgeless.collapsedHeight = height; diff --git a/blocksuite/affine/blocks/block-surface/src/watchers/connector.ts b/blocksuite/affine/blocks/block-surface/src/watchers/connector.ts index 2046fc3069..78a5c1d882 100644 --- a/blocksuite/affine/blocks/block-surface/src/watchers/connector.ts +++ b/blocksuite/affine/blocks/block-surface/src/watchers/connector.ts @@ -8,9 +8,9 @@ export const connectorWatcher: SurfaceMiddleware = ( surface: SurfaceBlockModel ) => { const hasElementById = (id: string) => - surface.hasElementById(id) || surface.doc.hasBlockById(id); + surface.hasElementById(id) || surface.doc.hasBlock(id); const elementGetter = (id: string) => - surface.getElementById(id) ?? (surface.doc.getBlockById(id) as GfxModel); + surface.getElementById(id) ?? (surface.doc.getModelById(id) as GfxModel); const updateConnectorPath = (connector: ConnectorElementModel) => { if ( ((connector.source?.id && hasElementById(connector.source.id)) || diff --git a/blocksuite/affine/model/src/blocks/frame/frame-model.ts b/blocksuite/affine/model/src/blocks/frame/frame-model.ts index bc7c2b8207..0fe70af0f3 100644 --- a/blocksuite/affine/model/src/blocks/frame/frame-model.ts +++ b/blocksuite/affine/model/src/blocks/frame/frame-model.ts @@ -78,7 +78,7 @@ export class FrameBlockModel for (const key of this.childIds) { const element = this.surface.getElementById(key) || - (this.surface.doc.getBlockById(key) as GfxBlockElementModel); + (this.surface.doc.getModelById(key) as GfxBlockElementModel); element && elements.push(element); } diff --git a/blocksuite/affine/model/src/blocks/root/root-block-model.ts b/blocksuite/affine/model/src/blocks/root/root-block-model.ts index 375e7b0fb5..3a0c6f8e5c 100644 --- a/blocksuite/affine/model/src/blocks/root/root-block-model.ts +++ b/blocksuite/affine/model/src/blocks/root/root-block-model.ts @@ -15,7 +15,7 @@ export class RootBlockModel extends BlockModel { const createdSubscription = this.created.subscribe(() => { createdSubscription.unsubscribe(); this.doc.slots.rootAdded.subscribe(id => { - const model = this.doc.getBlockById(id); + const model = this.doc.getModelById(id); if (model instanceof RootBlockModel) { const newDocMeta = this.doc.workspace.meta.getDocMeta(model.doc.id); if ( diff --git a/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts b/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts index 5ab2b14f7f..44b67d9925 100644 --- a/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts +++ b/blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts @@ -45,7 +45,7 @@ export function calcDropTarget( */ allowSublist: boolean = true ): DropTarget | null { - const schema = model.doc.getSchemaByFlavour('affine:database'); + const schema = model.doc.schema.get('affine:database'); const children = schema?.model.children ?? []; let shouldAppendToDatabase = true; diff --git a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/edgeless-watcher.ts b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/edgeless-watcher.ts index 70f6bc1680..72ffca543a 100644 --- a/blocksuite/affine/widgets/widget-drag-handle/src/watchers/edgeless-watcher.ts +++ b/blocksuite/affine/widgets/widget-drag-handle/src/watchers/edgeless-watcher.ts @@ -62,7 +62,7 @@ export class EdgelessWatcher { }; private readonly _showDragHandle = async () => { - const surfaceModel = this.widget.doc.getBlockByFlavour('affine:surface'); + const surfaceModel = this.widget.doc.getModelsByFlavour('affine:surface'); const surface = this.widget.std.view.getBlock( surfaceModel[0]!.id ) as SurfaceBlockComponent; diff --git a/blocksuite/docs/api/@blocksuite/store/README.md b/blocksuite/docs/api/@blocksuite/store/README.md index af37499c34..0284793d78 100644 --- a/blocksuite/docs/api/@blocksuite/store/README.md +++ b/blocksuite/docs/api/@blocksuite/store/README.md @@ -10,3 +10,7 @@ - [Extension](classes/Extension.md) - [StoreExtension](classes/StoreExtension.md) + +## Store + +- [Store](classes/Store.md) diff --git a/blocksuite/docs/api/@blocksuite/store/classes/Store.md b/blocksuite/docs/api/@blocksuite/store/classes/Store.md new file mode 100644 index 0000000000..15130b4ca9 --- /dev/null +++ b/blocksuite/docs/api/@blocksuite/store/classes/Store.md @@ -0,0 +1,786 @@ +[**@blocksuite/store**](../../../@blocksuite/store/README.md) + +*** + +[BlockSuite API Documentation](../../../README.md) / [@blocksuite/store](../README.md) / Store + +# Class: Store + +Core store class that manages blocks and their lifecycle in BlockSuite + +## Remarks + +The Store class is responsible for managing the lifecycle of blocks, handling transactions, +and maintaining the block tree structure. +A store is a piece of data created from one or a part of a Y.Doc. + +## Block CRUD + +### updateBlock() + +> **updateBlock**: \<`T`\>(`model`, `props`) => `void`(`model`, `callback`) => `void` + +Updates a block's properties or executes a callback in a transaction + +#### Type Parameters + +##### T + +`T` *extends* `Partial`\<`BlockProps`\> + +#### Parameters + +##### model + +`string` | `BlockModel`\<`object`\> + +##### props + +`T` + +#### Returns + +`void` + +#### Parameters + +##### model + +`string` | `BlockModel`\<`object`\> + +##### callback + +() => `void` + +#### Returns + +`void` + +#### Param + +The block model or block ID to update + +#### Param + +Either a callback function to execute or properties to update + +#### Throws + +When the block is not found or schema validation fails + +*** + +### blockSize + +#### Get Signature + +> **get** **blockSize**(): `number` + +Get the number of blocks in the store + +##### Returns + +`number` + +*** + +### addBlock() + +> **addBlock**(`flavour`, `blockProps`, `parent`?, `parentIndex`?): `string` + +Creates and adds a new block to the store + +#### Parameters + +##### flavour + +`string` + +The block's flavour (type) + +##### blockProps + +`Partial`\<`BlockSysProps` & `Record`\<`string`, `unknown`\> & `Omit`\<`BlockProps`, `"flavour"`\>\> = `{}` + +Optional properties for the new block + +##### parent? + +Optional parent block or parent block ID + +`null` | `string` | `BlockModel`\<`object`\> + +##### parentIndex? + +`number` + +Optional index position in parent's children + +#### Returns + +`string` + +The ID of the newly created block + +#### Throws + +When store is in readonly mode + +*** + +### addBlocks() + +> **addBlocks**(`blocks`, `parent`?, `parentIndex`?): `string`[] + +Add multiple blocks to the store + +#### Parameters + +##### blocks + +`object`[] + +Array of blocks to add + +##### parent? + +Optional parent block or parent block ID + +`null` | `string` | `BlockModel`\<`object`\> + +##### parentIndex? + +`number` + +Optional index position in parent's children + +#### Returns + +`string`[] + +Array of IDs of the newly created blocks + +*** + +### addSiblingBlocks() + +> **addSiblingBlocks**(`targetModel`, `props`, `place`): `string`[] + +Add sibling blocks to the store + +#### Parameters + +##### targetModel + +`BlockModel` + +The target block model + +##### props + +`Partial`\<`BlockProps`\>[] + +Array of block properties + +##### place + +Optional position to place the new blocks ('after' or 'before') + +`"after"` | `"before"` + +#### Returns + +`string`[] + +Array of IDs of the newly created blocks + +*** + +### deleteBlock() + +> **deleteBlock**(`model`, `options`): `void` + +Delete a block from the store + +#### Parameters + +##### model + +The block model or block ID to delete + +`string` | `BlockModel`\<`object`\> + +##### options + +Optional options for the deletion + +###### bringChildrenTo? + +`BlockModel`\<`object`\> + +Optional block model to bring children to + +###### deleteChildren? + +`boolean` + +Optional flag to delete children + +#### Returns + +`void` + +*** + +### getAllModels() + +> **getAllModels**(): `BlockModel`\<`object`\>[] + +Get all models in the store + +#### Returns + +`BlockModel`\<`object`\>[] + +Array of all models + +*** + +### getBlock() + +> **getBlock**(`id`): `undefined` \| `Block` + +Gets a block by its ID + +#### Parameters + +##### id + +`string` + +The block's ID + +#### Returns + +`undefined` \| `Block` + +The block instance if found, undefined otherwise + +*** + +### getBlock$() + +> **getBlock$**(`id`): `undefined` \| `Block` + +Gets a block by its ID + +#### Parameters + +##### id + +`string` + +The block's ID + +#### Returns + +`undefined` \| `Block` + +The block instance in signal if found, undefined otherwise + +*** + +### getBlocksByFlavour() + +> **getBlocksByFlavour**(`blockFlavour`): `Block`[] + +Gets all blocks of specified flavour(s) + +#### Parameters + +##### blockFlavour + +Single flavour or array of flavours to filter by + +`string` | `string`[] + +#### Returns + +`Block`[] + +Array of matching blocks + +*** + +### getModelById() + +> **getModelById**\<`Model`\>(`id`): `null` \| `Model` + +Get a model by its ID + +#### Type Parameters + +##### Model + +`Model` *extends* `BlockModel`\<`object`\> = `BlockModel`\<`object`\> + +#### Parameters + +##### id + +`string` + +The model's ID + +#### Returns + +`null` \| `Model` + +The model instance if found, null otherwise + +*** + +### getModelsByFlavour() + +> **getModelsByFlavour**(`blockFlavour`): `BlockModel`\<`object`\>[] + +Get all models of specified flavour(s) + +#### Parameters + +##### blockFlavour + +Single flavour or array of flavours to filter by + +`string` | `string`[] + +#### Returns + +`BlockModel`\<`object`\>[] + +Array of matching models + +*** + +### getNext() + +> **getNext**(`block`): `null` \| `BlockModel`\<`object`\> + +Get the next sibling block of a given block + +#### Parameters + +##### block + +Block model or block ID to find next sibling for + +`string` | `BlockModel`\<`object`\> + +#### Returns + +`null` \| `BlockModel`\<`object`\> + +The next sibling block model if found, null otherwise + +*** + +### getNexts() + +> **getNexts**(`block`): `BlockModel`\<`object`\>[] + +Get all next sibling blocks of a given block + +#### Parameters + +##### block + +Block model or block ID to find next siblings for + +`string` | `BlockModel`\<`object`\> + +#### Returns + +`BlockModel`\<`object`\>[] + +Array of next sibling blocks if found, empty array otherwise + +*** + +### getParent() + +> **getParent**(`target`): `null` \| `BlockModel`\<`object`\> + +Gets the parent block of a given block + +#### Parameters + +##### target + +Block model or block ID to find parent for + +`string` | `BlockModel`\<`object`\> + +#### Returns + +`null` \| `BlockModel`\<`object`\> + +The parent block model if found, null otherwise + +*** + +### getPrev() + +> **getPrev**(`block`): `null` \| `BlockModel`\<`object`\> + +Get the previous sibling block of a given block + +#### Parameters + +##### block + +Block model or block ID to find previous sibling for + +`string` | `BlockModel`\<`object`\> + +#### Returns + +`null` \| `BlockModel`\<`object`\> + +The previous sibling block model if found, null otherwise + +*** + +### getPrevs() + +> **getPrevs**(`block`): `BlockModel`\<`object`\>[] + +Get all previous sibling blocks of a given block + +#### Parameters + +##### block + +Block model or block ID to find previous siblings for + +`string` | `BlockModel`\<`object`\> + +#### Returns + +`BlockModel`\<`object`\>[] + +Array of previous sibling blocks if found, empty array otherwise + +*** + +### hasBlock() + +> **hasBlock**(`id`): `boolean` + +Check if a block exists by its ID + +#### Parameters + +##### id + +`string` + +The block's ID + +#### Returns + +`boolean` + +True if the block exists, false otherwise + +*** + +### moveBlocks() + +> **moveBlocks**(`blocksToMove`, `newParent`, `targetSibling`, `shouldInsertBeforeSibling`): `void` + +Move blocks to a new parent block + +#### Parameters + +##### blocksToMove + +`BlockModel`\<`object`\>[] + +Array of block models to move + +##### newParent + +`BlockModel` + +The new parent block model + +##### targetSibling + +Optional target sibling block model + +`null` | `BlockModel`\<`object`\> + +##### shouldInsertBeforeSibling + +`boolean` = `true` + +Optional flag to insert before sibling + +#### Returns + +`void` + +## Store Lifecycle + +### disposableGroup + +> **disposableGroup**: `DisposableGroup` + +Group of disposable resources managed by the store + +*** + +### slots + +> `readonly` **slots**: `object` & `object` + +Slots for receiving events from the store. + +#### Type declaration + +##### historyUpdated + +> **historyUpdated**: `Subject`\<`void`\> + +This fires when the doc history is updated. + +##### yBlockUpdated + +> **yBlockUpdated**: `Subject`\<\{ `id`: `string`; `isLocal`: `boolean`; `type`: `"add"`; \} \| \{ `id`: `string`; `isLocal`: `boolean`; `type`: `"delete"`; \}\> + +This fires when the doc yBlock is updated. + +#### Type declaration + +##### blockUpdated + +> **blockUpdated**: `Subject`\<`BlockUpdatedPayload`\> + +This fires when a block is updated via API call or has just been updated from existing ydoc. + +##### ready + +> **ready**: `Subject`\<`void`\> + +This is always triggered after `doc.load` is called. + +##### rootAdded + +> **rootAdded**: `Subject`\<`string`\> + +This fires when the root block is added via API call or has just been initialized from existing ydoc. +useful for internal block UI components to start subscribing following up events. +Note that at this moment, the whole block tree may not be fully initialized yet. + +##### rootDeleted + +> **rootDeleted**: `Subject`\<`string`\> + +This fires when the root block is deleted via API call or has just been removed from existing ydoc. + +*** + +### dispose() + +> **dispose**(): `void` + +Disposes the store and releases all resources + +#### Returns + +`void` + +*** + +### load() + +> **load**(`initFn`?): `Store` + +Initializes and loads the store + +#### Parameters + +##### initFn? + +() => `void` + +Optional initialization function + +#### Returns + +`Store` + +The store instance + +## Transformer + +### getTransformer() + +> **getTransformer**(`middlewares`): `Transformer` + +Creates a new transformer instance for the store + +#### Parameters + +##### middlewares + +`TransformerMiddleware`[] = `[]` + +Optional array of transformer middlewares + +#### Returns + +`Transformer` + +A new Transformer instance + +## Other + +### awarenessStore + +#### Get Signature + +> **get** **awarenessStore**(): `AwarenessStore` + +Get the AwarenessStore instance for current store + +##### Returns + +`AwarenessStore` + +*** + +### blobSync + +#### Get Signature + +> **get** **blobSync**(): `BlobEngine` + +Get the BlobEngine instance for current store. + +##### Returns + +`BlobEngine` + +*** + +### doc + +#### Get Signature + +> **get** **doc**(): `Doc` + +Get the Doc instance for current store. + +##### Returns + +`Doc` + +*** + +### get + +#### Get Signature + +> **get** **get**(): \<`T`\>(`identifier`, `options`?) => `T` + +Get an extension instance from the store + +##### Example + +```ts +const extension = store.get(SomeExtension); +``` + +##### Returns + +`Function` + +The extension instance + +###### Type Parameters + +###### T + +`T` + +###### Parameters + +###### identifier + +`GeneralServiceIdentifier`\<`T`\> + +###### options? + +`ResolveOptions` + +###### Returns + +`T` + +*** + +### getOptional + +#### Get Signature + +> **get** **getOptional**(): \<`T`\>(`identifier`, `options`?) => `null` \| `T` + +Optional get an extension instance from the store. +The major difference between `get` and `getOptional` is that `getOptional` will not throw an error if the extension is not found. + +##### Example + +```ts +const extension = store.getOptional(SomeExtension); +``` + +##### Returns + +`Function` + +The extension instance + +###### Type Parameters + +###### T + +`T` + +###### Parameters + +###### identifier + +`GeneralServiceIdentifier`\<`T`\> + +###### options? + +`ResolveOptions` + +###### Returns + +`null` \| `T` + +*** + +### provider + +#### Get Signature + +> **get** **provider**(): `ServiceProvider` + +Get the di provider for current store. + +##### Returns + +`ServiceProvider` diff --git a/blocksuite/docs/typedoc.json b/blocksuite/docs/typedoc.json index 0e19e105b9..9432de0e08 100644 --- a/blocksuite/docs/typedoc.json +++ b/blocksuite/docs/typedoc.json @@ -10,7 +10,8 @@ "excludeProtected": true, "excludeExternals": true, "externalPattern": ["node_modules/**/*"], - "disableSources": true + "disableSources": true, + "categoryOrder": ["*", "Other"] }, "readme": "none", "plugin": ["typedoc-plugin-markdown"], diff --git a/blocksuite/framework/block-std/src/gfx/layer.ts b/blocksuite/framework/block-std/src/gfx/layer.ts index ae05da5d5b..51732ad3a2 100644 --- a/blocksuite/framework/block-std/src/gfx/layer.ts +++ b/blocksuite/framework/block-std/src/gfx/layer.ts @@ -492,7 +492,7 @@ export class LayerManager extends GfxExtension { private _reset() { const elements = ( this._doc - .getStore() + .getAllModels() .filter( model => model instanceof GfxBlockElementModel && @@ -798,7 +798,7 @@ export class LayerManager extends GfxExtension { this._disposable.add( store.slots.blockUpdated.subscribe(payload => { if (payload.type === 'add') { - const block = store.getBlockById(payload.id)!; + const block = store.getModelById(payload.id)!; if ( block instanceof GfxBlockElementModel && @@ -810,7 +810,7 @@ export class LayerManager extends GfxExtension { } } if (payload.type === 'update') { - const block = store.getBlockById(payload.id)!; + const block = store.getModelById(payload.id)!; if ( (payload.props.key === 'index' || @@ -825,7 +825,7 @@ export class LayerManager extends GfxExtension { } } if (payload.type === 'delete') { - const block = store.getBlockById(payload.id); + const block = store.getModelById(payload.id); if (block instanceof GfxBlockElementModel) { this.delete(block as GfxBlockElementModel); diff --git a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts index c386462695..29498d888e 100644 --- a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts +++ b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts @@ -396,7 +396,7 @@ export abstract class GfxGroupLikeElementModel< for (const key of this.childIds) { const element = this.surface.getElementById(key) || - (this.surface.doc.getBlockById(key) as GfxBlockElementModel); + (this.surface.doc.getModelById(key) as GfxBlockElementModel); element && elements.push(element); } diff --git a/blocksuite/framework/block-std/src/gfx/selection.ts b/blocksuite/framework/block-std/src/gfx/selection.ts index 24973a0cf7..faa1cc96a2 100644 --- a/blocksuite/framework/block-std/src/gfx/selection.ts +++ b/blocksuite/framework/block-std/src/gfx/selection.ts @@ -316,7 +316,7 @@ export class GfxSelectionManager extends GfxExtension { } const { blocks = [], elements = [] } = groupBy(selection.elements, id => { - return this.std.store.getBlockById(id) ? 'blocks' : 'elements'; + return this.std.store.getModelById(id) ? 'blocks' : 'elements'; }); let instances: (SurfaceSelection | CursorSelection)[] = []; diff --git a/blocksuite/framework/block-std/src/range/range-binding.ts b/blocksuite/framework/block-std/src/range/range-binding.ts index 8e48b13b2b..4c0b9478b5 100644 --- a/blocksuite/framework/block-std/src/range/range-binding.ts +++ b/blocksuite/framework/block-std/src/range/range-binding.ts @@ -236,7 +236,7 @@ export class RangeBinding { return; } - const model = this.host.doc.getBlockById(textSelection.blockId); + const model = this.host.doc.getModelById(textSelection.blockId); // If the model is not found, the selection maybe in another editor if (!model) return; 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 e01685686e..0888f2f2b2 100644 --- a/blocksuite/framework/block-std/src/view/element/block-component.ts +++ b/blocksuite/framework/block-std/src/view/element/block-component.ts @@ -109,7 +109,7 @@ export class BlockComponent< if (this._model) { return this._model; } - const model = this.doc.getBlockById(this.blockId); + const model = this.doc.getModelById(this.blockId); if (!model) { throw new BlockSuiteError( ErrorCode.MissingViewModelError, diff --git a/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts b/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts index 2295849e3a..131f35e772 100644 --- a/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts +++ b/blocksuite/framework/store/src/__tests__/collection.unit.spec.ts @@ -347,7 +347,7 @@ describe('addBlock', () => { }) ); const blockId = await waitOnce(doc.slots.rootAdded); - const block = doc.getBlockById(blockId) as BlockModel; + const block = doc.getModelById(blockId) as BlockModel; assert.equal(block.flavour, 'affine:page'); }); @@ -512,7 +512,7 @@ describe('deleteBlock', () => { }, }); - const deletedModel = doc.getBlockById('1') as BlockModel; + const deletedModel = doc.getModelById('1') as BlockModel; doc.deleteBlock(deletedModel); assert.deepEqual(serializCollection(doc.rootDoc).spaces[spaceId].blocks, { @@ -581,8 +581,8 @@ describe('deleteBlock', () => { }, }); - const deletedModel = doc.getBlockById('2') as BlockModel; - const deletedModelParent = doc.getBlockById('1') as BlockModel; + const deletedModel = doc.getModelById('2') as BlockModel; + const deletedModelParent = doc.getModelById('1') as BlockModel; doc.deleteBlock(deletedModel, { bringChildrenTo: deletedModelParent, }); @@ -693,8 +693,8 @@ describe('deleteBlock', () => { }, }); - const deletedModel = doc.getBlockById('2') as BlockModel; - const moveToModel = doc.getBlockById('3') as BlockModel; + const deletedModel = doc.getModelById('2') as BlockModel; + const moveToModel = doc.getModelById('3') as BlockModel; doc.deleteBlock(deletedModel, { bringChildrenTo: moveToModel, }); @@ -820,11 +820,11 @@ describe('getBlock', () => { doc.addBlock('affine:paragraph', {}, noteId); doc.addBlock('affine:paragraph', {}, noteId); - const text = doc.getBlockById('3') as BlockModel; + const text = doc.getModelById('3') as BlockModel; assert.equal(text.flavour, 'affine:paragraph'); assert.equal(rootModel.children[0].children.indexOf(text), 1); - const invalid = doc.getBlockById('😅'); + const invalid = doc.getModelById('😅'); assert.equal(invalid, null); }); diff --git a/blocksuite/framework/store/src/model/doc.ts b/blocksuite/framework/store/src/model/doc.ts index 194a866bae..1a34b3a388 100644 --- a/blocksuite/framework/store/src/model/doc.ts +++ b/blocksuite/framework/store/src/model/doc.ts @@ -24,7 +24,13 @@ export interface Doc { dispose(): void; slots: { + /** + * This fires when the doc history is updated. + */ historyUpdated: Subject; + /** + * This fires when the doc yBlock is updated. + */ yBlockUpdated: Subject< | { type: 'add'; diff --git a/blocksuite/framework/store/src/model/store/store.ts b/blocksuite/framework/store/src/model/store/store.ts index f9019611ca..a4f53637fc 100644 --- a/blocksuite/framework/store/src/model/store/store.ts +++ b/blocksuite/framework/store/src/model/store/store.ts @@ -61,9 +61,24 @@ export type BlockUpdatedPayload = const internalExtensions = [StoreSelectionExtension]; +/** + * Core store class that manages blocks and their lifecycle in BlockSuite + * @remarks + * The Store class is responsible for managing the lifecycle of blocks, handling transactions, + * and maintaining the block tree structure. + * A store is a piece of data created from one or a part of a Y.Doc. + * + * @category Store + */ export class Store { + /** @internal */ readonly userExtensions: ExtensionType[]; + /** + * Group of disposable resources managed by the store + * + * @category Store Lifecycle + */ disposableGroup = new DisposableGroup(); private readonly _provider: ServiceProvider; @@ -91,6 +106,11 @@ export class Store { private readonly _schema: Schema; + /** + * Slots for receiving events from the store. + * + * @category Store Lifecycle + */ readonly slots: Doc['slots'] & { /** This is always triggered after `doc.load` is called. */ ready: Subject; @@ -100,106 +120,60 @@ export class Store { * Note that at this moment, the whole block tree may not be fully initialized yet. */ rootAdded: Subject; + /** + * This fires when the root block is deleted via API call or has just been removed from existing ydoc. + */ rootDeleted: Subject; + /** + * This fires when a block is updated via API call or has just been updated from existing ydoc. + */ blockUpdated: Subject; }; - updateBlock: { - >(model: BlockModel | string, props: T): void; - (model: BlockModel | string, callback: () => void): void; - } = ( - modelOrId: BlockModel | string, - callBackOrProps: (() => void) | Partial - ) => { - if (this.readonly) { - console.error('cannot modify data in readonly mode'); - return; - } - - const isCallback = typeof callBackOrProps === 'function'; - - const model = - typeof modelOrId === 'string' - ? this.getBlock(modelOrId)?.model - : modelOrId; - if (!model) { - throw new BlockSuiteError( - ErrorCode.ModelCRUDError, - `updating block: ${modelOrId} not found` - ); - } - - if (!isCallback) { - const parent = this.getParent(model); - this.schema.validate( - model.flavour, - parent?.flavour, - callBackOrProps.children?.map(child => child.flavour) - ); - } - - const yBlock = this._yBlocks.get(model.id); - if (!yBlock) { - throw new BlockSuiteError( - ErrorCode.ModelCRUDError, - `updating block: ${model.id} not found` - ); - } - - const block = this.getBlock(model.id); - if (!block) return; - - this.transact(() => { - if (isCallback) { - callBackOrProps(); - this._runQuery(block); - return; - } - - if (callBackOrProps.children) { - this._crud.updateBlockChildren( - model.id, - callBackOrProps.children.map(child => child.id) - ); - } - - const schema = this.schema.flavourSchemaMap.get(model.flavour); - if (!schema) { - throw new BlockSuiteError( - ErrorCode.ModelCRUDError, - `schema for flavour: ${model.flavour} not found` - ); - } - syncBlockProps(schema, model, yBlock, callBackOrProps); - this._runQuery(block); - return; - }); - }; - private get _yBlocks() { return this._doc.yBlocks; } + /** + * Get the {@link AwarenessStore} instance for current store + */ get awarenessStore() { return this._doc.awarenessStore; } + /** + * Get the di provider for current store. + */ get provider() { return this._provider; } + /** + * Get the {@link BlobEngine} instance for current store. + */ get blobSync() { return this.workspace.blobSync; } + /** + * Get the {@link Doc} instance for current store. + */ get doc() { return this._doc; } + /** + * @internal + */ get blocks() { return this._blocks; } + /** + * Get the number of blocks in the store + * + * @category Block CRUD + */ get blockSize() { return Object.values(this._blocks.peek()).length; } @@ -488,6 +462,17 @@ export class Store { } } + /** + * Creates and adds a new block to the store + * @param flavour - The block's flavour (type) + * @param blockProps - Optional properties for the new block + * @param parent - Optional parent block or parent block ID + * @param parentIndex - Optional index position in parent's children + * @returns The ID of the newly created block + * @throws {BlockSuiteError} When store is in readonly mode + * + * @category Block CRUD + */ addBlock( flavour: string, blockProps: Partial> = {}, @@ -516,6 +501,15 @@ export class Store { return id; } + /** + * Add multiple blocks to the store + * @param blocks - Array of blocks to add + * @param parent - Optional parent block or parent block ID + * @param parentIndex - Optional index position in parent's children + * @returns Array of IDs of the newly created blocks + * + * @category Block CRUD + */ addBlocks( blocks: Array<{ flavour: string; @@ -539,6 +533,15 @@ export class Store { return ids; } + /** + * Add sibling blocks to the store + * @param targetModel - The target block model + * @param props - Array of block properties + * @param place - Optional position to place the new blocks ('after' or 'before') + * @returns Array of IDs of the newly created blocks + * + * @category Block CRUD + */ addSiblingBlocks( targetModel: BlockModel, props: Array>, @@ -576,6 +579,95 @@ export class Store { return this.addBlocks(blocks, parent.id, insertIndex); } + /** + * Updates a block's properties or executes a callback in a transaction + * @param modelOrId - The block model or block ID to update + * @param callBackOrProps - Either a callback function to execute or properties to update + * @throws {BlockSuiteError} When the block is not found or schema validation fails + * + * @category Block CRUD + */ + updateBlock: { + >(model: BlockModel | string, props: T): void; + (model: BlockModel | string, callback: () => void): void; + } = ( + modelOrId: BlockModel | string, + callBackOrProps: (() => void) | Partial + ) => { + if (this.readonly) { + console.error('cannot modify data in readonly mode'); + return; + } + + const isCallback = typeof callBackOrProps === 'function'; + + const model = + typeof modelOrId === 'string' + ? this.getBlock(modelOrId)?.model + : modelOrId; + if (!model) { + throw new BlockSuiteError( + ErrorCode.ModelCRUDError, + `updating block: ${modelOrId} not found` + ); + } + + if (!isCallback) { + const parent = this.getParent(model); + this.schema.validate( + model.flavour, + parent?.flavour, + callBackOrProps.children?.map(child => child.flavour) + ); + } + + const yBlock = this._yBlocks.get(model.id); + if (!yBlock) { + throw new BlockSuiteError( + ErrorCode.ModelCRUDError, + `updating block: ${model.id} not found` + ); + } + + const block = this.getBlock(model.id); + if (!block) return; + + this.transact(() => { + if (isCallback) { + callBackOrProps(); + this._runQuery(block); + return; + } + + if (callBackOrProps.children) { + this._crud.updateBlockChildren( + model.id, + callBackOrProps.children.map(child => child.id) + ); + } + + const schema = this.schema.flavourSchemaMap.get(model.flavour); + if (!schema) { + throw new BlockSuiteError( + ErrorCode.ModelCRUDError, + `schema for flavour: ${model.flavour} not found` + ); + } + syncBlockProps(schema, model, yBlock, callBackOrProps); + this._runQuery(block); + return; + }); + }; + + /** + * Delete a block from the store + * @param model - The block model or block ID to delete + * @param options - Optional options for the deletion + * @param options.bringChildrenTo - Optional block model to bring children to + * @param options.deleteChildren - Optional flag to delete children + * + * @category Block CRUD + */ deleteBlock( model: BlockModel | string, options: { @@ -610,49 +702,49 @@ export class Store { }); } - dispose() { - this._provider.getAll(StoreExtensionIdentifier).forEach(ext => { - ext.disposed(); - }); - this.slots.ready.complete(); - this.slots.rootAdded.complete(); - this.slots.rootDeleted.complete(); - this.slots.blockUpdated.complete(); - this.disposableGroup.dispose(); - this._isDisposed = true; - } - + /** + * Gets a block by its ID + * @param id - The block's ID + * @returns The block instance if found, undefined otherwise + * + * @category Block CRUD + */ getBlock(id: string): Block | undefined { return this._blocks.peek()[id]; } + /** + * Gets a block by its ID + * @param id - The block's ID + * @returns The block instance in signal if found, undefined otherwise + * + * @category Block CRUD + */ getBlock$(id: string): Block | undefined { return this._blocks.value[id]; } /** - * @deprecated - * Use `getBlocksByFlavour` instead. + * Get a model by its ID + * @param id - The model's ID + * @returns The model instance if found, null otherwise + * + * @category Block CRUD */ - getBlockByFlavour(blockFlavour: string | string[]) { - return this.getBlocksByFlavour(blockFlavour).map(x => x.model); - } - - /** - * @deprecated - * Use `getBlock` instead. - */ - getBlockById( + getModelById( id: string ): Model | null { return (this.getBlock(id)?.model ?? null) as Model | null; } - getStore() { - return Object.values(this._blocks.peek()).map(block => block.model); - } - - getBlocksByFlavour(blockFlavour: string | string[]) { + /** + * Gets all blocks of specified flavour(s) + * @param blockFlavour - Single flavour or array of flavours to filter by + * @returns Array of matching blocks + * + * @category Block CRUD + */ + getBlocksByFlavour(blockFlavour: string | string[]): Block[] { const flavours = typeof blockFlavour === 'string' ? [blockFlavour] : blockFlavour; @@ -661,21 +753,34 @@ export class Store { ); } - getNext(block: BlockModel | string) { - return this._getSiblings( - block, - (parent, index) => parent.children[index + 1] ?? null - ); + /** + * Get all models in the store + * @returns Array of all models + * + * @category Block CRUD + */ + getAllModels() { + return Object.values(this._blocks.peek()).map(block => block.model); } - getNexts(block: BlockModel | string) { - return ( - this._getSiblings(block, (parent, index) => - parent.children.slice(index + 1) - ) ?? [] - ); + /** + * Get all models of specified flavour(s) + * @param blockFlavour - Single flavour or array of flavours to filter by + * @returns Array of matching models + * + * @category Block CRUD + */ + getModelsByFlavour(blockFlavour: string | string[]): BlockModel[] { + return this.getBlocksByFlavour(blockFlavour).map(x => x.model); } + /** + * Gets the parent block of a given block + * @param target - Block model or block ID to find parent for + * @returns The parent block model if found, null otherwise + * + * @category Block CRUD + */ getParent(target: BlockModel | string): BlockModel | null { const targetId = typeof target === 'string' ? target : target.id; const parentId = this._crud.getParent(targetId); @@ -687,6 +792,13 @@ export class Store { return parent.model; } + /** + * Get the previous sibling block of a given block + * @param block - Block model or block ID to find previous sibling for + * @returns The previous sibling block model if found, null otherwise + * + * @category Block CRUD + */ getPrev(block: BlockModel | string) { return this._getSiblings( block, @@ -694,6 +806,13 @@ export class Store { ); } + /** + * Get all previous sibling blocks of a given block + * @param block - Block model or block ID to find previous siblings for + * @returns Array of previous sibling blocks if found, empty array otherwise + * + * @category Block CRUD + */ getPrevs(block: BlockModel | string) { return ( this._getSiblings(block, (parent, index) => @@ -702,38 +821,55 @@ export class Store { ); } - getSchemaByFlavour(flavour: string) { - return this._schema.flavourSchemaMap.get(flavour); + /** + * Get the next sibling block of a given block + * @param block - Block model or block ID to find next sibling for + * @returns The next sibling block model if found, null otherwise + * + * @category Block CRUD + */ + getNext(block: BlockModel | string) { + return this._getSiblings( + block, + (parent, index) => parent.children[index + 1] ?? null + ); } + /** + * Get all next sibling blocks of a given block + * @param block - Block model or block ID to find next siblings for + * @returns Array of next sibling blocks if found, empty array otherwise + * + * @category Block CRUD + */ + getNexts(block: BlockModel | string) { + return ( + this._getSiblings(block, (parent, index) => + parent.children.slice(index + 1) + ) ?? [] + ); + } + + /** + * Check if a block exists by its ID + * @param id - The block's ID + * @returns True if the block exists, false otherwise + * + * @category Block CRUD + */ hasBlock(id: string) { return id in this._blocks.peek(); } /** - * @deprecated - * Use `hasBlock` instead. + * Move blocks to a new parent block + * @param blocksToMove - Array of block models to move + * @param newParent - The new parent block model + * @param targetSibling - Optional target sibling block model + * @param shouldInsertBeforeSibling - Optional flag to insert before sibling + * + * @category Block CRUD */ - hasBlockById(id: string) { - return this.hasBlock(id); - } - - load(initFn?: () => void) { - if (this._isDisposed) { - this.disposableGroup = new DisposableGroup(); - this._subscribeToSlots(); - this._isDisposed = false; - } - - this._doc.load(initFn); - this._provider.getAll(StoreExtensionIdentifier).forEach(ext => { - ext.loaded(); - }); - this.slots.ready.next(); - this.slots.rootAdded.next(this.root?.id ?? ''); - return this; - } - moveBlocks( blocksToMove: BlockModel[], newParent: BlockModel, @@ -755,14 +891,13 @@ export class Store { }); } - get get() { - return this.provider.get.bind(this.provider); - } - - get getOptional() { - return this.provider.getOptional.bind(this.provider); - } - + /** + * Creates a new transformer instance for the store + * @param middlewares - Optional array of transformer middlewares + * @returns A new Transformer instance + * + * @category Transformer + */ getTransformer(middlewares: TransformerMiddleware[] = []) { return new Transformer({ schema: this.schema, @@ -775,4 +910,72 @@ export class Store { middlewares, }); } + + /** + * Get an extension instance from the store + * @returns The extension instance + * + * @example + * ```ts + * const extension = store.get(SomeExtension); + * ``` + */ + get get() { + return this.provider.get.bind(this.provider); + } + + /** + * Optional get an extension instance from the store. + * The major difference between `get` and `getOptional` is that `getOptional` will not throw an error if the extension is not found. + * + * @returns The extension instance + * + * @example + * ```ts + * const extension = store.getOptional(SomeExtension); + * ``` + */ + get getOptional() { + return this.provider.getOptional.bind(this.provider); + } + + /** + * Initializes and loads the store + * @param initFn - Optional initialization function + * @returns The store instance + * + * @category Store Lifecycle + */ + load(initFn?: () => void) { + if (this._isDisposed) { + this.disposableGroup = new DisposableGroup(); + this._subscribeToSlots(); + this._isDisposed = false; + } + + this._doc.load(initFn); + this._provider.getAll(StoreExtensionIdentifier).forEach(ext => { + ext.loaded(); + }); + this.slots.ready.next(); + this.slots.rootAdded.next(this.root?.id ?? ''); + return this; + } + + /** + * Disposes the store and releases all resources + * + * @category Store Lifecycle + */ + dispose() { + this._provider.getAll(StoreExtensionIdentifier).forEach(ext => { + ext.disposed(); + }); + this.slots.ready.complete(); + this.slots.rootAdded.complete(); + this.slots.rootDeleted.complete(); + this.slots.blockUpdated.complete(); + this.disposableGroup.dispose(); + this._isDisposed = true; + } } diff --git a/blocksuite/framework/store/src/transformer/transformer.ts b/blocksuite/framework/store/src/transformer/transformer.ts index 75ca81ea70..80342efac4 100644 --- a/blocksuite/framework/store/src/transformer/transformer.ts +++ b/blocksuite/framework/store/src/transformer/transformer.ts @@ -293,7 +293,7 @@ export class Transformer { } const contentBlocks = blockTree.children - .map(tree => doc.getBlockById(tree.draft.id)) + .map(tree => doc.getModelById(tree.draft.id)) .filter((x): x is BlockModel => x !== null) .map(model => toDraftModel(model)); diff --git a/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts b/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts index f192cb7fbe..596d4af24c 100644 --- a/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts +++ b/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts @@ -14,7 +14,9 @@ let model: SurfaceBlockModel; beforeEach(async () => { const cleanup = await setupEditor('edgeless'); - const models = doc.getBlockByFlavour('affine:surface') as SurfaceBlockModel[]; + const models = doc.getModelsByFlavour( + 'affine:surface' + ) as SurfaceBlockModel[]; model = models[0]; diff --git a/blocksuite/integration-test/src/__tests__/main/snapshot.spec.ts b/blocksuite/integration-test/src/__tests__/main/snapshot.spec.ts index 4e3a6e6124..632094634f 100644 --- a/blocksuite/integration-test/src/__tests__/main/snapshot.spec.ts +++ b/blocksuite/integration-test/src/__tests__/main/snapshot.spec.ts @@ -51,7 +51,7 @@ const snapshotTest = async (snapshotUrl: string, elementsCount: number) => { editor.doc = newDoc; await wait(); - const surface = newDoc.getBlockByFlavour( + const surface = newDoc.getModelsByFlavour( 'affine:surface' )[0] as SurfaceBlockModel; const surfaceElements = [...surface['_elementModels']].map( diff --git a/blocksuite/integration-test/src/__tests__/utils/edgeless.ts b/blocksuite/integration-test/src/__tests__/utils/edgeless.ts index 340b72cc79..bfc86d0d21 100644 --- a/blocksuite/integration-test/src/__tests__/utils/edgeless.ts +++ b/blocksuite/integration-test/src/__tests__/utils/edgeless.ts @@ -8,7 +8,7 @@ import type { Store } from '@blocksuite/store'; import type { TestAffineEditorContainer } from '../../index.js'; export function getSurface(doc: Store, editor: TestAffineEditorContainer) { - const surfaceModel = doc.getBlockByFlavour('affine:surface'); + const surfaceModel = doc.getModelsByFlavour('affine:surface'); return editor.host!.view.getBlock( surfaceModel[0]!.id diff --git a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts index 2cabd299fc..addbca99c1 100644 --- a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts +++ b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts @@ -489,7 +489,7 @@ export class StarterDebugMenu extends ShadowlessElement { ); for (const doc of docs) { if (doc) { - const noteBlock = window.doc.getBlockByFlavour('affine:note'); + const noteBlock = window.doc.getModelsByFlavour('affine:note'); window.doc.addBlock( 'affine:paragraph', { diff --git a/blocksuite/playground/apps/starter/data/database.ts b/blocksuite/playground/apps/starter/data/database.ts index f3541e6b05..a4d5a64baa 100644 --- a/blocksuite/playground/apps/starter/data/database.ts +++ b/blocksuite/playground/apps/starter/data/database.ts @@ -27,7 +27,7 @@ export const database: InitFn = (collection: Workspace, id: string) => { // Add note block inside root block const noteId = doc.addBlock('affine:note', {}, rootId); const pId = doc.addBlock('affine:paragraph', {}, noteId); - const model = doc.getBlockById(pId); + const model = doc.getModelById(pId); if (!model) { throw new Error('model is not found'); } @@ -40,7 +40,7 @@ export const database: InitFn = (collection: Workspace, id: string) => { }, noteId ); - const database = doc.getBlockById(databaseId) as DatabaseBlockModel; + const database = doc.getModelById(databaseId) as DatabaseBlockModel; const datasource = new DatabaseBlockDataSource(database); datasource.viewManager.viewAdd('table'); database.props.title = new Text(title); diff --git a/packages/frontend/core/src/blocksuite/ai/_common/chat-actions-handle.ts b/packages/frontend/core/src/blocksuite/ai/_common/chat-actions-handle.ts index 95554b1d70..e616e0c29a 100644 --- a/packages/frontend/core/src/blocksuite/ai/_common/chat-actions-handle.ts +++ b/packages/frontend/core/src/blocksuite/ai/_common/chat-actions-handle.ts @@ -162,7 +162,7 @@ function addAIChatBlock( const { doc } = host; const surfaceBlock = doc - .getStore() + .getAllModels() .find(block => block.flavour === 'affine:surface'); if (!surfaceBlock) { return; @@ -520,7 +520,7 @@ const CREATE_AS_LINKED_DOC = { const { doc } = host; const surfaceBlock = doc - .getStore() + .getAllModels() .find(block => block.flavour === 'affine:surface'); if (!surfaceBlock) { return false; diff --git a/packages/frontend/core/src/blocksuite/ai/actions/page-response.ts b/packages/frontend/core/src/blocksuite/ai/actions/page-response.ts index e4c1f8efa9..b62c52cb28 100644 --- a/packages/frontend/core/src/blocksuite/ai/actions/page-response.ts +++ b/packages/frontend/core/src/blocksuite/ai/actions/page-response.ts @@ -236,7 +236,7 @@ function getEdgelessContentBound(host: EditorHost) { const elements = ( host.doc - .getStore() + .getAllModels() .filter( model => model instanceof GfxBlockElementModel && diff --git a/packages/frontend/core/src/blocksuite/ai/mini-mindmap/mindmap-preview.ts b/packages/frontend/core/src/blocksuite/ai/mini-mindmap/mindmap-preview.ts index 037664ec73..e81ab873a9 100644 --- a/packages/frontend/core/src/blocksuite/ai/mini-mindmap/mindmap-preview.ts +++ b/packages/frontend/core/src/blocksuite/ai/mini-mindmap/mindmap-preview.ts @@ -106,7 +106,7 @@ export class MiniMindmapPreview extends WithDisposable(LitElement) { const doc = collection.createDoc({ id: 'doc:home' }).load(); const rootId = doc.addBlock('affine:page', {}); const surfaceId = doc.addBlock('affine:surface', {}, rootId); - const surface = doc.getBlockById(surfaceId) as SurfaceBlockModel; + const surface = doc.getModelById(surfaceId) as SurfaceBlockModel; doc.resetHistory(); return { diff --git a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts index 30641a51fd..d24ed94236 100644 --- a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts +++ b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts @@ -163,7 +163,7 @@ export class AIChatBlockPeekView extends LitElement { const { doc } = this.host; // create a new AI chat block const surfaceBlock = doc - .getStore() + .getAllModels() .find(block => block.flavour === 'affine:surface'); if (!surfaceBlock) { return; diff --git a/packages/frontend/core/src/blocksuite/utils/markdown-utils.ts b/packages/frontend/core/src/blocksuite/utils/markdown-utils.ts index a9fba114b5..9fec76f37c 100644 --- a/packages/frontend/core/src/blocksuite/utils/markdown-utils.ts +++ b/packages/frontend/core/src/blocksuite/utils/markdown-utils.ts @@ -44,7 +44,7 @@ function processSnapshot( text: TextSelection, host: EditorHost ) { - const model = host.doc.getBlockById(snapshot.id); + const model = host.doc.getModelById(snapshot.id); if (!model) { return; } diff --git a/packages/frontend/core/src/components/hooks/affine/use-reference-link-helper.ts b/packages/frontend/core/src/components/hooks/affine/use-reference-link-helper.ts index 000643bf84..f5b17057dc 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-reference-link-helper.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-reference-link-helper.ts @@ -21,7 +21,7 @@ export function useReferenceLinkHelper(docCollection: Workspace) { }, }, ] as DeltaInsert[]); - const [frame] = page.getBlockByFlavour('affine:note'); + const [frame] = page.getModelsByFlavour('affine:note'); frame && page.addBlock('affine:paragraph', { text }, frame.id); }, diff --git a/packages/frontend/core/src/components/page-list/__tests__/use-block-suite-page-preview.spec.ts b/packages/frontend/core/src/components/page-list/__tests__/use-block-suite-page-preview.spec.ts index b33fd1fdd2..d5869ac4a1 100644 --- a/packages/frontend/core/src/components/page-list/__tests__/use-block-suite-page-preview.spec.ts +++ b/packages/frontend/core/src/components/page-list/__tests__/use-block-suite-page-preview.spec.ts @@ -44,12 +44,12 @@ describe('useBlockSuitePagePreview', () => { { text: new Text('Hello, world!'), }, - page.getBlockByFlavour('affine:note')[0].id + page.getModelsByFlavour('affine:note')[0].id ); const hook = renderHook(() => useAtomValue(useBlockSuitePagePreview(page))); expect(hook.result.current).toBe('Hello, world!'); page.transact(() => { - page.getBlockById(id)!.text!.insert('Test', 0); + page.getModelById(id)!.text!.insert('Test', 0); }); await new Promise(resolve => setTimeout(resolve, 100)); hook.rerender(); @@ -61,7 +61,7 @@ describe('useBlockSuitePagePreview', () => { { text: new Text('First block!'), }, - page.getBlockByFlavour('affine:note')[0].id, + page.getModelsByFlavour('affine:note')[0].id, 0 ); await new Promise(resolve => setTimeout(resolve, 100)); diff --git a/tests/blocksuite/e2e/embed-synced-doc.spec.ts b/tests/blocksuite/e2e/embed-synced-doc.spec.ts index e58d2a6c72..fc25adda22 100644 --- a/tests/blocksuite/e2e/embed-synced-doc.spec.ts +++ b/tests/blocksuite/e2e/embed-synced-doc.spec.ts @@ -251,7 +251,7 @@ test.describe('Embed synced doc', () => { }, noteId ); - const model = doc2.getBlockById(databaseId) as DatabaseBlockModel; + const model = doc2.getModelById(databaseId) as DatabaseBlockModel; const datasource = new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); datasource.viewManager.viewAdd('table'); diff --git a/tests/blocksuite/e2e/utils/actions/edgeless.ts b/tests/blocksuite/e2e/utils/actions/edgeless.ts index d342b229d3..2e9d446487 100644 --- a/tests/blocksuite/e2e/utils/actions/edgeless.ts +++ b/tests/blocksuite/e2e/utils/actions/edgeless.ts @@ -67,7 +67,7 @@ export async function getNoteRect(page: Page, noteId: string) { const xywh: string | null = await page.evaluate( ([noteId]) => { const doc = window.collection.getDoc('doc:home'); - const block = doc?.getBlockById(noteId); + const block = doc?.getModelById(noteId); if (block?.flavour === 'affine:note') { return (block as NoteBlockModel).xywh; } else { @@ -85,7 +85,7 @@ export async function getNoteProps(page: Page, noteId: string) { const props = await page.evaluate( ([id]) => { const doc = window.collection.getDoc('doc:home'); - const block = doc?.getBlockById(id); + const block = doc?.getModelById(id); if (block?.flavour === 'affine:note') { return (block as NoteBlockModel).keys.reduce( (pre, key) => { diff --git a/tests/blocksuite/e2e/utils/actions/misc.ts b/tests/blocksuite/e2e/utils/actions/misc.ts index cfbcf320cf..16262ca265 100644 --- a/tests/blocksuite/e2e/utils/actions/misc.ts +++ b/tests/blocksuite/e2e/utils/actions/misc.ts @@ -337,7 +337,7 @@ export async function initEmptyDatabaseState(page: Page, rootId?: string) { }, noteId ); - const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + const model = doc.getModelById(databaseId) as DatabaseBlockModel; const datasource = new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); datasource.viewManager.viewAdd('table'); @@ -373,7 +373,7 @@ export async function initKanbanViewState( }, noteId ); - const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + const model = doc.getModelById(databaseId) as DatabaseBlockModel; const datasource = new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); const rowIds = config.rows.map(rowText => { @@ -432,7 +432,7 @@ export async function initEmptyDatabaseWithParagraphState( }, noteId ); - const model = doc.getBlockById(databaseId) as DatabaseBlockModel; + const model = doc.getModelById(databaseId) as DatabaseBlockModel; const datasource = new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); datasource.viewManager.viewAdd('table');