Вход Регистрация
Файл: library/wysihtml5/src/selection/html_applier.js
Строк: 636
<?php
/**
 * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
 * http://code.google.com/p/rangy/
 *
 * changed in order to be able ...
 *    - to use custom tags
 *    - to detect and replace similar css classes via reg exp
 */
(function(wysihtml5rangy) {
  var 
defaultTagName "span";
  
  var 
REG_EXP_WHITE_SPACE = /s+/g;
  
  function 
hasClass(elcssClassregExp) {
    if (!
el.className) {
      return 
false;
    }
    
    var 
matchingClassNames el.className.match(regExp) || [];
    return 
matchingClassNames[matchingClassNames.length 1] === cssClass;
  }

  function 
addClass(elcssClassregExp) {
    if (
el.className) {
      
removeClass(elregExp);
      
el.className += " " cssClass;
    } else {
      
el.className cssClass;
    }
  }

  function 
removeClass(elregExp) {
    if (
el.className) {
      
el.className el.className.replace(regExp"");
    }
  }
  
  function 
hasSameClasses(el1el2) {
    return 
el1.className.replace(REG_EXP_WHITE_SPACE" ") == el2.className.replace(REG_EXP_WHITE_SPACE" ");
  }

  function 
replaceWithOwnChildren(el) {
    var 
parent el.parentNode;
    while (
el.firstChild) {
      
parent.insertBefore(el.firstChildel);
    }
    
parent.removeChild(el);
  }

  function 
elementsHaveSameNonClassAttributes(el1el2) {
    if (
el1.attributes.length != el2.attributes.length) {
      return 
false;
    }
    for (var 
0len el1.attributes.lengthattr1attr2namelen; ++i) {
      
attr1 el1.attributes[i];
      
name attr1.name;
      if (
name != "class") {
        
attr2 el2.attributes.getNamedItem(name);
        if (
attr1.specified != attr2.specified) {
          return 
false;
        }
        if (
attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
          return 
false;
        }
      }
    }
    return 
true;
  }

  function 
isSplitPoint(nodeoffset) {
    if (
rangy.dom.isCharacterDataNode(node)) {
      if (
offset == 0) {
        return !!
node.previousSibling;
      } else if (
offset == node.length) {
        return !!
node.nextSibling;
      } else {
        return 
true;
      }
    }

    return 
offset && offset node.childNodes.length;
  }

  function 
splitNodeAt(nodedescendantNodedescendantOffset) {
    var 
newNode;
    if (
rangy.dom.isCharacterDataNode(descendantNode)) {
      if (
descendantOffset == 0) {
        
descendantOffset rangy.dom.getNodeIndex(descendantNode);
        
descendantNode descendantNode.parentNode;
      } else if (
descendantOffset == descendantNode.length) {
        
descendantOffset rangy.dom.getNodeIndex(descendantNode) + 1;
        
descendantNode descendantNode.parentNode;
      } else {
        
newNode rangy.dom.splitDataNode(descendantNodedescendantOffset);
      }
    }
    if (!
newNode) {
      
newNode descendantNode.cloneNode(false);
      if (
newNode.id) {
        
newNode.removeAttribute("id");
      }
      var 
child;
      while ((
child descendantNode.childNodes[descendantOffset])) {
        
newNode.appendChild(child);
      }
      
rangy.dom.insertAfter(newNodedescendantNode);
    }
    return (
descendantNode == node) ? newNode splitNodeAt(nodenewNode.parentNoderangy.dom.getNodeIndex(newNode));
  }
  
  function 
Merge(firstNode) {
    
this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
    
this.firstTextNode this.isElementMerge firstNode.lastChild firstNode;
    
this.textNodes = [this.firstTextNode];
  }

  
Merge.prototype = {
    
doMerge: function() {
      var 
textBits = [], textNodeparenttext;
      for (var 
0len this.textNodes.lengthlen; ++i) {
        
textNode this.textNodes[i];
        
parent textNode.parentNode;
        
textBits[i] = textNode.data;
        if (
i) {
          
parent.removeChild(textNode);
          if (!
parent.hasChildNodes()) {
            
parent.parentNode.removeChild(parent);
          }
        }
      }
      
this.firstTextNode.data text textBits.join("");
      return 
text;
    },

    
getLength: function() {
      var 
this.textNodes.lengthlen 0;
      while (
i--) {
        
len += this.textNodes[i].length;
      }
      return 
len;
    },

    
toString: function() {
      var 
textBits = [];
      for (var 
0len this.textNodes.lengthlen; ++i) {
        
textBits[i] = "'" this.textNodes[i].data "'";
      }
      return 
"[Merge(" textBits.join(",") + ")]";
    }
  };

  function 
HTMLApplier(tagNamescssClasssimilarClassRegExpnormalize) {
    
this.tagNames tagNames || [defaultTagName];
    
this.cssClass cssClass || "";
    
this.similarClassRegExp similarClassRegExp;
    
this.normalize normalize;
    
this.applyToAnyTagName false;
  }

  
HTMLApplier.prototype = {
    
getAncestorWithClass: function(node) {
      var 
cssClassMatch;
      while (
node) {
        
cssClassMatch this.cssClass hasClass(nodethis.cssClassthis.similarClassRegExp) : true;
        if (
node.nodeType == wysihtml5.ELEMENT_NODE && rangy.dom.arrayContains(this.tagNamesnode.tagName.toLowerCase()) && cssClassMatch) {
          return 
node;
        }
        
node node.parentNode;
      }
      return 
false;
    },

    
// Normalizes nodes after applying a CSS class to a Range.
    
postApply: function(textNodesrange) {
      var 
firstNode textNodes[0], lastNode textNodes[textNodes.length 1];

      var 
merges = [], currentMerge;

      var 
rangeStartNode firstNoderangeEndNode lastNode;
      var 
rangeStartOffset 0rangeEndOffset lastNode.length;

      var 
textNodeprecedingTextNode;

      for (var 
0len textNodes.lengthlen; ++i) {
        
textNode textNodes[i];
        
precedingTextNode this.getAdjacentMergeableTextNode(textNode.parentNodefalse);
        if (
precedingTextNode) {
          if (!
currentMerge) {
            
currentMerge = new Merge(precedingTextNode);
            
merges.push(currentMerge);
          }
          
currentMerge.textNodes.push(textNode);
          if (
textNode === firstNode) {
            
rangeStartNode currentMerge.firstTextNode;
            
rangeStartOffset rangeStartNode.length;
          }
          if (
textNode === lastNode) {
            
rangeEndNode currentMerge.firstTextNode;
            
rangeEndOffset currentMerge.getLength();
          }
        } else {
          
currentMerge null;
        }
      }

      
// Test whether the first node after the range needs merging
      
var nextTextNode this.getAdjacentMergeableTextNode(lastNode.parentNodetrue);
      if (
nextTextNode) {
        if (!
currentMerge) {
          
currentMerge = new Merge(lastNode);
          
merges.push(currentMerge);
        }
        
currentMerge.textNodes.push(nextTextNode);
      }

      
// Do the merges
      
if (merges.length) {
        for (
0len merges.lengthlen; ++i) {
          
merges[i].doMerge();
        }
        
// Set the range boundaries
        
range.setStart(rangeStartNoderangeStartOffset);
        
range.setEnd(rangeEndNoderangeEndOffset);
      }
    },
    
    
getAdjacentMergeableTextNode: function(nodeforward) {
        var 
isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
        var 
el isTextNode node.parentNode node;
        var 
adjacentNode;
        var 
propName forward "nextSibling" "previousSibling";
        if (
isTextNode) {
          
// Can merge if the node's previous/next sibling is a text node
          
adjacentNode node[propName];
          if (
adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
            return 
adjacentNode;
          }
        } else {
          
// Compare element with its sibling
          
adjacentNode el[propName];
          if (
adjacentNode && this.areElementsMergeable(nodeadjacentNode)) {
            return 
adjacentNode[forward "firstChild" "lastChild"];
          }
        }
        return 
null;
    },
    
    
areElementsMergeable: function(el1el2) {
      return 
rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
        && 
rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
        && 
hasSameClasses(el1el2)
        && 
elementsHaveSameNonClassAttributes(el1el2);
    },

    
createContainer: function(doc) {
      var 
el doc.createElement(this.tagNames[0]);
      if (
this.cssClass) {
        
el.className this.cssClass;
      }
      return 
el;
    },

    
applyToTextNode: function(textNode) {
      var 
parent textNode.parentNode;
      if (
parent.childNodes.length == && rangy.dom.arrayContains(this.tagNamesparent.tagName.toLowerCase())) {
        if (
this.cssClass) {
          
addClass(parentthis.cssClassthis.similarClassRegExp);
        }
      } else {
        var 
el this.createContainer(rangy.dom.getDocument(textNode));
        
textNode.parentNode.insertBefore(eltextNode);
        
el.appendChild(textNode);
      }
    },

    
isRemovable: function(el) {
      return 
rangy.dom.arrayContains(this.tagNamesel.tagName.toLowerCase()) && wysihtml5.lang.string(el.className).trim() == this.cssClass;
    },

    
undoToTextNode: function(textNoderangeancestorWithClass) {
      if (!
range.containsNode(ancestorWithClass)) {
        
// Split out the portion of the ancestor from which we can remove the CSS class
        
var ancestorRange range.cloneRange();
        
ancestorRange.selectNode(ancestorWithClass);

        if (
ancestorRange.isPointInRange(range.endContainerrange.endOffset) && isSplitPoint(range.endContainerrange.endOffset)) {
          
splitNodeAt(ancestorWithClassrange.endContainerrange.endOffset);
          
range.setEndAfter(ancestorWithClass);
        }
        if (
ancestorRange.isPointInRange(range.startContainerrange.startOffset) && isSplitPoint(range.startContainerrange.startOffset)) {
          
ancestorWithClass splitNodeAt(ancestorWithClassrange.startContainerrange.startOffset);
        }
      }
      
      if (
this.similarClassRegExp) {
        
removeClass(ancestorWithClassthis.similarClassRegExp);
      }
      if (
this.isRemovable(ancestorWithClass)) {
        
replaceWithOwnChildren(ancestorWithClass);
      }
    },

    
applyToRange: function(range) {
        var 
textNodes range.getNodes([wysihtml5.TEXT_NODE]);
        if (!
textNodes.length) {
          try {
            var 
node this.createContainer(range.endContainer.ownerDocument);
            
range.surroundContents(node);
            
this.selectNode(rangenode);
            return;
          } catch(
e) {}
        }
        
        
range.splitBoundaries();
        
textNodes range.getNodes([wysihtml5.TEXT_NODE]);
        
        if (
textNodes.length) {
          var 
textNode;

          for (var 
0len textNodes.lengthlen; ++i) {
            
textNode textNodes[i];
            if (!
this.getAncestorWithClass(textNode)) {
              
this.applyToTextNode(textNode);
            }
          }
          
          
range.setStart(textNodes[0], 0);
          
textNode textNodes[textNodes.length 1];
          
range.setEnd(textNodetextNode.length);
          
          if (
this.normalize) {
            
this.postApply(textNodesrange);
          }
        }
    },

    
undoToRange: function(range) {
      var 
textNodes range.getNodes([wysihtml5.TEXT_NODE]), textNodeancestorWithClass;
      if (
textNodes.length) {
        
range.splitBoundaries();
        
textNodes range.getNodes([wysihtml5.TEXT_NODE]);
      } else {
        var 
doc range.endContainer.ownerDocument,
            
node doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
        
range.insertNode(node);
        
range.selectNode(node);
        
textNodes = [node];
      }
      
      for (var 
0len textNodes.lengthlen; ++i) {
        
textNode textNodes[i];
        
ancestorWithClass this.getAncestorWithClass(textNode);
        if (
ancestorWithClass) {
          
this.undoToTextNode(textNoderangeancestorWithClass);
        }
      }
      
      if (
len == 1) {
        
this.selectNode(rangetextNodes[0]);
      } else {
        
range.setStart(textNodes[0], 0);
        
textNode textNodes[textNodes.length 1];
        
range.setEnd(textNodetextNode.length);

        if (
this.normalize) {
          
this.postApply(textNodesrange);
        }
      }
    },
    
    
selectNode: function(rangenode) {
      var 
isElement       node.nodeType === wysihtml5.ELEMENT_NODE,
          
canHaveHTML     "canHaveHTML" in node node.canHaveHTML true,
          
content         isElement node.innerHTML node.data,
          
isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE);

      if (
isEmpty && isElement && canHaveHTML) {
        
// Make sure that caret is visible in node by inserting a zero width no breaking space
        
try { node.innerHTML wysihtml5.INVISIBLE_SPACE; } catch(e) {}
      }
      
range.selectNodeContents(node);
      if (
isEmpty && isElement) {
        
range.collapse(false);
      } else if (
isEmpty) {
        
range.setStartAfter(node);
        
range.setEndAfter(node);
      }
    },
    
    
getTextSelectedByRange: function(textNoderange) {
      var 
textRange range.cloneRange();
      
textRange.selectNodeContents(textNode);

      var 
intersectionRange textRange.intersection(range);
      var 
text intersectionRange intersectionRange.toString() : "";
      
textRange.detach();

      return 
text;
    },

    
isAppliedToRange: function(range) {
      var 
ancestors = [],
          
ancestor,
          
textNodes range.getNodes([wysihtml5.TEXT_NODE]);
      if (!
textNodes.length) {
        
ancestor this.getAncestorWithClass(range.startContainer);
        return 
ancestor ? [ancestor] : false;
      }
      
      for (var 
0len textNodes.lengthselectedTextlen; ++i) {
        
selectedText this.getTextSelectedByRange(textNodes[i], range);
        
ancestor this.getAncestorWithClass(textNodes[i]);
        if (
selectedText != "" && !ancestor) {
          return 
false;
        } else {
          
ancestors.push(ancestor);
        }
      }
      return 
ancestors;
    },

    
toggleRange: function(range) {
      if (
this.isAppliedToRange(range)) {
        
this.undoToRange(range);
      } else {
        
this.applyToRange(range);
      }
    }
  };

  
wysihtml5.selection.HTMLApplier HTMLApplier;
  
})(
wysihtml5rangy);
?>
Онлайн: 1
Реклама