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:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -95,6 +95,5 @@ export async function initStarterDocCollection(collection: TestWorkspace) {
|
|||||||
if (!doc?.loaded) {
|
if (!doc?.loaded) {
|
||||||
doc?.load();
|
doc?.load();
|
||||||
}
|
}
|
||||||
doc?.resetHistory();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user