refactor(editor): move block yjs listener to store (#12140)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Streamlined internal event handling for block updates, removing previous notification mechanisms from several components.
- **Chores**
  - Simplified and cleaned up internal logic related to block addition and deletion tracking.

No visible changes to the user interface or end-user features.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-05-06 07:44:57 +00:00
parent 83f7093144
commit 88ceeba5b6
4 changed files with 67 additions and 155 deletions

View File

@@ -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;

View File

@@ -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<void>;
/** @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<YBlock | Y.Text | Y.Array<unknown>>) {
// 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<YBlock | Y.Text>[]) => {
events.forEach(event => this._handleYEvent(event));
};
}

View File

@@ -20,11 +20,6 @@ export class TestDoc implements Doc {
private readonly _storeMap = new Map<string, Store>();
// doc/space container.
private readonly _handleYEvents = (events: Y.YEvent<YBlock | Y.Text>[]) => {
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<YBlock | Y.Text | Y.Array<unknown>>) {
// 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;