refactor(editor): move history from doc to store (#12131)

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

- **New Features**
  - Undo/redo history management is now centralized in the workspace, providing more consistent and robust undo/redo behavior.
  - History update events are emitted at the workspace level, enabling better tracking of changes.

- **Bug Fixes**
  - Improved reliability of undo/redo actions by shifting history management from documents to the workspace.

- **Documentation**
  - Updated and clarified documentation for history-related APIs, including improved examples and clearer descriptions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-05-05 09:24:09 +00:00
parent 61af6fd24e
commit d859c4252b
12 changed files with 145 additions and 337 deletions
@@ -1,4 +1,3 @@
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import * as Y from 'yjs';
@@ -17,10 +16,6 @@ type DocOptions = {
};
export class TestDoc implements Doc {
private readonly _canRedo$ = signal(false);
private readonly _canUndo$ = signal(false);
private readonly _collection: Workspace;
private readonly _storeMap = new Map<string, Store>();
@@ -30,13 +25,6 @@ export class TestDoc implements Doc {
events.forEach(event => this._handleYEvent(event));
};
private _history!: Y.UndoManager;
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.next();
};
private readonly _initSubDoc = () => {
let subDoc = this.rootDoc.getMap('spaces').get(this.id);
if (!subDoc) {
@@ -77,19 +65,6 @@ export class TestDoc implements Doc {
/** Indicate whether the block tree is ready */
private _ready = false;
private _shouldTransact = true;
private readonly _updateCanUndoRedoSignals = () => {
const canRedo = this._history.canRedo();
const canUndo = this._history.canUndo();
if (this._canRedo$.peek() !== canRedo) {
this._canRedo$.value = canRedo;
}
if (this._canUndo$.peek() !== canUndo) {
this._canUndo$.value = canUndo;
}
};
protected readonly _yBlocks: Y.Map<YBlock>;
/**
@@ -105,7 +80,6 @@ export class TestDoc implements Doc {
readonly rootDoc: Y.Doc;
readonly slots = {
historyUpdated: new Subject<void>(),
yBlockUpdated: new Subject<
| {
type: 'add';
@@ -124,30 +98,10 @@ export class TestDoc implements Doc {
return this.workspace.blobSync;
}
get canRedo() {
return this._canRedo$.peek();
}
get canRedo$() {
return this._canRedo$;
}
get canUndo() {
return this._canUndo$.peek();
}
get canUndo$() {
return this._canUndo$;
}
get workspace() {
return this._collection;
}
get history() {
return this._history;
}
get isEmpty() {
return this._yBlocks.size === 0;
}
@@ -227,19 +181,6 @@ export class TestDoc implements Doc {
private _initYBlocks() {
const { _yBlocks } = this;
_yBlocks.observeDeep(this._handleYEvents);
this._history = new Y.UndoManager([_yBlocks], {
trackedOrigins: new Set([this._ySpaceDoc.clientID]),
});
this._history.on('stack-cleared', this._historyObserver);
this._history.on('stack-item-added', this._historyObserver);
this._history.on('stack-item-popped', this._historyObserver);
this._history.on('stack-item-updated', this._historyObserver);
}
/** Capture current operations to undo stack synchronously. */
captureSync() {
this._history.stopCapturing();
}
clear() {
@@ -258,8 +199,6 @@ export class TestDoc implements Doc {
}
dispose() {
this.slots.historyUpdated.complete();
if (this.ready) {
this._yBlocks.unobserveDeep(this._handleYEvents);
this._yBlocks.clear();
@@ -337,45 +276,8 @@ export class TestDoc implements Doc {
return this;
}
redo() {
this._history.redo();
}
undo() {
this._history.undo();
}
remove() {
this._destroy();
this.rootDoc.getMap('spaces').delete(this.id);
}
resetHistory() {
this._history.clear();
}
/**
* If `shouldTransact` is `false`, the transaction will not be push to the history stack.
*/
transact(fn: () => void, shouldTransact: boolean = this._shouldTransact) {
this._ySpaceDoc.transact(
() => {
try {
fn();
} catch (e) {
console.error(
`An error occurred while Y.doc ${this._ySpaceDoc.guid} transacting:`
);
console.error(e);
}
},
shouldTransact ? this.rootDoc.clientID : null
);
}
withoutTransact(callback: () => void) {
this._shouldTransact = false;
callback();
this._shouldTransact = true;
}
}