diff --git a/blocksuite/framework/store/src/model/doc.ts b/blocksuite/framework/store/src/model/doc.ts index 33ad2cdb55..b9944cb36f 100644 --- a/blocksuite/framework/store/src/model/doc.ts +++ b/blocksuite/framework/store/src/model/doc.ts @@ -1,4 +1,3 @@ -import type { Subject } from 'rxjs'; import type * as Y from 'yjs'; import type { AwarenessStore } from '../yjs/awareness.js'; @@ -19,18 +18,6 @@ export interface Doc { get ready(): boolean; dispose(): void; - slots: { - /** - * @internal - * This fires when the doc yBlock is updated. - */ - yBlockUpdated: Subject<{ - type: 'add' | 'delete'; - id: string; - isLocal: boolean; - }>; - }; - clear(): void; getStore(options?: GetBlocksOptions): Store; clearQuery(query: Query, readonly?: boolean): void; diff --git a/blocksuite/framework/store/src/model/store/store.ts b/blocksuite/framework/store/src/model/store/store.ts index c790761415..d5df382f30 100644 --- a/blocksuite/framework/store/src/model/store/store.ts +++ b/blocksuite/framework/store/src/model/store/store.ts @@ -19,6 +19,7 @@ import { type BlockModel, type BlockOptions, type BlockProps, + type YBlock, } from '../block/index.js'; import type { Doc } from '../doc.js'; import { DocCRUD } from './crud.js'; @@ -134,7 +135,7 @@ type StoreBlockUpdatedPayloads = * @interface * @category Store */ -export type StoreSlots = Doc['slots'] & { +export type StoreSlots = { /** * This fires after `doc.load` is called. * The Y.Doc is fully loaded and ready to use. @@ -165,6 +166,19 @@ export type StoreSlots = Doc['slots'] & { * This fires when the history is updated. */ historyUpdated: Subject; + /** @internal */ + yBlockUpdated: Subject< + | { + type: 'add'; + id: string; + isLocal: boolean; + } + | { + type: 'delete'; + id: string; + isLocal: boolean; + } + >; }; const internalExtensions = [StoreSelectionExtension]; @@ -555,7 +569,7 @@ export class Store { rootDeleted: new Subject(), blockUpdated: new Subject(), historyUpdated: new Subject(), - yBlockUpdated: this._doc.slots.yBlockUpdated, + yBlockUpdated: new Subject(), }; this._schema = new Schema(); @@ -584,6 +598,11 @@ export class Store { this._query = query; } + this._yBlocks.observeDeep(this._handleYEvents); + this._yBlocks.forEach((_, id) => { + this._handleYBlockAdd(id, false); + }); + this._yBlocks.forEach((_, id) => { if (id in this._blocks.peek()) { return; @@ -622,7 +641,7 @@ export class Store { private readonly _subscribeToSlots = () => { this.disposableGroup.add( - this._doc.slots.yBlockUpdated.subscribe(({ type, id, isLocal }) => { + this.slots.yBlockUpdated.subscribe(({ type, id, isLocal }) => { switch (type) { case 'add': { this._onBlockAdded(id, isLocal, false); @@ -1252,12 +1271,57 @@ export class Store { this._provider.getAll(StoreExtensionIdentifier).forEach(ext => { ext.disposed(); }); + if (this.doc.ready) { + this._yBlocks.unobserveDeep(this._handleYEvents); + } this.slots.ready.complete(); this.slots.rootAdded.complete(); this.slots.rootDeleted.complete(); this.slots.blockUpdated.complete(); this.slots.historyUpdated.complete(); + this.slots.yBlockUpdated.complete(); this.disposableGroup.dispose(); this._isDisposed = true; } + + private _handleYBlockAdd(id: string, isLocal: boolean) { + this.slots.yBlockUpdated.next({ type: 'add', id, isLocal }); + } + + private _handleYBlockDelete(id: string, isLocal: boolean) { + this.slots.yBlockUpdated.next({ type: 'delete', id, isLocal }); + } + + private _handleYEvent(event: Y.YEvent>) { + // event on top-level block store + if (event.target !== this._yBlocks) { + return; + } + const isLocal = + !event.transaction.origin || + !this._yBlocks.doc || + event.transaction.origin instanceof Y.UndoManager || + event.transaction.origin.proxy + ? true + : event.transaction.origin === this._yBlocks.doc.clientID; + event.keys.forEach((value, id) => { + try { + if (value.action === 'add') { + this._handleYBlockAdd(id, isLocal); + return; + } + if (value.action === 'delete') { + this._handleYBlockDelete(id, isLocal); + return; + } + } catch (e) { + console.error('An error occurred while handling Yjs event:'); + console.error(e); + } + }); + } + + private readonly _handleYEvents = (events: Y.YEvent[]) => { + events.forEach(event => this._handleYEvent(event)); + }; } diff --git a/blocksuite/framework/store/src/test/test-doc.ts b/blocksuite/framework/store/src/test/test-doc.ts index 415b8bcffd..ba9255def0 100644 --- a/blocksuite/framework/store/src/test/test-doc.ts +++ b/blocksuite/framework/store/src/test/test-doc.ts @@ -20,11 +20,6 @@ export class TestDoc implements Doc { private readonly _storeMap = new Map(); - // doc/space container. - private readonly _handleYEvents = (events: Y.YEvent[]) => { - events.forEach(event => this._handleYEvent(event)); - }; - private readonly _initSubDoc = () => { let subDoc = this.rootDoc.getMap('spaces').get(this.id); if (!subDoc) { @@ -79,21 +74,6 @@ export class TestDoc implements Doc { readonly rootDoc: Y.Doc; - readonly slots = { - yBlockUpdated: new Subject< - | { - type: 'add'; - id: string; - isLocal: boolean; - } - | { - type: 'delete'; - id: string; - isLocal: boolean; - } - >(), - }; - get blobSync() { return this.workspace.blobSync; } @@ -141,48 +121,6 @@ export class TestDoc implements Doc { return (readonly?.toString() as 'true' | 'false') ?? 'false'; } - private _handleYBlockAdd(id: string, isLocal: boolean) { - this.slots.yBlockUpdated.next({ type: 'add', id, isLocal }); - } - - private _handleYBlockDelete(id: string, isLocal: boolean) { - this.slots.yBlockUpdated.next({ type: 'delete', id, isLocal }); - } - - private _handleYEvent(event: Y.YEvent>) { - // event on top-level block store - if (event.target !== this._yBlocks) { - return; - } - const isLocal = - !event.transaction.origin || - !this._yBlocks.doc || - event.transaction.origin instanceof Y.UndoManager || - event.transaction.origin.proxy - ? true - : event.transaction.origin === this._yBlocks.doc.clientID; - event.keys.forEach((value, id) => { - try { - if (value.action === 'add') { - this._handleYBlockAdd(id, isLocal); - return; - } - if (value.action === 'delete') { - this._handleYBlockDelete(id, isLocal); - return; - } - } catch (e) { - console.error('An error occurred while handling Yjs event:'); - console.error(e); - } - }); - } - - private _initYBlocks() { - const { _yBlocks } = this; - _yBlocks.observeDeep(this._handleYEvents); - } - clear() { this._yBlocks.clear(); } @@ -200,7 +138,6 @@ export class TestDoc implements Doc { dispose() { if (this.ready) { - this._yBlocks.unobserveDeep(this._handleYEvents); this._yBlocks.clear(); } } @@ -263,12 +200,6 @@ export class TestDoc implements Doc { this._ySpaceDoc.load(); - this._initYBlocks(); - - this._yBlocks.forEach((_, id) => { - this._handleYBlockAdd(id, false); - }); - initFn?.(); this._ready = true; diff --git a/packages/frontend/core/src/modules/workspace/impls/doc.ts b/packages/frontend/core/src/modules/workspace/impls/doc.ts index ba4711ce7b..5789a01748 100644 --- a/packages/frontend/core/src/modules/workspace/impls/doc.ts +++ b/packages/frontend/core/src/modules/workspace/impls/doc.ts @@ -24,11 +24,6 @@ export class DocImpl implements Doc { private readonly _storeMap = new Map(); - // doc/space container. - private readonly _handleYEvents = (events: Y.YEvent[]) => { - events.forEach(event => this._handleYEvent(event)); - }; - private readonly _initSubDoc = () => { { // This is a piece of old version compatible code. The old version relies on the subdoc instance on `spaces`. @@ -74,22 +69,6 @@ export class DocImpl implements Doc { readonly rootDoc: Y.Doc; - readonly slots = { - // eslint-disable-next-line rxjs/finnish - yBlockUpdated: new Subject< - | { - type: 'add'; - id: string; - isLocal: boolean; - } - | { - type: 'delete'; - id: string; - isLocal: boolean; - } - >(), - }; - get blobSync() { return this.workspace.blobSync; } @@ -136,48 +115,6 @@ export class DocImpl implements Doc { return (readonly?.toString() as 'true' | 'false') ?? 'false'; } - private _handleYBlockAdd(id: string, isLocal: boolean) { - this.slots.yBlockUpdated.next({ type: 'add', id, isLocal }); - } - - private _handleYBlockDelete(id: string, isLocal: boolean) { - this.slots.yBlockUpdated.next({ type: 'delete', id, isLocal }); - } - - private _handleYEvent(event: Y.YEvent>) { - // event on top-level block store - if (event.target !== this._yBlocks) { - return; - } - const isLocal = - !event.transaction.origin || - !this._yBlocks.doc || - event.transaction.origin instanceof Y.UndoManager || - event.transaction.origin.proxy - ? true - : event.transaction.origin === this._yBlocks.doc.clientID; - event.keys.forEach((value, id) => { - try { - if (value.action === 'add') { - this._handleYBlockAdd(id, isLocal); - return; - } - if (value.action === 'delete') { - this._handleYBlockDelete(id, isLocal); - return; - } - } catch (e) { - console.error('An error occurred while handling Yjs event:'); - console.error(e); - } - }); - } - - private _initYBlocks() { - const { _yBlocks } = this; - _yBlocks.observeDeep(this._handleYEvents); - } - clear() { this._yBlocks.clear(); } @@ -198,7 +135,6 @@ export class DocImpl implements Doc { this._destroy(); if (this.ready) { - this._yBlocks.unobserveDeep(this._handleYEvents); this._yBlocks.clear(); } } @@ -266,12 +202,6 @@ export class DocImpl implements Doc { this.workspace.onLoadDoc?.(this.spaceDoc); this.workspace.onLoadAwareness?.(this.awarenessStore.awareness); - this._initYBlocks(); - - this._yBlocks.forEach((_, id) => { - this._handleYBlockAdd(id, false); - }); - initFn?.(); this._loaded = true;