[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/editor/atto/yui/src/rangy/js/ -> rangy-cssclassapplier.js (source)

   1  /**

   2   * @license CSS Class Applier module for Rangy.

   3   * Adds, removes and toggles CSS classes on Ranges and Selections

   4   *

   5   * Part of Rangy, a cross-browser JavaScript range and selection library

   6   * http://code.google.com/p/rangy/

   7   *

   8   * Depends on Rangy core.

   9   *

  10   * Copyright 2012, Tim Down

  11   * Licensed under the MIT license.

  12   * Version: 1.2.3

  13   * Build date: 26 February 2012

  14   */
  15  rangy.createModule("CssClassApplier", function(api, module) {
  16      api.requireModules( ["WrappedSelection", "WrappedRange"] );
  17  
  18      var dom = api.dom;
  19  
  20  
  21  
  22      var defaultTagName = "span";
  23  
  24      function trim(str) {
  25          return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
  26      }
  27  
  28      function hasClass(el, cssClass) {
  29          return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
  30      }
  31  
  32      function addClass(el, cssClass) {
  33          if (el.className) {
  34              if (!hasClass(el, cssClass)) {
  35                  el.className += " " + cssClass;
  36              }
  37          } else {
  38              el.className = cssClass;
  39          }
  40      }
  41  
  42      var removeClass = (function() {
  43          function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
  44              return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
  45          }
  46  
  47          return function(el, cssClass) {
  48              if (el.className) {
  49                  el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer);
  50              }
  51          };
  52      })();
  53  
  54      function sortClassName(className) {
  55          return className.split(/\s+/).sort().join(" ");
  56      }
  57  
  58      function getSortedClassName(el) {
  59          return sortClassName(el.className);
  60      }
  61  
  62      function haveSameClasses(el1, el2) {
  63          return getSortedClassName(el1) == getSortedClassName(el2);
  64      }
  65  
  66      function replaceWithOwnChildren(el) {
  67  
  68          var parent = el.parentNode;
  69          while (el.hasChildNodes()) {
  70              parent.insertBefore(el.firstChild, el);
  71          }
  72          parent.removeChild(el);
  73      }
  74  
  75      function rangeSelectsAnyText(range, textNode) {
  76          var textRange = range.cloneRange();
  77          textRange.selectNodeContents(textNode);
  78  
  79          var intersectionRange = textRange.intersection(range);
  80          var text = intersectionRange ? intersectionRange.toString() : "";
  81          textRange.detach();
  82  
  83          return text != "";
  84      }
  85  
  86      function getEffectiveTextNodes(range) {
  87          return range.getNodes([3], function(textNode) {
  88              return rangeSelectsAnyText(range, textNode);
  89          });
  90      }
  91  
  92      function elementsHaveSameNonClassAttributes(el1, el2) {
  93          if (el1.attributes.length != el2.attributes.length) return false;
  94          for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
  95              attr1 = el1.attributes[i];
  96              name = attr1.name;
  97              if (name != "class") {
  98                  attr2 = el2.attributes.getNamedItem(name);
  99                  if (attr1.specified != attr2.specified) return false;
 100                  if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
 101              }
 102          }
 103          return true;
 104      }
 105  
 106      function elementHasNonClassAttributes(el, exceptions) {
 107          for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
 108              attrName = el.attributes[i].name;
 109              if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
 110                  return true;
 111              }
 112          }
 113          return false;
 114      }
 115  
 116      function elementHasProps(el, props) {
 117          for (var p in props) {
 118              if (props.hasOwnProperty(p) && el[p] !== props[p]) {
 119                  return false;
 120              }
 121          }
 122          return true;
 123      }
 124  
 125      var getComputedStyleProperty;
 126  
 127      if (typeof window.getComputedStyle != "undefined") {
 128          getComputedStyleProperty = function(el, propName) {
 129              return dom.getWindow(el).getComputedStyle(el, null)[propName];
 130          };
 131      } else if (typeof document.documentElement.currentStyle != "undefined") {
 132          getComputedStyleProperty = function(el, propName) {
 133              return el.currentStyle[propName];
 134          };
 135      } else {
 136          module.fail("No means of obtaining computed style properties found");
 137      }
 138  
 139      var isEditableElement;
 140  
 141      (function() {
 142          var testEl = document.createElement("div");
 143          if (typeof testEl.isContentEditable == "boolean") {
 144              isEditableElement = function(node) {
 145                  return node && node.nodeType == 1 && node.isContentEditable;
 146              };
 147          } else {
 148              isEditableElement = function(node) {
 149                  if (!node || node.nodeType != 1 || node.contentEditable == "false") {
 150                      return false;
 151                  }
 152                  return node.contentEditable == "true" || isEditableElement(node.parentNode);
 153              };
 154          }
 155      })();
 156  
 157      function isEditingHost(node) {
 158          var parent;
 159          return node && node.nodeType == 1
 160              && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
 161              || (isEditableElement(node) && !isEditableElement(node.parentNode)));
 162      }
 163  
 164      function isEditable(node) {
 165          return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
 166      }
 167  
 168      var inlineDisplayRegex = /^inline(-block|-table)?$/i;
 169  
 170      function isNonInlineElement(node) {
 171          return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
 172      }
 173  
 174      // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)

 175      var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
 176  
 177      function isUnrenderedWhiteSpaceNode(node) {
 178          if (node.data.length == 0) {
 179              return true;
 180          }
 181          if (htmlNonWhiteSpaceRegex.test(node.data)) {
 182              return false;
 183          }
 184          var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
 185          switch (cssWhiteSpace) {
 186              case "pre":
 187              case "pre-wrap":
 188              case "-moz-pre-wrap":
 189                  return false;
 190              case "pre-line":
 191                  if (/[\r\n]/.test(node.data)) {
 192                      return false;
 193                  }
 194          }
 195  
 196          // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a

 197          // non-inline element, it will not be rendered. This seems to be a good enough definition.

 198          return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
 199      }
 200  
 201      function isSplitPoint(node, offset) {
 202          if (dom.isCharacterDataNode(node)) {
 203              if (offset == 0) {
 204                  return !!node.previousSibling;
 205              } else if (offset == node.length) {
 206                  return !!node.nextSibling;
 207              } else {
 208                  return true;
 209              }
 210          }
 211  
 212          return offset > 0 && offset < node.childNodes.length;
 213      }
 214  
 215      function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) {
 216          var newNode;
 217          var splitAtStart = (descendantOffset == 0);
 218  
 219          if (dom.isAncestorOf(descendantNode, node)) {
 220  
 221              return node;
 222          }
 223  
 224          if (dom.isCharacterDataNode(descendantNode)) {
 225              if (descendantOffset == 0) {
 226                  descendantOffset = dom.getNodeIndex(descendantNode);
 227                  descendantNode = descendantNode.parentNode;
 228              } else if (descendantOffset == descendantNode.length) {
 229                  descendantOffset = dom.getNodeIndex(descendantNode) + 1;
 230                  descendantNode = descendantNode.parentNode;
 231              } else {
 232                  throw module.createError("splitNodeAt should not be called with offset in the middle of a data node ("
 233                      + descendantOffset + " in " + descendantNode.data);
 234              }
 235          }
 236  
 237          if (isSplitPoint(descendantNode, descendantOffset)) {
 238              if (!newNode) {
 239                  newNode = descendantNode.cloneNode(false);
 240                  if (newNode.id) {
 241                      newNode.removeAttribute("id");
 242                  }
 243                  var child;
 244                  while ((child = descendantNode.childNodes[descendantOffset])) {
 245                      newNode.appendChild(child);
 246                  }
 247                  dom.insertAfter(newNode, descendantNode);
 248              }
 249              return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve);
 250          } else if (node != descendantNode) {
 251              newNode = descendantNode.parentNode;
 252  
 253              // Work out a new split point in the parent node

 254              var newNodeIndex = dom.getNodeIndex(descendantNode);
 255  
 256              if (!splitAtStart) {
 257                  newNodeIndex++;
 258              }
 259              return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve);
 260          }
 261          return node;
 262      }
 263  
 264      function areElementsMergeable(el1, el2) {
 265          return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2);
 266      }
 267  
 268      function createAdjacentMergeableTextNodeGetter(forward) {
 269          var propName = forward ? "nextSibling" : "previousSibling";
 270  
 271          return function(textNode, checkParentElement) {
 272              var el = textNode.parentNode;
 273              var adjacentNode = textNode[propName];
 274              if (adjacentNode) {
 275                  // Can merge if the node's previous/next sibling is a text node

 276                  if (adjacentNode && adjacentNode.nodeType == 3) {
 277                      return adjacentNode;
 278                  }
 279              } else if (checkParentElement) {
 280                  // Compare text node parent element with its sibling

 281                  adjacentNode = el[propName];
 282  
 283                  if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
 284                      return adjacentNode[forward ? "firstChild" : "lastChild"];
 285                  }
 286              }
 287              return null;
 288          }
 289      }
 290  
 291      var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
 292          getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
 293  
 294  
 295      function Merge(firstNode) {
 296          this.isElementMerge = (firstNode.nodeType == 1);
 297          this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
 298          this.textNodes = [this.firstTextNode];
 299      }
 300  
 301      Merge.prototype = {
 302          doMerge: function() {
 303              var textBits = [], textNode, parent, text;
 304              for (var i = 0, len = this.textNodes.length; i < len; ++i) {
 305                  textNode = this.textNodes[i];
 306                  parent = textNode.parentNode;
 307                  textBits[i] = textNode.data;
 308                  if (i) {
 309                      parent.removeChild(textNode);
 310                      if (!parent.hasChildNodes()) {
 311                          parent.parentNode.removeChild(parent);
 312                      }
 313                  }
 314              }
 315              this.firstTextNode.data = text = textBits.join("");
 316              return text;
 317          },
 318  
 319          getLength: function() {
 320              var i = this.textNodes.length, len = 0;
 321              while (i--) {
 322                  len += this.textNodes[i].length;
 323              }
 324              return len;
 325          },
 326  
 327          toString: function() {
 328              var textBits = [];
 329              for (var i = 0, len = this.textNodes.length; i < len; ++i) {
 330                  textBits[i] = "'" + this.textNodes[i].data + "'";
 331              }
 332              return "[Merge(" + textBits.join(",") + ")]";
 333          }
 334      };
 335  
 336      var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"];
 337  
 338      // Allow "class" as a property name in object properties

 339      var mappedPropertyNames = {"class" : "className"};
 340  
 341      function CssClassApplier(cssClass, options, tagNames) {
 342          this.cssClass = cssClass;
 343          var normalize, i, len, propName;
 344  
 345          var elementPropertiesFromOptions = null;
 346  
 347          // Initialize from options object

 348          if (typeof options == "object" && options !== null) {
 349              tagNames = options.tagNames;
 350              elementPropertiesFromOptions = options.elementProperties;
 351  
 352              for (i = 0; propName = optionProperties[i++]; ) {
 353                  if (options.hasOwnProperty(propName)) {
 354                      this[propName] = options[propName];
 355                  }
 356              }
 357              normalize = options.normalize;
 358          } else {
 359              normalize = options;
 360          }
 361  
 362          // Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization

 363          this.normalize = (typeof normalize == "undefined") ? true : normalize;
 364  
 365          // Initialize element properties and attribute exceptions

 366          this.attrExceptions = [];
 367          var el = document.createElement(this.elementTagName);
 368          this.elementProperties = {};
 369          for (var p in elementPropertiesFromOptions) {
 370              if (elementPropertiesFromOptions.hasOwnProperty(p)) {
 371                  // Map "class" to "className"

 372                  if (mappedPropertyNames.hasOwnProperty(p)) {
 373                      p = mappedPropertyNames[p];
 374                  }
 375                  el[p] = elementPropertiesFromOptions[p];
 376  
 377                  // Copy the property back from the dummy element so that later comparisons to check whether elements

 378                  // may be removed are checking against the right value. For example, the href property of an element

 379                  // returns a fully qualified URL even if it was previously assigned a relative URL.

 380                  this.elementProperties[p] = el[p];
 381                  this.attrExceptions.push(p);
 382              }
 383          }
 384  
 385          this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
 386              sortClassName(this.elementProperties.className + " " + cssClass) : cssClass;
 387  
 388          // Initialize tag names

 389          this.applyToAnyTagName = false;
 390          var type = typeof tagNames;
 391          if (type == "string") {
 392              if (tagNames == "*") {
 393                  this.applyToAnyTagName = true;
 394              } else {
 395                  this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
 396              }
 397          } else if (type == "object" && typeof tagNames.length == "number") {
 398              this.tagNames = [];
 399              for (i = 0, len = tagNames.length; i < len; ++i) {
 400                  if (tagNames[i] == "*") {
 401                      this.applyToAnyTagName = true;
 402                  } else {
 403                      this.tagNames.push(tagNames[i].toLowerCase());
 404                  }
 405              }
 406          } else {
 407              this.tagNames = [this.elementTagName];
 408          }
 409      }
 410  
 411      CssClassApplier.prototype = {
 412          elementTagName: defaultTagName,
 413          elementProperties: {},
 414          ignoreWhiteSpace: true,
 415          applyToEditableOnly: false,
 416  
 417          hasClass: function(node) {
 418              return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass);
 419          },
 420  
 421          getSelfOrAncestorWithClass: function(node) {
 422              while (node) {
 423                  if (this.hasClass(node, this.cssClass)) {
 424                      return node;
 425                  }
 426                  node = node.parentNode;
 427              }
 428              return null;
 429          },
 430  
 431          isModifiable: function(node) {
 432              return !this.applyToEditableOnly || isEditable(node);
 433          },
 434  
 435          // White space adjacent to an unwrappable node can be ignored for wrapping

 436          isIgnorableWhiteSpaceNode: function(node) {
 437              return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
 438          },
 439  
 440          // Normalizes nodes after applying a CSS class to a Range.

 441          postApply: function(textNodes, range, isUndo) {
 442  
 443              var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
 444  
 445              var merges = [], currentMerge;
 446  
 447              var rangeStartNode = firstNode, rangeEndNode = lastNode;
 448              var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
 449  
 450              var textNode, precedingTextNode;
 451  
 452              for (var i = 0, len = textNodes.length; i < len; ++i) {
 453                  textNode = textNodes[i];
 454                  precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
 455  
 456                  if (precedingTextNode) {
 457                      if (!currentMerge) {
 458                          currentMerge = new Merge(precedingTextNode);
 459                          merges.push(currentMerge);
 460                      }
 461                      currentMerge.textNodes.push(textNode);
 462                      if (textNode === firstNode) {
 463                          rangeStartNode = currentMerge.firstTextNode;
 464                          rangeStartOffset = rangeStartNode.length;
 465                      }
 466                      if (textNode === lastNode) {
 467                          rangeEndNode = currentMerge.firstTextNode;
 468                          rangeEndOffset = currentMerge.getLength();
 469                      }
 470                  } else {
 471                      currentMerge = null;
 472                  }
 473              }
 474  
 475              // Test whether the first node after the range needs merging

 476              var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
 477  
 478              if (nextTextNode) {
 479                  if (!currentMerge) {
 480                      currentMerge = new Merge(lastNode);
 481                      merges.push(currentMerge);
 482                  }
 483                  currentMerge.textNodes.push(nextTextNode);
 484              }
 485  
 486              // Do the merges

 487              if (merges.length) {
 488  
 489                  for (i = 0, len = merges.length; i < len; ++i) {
 490                      merges[i].doMerge();
 491                  }
 492  
 493  
 494                  // Set the range boundaries

 495                  range.setStart(rangeStartNode, rangeStartOffset);
 496                  range.setEnd(rangeEndNode, rangeEndOffset);
 497              }
 498  
 499          },
 500  
 501          createContainer: function(doc) {
 502              var el = doc.createElement(this.elementTagName);
 503              api.util.extend(el, this.elementProperties);
 504              addClass(el, this.cssClass);
 505              return el;
 506          },
 507  
 508          applyToTextNode: function(textNode) {
 509  
 510  
 511              var parent = textNode.parentNode;
 512              if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
 513                  addClass(parent, this.cssClass);
 514              } else {
 515                  var el = this.createContainer(dom.getDocument(textNode));
 516                  textNode.parentNode.insertBefore(el, textNode);
 517                  el.appendChild(textNode);
 518              }
 519  
 520          },
 521  
 522          isRemovable: function(el) {
 523              return el.tagName.toLowerCase() == this.elementTagName
 524                      && getSortedClassName(el) == this.elementSortedClassName
 525                      && elementHasProps(el, this.elementProperties)
 526                      && !elementHasNonClassAttributes(el, this.attrExceptions)
 527                      && this.isModifiable(el);
 528          },
 529  
 530          undoToTextNode: function(textNode, range, ancestorWithClass) {
 531  
 532              if (!range.containsNode(ancestorWithClass)) {
 533                  // Split out the portion of the ancestor from which we can remove the CSS class

 534                  //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);

 535                  var ancestorRange = range.cloneRange();
 536                  ancestorRange.selectNode(ancestorWithClass);
 537  
 538                  if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) {
 539                      splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]);
 540                      range.setEndAfter(ancestorWithClass);
 541                  }
 542                  if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) {
 543                      ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]);
 544                  }
 545              }
 546  
 547              if (this.isRemovable(ancestorWithClass)) {
 548                  replaceWithOwnChildren(ancestorWithClass);
 549              } else {
 550                  removeClass(ancestorWithClass, this.cssClass);
 551              }
 552          },
 553  
 554          applyToRange: function(range) {
 555              range.splitBoundaries();
 556              var textNodes = getEffectiveTextNodes(range);
 557  
 558              if (textNodes.length) {
 559                  var textNode;
 560  
 561                  for (var i = 0, len = textNodes.length; i < len; ++i) {
 562                      textNode = textNodes[i];
 563  
 564                      if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
 565                              && this.isModifiable(textNode)) {
 566                          this.applyToTextNode(textNode);
 567                      }
 568                  }
 569                  range.setStart(textNodes[0], 0);
 570                  textNode = textNodes[textNodes.length - 1];
 571                  range.setEnd(textNode, textNode.length);
 572                  if (this.normalize) {
 573                      this.postApply(textNodes, range, false);
 574                  }
 575              }
 576          },
 577  
 578          applyToSelection: function(win) {
 579  
 580              win = win || window;
 581              var sel = api.getSelection(win);
 582  
 583              var range, ranges = sel.getAllRanges();
 584              sel.removeAllRanges();
 585              var i = ranges.length;
 586              while (i--) {
 587                  range = ranges[i];
 588                  this.applyToRange(range);
 589                  sel.addRange(range);
 590              }
 591  
 592          },
 593  
 594          undoToRange: function(range) {
 595  
 596              range.splitBoundaries();
 597              var textNodes = getEffectiveTextNodes(range);
 598              var textNode, ancestorWithClass;
 599              var lastTextNode = textNodes[textNodes.length - 1];
 600  
 601              if (textNodes.length) {
 602                  for (var i = 0, len = textNodes.length; i < len; ++i) {
 603                      textNode = textNodes[i];
 604                      ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
 605                      if (ancestorWithClass && this.isModifiable(textNode)) {
 606                          this.undoToTextNode(textNode, range, ancestorWithClass);
 607                      }
 608  
 609                      // Ensure the range is still valid

 610                      range.setStart(textNodes[0], 0);
 611                      range.setEnd(lastTextNode, lastTextNode.length);
 612                  }
 613  
 614  
 615  
 616                  if (this.normalize) {
 617                      this.postApply(textNodes, range, true);
 618                  }
 619              }
 620          },
 621  
 622          undoToSelection: function(win) {
 623              win = win || window;
 624              var sel = api.getSelection(win);
 625              var ranges = sel.getAllRanges(), range;
 626              sel.removeAllRanges();
 627              for (var i = 0, len = ranges.length; i < len; ++i) {
 628                  range = ranges[i];
 629                  this.undoToRange(range);
 630                  sel.addRange(range);
 631              }
 632          },
 633  
 634          getTextSelectedByRange: function(textNode, range) {
 635              var textRange = range.cloneRange();
 636              textRange.selectNodeContents(textNode);
 637  
 638              var intersectionRange = textRange.intersection(range);
 639              var text = intersectionRange ? intersectionRange.toString() : "";
 640              textRange.detach();
 641  
 642              return text;
 643          },
 644  
 645          isAppliedToRange: function(range) {
 646              if (range.collapsed) {
 647                  return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
 648              } else {
 649                  var textNodes = range.getNodes( [3] );
 650                  for (var i = 0, textNode; textNode = textNodes[i++]; ) {
 651                      if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
 652                              && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
 653                          return false;
 654                      }
 655                  }
 656                  return true;
 657              }
 658          },
 659  
 660          isAppliedToSelection: function(win) {
 661              win = win || window;
 662              var sel = api.getSelection(win);
 663              var ranges = sel.getAllRanges();
 664              var i = ranges.length;
 665              while (i--) {
 666                  if (!this.isAppliedToRange(ranges[i])) {
 667                      return false;
 668                  }
 669              }
 670  
 671              return true;
 672          },
 673  
 674          toggleRange: function(range) {
 675              if (this.isAppliedToRange(range)) {
 676                  this.undoToRange(range);
 677              } else {
 678                  this.applyToRange(range);
 679              }
 680          },
 681  
 682          toggleSelection: function(win) {
 683              if (this.isAppliedToSelection(win)) {
 684                  this.undoToSelection(win);
 685              } else {
 686                  this.applyToSelection(win);
 687              }
 688          },
 689  
 690          detach: function() {}
 691      };
 692  
 693      function createCssClassApplier(cssClass, options, tagNames) {
 694          return new CssClassApplier(cssClass, options, tagNames);
 695      }
 696  
 697      CssClassApplier.util = {
 698          hasClass: hasClass,
 699          addClass: addClass,
 700          removeClass: removeClass,
 701          hasSameClasses: haveSameClasses,
 702          replaceWithOwnChildren: replaceWithOwnChildren,
 703          elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
 704          elementHasNonClassAttributes: elementHasNonClassAttributes,
 705          splitNodeAt: splitNodeAt,
 706          isEditableElement: isEditableElement,
 707          isEditingHost: isEditingHost,
 708          isEditable: isEditable
 709      };
 710  
 711      api.CssClassApplier = CssClassApplier;
 712      api.createCssClassApplier = createCssClassApplier;
 713  });


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1