mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 18:16:15 +08:00
feat(editor): replace slot with rxjs subject (#10768)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user