src/cm/media/js/client/c_selection.js
author Simon Descarpentries <sid@sopinspace.com>
Mon, 21 Oct 2013 16:37:07 +0200
changeset 553 bf26fb47a14c
parent 532 0bad3613f59d
child 556 69503659fe8f
permissions -rw-r--r--
To allow scrolling in Safari mobile, we set the content of text_view_comments frame in a jQuery UI layout. So the automated scrolling operations in c_sync.js must be adjustable to the right part to scroll. Also, if a comment have to be shown outside of the current viewport, we scroll the correct part to that viewport and then set the comment top Y offset to juste what it needs to avoid the "Add comment" button after scrolling operation. If not in Safari mobile, we add an offset here to avoid comment to display under the "Add comment" button.

getWrapperAncestor = function(elt) {
  var parent = elt ;
  while (parent != null) {
      if (CY.DOM.hasClass(parent, 'c-s')) 
        return parent ;
      parent = parent.parentNode ;
  }
  return null ; 
}

hasWrapperAncestor = function(elt) {
  return (getWrapperAncestor(elt) != null) ;
/*  var parent = elt ;
  while (parent != null) {
      if (CY.DOM.hasClass(parent, 'c-s')) 
        return true ;
      parent = parent.parentNode ;
  }
  return false ;*/ 
}

// returns null or :
// {'text' : textcontent, 'start': {'elt':startNode, 'nbChar':startOffset(==number of characters to selection start in the start node},
// 'end': ....}
// the text attribute is informational having it empty doesn't mean selection is empty !!

// when selection starts/ends in/on a non textual element (<hr/> for example) we very often have anchorNode/focusNode == body elt
// TODO adapt this body case by considering offset ( cf. http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html)
getSelectionInfo  = function () {
  var startNode = null, endNode = null, startOffset = 0, endOffset = 0, text = '' ;
  
  if (window.getSelection) { // everything else than IE
    var userSelection = window.getSelection();

    if (userSelection.rangeCount > 0) {
      var range = userSelection.getRangeAt(0) ;
      text = range.toString() ;
      if (text != "")  {
        
        // selection occured from right to left ? :
        var r1 = document.createRange() ;r1.setStart(userSelection.anchorNode, userSelection.anchorOffset) ;r1.collapse(true) ;
        var r2 = document.createRange() ;r2.setEnd(userSelection.focusNode, userSelection.focusOffset) ;r2.collapse(false) ;
        var leftToRight = (r2.compareBoundaryPoints(2, r1) == 1) ; // 2 is for END_TO_END
//        CY.log("leftToRight : " + leftToRight) ;
        startNode = (leftToRight) ? userSelection.anchorNode.parentNode : userSelection.focusNode.parentNode ;  
        // GIB: when selecting amath, we should go up in the dom to find the accurate start/end Nodes
        if (startNode.nodeName == 'mi' || startNode.nodeName == 'mo') {
          startNode = startNode.parentElement.parentElement.parentElement.parentElement;
        }
        innerStartNode = (leftToRight) ? userSelection.anchorNode : userSelection.focusNode ;
        endNode = (leftToRight) ? userSelection.focusNode.parentNode : userSelection.anchorNode.parentNode;
        // GIB: when selecting amath, we should go up in the dom to find the accurate start/end Nodes
        if (endNode.nodeName == 'mi' || endNode.nodeName == 'mo') {
          endNode = endNode.parentElement.parentElement.parentElement.parentElement;
        }
        innerEndNode = (leftToRight) ? userSelection.focusNode : userSelection.anchorNode;
          
        startOffset = (leftToRight) ? userSelection.anchorOffset : userSelection.focusOffset;
        endOffset = (leftToRight) ? userSelection.focusOffset : userSelection.anchorOffset ;

        if (!hasWrapperAncestor(endNode) && hasWrapperAncestor(startNode)){
          var r3 = document.createRange() ;
          r3.setStart(innerStartNode, startOffset) ;

          var csStartAncestor = getWrapperAncestor(startNode) ;
          var next = csStartAncestor ;
          r3.setEndAfter(next) ;
          
          var ind = parseInt(csStartAncestor.id.substring('sv_'.length)) ;
          while(r3.toString().length < range.toString().length) {
              ind++ ;
              var node = CY.get("#sv_"+ind) ;
              if (node) {
                next = CY.Node.getDOMNode(node) ;
                r3.setEndAfter(next) ;
              }
              else 
                break ;
          }
          endNode = next.lastChild ;
          endOffset = CY.DOM.getText(endNode).length ;
        }
        else if (!hasWrapperAncestor(startNode) && hasWrapperAncestor(endNode)){
          var r3 = document.createRange() ;
          r3.setEnd(innerEndNode, endOffset) ;

          var csEndAncestor = getWrapperAncestor(endNode) ;
          var prev = csEndAncestor ;
          r3.setStartBefore(prev) ;
          
          var ind = parseInt(csEndAncestor.id.substring('sv_'.length)) ;
          while(r3.toString().length < range.toString().length) {
              ind-- ;
              var node = CY.get("#sv_"+ind) ;
              if (node) {
                prev = CY.Node.getDOMNode(node) ;
                r3.setStartBefore(prev) ;
              }
              else 
                break ;
          }
          startNode = prev.firstChild ;
          startOffset = 0 ;
        }
        else if (!hasWrapperAncestor(startNode) && !hasWrapperAncestor(endNode)){
          var textLength = text.length ;
          
          // gather nodes with id sv_xxxx as candidates for start ancestor
          var startNodeInds = [] ;
          for (var ind = 0 ;  ; ind++) {
            var svNode = CY.get("#sv_"+ind) ;
            if (svNode == null) {
              break;
            }
            else {
              var svText = svNode.get("text") ;
              if (text.indexOf(svText) == 0) {
                startNodeInds.push(ind) ;
              }
            }
          }
          
          // gather nodes with id sv_xxxx as candidates for end ancestor
          var endNodeInds = [] ;
          for (var ind = 0 ;  ; ind++) {
            var svNode = CY.get("#sv_"+ind) ;
            if (svNode == null) {
              break;
            }
            else {
              var svText = svNode.get("text") ;
              if (text.indexOf(svText) == (textLength - svText.length)) { // i.e. the selection exactly ends with svText
                endNodeInds.push(ind) ;
              }
            }
          }

          var stop = false ;
          for (var i = 0 ; i < startNodeInds.length ; i++) {
            for (var j = 0 ; j < endNodeInds.length ; j++) {
              var r4 = document.createRange() ;
              
              var s = CY.Node.getDOMNode(CY.get("#sv_"+startNodeInds[i])) ; var e = CY.Node.getDOMNode(CY.get("#sv_"+endNodeInds[j])) ;
              
              r4.setStartBefore(s) ; r4.setEndAfter(CY.Node.getDOMNode(e)) ;
              
              // does r4 starts after range start and r4 ends before range end ? 
              if ((-1 < r4.compareBoundaryPoints(0, range)) && (1 > r4.compareBoundaryPoints(2, range))) { 
                startNode = s.firstChild ;
                startOffset = 0 ;
                endNode = e.lastChild ;
                endOffset = CY.DOM.getText(e).length ;
                
                stop = true ; 
                break ;
              }
            }
            if (stop)
              break ;
          }             
        }       
        
        r1.detach() ;
        r2.detach() ;
      }
      else 
        return null ;
    }
    else 
      return null ;
    
  }
  else if (document.selection) { // IE case
    var rng = document.selection.createRange();
    if (rng.text.length == 0) 
      return null ;
    var el = rng.parentElement();

    // duplicate the range and collapse it to its start, to ask IE the parent element of the start textNode.    
    var rngStart = rng.duplicate();
    var rngEnd = rng.duplicate();

    rngStart.collapse(true); // collapse to start
    rngEnd.collapse(false);  // collapse to end
    
    startNode = rngStart.parentElement() ;
    while(rngStart.moveStart('character', -1) != 0) {
      if (rngStart.parentElement() != startNode)
        break ;
      startOffset++ ;
    }
    endNode = rngEnd.parentElement() ;
    while(rngEnd.moveEnd('character', -1) != 0) {
      if (rngEnd.parentElement() != endNode)
        break ;
      endOffset++ ;
    }
    
    text = rng.text ;
  }
  
  if (!hasWrapperAncestor(startNode) || !hasWrapperAncestor(endNode)){
    // CY.log('no wrapper on one end') ;
    return null ;
  }
  return {'text' : text, 'start' : {'elt':startNode, 'offset':startOffset}, 'end' : {'elt':endNode, 'offset':endOffset}} ;
}