All Downloads are FREE. Search and download functionalities are using the official Maven repository.

goog.editor.plugins.basictextformatter.js Maven / Gradle / Ivy

Go to download

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/

There is a newer version: 0.0-20230227-c7c0a541
Show newest version
// 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*