Stackedit/src/services/editor/cledit/cleditUndoMgr.js

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;