aboutsummaryrefslogtreecommitdiff
path: root/contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js
diff options
context:
space:
mode:
Diffstat (limited to 'contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js')
-rw-r--r--contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js1014
1 files changed, 0 insertions, 1014 deletions
diff --git a/contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js b/contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js
deleted file mode 100644
index 9e57022..0000000
--- a/contexts/data/lib/closure-library/closure/goog/editor/plugins/undoredo.js
+++ /dev/null
@@ -1,1014 +0,0 @@
-// Copyright 2005 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-/**
- * @fileoverview Code for handling edit history (undo/redo).
- *
- */
-
-
-goog.provide('goog.editor.plugins.UndoRedo');
-
-goog.require('goog.debug.Logger');
-goog.require('goog.dom');
-goog.require('goog.dom.NodeOffset');
-goog.require('goog.dom.Range');
-goog.require('goog.editor.BrowserFeature');
-goog.require('goog.editor.Command');
-goog.require('goog.editor.Field.EventType');
-goog.require('goog.editor.Plugin');
-goog.require('goog.editor.plugins.UndoRedoManager');
-goog.require('goog.editor.plugins.UndoRedoState');
-goog.require('goog.events');
-goog.require('goog.events.EventHandler');
-
-
-
-/**
- * Encapsulates undo/redo logic using a custom undo stack (i.e. not browser
- * built-in). Browser built-in undo stacks are too flaky (e.g. IE's gets
- * clobbered on DOM modifications). Also, this allows interleaving non-editing
- * commands into the undo stack via the UndoRedoManager.
- *
- * @param {goog.editor.plugins.UndoRedoManager=} opt_manager An undo redo
- * manager to be used by this plugin. If none is provided one is created.
- * @constructor
- * @extends {goog.editor.Plugin}
- */
-goog.editor.plugins.UndoRedo = function(opt_manager) {
- goog.editor.Plugin.call(this);
-
- this.setUndoRedoManager(opt_manager ||
- new goog.editor.plugins.UndoRedoManager());
-
- // Map of goog.editor.Field hashcode to goog.events.EventHandler
- this.eventHandlers_ = {};
-
- this.currentStates_ = {};
-
- /**
- * @type {?string}
- * @private
- */
- this.initialFieldChange_ = null;
-
- /**
- * A copy of {@code goog.editor.plugins.UndoRedo.restoreState} bound to this,
- * used by undo-redo state objects to restore the state of an editable field.
- * @type {Function}
- * @see goog.editor.plugins.UndoRedo#restoreState
- * @private
- */
- this.boundRestoreState_ = goog.bind(this.restoreState, this);
-};
-goog.inherits(goog.editor.plugins.UndoRedo, goog.editor.Plugin);
-
-
-/**
- * The logger for this class.
- * @type {goog.debug.Logger}
- * @protected
- * @override
- */
-goog.editor.plugins.UndoRedo.prototype.logger =
- goog.debug.Logger.getLogger('goog.editor.plugins.UndoRedo');
-
-
-/**
- * The {@code UndoState_} whose change is in progress, null if an undo or redo
- * is not in progress.
- *
- * @type {goog.editor.plugins.UndoRedo.UndoState_?}
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.inProgressUndo_ = null;
-
-
-/**
- * The undo-redo stack manager used by this plugin.
- * @type {goog.editor.plugins.UndoRedoManager}
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.undoManager_;
-
-
-/**
- * The key for the event listener handling state change events from the
- * undo-redo manager.
- * @type {number}
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.managerStateChangeKey_;
-
-
-/**
- * Commands implemented by this plugin.
- * @enum {string}
- */
-goog.editor.plugins.UndoRedo.COMMAND = {
- UNDO: '+undo',
- REDO: '+redo'
-};
-
-
-/**
- * Inverse map of execCommand strings to
- * {@link goog.editor.plugins.UndoRedo.COMMAND} constants. Used to determine
- * whether a string corresponds to a command this plugin handles in O(1) time.
- * @type {Object}
- * @private
- */
-goog.editor.plugins.UndoRedo.SUPPORTED_COMMANDS_ =
- goog.object.transpose(goog.editor.plugins.UndoRedo.COMMAND);
-
-
-/**
- * Set the max undo stack depth (not the real memory usage).
- * @param {number} depth Depth of the stack.
- */
-goog.editor.plugins.UndoRedo.prototype.setMaxUndoDepth = function(depth) {
- this.undoManager_.setMaxUndoDepth(depth);
-};
-
-
-/**
- * Set the undo-redo manager used by this plugin. Any state on a previous
- * undo-redo manager is lost.
- * @param {goog.editor.plugins.UndoRedoManager} manager The undo-redo manager.
- */
-goog.editor.plugins.UndoRedo.prototype.setUndoRedoManager = function(manager) {
- if (this.managerStateChangeKey_) {
- goog.events.unlistenByKey(this.managerStateChangeKey_);
- }
-
- this.undoManager_ = manager;
- this.managerStateChangeKey_ = /** @type {number} */ (
- goog.events.listen(this.undoManager_,
- goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE,
- this.dispatchCommandValueChange_,
- false,
- this));
-};
-
-
-/**
- * Whether the string corresponds to a command this plugin handles.
- * @param {string} command Command string to check.
- * @return {boolean} Whether the string corresponds to a command
- * this plugin handles.
- * @override
- */
-goog.editor.plugins.UndoRedo.prototype.isSupportedCommand = function(command) {
- return command in goog.editor.plugins.UndoRedo.SUPPORTED_COMMANDS_;
-};
-
-
-/**
- * Unregisters and disables the fieldObject with this plugin. Thie does *not*
- * clobber the undo stack for the fieldObject though.
- * TODO(user): For the multifield version, we really should add a way to
- * ignore undo actions on field's that have been made uneditable.
- * This is probably as simple as skipping over entries in the undo stack
- * that have a hashcode of an uneditable field.
- * @param {goog.editor.Field} fieldObject The field to register with the plugin.
- * @override
- */
-goog.editor.plugins.UndoRedo.prototype.unregisterFieldObject = function(
- fieldObject) {
- this.disable(fieldObject);
- this.setFieldObject(null);
-};
-
-
-/**
- * This is so subclasses can deal with multifield undo-redo.
- * @return {goog.editor.Field} The active field object for this field. This is
- * the one registered field object for the single-plugin case and the
- * focused field for the multi-field plugin case.
- */
-goog.editor.plugins.UndoRedo.prototype.getCurrentFieldObject = function() {
- return this.getFieldObject();
-};
-
-
-/**
- * This is so subclasses can deal with multifield undo-redo.
- * @param {string} fieldHashCode The Field's hashcode.
- * @return {goog.editor.Field} The field object with the hashcode.
- */
-goog.editor.plugins.UndoRedo.prototype.getFieldObjectForHash = function(
- fieldHashCode) {
- // With single field undoredo, there's only one Field involved.
- return this.getFieldObject();
-};
-
-
-/**
- * This is so subclasses can deal with multifield undo-redo.
- * @return {goog.editor.Field} Target for COMMAND_VALUE_CHANGE events.
- */
-goog.editor.plugins.UndoRedo.prototype.getCurrentEventTarget = function() {
- return this.getFieldObject();
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.enable = function(fieldObject) {
- if (this.isEnabled(fieldObject)) {
- return;
- }
-
- // Don't want pending delayed changes from when undo-redo was disabled
- // firing after undo-redo is enabled since they might cause undo-redo stack
- // updates.
- fieldObject.clearDelayedChange();
-
- var eventHandler = new goog.events.EventHandler(this);
-
- // TODO(user): From ojan during a code review:
- // The beforechange handler is meant to be there so you can grab the cursor
- // position *before* the change is made as that's where you want the cursor to
- // be after an undo.
- //
- // It kinda looks like updateCurrentState_ doesn't do that correctly right
- // now, but it really should be fixed to do so. The cursor position stored in
- // the state should be the cursor position before any changes are made, not
- // the cursor position when the change finishes.
- //
- // It also seems like the if check below is just a bad one. We should do this
- // for browsers that use mutation events as well even though the beforechange
- // happens too late...maybe not. I don't know about this.
- if (!goog.editor.BrowserFeature.USE_MUTATION_EVENTS) {
- // We don't listen to beforechange in mutation-event browsers because
- // there we fire beforechange, then syncronously file change. The point
- // of before change is to capture before the user has changed anything.
- eventHandler.listen(fieldObject,
- goog.editor.Field.EventType.BEFORECHANGE, this.handleBeforeChange_);
- }
- eventHandler.listen(fieldObject,
- goog.editor.Field.EventType.DELAYEDCHANGE, this.handleDelayedChange_);
- eventHandler.listen(fieldObject, goog.editor.Field.EventType.BLUR,
- this.handleBlur_);
-
- this.eventHandlers_[fieldObject.getHashCode()] = eventHandler;
-
- // We want to capture the initial state of a Trogedit field before any
- // editing has happened. This is necessary so that we can undo the first
- // change to a field, even if we don't handle beforeChange.
- this.updateCurrentState_(fieldObject);
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.disable = function(fieldObject) {
- // Process any pending changes so we don't lose any undo-redo states that we
- // want prior to disabling undo-redo.
- fieldObject.clearDelayedChange();
-
- var eventHandler = this.eventHandlers_[fieldObject.getHashCode()];
- if (eventHandler) {
- eventHandler.dispose();
- delete this.eventHandlers_[fieldObject.getHashCode()];
- }
-
- // We delete the current state of the field on disable. When we re-enable
- // the state will be re-fetched. In most cases the content will be the same,
- // but this allows us to pick up changes while not editable. That way, when
- // undoing after starting an editable session, you can always undo to the
- // state you started in. Given this sequence of events:
- // Make editable
- // Type 'anakin'
- // Make not editable
- // Set HTML to be 'padme'
- // Make editable
- // Type 'dark side'
- // Undo
- // Without re-snapshoting current state on enable, the undo would go from
- // 'dark-side' -> 'anakin', rather than 'dark-side' -> 'padme'. You couldn't
- // undo the field to the state that existed immediately after it was made
- // editable for the second time.
- if (this.currentStates_[fieldObject.getHashCode()]) {
- delete this.currentStates_[fieldObject.getHashCode()];
- }
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.isEnabled = function(fieldObject) {
- // All enabled plugins have a eventHandler so reuse that map rather than
- // storing additional enabled state.
- return !!this.eventHandlers_[fieldObject.getHashCode()];
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.disposeInternal = function() {
- goog.editor.plugins.UndoRedo.superClass_.disposeInternal.call(this);
-
- for (var hashcode in this.eventHandlers_) {
- this.eventHandlers_[hashcode].dispose();
- delete this.eventHandlers_[hashcode];
- }
- this.setFieldObject(null);
-
- if (this.undoManager_) {
- this.undoManager_.dispose();
- delete this.undoManager_;
- }
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.getTrogClassId = function() {
- return 'UndoRedo';
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.execCommand = function(command,
- var_args) {
- if (command == goog.editor.plugins.UndoRedo.COMMAND.UNDO) {
- this.undoManager_.undo();
- } else if (command == goog.editor.plugins.UndoRedo.COMMAND.REDO) {
- this.undoManager_.redo();
- }
-};
-
-
-/** @override */
-goog.editor.plugins.UndoRedo.prototype.queryCommandValue = function(command) {
- var state = null;
- if (command == goog.editor.plugins.UndoRedo.COMMAND.UNDO) {
- state = this.undoManager_.hasUndoState();
- } else if (command == goog.editor.plugins.UndoRedo.COMMAND.REDO) {
- state = this.undoManager_.hasRedoState();
- }
- return state;
-};
-
-
-/**
- * Dispatches the COMMAND_VALUE_CHANGE event on the editable field or the field
- * manager, as appropriate.
- * Note: Really, people using multi field mode should be listening directly
- * to the undo-redo manager for events.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.dispatchCommandValueChange_ =
- function() {
- var eventTarget = this.getCurrentEventTarget();
- eventTarget.dispatchEvent({
- type: goog.editor.Field.EventType.COMMAND_VALUE_CHANGE,
- commands: [goog.editor.plugins.UndoRedo.COMMAND.REDO,
- goog.editor.plugins.UndoRedo.COMMAND.UNDO]});
-};
-
-
-/**
- * Restores the state of the editable field.
- * @param {goog.editor.plugins.UndoRedo.UndoState_} state The state initiating
- * the restore.
- * @param {string} content The content to restore.
- * @param {goog.editor.plugins.UndoRedo.CursorPosition_?} cursorPosition
- * The cursor position within the content.
- */
-goog.editor.plugins.UndoRedo.prototype.restoreState = function(
- state, content, cursorPosition) {
- // Fire any pending changes to get the current field state up to date and
- // then stop listening to changes while doing the undo/redo.
- var fieldObj = this.getFieldObjectForHash(state.fieldHashCode);
- if (!fieldObj) {
- return;
- }
-
- // Fires any pending changes, and stops the change events. Still want to
- // dispatch before change, as a change is being made and the change event
- // will be manually dispatched below after the new content has been restored
- // (also restarting change events).
- fieldObj.stopChangeEvents(true, true);
-
- // To prevent the situation where we stop change events and then an exception
- // happens before we can restart change events, the following code must be in
- // a try-finally block.
- try {
- fieldObj.dispatchBeforeChange();
-
- // Restore the state
- fieldObj.execCommand(goog.editor.Command.CLEAR_LOREM, true);
-
- // We specifically set the raw innerHTML of the field here as that's what
- // we get from the field when we save an undo/redo state. There's
- // no need to clean/unclean the contents in either direction.
- fieldObj.getElement().innerHTML = content;
-
- if (cursorPosition) {
- cursorPosition.select();
- }
-
- var previousFieldObject = this.getCurrentFieldObject();
- fieldObj.focus();
-
- // Apps that integrate their undo-redo with Trogedit may be
- // in a state where there is no previous field object (no field focused at
- // the time of undo), so check for existence first.
- if (previousFieldObject &&
- previousFieldObject.getHashCode() != state.fieldHashCode) {
- previousFieldObject.execCommand(goog.editor.Command.UPDATE_LOREM);
- }
-
- // We need to update currentState_ to reflect the change.
- this.currentStates_[state.fieldHashCode].setUndoState(
- content, cursorPosition);
- } catch (e) {
- this.logger.severe('Error while restoring undo state', e);
- } finally {
- // Clear the delayed change event, set flag so we know not to act on it.
- this.inProgressUndo_ = state;
- // Notify the editor that we've changed (fire autosave).
- // Note that this starts up change events again, so we don't have to
- // manually do so even though we stopped change events above.
- fieldObj.dispatchChange();
- fieldObj.dispatchSelectionChangeEvent();
- }
-};
-
-
-/**
- * @override
- */
-goog.editor.plugins.UndoRedo.prototype.handleKeyboardShortcut = function(e, key,
- isModifierPressed) {
- if (isModifierPressed) {
- var command;
- if (key == 'z') {
- command = e.shiftKey ? goog.editor.plugins.UndoRedo.COMMAND.REDO :
- goog.editor.plugins.UndoRedo.COMMAND.UNDO;
- } else if (key == 'y') {
- command = goog.editor.plugins.UndoRedo.COMMAND.REDO;
- }
-
- if (command) {
- // In the case where Trogedit shares its undo redo stack with another
- // application it's possible that an undo or redo will not be for an
- // goog.editor.Field. In this case we don't want to go through the
- // goog.editor.Field execCommand flow which stops and restarts events on
- // the current field. Only Trogedit UndoState's have a fieldHashCode so
- // use that to distinguish between Trogedit and other states.
- var state = command == goog.editor.plugins.UndoRedo.COMMAND.UNDO ?
- this.undoManager_.undoPeek() : this.undoManager_.redoPeek();
- if (state && state.fieldHashCode) {
- this.getCurrentFieldObject().execCommand(command);
- } else {
- this.execCommand(command);
- }
-
- return true;
- }
- }
-
- return false;
-};
-
-
-/**
- * Clear the undo/redo stack.
- */
-goog.editor.plugins.UndoRedo.prototype.clearHistory = function() {
- // Fire all pending change events, so that they don't come back
- // asynchronously to fill the queue.
- this.getFieldObject().stopChangeEvents(true, true);
- this.undoManager_.clearHistory();
- this.getFieldObject().startChangeEvents();
-};
-
-
-/**
- * Refreshes the current state of the editable field as maintained by undo-redo,
- * without adding any undo-redo states to the stack.
- * @param {goog.editor.Field} fieldObject The editable field.
- */
-goog.editor.plugins.UndoRedo.prototype.refreshCurrentState = function(
- fieldObject) {
- if (this.isEnabled(fieldObject)) {
- if (this.currentStates_[fieldObject.getHashCode()]) {
- delete this.currentStates_[fieldObject.getHashCode()];
- }
- this.updateCurrentState_(fieldObject);
- }
-};
-
-
-/**
- * Before the field changes, we want to save the state.
- * @param {goog.events.Event} e The event.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.handleBeforeChange_ = function(e) {
- if (this.inProgressUndo_) {
- // We are in between a previous undo and its delayed change event.
- // Continuing here clobbers the redo stack.
- // This does mean that if you are trying to undo/redo really quickly, it
- // will be gated by the speed of delayed change events.
- return;
- }
-
- var fieldObj = /** @type {goog.editor.Field} */ (e.target);
- var fieldHashCode = fieldObj.getHashCode();
-
- if (this.initialFieldChange_ != fieldHashCode) {
- this.initialFieldChange_ = fieldHashCode;
- this.updateCurrentState_(fieldObj);
- }
-};
-
-
-/**
- * After some idle time, we want to save the state.
- * @param {goog.events.Event} e The event.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.handleDelayedChange_ = function(e) {
- // This was undo making a change, don't add it BACK into the history
- if (this.inProgressUndo_) {
- // Must clear this.inProgressUndo_ before dispatching event because the
- // dispatch can cause another, queued undo that should be allowed to go
- // through.
- var state = this.inProgressUndo_;
- this.inProgressUndo_ = null;
- state.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
- return;
- }
-
- this.updateCurrentState_(/** @type {goog.editor.Field} */ (e.target));
-};
-
-
-/**
- * When the user blurs away, we need to save the state on that field.
- * @param {goog.events.Event} e The event.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.handleBlur_ = function(e) {
- var fieldObj = /** @type {goog.editor.Field} */ (e.target);
- if (fieldObj) {
- fieldObj.clearDelayedChange();
- }
-};
-
-
-/**
- * Returns the goog.editor.plugins.UndoRedo.CursorPosition_ for the current
- * selection in the given Field.
- * @param {goog.editor.Field} fieldObj The field object.
- * @return {goog.editor.plugins.UndoRedo.CursorPosition_} The CursorPosition_ or
- * null if there is no valid selection.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.getCursorPosition_ = function(fieldObj) {
- var cursorPos = new goog.editor.plugins.UndoRedo.CursorPosition_(fieldObj);
- if (!cursorPos.isValid()) {
- return null;
- }
- return cursorPos;
-};
-
-
-/**
- * Helper method for saving state.
- * @param {goog.editor.Field} fieldObj The field object.
- * @private
- */
-goog.editor.plugins.UndoRedo.prototype.updateCurrentState_ = function(
- fieldObj) {
- var fieldHashCode = fieldObj.getHashCode();
- // We specifically grab the raw innerHTML of the field here as that's what
- // we would set on the field in the case of an undo/redo operation. There's
- // no need to clean/unclean the contents in either direction. In the case of
- // lorem ipsum being used, we want to capture the effective state (empty, no
- // cursor position) rather than capturing the lorem html.
- var content, cursorPos;
- if (fieldObj.queryCommandValue(goog.editor.Command.USING_LOREM)) {
- content = '';
- cursorPos = null;
- } else {
- content = fieldObj.getElement().innerHTML;
- cursorPos = this.getCursorPosition_(fieldObj);
- }
-
- var currentState = this.currentStates_[fieldHashCode];
- if (currentState) {
- // Don't create states if the content hasn't changed (spurious
- // delayed change). This can happen when lorem is cleared, for example.
- if (currentState.undoContent_ == content) {
- return;
- } else if (content == '' || currentState.undoContent_ == '') {
- // If lorem ipsum is on we say the contents are the empty string. However,
- // for an empty text shape with focus, the empty contents might not be
- // the same, depending on plugins. We want these two empty states to be
- // considered identical because to the user they are indistinguishable,
- // so we use fieldObj.getInjectableContents to map between them.
- // We cannot use getInjectableContents when first creating the undo
- // content for a field with lorem, because on enable when this is first
- // called we can't guarantee plugin registration order, so the
- // injectableContents at that time might not match the final
- // injectableContents.
- var emptyContents = fieldObj.getInjectableContents('', {});
- if (content == emptyContents && currentState.undoContent_ == '' ||
- currentState.undoContent_ == emptyContents && content == '') {
- return;
- }
- }
-
- currentState.setRedoState(content, cursorPos);
- this.undoManager_.addState(currentState);
- }
-
- this.currentStates_[fieldHashCode] =
- new goog.editor.plugins.UndoRedo.UndoState_(fieldHashCode, content,
- cursorPos, this.boundRestoreState_);
-};
-
-
-
-/**
- * This object encapsulates the state of an editable field.
- *
- * @param {string} fieldHashCode String the id of the field we're saving the
- * content of.
- * @param {string} content String the actual text we're saving.
- * @param {goog.editor.plugins.UndoRedo.CursorPosition_?} cursorPosition
- * CursorPosLite object for the cursor position in the field.
- * @param {Function} restore The function used to restore editable field state.
- * @private
- * @constructor
- * @extends {goog.editor.plugins.UndoRedoState}
- */
-goog.editor.plugins.UndoRedo.UndoState_ = function(fieldHashCode, content,
- cursorPosition, restore) {
- goog.editor.plugins.UndoRedoState.call(this, true);
-
- /**
- * The hash code for the field whose content is being saved.
- * @type {string}
- */
- this.fieldHashCode = fieldHashCode;
-
- /**
- * The bound copy of {@code goog.editor.plugins.UndoRedo.restoreState} used by
- * this state.
- * @type {Function}
- * @private
- */
- this.restore_ = restore;
-
- this.setUndoState(content, cursorPosition);
-};
-goog.inherits(goog.editor.plugins.UndoRedo.UndoState_,
- goog.editor.plugins.UndoRedoState);
-
-
-/**
- * The content to restore on undo.
- * @type {string}
- * @private
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.undoContent_;
-
-
-/**
- * The cursor position to restore on undo.
- * @type {goog.editor.plugins.UndoRedo.CursorPosition_?}
- * @private
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.undoCursorPosition_;
-
-
-/**
- * The content to restore on redo, undefined until the state is pushed onto the
- * undo stack.
- * @type {string|undefined}
- * @private
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.redoContent_;
-
-
-/**
- * The cursor position to restore on redo, undefined until the state is pushed
- * onto the undo stack.
- * @type {goog.editor.plugins.UndoRedo.CursorPosition_|null|undefined}
- * @private
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.redoCursorPosition_;
-
-
-/**
- * Performs the undo operation represented by this state.
- * @override
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.undo = function() {
- this.restore_(this, this.undoContent_,
- this.undoCursorPosition_);
-};
-
-
-/**
- * Performs the redo operation represented by this state.
- * @override
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.redo = function() {
- this.restore_(this, this.redoContent_,
- this.redoCursorPosition_);
-};
-
-
-/**
- * Updates the undo portion of this state. Should only be used to update the
- * current state of an editable field, which is not yet on the undo stack after
- * an undo or redo operation. You should never be modifying states on the stack!
- * @param {string} content The current content.
- * @param {goog.editor.plugins.UndoRedo.CursorPosition_?} cursorPosition
- * The current cursor position.
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.setUndoState = function(
- content, cursorPosition) {
- this.undoContent_ = content;
- this.undoCursorPosition_ = cursorPosition;
-};
-
-
-/**
- * Adds redo information to this state. This method should be called before the
- * state is added onto the undo stack.
- *
- * @param {string} content The content to restore on a redo.
- * @param {goog.editor.plugins.UndoRedo.CursorPosition_?} cursorPosition
- * The cursor position to restore on a redo.
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.setRedoState = function(
- content, cursorPosition) {
- this.redoContent_ = content;
- this.redoCursorPosition_ = cursorPosition;
-};
-
-
-/**
- * Checks if the *contents* of two
- * {@code goog.editor.plugins.UndoRedo.UndoState_}s are the same. We don't
- * bother checking the cursor position (that's not something we'd want to save
- * anyway).
- * @param {goog.editor.plugins.UndoRedoState} rhs The state to compare.
- * @return {boolean} Whether the contents are the same.
- * @override
- */
-goog.editor.plugins.UndoRedo.UndoState_.prototype.equals = function(rhs) {
- return this.fieldHashCode == rhs.fieldHashCode &&
- this.undoContent_ == rhs.undoContent_ &&
- this.redoContent_ == rhs.redoContent_;
-};
-
-
-
-/**
- * Stores the state of the selection in a way the survives DOM modifications
- * that don't modify the user-interactable content (e.g. making something bold
- * vs. typing a character).
- *
- * TODO(user): Completely get rid of this and use goog.dom.SavedCaretRange.
- *
- * @param {goog.editor.Field} field The field the selection is in.
- * @private
- * @constructor
- */
-goog.editor.plugins.UndoRedo.CursorPosition_ = function(field) {
- this.field_ = field;
-
- var win = field.getEditableDomHelper().getWindow();
- var range = field.getRange();
- var isValidRange = !!range && range.isRangeInDocument() &&
- range.getWindow() == win;
- range = isValidRange ? range : null;
-
- if (goog.editor.BrowserFeature.HAS_W3C_RANGES) {
- this.initW3C_(range);
- } else if (goog.editor.BrowserFeature.HAS_IE_RANGES) {
- this.initIE_(range);
- }
-};
-
-
-/**
- * The standards compliant version keeps a list of childNode offsets.
- * @param {goog.dom.AbstractRange?} range The range to save.
- * @private
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.initW3C_ = function(
- range) {
- this.isValid_ = false;
-
- // TODO: Check if the range is in the field before trying to save it
- // for FF 3 contentEditable.
- if (!range) {
- return;
- }
-
- var anchorNode = range.getAnchorNode();
- var focusNode = range.getFocusNode();
- if (!anchorNode || !focusNode) {
- return;
- }
-
- var anchorOffset = range.getAnchorOffset();
- var anchor = new goog.dom.NodeOffset(anchorNode, this.field_.getElement());
-
- var focusOffset = range.getFocusOffset();
- var focus = new goog.dom.NodeOffset(focusNode, this.field_.getElement());
-
- // Test range direction.
- if (range.isReversed()) {
- this.startOffset_ = focus;
- this.startChildOffset_ = focusOffset;
- this.endOffset_ = anchor;
- this.endChildOffset_ = anchorOffset;
- } else {
- this.startOffset_ = anchor;
- this.startChildOffset_ = anchorOffset;
- this.endOffset_ = focus;
- this.endChildOffset_ = focusOffset;
- }
-
- this.isValid_ = true;
-};
-
-
-/**
- * In IE, we just keep track of the text offset (number of characters).
- * @param {goog.dom.AbstractRange?} range The range to save.
- * @private
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.initIE_ = function(
- range) {
- this.isValid_ = false;
-
- if (!range) {
- return;
- }
-
- var ieRange = range.getTextRange(0).getBrowserRangeObject();
-
- if (!goog.dom.contains(this.field_.getElement(), ieRange.parentElement())) {
- return;
- }
-
- // Create a range that encompasses the contentEditable region to serve
- // as a reference to form ranges below.
- var contentEditableRange =
- this.field_.getEditableDomHelper().getDocument().body.createTextRange();
- contentEditableRange.moveToElementText(this.field_.getElement());
-
- // startMarker is a range from the start of the contentEditable node to the
- // start of the current selection.
- var startMarker = ieRange.duplicate();
- startMarker.collapse(true);
- startMarker.setEndPoint('StartToStart', contentEditableRange);
- this.startOffset_ =
- goog.editor.plugins.UndoRedo.CursorPosition_.computeEndOffsetIE_(
- startMarker);
-
- // endMarker is a range from the start of teh contentEditable node to the
- // end of the current selection.
- var endMarker = ieRange.duplicate();
- endMarker.setEndPoint('StartToStart', contentEditableRange);
- this.endOffset_ =
- goog.editor.plugins.UndoRedo.CursorPosition_.computeEndOffsetIE_(
- endMarker);
-
- this.isValid_ = true;
-};
-
-
-/**
- * @return {boolean} Whether this object is valid.
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.isValid = function() {
- return this.isValid_;
-};
-
-
-/**
- * @return {string} A string representation of this object.
- * @override
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.toString = function() {
- if (goog.editor.BrowserFeature.HAS_W3C_RANGES) {
- return 'W3C:' + this.startOffset_.toString() + '\n' +
- this.startChildOffset_ + ':' + this.endOffset_.toString() + '\n' +
- this.endChildOffset_;
- }
- return 'IE:' + this.startOffset_ + ',' + this.endOffset_;
-};
-
-
-/**
- * Makes the browser's selection match the cursor position.
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.select = function() {
- var range = this.getRange_(this.field_.getElement());
- if (range) {
- if (goog.editor.BrowserFeature.HAS_IE_RANGES) {
- this.field_.getElement().focus();
- }
- goog.dom.Range.createFromBrowserRange(range).select();
- }
-};
-
-
-/**
- * Get the range that encompases the the cursor position relative to a given
- * base node.
- * @param {Element} baseNode The node to get the cursor position relative to.
- * @return {Range|TextRange|null} The browser range for this position.
- * @private
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.prototype.getRange_ =
- function(baseNode) {
- if (goog.editor.BrowserFeature.HAS_W3C_RANGES) {
- var startNode = this.startOffset_.findTargetNode(baseNode);
- var endNode = this.endOffset_.findTargetNode(baseNode);
- if (!startNode || !endNode) {
- return null;
- }
-
- // Create range.
- return /** @type {Range} */ (
- goog.dom.Range.createFromNodes(startNode, this.startChildOffset_,
- endNode, this.endChildOffset_).getBrowserRangeObject());
- }
-
- // Create a collapsed selection at the start of the contentEditable region,
- // which the offsets were calculated relative to before. Note that we force
- // a text range here so we can use moveToElementText.
- var sel = baseNode.ownerDocument.body.createTextRange();
- sel.moveToElementText(baseNode);
- sel.collapse(true);
- sel.moveEnd('character', this.endOffset_);
- sel.moveStart('character', this.startOffset_);
- return sel;
-};
-
-
-/**
- * Compute the number of characters to the end of the range in IE.
- * @param {TextRange} range The range to compute an offset for.
- * @return {number} The number of characters to the end of the range.
- * @private
- */
-goog.editor.plugins.UndoRedo.CursorPosition_.computeEndOffsetIE_ =
- function(range) {
- var testRange = range.duplicate();
-
- // The number of offset characters is a little off depending on
- // what type of block elements happen to be between the start of the
- // textedit and the cursor position. We fudge the offset until the
- // two ranges match.
- var text = range.text;
- var guess = text.length;
-
- testRange.collapse(true);
- testRange.moveEnd('character', guess);
-
- // Adjust the range until the end points match. This doesn't quite
- // work if we're at the end of the field so we give up after a few
- // iterations.
- var diff;
- var numTries = 10;
- while (diff = testRange.compareEndPoints('EndToEnd', range)) {
- guess -= diff;
- testRange.moveEnd('character', -diff);
- --numTries;
- if (0 == numTries) {
- break;
- }
- }
- // When we set innerHTML, blank lines become a single space, causing
- // the cursor position to be off by one. So we accommodate for blank
- // lines.
- var offset = 0;
- var pos = text.indexOf('\n\r');
- while (pos != -1) {
- ++offset;
- pos = text.indexOf('\n\r', pos + 1);
- }
- return guess + offset;
-};