http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/tagonenterhandler.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/tagonenterhandler.js 
b/externs/GCL/externs/goog/editor/plugins/tagonenterhandler.js
new file mode 100644
index 0000000..e5776fd
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/tagonenterhandler.js
@@ -0,0 +1,744 @@
+// Copyright 2008 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 TrogEdit plugin to handle enter keys by inserting the
+ * specified block level tag.
+ *
+ * @author [email protected] (Robby Walker)
+ */
+
+goog.provide('goog.editor.plugins.TagOnEnterHandler');
+
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.Command');
+goog.require('goog.editor.node');
+goog.require('goog.editor.plugins.EnterHandler');
+goog.require('goog.editor.range');
+goog.require('goog.editor.style');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.functions');
+goog.require('goog.string.Unicode');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Plugin to handle enter keys. This subclass normalizes all browsers to use
+ * the given block tag on enter.
+ * @param {goog.dom.TagName} tag The type of tag to add on enter.
+ * @constructor
+ * @extends {goog.editor.plugins.EnterHandler}
+ */
+goog.editor.plugins.TagOnEnterHandler = function(tag) {
+  this.tag = tag;
+
+  goog.editor.plugins.EnterHandler.call(this);
+};
+goog.inherits(goog.editor.plugins.TagOnEnterHandler,
+    goog.editor.plugins.EnterHandler);
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.getTrogClassId = function() {
+  return 'TagOnEnterHandler';
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.getNonCollapsingBlankHtml =
+    function() {
+  if (this.tag == goog.dom.TagName.P) {
+    return '<p>&nbsp;</p>';
+  } else if (this.tag == goog.dom.TagName.DIV) {
+    return '<div><br></div>';
+  }
+  return '<br>';
+};
+
+
+/**
+ * This plugin is active on uneditable fields so it can provide a value for
+ * queryCommandValue calls asking for goog.editor.Command.BLOCKQUOTE.
+ * @return {boolean} True.
+ * @override
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.activeOnUneditableFields =
+    goog.functions.TRUE;
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.isSupportedCommand = function(
+    command) {
+  return command == goog.editor.Command.DEFAULT_TAG;
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.queryCommandValue = function(
+    command) {
+  return command == goog.editor.Command.DEFAULT_TAG ? this.tag : null;
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.handleBackspaceInternal =
+    function(e, range) {
+  goog.editor.plugins.TagOnEnterHandler.superClass_.handleBackspaceInternal.
+      call(this, e, range);
+
+  if (goog.userAgent.GECKO) {
+    this.markBrToNotBeRemoved_(range, true);
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.processParagraphTagsInternal =
+    function(e, split) {
+  if ((goog.userAgent.OPERA || goog.userAgent.IE) &&
+      this.tag != goog.dom.TagName.P) {
+    this.ensureBlockIeOpera(this.tag);
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.handleDeleteGecko = function(
+    e) {
+  var range = this.getFieldObject().getRange();
+  var container = goog.editor.style.getContainer(
+      range && range.getContainerElement());
+  if (this.getFieldObject().getElement().lastChild == container &&
+      goog.editor.plugins.EnterHandler.isBrElem(container)) {
+    // Don't delete if it's the last node in the field and just has a BR.
+    e.preventDefault();
+    // TODO(user): I think we probably don't need to stopPropagation here
+    e.stopPropagation();
+  } else {
+    // Go ahead with deletion.
+    // Prevent an existing BR immediately following the selection being deleted
+    // from being removed in the keyup stage (as opposed to a BR added by FF
+    // after deletion, which we do remove).
+    this.markBrToNotBeRemoved_(range, false);
+    // Manually delete the selection if it's at a BR.
+    this.deleteBrGecko(e);
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.handleKeyUpInternal = function(
+    e) {
+  if (goog.userAgent.GECKO) {
+    if (e.keyCode == goog.events.KeyCodes.DELETE) {
+      this.removeBrIfNecessary_(false);
+    } else if (e.keyCode == goog.events.KeyCodes.BACKSPACE) {
+      this.removeBrIfNecessary_(true);
+    }
+  } else if ((goog.userAgent.IE || goog.userAgent.OPERA) &&
+             e.keyCode == goog.events.KeyCodes.ENTER) {
+    this.ensureBlockIeOpera(this.tag, true);
+  }
+  // Safari uses DIVs by default.
+};
+
+
+/**
+ * String that matches a single BR tag or NBSP surrounded by non-breaking
+ * whitespace
+ * @type {string}
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.BrOrNbspSurroundedWithWhiteSpace_ =
+    '[\t\n\r ]*(<br[^>]*\/?>|&nbsp;)[\t\n\r ]*';
+
+
+/**
+ * String that matches a single BR tag or NBSP surrounded by non-breaking
+ * whitespace
+ * @type {RegExp}
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.emptyLiRegExp_ = new RegExp('^' +
+    goog.editor.plugins.TagOnEnterHandler.BrOrNbspSurroundedWithWhiteSpace_ +
+    '$');
+
+
+/**
+ * Ensures the current node is wrapped in the tag.
+ * @param {Node} node The node to ensure gets wrapped.
+ * @param {Element} container Element containing the selection.
+ * @return {Element} Element containing the selection, after the wrapping.
+  * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.ensureNodeIsWrappedW3c_ =
+    function(node, container) {
+  if (container == this.getFieldObject().getElement()) {
+    // If the first block-level ancestor of cursor is the field,
+    // don't split the tree. Find all the text from the cursor
+    // to both block-level elements surrounding it (if they exist)
+    // and split the text into two elements.
+    // This is the IE contentEditable behavior.
+
+    // The easy way to do this is to wrap all the text in an element
+    // and then split the element as if the user had hit enter
+    // in the paragraph
+
+    // However, simply wrapping the text into an element creates problems
+    // if the text was already wrapped using some other element such as an
+    // anchor.  For example, wrapping the text of
+    //   <a href="">Text</a>
+    // would produce
+    //   <a href=""><p>Text</p></a>
+    // which is not what we want.  What we really want is
+    //   <p><a href="">Text</a></p>
+    // So we need to search for an ancestor of position.node to be wrapped.
+    // We do this by iterating up the hierarchy of postiion.node until we've
+    // reached the node that's just under the container.
+    var isChildOfFn = function(child) {
+      return container == child.parentNode; };
+    var nodeToWrap = goog.dom.getAncestor(node, isChildOfFn, true);
+    container = goog.editor.plugins.TagOnEnterHandler.wrapInContainerW3c_(
+        this.tag, {node: nodeToWrap, offset: 0}, container);
+  }
+  return container;
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.handleEnterWebkitInternal =
+    function(e) {
+  if (this.tag == goog.dom.TagName.DIV) {
+    var range = this.getFieldObject().getRange();
+    var container =
+        goog.editor.style.getContainer(range.getContainerElement());
+
+    var position = goog.editor.range.getDeepEndPoint(range, true);
+    container = this.ensureNodeIsWrappedW3c_(position.node, container);
+    goog.dom.Range.createCaret(position.node, position.offset).select();
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.TagOnEnterHandler.prototype.
+    handleEnterAtCursorGeckoInternal = function(e, wasCollapsed, range) {
+  // We use this because there are a few cases where FF default
+  // implementation doesn't follow IE's:
+  //   -Inserts BRs into empty elements instead of NBSP which has nasty
+  //    side effects w/ making/deleting selections
+  //   -Hitting enter when your cursor is in the field itself. IE will
+  //    create two elements. FF just inserts a BR.
+  //   -Hitting enter inside an empty list-item doesn't create a block
+  //    tag. It just splits the list and puts your cursor in the middle.
+  var li = null;
+  if (wasCollapsed) {
+    // Only break out of lists for collapsed selections.
+    li = goog.dom.getAncestorByTagNameAndClass(
+        range && range.getContainerElement(), goog.dom.TagName.LI);
+  }
+  var isEmptyLi = (li &&
+      li.innerHTML.match(
+          goog.editor.plugins.TagOnEnterHandler.emptyLiRegExp_));
+  var elementAfterCursor = isEmptyLi ?
+      this.breakOutOfEmptyListItemGecko_(li) :
+      this.handleRegularEnterGecko_();
+
+  // Move the cursor in front of "nodeAfterCursor", and make sure it
+  // is visible
+  this.scrollCursorIntoViewGecko_(elementAfterCursor);
+
+  // Fix for http://b/1991234 :
+  if (goog.editor.plugins.EnterHandler.isBrElem(elementAfterCursor)) {
+    // The first element in the new line is a line with just a BR and maybe 
some
+    // whitespace.
+    // Calling normalize() is needed because there might be empty text nodes
+    // before BR and empty text nodes cause the cursor position bug in Firefox.
+    // See http://b/5220858
+    elementAfterCursor.normalize();
+    var br = elementAfterCursor.getElementsByTagName(goog.dom.TagName.BR)[0];
+    if (br.previousSibling &&
+        br.previousSibling.nodeType == goog.dom.NodeType.TEXT) {
+      // If there is some whitespace before the BR, don't put the selection on
+      // the BR, put it in the text node that's there, otherwise when you type
+      // it will create adjacent text nodes.
+      elementAfterCursor = br.previousSibling;
+    }
+  }
+
+  goog.editor.range.selectNodeStart(elementAfterCursor);
+
+  e.preventDefault();
+  // TODO(user): I think we probably don't need to stopPropagation here
+  e.stopPropagation();
+};
+
+
+/**
+ * If The cursor is in an empty LI then break out of the list like in IE
+ * @param {Node} li LI to break out of.
+ * @return {!Element} Element to put the cursor after.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.breakOutOfEmptyListItemGecko_ =
+    function(li) {
+  // Do this as follows:
+  // 1. <ul>...<li>&nbsp;</li>...</ul>
+  // 2. <ul id='foo1'>...<li id='foo2'>&nbsp;</li>...</ul>
+  // 3. <ul id='foo1'>...</ul><p id='foo3'>&nbsp;</p><ul id='foo2'>...</ul>
+  // 4. <ul>...</ul><p>&nbsp;</p><ul>...</ul>
+  //
+  // There are a couple caveats to the above. If the UL is contained in
+  // a list, then the new node inserted is an LI, not a P.
+  // For an OL, it's all the same, except the tagname of course.
+  // Finally, it's possible that with the LI at the beginning or the end
+  // of the list that we'll end up with an empty list. So we special case
+  // those cases.
+
+  var listNode = li.parentNode;
+  var grandparent = listNode.parentNode;
+  var inSubList = grandparent.tagName == goog.dom.TagName.OL ||
+      grandparent.tagName == goog.dom.TagName.UL;
+
+  // TODO(robbyw): Should we apply the list or list item styles to the new 
node?
+  var newNode = goog.dom.getDomHelper(li).createElement(
+      inSubList ? goog.dom.TagName.LI : this.tag);
+
+  if (!li.previousSibling) {
+    goog.dom.insertSiblingBefore(newNode, listNode);
+  } else {
+    if (li.nextSibling) {
+      var listClone = listNode.cloneNode(false);
+      while (li.nextSibling) {
+        listClone.appendChild(li.nextSibling);
+      }
+      goog.dom.insertSiblingAfter(listClone, listNode);
+    }
+    goog.dom.insertSiblingAfter(newNode, listNode);
+  }
+  if (goog.editor.node.isEmpty(listNode)) {
+    goog.dom.removeNode(listNode);
+  }
+  goog.dom.removeNode(li);
+  newNode.innerHTML = '&nbsp;';
+
+  return newNode;
+};
+
+
+/**
+ * Wrap the text indicated by "position" in an HTML container of type
+ * "nodeName".
+ * @param {string} nodeName Type of container, e.g. "p" (paragraph).
+ * @param {Object} position The W3C cursor position object
+ *     (from getCursorPositionW3c).
+ * @param {Node} container The field containing position.
+ * @return {!Element} The container element that holds the contents from
+ *     position.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.wrapInContainerW3c_ = function(nodeName,
+    position, container) {
+  var start = position.node;
+  while (start.previousSibling &&
+         !goog.editor.style.isContainer(start.previousSibling)) {
+    start = start.previousSibling;
+  }
+
+  var end = position.node;
+  while (end.nextSibling &&
+         !goog.editor.style.isContainer(end.nextSibling)) {
+    end = end.nextSibling;
+  }
+
+  var para = container.ownerDocument.createElement(nodeName);
+  while (start != end) {
+    var newStart = start.nextSibling;
+    goog.dom.appendChild(para, start);
+    start = newStart;
+  }
+  var nextSibling = end.nextSibling;
+  goog.dom.appendChild(para, end);
+  container.insertBefore(para, nextSibling);
+
+  return para;
+};
+
+
+/**
+ * When we delete an element, FF inserts a BR. We want to strip that
+ * BR after the fact, but in the case where your cursor is at a character
+ * right before a BR and you delete that character, we don't want to
+ * strip it. So we detect this case on keydown and mark the BR as not needing
+ * removal.
+ * @param {goog.dom.AbstractRange} range The closure range object.
+ * @param {boolean} isBackspace Whether this is handling the backspace key.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.markBrToNotBeRemoved_ =
+    function(range, isBackspace) {
+  var focusNode = range.getFocusNode();
+  var focusOffset = range.getFocusOffset();
+  var newEndOffset = isBackspace ? focusOffset : focusOffset + 1;
+
+  if (goog.editor.node.getLength(focusNode) == newEndOffset) {
+    var sibling = focusNode.nextSibling;
+    if (sibling && sibling.tagName == goog.dom.TagName.BR) {
+      this.brToKeep_ = sibling;
+    }
+  }
+};
+
+
+/**
+ * If we hit delete/backspace to merge elements, FF inserts a BR.
+ * We want to strip that BR. In markBrToNotBeRemoved, we detect if
+ * there was already a BR there before the delete/backspace so that
+ * we don't accidentally remove a user-inserted BR.
+ * @param {boolean} isBackSpace Whether this is handling the backspace key.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.removeBrIfNecessary_ = 
function(
+    isBackSpace) {
+  var range = this.getFieldObject().getRange();
+  var focusNode = range.getFocusNode();
+  var focusOffset = range.getFocusOffset();
+
+  var sibling;
+  if (isBackSpace && focusNode.data == '') {
+    // nasty hack. sometimes firefox will backspace a paragraph and put
+    // the cursor before the BR. when it does this, the focusNode is
+    // an empty textnode.
+    sibling = focusNode.nextSibling;
+  } else if (isBackSpace && focusOffset == 0) {
+    var node = focusNode;
+    while (node && !node.previousSibling &&
+           node.parentNode != this.getFieldObject().getElement()) {
+      node = node.parentNode;
+    }
+    sibling = node.previousSibling;
+  } else if (focusNode.length == focusOffset) {
+    sibling = focusNode.nextSibling;
+  }
+
+  if (!sibling || sibling.tagName != goog.dom.TagName.BR ||
+      this.brToKeep_ == sibling) {
+    return;
+  }
+
+  goog.dom.removeNode(sibling);
+  if (focusNode.nodeType == goog.dom.NodeType.TEXT) {
+    // Sometimes firefox inserts extra whitespace. Do our best to deal.
+    // This is buggy though.
+    focusNode.data =
+        goog.editor.plugins.TagOnEnterHandler.trimTabsAndLineBreaks_(
+            focusNode.data);
+    // When we strip whitespace, make sure that our cursor is still at
+    // the end of the textnode.
+    goog.dom.Range.createCaret(focusNode,
+        Math.min(focusOffset, focusNode.length)).select();
+  }
+};
+
+
+/**
+ * Trim the tabs and line breaks from a string.
+ * @param {string} string String to trim.
+ * @return {string} Trimmed string.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.trimTabsAndLineBreaks_ = function(
+    string) {
+  return string.replace(/^[\t\n\r]|[\t\n\r]$/g, '');
+};
+
+
+/**
+ * Called in response to a normal enter keystroke. It has the action of
+ * splitting elements.
+ * @return {Element} The node that the cursor should be before.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.handleRegularEnterGecko_ =
+    function() {
+  var range = this.getFieldObject().getRange();
+  var container =
+      goog.editor.style.getContainer(range.getContainerElement());
+  var newNode;
+  if (goog.editor.plugins.EnterHandler.isBrElem(container)) {
+    if (container.tagName == goog.dom.TagName.BODY) {
+      // If the field contains only a single BR, this code ensures we don't
+      // try to clone the body tag.
+      container = this.ensureNodeIsWrappedW3c_(
+          container.getElementsByTagName(goog.dom.TagName.BR)[0],
+          container);
+    }
+
+    newNode = container.cloneNode(true);
+    goog.dom.insertSiblingAfter(newNode, container);
+  } else {
+    if (!container.firstChild) {
+      container.innerHTML = '&nbsp;';
+    }
+
+    var position = goog.editor.range.getDeepEndPoint(range, true);
+    container = this.ensureNodeIsWrappedW3c_(position.node, container);
+
+    newNode = goog.editor.plugins.TagOnEnterHandler.splitDomAndAppend_(
+        position.node, position.offset, container);
+
+    // If the left half and right half of the splitted node are anchors then
+    // that means the user pressed enter while the caret was inside
+    // an anchor tag and split it.  The left half is the first anchor
+    // found while traversing the right branch of container.  The right half
+    // is the first anchor found while traversing the left branch of newNode.
+    var leftAnchor =
+        goog.editor.plugins.TagOnEnterHandler.findAnchorInTraversal_(
+            container);
+    var rightAnchor =
+        goog.editor.plugins.TagOnEnterHandler.findAnchorInTraversal_(
+            newNode, true);
+    if (leftAnchor && rightAnchor &&
+        leftAnchor.tagName == goog.dom.TagName.A &&
+        rightAnchor.tagName == goog.dom.TagName.A) {
+      // If the original anchor (left anchor) is now empty, that means
+      // the user pressed [Enter] at the beginning of the anchor,
+      // in which case we we
+      // want to replace that anchor with its child nodes
+      // Otherwise, we take the second half of the splitted text and break
+      // it out of the anchor.
+      var anchorToRemove = goog.editor.node.isEmpty(leftAnchor, false) ?
+          leftAnchor : rightAnchor;
+      goog.dom.flattenElement(/** @type {!Element} */ (anchorToRemove));
+    }
+  }
+  return /** @type {!Element} */ (newNode);
+};
+
+
+/**
+ * Scroll the cursor into view, resulting from splitting the paragraph/adding
+ * a br. It behaves differently than scrollIntoView
+ * @param {Element} element The element immediately following the cursor. Will
+ *     be used to determine how to scroll in order to make the cursor visible.
+ *     CANNOT be a BR, as they do not have offsetHeight/offsetTop.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.prototype.scrollCursorIntoViewGecko_ =
+    function(element) {
+  if (!this.getFieldObject().isFixedHeight()) {
+    return; // Only need to scroll fixed height fields.
+  }
+
+  var field = this.getFieldObject().getElement();
+
+  // Get the y position of the element we want to scroll to
+  var elementY = goog.style.getPageOffsetTop(element);
+
+  // Determine the height of that element, since we want the bottom of the
+  // element to be in view.
+  var bottomOfNode = elementY + element.offsetHeight;
+
+  var dom = this.getFieldDomHelper();
+  var win = this.getFieldDomHelper().getWindow();
+  var scrollY = dom.getDocumentScroll().y;
+  var viewportHeight = goog.dom.getViewportSize(win).height;
+
+  // If the botom of the element is outside the viewport, move it into view
+  if (bottomOfNode > viewportHeight + scrollY) {
+    // In standards mode, use the html element and not the body
+    if (field.tagName == goog.dom.TagName.BODY &&
+        goog.editor.node.isStandardsMode(field)) {
+      field = field.parentNode;
+    }
+    field.scrollTop = bottomOfNode - viewportHeight;
+  }
+};
+
+
+/**
+ * Splits the DOM tree around the given node and returns the node
+ * containing the second half of the tree. The first half of the tree
+ * is modified, but not removed from the DOM.
+ * @param {Node} positionNode Node to split at.
+ * @param {number} positionOffset Offset into positionNode to split at.  If
+ *     positionNode is a text node, this offset is an offset in to the text
+ *     content of that node.  Otherwise, positionOffset is an offset in to
+ *     the childNodes array.  All elements with child index of  positionOffset
+ *     or greater will be moved to the second half.  If positionNode is an
+ *     empty element, the dom will be split at that element, with positionNode
+ *     ending up in the second half.  positionOffset must be 0 in this case.
+ * @param {Node=} opt_root Node at which to stop splitting the dom (the root
+ *     is also split).
+ * @return {!Node} The node containing the second half of the tree.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.splitDom_ = function(
+    positionNode, positionOffset, opt_root) {
+  if (!opt_root) opt_root = positionNode.ownerDocument.body;
+
+  // Split the node.
+  var textSplit = positionNode.nodeType == goog.dom.NodeType.TEXT;
+  var secondHalfOfSplitNode;
+  if (textSplit) {
+    if (goog.userAgent.IE &&
+        positionOffset == positionNode.nodeValue.length) {
+      // Since splitText fails in IE at the end of a node, we split it 
manually.
+      secondHalfOfSplitNode = goog.dom.getDomHelper(positionNode).
+          createTextNode('');
+      goog.dom.insertSiblingAfter(secondHalfOfSplitNode, positionNode);
+    } else {
+      secondHalfOfSplitNode = positionNode.splitText(positionOffset);
+    }
+  } else {
+    // Here we ensure positionNode is the last node in the first half of the
+    // resulting tree.
+    if (positionOffset) {
+      // Use offset as an index in to childNodes.
+      positionNode = positionNode.childNodes[positionOffset - 1];
+    } else {
+      // In this case, positionNode would be the last node in the first half
+      // of the tree, but we actually want to move it to the second half.
+      // Therefore we set secondHalfOfSplitNode to the same node.
+      positionNode = secondHalfOfSplitNode = positionNode.firstChild ||
+          positionNode;
+    }
+  }
+
+  // Create second half of the tree.
+  var secondHalf = goog.editor.node.splitDomTreeAt(
+      positionNode, secondHalfOfSplitNode, opt_root);
+
+  if (textSplit) {
+    // Join secondHalfOfSplitNode and its right text siblings together and
+    // then replace leading NonNbspWhiteSpace with a Nbsp.  If
+    // secondHalfOfSplitNode has a right sibling that isn't a text node,
+    // then we can leave secondHalfOfSplitNode empty.
+    secondHalfOfSplitNode =
+        goog.editor.plugins.TagOnEnterHandler.joinTextNodes_(
+            secondHalfOfSplitNode, true);
+    goog.editor.plugins.TagOnEnterHandler.replaceWhiteSpaceWithNbsp_(
+        secondHalfOfSplitNode, true, !!secondHalfOfSplitNode.nextSibling);
+
+    // Join positionNode and its left text siblings together and then replace
+    // trailing NonNbspWhiteSpace with a Nbsp.
+    var firstHalf = goog.editor.plugins.TagOnEnterHandler.joinTextNodes_(
+        positionNode, false);
+    goog.editor.plugins.TagOnEnterHandler.replaceWhiteSpaceWithNbsp_(
+        firstHalf, false, false);
+  }
+
+  return secondHalf;
+};
+
+
+/**
+ * Splits the DOM tree around the given node and returns the node containing
+ * second half of the tree, which is appended after the old node.  The first
+ * half of the tree is modified, but not removed from the DOM.
+ * @param {Node} positionNode Node to split at.
+ * @param {number} positionOffset Offset into positionNode to split at.  If
+ *     positionNode is a text node, this offset is an offset in to the text
+ *     content of that node.  Otherwise, positionOffset is an offset in to
+ *     the childNodes array.  All elements with child index of  positionOffset
+ *     or greater will be moved to the second half.  If positionNode is an
+ *     empty element, the dom will be split at that element, with positionNode
+ *     ending up in the second half.  positionOffset must be 0 in this case.
+ * @param {Node} node Node to split.
+ * @return {!Node} The node containing the second half of the tree.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.splitDomAndAppend_ = function(
+    positionNode, positionOffset, node) {
+  var newNode = goog.editor.plugins.TagOnEnterHandler.splitDom_(
+      positionNode, positionOffset, node);
+  goog.dom.insertSiblingAfter(newNode, node);
+  return newNode;
+};
+
+
+/**
+ * Joins node and its adjacent text nodes together.
+ * @param {Node} node The node to start joining.
+ * @param {boolean} moveForward Determines whether to join left siblings 
(false)
+ *     or right siblings (true).
+ * @return {Node} The joined text node.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.joinTextNodes_ = function(node,
+    moveForward) {
+  if (node && node.nodeName == '#text') {
+    var nextNodeFn = moveForward ? 'nextSibling' : 'previousSibling';
+    var prevNodeFn = moveForward ? 'previousSibling' : 'nextSibling';
+    var nodeValues = [node.nodeValue];
+    while (node[nextNodeFn] &&
+           node[nextNodeFn].nodeType == goog.dom.NodeType.TEXT) {
+      node = node[nextNodeFn];
+      nodeValues.push(node.nodeValue);
+      goog.dom.removeNode(node[prevNodeFn]);
+    }
+    if (!moveForward) {
+      nodeValues.reverse();
+    }
+    node.nodeValue = nodeValues.join('');
+  }
+  return node;
+};
+
+
+/**
+ * Replaces leading or trailing spaces of a text node to a single Nbsp.
+ * @param {Node} textNode The text node to search and replace white spaces.
+ * @param {boolean} fromStart Set to true to replace leading spaces, false to
+ *     replace trailing spaces.
+ * @param {boolean} isLeaveEmpty Set to true to leave the node empty if the
+ *     text node was empty in the first place, otherwise put a Nbsp into the
+ *     text node.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.replaceWhiteSpaceWithNbsp_ = function(
+    textNode, fromStart, isLeaveEmpty) {
+  var regExp = fromStart ? /^[ \t\r\n]+/ : /[ \t\r\n]+$/;
+  textNode.nodeValue = textNode.nodeValue.replace(regExp,
+                                                  goog.string.Unicode.NBSP);
+
+  if (!isLeaveEmpty && textNode.nodeValue == '') {
+    textNode.nodeValue = goog.string.Unicode.NBSP;
+  }
+};
+
+
+/**
+ * Finds the first A element in a traversal from the input node.  The input
+ * node itself is not included in the search.
+ * @param {Node} node The node to start searching from.
+ * @param {boolean=} opt_useFirstChild Whether to traverse along the first 
child
+ *     (true) or last child (false).
+ * @return {Node} The first anchor node found in the search, or null if none
+ *     was found.
+ * @private
+ */
+goog.editor.plugins.TagOnEnterHandler.findAnchorInTraversal_ = function(node,
+    opt_useFirstChild) {
+  while ((node = opt_useFirstChild ? node.firstChild : node.lastChild) &&
+         node.tagName != goog.dom.TagName.A) {
+    // Do nothing - advancement is handled in the condition.
+  }
+  return node;
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/undoredo.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/undoredo.js 
b/externs/GCL/externs/goog/editor/plugins/undoredo.js
new file mode 100644
index 0000000..d273a4c
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/undoredo.js
@@ -0,0 +1,1016 @@
+// 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.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');
+goog.require('goog.editor.Plugin');
+goog.require('goog.editor.node');
+goog.require('goog.editor.plugins.UndoRedoManager');
+goog.require('goog.editor.plugins.UndoRedoState');
+goog.require('goog.events');
+goog.require('goog.events.EventHandler');
+goog.require('goog.log');
+goog.require('goog.object');
+
+
+
+/**
+ * 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.log.Logger}
+ * @protected
+ * @override
+ */
+goog.editor.plugins.UndoRedo.prototype.logger =
+    goog.log.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 {goog.events.Key}
+ * @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_ =
+      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.
+    goog.editor.node.replaceInnerHtml(fieldObj.getElement(), 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) {
+    goog.log.error(this.logger, '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;
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/undoredomanager.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/undoredomanager.js 
b/externs/GCL/externs/goog/editor/plugins/undoredomanager.js
new file mode 100644
index 0000000..5e054fd
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/undoredomanager.js
@@ -0,0 +1,338 @@
+// Copyright 2008 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 managing series of undo-redo actions in the form of
+ * {@link goog.editor.plugins.UndoRedoState}s.
+ *
+ */
+
+
+goog.provide('goog.editor.plugins.UndoRedoManager');
+goog.provide('goog.editor.plugins.UndoRedoManager.EventType');
+
+goog.require('goog.editor.plugins.UndoRedoState');
+goog.require('goog.events');
+goog.require('goog.events.EventTarget');
+
+
+
+/**
+ * Manages undo and redo operations through a series of {@code UndoRedoState}s
+ * maintained on undo and redo stacks.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.editor.plugins.UndoRedoManager = function() {
+  goog.events.EventTarget.call(this);
+
+  /**
+   * The maximum number of states on the undo stack at any time. Used to limit
+   * the memory footprint of the undo-redo stack.
+   * TODO(user) have a separate memory size based limit.
+   * @type {number}
+   * @private
+   */
+  this.maxUndoDepth_ = 100;
+
+  /**
+   * The undo stack.
+   * @type {Array<goog.editor.plugins.UndoRedoState>}
+   * @private
+   */
+  this.undoStack_ = [];
+
+  /**
+   * The redo stack.
+   * @type {Array<goog.editor.plugins.UndoRedoState>}
+   * @private
+   */
+  this.redoStack_ = [];
+
+  /**
+   * A queue of pending undo or redo actions. Stored as objects with two
+   * properties: func and state. The func property stores the undo or redo
+   * function to be called, the state property stores the state that method
+   * came from.
+   * @type {Array<Object>}
+   * @private
+   */
+  this.pendingActions_ = [];
+};
+goog.inherits(goog.editor.plugins.UndoRedoManager, goog.events.EventTarget);
+
+
+/**
+ * Event types for the events dispatched by undo-redo manager.
+ * @enum {string}
+ */
+goog.editor.plugins.UndoRedoManager.EventType = {
+  /**
+   * Signifies that he undo or redo stack transitioned between 0 and 1 states,
+   * meaning that the ability to peform undo or redo operations has changed.
+   */
+  STATE_CHANGE: 'state_change',
+
+  /**
+   * Signifies that a state was just added to the undo stack. Events of this
+   * type will have a {@code state} property whose value is the state that
+   * was just added.
+   */
+  STATE_ADDED: 'state_added',
+
+  /**
+   * Signifies that the undo method of a state is about to be called.
+   * Events of this type will have a {@code state} property whose value is the
+   * state whose undo action is about to be performed. If the event is 
cancelled
+   * the action does not proceed, but the state will still transition between
+   * stacks.
+   */
+  BEFORE_UNDO: 'before_undo',
+
+  /**
+   * Signifies that the redo method of a state is about to be called.
+   * Events of this type will have a {@code state} property whose value is the
+   * state whose redo action is about to be performed. If the event is 
cancelled
+   * the action does not proceed, but the state will still transition between
+   * stacks.
+   */
+  BEFORE_REDO: 'before_redo'
+};
+
+
+/**
+ * The key for the listener for the completion of the asynchronous state whose
+ * undo or redo action is in progress. Null if no action is in progress.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.inProgressActionKey_ = null;
+
+
+/**
+ * Set the max undo stack depth (not the real memory usage).
+ * @param {number} depth Depth of the stack.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.setMaxUndoDepth =
+    function(depth) {
+  this.maxUndoDepth_ = depth;
+};
+
+
+/**
+ * Add state to the undo stack. This clears the redo stack.
+ *
+ * @param {goog.editor.plugins.UndoRedoState} state The state to add to the 
undo
+ *     stack.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.addState = function(state) {
+  // TODO: is the state.equals check necessary?
+  if (this.undoStack_.length == 0 ||
+      !state.equals(this.undoStack_[this.undoStack_.length - 1])) {
+    this.undoStack_.push(state);
+    if (this.undoStack_.length > this.maxUndoDepth_) {
+      this.undoStack_.shift();
+    }
+    // Clobber the redo stack.
+    var redoLength = this.redoStack_.length;
+    this.redoStack_.length = 0;
+
+    this.dispatchEvent({
+      type: goog.editor.plugins.UndoRedoManager.EventType.STATE_ADDED,
+      state: state
+    });
+
+    // If the redo state had states on it, then clobbering the redo stack above
+    // has caused a state change.
+    if (this.undoStack_.length == 1 || redoLength) {
+      this.dispatchStateChange_();
+    }
+  }
+};
+
+
+/**
+ * Dispatches a STATE_CHANGE event with this manager as the target.
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.dispatchStateChange_ =
+    function() {
+  this.dispatchEvent(
+      goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE);
+};
+
+
+/**
+ * Performs the undo operation of the state at the top of the undo stack, 
moving
+ * that state to the top of the redo stack. If the undo stack is empty, does
+ * nothing.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.undo = function() {
+  this.shiftState_(this.undoStack_, this.redoStack_);
+};
+
+
+/**
+ * Performs the redo operation of the state at the top of the redo stack, 
moving
+ * that state to the top of the undo stack. If redo undo stack is empty, does
+ * nothing.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.redo = function() {
+  this.shiftState_(this.redoStack_, this.undoStack_);
+};
+
+
+/**
+ * @return {boolean} Wether the undo stack has items on it, i.e., if it is
+ *     possible to perform an undo operation.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.hasUndoState = function() {
+  return this.undoStack_.length > 0;
+};
+
+
+/**
+ * @return {boolean} Wether the redo stack has items on it, i.e., if it is
+ *     possible to perform a redo operation.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.hasRedoState = function() {
+  return this.redoStack_.length > 0;
+};
+
+
+/**
+ * Move a state from one stack to the other, performing the appropriate undo
+ * or redo action.
+ *
+ * @param {Array<goog.editor.plugins.UndoRedoState>} fromStack Stack to move
+ *     the state from.
+ * @param {Array<goog.editor.plugins.UndoRedoState>} toStack Stack to move
+ *     the state to.
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.shiftState_ = function(
+    fromStack, toStack) {
+  if (fromStack.length) {
+    var state = fromStack.pop();
+
+    // Push the current state into the redo stack.
+    toStack.push(state);
+
+    this.addAction_({
+      type: fromStack == this.undoStack_ ?
+          goog.editor.plugins.UndoRedoManager.EventType.BEFORE_UNDO :
+          goog.editor.plugins.UndoRedoManager.EventType.BEFORE_REDO,
+      func: fromStack == this.undoStack_ ? state.undo : state.redo,
+      state: state
+    });
+
+    // If either stack transitioned between 0 and 1 in size then the ability
+    // to do an undo or redo has changed and we must dispatch a state change.
+    if (fromStack.length == 0 || toStack.length == 1) {
+      this.dispatchStateChange_();
+    }
+  }
+};
+
+
+/**
+ * Adds an action to the queue of pending undo or redo actions. If no actions
+ * are pending, immediately performs the action.
+ *
+ * @param {Object} action An undo or redo action. Stored as an object with two
+ *     properties: func and state. The func property stores the undo or redo
+ *     function to be called, the state property stores the state that method
+ *     came from.
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.addAction_ = function(action) {
+  this.pendingActions_.push(action);
+  if (this.pendingActions_.length == 1) {
+    this.doAction_();
+  }
+};
+
+
+/**
+ * Executes the action at the front of the pending actions queue. If an action
+ * is already in progress or the queue is empty, does nothing.
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.doAction_ = function() {
+  if (this.inProgressActionKey_ || this.pendingActions_.length == 0) {
+    return;
+  }
+
+  var action = this.pendingActions_.shift();
+
+  var e = {
+    type: action.type,
+    state: action.state
+  };
+
+  if (this.dispatchEvent(e)) {
+    if (action.state.isAsynchronous()) {
+      this.inProgressActionKey_ = goog.events.listen(action.state,
+          goog.editor.plugins.UndoRedoState.ACTION_COMPLETED,
+          this.finishAction_, false, this);
+      action.func.call(action.state);
+    } else {
+      action.func.call(action.state);
+      this.doAction_();
+    }
+  }
+};
+
+
+/**
+ * Finishes processing the current in progress action, starting the next queued
+ * action if one exists.
+ * @private
+ */
+goog.editor.plugins.UndoRedoManager.prototype.finishAction_ = function() {
+  goog.events.unlistenByKey(/** @type {number} */ (this.inProgressActionKey_));
+  this.inProgressActionKey_ = null;
+  this.doAction_();
+};
+
+
+/**
+ * Clears the undo and redo stacks.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.clearHistory = function() {
+  if (this.undoStack_.length > 0 || this.redoStack_.length > 0) {
+    this.undoStack_.length = 0;
+    this.redoStack_.length = 0;
+    this.dispatchStateChange_();
+  }
+};
+
+
+/**
+ * @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top 
of
+ *     the undo stack without removing it from the stack.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.undoPeek = function() {
+  return this.undoStack_[this.undoStack_.length - 1];
+};
+
+
+/**
+ * @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top 
of
+ *     the redo stack without removing it from the stack.
+ */
+goog.editor.plugins.UndoRedoManager.prototype.redoPeek = function() {
+  return this.redoStack_[this.redoStack_.length - 1];
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/undoredostate.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/undoredostate.js 
b/externs/GCL/externs/goog/editor/plugins/undoredostate.js
new file mode 100644
index 0000000..9a772dd
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/undoredostate.js
@@ -0,0 +1,86 @@
+// Copyright 2008 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 an UndoRedoState interface representing an undo and
+ * redo action for a particular state change. To be used by
+ * {@link goog.editor.plugins.UndoRedoManager}.
+ *
+ */
+
+
+goog.provide('goog.editor.plugins.UndoRedoState');
+
+goog.require('goog.events.EventTarget');
+
+
+
+/**
+ * Represents an undo and redo action for a particular state transition.
+ *
+ * @param {boolean} asynchronous Whether the undo or redo actions for this
+ *     state complete asynchronously. If true, then this state must fire
+ *     an ACTION_COMPLETED event when undo or redo is complete.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.editor.plugins.UndoRedoState = function(asynchronous) {
+  goog.editor.plugins.UndoRedoState.base(this, 'constructor');
+
+  /**
+   * Indicates if the undo or redo actions for this state complete
+   * asynchronously.
+   * @type {boolean}
+   * @private
+   */
+  this.asynchronous_ = asynchronous;
+};
+goog.inherits(goog.editor.plugins.UndoRedoState, goog.events.EventTarget);
+
+
+/**
+ * Event type for events indicating that this state has completed an undo or
+ * redo operation.
+ */
+goog.editor.plugins.UndoRedoState.ACTION_COMPLETED = 'action_completed';
+
+
+/**
+ * @return {boolean} Whether or not the undo and redo actions of this state
+ *     complete asynchronously. If true, the state will fire an 
ACTION_COMPLETED
+ *     event when an undo or redo action is complete.
+ */
+goog.editor.plugins.UndoRedoState.prototype.isAsynchronous = function() {
+  return this.asynchronous_;
+};
+
+
+/**
+ * Undoes the action represented by this state.
+ */
+goog.editor.plugins.UndoRedoState.prototype.undo = goog.abstractMethod;
+
+
+/**
+ * Redoes the action represented by this state.
+ */
+goog.editor.plugins.UndoRedoState.prototype.redo = goog.abstractMethod;
+
+
+/**
+ * Checks if two undo-redo states are the same.
+ * @param {goog.editor.plugins.UndoRedoState} state The state to compare.
+ * @return {boolean} Wether the two states are equal.
+ */
+goog.editor.plugins.UndoRedoState.prototype.equals = goog.abstractMethod;

Reply via email to