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 [email protected] (Nick Santos) + * @author [email protected] (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 [email protected] (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 [email protected] (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 . + var nbspRegExp = + goog.userAgent.isVersionOrHigher('528') ? / /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 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 [email protected] (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]); +};
