text: // 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 Abstract API for TrogEdit plugins.
   *
   * @see ../demos/editor/editor.html
   */
  
  goog.provide('goog.editor.Plugin');
  
  goog.require('goog.debug.Logger');
  // TODO(user): Remove the dependency on goog.editor.Command asap. Currently only
  // needed for execCommand issues with links.
  goog.require('goog.editor.Command');
  goog.require('goog.events.EventTarget');
  goog.require('goog.functions');
  goog.require('goog.object');
  goog.require('goog.reflect');
  
  
  
  /**
   * Abstract API for trogedit plugins.
   * @constructor
   * @extends {goog.events.EventTarget}
   */
  goog.editor.Plugin = function() {
    goog.events.EventTarget.call(this);
  
    /**
     * Whether this plugin is enabled for the registered field object.
     * @type {boolean}
     * @private
     */
    this.enabled_ = this.activeOnUneditableFields();
  };
  goog.inherits(goog.editor.Plugin, goog.events.EventTarget);
  
  
  /**
   * The field object this plugin is attached to.
   * @type {goog.editor.Field}
   * @protected
   */
  goog.editor.Plugin.prototype.fieldObject = null;
  
  
  /**
   * @return {goog.dom.DomHelper?} The dom helper object associated with the
   *     currently active field.
   */
  goog.editor.Plugin.prototype.getFieldDomHelper = function() {
    return this.fieldObject && this.fieldObject.getEditableDomHelper();
  };
  
  
  /**
   * Indicates if this plugin should be automatically disposed when the
   * registered field is disposed. This should be changed to false for
   * plugins used as multi-field plugins.
   * @type {boolean}
   * @private
   */
  goog.editor.Plugin.prototype.autoDispose_ = true;
  
  
  /**
   * The logger for this plugin.
   * @type {goog.debug.Logger}
   * @protected
   */
  goog.editor.Plugin.prototype.logger =
      goog.debug.Logger.getLogger('goog.editor.Plugin');
  
  
  /**
   * Registers the field object for use with this plugin.
   * @param {goog.editor.Field} fieldObject The editable field object.
   */
  goog.editor.Plugin.prototype.registerFieldObject = function(fieldObject) {
    this.fieldObject = fieldObject;
  };
  
  
  /**
   * Unregisters and disables this plugin for the current field object.
   * @param {goog.editor.Field} fieldObj The field object. For single-field
   *     plugins, this parameter is ignored.
   */
  goog.editor.Plugin.prototype.unregisterFieldObject = function(fieldObj) {
    if (this.fieldObject) {
      this.disable(this.fieldObject);
      this.fieldObject = null;
    }
  };
  
  
  /**
   * Enables this plugin for the specified, registered field object. A field
   * object should only be enabled when it is loaded.
   * @param {goog.editor.Field} fieldObject The field object.
   */
  goog.editor.Plugin.prototype.enable = function(fieldObject) {
    if (this.fieldObject == fieldObject) {
      this.enabled_ = true;
    } else {
      this.logger.severe('Trying to enable an unregistered field with ' +
          'this plugin.');
    }
  };
  
  
  /**
   * Disables this plugin for the specified, registered field object.
   * @param {goog.editor.Field} fieldObject The field object.
   */
  goog.editor.Plugin.prototype.disable = function(fieldObject) {
    if (this.fieldObject == fieldObject) {
      this.enabled_ = false;
    } else {
      this.logger.severe('Trying to disable an unregistered field ' +
          'with this plugin.');
    }
  };
  
  
  /**
   * Returns whether this plugin is enabled for the field object.
   *
   * @param {goog.editor.Field} fieldObject The field object.
   * @return {boolean} Whether this plugin is enabled for the field object.
   */
  goog.editor.Plugin.prototype.isEnabled = function(fieldObject) {
    return this.fieldObject == fieldObject ? this.enabled_ : false;
  };
  
  
  /**
   * Set if this plugin should automatically be disposed when the registered
   * field is disposed.
   * @param {boolean} autoDispose Whether to autoDispose.
   */
  goog.editor.Plugin.prototype.setAutoDispose = function(autoDispose) {
    this.autoDispose_ = autoDispose;
  };
  
  
  /**
   * @return {boolean} Whether or not this plugin should automatically be disposed
   *     when it's registered field is disposed.
   */
  goog.editor.Plugin.prototype.isAutoDispose = function() {
    return this.autoDispose_;
  };
  
  
  /**
   * @return {boolean} If true, field will not disable the command
   *     when the field becomes uneditable.
   */
  goog.editor.Plugin.prototype.activeOnUneditableFields = goog.functions.FALSE;
  
  
  /**
   * @param {string} command The command to check.
   * @return {boolean} If true, field will not dispatch change events
   *     for commands of this type. This is useful for "seamless" plugins like
   *     dialogs and lorem ipsum.
   */
  goog.editor.Plugin.prototype.isSilentCommand = goog.functions.FALSE;
  
  
  /** @override */
  goog.editor.Plugin.prototype.disposeInternal = function() {
    if (this.fieldObject) {
      this.unregisterFieldObject(this.fieldObject);
    }
  
    goog.editor.Plugin.superClass_.disposeInternal.call(this);
  };
  
  
  /**
   * @return {string} The ID unique to this plugin class. Note that different
   *     instances off the plugin share the same classId.
   */
  goog.editor.Plugin.prototype.getTrogClassId;
  
  
  /**
   * An enum of operations that plugins may support.
   * @enum {number}
   */
  goog.editor.Plugin.Op = {
    KEYDOWN: 1,
    KEYPRESS: 2,
    KEYUP: 3,
    SELECTION: 4,
    SHORTCUT: 5,
    EXEC_COMMAND: 6,
    QUERY_COMMAND: 7,
    PREPARE_CONTENTS_HTML: 8,
    CLEAN_CONTENTS_HTML: 10,
    CLEAN_CONTENTS_DOM: 11
  };
  
  
  /**
   * A map from plugin operations to the names of the methods that
   * invoke those operations.
   */
  goog.editor.Plugin.OPCODE = goog.object.transpose(
      goog.reflect.object(goog.editor.Plugin, {
        handleKeyDown: goog.editor.Plugin.Op.KEYDOWN,
        handleKeyPress: goog.editor.Plugin.Op.KEYPRESS,
        handleKeyUp: goog.editor.Plugin.Op.KEYUP,
        handleSelectionChange: goog.editor.Plugin.Op.SELECTION,
        handleKeyboardShortcut: goog.editor.Plugin.Op.SHORTCUT,
        execCommand: goog.editor.Plugin.Op.EXEC_COMMAND,
        queryCommandValue: goog.editor.Plugin.Op.QUERY_COMMAND,
        prepareContentsHtml: goog.editor.Plugin.Op.PREPARE_CONTENTS_HTML,
        cleanContentsHtml: goog.editor.Plugin.Op.CLEAN_CONTENTS_HTML,
        cleanContentsDom: goog.editor.Plugin.Op.CLEAN_CONTENTS_DOM
      }));
  
  
  /**
   * A set of op codes that run even on disabled plugins.
   */
  goog.editor.Plugin.IRREPRESSIBLE_OPS = goog.object.createSet(
      goog.editor.Plugin.Op.PREPARE_CONTENTS_HTML,
      goog.editor.Plugin.Op.CLEAN_CONTENTS_HTML,
      goog.editor.Plugin.Op.CLEAN_CONTENTS_DOM);
  
  
  /**
   * Handles keydown. It is run before handleKeyboardShortcut and if it returns
   * true handleKeyboardShortcut will not be called.
   * @param {!goog.events.BrowserEvent} e The browser event.
   * @return {boolean} Whether the event was handled and thus should *not* be
   *     propagated to other plugins or handleKeyboardShortcut.
   */
  goog.editor.Plugin.prototype.handleKeyDown;
  
  
  /**
   * Handles keypress. It is run before handleKeyboardShortcut and if it returns
   * true handleKeyboardShortcut will not be called.
   * @param {!goog.events.BrowserEvent} e The browser event.
   * @return {boolean} Whether the event was handled and thus should *not* be
   *     propagated to other plugins or handleKeyboardShortcut.
   */
  goog.editor.Plugin.prototype.handleKeyPress;
  
  
  /**
   * Handles keyup.
   * @param {!goog.events.BrowserEvent} e The browser event.
   * @return {boolean} Whether the event was handled and thus should *not* be
   *     propagated to other plugins.
   */
  goog.editor.Plugin.prototype.handleKeyUp;
  
  
  /**
   * Handles selection change.
   * @param {!goog.events.BrowserEvent=} opt_e The browser event.
   * @param {!Node=} opt_target The node the selection changed to.
   * @return {boolean} Whether the event was handled and thus should *not* be
   *     propagated to other plugins.
   */
  goog.editor.Plugin.prototype.handleSelectionChange;
  
  
  /**
   * Handles keyboard shortcuts.  Preferred to using handleKey* as it will use
   * the proper event based on browser and will be more performant. If
   * handleKeyPress/handleKeyDown returns true, this will not be called. If the
   * plugin handles the shortcut, it is responsible for dispatching appropriate
   * events (change, selection change at the time of this comment). If the plugin
   * calls execCommand on the editable field, then execCommand already takes care
   * of dispatching events.
   * NOTE: For performance reasons this is only called when any key is pressed
   * in conjunction with ctrl/meta keys OR when a small subset of keys (defined
   * in goog.editor.Field.POTENTIAL_SHORTCUT_KEYCODES_) are pressed without
   * ctrl/meta keys. We specifically don't invoke it when altKey is pressed since
   * alt key is used in many i8n UIs to enter certain characters.
   * @param {!goog.events.BrowserEvent} e The browser event.
   * @param {string} key The key pressed.
   * @param {boolean} isModifierPressed Whether the ctrl/meta key was pressed or
   *     not.
   * @return {boolean} Whether the event was handled and thus should *not* be
   *     propagated to other plugins. We also call preventDefault on the event if
   *     the return value is true.
   */
  goog.editor.Plugin.prototype.handleKeyboardShortcut;
  
  
  /**
   * Handles execCommand. This default implementation handles dispatching
   * BEFORECHANGE, CHANGE, and SELECTIONCHANGE events, and calls
   * execCommandInternal to perform the actual command. Plugins that want to
   * do their own event dispatching should override execCommand, otherwise
   * it is preferred to only override execCommandInternal.
   *
   * This version of execCommand will only work for single field plugins.
   * Multi-field plugins must override execCommand.
   *
   * @param {string} command The command to execute.
   * @param {...*} var_args Any additional parameters needed to
   *     execute the command.
   * @return {*} The result of the execCommand, if any.
   */
  goog.editor.Plugin.prototype.execCommand = function(command, var_args) {
    // TODO(user): Replace all uses of isSilentCommand with plugins that just
    // override this base execCommand method.
    var silent = this.isSilentCommand(command);
    if (!silent) {
      // Stop listening to mutation events in Firefox while text formatting
      // is happening.  This prevents us from trying to size the field in the
      // middle of an execCommand, catching the field in a strange intermediary
      // state where both replacement nodes and original nodes are appended to
      // the dom.  Note that change events get turned back on by
      // fieldObj.dispatchChange.
      if (goog.userAgent.GECKO) {
        this.fieldObject.stopChangeEvents(true, true);
      }
  
      this.fieldObject.dispatchBeforeChange();
    }
  
    try {
      var result = this.execCommandInternal.apply(this, arguments);
    } finally {
      // If the above execCommandInternal call throws an exception, we still need
      // to turn change events back on (see http://b/issue?id=1471355).
      // NOTE: If if you add to or change the methods called in this finally
      // block, please add them as expected calls to the unit test function
      // testExecCommandException().
      if (!silent) {
        // dispatchChange includes a call to startChangeEvents, which unwinds the
        // call to stopChangeEvents made before the try block.
        this.fieldObject.dispatchChange();
        // TODO(user): Remove this conditional.  We should always dispatch a
        // a selection change event here, but link creation is wierd.  See
        // TR_Editor.prototype.execute for more details
        if (command != goog.editor.Command.LINK) {
          this.fieldObject.dispatchSelectionChangeEvent();
        }
      }
    }
  
    return result;
  };
  
  
  /**
   * Handles execCommand. This default implementation does nothing, and is
   * called by execCommand, which handles event dispatching. This method should
   * be overriden by plugins that don't need to do their own event dispatching.
   * If custom event dispatching is needed, execCommand shoul be overriden
   * instead.
   *
   * @param {string} command The command to execute.
   * @param {...*} var_args Any additional parameters needed to
   *     execute the command.
   * @return {*} The result of the execCommand, if any.
   * @protected
   */
  goog.editor.Plugin.prototype.execCommandInternal;
  
  
  /**
   * Gets the state of this command if this plugin serves that command.
   * @param {string} command The command to check.
   * @return {*} The value of the command.
   */
  goog.editor.Plugin.prototype.queryCommandValue;
  
  
  /**
   * Prepares the given HTML for editing. Strips out content that should not
   * appear in an editor, and normalizes content as appropriate. The inverse
   * of cleanContentsHtml.
   *
   * This op is invoked even on disabled plugins.
   *
   * @param {string} originalHtml The original HTML.
   * @param {Object} styles A map of strings. If the plugin wants to add
   *     any styles to the field element, it should add them as key-value
   *     pairs to this object.
   * @return {string} New HTML that's ok for editing.
   */
  goog.editor.Plugin.prototype.prepareContentsHtml;
  
  
  /**
   * Cleans the contents of the node passed to it. The node contents are modified
   * directly, and the modifications will subsequently be used, for operations
   * such as saving the innerHTML of the editor etc. Since the plugins act on
   * the DOM directly, this method can be very expensive.
   *
   * This op is invoked even on disabled plugins.
   *
   * @param {!Element} fieldCopy The copy of the editable field which
   *     needs to be cleaned up.
   */
  goog.editor.Plugin.prototype.cleanContentsDom;
  
  
  /**
   * Cleans the html contents of Trogedit. Both cleanContentsDom and
   * and cleanContentsHtml will be called on contents extracted from Trogedit.
   * The inverse of prepareContentsHtml.
   *
   * This op is invoked even on disabled plugins.
   *
   * @param {string} originalHtml The trogedit HTML.
   * @return {string} Cleaned-up HTML.
   */
  goog.editor.Plugin.prototype.cleanContentsHtml;
  
  
  /**
   * Whether the string corresponds to a command this plugin handles.
   * @param {string} command Command string to check.
   * @return {boolean} Whether the plugin handles this type of command.
   */
  goog.editor.Plugin.prototype.isSupportedCommand = function(command) {
    return false;
  };
  