464 lines
15 KiB
JavaScript
464 lines
15 KiB
JavaScript
// 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');
|
|
|
|
// 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.log');
|
|
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
|
|
* @deprecated Use goog.editor.Plugin.getFieldObject and
|
|
* goog.editor.Plugin.setFieldObject.
|
|
*/
|
|
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.getFieldObject() && this.getFieldObject().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.log.Logger}
|
|
* @protected
|
|
*/
|
|
goog.editor.Plugin.prototype.logger =
|
|
goog.log.getLogger('goog.editor.Plugin');
|
|
|
|
|
|
/**
|
|
* Sets the field object for use with this plugin.
|
|
* @return {goog.editor.Field} The editable field object.
|
|
* @protected
|
|
* @suppress {deprecated} Until fieldObject can be made private.
|
|
*/
|
|
goog.editor.Plugin.prototype.getFieldObject = function() {
|
|
return this.fieldObject;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the field object for use with this plugin.
|
|
* @param {goog.editor.Field} fieldObject The editable field object.
|
|
* @protected
|
|
* @suppress {deprecated} Until fieldObject can be made private.
|
|
*/
|
|
goog.editor.Plugin.prototype.setFieldObject = function(fieldObject) {
|
|
this.fieldObject = fieldObject;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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.setFieldObject(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.getFieldObject()) {
|
|
this.disable(this.getFieldObject());
|
|
this.setFieldObject(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.getFieldObject() == fieldObject) {
|
|
this.enabled_ = true;
|
|
} else {
|
|
goog.log.error(this.logger, '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.getFieldObject() == fieldObject) {
|
|
this.enabled_ = false;
|
|
} else {
|
|
goog.log.error(this.logger, '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.getFieldObject() == 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.getFieldObject()) {
|
|
this.unregisterFieldObject(this.getFieldObject());
|
|
}
|
|
|
|
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.getFieldObject().stopChangeEvents(true, true);
|
|
}
|
|
|
|
this.getFieldObject().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.getFieldObject().dispatchChange();
|
|
this.getFieldObject().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;
|
|
};
|