UNPKG

@wordpress/sync

Version:
111 lines (97 loc) 3.01 kB
/** * External dependencies */ import type * as Y from 'yjs'; /** * WordPress dependencies */ import type { HistoryRecord } from '@wordpress/undo-manager'; /** * Internal dependencies */ import { LOCAL_EDITOR_ORIGIN } from './config'; import { YMultiDocUndoManager } from './y-utilities/y-multidoc-undomanager'; import type { ObjectData, SyncUndoManager } from './types'; /** * Implementation of the WordPress UndoManager interface using YMultiDocUndoManager * internally. This allows undo/redo operations to be transacted against multiple * CRDT documents (one per entity) and giving each peer their own undo/redo stack * without conflicts. */ export function createUndoManager(): SyncUndoManager { const yUndoManager = new YMultiDocUndoManager( [], { // Throttle undo/redo captures. (default: 500ms) captureTimeout: 200, // Ensure that we only scope the undo/redo to the current editor. // The yjs document's clientID is added once it's available. trackedOrigins: new Set( [ LOCAL_EDITOR_ORIGIN ] ), } ); return { /** * Record changes into the history. * Since Yjs automatically tracks changes, this method translates the WordPress * HistoryRecord format into Yjs operations. * * @param _record A record of changes to record. * @param _isStaged Whether to immediately create an undo point or not. */ addRecord( _record?: HistoryRecord< ObjectData >, _isStaged = false // eslint-disable-line @typescript-eslint/no-unused-vars ): void { // This is a no-op for Yjs since it automatically tracks changes. // If needed, we could implement custom logic to handle specific records. }, /** * Add a Yjs map to the scope of the undo manager. * * @param {Y.Map< any >} ymap The Yjs map to add to the scope. */ addToScope( ymap: Y.Map< any > ): void { yUndoManager.addToScope( ymap ); }, /** * Undo the last recorded changes. * */ undo(): HistoryRecord< ObjectData > | undefined { if ( ! yUndoManager.canUndo() ) { return; } // Perform the undo operation yUndoManager.undo(); // Intentionally return an empty array, because the SyncProvider will update // the entity record based on the Yjs document changes. return []; }, /** * Redo the last undone changes. */ redo(): HistoryRecord< ObjectData > | undefined { if ( ! yUndoManager.canRedo() ) { return; } // Perform the redo operation yUndoManager.redo(); // Intentionally return an empty array, because the SyncProvider will update // the entity record based on the Yjs document changes. return []; }, /** * Check if there are changes that can be undone. * * @return {boolean} Whether there are changes to undo. */ hasUndo(): boolean { return yUndoManager.canUndo(); }, /** * Check if there are changes that can be redone. * * @return {boolean} Whether there are changes to redo. */ hasRedo(): boolean { return yUndoManager.canRedo(); }, }; }