flex-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ftho...@apache.org
Subject [22/51] [abbrv] [partial] git commit: [flex-falcon] [refs/heads/JsToAs] - Added GCL extern.
Date Thu, 17 Sep 2015 15:28:37 GMT
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/linkdialogplugin.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/linkdialogplugin.js b/externs/GCL/externs/goog/editor/plugins/linkdialogplugin.js
new file mode 100644
index 0000000..9c804a6
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/linkdialogplugin.js
@@ -0,0 +1,438 @@
+// 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 A plugin for the LinkDialog.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ * @author robbyw@google.com (Robby Walker)
+ */
+
+goog.provide('goog.editor.plugins.LinkDialogPlugin');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.editor.Command');
+goog.require('goog.editor.plugins.AbstractDialogPlugin');
+goog.require('goog.events.EventHandler');
+goog.require('goog.functions');
+goog.require('goog.ui.editor.AbstractDialog');
+goog.require('goog.ui.editor.LinkDialog');
+goog.require('goog.uri.utils');
+
+
+
+/**
+ * A plugin that opens the link dialog.
+ * @constructor
+ * @extends {goog.editor.plugins.AbstractDialogPlugin}
+ */
+goog.editor.plugins.LinkDialogPlugin = function() {
+  goog.editor.plugins.LinkDialogPlugin.base(
+      this, 'constructor', goog.editor.Command.MODAL_LINK_EDITOR);
+
+  /**
+   * Event handler for this object.
+   * @type {goog.events.EventHandler<!goog.editor.plugins.LinkDialogPlugin>}
+   * @private
+   */
+  this.eventHandler_ = new goog.events.EventHandler(this);
+
+
+  /**
+   * A list of whitelisted URL schemes which are safe to open.
+   * @type {Array<string>}
+   * @private
+   */
+  this.safeToOpenSchemes_ = ['http', 'https', 'ftp'];
+};
+goog.inherits(goog.editor.plugins.LinkDialogPlugin,
+    goog.editor.plugins.AbstractDialogPlugin);
+
+
+/**
+ * Link object that the dialog is editing.
+ * @type {goog.editor.Link}
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.currentLink_;
+
+
+/**
+ * Optional warning to show about email addresses.
+ * @type {goog.html.SafeHtml}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.emailWarning_;
+
+
+/**
+ * Whether to show a checkbox where the user can choose to have the link open in
+ * a new window.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.showOpenLinkInNewWindow_ = false;
+
+
+/**
+ * Whether the "open link in new window" checkbox should be checked when the
+ * dialog is shown, and also whether it was checked last time the dialog was
+ * closed.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.isOpenLinkInNewWindowChecked_ =
+    false;
+
+
+/**
+ * Weather to show a checkbox where the user can choose to add 'rel=nofollow'
+ * attribute added to the link.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.showRelNoFollow_ = false;
+
+
+/**
+ * Whether to stop referrer leaks.  Defaults to false.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.stopReferrerLeaks_ = false;
+
+
+/**
+ * Whether to block opening links with a non-whitelisted URL scheme.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.blockOpeningUnsafeSchemes_ =
+    true;
+
+
+/** @override */
+goog.editor.plugins.LinkDialogPlugin.prototype.getTrogClassId =
+    goog.functions.constant('LinkDialogPlugin');
+
+
+/**
+ * Tells the plugin whether to block URLs with schemes not in the whitelist.
+ * If blocking is enabled, this plugin will stop the 'Test Link' popup
+ * window from being created. Blocking doesn't affect link creation--if the
+ * user clicks the 'OK' button with an unsafe URL, the link will still be
+ * created as normal.
+ * @param {boolean} blockOpeningUnsafeSchemes Whether to block non-whitelisted
+ *     schemes.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.setBlockOpeningUnsafeSchemes =
+    function(blockOpeningUnsafeSchemes) {
+  this.blockOpeningUnsafeSchemes_ = blockOpeningUnsafeSchemes;
+};
+
+
+/**
+ * Sets a whitelist of allowed URL schemes that are safe to open.
+ * Schemes should all be in lowercase. If the plugin is set to block opening
+ * unsafe schemes, user-entered URLs will be converted to lowercase and checked
+ * against this list. The whitelist has no effect if blocking is not enabled.
+ * @param {Array<string>} schemes String array of URL schemes to allow (http,
+ *     https, etc.).
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.setSafeToOpenSchemes =
+    function(schemes) {
+  this.safeToOpenSchemes_ = schemes;
+};
+
+
+/**
+ * Tells the dialog to show a checkbox where the user can choose to have the
+ * link open in a new window.
+ * @param {boolean} startChecked Whether to check the checkbox the first
+ *     time the dialog is shown. Subesquent times the checkbox will remember its
+ *     previous state.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.showOpenLinkInNewWindow =
+    function(startChecked) {
+  this.showOpenLinkInNewWindow_ = true;
+  this.isOpenLinkInNewWindowChecked_ = startChecked;
+};
+
+
+/**
+ * Tells the dialog to show a checkbox where the user can choose to have
+ * 'rel=nofollow' attribute added to the link.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.showRelNoFollow = function() {
+  this.showRelNoFollow_ = true;
+};
+
+
+/**
+ * Returns whether the"open link in new window" checkbox was checked last time
+ * the dialog was closed.
+ * @return {boolean} Whether the"open link in new window" checkbox was checked
+ *     last time the dialog was closed.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.
+    getOpenLinkInNewWindowCheckedState = function() {
+  return this.isOpenLinkInNewWindowChecked_;
+};
+
+
+/**
+ * Tells the plugin to stop leaking the page's url via the referrer header when
+ * the "test this link" link is clicked. When the user clicks on a link, the
+ * browser makes a request for the link url, passing the url of the current page
+ * in the request headers. If the user wants the current url to be kept secret
+ * (e.g. an unpublished document), the owner of the url that was clicked will
+ * see the secret url in the request headers, and it will no longer be a secret.
+ * Calling this method will not send a referrer header in the request, just as
+ * if the user had opened a blank window and typed the url in themselves.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.stopReferrerLeaks = function() {
+  this.stopReferrerLeaks_ = true;
+};
+
+
+/**
+ * Sets the warning message to show to users about including email addresses on
+ * public web pages.
+ * @param {!goog.html.SafeHtml} emailWarning Warning message to show users about
+ *     including email addresses on the web.
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.setEmailWarning = function(
+    emailWarning) {
+  this.emailWarning_ = emailWarning;
+};
+
+
+/**
+ * Handles execCommand by opening the dialog.
+ * @param {string} command The command to execute.
+ * @param {*=} opt_arg {@link A goog.editor.Link} object representing the link
+ *     being edited.
+ * @return {*} Always returns true, indicating the dialog was shown.
+ * @protected
+ * @override
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.execCommandInternal = function(
+    command, opt_arg) {
+  this.currentLink_ = /** @type {goog.editor.Link} */(opt_arg);
+  return goog.editor.plugins.LinkDialogPlugin.base(
+      this, 'execCommandInternal', command, opt_arg);
+};
+
+
+/**
+ * Handles when the dialog closes.
+ * @param {goog.events.Event} e The AFTER_HIDE event object.
+ * @override
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.handleAfterHide = function(e) {
+  goog.editor.plugins.LinkDialogPlugin.base(this, 'handleAfterHide', e);
+  this.currentLink_ = null;
+};
+
+
+/**
+ * @return {goog.events.EventHandler<T>} The event handler.
+ * @protected
+ * @this T
+ * @template T
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.getEventHandler = function() {
+  return this.eventHandler_;
+};
+
+
+/**
+ * @return {goog.editor.Link} The link being edited.
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.getCurrentLink = function() {
+  return this.currentLink_;
+};
+
+
+/**
+ * Creates a new instance of the dialog and registers for the relevant events.
+ * @param {goog.dom.DomHelper} dialogDomHelper The dom helper to be used to
+ *     create the dialog.
+ * @param {*=} opt_link The target link (should be a goog.editor.Link).
+ * @return {!goog.ui.editor.LinkDialog} The dialog.
+ * @override
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.createDialog = function(
+    dialogDomHelper, opt_link) {
+  var dialog = new goog.ui.editor.LinkDialog(dialogDomHelper,
+      /** @type {goog.editor.Link} */ (opt_link));
+  if (this.emailWarning_) {
+    dialog.setEmailWarning(this.emailWarning_);
+  }
+  if (this.showOpenLinkInNewWindow_) {
+    dialog.showOpenLinkInNewWindow(this.isOpenLinkInNewWindowChecked_);
+  }
+  if (this.showRelNoFollow_) {
+    dialog.showRelNoFollow();
+  }
+  dialog.setStopReferrerLeaks(this.stopReferrerLeaks_);
+  this.eventHandler_.
+      listen(dialog, goog.ui.editor.AbstractDialog.EventType.OK,
+          this.handleOk).
+      listen(dialog, goog.ui.editor.AbstractDialog.EventType.CANCEL,
+          this.handleCancel_).
+      listen(dialog, goog.ui.editor.LinkDialog.EventType.BEFORE_TEST_LINK,
+          this.handleBeforeTestLink);
+  return dialog;
+};
+
+
+/** @override */
+goog.editor.plugins.LinkDialogPlugin.prototype.disposeInternal = function() {
+  goog.editor.plugins.LinkDialogPlugin.base(this, 'disposeInternal');
+  this.eventHandler_.dispose();
+};
+
+
+/**
+ * Handles the OK event from the dialog by updating the link in the field.
+ * @param {goog.ui.editor.LinkDialog.OkEvent} e OK event object.
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.handleOk = function(e) {
+  // We're not restoring the original selection, so clear it out.
+  this.disposeOriginalSelection();
+
+  this.currentLink_.setTextAndUrl(e.linkText, e.linkUrl);
+  if (this.showOpenLinkInNewWindow_) {
+    // Save checkbox state for next time.
+    this.isOpenLinkInNewWindowChecked_ = e.openInNewWindow;
+  }
+
+  var anchor = this.currentLink_.getAnchor();
+  this.touchUpAnchorOnOk_(anchor, e);
+  var extraAnchors = this.currentLink_.getExtraAnchors();
+  for (var i = 0; i < extraAnchors.length; ++i) {
+    extraAnchors[i].href = anchor.href;
+    this.touchUpAnchorOnOk_(extraAnchors[i], e);
+  }
+
+  // Place cursor to the right of the modified link.
+  this.currentLink_.placeCursorRightOf();
+
+  this.getFieldObject().focus();
+
+  this.getFieldObject().dispatchSelectionChangeEvent();
+  this.getFieldObject().dispatchChange();
+
+  this.eventHandler_.removeAll();
+};
+
+
+/**
+ * Apply the necessary properties to a link upon Ok being clicked in the dialog.
+ * @param {HTMLAnchorElement} anchor The anchor to set properties on.
+ * @param {goog.events.Event} e Event object.
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.touchUpAnchorOnOk_ =
+    function(anchor, e) {
+  if (this.showOpenLinkInNewWindow_) {
+    if (e.openInNewWindow) {
+      anchor.target = '_blank';
+    } else {
+      if (anchor.target == '_blank') {
+        anchor.target = '';
+      }
+      // If user didn't indicate to open in a new window but the link already
+      // had a target other than '_blank', let's leave what they had before.
+    }
+  }
+
+  if (this.showRelNoFollow_) {
+    var alreadyPresent = goog.ui.editor.LinkDialog.hasNoFollow(anchor.rel);
+    if (alreadyPresent && !e.noFollow) {
+      anchor.rel = goog.ui.editor.LinkDialog.removeNoFollow(anchor.rel);
+    } else if (!alreadyPresent && e.noFollow) {
+      anchor.rel = anchor.rel ? anchor.rel + ' nofollow' : 'nofollow';
+    }
+  }
+};
+
+
+/**
+ * Handles the CANCEL event from the dialog by clearing the anchor if needed.
+ * @param {goog.events.Event} e Event object.
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.handleCancel_ = function(e) {
+  if (this.currentLink_.isNew()) {
+    goog.dom.flattenElement(this.currentLink_.getAnchor());
+    var extraAnchors = this.currentLink_.getExtraAnchors();
+    for (var i = 0; i < extraAnchors.length; ++i) {
+      goog.dom.flattenElement(extraAnchors[i]);
+    }
+    // Make sure listeners know the anchor was flattened out.
+    this.getFieldObject().dispatchChange();
+  }
+
+  this.eventHandler_.removeAll();
+};
+
+
+/**
+ * Handles the BeforeTestLink event fired when the 'test' link is clicked.
+ * @param {goog.ui.editor.LinkDialog.BeforeTestLinkEvent} e BeforeTestLink event
+ *     object.
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.handleBeforeTestLink =
+    function(e) {
+  if (!this.shouldOpenUrl(e.url)) {
+    /** @desc Message when the user tries to test (preview) a link, but the
+     * link cannot be tested. */
+    var MSG_UNSAFE_LINK = goog.getMsg('This link cannot be tested.');
+    alert(MSG_UNSAFE_LINK);
+    e.preventDefault();
+  }
+};
+
+
+/**
+ * Checks whether the plugin should open the given url in a new window.
+ * @param {string} url The url to check.
+ * @return {boolean} If the plugin should open the given url in a new window.
+ * @protected
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.shouldOpenUrl = function(url) {
+  return !this.blockOpeningUnsafeSchemes_ || this.isSafeSchemeToOpen_(url);
+};
+
+
+/**
+ * Determines whether or not a url has a scheme which is safe to open.
+ * Schemes like javascript are unsafe due to the possibility of XSS.
+ * @param {string} url A url.
+ * @return {boolean} Whether the url has a safe scheme.
+ * @private
+ */
+goog.editor.plugins.LinkDialogPlugin.prototype.isSafeSchemeToOpen_ =
+    function(url) {
+  var scheme = goog.uri.utils.getScheme(url) || 'http';
+  return goog.array.contains(this.safeToOpenSchemes_, scheme.toLowerCase());
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/linkshortcutplugin.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/linkshortcutplugin.js b/externs/GCL/externs/goog/editor/plugins/linkshortcutplugin.js
new file mode 100644
index 0000000..b77910c
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/linkshortcutplugin.js
@@ -0,0 +1,61 @@
+// Copyright 2011 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 Adds a keyboard shortcut for the link command.
+ *
+ */
+
+goog.provide('goog.editor.plugins.LinkShortcutPlugin');
+
+goog.require('goog.editor.Command');
+goog.require('goog.editor.Plugin');
+
+
+
+/**
+ * Plugin to add a keyboard shortcut for the link command
+ * @constructor
+ * @extends {goog.editor.Plugin}
+ * @final
+ */
+goog.editor.plugins.LinkShortcutPlugin = function() {
+  goog.editor.plugins.LinkShortcutPlugin.base(this, 'constructor');
+};
+goog.inherits(goog.editor.plugins.LinkShortcutPlugin, goog.editor.Plugin);
+
+
+/** @override */
+goog.editor.plugins.LinkShortcutPlugin.prototype.getTrogClassId = function() {
+  return 'LinkShortcutPlugin';
+};
+
+
+/**
+ * @override
+ */
+goog.editor.plugins.LinkShortcutPlugin.prototype.handleKeyboardShortcut =
+    function(e, key, isModifierPressed) {
+  if (isModifierPressed && key == 'k' && !e.shiftKey) {
+    var link = /** @type {goog.editor.Link?} */ (
+        this.getFieldObject().execCommand(goog.editor.Command.LINK));
+    if (link) {
+      link.finishLinkCreation(this.getFieldObject());
+    }
+    return true;
+  }
+
+  return false;
+};
+

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/listtabhandler.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/listtabhandler.js b/externs/GCL/externs/goog/editor/plugins/listtabhandler.js
new file mode 100644
index 0000000..03f78ca
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/listtabhandler.js
@@ -0,0 +1,68 @@
+// 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 Editor plugin to handle tab keys in lists to indent and
+ * outdent.
+ *
+ * @author robbyw@google.com (Robby Walker)
+ */
+
+goog.provide('goog.editor.plugins.ListTabHandler');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.Command');
+goog.require('goog.editor.plugins.AbstractTabHandler');
+goog.require('goog.iter');
+
+
+
+/**
+ * Plugin to handle tab keys in lists to indent and outdent.
+ * @constructor
+ * @extends {goog.editor.plugins.AbstractTabHandler}
+ * @final
+ */
+goog.editor.plugins.ListTabHandler = function() {
+  goog.editor.plugins.AbstractTabHandler.call(this);
+};
+goog.inherits(goog.editor.plugins.ListTabHandler,
+    goog.editor.plugins.AbstractTabHandler);
+
+
+/** @override */
+goog.editor.plugins.ListTabHandler.prototype.getTrogClassId = function() {
+  return 'ListTabHandler';
+};
+
+
+/** @override */
+goog.editor.plugins.ListTabHandler.prototype.handleTabKey = function(e) {
+  var range = this.getFieldObject().getRange();
+  if (goog.dom.getAncestorByTagNameAndClass(range.getContainerElement(),
+                                            goog.dom.TagName.LI) ||
+      goog.iter.some(range, function(node) {
+        return node.tagName == goog.dom.TagName.LI;
+      })) {
+    this.getFieldObject().execCommand(e.shiftKey ?
+        goog.editor.Command.OUTDENT :
+        goog.editor.Command.INDENT);
+    e.preventDefault();
+    return true;
+  }
+
+  return false;
+};
+

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/loremipsum.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/loremipsum.js b/externs/GCL/externs/goog/editor/plugins/loremipsum.js
new file mode 100644
index 0000000..90ba31d
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/loremipsum.js
@@ -0,0 +1,192 @@
+// 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 A plugin that fills the field with lorem ipsum text when it's
+ * empty and does not have the focus. Applies to both editable and uneditable
+ * fields.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.editor.plugins.LoremIpsum');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.editor.Command');
+goog.require('goog.editor.Field');
+goog.require('goog.editor.Plugin');
+goog.require('goog.editor.node');
+goog.require('goog.functions');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A plugin that manages lorem ipsum state of editable fields.
+ * @param {string} message The lorem ipsum message.
+ * @constructor
+ * @extends {goog.editor.Plugin}
+ * @final
+ */
+goog.editor.plugins.LoremIpsum = function(message) {
+  goog.editor.Plugin.call(this);
+
+  /**
+   * The lorem ipsum message.
+   * @type {string}
+   * @private
+   */
+  this.message_ = message;
+};
+goog.inherits(goog.editor.plugins.LoremIpsum, goog.editor.Plugin);
+
+
+/** @override */
+goog.editor.plugins.LoremIpsum.prototype.getTrogClassId =
+    goog.functions.constant('LoremIpsum');
+
+
+/** @override */
+goog.editor.plugins.LoremIpsum.prototype.activeOnUneditableFields =
+    goog.functions.TRUE;
+
+
+/**
+ * Whether the field is currently filled with lorem ipsum text.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.plugins.LoremIpsum.prototype.usingLorem_ = false;
+
+
+/**
+ * Handles queryCommandValue.
+ * @param {string} command The command to query.
+ * @return {boolean} The result.
+ * @override
+ */
+goog.editor.plugins.LoremIpsum.prototype.queryCommandValue = function(command) {
+  return command == goog.editor.Command.USING_LOREM && this.usingLorem_;
+};
+
+
+/**
+ * Handles execCommand.
+ * @param {string} command The command to execute.
+ *     Should be CLEAR_LOREM or UPDATE_LOREM.
+ * @param {*=} opt_placeCursor Whether to place the cursor in the field
+ *     after clearing lorem. Should be a boolean.
+ * @override
+ */
+goog.editor.plugins.LoremIpsum.prototype.execCommand = function(command,
+    opt_placeCursor) {
+  if (command == goog.editor.Command.CLEAR_LOREM) {
+    this.clearLorem_(!!opt_placeCursor);
+  } else if (command == goog.editor.Command.UPDATE_LOREM) {
+    this.updateLorem_();
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.LoremIpsum.prototype.isSupportedCommand =
+    function(command) {
+  return command == goog.editor.Command.CLEAR_LOREM ||
+      command == goog.editor.Command.UPDATE_LOREM ||
+      command == goog.editor.Command.USING_LOREM;
+};
+
+
+/**
+ * Set the lorem ipsum text in a goog.editor.Field if needed.
+ * @private
+ */
+goog.editor.plugins.LoremIpsum.prototype.updateLorem_ = function() {
+  // Try to apply lorem ipsum if:
+  // 1) We have lorem ipsum text
+  // 2) There's not a dialog open, as that screws
+  //    with the dialog's ability to properly restore the selection
+  //    on dialog close (since the DOM nodes would get clobbered in FF)
+  // 3) We're not using lorem already
+  // 4) The field is not currently active (doesn't have focus).
+  var fieldObj = this.getFieldObject();
+  if (!this.usingLorem_ &&
+      !fieldObj.inModalMode() &&
+      goog.editor.Field.getActiveFieldId() != fieldObj.id) {
+    var field = fieldObj.getElement();
+    if (!field) {
+      // Fallback on the original element. This is needed by
+      // fields managed by click-to-edit.
+      field = fieldObj.getOriginalElement();
+    }
+
+    goog.asserts.assert(field);
+    if (goog.editor.node.isEmpty(field)) {
+      this.usingLorem_ = true;
+
+      // Save the old font style so it can be restored when we
+      // clear the lorem ipsum style.
+      this.oldFontStyle_ = field.style.fontStyle;
+      field.style.fontStyle = 'italic';
+      fieldObj.setHtml(true, this.message_, true);
+    }
+  }
+};
+
+
+/**
+ * Clear an EditableField's lorem ipsum and put in initial text if needed.
+ *
+ * If using click-to-edit mode (where Trogedit manages whether the field
+ * is editable), this works for both editable and uneditable fields.
+ *
+ * TODO(user): Is this really necessary? See TODO below.
+ * @param {boolean=} opt_placeCursor Whether to place the cursor in the field
+ *     after clearing lorem.
+ * @private
+ */
+goog.editor.plugins.LoremIpsum.prototype.clearLorem_ = function(
+    opt_placeCursor) {
+  // Don't mess with lorem state when a dialog is open as that screws
+  // with the dialog's ability to properly restore the selection
+  // on dialog close (since the DOM nodes would get clobbered)
+  var fieldObj = this.getFieldObject();
+  if (this.usingLorem_ && !fieldObj.inModalMode()) {
+    var field = fieldObj.getElement();
+    if (!field) {
+      // Fallback on the original element. This is needed by
+      // fields managed by click-to-edit.
+      field = fieldObj.getOriginalElement();
+    }
+
+    goog.asserts.assert(field);
+    this.usingLorem_ = false;
+    field.style.fontStyle = this.oldFontStyle_;
+    fieldObj.setHtml(true, null, true);
+
+    // TODO(nicksantos): I'm pretty sure that this is a hack, but talk to
+    // Julie about why this is necessary and what to do with it. Really,
+    // we need to figure out where it's necessary and remove it where it's
+    // not. Safari never places the cursor on its own willpower.
+    if (opt_placeCursor && fieldObj.isLoaded()) {
+      if (goog.userAgent.WEBKIT) {
+        goog.dom.getOwnerDocument(fieldObj.getElement()).body.focus();
+        fieldObj.focusAndPlaceCursorAtStart();
+      } else if (goog.userAgent.OPERA) {
+        fieldObj.placeCursorAtStart();
+      }
+    }
+  }
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/removeformatting.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/removeformatting.js b/externs/GCL/externs/goog/editor/plugins/removeformatting.js
new file mode 100644
index 0000000..dd73a31
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/removeformatting.js
@@ -0,0 +1,780 @@
+// 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.
+// All Rights Reserved.
+
+/**
+ * @fileoverview Plugin to handle Remove Formatting.
+ *
+ */
+
+goog.provide('goog.editor.plugins.RemoveFormatting');
+
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.BrowserFeature');
+goog.require('goog.editor.Plugin');
+goog.require('goog.editor.node');
+goog.require('goog.editor.range');
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A plugin to handle removing formatting from selected text.
+ * @constructor
+ * @extends {goog.editor.Plugin}
+ * @final
+ */
+goog.editor.plugins.RemoveFormatting = function() {
+  goog.editor.Plugin.call(this);
+
+  /**
+   * Optional function to perform remove formatting in place of the
+   * provided removeFormattingWorker_.
+   * @type {?function(string): string}
+   * @private
+   */
+  this.optRemoveFormattingFunc_ = null;
+};
+goog.inherits(goog.editor.plugins.RemoveFormatting, goog.editor.Plugin);
+
+
+/**
+ * The editor command this plugin in handling.
+ * @type {string}
+ */
+goog.editor.plugins.RemoveFormatting.REMOVE_FORMATTING_COMMAND =
+    '+removeFormat';
+
+
+/**
+ * Regular expression that matches a block tag name.
+ * @type {RegExp}
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.BLOCK_RE_ =
+    /^(DIV|TR|LI|BLOCKQUOTE|H\d|PRE|XMP)/;
+
+
+/**
+ * Appends a new line to a string buffer.
+ * @param {Array<string>} sb The string buffer to add to.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.appendNewline_ = function(sb) {
+  sb.push('<br>');
+};
+
+
+/**
+ * Create a new range delimited by the start point of the first range and
+ * the end point of the second range.
+ * @param {goog.dom.AbstractRange} startRange Use the start point of this
+ *    range as the beginning of the new range.
+ * @param {goog.dom.AbstractRange} endRange Use the end point of this
+ *    range as the end of the new range.
+ * @return {!goog.dom.AbstractRange} The new range.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.createRangeDelimitedByRanges_ = function(
+    startRange, endRange) {
+  return goog.dom.Range.createFromNodes(
+      startRange.getStartNode(), startRange.getStartOffset(),
+      endRange.getEndNode(), endRange.getEndOffset());
+};
+
+
+/** @override */
+goog.editor.plugins.RemoveFormatting.prototype.getTrogClassId = function() {
+  return 'RemoveFormatting';
+};
+
+
+/** @override */
+goog.editor.plugins.RemoveFormatting.prototype.isSupportedCommand = function(
+    command) {
+  return command ==
+      goog.editor.plugins.RemoveFormatting.REMOVE_FORMATTING_COMMAND;
+};
+
+
+/** @override */
+goog.editor.plugins.RemoveFormatting.prototype.execCommandInternal =
+    function(command, var_args) {
+  if (command ==
+      goog.editor.plugins.RemoveFormatting.REMOVE_FORMATTING_COMMAND) {
+    this.removeFormatting_();
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.RemoveFormatting.prototype.handleKeyboardShortcut =
+    function(e, key, isModifierPressed) {
+  if (!isModifierPressed) {
+    return false;
+  }
+
+  if (key == ' ') {
+    this.getFieldObject().execCommand(
+        goog.editor.plugins.RemoveFormatting.REMOVE_FORMATTING_COMMAND);
+    return true;
+  }
+
+  return false;
+};
+
+
+/**
+ * Removes formatting from the current selection.  Removes basic formatting
+ * (B/I/U) using the browser's execCommand.  Then extracts the html from the
+ * selection to convert, calls either a client's specified removeFormattingFunc
+ * callback or trogedit's general built-in removeFormattingWorker_,
+ * and then replaces the current selection with the converted text.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.removeFormatting_ = function() {
+  var range = this.getFieldObject().getRange();
+  if (range.isCollapsed()) {
+    return;
+  }
+
+  // Get the html to format and send it off for formatting. Built in
+  // removeFormat only strips some inline elements and some inline CSS styles
+  var convFunc = this.optRemoveFormattingFunc_ ||
+                 goog.bind(this.removeFormattingWorker_, this);
+  this.convertSelectedHtmlText_(convFunc);
+
+  // Do the execCommand last as it needs block elements removed to work
+  // properly on background/fontColor in FF. There are, unfortunately, still
+  // cases where background/fontColor are not removed here.
+  var doc = this.getFieldDomHelper().getDocument();
+  doc.execCommand('RemoveFormat', false, undefined);
+
+  if (goog.editor.BrowserFeature.ADDS_NBSPS_IN_REMOVE_FORMAT) {
+    // WebKit converts spaces to non-breaking spaces when doing a RemoveFormat.
+    // See: https://bugs.webkit.org/show_bug.cgi?id=14062
+    this.convertSelectedHtmlText_(function(text) {
+      // This loses anything that might have legitimately been a non-breaking
+      // space, but that's better than the alternative of only having non-
+      // breaking spaces.
+      // Old versions of WebKit (Safari 3, Chrome 1) incorrectly match /u00A0
+      // and newer versions properly match &nbsp;.
+      var nbspRegExp =
+          goog.userAgent.isVersionOrHigher('528') ? /&nbsp;/g : /\u00A0/g;
+      return text.replace(nbspRegExp, ' ');
+    });
+  }
+};
+
+
+/**
+ * Finds the nearest ancestor of the node that is a table.
+ * @param {Node} nodeToCheck Node to search from.
+ * @return {Node} The table, or null if one was not found.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.getTableAncestor_ = function(
+    nodeToCheck) {
+  var fieldElement = this.getFieldObject().getElement();
+  while (nodeToCheck && nodeToCheck != fieldElement) {
+    if (nodeToCheck.tagName == goog.dom.TagName.TABLE) {
+      return nodeToCheck;
+    }
+    nodeToCheck = nodeToCheck.parentNode;
+  }
+  return null;
+};
+
+
+/**
+ * Replaces the contents of the selection with html. Does its best to maintain
+ * the original selection. Also does its best to result in a valid DOM.
+ *
+ * TODO(user): See if there's any way to make this work on Ranges, and then
+ * move it into goog.editor.range. The Firefox implementation uses execCommand
+ * on the document, so must work on the actual selection.
+ *
+ * @param {string} html The html string to insert into the range.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.pasteHtml_ = function(html) {
+  var range = this.getFieldObject().getRange();
+
+  var dh = this.getFieldDomHelper();
+  // Use markers to set the extent of the selection so that we can reselect it
+  // afterwards. This works better than builtin range manipulation in FF and IE
+  // because their implementations are so self-inconsistent and buggy.
+  var startSpanId = goog.string.createUniqueString();
+  var endSpanId = goog.string.createUniqueString();
+  html = '<span id="' + startSpanId + '"></span>' + html +
+      '<span id="' + endSpanId + '"></span>';
+  var dummyNodeId = goog.string.createUniqueString();
+  var dummySpanText = '<span id="' + dummyNodeId + '"></span>';
+
+  if (goog.editor.BrowserFeature.HAS_IE_RANGES) {
+    // IE's selection often doesn't include the outermost tags.
+    // We want to use pasteHTML to replace the range contents with the newly
+    // unformatted text, so we have to check to make sure we aren't just
+    // pasting into some stray tags.  To do this, we first clear out the
+    // contents of the range and then delete all empty nodes parenting the now
+    // empty range. This way, the pasted contents are never re-embedded into
+    // formated nodes. Pasting purely empty html does not work, since IE moves
+    // the selection inside the next node, so we insert a dummy span.
+    var textRange = range.getTextRange(0).getBrowserRangeObject();
+    textRange.pasteHTML(dummySpanText);
+    var parent;
+    while ((parent = textRange.parentElement()) &&
+           goog.editor.node.isEmpty(parent) &&
+           !goog.editor.node.isEditableContainer(parent)) {
+      var tag = parent.nodeName;
+      // We can't remove these table tags as it will invalidate the table dom.
+      if (tag == goog.dom.TagName.TD ||
+          tag == goog.dom.TagName.TR ||
+          tag == goog.dom.TagName.TH) {
+        break;
+      }
+
+      goog.dom.removeNode(parent);
+    }
+    textRange.pasteHTML(html);
+    var dummySpan = dh.getElement(dummyNodeId);
+    // If we entered the while loop above, the node has already been removed
+    // since it was a child of parent and parent was removed.
+    if (dummySpan) {
+      goog.dom.removeNode(dummySpan);
+    }
+  } else if (goog.editor.BrowserFeature.HAS_W3C_RANGES) {
+    // insertHtml and range.insertNode don't merge blocks correctly.
+    // (e.g. if your selection spans two paragraphs)
+    dh.getDocument().execCommand('insertImage', false, dummyNodeId);
+    var dummyImageNodePattern = new RegExp('<[^<]*' + dummyNodeId + '[^>]*>');
+    var parent = this.getFieldObject().getRange().getContainerElement();
+    if (parent.nodeType == goog.dom.NodeType.TEXT) {
+      // Opera sometimes returns a text node here.
+      // TODO(user): perhaps we should modify getParentContainer?
+      parent = parent.parentNode;
+    }
+
+    // We have to search up the DOM because in some cases, notably when
+    // selecting li's within a list, execCommand('insertImage') actually splits
+    // tags in such a way that parent that used to contain the selection does
+    // not contain inserted image.
+    while (!dummyImageNodePattern.test(parent.innerHTML)) {
+      parent = parent.parentNode;
+    }
+
+    // Like the IE case above, sometimes the selection does not include the
+    // outermost tags.  For Gecko, we have already expanded the range so that
+    // it does, so we can just replace the dummy image with the final html.
+    // For WebKit, we use the same approach as we do with IE  - we
+    // inject a dummy span where we will eventually place the contents, and
+    // remove parentNodes of the span while they are empty.
+
+    if (goog.userAgent.GECKO) {
+      goog.editor.node.replaceInnerHtml(parent,
+          parent.innerHTML.replace(dummyImageNodePattern, html));
+    } else {
+      goog.editor.node.replaceInnerHtml(parent,
+          parent.innerHTML.replace(dummyImageNodePattern, dummySpanText));
+      var dummySpan = dh.getElement(dummyNodeId);
+      parent = dummySpan;
+      while ((parent = dummySpan.parentNode) &&
+             goog.editor.node.isEmpty(parent) &&
+             !goog.editor.node.isEditableContainer(parent)) {
+        var tag = parent.nodeName;
+        // We can't remove these table tags as it will invalidate the table dom.
+        if (tag == goog.dom.TagName.TD ||
+            tag == goog.dom.TagName.TR ||
+            tag == goog.dom.TagName.TH) {
+          break;
+        }
+
+        // We can't just remove parent since dummySpan is inside it, and we need
+        // to keep dummy span around for the replacement.  So we move the
+        // dummySpan up as we go.
+        goog.dom.insertSiblingAfter(dummySpan, parent);
+        goog.dom.removeNode(parent);
+      }
+      goog.editor.node.replaceInnerHtml(parent,
+          parent.innerHTML.replace(new RegExp(dummySpanText, 'i'), html));
+    }
+  }
+
+  var startSpan = dh.getElement(startSpanId);
+  var endSpan = dh.getElement(endSpanId);
+  goog.dom.Range.createFromNodes(startSpan, 0, endSpan,
+      endSpan.childNodes.length).select();
+  goog.dom.removeNode(startSpan);
+  goog.dom.removeNode(endSpan);
+};
+
+
+/**
+ * Gets the html inside the selection to send off for further processing.
+ *
+ * TODO(user): Make this general so that it can be moved into
+ * goog.editor.range.  The main reason it can't be moved is becuase we need to
+ * get the range before we do the execCommand and continue to operate on that
+ * same range (reasons are documented above).
+ *
+ * @param {goog.dom.AbstractRange} range The selection.
+ * @return {string} The html string to format.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.getHtmlText_ = function(range) {
+  var div = this.getFieldDomHelper().createDom(goog.dom.TagName.DIV);
+  var textRange = range.getBrowserRangeObject();
+
+  if (goog.editor.BrowserFeature.HAS_W3C_RANGES) {
+    // Get the text to convert.
+    div.appendChild(textRange.cloneContents());
+  } else if (goog.editor.BrowserFeature.HAS_IE_RANGES) {
+    // Trim the whitespace on the ends of the range, so that it the container
+    // will be the container of only the text content that we are changing.
+    // This gets around issues in IE where the spaces are included in the
+    // selection, but ignored sometimes by execCommand, and left orphaned.
+    var rngText = range.getText();
+
+    // BRs get reported as \r\n, but only count as one character for moves.
+    // Adjust the string so our move counter is correct.
+    rngText = rngText.replace(/\r\n/g, '\r');
+
+    var rngTextLength = rngText.length;
+    var left = rngTextLength - goog.string.trimLeft(rngText).length;
+    var right = rngTextLength - goog.string.trimRight(rngText).length;
+
+    textRange.moveStart('character', left);
+    textRange.moveEnd('character', -right);
+
+    var htmlText = textRange.htmlText;
+    // Check if in pretag and fix up formatting so that new lines are preserved.
+    if (textRange.queryCommandValue('formatBlock') == 'Formatted') {
+      htmlText = goog.string.newLineToBr(textRange.htmlText);
+    }
+    div.innerHTML = htmlText;
+  }
+
+  // Get the innerHTML of the node instead of just returning the text above
+  // so that its properly html escaped.
+  return div.innerHTML;
+};
+
+
+/**
+ * Move the range so that it doesn't include any partially selected tables.
+ * @param {goog.dom.AbstractRange} range The range to adjust.
+ * @param {Node} startInTable Table node that the range starts in.
+ * @param {Node} endInTable Table node that the range ends in.
+ * @return {!goog.dom.SavedCaretRange} Range to use to restore the
+ *     selection after we run our custom remove formatting.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.adjustRangeForTables_ =
+    function(range, startInTable, endInTable) {
+  // Create placeholders for the current selection so we can restore it
+  // later.
+  var savedCaretRange = goog.editor.range.saveUsingNormalizedCarets(range);
+
+  var startNode = range.getStartNode();
+  var startOffset = range.getStartOffset();
+  var endNode = range.getEndNode();
+  var endOffset = range.getEndOffset();
+  var dh = this.getFieldDomHelper();
+
+  // Move start after the table.
+  if (startInTable) {
+    var textNode = dh.createTextNode('');
+    goog.dom.insertSiblingAfter(textNode, startInTable);
+    startNode = textNode;
+    startOffset = 0;
+  }
+  // Move end before the table.
+  if (endInTable) {
+    var textNode = dh.createTextNode('');
+    goog.dom.insertSiblingBefore(textNode, endInTable);
+    endNode = textNode;
+    endOffset = 0;
+  }
+
+  goog.dom.Range.createFromNodes(startNode, startOffset,
+      endNode, endOffset).select();
+
+  return savedCaretRange;
+};
+
+
+/**
+ * Remove a caret from the dom and hide it in a safe place, so it can
+ * be restored later via restoreCaretsFromCave.
+ * @param {goog.dom.SavedCaretRange} caretRange The caret range to
+ *     get the carets from.
+ * @param {boolean} isStart Whether this is the start or end caret.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.putCaretInCave_ = function(
+    caretRange, isStart) {
+  var cavedCaret = goog.dom.removeNode(caretRange.getCaret(isStart));
+  if (isStart) {
+    this.startCaretInCave_ = cavedCaret;
+  } else {
+    this.endCaretInCave_ = cavedCaret;
+  }
+};
+
+
+/**
+ * Restore carets that were hidden away by adding them back into the dom.
+ * Note: this does not restore to the original dom location, as that
+ * will likely have been modified with remove formatting.  The only
+ * guarentees here are that start will still be before end, and that
+ * they will be in the editable region.  This should only be used when
+ * you don't actually intend to USE the caret again.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.restoreCaretsFromCave_ =
+    function() {
+  // To keep start before end, we put the end caret at the bottom of the field
+  // and the start caret at the start of the field.
+  var field = this.getFieldObject().getElement();
+  if (this.startCaretInCave_) {
+    field.insertBefore(this.startCaretInCave_, field.firstChild);
+    this.startCaretInCave_ = null;
+  }
+  if (this.endCaretInCave_) {
+    field.appendChild(this.endCaretInCave_);
+    this.endCaretInCave_ = null;
+  }
+};
+
+
+/**
+ * Gets the html inside the current selection, passes it through the given
+ * conversion function, and puts it back into the selection.
+ *
+ * @param {function(string): string} convertFunc A conversion function that
+ *    transforms an html string to new html string.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.convertSelectedHtmlText_ =
+    function(convertFunc) {
+  var range = this.getFieldObject().getRange();
+
+  // For multiple ranges, it is really hard to do our custom remove formatting
+  // without invalidating other ranges. So instead of always losing the
+  // content, this solution at least lets the browser do its own remove
+  // formatting which works correctly most of the time.
+  if (range.getTextRangeCount() > 1) {
+    return;
+  }
+
+  if (goog.userAgent.GECKO) {
+    // Determine if we need to handle tables, since they are special cases.
+    // If the selection is entirely within a table, there is no extra
+    // formatting removal we can do.  If a table is fully selected, we will
+    // just blow it away. If a table is only partially selected, we can
+    // perform custom remove formatting only on the non table parts, since we
+    // we can't just remove the parts and paste back into it (eg. we can't
+    // inject html where a TR used to be).
+    // If the selection contains the table and more, this is automatically
+    // handled, but if just the table is selected, it can be tricky to figure
+    // this case out, because of the numerous ways selections can be formed -
+    // ex. if a table has a single tr with a single td with a single text node
+    // in it, and the selection is (textNode: 0), (textNode: nextNode.length)
+    // then the entire table is selected, even though the start and end aren't
+    // the table itself. We are truly inside a table if the expanded endpoints
+    // are still inside the table.
+
+    // Expand the selection to include any outermost tags that weren't included
+    // in the selection, but have the same visible selection. Stop expanding
+    // if we reach the top level field.
+    var expandedRange = goog.editor.range.expand(range,
+        this.getFieldObject().getElement());
+
+    var startInTable = this.getTableAncestor_(expandedRange.getStartNode());
+    var endInTable = this.getTableAncestor_(expandedRange.getEndNode());
+
+    if (startInTable || endInTable) {
+      if (startInTable == endInTable) {
+        // We are fully contained in the same table, there is no extra
+        // remove formatting that we can do, just return and run browser
+        // formatting only.
+        return;
+      }
+
+      // Adjust the range to not contain any partially selected tables, since
+      // we don't want to run our custom remove formatting on them.
+      var savedCaretRange = this.adjustRangeForTables_(range,
+          startInTable, endInTable);
+
+      // Hack alert!!
+      // If start is not in a table, then the saved caret will get sent out
+      // for uber remove formatting, and it will get blown away.  This is
+      // fine, except that we need to be able to re-create a range from the
+      // savedCaretRange later on.  So, we just remove it from the dom, and
+      // put it back later so we can create a range later (not exactly in the
+      // same spot, but don't worry we don't actually try to use it later)
+      // and then it will be removed when we dispose the range.
+      if (!startInTable) {
+        this.putCaretInCave_(savedCaretRange, true);
+      }
+      if (!endInTable) {
+        this.putCaretInCave_(savedCaretRange, false);
+      }
+
+      // Re-fetch the range, and re-expand it, since we just modified it.
+      range = this.getFieldObject().getRange();
+      expandedRange = goog.editor.range.expand(range,
+          this.getFieldObject().getElement());
+    }
+
+    expandedRange.select();
+    range = expandedRange;
+  }
+
+  // Convert the selected text to the format-less version, paste back into
+  // the selection.
+  var text = this.getHtmlText_(range);
+  this.pasteHtml_(convertFunc(text));
+
+  if (goog.userAgent.GECKO && savedCaretRange) {
+    // If we moved the selection, move it back so the user can't tell we did
+    // anything crazy and so the browser removeFormat that we call next
+    // will operate on the entire originally selected range.
+    range = this.getFieldObject().getRange();
+    this.restoreCaretsFromCave_();
+    var realSavedCaretRange = savedCaretRange.toAbstractRange();
+    var startRange = startInTable ? realSavedCaretRange : range;
+    var endRange = endInTable ? realSavedCaretRange : range;
+    var restoredRange =
+        goog.editor.plugins.RemoveFormatting.createRangeDelimitedByRanges_(
+            startRange, endRange);
+    restoredRange.select();
+    savedCaretRange.dispose();
+  }
+};
+
+
+/**
+ * Does a best-effort attempt at clobbering all formatting that the
+ * browser's execCommand couldn't clobber without being totally inefficient.
+ * Attempts to convert visual line breaks to BRs. Leaves anchors that contain an
+ * href and images.
+ * Adapted from Gmail's MessageUtil's htmlToPlainText. http://go/messageutil.js
+ * @param {string} html The original html of the message.
+ * @return {string} The unformatted html, which is just text, br's, anchors and
+ *     images.
+ * @private
+ */
+goog.editor.plugins.RemoveFormatting.prototype.removeFormattingWorker_ =
+    function(html) {
+  var el = goog.dom.createElement(goog.dom.TagName.DIV);
+  el.innerHTML = html;
+
+  // Put everything into a string buffer to avoid lots of expensive string
+  // concatenation along the way.
+  var sb = [];
+  var stack = [el.childNodes, 0];
+
+  // Keep separate stacks for places where we need to keep track of
+  // how deeply embedded we are.  These are analogous to the general stack.
+  var preTagStack = [];
+  var preTagLevel = 0;  // Length of the prestack.
+  var tableStack = [];
+  var tableLevel = 0;
+
+  // sp = stack pointer, pointing to the stack array.
+  // decrement by 2 since the stack alternates node lists and
+  // processed node counts
+  for (var sp = 0; sp >= 0; sp -= 2) {
+    // Check if we should pop the table level.
+    var changedLevel = false;
+    while (tableLevel > 0 && sp <= tableStack[tableLevel - 1]) {
+      tableLevel--;
+      changedLevel = true;
+    }
+    if (changedLevel) {
+      goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+    }
+
+
+    // Check if we should pop the <pre>/<xmp> level.
+    changedLevel = false;
+    while (preTagLevel > 0 && sp <= preTagStack[preTagLevel - 1]) {
+      preTagLevel--;
+      changedLevel = true;
+    }
+    if (changedLevel) {
+      goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+    }
+
+    // The list of of nodes to process at the current stack level.
+    var nodeList = stack[sp];
+    // The number of nodes processed so far, stored in the stack immediately
+    // following the node list for that stack level.
+    var numNodesProcessed = stack[sp + 1];
+
+    while (numNodesProcessed < nodeList.length) {
+      var node = nodeList[numNodesProcessed++];
+      var nodeName = node.nodeName;
+
+      var formatted = this.getValueForNode(node);
+      if (goog.isDefAndNotNull(formatted)) {
+        sb.push(formatted);
+        continue;
+      }
+
+      // TODO(user): Handle case 'EMBED' and case 'OBJECT'.
+      switch (nodeName) {
+        case '#text':
+          // Note that IE does not preserve whitespace in the dom
+          // values, even in a pre tag, so this is useless for IE.
+          var nodeValue = preTagLevel > 0 ?
+              node.nodeValue :
+              goog.string.stripNewlines(node.nodeValue);
+          nodeValue = goog.string.htmlEscape(nodeValue);
+          sb.push(nodeValue);
+          continue;
+
+        case goog.dom.TagName.P:
+          goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          break;  // break (not continue) so that child nodes are processed.
+
+        case goog.dom.TagName.BR:
+          goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          continue;
+
+        case goog.dom.TagName.TABLE:
+          goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          tableStack[tableLevel++] = sp;
+          break;
+
+        case goog.dom.TagName.PRE:
+        case 'XMP':
+          // This doesn't fully handle xmp, since
+          // it doesn't actually ignore tags within the xmp tag.
+          preTagStack[preTagLevel++] = sp;
+          break;
+
+        case goog.dom.TagName.STYLE:
+        case goog.dom.TagName.SCRIPT:
+        case goog.dom.TagName.SELECT:
+          continue;
+
+        case goog.dom.TagName.A:
+          if (node.href && node.href != '') {
+            sb.push("<a href='");
+            sb.push(node.href);
+            sb.push("'>");
+            sb.push(this.removeFormattingWorker_(node.innerHTML));
+            sb.push('</a>');
+            continue; // Children taken care of.
+          } else {
+            break; // Take care of the children.
+          }
+
+        case goog.dom.TagName.IMG:
+          sb.push("<img src='");
+          sb.push(node.src);
+          sb.push("'");
+          // border=0 is a common way to not show a blue border around an image
+          // that is wrapped by a link. If we remove that, the blue border will
+          // show up, which to the user looks like adding format, not removing.
+          if (node.border == '0') {
+            sb.push(" border='0'");
+          }
+          sb.push('>');
+          continue;
+
+        case goog.dom.TagName.TD:
+          // Don't add a space for the first TD, we only want spaces to
+          // separate td's.
+          if (node.previousSibling) {
+            sb.push(' ');
+          }
+          break;
+
+        case goog.dom.TagName.TR:
+          // Don't add a newline for the first TR.
+          if (node.previousSibling) {
+            goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          }
+          break;
+
+        case goog.dom.TagName.DIV:
+          var parent = node.parentNode;
+          if (parent.firstChild == node &&
+              goog.editor.plugins.RemoveFormatting.BLOCK_RE_.test(
+                  parent.tagName)) {
+            // If a DIV is the first child of another element that itself is a
+            // block element, the DIV does not add a new line.
+            break;
+          }
+          // Otherwise, the DIV does add a new line.  Fall through.
+
+        default:
+          if (goog.editor.plugins.RemoveFormatting.BLOCK_RE_.test(nodeName)) {
+            goog.editor.plugins.RemoveFormatting.appendNewline_(sb);
+          }
+      }
+
+      // Recurse down the node.
+      var children = node.childNodes;
+      if (children.length > 0) {
+        // Push the current state on the stack.
+        stack[sp++] = nodeList;
+        stack[sp++] = numNodesProcessed;
+
+        // Iterate through the children nodes.
+        nodeList = children;
+        numNodesProcessed = 0;
+      }
+    }
+  }
+
+  // Replace &nbsp; with white space.
+  return goog.string.normalizeSpaces(sb.join(''));
+};
+
+
+/**
+ * Handle per node special processing if neccessary. If this function returns
+ * null then standard cleanup is applied. Otherwise this node and all children
+ * are assumed to be cleaned.
+ * NOTE(user): If an alternate RemoveFormatting processor is provided
+ * (setRemoveFormattingFunc()), this will no longer work.
+ * @param {Element} node The node to clean.
+ * @return {?string} The HTML strig representation of the cleaned data.
+ */
+goog.editor.plugins.RemoveFormatting.prototype.getValueForNode = function(
+    node) {
+  return null;
+};
+
+
+/**
+ * Sets a function to be used for remove formatting.
+ * @param {function(string): string} removeFormattingFunc - A function that
+ *     takes  a string of html and returns a string of html that does any other
+ *     formatting changes desired.  Use this only if trogedit's behavior doesn't
+ *     meet your needs.
+ */
+goog.editor.plugins.RemoveFormatting.prototype.setRemoveFormattingFunc =
+    function(removeFormattingFunc) {
+  this.optRemoveFormattingFunc_ = removeFormattingFunc;
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/spacestabhandler.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/spacestabhandler.js b/externs/GCL/externs/goog/editor/plugins/spacestabhandler.js
new file mode 100644
index 0000000..47fcf7a
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/spacestabhandler.js
@@ -0,0 +1,92 @@
+// 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 Editor plugin to handle tab keys not in lists to add 4 spaces.
+ *
+ * @author robbyw@google.com (Robby Walker)
+ */
+
+goog.provide('goog.editor.plugins.SpacesTabHandler');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.plugins.AbstractTabHandler');
+goog.require('goog.editor.range');
+
+
+
+/**
+ * Plugin to handle tab keys when not in lists to add 4 spaces.
+ * @constructor
+ * @extends {goog.editor.plugins.AbstractTabHandler}
+ * @final
+ */
+goog.editor.plugins.SpacesTabHandler = function() {
+  goog.editor.plugins.AbstractTabHandler.call(this);
+};
+goog.inherits(goog.editor.plugins.SpacesTabHandler,
+    goog.editor.plugins.AbstractTabHandler);
+
+
+/** @override */
+goog.editor.plugins.SpacesTabHandler.prototype.getTrogClassId = function() {
+  return 'SpacesTabHandler';
+};
+
+
+/** @override */
+goog.editor.plugins.SpacesTabHandler.prototype.handleTabKey = function(e) {
+  var dh = this.getFieldDomHelper();
+  var range = this.getFieldObject().getRange();
+  if (!goog.editor.range.intersectsTag(range, goog.dom.TagName.LI)) {
+    // In the shift + tab case we don't want to insert spaces, but we don't
+    // want focus to move either so skip the spacing logic and just prevent
+    // default.
+    if (!e.shiftKey) {
+      // Not in a list but we want to insert 4 spaces.
+
+      // Stop change events while we make multiple field changes.
+      this.getFieldObject().stopChangeEvents(true, true);
+
+      // Inserting nodes below completely messes up the selection, doing the
+      // deletion here before it's messed up. Only delete if text is selected,
+      // otherwise we would remove the character to the right of the cursor.
+      if (!range.isCollapsed()) {
+        dh.getDocument().execCommand('delete', false, null);
+        // Safari 3 has some DOM exceptions if we don't reget the range here,
+        // doing it all the time just to be safe.
+        range = this.getFieldObject().getRange();
+      }
+
+      // Emulate tab by removing selection and inserting 4 spaces
+      // Two breaking spaces in a row can be collapsed by the browser into one
+      // space. Inserting the string below because it is guaranteed to never
+      // collapse to less than four spaces, regardless of what is adjacent to
+      // the inserted spaces. This might make line wrapping slightly
+      // sub-optimal around a grouping of non-breaking spaces.
+      var elem = dh.createDom(goog.dom.TagName.SPAN, null,
+                              '\u00a0\u00a0 \u00a0');
+      elem = range.insertNode(elem, false);
+
+      this.getFieldObject().dispatchChange();
+      goog.editor.range.placeCursorNextTo(elem, false);
+      this.getFieldObject().dispatchSelectionChangeEvent();
+    }
+
+    e.preventDefault();
+    return true;
+  }
+
+  return false;
+};

http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/plugins/tableeditor.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/plugins/tableeditor.js b/externs/GCL/externs/goog/editor/plugins/tableeditor.js
new file mode 100644
index 0000000..be7cad0
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/plugins/tableeditor.js
@@ -0,0 +1,475 @@
+// 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 Plugin that enables table editing.
+ *
+ * @see ../../demos/editor/tableeditor.html
+ */
+
+goog.provide('goog.editor.plugins.TableEditor');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.Plugin');
+goog.require('goog.editor.Table');
+goog.require('goog.editor.node');
+goog.require('goog.editor.range');
+goog.require('goog.object');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Plugin that adds support for table creation and editing commands.
+ * @constructor
+ * @extends {goog.editor.Plugin}
+ * @final
+ */
+goog.editor.plugins.TableEditor = function() {
+  goog.editor.plugins.TableEditor.base(this, 'constructor');
+
+  /**
+   * The array of functions that decide whether a table element could be
+   * editable by the user or not.
+   * @type {Array<function(Element):boolean>}
+   * @private
+   */
+  this.isTableEditableFunctions_ = [];
+
+  /**
+   * The pre-bound function that decides whether a table element could be
+   * editable by the user or not overall.
+   * @type {function(Node):boolean}
+   * @private
+   */
+  this.isUserEditableTableBound_ = goog.bind(this.isUserEditableTable_, this);
+};
+goog.inherits(goog.editor.plugins.TableEditor, goog.editor.Plugin);
+
+
+/** @override */
+// TODO(user): remove this once there's a sensible default
+// implementation in the base Plugin.
+goog.editor.plugins.TableEditor.prototype.getTrogClassId = function() {
+  return String(goog.getUid(this.constructor));
+};
+
+
+/**
+ * Commands supported by goog.editor.plugins.TableEditor.
+ * @enum {string}
+ */
+goog.editor.plugins.TableEditor.COMMAND = {
+  TABLE: '+table',
+  INSERT_ROW_AFTER: '+insertRowAfter',
+  INSERT_ROW_BEFORE: '+insertRowBefore',
+  INSERT_COLUMN_AFTER: '+insertColumnAfter',
+  INSERT_COLUMN_BEFORE: '+insertColumnBefore',
+  REMOVE_ROWS: '+removeRows',
+  REMOVE_COLUMNS: '+removeColumns',
+  SPLIT_CELL: '+splitCell',
+  MERGE_CELLS: '+mergeCells',
+  REMOVE_TABLE: '+removeTable'
+};
+
+
+/**
+ * Inverse map of execCommand strings to
+ * {@link goog.editor.plugins.TableEditor.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.TableEditor.SUPPORTED_COMMANDS_ =
+    goog.object.transpose(goog.editor.plugins.TableEditor.COMMAND);
+
+
+/**
+ * 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.TableEditor.prototype.isSupportedCommand =
+    function(command) {
+  return command in goog.editor.plugins.TableEditor.SUPPORTED_COMMANDS_;
+};
+
+
+/** @override */
+goog.editor.plugins.TableEditor.prototype.enable = function(fieldObject) {
+  goog.editor.plugins.TableEditor.base(this, 'enable', fieldObject);
+
+  // enableObjectResizing is supported only for Gecko.
+  // You can refer to http://qooxdoo.org/contrib/project/htmlarea/html_editing
+  // for a compatibility chart.
+  if (goog.userAgent.GECKO) {
+    var doc = this.getFieldDomHelper().getDocument();
+    doc.execCommand('enableObjectResizing', false, 'true');
+  }
+};
+
+
+/**
+ * Returns the currently selected table.
+ * @return {Element?} The table in which the current selection is
+ *     contained, or null if there isn't such a table.
+ * @private
+ */
+goog.editor.plugins.TableEditor.prototype.getCurrentTable_ = function() {
+  var selectedElement = this.getFieldObject().getRange().getContainer();
+  return this.getAncestorTable_(selectedElement);
+};
+
+
+/**
+ * Finds the first user-editable table element in the input node's ancestors.
+ * @param {Node?} node The node to start with.
+ * @return {Element?} The table element that is closest ancestor of the node.
+ * @private
+ */
+goog.editor.plugins.TableEditor.prototype.getAncestorTable_ = function(node) {
+  var ancestor = goog.dom.getAncestor(node, this.isUserEditableTableBound_,
+      true);
+  if (goog.editor.node.isEditable(ancestor)) {
+    return /** @type {Element?} */(ancestor);
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * Returns the current value of a given command. Currently this plugin
+ * only returns a value for goog.editor.plugins.TableEditor.COMMAND.TABLE.
+ * @override
+ */
+goog.editor.plugins.TableEditor.prototype.queryCommandValue =
+    function(command) {
+  if (command == goog.editor.plugins.TableEditor.COMMAND.TABLE) {
+    return !!this.getCurrentTable_();
+  }
+};
+
+
+/** @override */
+goog.editor.plugins.TableEditor.prototype.execCommandInternal = function(
+    command, opt_arg) {
+  var result = null;
+  // TD/TH in which to place the cursor, if the command destroys the current
+  // cursor position.
+  var cursorCell = null;
+  var range = this.getFieldObject().getRange();
+  if (command == goog.editor.plugins.TableEditor.COMMAND.TABLE) {
+    // Don't create a table if the cursor isn't in an editable region.
+    if (!goog.editor.range.isEditable(range)) {
+      return null;
+    }
+    // Create the table.
+    var tableProps = opt_arg || {width: 4, height: 2};
+    var doc = this.getFieldDomHelper().getDocument();
+    var table = goog.editor.Table.createDomTable(
+        doc, tableProps.width, tableProps.height);
+    range.replaceContentsWithNode(table);
+    // In IE, replaceContentsWithNode uses pasteHTML, so we lose our reference
+    // to the inserted table.
+    // TODO(user): use the reference to the table element returned from
+    // replaceContentsWithNode.
+    if (!goog.userAgent.IE) {
+      cursorCell = table.getElementsByTagName(goog.dom.TagName.TD)[0];
+    }
+  } else {
+    var cellSelection = new goog.editor.plugins.TableEditor.CellSelection_(
+        range, goog.bind(this.getAncestorTable_, this));
+    var table = cellSelection.getTable();
+    if (!table) {
+      return null;
+    }
+    switch (command) {
+      case goog.editor.plugins.TableEditor.COMMAND.INSERT_ROW_BEFORE:
+        table.insertRow(cellSelection.getFirstRowIndex());
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.INSERT_ROW_AFTER:
+        table.insertRow(cellSelection.getLastRowIndex() + 1);
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.INSERT_COLUMN_BEFORE:
+        table.insertColumn(cellSelection.getFirstColumnIndex());
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.INSERT_COLUMN_AFTER:
+        table.insertColumn(cellSelection.getLastColumnIndex() + 1);
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.REMOVE_ROWS:
+        var startRow = cellSelection.getFirstRowIndex();
+        var endRow = cellSelection.getLastRowIndex();
+        if (startRow == 0 && endRow == (table.rows.length - 1)) {
+          // Instead of deleting all rows, delete the entire table.
+          return this.execCommandInternal(
+              goog.editor.plugins.TableEditor.COMMAND.REMOVE_TABLE);
+        }
+        var startColumn = cellSelection.getFirstColumnIndex();
+        var rowCount = (endRow - startRow) + 1;
+        for (var i = 0; i < rowCount; i++) {
+          table.removeRow(startRow);
+        }
+        if (table.rows.length > 0) {
+          // Place cursor in the previous/first row.
+          var closestRow = Math.min(startRow, table.rows.length - 1);
+          cursorCell = table.rows[closestRow].columns[startColumn].element;
+        }
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.REMOVE_COLUMNS:
+        var startCol = cellSelection.getFirstColumnIndex();
+        var endCol = cellSelection.getLastColumnIndex();
+        if (startCol == 0 && endCol == (table.rows[0].columns.length - 1)) {
+          // Instead of deleting all columns, delete the entire table.
+          return this.execCommandInternal(
+              goog.editor.plugins.TableEditor.COMMAND.REMOVE_TABLE);
+        }
+        var startRow = cellSelection.getFirstRowIndex();
+        var removeCount = (endCol - startCol) + 1;
+        for (var i = 0; i < removeCount; i++) {
+          table.removeColumn(startCol);
+        }
+        var currentRow = table.rows[startRow];
+        if (currentRow) {
+          // Place cursor in the previous/first column.
+          var closestCol = Math.min(startCol, currentRow.columns.length - 1);
+          cursorCell = currentRow.columns[closestCol].element;
+        }
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.MERGE_CELLS:
+        if (cellSelection.isRectangle()) {
+          table.mergeCells(cellSelection.getFirstRowIndex(),
+                           cellSelection.getFirstColumnIndex(),
+                           cellSelection.getLastRowIndex(),
+                           cellSelection.getLastColumnIndex());
+        }
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.SPLIT_CELL:
+        if (cellSelection.containsSingleCell()) {
+          table.splitCell(cellSelection.getFirstRowIndex(),
+                          cellSelection.getFirstColumnIndex());
+        }
+        break;
+      case goog.editor.plugins.TableEditor.COMMAND.REMOVE_TABLE:
+        table.element.parentNode.removeChild(table.element);
+        break;
+      default:
+    }
+  }
+  if (cursorCell) {
+    range = goog.dom.Range.createFromNodeContents(cursorCell);
+    range.collapse(false);
+    range.select();
+  }
+  return result;
+};
+
+
+/**
+ * Checks whether the element is a table editable by the user.
+ * @param {Node} element The element in question.
+ * @return {boolean} Whether the element is a table editable by the user.
+ * @private
+ */
+goog.editor.plugins.TableEditor.prototype.isUserEditableTable_ =
+    function(element) {
+  // Default implementation.
+  if (element.tagName != goog.dom.TagName.TABLE) {
+    return false;
+  }
+
+  // Check for extra user-editable filters.
+  return goog.array.every(this.isTableEditableFunctions_, function(func) {
+    return func(/** @type {Element} */ (element));
+  });
+};
+
+
+/**
+ * Adds a function to filter out non-user-editable tables.
+ * @param {function(Element):boolean} func A function to decide whether the
+ *   table element could be editable by the user or not.
+ */
+goog.editor.plugins.TableEditor.prototype.addIsTableEditableFunction =
+    function(func) {
+  goog.array.insert(this.isTableEditableFunctions_, func);
+};
+
+
+
+/**
+ * Class representing the selected cell objects within a single  table.
+ * @param {goog.dom.AbstractRange} range Selected range from which to calculate
+ *     selected cells.
+ * @param {function(Element):Element?} getParentTableFunction A function that
+ *     finds the user-editable table from a given element.
+ * @constructor
+ * @private
+ */
+goog.editor.plugins.TableEditor.CellSelection_ =
+    function(range, getParentTableFunction) {
+  this.cells_ = [];
+
+  // Mozilla lets users select groups of cells, with each cell showing
+  // up as a separate range in the selection. goog.dom.Range doesn't
+  // currently support this.
+  // TODO(user): support this case in range.js
+  var selectionContainer = range.getContainerElement();
+  var elementInSelection = function(node) {
+    // TODO(user): revert to the more liberal containsNode(node, true),
+    // which will match partially-selected cells. We're using
+    // containsNode(node, false) at the moment because otherwise it's
+    // broken in WebKit due to a closure range bug.
+    return selectionContainer == node ||
+        selectionContainer.parentNode == node ||
+        range.containsNode(node, false);
+  };
+
+  var parentTableElement = selectionContainer &&
+      getParentTableFunction(selectionContainer);
+  if (!parentTableElement) {
+    return;
+  }
+
+  var parentTable = new goog.editor.Table(parentTableElement);
+  // It's probably not possible to select a table with no cells, but
+  // do a sanity check anyway.
+  if (!parentTable.rows.length || !parentTable.rows[0].columns.length) {
+    return;
+  }
+  // Loop through cells to calculate dimensions for this CellSelection.
+  for (var i = 0, row; row = parentTable.rows[i]; i++) {
+    for (var j = 0, cell; cell = row.columns[j]; j++) {
+      if (elementInSelection(cell.element)) {
+        // Update dimensions based on cell.
+        if (!this.cells_.length) {
+          this.firstRowIndex_ = cell.startRow;
+          this.lastRowIndex_ = cell.endRow;
+          this.firstColIndex_ = cell.startCol;
+          this.lastColIndex_ = cell.endCol;
+        } else {
+          this.firstRowIndex_ = Math.min(this.firstRowIndex_, cell.startRow);
+          this.lastRowIndex_ = Math.max(this.lastRowIndex_, cell.endRow);
+          this.firstColIndex_ = Math.min(this.firstColIndex_, cell.startCol);
+          this.lastColIndex_ = Math.max(this.lastColIndex_, cell.endCol);
+        }
+        this.cells_.push(cell);
+      }
+    }
+  }
+  this.parentTable_ = parentTable;
+};
+
+
+/**
+ * Returns the EditableTable object of which this selection's cells are a
+ * subset.
+ * @return {!goog.editor.Table} the table.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getTable =
+    function() {
+  return this.parentTable_;
+};
+
+
+/**
+ * Returns the row index of the uppermost cell in this selection.
+ * @return {number} The row index.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getFirstRowIndex =
+    function() {
+  return this.firstRowIndex_;
+};
+
+
+/**
+ * Returns the row index of the lowermost cell in this selection.
+ * @return {number} The row index.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getLastRowIndex =
+    function() {
+  return this.lastRowIndex_;
+};
+
+
+/**
+ * Returns the column index of the farthest left cell in this selection.
+ * @return {number} The column index.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getFirstColumnIndex =
+    function() {
+  return this.firstColIndex_;
+};
+
+
+/**
+ * Returns the column index of the farthest right cell in this selection.
+ * @return {number} The column index.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getLastColumnIndex =
+    function() {
+  return this.lastColIndex_;
+};
+
+
+/**
+ * Returns the cells in this selection.
+ * @return {!Array<Element>} Cells in this selection.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.getCells = function() {
+  return this.cells_;
+};
+
+
+/**
+ * Returns a boolean value indicating whether or not the cells in this
+ * selection form a rectangle.
+ * @return {boolean} Whether the selection forms a rectangle.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.isRectangle =
+    function() {
+  // TODO(user): check for missing cells. Right now this returns
+  // whether all cells in the selection are in the rectangle, but doesn't
+  // verify that every expected cell is present.
+  if (!this.cells_.length) {
+    return false;
+  }
+  var firstCell = this.cells_[0];
+  var lastCell = this.cells_[this.cells_.length - 1];
+  return !(this.firstRowIndex_ < firstCell.startRow ||
+           this.lastRowIndex_ > lastCell.endRow ||
+           this.firstColIndex_ < firstCell.startCol ||
+           this.lastColIndex_ > lastCell.endCol);
+};
+
+
+/**
+ * Returns a boolean value indicating whether or not there is exactly
+ * one cell in this selection. Note that this may not be the same as checking
+ * whether getCells().length == 1; if there is a single cell with
+ * rowSpan/colSpan set it will appear multiple times.
+ * @return {boolean} Whether there is exatly one cell in this selection.
+ */
+goog.editor.plugins.TableEditor.CellSelection_.prototype.containsSingleCell =
+    function() {
+  var cellCount = this.cells_.length;
+  return cellCount > 0 &&
+      (this.cells_[0] == this.cells_[cellCount - 1]);
+};


Mime
View raw message