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

View File

@@ -310,7 +310,7 @@ const builtinSurfaceToolbarConfig = {
note.props.displayMode !== NoteDisplayMode.EdgelessOnly note.props.displayMode !== NoteDisplayMode.EdgelessOnly
); );
ctx.doc.captureSync(); ctx.store.captureSync();
ctx.chain ctx.chain
.pipe(duplicateSelectedModelsCommand, { .pipe(duplicateSelectedModelsCommand, {
selectedModels: [model], selectedModels: [model],
@@ -350,7 +350,7 @@ const builtinSurfaceToolbarConfig = {
} }
if (contentModels.length === 0) return; if (contentModels.length === 0) return;
ctx.doc.captureSync(); ctx.store.captureSync();
ctx.chain ctx.chain
.pipe(draftSelectedModelsCommand, { .pipe(draftSelectedModelsCommand, {
selectedModels: contentModels, selectedModels: contentModels,

View File

@@ -32,7 +32,7 @@ export function createDefaultDoc(
store.addBlock('affine:paragraph', {}, noteId); store.addBlock('affine:paragraph', {}, noteId);
// To make sure the content of new doc would not be clear // To make sure the content of new doc would not be clear
// By undo operation for the first time // By undo operation for the first time
doc.resetHistory(); store.resetHistory();
return store; return store;
} }

View File

@@ -721,36 +721,6 @@ Check if the store can undo
*** ***
### captureSync
#### Get Signature
> **get** **captureSync**(): () => `void`
Force the following history to be captured into a new stack.
##### Example
```ts
op1();
op2();
store.captureSync();
op3();
store.undo(); // undo op3
store.undo(); // undo op1, op2
```
##### Returns
> (): `void`
###### Returns
`void`
***
### history ### history
#### Get Signature #### Get Signature
@@ -765,51 +735,75 @@ Get the Y.UndoManager instance for current store.
*** ***
### redo ### captureSync()
#### Get Signature > **captureSync**(): `void`
> **get** **redo**(): () => `void` Force the following history to be captured into a new stack.
#### Returns
`void`
#### Example
```ts
op1();
op2();
store.captureSync();
op3();
store.undo(); // undo op3
store.undo(); // undo op1, op2
```
***
### redo()
> **redo**(): `void`
Redo the last undone transaction. Redo the last undone transaction.
##### Returns #### Returns
> (): `void`
###### Returns
`void` `void`
*** ***
### resetHistory ### resetHistory()
#### Get Signature > **resetHistory**(): `void`
> **get** **resetHistory**(): () => `void`
Reset the history of the store. Reset the history of the store.
##### Returns #### Returns
> (): `void`
###### Returns
`void` `void`
*** ***
### transact ### transact()
#### Get Signature > **transact**(`fn`, `shouldTransact`): `void`
> **get** **transact**(): (`fn`, `shouldTransact?`) => `void`
Execute a transaction. Execute a transaction.
##### Example #### Parameters
##### fn
() => `void`
##### shouldTransact
`boolean` = `...`
#### Returns
`void`
#### Example
```ts ```ts
store.transact(() => { store.transact(() => {
@@ -818,53 +812,37 @@ store.transact(() => {
}); });
``` ```
##### Returns
> (`fn`, `shouldTransact?`): `void`
###### Parameters
###### fn
() => `void`
###### shouldTransact?
`boolean`
###### Returns
`void`
*** ***
### undo ### undo()
#### Get Signature > **undo**(): `void`
> **get** **undo**(): () => `void`
Undo the last transaction. Undo the last transaction.
##### Returns #### Returns
> (): `void`
###### Returns
`void` `void`
*** ***
### withoutTransact ### withoutTransact()
#### Get Signature > **withoutTransact**(`fn`): `void`
> **get** **withoutTransact**(): (`fn`) => `void`
Execute a transaction without capturing the history. Execute a transaction without capturing the history.
##### Example #### Parameters
##### fn
() => `void`
#### Returns
`void`
#### Example
```ts ```ts
store.withoutTransact(() => { store.withoutTransact(() => {
@@ -873,20 +851,6 @@ store.withoutTransact(() => {
}); });
``` ```
##### Returns
> (`fn`): `void`
###### Parameters
###### fn
() => `void`
###### Returns
`void`
## Store Lifecycle ## Store Lifecycle
### disposableGroup ### disposableGroup

View File

@@ -36,7 +36,7 @@ The payload can have three types:
> **historyUpdated**: `Subject`\<`void`\> > **historyUpdated**: `Subject`\<`void`\>
This fires when the doc history is updated. This fires when the history is updated.
*** ***

View File

@@ -9,10 +9,6 @@ import type { Workspace } from './workspace.js';
import type { DocMeta } from './workspace-meta.js'; import type { DocMeta } from './workspace-meta.js';
export type GetBlocksOptions = Omit<StoreOptions, 'schema' | 'doc'>; export type GetBlocksOptions = Omit<StoreOptions, 'schema' | 'doc'>;
export type CreateBlocksOptions = GetBlocksOptions & {
id?: string;
};
export type YBlocks = Y.Map<YBlock>;
export interface Doc { export interface Doc {
readonly id: string; readonly id: string;
@@ -24,10 +20,6 @@ export interface Doc {
dispose(): void; dispose(): void;
slots: { slots: {
/**
* This fires when the doc history is updated.
*/
historyUpdated: Subject<void>;
/** /**
* @internal * @internal
* This fires when the doc yBlock is updated. * This fires when the doc yBlock is updated.
@@ -39,16 +31,6 @@ export interface Doc {
}>; }>;
}; };
get history(): Y.UndoManager;
get canRedo(): boolean;
get canUndo(): boolean;
undo(): void;
redo(): void;
resetHistory(): void;
transact(fn: () => void, shouldTransact?: boolean): void;
withoutTransact(fn: () => void): void;
captureSync(): void;
clear(): void; clear(): void;
getStore(options?: GetBlocksOptions): Store; getStore(options?: GetBlocksOptions): Store;
clearQuery(query: Query, readonly?: boolean): void; clearQuery(query: Query, readonly?: boolean): void;

View File

@@ -3,6 +3,7 @@ import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { computed, signal } from '@preact/signals-core'; import { computed, signal } from '@preact/signals-core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import * as Y from 'yjs';
import type { ExtensionType } from '../../extension/extension.js'; import type { ExtensionType } from '../../extension/extension.js';
import { import {
@@ -160,6 +161,10 @@ export type StoreSlots = Doc['slots'] & {
* *
*/ */
blockUpdated: Subject<StoreBlockUpdatedPayloads>; blockUpdated: Subject<StoreBlockUpdatedPayloads>;
/**
* This fires when the history is updated.
*/
historyUpdated: Subject<void>;
}; };
const internalExtensions = [StoreSelectionExtension]; const internalExtensions = [StoreSelectionExtension];
@@ -186,6 +191,8 @@ export class Store {
private readonly _provider: ServiceProvider; private readonly _provider: ServiceProvider;
private _shouldTransact = true;
private readonly _runQuery = (block: Block) => { private readonly _runQuery = (block: Block) => {
runQuery(this._query, block); runQuery(this._query, block);
}; };
@@ -209,6 +216,10 @@ export class Store {
private readonly _schema: Schema; private readonly _schema: Schema;
private readonly _canRedo = signal(false);
private readonly _canUndo = signal(false);
/** /**
* Get the id of the store. * Get the id of the store.
* *
@@ -284,7 +295,7 @@ export class Store {
if (this.readonly) { if (this.readonly) {
return false; return false;
} }
return this._doc.canRedo; return this._canRedo.peek();
} }
/** /**
@@ -296,7 +307,7 @@ export class Store {
if (this.readonly) { if (this.readonly) {
return false; return false;
} }
return this._doc.canUndo; return this._canUndo.peek();
} }
/** /**
@@ -304,13 +315,12 @@ export class Store {
* *
* @category History * @category History
*/ */
get undo() { undo() {
if (this.readonly) { if (this.readonly) {
return () => { console.error('cannot undo in readonly mode');
console.error('cannot undo in readonly mode'); return;
};
} }
return this._doc.undo.bind(this._doc); this._history.undo();
} }
/** /**
@@ -318,13 +328,12 @@ export class Store {
* *
* @category History * @category History
*/ */
get redo() { redo() {
if (this.readonly) { if (this.readonly) {
return () => { console.error('cannot undo in readonly mode');
console.error('cannot undo in readonly mode'); return;
};
} }
return this._doc.redo.bind(this._doc); this._history.redo();
} }
/** /**
@@ -332,8 +341,8 @@ export class Store {
* *
* @category History * @category History
*/ */
get resetHistory() { resetHistory() {
return this._doc.resetHistory.bind(this._doc); return this._history.clear();
} }
/** /**
@@ -349,8 +358,21 @@ export class Store {
* *
* @category History * @category History
*/ */
get transact() { transact(fn: () => void, shouldTransact: boolean = this._shouldTransact) {
return this._doc.transact.bind(this._doc); const spaceDoc = this.doc.spaceDoc;
spaceDoc.transact(
() => {
try {
fn();
} catch (e) {
console.error(
`An error occurred while Y.doc ${spaceDoc.guid} transacting:`
);
console.error(e);
}
},
shouldTransact ? this.rootDoc.clientID : null
);
} }
/** /**
@@ -366,8 +388,10 @@ export class Store {
* *
* @category History * @category History
*/ */
get withoutTransact() { withoutTransact(fn: () => void) {
return this._doc.withoutTransact.bind(this._doc); this._shouldTransact = false;
fn();
this._shouldTransact = true;
} }
/** /**
@@ -386,8 +410,8 @@ export class Store {
* *
* @category History * @category History
*/ */
get captureSync() { captureSync() {
return this._doc.captureSync.bind(this._doc); this._history.stopCapturing();
} }
/** /**
@@ -403,7 +427,7 @@ export class Store {
* @category History * @category History
*/ */
get history() { get history() {
return this._doc.history; return this._history;
} }
/** /**
@@ -516,6 +540,8 @@ export class Store {
private _isDisposed = false; private _isDisposed = false;
private readonly _history!: Y.UndoManager;
/** /**
* @internal * @internal
* In most cases, you don't need to use the constructor directly. * In most cases, you don't need to use the constructor directly.
@@ -528,7 +554,7 @@ export class Store {
rootAdded: new Subject(), rootAdded: new Subject(),
rootDeleted: new Subject(), rootDeleted: new Subject(),
blockUpdated: new Subject(), blockUpdated: new Subject(),
historyUpdated: this._doc.slots.historyUpdated, historyUpdated: new Subject(),
yBlockUpdated: this._doc.slots.yBlockUpdated, yBlockUpdated: this._doc.slots.yBlockUpdated,
}; };
this._schema = new Schema(); this._schema = new Schema();
@@ -565,9 +591,35 @@ export class Store {
this._onBlockAdded(id, false, true); this._onBlockAdded(id, false, true);
}); });
this._history = new Y.UndoManager([this._yBlocks], {
trackedOrigins: new Set([this.doc.spaceDoc.clientID]),
});
this._updateCanUndoRedoSignals();
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);
this._subscribeToSlots(); this._subscribeToSlots();
} }
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;
}
};
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.next();
};
private readonly _subscribeToSlots = () => { private readonly _subscribeToSlots = () => {
this.disposableGroup.add( this.disposableGroup.add(
this._doc.slots.yBlockUpdated.subscribe(({ type, id, isLocal }) => { this._doc.slots.yBlockUpdated.subscribe(({ type, id, isLocal }) => {
@@ -1204,6 +1256,7 @@ export class Store {
this.slots.rootAdded.complete(); this.slots.rootAdded.complete();
this.slots.rootDeleted.complete(); this.slots.rootDeleted.complete();
this.slots.blockUpdated.complete(); this.slots.blockUpdated.complete();
this.slots.historyUpdated.complete();
this.disposableGroup.dispose(); this.disposableGroup.dispose();
this._isDisposed = true; this._isDisposed = true;
} }

View File

@@ -1,4 +1,3 @@
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import * as Y from 'yjs'; import * as Y from 'yjs';
@@ -17,10 +16,6 @@ type DocOptions = {
}; };
export class TestDoc implements Doc { export class TestDoc implements Doc {
private readonly _canRedo$ = signal(false);
private readonly _canUndo$ = signal(false);
private readonly _collection: Workspace; private readonly _collection: Workspace;
private readonly _storeMap = new Map<string, Store>(); private readonly _storeMap = new Map<string, Store>();
@@ -30,13 +25,6 @@ export class TestDoc implements Doc {
events.forEach(event => this._handleYEvent(event)); events.forEach(event => this._handleYEvent(event));
}; };
private _history!: Y.UndoManager;
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.next();
};
private readonly _initSubDoc = () => { private readonly _initSubDoc = () => {
let subDoc = this.rootDoc.getMap('spaces').get(this.id); let subDoc = this.rootDoc.getMap('spaces').get(this.id);
if (!subDoc) { if (!subDoc) {
@@ -77,19 +65,6 @@ export class TestDoc implements Doc {
/** Indicate whether the block tree is ready */ /** Indicate whether the block tree is ready */
private _ready = false; 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>; protected readonly _yBlocks: Y.Map<YBlock>;
/** /**
@@ -105,7 +80,6 @@ export class TestDoc implements Doc {
readonly rootDoc: Y.Doc; readonly rootDoc: Y.Doc;
readonly slots = { readonly slots = {
historyUpdated: new Subject<void>(),
yBlockUpdated: new Subject< yBlockUpdated: new Subject<
| { | {
type: 'add'; type: 'add';
@@ -124,30 +98,10 @@ export class TestDoc implements Doc {
return this.workspace.blobSync; 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() { get workspace() {
return this._collection; return this._collection;
} }
get history() {
return this._history;
}
get isEmpty() { get isEmpty() {
return this._yBlocks.size === 0; return this._yBlocks.size === 0;
} }
@@ -227,19 +181,6 @@ export class TestDoc implements Doc {
private _initYBlocks() { private _initYBlocks() {
const { _yBlocks } = this; const { _yBlocks } = this;
_yBlocks.observeDeep(this._handleYEvents); _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() { clear() {
@@ -258,8 +199,6 @@ export class TestDoc implements Doc {
} }
dispose() { dispose() {
this.slots.historyUpdated.complete();
if (this.ready) { if (this.ready) {
this._yBlocks.unobserveDeep(this._handleYEvents); this._yBlocks.unobserveDeep(this._handleYEvents);
this._yBlocks.clear(); this._yBlocks.clear();
@@ -337,45 +276,8 @@ export class TestDoc implements Doc {
return this; return this;
} }
redo() {
this._history.redo();
}
undo() {
this._history.undo();
}
remove() { remove() {
this._destroy(); this._destroy();
this.rootDoc.getMap('spaces').delete(this.id); 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;
}
} }

View File

@@ -112,7 +112,7 @@ export const database: InitFn = (collection: Workspace, id: string) => {
store.addBlock('affine:paragraph', {}, noteId); store.addBlock('affine:paragraph', {}, noteId);
datasource.viewManager.viewAdd(viewPresets.kanbanViewMeta.type); datasource.viewManager.viewAdd(viewPresets.kanbanViewMeta.type);
doc.resetHistory(); store.resetHistory();
}; };
// Add database block inside note block // Add database block inside note block
addDatabase('Database 1', false); addDatabase('Database 1', false);

View File

@@ -47,7 +47,7 @@ export const embed: InitFn = (collection: Workspace, id: string) => {
store.addBlock('affine:paragraph', {}, noteId); store.addBlock('affine:paragraph', {}, noteId);
}); });
doc.resetHistory(); store.resetHistory();
}; };
embed.id = 'embed'; embed.id = 'embed';

View File

@@ -21,7 +21,7 @@ export const empty: InitFn = (collection: Workspace, id: string) => {
store.addBlock('affine:paragraph', {}, noteId); store.addBlock('affine:paragraph', {}, noteId);
}); });
doc.resetHistory(); store.resetHistory();
}; };
empty.id = 'empty'; empty.id = 'empty';

View File

@@ -95,6 +95,5 @@ export async function initStarterDocCollection(collection: TestWorkspace) {
if (!doc?.loaded) { if (!doc?.loaded) {
doc?.load(); doc?.load();
} }
doc?.resetHistory();
} }
} }

View File

@@ -9,7 +9,6 @@ import {
type Workspace, type Workspace,
type YBlock, type YBlock,
} from '@blocksuite/affine/store'; } from '@blocksuite/affine/store';
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { Awareness } from 'y-protocols/awareness.js'; import { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs'; import * as Y from 'yjs';
@@ -21,10 +20,6 @@ type DocOptions = {
}; };
export class DocImpl implements Doc { export class DocImpl implements Doc {
private readonly _canRedo = signal(false);
private readonly _canUndo = signal(false);
private readonly _collection: Workspace; private readonly _collection: Workspace;
private readonly _storeMap = new Map<string, Store>(); private readonly _storeMap = new Map<string, Store>();
@@ -34,13 +29,6 @@ export class DocImpl implements Doc {
events.forEach(event => this._handleYEvent(event)); events.forEach(event => this._handleYEvent(event));
}; };
private _history!: Y.UndoManager;
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.next();
};
private readonly _initSubDoc = () => { private readonly _initSubDoc = () => {
{ {
// This is a piece of old version compatible code. The old version relies on the subdoc instance on `spaces`. // This is a piece of old version compatible code. The old version relies on the subdoc instance on `spaces`.
@@ -55,9 +43,7 @@ export class DocImpl implements Doc {
} }
} }
const spaceDoc = new Y.Doc({ const spaceDoc = new Y.Doc({ guid: this.id });
guid: this.id,
});
spaceDoc.clientID = this.rootDoc.clientID; spaceDoc.clientID = this.rootDoc.clientID;
this._loaded = false; this._loaded = false;
@@ -72,19 +58,6 @@ export class DocImpl implements Doc {
/** Indicate whether the block tree is ready */ /** Indicate whether the block tree is ready */
private _ready = false; 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>; protected readonly _yBlocks: Y.Map<YBlock>;
/** /**
@@ -102,8 +75,6 @@ export class DocImpl implements Doc {
readonly rootDoc: Y.Doc; readonly rootDoc: Y.Doc;
readonly slots = { readonly slots = {
// eslint-disable-next-line rxjs/finnish
historyUpdated: new Subject<void>(),
// eslint-disable-next-line rxjs/finnish // eslint-disable-next-line rxjs/finnish
yBlockUpdated: new Subject< yBlockUpdated: new Subject<
| { | {
@@ -123,22 +94,10 @@ export class DocImpl implements Doc {
return this.workspace.blobSync; return this.workspace.blobSync;
} }
get canRedo() {
return this._canRedo.peek();
}
get canUndo() {
return this._canUndo.peek();
}
get workspace() { get workspace() {
return this._collection; return this._collection;
} }
get history() {
return this._history;
}
get isEmpty() { get isEmpty() {
return this._yBlocks.size === 0; return this._yBlocks.size === 0;
} }
@@ -217,19 +176,6 @@ export class DocImpl implements Doc {
private _initYBlocks() { private _initYBlocks() {
const { _yBlocks } = this; const { _yBlocks } = this;
_yBlocks.observeDeep(this._handleYEvents); _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() { clear() {
@@ -250,7 +196,6 @@ export class DocImpl implements Doc {
dispose() { dispose() {
this._destroy(); this._destroy();
this.slots.historyUpdated.unsubscribe();
if (this.ready) { if (this.ready) {
this._yBlocks.unobserveDeep(this._handleYEvents); this._yBlocks.unobserveDeep(this._handleYEvents);
@@ -335,45 +280,8 @@ export class DocImpl implements Doc {
return this; return this;
} }
redo() {
this._history.redo();
}
undo() {
this._history.undo();
}
remove() { remove() {
this._destroy(); this._destroy();
this.rootDoc.getMap('spaces').delete(this.id); 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;
}
} }