Вход Регистрация
Файл: library/wysihtml5/src/selection/selection.js
Строк: 683
<?php
/**
 * Selection API
 *
 * @example
 *    var selection = new wysihtml5.Selection(editor);
 */
(function(wysihtml5) {
  var 
dom wysihtml5.dom;
  
  function 
_getCumulativeOffsetTop(element) {
    var 
top 0;
    if (
element.parentNode) {
      do {
        
top += element.offsetTop || 0;
        
element element.offsetParent;
      } while (
element);
    }
    return 
top;
  }
  
  
wysihtml5.Selection Base.extend(
    
/** @scope wysihtml5.Selection.prototype */ {
    
constructor: function(editor) {
      
// Make sure that our external range library is initialized
      
window.rangy.init();
      
      
this.editor   editor;
      
this.composer editor.composer;
      
this.doc      this.composer.doc;
    },
    
    
/**
     * Get the current selection as a bookmark to be able to later restore it
     *
     * @return {Object} An object that represents the current selection
     */
    
getBookmark: function() {
      var 
range this.getRange();
      return 
range && range.cloneRange();
    },

    
/**
     * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
     *
     * @param {Object} bookmark An object that represents the current selection
     */
    
setBookmark: function(bookmark) {
      if (!
bookmark) {
        return;
      }

      
this.setSelection(bookmark);
    },

    
/**
     * Set the caret in front of the given node
     *
     * @param {Object} node The element or text node where to position the caret in front of
     * @example
     *    selection.setBefore(myElement);
     */
    
setBefore: function(node) {
      var 
range rangy.createRange(this.doc);
      
range.setStartBefore(node);
      
range.setEndBefore(node);
      return 
this.setSelection(range);
    },

    
/**
     * Set the caret after the given node
     *
     * @param {Object} node The element or text node where to position the caret in front of
     * @example
     *    selection.setBefore(myElement);
     */
    
setAfter: function(node) {
      var 
range rangy.createRange(this.doc);
      
range.setStartAfter(node);
      
range.setEndAfter(node);
      return 
this.setSelection(range);
    },

    
/**
     * Ability to select/mark nodes
     *
     * @param {Element} node The node/element to select
     * @example
     *    selection.selectNode(document.getElementById("my-image"));
     */
    
selectNode: function(nodeavoidInvisibleSpace) {
      var 
range           rangy.createRange(this.doc),
          
isElement       node.nodeType === wysihtml5.ELEMENT_NODE,
          
canHaveHTML     "canHaveHTML" in node node.canHaveHTML : (node.nodeName !== "IMG"),
          
content         isElement node.innerHTML node.data,
          
isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
          
displayStyle    dom.getStyle("display").from(node),
          
isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");

      if (
isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
        
// Make sure that caret is visible in node by inserting a zero width no breaking space
        
try { node.innerHTML wysihtml5.INVISIBLE_SPACE; } catch(e) {}
      }

      if (
canHaveHTML) {
        
range.selectNodeContents(node);
      } else {
        
range.selectNode(node);
      }

      if (
canHaveHTML && isEmpty && isElement) {
        
range.collapse(isBlockElement);
      } else if (
canHaveHTML && isEmpty) {
        
range.setStartAfter(node);
        
range.setEndAfter(node);
      }

      
this.setSelection(range);
    },

    
/**
     * Get the node which contains the selection
     *
     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
     * @return {Object} The node that contains the caret
     * @example
     *    var nodeThatContainsCaret = selection.getSelectedNode();
     */
    
getSelectedNode: function(controlRange) {
      var 
selection,
          
range;

      if (
controlRange && this.doc.selection && this.doc.selection.type === "Control") {
        
range this.doc.selection.createRange();
        if (
range && range.length) {
          return 
range.item(0);
        }
      }

      
selection this.getSelection(this.doc);
      if (
selection.focusNode === selection.anchorNode) {
        return 
selection.focusNode;
      } else {
        
range this.getRange(this.doc);
        return 
range range.commonAncestorContainer this.doc.body;
      }
    },

    
executeAndRestore: function(methodrestoreScrollPosition) {
      var 
body                  this.doc.body,
          
oldScrollTop          restoreScrollPosition && body.scrollTop,
          
oldScrollLeft         restoreScrollPosition && body.scrollLeft,
          
className             "_wysihtml5-temp-placeholder",
          
placeholderHtml       '<span class="' className '">' wysihtml5.INVISIBLE_SPACE '</span>',
          
range                 this.getRange(this.doc),
          
caretPlaceholder,
          
newCaretPlaceholder,
          
nextSibling,
          
node,
          
newRange;
      
      
// Nothing selected, execute and say goodbye
      
if (!range) {
        
method(bodybody);
        return;
      }
      
      if (
wysihtml5.browser.hasInsertNodeIssue()) {
        
this.doc.execCommand("insertHTML"falseplaceholderHtml);
      } else {
        
node range.createContextualFragment(placeholderHtml);
        
range.insertNode(node);
      }
      
      
// Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
      
try {
        
method(range.startContainerrange.endContainer);
      } catch(
e) {
        
setTimeout(function() { throw e; }, 0);
      }
      
      
caretPlaceholder this.doc.querySelector("." className);
      if (
caretPlaceholder) {
        
newRange rangy.createRange(this.doc);
        
nextSibling caretPlaceholder.nextSibling;
        
// Opera is so fucked up when you wanna set focus before a <br>
        
if (wysihtml5.browser.hasInsertNodeIssue() && nextSibling && nextSibling.nodeName === "BR") {
          
newCaretPlaceholder this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
          
dom.insert(newCaretPlaceholder).after(caretPlaceholder);
          
newRange.setStartBefore(newCaretPlaceholder);
          
newRange.setEndBefore(newCaretPlaceholder);
        } else {
          
newRange.selectNode(caretPlaceholder);
          
newRange.deleteContents();
        }
        
this.setSelection(newRange);
      } else {
        
// fallback for when all hell breaks loose
        
body.focus();
      }

      if (
restoreScrollPosition) {
        
body.scrollTop  oldScrollTop;
        
body.scrollLeft oldScrollLeft;
      }

      
// Remove it again, just to make sure that the placeholder is definitely out of the dom tree
      
try {
        
caretPlaceholder.parentNode.removeChild(caretPlaceholder);
      } catch(
e2) {}
    },

    
/**
     * Different approach of preserving the selection (doesn't modify the dom)
     * Takes all text nodes in the selection and saves the selection position in the first and last one
     */
    
executeAndRestoreSimple: function(method) {
      var 
range this.getRange(),
          
body  this.doc.body,
          
newRange,
          
firstNode,
          
lastNode,
          
textNodes,
          
rangeBackup;

      
// Nothing selected, execute and say goodbye
      
if (!range) {
        
method(bodybody);
        return;
      }

      
textNodes range.getNodes([3]);
      
firstNode textNodes[0] || range.startContainer;
      
lastNode  textNodes[textNodes.length 1] || range.endContainer;

      
rangeBackup = {
        
collapsed:      range.collapsed,
        
startContainerfirstNode,
        
startOffset:    firstNode === range.startContainer range.startOffset 0,
        
endContainer:   lastNode,
        
endOffset:      lastNode === range.endContainer range.endOffset lastNode.length
      
};

      try {
        
method(range.startContainerrange.endContainer);
      } catch(
e) {
        
setTimeout(function() { throw e; }, 0);
      }

      
newRange rangy.createRange(this.doc);
      try { 
newRange.setStart(rangeBackup.startContainerrangeBackup.startOffset); } catch(e1) {}
      try { 
newRange.setEnd(rangeBackup.endContainerrangeBackup.endOffset); } catch(e2) {}
      try { 
this.setSelection(newRange); } catch(e3) {}
    },
    
    
set: function(nodeoffset) {
      var 
newRange rangy.createRange(this.doc);
      
newRange.setStart(nodeoffset || 0);
      
this.setSelection(newRange);
    },
    
    
/**
     * Insert html at the caret position and move the cursor after the inserted html
     *
     * @param {String} html HTML string to insert
     * @example
     *    selection.insertHTML("<p>foobar</p>");
     */
    
insertHTML: function(html) {
      var 
range     rangy.createRange(this.doc),
          
node      range.createContextualFragment(html),
          
lastChild node.lastChild;
      
this.insertNode(node);
      if (
lastChild) {
        
this.setAfter(lastChild);
      }
    },

    
/**
     * Insert a node at the caret position and move the cursor behind it
     *
     * @param {Object} node HTML string to insert
     * @example
     *    selection.insertNode(document.createTextNode("foobar"));
     */
    
insertNode: function(node) {
      var 
range this.getRange();
      if (
range) {
        
range.insertNode(node);
      }
    },

    
/**
     * Wraps current selection with the given node
     *
     * @param {Object} node The node to surround the selected elements with
     */
    
surround: function(node) {
      var 
range this.getRange();
      if (!
range) {
        return;
      }

      try {
        
// This only works when the range boundaries are not overlapping other elements
        
range.surroundContents(node);
        
this.selectNode(node);
      } catch(
e) {
        
// fallback
        
node.appendChild(range.extractContents());
        
range.insertNode(node);
      }
    },

    
/**
     * Scroll the current caret position into the view
     * FIXME: This is a bit hacky, there might be a smarter way of doing this
     *
     * @example
     *    selection.scrollIntoView();
     */
    
scrollIntoView: function() {
      var 
doc           this.doc,
          
tolerance     5// px
          
hasScrollBars doc.documentElement.scrollHeight doc.documentElement.offsetHeight,
          
tempElement   doc._wysihtml5ScrollIntoViewElement doc._wysihtml5ScrollIntoViewElement || (function() {
            var 
element doc.createElement("span");
            
// The element needs content in order to be able to calculate it's position properly
            
element.innerHTML wysihtml5.INVISIBLE_SPACE;
            return 
element;
          })(),
          
offsetTop;

      if (
hasScrollBars) {
        
this.insertNode(tempElement);
        
offsetTop _getCumulativeOffsetTop(tempElement);
        
tempElement.parentNode.removeChild(tempElement);
        if (
offsetTop >= (doc.body.scrollTop doc.documentElement.offsetHeight tolerance)) {
          
doc.body.scrollTop offsetTop;
        }
      }
    },

    
/**
     * Select line where the caret is in
     */
    
selectLine: function() {
      if (
wysihtml5.browser.supportsSelectionModify()) {
        
this._selectLine_W3C();
      } else if (
this.doc.selection) {
        
this._selectLine_MSIE();
      }
    },

    
/**
     * See https://developer.mozilla.org/en/DOM/Selection/modify
     */
    
_selectLine_W3C: function() {
      var 
win       this.doc.defaultView,
          
selection win.getSelection();
      
selection.modify("extend""left""lineboundary");
      
selection.modify("extend""right""lineboundary");
    },

    
_selectLine_MSIE: function() {
      var 
range       this.doc.selection.createRange(),
          
rangeTop    range.boundingTop,
          
scrollWidth this.doc.body.scrollWidth,
          
rangeBottom,
          
rangeEnd,
          
measureNode,
          
i,
          
j;

      if (!
range.moveToPoint) {
        return;
      }

      if (
rangeTop === 0) {
        
// Don't know why, but when the selection ends at the end of a line
        // range.boundingTop is 0
        
measureNode this.doc.createElement("span");
        
this.insertNode(measureNode);
        
rangeTop measureNode.offsetTop;
        
measureNode.parentNode.removeChild(measureNode);
      }

      
rangeTop += 1;

      for (
i=-10i<scrollWidthi+=2) {
        try {
          
range.moveToPoint(irangeTop);
          break;
        } catch(
e1) {}
      }

      
// Investigate the following in order to handle multi line selections
      // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
      
rangeBottom rangeTop;
      
rangeEnd this.doc.selection.createRange();
      for (
j=scrollWidthj>=0j--) {
        try {
          
rangeEnd.moveToPoint(jrangeBottom);
          break;
        } catch(
e2) {}
      }

      
range.setEndPoint("EndToEnd"rangeEnd);
      
range.select();
    },

    
getText: function() {
      var 
selection this.getSelection();
      return 
selection selection.toString() : "";
    },

    
getNodes: function(nodeTypefilter) {
      var 
range this.getRange();
      if (
range) {
        return 
range.getNodes([nodeType], filter);
      } else {
        return [];
      }
    },
    
    
getRange: function() {
      var 
selection this.getSelection();
      return 
selection && selection.rangeCount && selection.getRangeAt(0);
    },

    
getSelection: function() {
      return 
rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
    },

    
setSelection: function(range) {
      var 
win       this.doc.defaultView || this.doc.parentWindow,
          
selection rangy.getSelection(win);
      return 
selection.setSingleRange(range);
    }
  });
  
})(
wysihtml5);
?>
Онлайн: 1
Реклама