[ Index ]

MailPress 7.2

[ Index ]     [ Classes ]     [ Functions ]     [ Variables ]     [ Constants ]     [ Statistics ]    

title

Body

[close]

/mp-includes/js/codemirror/js/ -> select.js (source)

   1  /* Functionality for finding, storing, and restoring selections
   2   *
   3   * This does not provide a generic API, just the minimal functionality
   4   * required by the CodeMirror system.
   5   */
   6  
   7  // Namespace object.
   8  var select = {};
   9  
  10  (function() {
  11    select.ie_selection = document.selection && document.selection.createRangeCollection;
  12  
  13    // Find the 'top-level' (defined as 'a direct child of the node
  14    // passed as the top argument') node that the given node is
  15    // contained in. Return null if the given node is not inside the top
  16    // node.
  17    function topLevelNodeAt(node, top) {
  18      while (node && node.parentNode != top)
  19        node = node.parentNode;
  20      return node;
  21    }
  22  
  23    // Find the top-level node that contains the node before this one.
  24    function topLevelNodeBefore(node, top) {
  25      while (!node.previousSibling && node.parentNode != top)
  26        node = node.parentNode;
  27      return topLevelNodeAt(node.previousSibling, top);
  28    }
  29  
  30    var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
  31  
  32    select.scrollToNode = function(node, cursor) {
  33      if (!node) return;
  34      var element = node, body = document.body,
  35          html = document.documentElement,
  36          atEnd = !element.nextSibling || !element.nextSibling.nextSibling
  37                  || !element.nextSibling.nextSibling.nextSibling;
  38      // In Opera (and recent Webkit versions), BR elements *always*
  39      // have a offsetTop property of zero.
  40      var compensateHack = 0;
  41      while (element && !element.offsetTop) {
  42        compensateHack++;
  43        element = element.previousSibling;
  44      }
  45      // atEnd is another kludge for these browsers -- if the cursor is
  46      // at the end of the document, and the node doesn't have an
  47      // offset, just scroll to the end.
  48      if (compensateHack == 0) atEnd = false;
  49  
  50      // WebKit has a bad habit of (sometimes) happily returning bogus
  51      // offsets when the document has just been changed. This seems to
  52      // always be 5/5, so we don't use those.
  53      if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
  54        return;
  55  
  56      var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
  57          width = (node ? node.offsetWidth : 0), pos = element;
  58      while (pos && pos.offsetParent) {
  59        y += pos.offsetTop;
  60        // Don't count X offset for <br> nodes
  61        if (!isBR(pos))
  62          x += pos.offsetLeft;
  63        pos = pos.offsetParent;
  64      }
  65  
  66      var scroll_x = body.scrollLeft || html.scrollLeft || 0,
  67          scroll_y = body.scrollTop || html.scrollTop || 0,
  68          scroll = false, screen_width = window.innerWidth || html.clientWidth || 0;
  69  
  70      if (cursor || width < screen_width) {
  71        if (cursor) {
  72          var off = select.offsetInNode(node), size = nodeText(node).length;
  73          if (size) x += width * (off / size);
  74        }
  75        var screen_x = x - scroll_x;
  76        if (screen_x < 0 || screen_x > screen_width) {
  77          scroll_x = x;
  78          scroll = true;
  79        }
  80      }
  81      var screen_y = y - scroll_y;
  82      if (screen_y < 0 || atEnd || screen_y > (window.innerHeight || html.clientHeight || 0) - 50) {
  83        scroll_y = atEnd ? 1e6 : y;
  84        scroll = true;
  85      }
  86      if (scroll) window.scrollTo(scroll_x, scroll_y);
  87    };
  88  
  89    select.scrollToCursor = function(container) {
  90      select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
  91    };
  92  
  93    // Used to prevent restoring a selection when we do not need to.
  94    var currentSelection = null;
  95  
  96    select.snapshotChanged = function() {
  97      if (currentSelection) currentSelection.changed = true;
  98    };
  99  
 100    // This is called by the code in editor.js whenever it is replacing
 101    // a text node. The function sees whether the given oldNode is part
 102    // of the current selection, and updates this selection if it is.
 103    // Because nodes are often only partially replaced, the length of
 104    // the part that gets replaced has to be taken into account -- the
 105    // selection might stay in the oldNode if the newNode is smaller
 106    // than the selection's offset. The offset argument is needed in
 107    // case the selection does move to the new object, and the given
 108    // length is not the whole length of the new node (part of it might
 109    // have been used to replace another node).
 110    select.snapshotReplaceNode = function(from, to, length, offset) {
 111      if (!currentSelection) return;
 112  
 113      function replace(point) {
 114        if (from == point.node) {
 115          currentSelection.changed = true;
 116          if (length && point.offset > length) {
 117            point.offset -= length;
 118          }
 119          else {
 120            point.node = to;
 121            point.offset += (offset || 0);
 122          }
 123        }
 124      }
 125      replace(currentSelection.start);
 126      replace(currentSelection.end);
 127    };
 128  
 129    select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
 130      if (!currentSelection) return;
 131  
 132      function move(point) {
 133        if (from == point.node && (!ifAtStart || point.offset == 0)) {
 134          currentSelection.changed = true;
 135          point.node = to;
 136          if (relative) point.offset = Math.max(0, point.offset + distance);
 137          else point.offset = distance;
 138        }
 139      }
 140      move(currentSelection.start);
 141      move(currentSelection.end);
 142    };
 143  
 144    // Most functions are defined in two ways, one for the IE selection
 145    // model, one for the W3C one.
 146    if (select.ie_selection) {
 147      function selectionNode(start) {
 148        var range = document.selection.createRange();
 149        range.collapse(start);
 150  
 151        function nodeAfter(node) {
 152          var found = null;
 153          while (!found && node) {
 154            found = node.nextSibling;
 155            node = node.parentNode;
 156          }
 157          return nodeAtStartOf(found);
 158        }
 159  
 160        function nodeAtStartOf(node) {
 161          while (node && node.firstChild) node = node.firstChild;
 162          return {node: node, offset: 0};
 163        }
 164  
 165        var containing = range.parentElement();
 166        if (!isAncestor(document.body, containing)) return null;
 167        if (!containing.firstChild) return nodeAtStartOf(containing);
 168  
 169        var working = range.duplicate();
 170        working.moveToElementText(containing);
 171        working.collapse(true);
 172        for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
 173          if (cur.nodeType == 3) {
 174            var size = cur.nodeValue.length;
 175            working.move("character", size);
 176          }
 177          else {
 178            working.moveToElementText(cur);
 179            working.collapse(false);
 180          }
 181  
 182          var dir = range.compareEndPoints("StartToStart", working);
 183          if (dir == 0) return nodeAfter(cur);
 184          if (dir == 1) continue;
 185          if (cur.nodeType != 3) return nodeAtStartOf(cur);
 186  
 187          working.setEndPoint("StartToEnd", range);
 188          return {node: cur, offset: size - working.text.length};
 189        }
 190        return nodeAfter(containing);
 191      }
 192  
 193      select.markSelection = function() {
 194        currentSelection = null;
 195        var sel = document.selection;
 196        if (!sel) return;
 197        var start = selectionNode(true),
 198            end = selectionNode(false);
 199        if (!start || !end) return;
 200        currentSelection = {start: start, end: end, changed: false};
 201      };
 202  
 203      select.selectMarked = function() {
 204        if (!currentSelection || !currentSelection.changed) return;
 205  
 206        function makeRange(point) {
 207          var range = document.body.createTextRange(),
 208              node = point.node;
 209          if (!node) {
 210            range.moveToElementText(document.body);
 211            range.collapse(false);
 212          }
 213          else if (node.nodeType == 3) {
 214            range.moveToElementText(node.parentNode);
 215            var offset = point.offset;
 216            while (node.previousSibling) {
 217              node = node.previousSibling;
 218              offset += (node.innerText || "").length;
 219            }
 220            range.move("character", offset);
 221          }
 222          else {
 223            range.moveToElementText(node);
 224            range.collapse(true);
 225          }
 226          return range;
 227        }
 228  
 229        var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
 230        start.setEndPoint("StartToEnd", end);
 231        start.select();
 232      };
 233  
 234      select.offsetInNode = function(node) {
 235        var sel = document.selection;
 236        if (!sel) return 0;
 237        var range = sel.createRange(), range2 = range.duplicate();
 238        try {range2.moveToElementText(node);} catch(e){return 0;}
 239        range.setEndPoint("StartToStart", range2);
 240        return range.text.length;
 241      };
 242  
 243      // Get the top-level node that one end of the cursor is inside or
 244      // after. Note that this returns false for 'no cursor', and null
 245      // for 'start of document'.
 246      select.selectionTopNode = function(container, start) {
 247        var selection = document.selection;
 248        if (!selection) return false;
 249  
 250        var range = selection.createRange(), range2 = range.duplicate();
 251        range.collapse(start);
 252        var around = range.parentElement();
 253        if (around && isAncestor(container, around)) {
 254          // Only use this node if the selection is not at its start.
 255          range2.moveToElementText(around);
 256          if (range.compareEndPoints("StartToStart", range2) == 1)
 257            return topLevelNodeAt(around, container);
 258        }
 259  
 260        // Move the start of a range to the start of a node,
 261        // compensating for the fact that you can't call
 262        // moveToElementText with text nodes.
 263        function moveToNodeStart(range, node) {
 264          if (node.nodeType == 3) {
 265            var count = 0, cur = node.previousSibling;
 266            while (cur && cur.nodeType == 3) {
 267              count += cur.nodeValue.length;
 268              cur = cur.previousSibling;
 269            }
 270            if (cur) {
 271              try{range.moveToElementText(cur);}
 272              catch(e){return false;}
 273              range.collapse(false);
 274            }
 275            else range.moveToElementText(node.parentNode);
 276            if (count) range.move("character", count);
 277          }
 278          else {
 279            try{range.moveToElementText(node);}
 280            catch(e){return false;}
 281          }
 282          return true;
 283        }
 284  
 285        // Do a binary search through the container object, comparing
 286        // the start of each node to the selection
 287        var start = 0, end = container.childNodes.length - 1;
 288        while (start < end) {
 289          var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
 290          if (!node) return false; // Don't ask. IE6 manages this sometimes.
 291          if (!moveToNodeStart(range2, node)) return false;
 292          if (range.compareEndPoints("StartToStart", range2) == 1)
 293            start = middle;
 294          else
 295            end = middle - 1;
 296        }
 297        
 298        if (start == 0) {
 299          var test1 = selection.createRange(), test2 = test1.duplicate();
 300          test2.moveToElementText(container);
 301          if (test1.compareEndPoints("StartToStart", test2) == 0)
 302            return null;
 303        }
 304        return container.childNodes[start] || null;
 305      };
 306  
 307      // Place the cursor after this.start. This is only useful when
 308      // manually moving the cursor instead of restoring it to its old
 309      // position.
 310      select.focusAfterNode = function(node, container) {
 311        var range = document.body.createTextRange();
 312        range.moveToElementText(node || container);
 313        range.collapse(!node);
 314        range.select();
 315      };
 316  
 317      select.somethingSelected = function() {
 318        var sel = document.selection;
 319        return sel && (sel.createRange().text != "");
 320      };
 321  
 322      function insertAtCursor(html) {
 323        var selection = document.selection;
 324        if (selection) {
 325          var range = selection.createRange();
 326          range.pasteHTML(html);
 327          range.collapse(false);
 328          range.select();
 329        }
 330      }
 331  
 332      // Used to normalize the effect of the enter key, since browsers
 333      // do widely different things when pressing enter in designMode.
 334      select.insertNewlineAtCursor = function() {
 335        insertAtCursor("<br>");
 336      };
 337  
 338      select.insertTabAtCursor = function() {
 339        insertAtCursor(fourSpaces);
 340      };
 341  
 342      // Get the BR node at the start of the line on which the cursor
 343      // currently is, and the offset into the line. Returns null as
 344      // node if cursor is on first line.
 345      select.cursorPos = function(container, start) {
 346        var selection = document.selection;
 347        if (!selection) return null;
 348  
 349        var topNode = select.selectionTopNode(container, start);
 350        while (topNode && !isBR(topNode))
 351          topNode = topNode.previousSibling;
 352  
 353        var range = selection.createRange(), range2 = range.duplicate();
 354        range.collapse(start);
 355        if (topNode) {
 356          range2.moveToElementText(topNode);
 357          range2.collapse(false);
 358        }
 359        else {
 360          // When nothing is selected, we can get all kinds of funky errors here.
 361          try { range2.moveToElementText(container); }
 362          catch (e) { return null; }
 363          range2.collapse(true);
 364        }
 365        range.setEndPoint("StartToStart", range2);
 366  
 367        return {node: topNode, offset: range.text.length};
 368      };
 369  
 370      select.setCursorPos = function(container, from, to) {
 371        function rangeAt(pos) {
 372          var range = document.body.createTextRange();
 373          if (!pos.node) {
 374            range.moveToElementText(container);
 375            range.collapse(true);
 376          }
 377          else {
 378            range.moveToElementText(pos.node);
 379            range.collapse(false);
 380          }
 381          range.move("character", pos.offset);
 382          return range;
 383        }
 384  
 385        var range = rangeAt(from);
 386        if (to && to != from)
 387          range.setEndPoint("EndToEnd", rangeAt(to));
 388        range.select();
 389      }
 390  
 391      // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
 392      select.getBookmark = function (container) {
 393        var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
 394        if (from && to) return {from: from, to: to};
 395      };
 396  
 397      // Restore a stored selection.
 398      select.setBookmark = function(container, mark) {
 399        if (!mark) return;
 400        select.setCursorPos(container, mark.from, mark.to);
 401      };
 402    }
 403    // W3C model
 404    else {
 405      // Find the node right at the cursor, not one of its
 406      // ancestors with a suitable offset. This goes down the DOM tree
 407      // until a 'leaf' is reached (or is it *up* the DOM tree?).
 408      function innerNode(node, offset) {
 409        while (node.nodeType != 3 && !isBR(node)) {
 410          var newNode = node.childNodes[offset] || node.nextSibling;
 411          offset = 0;
 412          while (!newNode && node.parentNode) {
 413            node = node.parentNode;
 414            newNode = node.nextSibling;
 415          }
 416          node = newNode;
 417          if (!newNode) break;
 418        }
 419        return {node: node, offset: offset};
 420      }
 421  
 422      // Store start and end nodes, and offsets within these, and refer
 423      // back to the selection object from those nodes, so that this
 424      // object can be updated when the nodes are replaced before the
 425      // selection is restored.
 426      select.markSelection = function () {
 427        var selection = window.getSelection();
 428        if (!selection || selection.rangeCount == 0)
 429          return (currentSelection = null);
 430        var range = selection.getRangeAt(0);
 431  
 432        currentSelection = {
 433          start: innerNode(range.startContainer, range.startOffset),
 434          end: innerNode(range.endContainer, range.endOffset),
 435          changed: false
 436        };
 437      };
 438  
 439      select.selectMarked = function () {
 440        var cs = currentSelection;
 441        // on webkit-based browsers, it is apparently possible that the
 442        // selection gets reset even when a node that is not one of the
 443        // endpoints get messed with. the most common situation where
 444        // this occurs is when a selection is deleted or overwitten. we
 445        // check for that here.
 446        function focusIssue() {
 447          if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
 448            var selection = window.getSelection();
 449            if (!selection || selection.rangeCount == 0) return true;
 450            var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
 451            return cs.start.node != point.node || cs.start.offset != point.offset;
 452          }
 453        }
 454        if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
 455        var range = document.createRange();
 456  
 457        function setPoint(point, which) {
 458          if (point.node) {
 459            // Some magic to generalize the setting of the start and end
 460            // of a range.
 461            if (point.offset == 0)
 462              range["set" + which + "Before"](point.node);
 463            else
 464              range["set" + which](point.node, point.offset);
 465          }
 466          else {
 467            range.setStartAfter(document.body.lastChild || document.body);
 468          }
 469        }
 470  
 471        setPoint(cs.end, "End");
 472        setPoint(cs.start, "Start");
 473        selectRange(range);
 474      };
 475  
 476      // Helper for selecting a range object.
 477      function selectRange(range) {
 478        var selection = window.getSelection();
 479        if (!selection) return;
 480        selection.removeAllRanges();
 481        selection.addRange(range);
 482      }
 483      function selectionRange() {
 484        var selection = window.getSelection();
 485        if (!selection || selection.rangeCount == 0)
 486          return false;
 487        else
 488          return selection.getRangeAt(0);
 489      }
 490  
 491      // Finding the top-level node at the cursor in the W3C is, as you
 492      // can see, quite an involved process.
 493      select.selectionTopNode = function(container, start) {
 494        var range = selectionRange();
 495        if (!range) return false;
 496  
 497        var node = start ? range.startContainer : range.endContainer;
 498        var offset = start ? range.startOffset : range.endOffset;
 499        // Work around (yet another) bug in Opera's selection model.
 500        if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
 501            container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
 502          offset--;
 503  
 504        // For text nodes, we look at the node itself if the cursor is
 505        // inside, or at the node before it if the cursor is at the
 506        // start.
 507        if (node.nodeType == 3){
 508          if (offset > 0)
 509            return topLevelNodeAt(node, container);
 510          else
 511            return topLevelNodeBefore(node, container);
 512        }
 513        // Occasionally, browsers will return the HTML node as
 514        // selection. If the offset is 0, we take the start of the frame
 515        // ('after null'), otherwise, we take the last node.
 516        else if (node.nodeName.toUpperCase() == "HTML") {
 517          return (offset == 1 ? null : container.lastChild);
 518        }
 519        // If the given node is our 'container', we just look up the
 520        // correct node by using the offset.
 521        else if (node == container) {
 522          return (offset == 0) ? null : node.childNodes[offset - 1];
 523        }
 524        // In any other case, we have a regular node. If the cursor is
 525        // at the end of the node, we use the node itself, if it is at
 526        // the start, we use the node before it, and in any other
 527        // case, we look up the child before the cursor and use that.
 528        else {
 529          if (offset == node.childNodes.length)
 530            return topLevelNodeAt(node, container);
 531          else if (offset == 0)
 532            return topLevelNodeBefore(node, container);
 533          else
 534            return topLevelNodeAt(node.childNodes[offset - 1], container);
 535        }
 536      };
 537  
 538      select.focusAfterNode = function(node, container) {
 539        var range = document.createRange();
 540        range.setStartBefore(container.firstChild || container);
 541        // In Opera, setting the end of a range at the end of a line
 542        // (before a BR) will cause the cursor to appear on the next
 543        // line, so we set the end inside of the start node when
 544        // possible.
 545        if (node && !node.firstChild)
 546          range.setEndAfter(node);
 547        else if (node)
 548          range.setEnd(node, node.childNodes.length);
 549        else
 550          range.setEndBefore(container.firstChild || container);
 551        range.collapse(false);
 552        selectRange(range);
 553      };
 554  
 555      select.somethingSelected = function() {
 556        var range = selectionRange();
 557        return range && !range.collapsed;
 558      };
 559  
 560      select.offsetInNode = function(node) {
 561        var range = selectionRange();
 562        if (!range) return 0;
 563        range = range.cloneRange();
 564        range.setStartBefore(node);
 565        return range.toString().length;
 566      };
 567  
 568      function insertNodeAtCursor(node) {
 569        var range = selectionRange();
 570        if (!range) return;
 571  
 572        range.deleteContents();
 573        range.insertNode(node);
 574        webkitLastLineHack(document.body);
 575  
 576        // work around weirdness where Opera will magically insert a new
 577        // BR node when a BR node inside a span is moved around. makes
 578        // sure the BR ends up outside of spans.
 579        if (window.opera && isBR(node) && isSpan(node.parentNode)) {
 580          var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
 581          outer.insertBefore(node, p.nextSibling);
 582          var textAfter = "";
 583          for (; next && next.nodeType == 3; next = next.nextSibling) {
 584            textAfter += next.nodeValue;
 585            removeElement(next);
 586          }
 587          outer.insertBefore(makePartSpan(textAfter, document), node.nextSibling);
 588        }
 589        range = document.createRange();
 590        range.selectNode(node);
 591        range.collapse(false);
 592        selectRange(range);
 593      }
 594  
 595      select.insertNewlineAtCursor = function() {
 596        insertNodeAtCursor(document.createElement("BR"));
 597      };
 598  
 599      select.insertTabAtCursor = function() {
 600        insertNodeAtCursor(document.createTextNode(fourSpaces));
 601      };
 602  
 603      select.cursorPos = function(container, start) {
 604        var range = selectionRange();
 605        if (!range) return;
 606  
 607        var topNode = select.selectionTopNode(container, start);
 608        while (topNode && !isBR(topNode))
 609          topNode = topNode.previousSibling;
 610  
 611        range = range.cloneRange();
 612        range.collapse(start);
 613        if (topNode)
 614          range.setStartAfter(topNode);
 615        else
 616          range.setStartBefore(container);
 617  
 618        var text = range.toString();
 619        return {node: topNode, offset: text.length};
 620      };
 621  
 622      select.setCursorPos = function(container, from, to) {
 623        var range = document.createRange();
 624  
 625        function setPoint(node, offset, side) {
 626          if (offset == 0 && node && !node.nextSibling) {
 627            range["set" + side + "After"](node);
 628            return true;
 629          }
 630  
 631          if (!node)
 632            node = container.firstChild;
 633          else
 634            node = node.nextSibling;
 635  
 636          if (!node) return;
 637  
 638          if (offset == 0) {
 639            range["set" + side + "Before"](node);
 640            return true;
 641          }
 642  
 643          var backlog = []
 644          function decompose(node) {
 645            if (node.nodeType == 3)
 646              backlog.push(node);
 647            else
 648              forEach(node.childNodes, decompose);
 649          }
 650          while (true) {
 651            while (node && !backlog.length) {
 652              decompose(node);
 653              node = node.nextSibling;
 654            }
 655            var cur = backlog.shift();
 656            if (!cur) return false;
 657  
 658            var length = cur.nodeValue.length;
 659            if (length >= offset) {
 660              range["set" + side](cur, offset);
 661              return true;
 662            }
 663            offset -= length;
 664          }
 665        }
 666  
 667        to = to || from;
 668        if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
 669          selectRange(range);
 670      };
 671    }
 672  })();


Generated: Tue May 19 15:55:14 2020 Cross-referenced by PHPXref 0.7.1