goog.editor.plugins.basictextformatter.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of google-closure-library
Show all versions of google-closure-library
The Google Closure Library is a collection of JavaScript code
designed for use with the Google Closure JavaScript Compiler.
This non-official distribution was prepared by the ClojureScript
team at http://clojure.org/
// Copyright 2006 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 Functions to style text.
*
* @author [email protected] (Nick Santos)
*/
goog.provide('goog.editor.plugins.BasicTextFormatter');
goog.provide('goog.editor.plugins.BasicTextFormatter.COMMAND');
goog.require('goog.array');
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.Command');
goog.require('goog.editor.Link');
goog.require('goog.editor.Plugin');
goog.require('goog.editor.node');
goog.require('goog.editor.range');
goog.require('goog.editor.style');
goog.require('goog.iter');
goog.require('goog.iter.StopIteration');
goog.require('goog.log');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.string.Unicode');
goog.require('goog.style');
goog.require('goog.ui.editor.messages');
goog.require('goog.userAgent');
/**
* Functions to style text (e.g. underline, make bold, etc.)
* @constructor
* @extends {goog.editor.Plugin}
*/
goog.editor.plugins.BasicTextFormatter = function() {
goog.editor.Plugin.call(this);
};
goog.inherits(goog.editor.plugins.BasicTextFormatter, goog.editor.Plugin);
/** @override */
goog.editor.plugins.BasicTextFormatter.prototype.getTrogClassId = function() {
return 'BTF';
};
/**
* Logging object.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.editor.plugins.BasicTextFormatter.prototype.logger =
goog.log.getLogger('goog.editor.plugins.BasicTextFormatter');
/**
* Commands implemented by this plugin.
* @enum {string}
*/
goog.editor.plugins.BasicTextFormatter.COMMAND = {
LINK: '+link',
CREATE_LINK: '+createLink',
FORMAT_BLOCK: '+formatBlock',
INDENT: '+indent',
OUTDENT: '+outdent',
STRIKE_THROUGH: '+strikeThrough',
HORIZONTAL_RULE: '+insertHorizontalRule',
SUBSCRIPT: '+subscript',
SUPERSCRIPT: '+superscript',
UNDERLINE: '+underline',
BOLD: '+bold',
ITALIC: '+italic',
FONT_SIZE: '+fontSize',
FONT_FACE: '+fontName',
FONT_COLOR: '+foreColor',
BACKGROUND_COLOR: '+backColor',
ORDERED_LIST: '+insertOrderedList',
UNORDERED_LIST: '+insertUnorderedList',
JUSTIFY_CENTER: '+justifyCenter',
JUSTIFY_FULL: '+justifyFull',
JUSTIFY_RIGHT: '+justifyRight',
JUSTIFY_LEFT: '+justifyLeft'
};
/**
* Inverse map of execCommand strings to
* {@link goog.editor.plugins.BasicTextFormatter.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.BasicTextFormatter.SUPPORTED_COMMANDS_ =
goog.object.transpose(goog.editor.plugins.BasicTextFormatter.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.BasicTextFormatter.prototype.isSupportedCommand = function(
command) {
// TODO(user): restore this to simple check once table editing
// is moved out into its own plugin
return command in goog.editor.plugins.BasicTextFormatter.SUPPORTED_COMMANDS_;
};
/**
* Array of execCommand strings which should be silent.
* @type {!Array}
* @private
*/
goog.editor.plugins.BasicTextFormatter.SILENT_COMMANDS_ =
[goog.editor.plugins.BasicTextFormatter.COMMAND.CREATE_LINK];
/**
* Whether the string corresponds to a command that should be silent.
* @override
*/
goog.editor.plugins.BasicTextFormatter.prototype.isSilentCommand = function(
command) {
return goog.array.contains(
goog.editor.plugins.BasicTextFormatter.SILENT_COMMANDS_, command);
};
/**
* @return {goog.dom.AbstractRange} The closure range object that wraps the
* current user selection.
* @private
*/
goog.editor.plugins.BasicTextFormatter.prototype.getRange_ = function() {
return this.getFieldObject().getRange();
};
/**
* @return {!Document} The document object associated with the currently active
* field.
* @private
*/
goog.editor.plugins.BasicTextFormatter.prototype.getDocument_ = function() {
return this.getFieldDomHelper().getDocument();
};
/**
* Execute a user-initiated command.
* @param {string} command Command to execute.
* @param {...*} var_args For color commands, this
* should be the hex color (with the #). For FORMAT_BLOCK, this should be
* the goog.editor.plugins.BasicTextFormatter.BLOCK_COMMAND.
* It will be unused for other commands.
* @return {Object|undefined} The result of the command.
* @override
*/
goog.editor.plugins.BasicTextFormatter.prototype.execCommandInternal = function(
command, var_args) {
var preserveDir, styleWithCss, needsFormatBlockDiv, hasDummySelection;
var result;
var opt_arg = arguments[1];
switch (command) {
case goog.editor.plugins.BasicTextFormatter.COMMAND.BACKGROUND_COLOR:
// Don't bother for no color selected, color picker is resetting itself.
if (!goog.isNull(opt_arg)) {
if (goog.editor.BrowserFeature.EATS_EMPTY_BACKGROUND_COLOR) {
this.applyBgColorManually_(opt_arg);
} else if (goog.userAgent.OPERA) {
// backColor will color the block level element instead of
// the selected span of text in Opera.
this.execCommandHelper_('hiliteColor', opt_arg);
} else {
this.execCommandHelper_(command, opt_arg);
}
}
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.CREATE_LINK:
result = this.createLink_(arguments[1], arguments[2], arguments[3]);
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.LINK:
result = this.toggleLink_(opt_arg);
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT:
this.justify_(command);
break;
default:
if (goog.userAgent.IE &&
command ==
goog.editor.plugins.BasicTextFormatter.COMMAND.FORMAT_BLOCK &&
opt_arg) {
// IE requires that the argument be in the form of an opening
// tag, like , including angle brackets. WebKit will accept
// the arguemnt with or without brackets, and Firefox pre-3 supports
// only a fixed subset of tags with brackets, and prefers without.
// So we only add them IE only.
opt_arg = '<' + opt_arg + '>';
}
if (command ==
goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_COLOR &&
goog.isNull(opt_arg)) {
// If we don't have a color, then FONT_COLOR is a no-op.
break;
}
switch (command) {
case goog.editor.plugins.BasicTextFormatter.COMMAND.INDENT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.OUTDENT:
if (goog.editor.BrowserFeature.HAS_STYLE_WITH_CSS) {
if (goog.userAgent.GECKO) {
styleWithCss = true;
}
if (goog.userAgent.OPERA) {
if (command ==
goog.editor.plugins.BasicTextFormatter.COMMAND.OUTDENT) {
// styleWithCSS actually sets negative margins on
// to outdent them. If the command is enabled without
// styleWithCSS flipped on, then the caret is in a blockquote so
// styleWithCSS must not be used. But if the command is not
// enabled, styleWithCSS should be used so that elements such as
// a with a margin-left style can still be outdented.
// (Opera bug: CORE-21118)
styleWithCss =
!this.getDocument_().queryCommandEnabled('outdent');
} else {
// Always use styleWithCSS for indenting. Otherwise, Opera will
// make separate s around *each* indented line,
// which adds big default margins between each
// indented line.
styleWithCss = true;
}
}
}
// Fall through.
case goog.editor.plugins.BasicTextFormatter.COMMAND.ORDERED_LIST:
case goog.editor.plugins.BasicTextFormatter.COMMAND.UNORDERED_LIST:
if (goog.editor.BrowserFeature.LEAVES_P_WHEN_REMOVING_LISTS &&
this.queryCommandStateInternal_(this.getDocument_(), command)) {
// IE leaves behind P tags when unapplying lists.
// If we're not in P-mode, then we want divs
// So, unlistify, then convert the Ps into divs.
needsFormatBlockDiv =
this.getFieldObject().queryCommandValue(
goog.editor.Command.DEFAULT_TAG) != goog.dom.TagName.P;
} else if (!goog.editor.BrowserFeature.CAN_LISTIFY_BR) {
// IE doesn't convert BRed line breaks into separate list items.
// So convert the BRs to divs, then do the listify.
this.convertBreaksToDivs_();
}
// This fix only works in Gecko.
if (goog.userAgent.GECKO &&
goog.editor.BrowserFeature.FORGETS_FORMATTING_WHEN_LISTIFYING &&
!this.queryCommandValue(command)) {
hasDummySelection |= this.beforeInsertListGecko_();
}
// Fall through to preserveDir block
case goog.editor.plugins.BasicTextFormatter.COMMAND.FORMAT_BLOCK:
// Both FF & IE may lose directionality info. Save/restore it.
// TODO(user): Does Safari also need this?
// TODO (gmark, jparent): This isn't ideal because it uses a string
// literal, so if the plugin name changes, it would break. We need a
// better solution. See also other places in code that use
// this.getPluginByClassId('Bidi').
preserveDir = !!this.getFieldObject().getPluginByClassId('Bidi');
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT:
if (goog.editor.BrowserFeature.NESTS_SUBSCRIPT_SUPERSCRIPT) {
// This browser nests subscript and superscript when both are
// applied, instead of canceling out the first when applying the
// second.
this.applySubscriptSuperscriptWorkarounds_(command);
}
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.UNDERLINE:
case goog.editor.plugins.BasicTextFormatter.COMMAND.BOLD:
case goog.editor.plugins.BasicTextFormatter.COMMAND.ITALIC:
// If we are applying the formatting, then we want to have
// styleWithCSS false so that we generate html tags (like ). If we
// are unformatting something, we want to have styleWithCSS true so
// that we can unformat both html tags and inline styling.
// TODO(user): What about WebKit and Opera?
styleWithCss = goog.userAgent.GECKO &&
goog.editor.BrowserFeature.HAS_STYLE_WITH_CSS &&
this.queryCommandValue(command);
break;
case goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_COLOR:
case goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_FACE:
// It is very expensive in FF (order of magnitude difference) to use
// font tags instead of styled spans. Whenever possible,
// force FF to use spans.
// Font size is very expensive too, but FF always uses font tags,
// regardless of which styleWithCSS value you use.
styleWithCss = goog.editor.BrowserFeature.HAS_STYLE_WITH_CSS &&
goog.userAgent.GECKO;
}
/**
* Cases where we just use the default execCommand (in addition
* to the above fall-throughs)
* goog.editor.plugins.BasicTextFormatter.COMMAND.STRIKE_THROUGH:
* goog.editor.plugins.BasicTextFormatter.COMMAND.HORIZONTAL_RULE:
* goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT:
* goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT:
* goog.editor.plugins.BasicTextFormatter.COMMAND.UNDERLINE:
* goog.editor.plugins.BasicTextFormatter.COMMAND.BOLD:
* goog.editor.plugins.BasicTextFormatter.COMMAND.ITALIC:
* goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE:
* goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_FACE:
*/
this.execCommandHelper_(command, opt_arg, preserveDir, !!styleWithCss);
if (hasDummySelection) {
this.getDocument_().execCommand('Delete', false, true);
}
if (needsFormatBlockDiv) {
this.getDocument_().execCommand('FormatBlock', false, '');
}
}
// FF loses focus, so we have to set the focus back to the document or the
// user can't type after selecting from menu. In IE, focus is set correctly
// and resetting it here messes it up.
if (goog.userAgent.GECKO && !this.getFieldObject().inModalMode()) {
this.focusField_();
}
return result;
};
/**
* Focuses on the field.
* @private
*/
goog.editor.plugins.BasicTextFormatter.prototype.focusField_ = function() {
this.getFieldDomHelper().getWindow().focus();
};
/**
* Gets the command value.
* @param {string} command The command value to get.
* @return {string|boolean|null} The current value of the command in the given
* selection. NOTE: This return type list is not documented in MSDN or MDC
* and has been constructed from experience. Please update it
* if necessary.
* @override
*/
goog.editor.plugins.BasicTextFormatter.prototype.queryCommandValue = function(
command) {
var styleWithCss;
switch (command) {
case goog.editor.plugins.BasicTextFormatter.COMMAND.LINK:
return this.isNodeInState_(goog.dom.TagName.A);
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_CENTER:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_FULL:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_RIGHT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.JUSTIFY_LEFT:
return this.isJustification_(command);
case goog.editor.plugins.BasicTextFormatter.COMMAND.FORMAT_BLOCK:
// TODO(nicksantos): See if we can use queryCommandValue here.
return goog.editor.plugins.BasicTextFormatter.getSelectionBlockState_(
this.getFieldObject().getRange());
case goog.editor.plugins.BasicTextFormatter.COMMAND.INDENT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.OUTDENT:
case goog.editor.plugins.BasicTextFormatter.COMMAND.HORIZONTAL_RULE:
// TODO: See if there are reasonable results to return for
// these commands.
return false;
case goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_SIZE:
case goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_FACE:
case goog.editor.plugins.BasicTextFormatter.COMMAND.FONT_COLOR:
case goog.editor.plugins.BasicTextFormatter.COMMAND.BACKGROUND_COLOR:
// We use queryCommandValue here since we don't just want to know if a
// color/fontface/fontsize is applied, we want to know WHICH one it is.
return this.queryCommandValueInternal_(
this.getDocument_(), command,
goog.editor.BrowserFeature.HAS_STYLE_WITH_CSS &&
goog.userAgent.GECKO);
case goog.editor.plugins.BasicTextFormatter.COMMAND.UNDERLINE:
case goog.editor.plugins.BasicTextFormatter.COMMAND.BOLD:
case goog.editor.plugins.BasicTextFormatter.COMMAND.ITALIC:
styleWithCss =
goog.editor.BrowserFeature.HAS_STYLE_WITH_CSS && goog.userAgent.GECKO;
default:
/**
* goog.editor.plugins.BasicTextFormatter.COMMAND.STRIKE_THROUGH
* goog.editor.plugins.BasicTextFormatter.COMMAND.SUBSCRIPT
* goog.editor.plugins.BasicTextFormatter.COMMAND.SUPERSCRIPT
* goog.editor.plugins.BasicTextFormatter.COMMAND.UNDERLINE
* goog.editor.plugins.BasicTextFormatter.COMMAND.BOLD
* goog.editor.plugins.BasicTextFormatter.COMMAND.ITALIC
* goog.editor.plugins.BasicTextFormatter.COMMAND.ORDERED_LIST
* goog.editor.plugins.BasicTextFormatter.COMMAND.UNORDERED_LIST
*/
// This only works for commands that use the default execCommand
return this.queryCommandStateInternal_(
this.getDocument_(), command, styleWithCss);
}
};
/**
* @override
*/
goog.editor.plugins.BasicTextFormatter.prototype.prepareContentsHtml = function(
html) {
// If the browser collapses empty nodes and the field has only a script
// tag in it, then it will collapse this node. Which will mean the user
// can't click into it to edit it.
if (goog.editor.BrowserFeature.COLLAPSES_EMPTY_NODES &&
html.match(/^\s*