/
githubmirror
/
gutenberg
Обзор
Документация
Войти
/
githubmirror
/
gutenberg
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
packages/sync/src/undo-manager.ts
184 строки
5 KB
Marco Ciampini
ESLint: Replace strict config with bulk suppressions (#81248)
07 авг 2026, 14:34
Не верифицирован
07 авг 2026, 14:34
7f43eaf
Код
Авторство
О чём код?
import type * as Y from 'yjs'; import type { HistoryRecord } from '@wordpress/undo-manager'; import { LOCAL_EDITOR_ORIGIN } from './config'; import { YMultiDocUndoManager } from './y-utilities/y-multidoc-undomanager'; import type { ObjectData, RecordHandlers, SyncUndoManager, SyncUndoStackState, } from './types'; type UndoMetaHandlers = Pick< RecordHandlers, 'addUndoMeta' | 'onUndoStackChange' | 'restoreUndoMeta' >; interface StackItemEvent { stackItem: { meta: Map< any, any > }; origin: any; type: 'undo' | 'redo'; changedParentTypes: Map< Y.AbstractType< any >, Y.YEvent< any >[] >; ydoc: Y.Doc; } /** * 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 undoMetaHandlers = new Map< Y.Doc, UndoMetaHandlers >(); const yUndoManager = new YMultiDocUndoManager( [], { // Throttle undo/redo captures after 500ms of inactivity. // 500 was selected from subjective local UX testing, shorter timeouts // may cause mid-word undo stack items. captureTimeout: 500, // 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 ] ), } ); const getUndoStackState = (): SyncUndoStackState => ( { hasRedo: yUndoManager.canRedo(), hasUndo: yUndoManager.canUndo(), } ); const notifyUndoStackChange = ( ydoc: Y.Doc ): void => { undoMetaHandlers .get( ydoc ) ?.onUndoStackChange?.( getUndoStackState() ); }; yUndoManager.on( 'stack-item-added', ( event: StackItemEvent ) => { const handlers = undoMetaHandlers.get( event.ydoc ); if ( ! handlers ) { return; } handlers.addUndoMeta( event.ydoc, event.stackItem.meta ); notifyUndoStackChange( event.ydoc ); } ); yUndoManager.on( 'stack-item-updated', ( event: StackItemEvent ) => { notifyUndoStackChange( event.ydoc ); } ); yUndoManager.on( 'stack-item-popped', ( event: StackItemEvent ) => { const handlers = undoMetaHandlers.get( event.ydoc ); if ( ! handlers ) { return; } handlers.restoreUndoMeta( event.ydoc, event.stackItem.meta ); notifyUndoStackChange( event.ydoc ); } ); yUndoManager.on( 'stack-cleared', () => { undoMetaHandlers.forEach( ( handlers ) => { handlers.onUndoStackChange?.( getUndoStackState() ); } ); } ); 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. * @param handlers Handlers for the scoped document. * @param handlers.addUndoMeta Handler to add metadata to undo items. * @param handlers.onUndoStackChange Handler for undo stack changes. * @param handlers.restoreUndoMeta Handler to restore metadata from undo items. */ addToScope( ymap: Y.Map< any >, handlers: UndoMetaHandlers ): void { if ( ymap.doc === null ) { // Necessary for a type check, but this shouldn't happen. return; } const ydoc = ymap.doc; yUndoManager.addToScope( ymap ); if ( ! undoMetaHandlers.has( ydoc ) ) { ydoc.on( 'destroy', () => undoMetaHandlers.delete( ydoc ) ); } undoMetaHandlers.set( ydoc, handlers ); }, /** * 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(); }, /** * Stop capturing changes into the current undo item. * The next change will create a new undo item. */ stopCapturing(): void { yUndoManager.stopCapturing(); }, }; }