feat(editor): replace slot with rxjs subject (#10768)

This commit is contained in:
Mirone
2025-03-12 11:29:24 +09:00
committed by GitHub
parent 19f978d9aa
commit cd63e0ed8b
302 changed files with 1405 additions and 1251 deletions
@@ -1,5 +1,7 @@
import { type Disposable, Slot } from '@blocksuite/global/slot';
import type { Disposable } from '@blocksuite/global/disposable';
import { computed, type Signal, signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import type { Text } from '../../reactive/index.js';
import type { Store } from '../store/store.js';
@@ -57,9 +59,9 @@ export class BlockModel<
}, new Map<string, number>())
);
created = new Slot();
created = new Subject<void>();
deleted = new Slot();
deleted = new Subject<void>();
id!: string;
@@ -76,7 +78,7 @@ export class BlockModel<
pop!: (prop: keyof Props & string) => void;
propsUpdated = new Slot<{ key: string }>();
propsUpdated = new Subject<{ key: string }>();
stash!: (prop: keyof Props & string) => void;
@@ -124,26 +126,30 @@ export class BlockModel<
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();
});
this._onCreated = {
dispose: this.created.pipe(take(1)).subscribe(() => {
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();
}
});
}).unsubscribe,
};
this._onDeleted = {
dispose: this.deleted.pipe(take(1)).subscribe(() => {
this._onCreated.dispose();
}).unsubscribe,
};
}
dispose() {
this.created.dispose();
this.deleted.dispose();
this.propsUpdated.dispose();
this.created.complete();
this.deleted.complete();
this.propsUpdated.complete();
}
firstChild(): BlockModel | null {
@@ -142,7 +142,10 @@ export class SyncController {
this.model[key] = value;
});
});
model.deleted.once(dispose);
const subscription = model.deleted.subscribe(() => {
subscription.unsubscribe();
dispose();
});
return {
...acc,
[`${key}$`]: data,
+3 -3
View File
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
import type * as Y from 'yjs';
import type { AwarenessStore } from '../yjs/awareness.js';
@@ -24,8 +24,8 @@ export interface Doc {
dispose(): void;
slots: {
historyUpdated: Slot;
yBlockUpdated: Slot<
historyUpdated: Subject<void>;
yBlockUpdated: Subject<
| {
type: 'add';
id: string;
@@ -1,7 +1,8 @@
import { Container, type ServiceProvider } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { computed, signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import type { ExtensionType } from '../../extension/extension.js';
import {
@@ -67,15 +68,15 @@ export class Store {
readonly slots: Doc['slots'] & {
/** This is always triggered after `doc.load` is called. */
ready: Slot;
ready: Subject<void>;
/**
* 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.
*/
rootAdded: Slot<string>;
rootDeleted: Slot<string>;
blockUpdated: Slot<
rootAdded: Subject<string>;
rootDeleted: Subject<string>;
blockUpdated: Subject<
| {
type: 'add';
id: string;
@@ -313,13 +314,15 @@ export class Store {
return this._doc.withoutTransact.bind(this._doc);
}
private _isDisposed = false;
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
this._doc = doc;
this.slots = {
ready: new Slot(),
rootAdded: new Slot(),
rootDeleted: new Slot(),
blockUpdated: new Slot(),
ready: new Subject(),
rootAdded: new Subject(),
rootDeleted: new Subject(),
blockUpdated: new Subject(),
historyUpdated: this._doc.slots.historyUpdated,
yBlockUpdated: this._doc.slots.yBlockUpdated,
};
@@ -362,18 +365,31 @@ export class Store {
private readonly _subscribeToSlots = () => {
this.disposableGroup.add(
this._doc.slots.yBlockUpdated.on(({ type, id }) => {
switch (type) {
case 'add': {
this._onBlockAdded(id);
return;
}
case 'delete': {
this._onBlockRemoved(id);
return;
this._doc.slots.yBlockUpdated.subscribe(
({ type, id }: { type: string; id: string }) => {
switch (type) {
case 'add': {
this._onBlockAdded(id);
return;
}
case 'delete': {
this._onBlockRemoved(id);
return;
}
case 'update': {
const block = this.getBlock(id);
if (!block) return;
this.slots.blockUpdated.next({
type: 'update',
id,
flavour: block.flavour,
props: { key: 'content' },
});
return;
}
}
}
})
)
);
this.disposableGroup.add(this.slots.ready);
this.disposableGroup.add(this.slots.blockUpdated);
@@ -412,10 +428,10 @@ export class Store {
const options: BlockOptions = {
onChange: (block, key) => {
if (key) {
block.model.propsUpdated.emit({ key });
block.model.propsUpdated.next({ key });
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'update',
id,
flavour: block.flavour,
@@ -431,13 +447,13 @@ export class Store {
...this._blocks.value,
[id]: block,
};
block.model.created.emit();
block.model.created.next();
if (block.model.role === 'root') {
this.slots.rootAdded.emit(id);
this.slots.rootAdded.next(id);
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'add',
id,
init,
@@ -456,13 +472,13 @@ export class Store {
if (!block) return;
if (block.model.role === 'root') {
this.slots.rootDeleted.emit(id);
this.slots.rootDeleted.next(id);
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'delete',
id,
flavour: block.model.flavour,
flavour: block.flavour,
parent: this.getParent(block.model)?.id ?? '',
model: block.model,
});
@@ -470,7 +486,7 @@ export class Store {
const { [id]: _, ...blocks } = this._blocks.peek();
this._blocks.value = blocks;
block.model.deleted.emit();
block.model.deleted.next();
block.model.dispose();
} catch (e) {
console.error('An error occurred while removing block:');
@@ -604,8 +620,12 @@ export class Store {
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;
}
getBlock(id: string): Block | undefined {
@@ -705,16 +725,18 @@ export class Store {
}
load(initFn?: () => void) {
if (this.disposableGroup.disposed) {
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.emit();
this.slots.ready.next();
this.slots.rootAdded.next(this.root?.id ?? '');
return this;
}
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
export type Tag = {
id: string;
@@ -39,8 +39,8 @@ export interface WorkspaceMeta {
get docs(): unknown[] | undefined;
initialize(): void;
commonFieldsUpdated: Slot;
docMetaAdded: Slot<string>;
docMetaRemoved: Slot<string>;
docMetaUpdated: Slot;
commonFieldsUpdated: Subject<void>;
docMetaAdded: Subject<string>;
docMetaRemoved: Subject<string>;
docMetaUpdated: Subject<void>;
}
@@ -1,5 +1,5 @@
import type { Slot } from '@blocksuite/global/slot';
import type { BlobEngine } from '@blocksuite/sync';
import type { Subject } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js';
import type * as Y from 'yjs';
@@ -20,9 +20,9 @@ export interface Workspace {
get docs(): Map<string, Doc>;
slots: {
docListUpdated: Slot;
docCreated: Slot<string>;
docRemoved: Slot<string>;
docListUpdated: Subject<void>;
docCreated: Subject<string>;
docRemoved: Subject<string>;
};
createDoc(options?: CreateBlocksOptions): Store;