177 lines
4.5 KiB
JavaScript
177 lines
4.5 KiB
JavaScript
import DiffMatchPatch from 'diff-match-patch';
|
|
import cledit from './cleditCore';
|
|
|
|
function UndoMgr(editor) {
|
|
cledit.Utils.createEventHooks(this);
|
|
|
|
/* eslint-disable new-cap */
|
|
const diffMatchPatch = new DiffMatchPatch();
|
|
/* eslint-enable new-cap */
|
|
|
|
const self = this;
|
|
let selectionMgr;
|
|
const undoStack = [];
|
|
const redoStack = [];
|
|
let currentState;
|
|
let previousPatches = [];
|
|
let currentPatches = [];
|
|
const { debounce } = cledit.Utils;
|
|
|
|
this.options = {
|
|
undoStackMaxSize: 200,
|
|
bufferStateUntilIdle: 1000,
|
|
patchHandler: {
|
|
makePatches(oldContent, newContent, diffs) {
|
|
return diffMatchPatch.patch_make(oldContent, diffs);
|
|
},
|
|
applyPatches(patches, content) {
|
|
return diffMatchPatch.patch_apply(patches, content)[0];
|
|
},
|
|
reversePatches(patches) {
|
|
const reversedPatches = diffMatchPatch.patch_deepCopy(patches).reverse();
|
|
reversedPatches.cl_each((patch) => {
|
|
patch.diffs.cl_each((diff) => {
|
|
diff[0] = -diff[0];
|
|
});
|
|
});
|
|
return reversedPatches;
|
|
},
|
|
},
|
|
};
|
|
|
|
let stateMgr;
|
|
function StateMgr() {
|
|
let currentTime;
|
|
let lastTime;
|
|
let lastMode;
|
|
|
|
this.isBufferState = () => {
|
|
currentTime = Date.now();
|
|
return this.currentMode !== 'single' &&
|
|
this.currentMode === lastMode &&
|
|
currentTime - lastTime < self.options.bufferStateUntilIdle;
|
|
};
|
|
|
|
this.setDefaultMode = (mode) => {
|
|
this.currentMode = this.currentMode || mode;
|
|
};
|
|
|
|
this.resetMode = () => {
|
|
stateMgr.currentMode = undefined;
|
|
lastMode = undefined;
|
|
};
|
|
|
|
this.saveMode = () => {
|
|
lastMode = this.currentMode;
|
|
this.currentMode = undefined;
|
|
lastTime = currentTime;
|
|
};
|
|
}
|
|
|
|
class State {
|
|
addToUndoStack() {
|
|
undoStack.push(this);
|
|
this.patches = previousPatches;
|
|
previousPatches = [];
|
|
}
|
|
addToRedoStack() {
|
|
redoStack.push(this);
|
|
this.patches = previousPatches;
|
|
previousPatches = [];
|
|
}
|
|
}
|
|
|
|
stateMgr = new StateMgr();
|
|
this.setCurrentMode = (mode) => {
|
|
stateMgr.currentMode = mode;
|
|
};
|
|
this.setDefaultMode = stateMgr.setDefaultMode.cl_bind(stateMgr);
|
|
|
|
this.addDiffs = (oldContent, newContent, diffs) => {
|
|
const patches = this.options.patchHandler.makePatches(oldContent, newContent, diffs);
|
|
patches.cl_each(patch => currentPatches.push(patch));
|
|
};
|
|
|
|
function saveCurrentPatches() {
|
|
// Move currentPatches into previousPatches
|
|
Array.prototype.push.apply(previousPatches, currentPatches);
|
|
currentPatches = [];
|
|
}
|
|
|
|
this.saveState = debounce(() => {
|
|
redoStack.length = 0;
|
|
if (!stateMgr.isBufferState()) {
|
|
currentState.addToUndoStack();
|
|
|
|
// Limit the size of the stack
|
|
while (undoStack.length > this.options.undoStackMaxSize) {
|
|
undoStack.shift();
|
|
}
|
|
}
|
|
saveCurrentPatches();
|
|
currentState = new State();
|
|
stateMgr.saveMode();
|
|
this.$trigger('undoStateChange');
|
|
});
|
|
|
|
this.canUndo = () => !!undoStack.length;
|
|
this.canRedo = () => !!redoStack.length;
|
|
|
|
const restoreState = (patchesParam, isForward) => {
|
|
let patches = patchesParam;
|
|
// Update editor
|
|
const content = editor.getContent();
|
|
if (!isForward) {
|
|
patches = this.options.patchHandler.reversePatches(patches);
|
|
}
|
|
|
|
const newContent = this.options.patchHandler.applyPatches(patches, content);
|
|
const newContentText = newContent.text || newContent;
|
|
const range = editor.setContent(newContentText, true);
|
|
const selection = newContent.selection || {
|
|
start: range.end,
|
|
end: range.end,
|
|
};
|
|
|
|
selectionMgr.setSelectionStartEnd(selection.start, selection.end);
|
|
selectionMgr.updateCursorCoordinates(true);
|
|
|
|
stateMgr.resetMode();
|
|
this.$trigger('undoStateChange');
|
|
editor.adjustCursorPosition();
|
|
};
|
|
|
|
this.undo = () => {
|
|
const state = undoStack.pop();
|
|
if (!state) {
|
|
return;
|
|
}
|
|
saveCurrentPatches();
|
|
currentState.addToRedoStack();
|
|
restoreState(currentState.patches);
|
|
previousPatches = state.patches;
|
|
currentState = state;
|
|
};
|
|
|
|
this.redo = () => {
|
|
const state = redoStack.pop();
|
|
if (!state) {
|
|
return;
|
|
}
|
|
currentState.addToUndoStack();
|
|
restoreState(state.patches, true);
|
|
previousPatches = state.patches;
|
|
currentState = state;
|
|
};
|
|
|
|
this.init = (options) => {
|
|
this.options.cl_extend(options || {});
|
|
({ selectionMgr } = editor);
|
|
if (!currentState) {
|
|
currentState = new State();
|
|
}
|
|
};
|
|
}
|
|
|
|
cledit.UndoMgr = UndoMgr;
|