(function ($) { 'use strict'; $.sceditor.plugins.undo = function () { var base = this; var editor; var charChangedCount = 0; var previousValue; var undoLimit = 50; var redoStates = []; var undoStates = []; var ignoreNextValueChanged = false; /** * Sets the editor to the specified state. * * @param {Object} state * @private */ var applyState = function (state) { ignoreNextValueChanged = true; previousValue = state.value; editor.sourceMode(state.sourceMode); editor.val(state.value, false); editor.focus(); if (state.sourceMode) { editor.sourceEditorCaret(state.caret); } else { editor.getRangeHelper().restoreRange(); } ignoreNextValueChanged = false; }; /** * Caluclates the number of characters that have changed * between two strings. * * @param {String} strA * @param {String} strB * @return {String} * @private */ var simpleDiff = function (strA, strB) { var start, end, aLenDiff, bLenDiff, aLength = strA.length, bLength = strB.length, length = Math.max(aLength, bLength); // Calculate the start for (start = 0; start < length; start++) { if (strA.charAt(start) !== strB.charAt(start)) { break; } } // Calculate the end aLenDiff = aLength < bLength ? bLength - aLength : 0; bLenDiff = bLength < aLength ? aLength - bLength : 0; for (end = length - 1; end >= 0; end--) { if (strA.charAt(end - aLenDiff) !== strB.charAt(end - bLenDiff)) { break; } } return (end - start) + 1; }; base.init = function () { // The this variable will be set to the instance of the editor // calling it, hence why the plugins "this" is saved to the base // variable. editor = this; undoLimit = editor.undoLimit || undoLimit; // addShortcut is the easiest way to add handlers to specific // shortcuts editor.addShortcut('ctrl+z', base.undo); editor.addShortcut('ctrl+shift+z', base.redo); editor.addShortcut('ctrl+y', base.redo); }; base.undo = function () { var state = undoStates.pop(); var rawEditorValue = editor.val(null, false); if (state && !redoStates.length && rawEditorValue === state.value) { state = undoStates.pop(); } if (state) { if (!redoStates.length) { redoStates.push({ 'caret': editor.sourceEditorCaret(), 'sourceMode': editor.sourceMode(), 'value': rawEditorValue }); } redoStates.push(state); applyState(state); } return false; }; base.redo = function () { var state = redoStates.pop(); if (!undoStates.length) { undoStates.push(state); state = redoStates.pop(); } if (state) { undoStates.push(state); applyState(state); } return false; }; base.signalReady = function () { var rawValue = editor.val(null, false); // Store the initial value as the last value previousValue = rawValue; undoStates.push({ 'caret': this.sourceEditorCaret(), 'sourceMode': this.sourceMode(), 'value': rawValue }); }; /** * Handle the valueChanged signal. * * e.rawValue will either be the raw HTML from the WYSIWYG editor with * the rangeHelper range markers inserted, or it will be the raw value * of the source editor (BBCode or HTML depening on plugins). * @return {void} */ base.signalValuechangedEvent = function (e) { var rawValue = e.rawValue; if (undoLimit > 0 && undoStates.length > undoLimit) { undoStates.shift(); } // If the editor hasn't fully loaded yet, // then the previous value won't be set. if (ignoreNextValueChanged || !previousValue || previousValue === rawValue) { return; } // Value has changed so remove all redo states redoStates.length = 0; charChangedCount += simpleDiff(previousValue, rawValue); if (charChangedCount < 20) { return; // ?? } else if (charChangedCount < 50 && !/\s$/g.test(e.rawValue)) { return; } undoStates.push({ 'caret': editor.sourceEditorCaret(), 'sourceMode': editor.sourceMode(), 'value': rawValue }); charChangedCount = 0; previousValue = rawValue; }; }; }(jQuery));