Rearrange source to src dir.
[redakcja.git] / src / redakcja / static / js / lib / codemirror-0.8 / select.js
diff --git a/src/redakcja/static/js/lib/codemirror-0.8/select.js b/src/redakcja/static/js/lib/codemirror-0.8/select.js
new file mode 100644 (file)
index 0000000..01fe996
--- /dev/null
@@ -0,0 +1,672 @@
+/* Functionality for finding, storing, and restoring selections
+ *
+ * This does not provide a generic API, just the minimal functionality
+ * required by the CodeMirror system.
+ */
+
+// Namespace object.
+var select = {};
+
+(function() {
+  select.ie_selection = document.selection && document.selection.createRangeCollection;
+
+  // Find the 'top-level' (defined as 'a direct child of the node
+  // passed as the top argument') node that the given node is
+  // contained in. Return null if the given node is not inside the top
+  // node.
+  function topLevelNodeAt(node, top) {
+    while (node && node.parentNode != top)
+      node = node.parentNode;
+    return node;
+  }
+
+  // Find the top-level node that contains the node before this one.
+  function topLevelNodeBefore(node, top) {
+    while (!node.previousSibling && node.parentNode != top)
+      node = node.parentNode;
+    return topLevelNodeAt(node.previousSibling, top);
+  }
+
+  var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
+
+  select.scrollToNode = function(node, cursor) {
+    if (!node) return;
+    var element = node,
+        doc = element.ownerDocument, body = doc.body,
+        win = (doc.defaultView || doc.parentWindow),
+        html = doc.documentElement,
+        atEnd = !element.nextSibling || !element.nextSibling.nextSibling
+                || !element.nextSibling.nextSibling.nextSibling;
+    // In Opera (and recent Webkit versions), BR elements *always*
+    // have a offsetTop property of zero.
+    var compensateHack = 0;
+    while (element && !element.offsetTop) {
+      compensateHack++;
+      element = element.previousSibling;
+    }
+    // atEnd is another kludge for these browsers -- if the cursor is
+    // at the end of the document, and the node doesn't have an
+    // offset, just scroll to the end.
+    if (compensateHack == 0) atEnd = false;
+
+    // WebKit has a bad habit of (sometimes) happily returning bogus
+    // offsets when the document has just been changed. This seems to
+    // always be 5/5, so we don't use those.
+    if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
+      return;
+
+    var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
+        width = (node ? node.offsetWidth : 0), pos = element;
+    while (pos && pos.offsetParent) {
+      y += pos.offsetTop;
+      // Don't count X offset for <br> nodes
+      if (!isBR(pos))
+        x += pos.offsetLeft;
+      pos = pos.offsetParent;
+    }
+
+    var scroll_x = body.scrollLeft || html.scrollLeft || 0,
+        scroll_y = body.scrollTop || html.scrollTop || 0,
+        scroll = false, screen_width = win.innerWidth || html.clientWidth || 0;
+
+    if (cursor || width < screen_width) {
+      if (cursor) {
+        var off = select.offsetInNode(win, node), size = nodeText(node).length;
+        if (size) x += width * (off / size);
+      }
+      var screen_x = x - scroll_x;
+      if (screen_x < 0 || screen_x > screen_width) {
+        scroll_x = x;
+        scroll = true;
+      }
+    }
+    var screen_y = y - scroll_y;
+    if (screen_y < 0 || atEnd || screen_y > (win.innerHeight || html.clientHeight || 0) - 50) {
+      scroll_y = atEnd ? 1e6 : y;
+      scroll = true;
+    }
+    if (scroll) win.scrollTo(scroll_x, scroll_y);
+  };
+
+  select.scrollToCursor = function(container) {
+    select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
+  };
+
+  // Used to prevent restoring a selection when we do not need to.
+  var currentSelection = null;
+
+  select.snapshotChanged = function() {
+    if (currentSelection) currentSelection.changed = true;
+  };
+
+  // This is called by the code in editor.js whenever it is replacing
+  // a text node. The function sees whether the given oldNode is part
+  // of the current selection, and updates this selection if it is.
+  // Because nodes are often only partially replaced, the length of
+  // the part that gets replaced has to be taken into account -- the
+  // selection might stay in the oldNode if the newNode is smaller
+  // than the selection's offset. The offset argument is needed in
+  // case the selection does move to the new object, and the given
+  // length is not the whole length of the new node (part of it might
+  // have been used to replace another node).
+  select.snapshotReplaceNode = function(from, to, length, offset) {
+    if (!currentSelection) return;
+
+    function replace(point) {
+      if (from == point.node) {
+        currentSelection.changed = true;
+        if (length && point.offset > length) {
+          point.offset -= length;
+        }
+        else {
+          point.node = to;
+          point.offset += (offset || 0);
+        }
+      }
+    }
+    replace(currentSelection.start);
+    replace(currentSelection.end);
+  };
+
+  select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
+    if (!currentSelection) return;
+
+    function move(point) {
+      if (from == point.node && (!ifAtStart || point.offset == 0)) {
+        currentSelection.changed = true;
+        point.node = to;
+        if (relative) point.offset = Math.max(0, point.offset + distance);
+        else point.offset = distance;
+      }
+    }
+    move(currentSelection.start);
+    move(currentSelection.end);
+  };
+
+  // Most functions are defined in two ways, one for the IE selection
+  // model, one for the W3C one.
+  if (select.ie_selection) {
+    function selectionNode(win, start) {
+      var range = win.document.selection.createRange();
+      range.collapse(start);
+
+      function nodeAfter(node) {
+        var found = null;
+        while (!found && node) {
+          found = node.nextSibling;
+          node = node.parentNode;
+        }
+        return nodeAtStartOf(found);
+      }
+
+      function nodeAtStartOf(node) {
+        while (node && node.firstChild) node = node.firstChild;
+        return {node: node, offset: 0};
+      }
+
+      var containing = range.parentElement();
+      if (!isAncestor(win.document.body, containing)) return null;
+      if (!containing.firstChild) return nodeAtStartOf(containing);
+
+      var working = range.duplicate();
+      working.moveToElementText(containing);
+      working.collapse(true);
+      for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
+        if (cur.nodeType == 3) {
+          var size = cur.nodeValue.length;
+          working.move("character", size);
+        }
+        else {
+          working.moveToElementText(cur);
+          working.collapse(false);
+        }
+
+        var dir = range.compareEndPoints("StartToStart", working);
+        if (dir == 0) return nodeAfter(cur);
+        if (dir == 1) continue;
+        if (cur.nodeType != 3) return nodeAtStartOf(cur);
+
+        working.setEndPoint("StartToEnd", range);
+        return {node: cur, offset: size - working.text.length};
+      }
+      return nodeAfter(containing);
+    }
+
+    select.markSelection = function(win) {
+      currentSelection = null;
+      var sel = win.document.selection;
+      if (!sel) return;
+      var start = selectionNode(win, true),
+          end = selectionNode(win, false);
+      if (!start || !end) return;
+      currentSelection = {start: start, end: end, window: win, changed: false};
+    };
+
+    select.selectMarked = function() {
+      if (!currentSelection || !currentSelection.changed) return;
+      var win = currentSelection.window, doc = win.document;
+
+      function makeRange(point) {
+        var range = doc.body.createTextRange(),
+            node = point.node;
+        if (!node) {
+          range.moveToElementText(currentSelection.window.document.body);
+          range.collapse(false);
+        }
+        else if (node.nodeType == 3) {
+          range.moveToElementText(node.parentNode);
+          var offset = point.offset;
+          while (node.previousSibling) {
+            node = node.previousSibling;
+            offset += (node.innerText || "").length;
+          }
+          range.move("character", offset);
+        }
+        else {
+          range.moveToElementText(node);
+          range.collapse(true);
+        }
+        return range;
+      }
+
+      var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
+      start.setEndPoint("StartToEnd", end);
+      start.select();
+    };
+
+    select.offsetInNode = function(win, node) {
+      var sel = win.document.selection;
+      if (!sel) return 0;
+      var range = sel.createRange(), range2 = range.duplicate();
+      try {range2.moveToElementText(node);} catch(e){return 0;}
+      range.setEndPoint("StartToStart", range2);
+      return range.text.length;
+    };
+
+    // Get the top-level node that one end of the cursor is inside or
+    // after. Note that this returns false for 'no cursor', and null
+    // for 'start of document'.
+    select.selectionTopNode = function(container, start) {
+      var selection = container.ownerDocument.selection;
+      if (!selection) return false;
+
+      var range = selection.createRange(), range2 = range.duplicate();
+      range.collapse(start);
+      var around = range.parentElement();
+      if (around && isAncestor(container, around)) {
+        // Only use this node if the selection is not at its start.
+        range2.moveToElementText(around);
+        if (range.compareEndPoints("StartToStart", range2) == 1)
+          return topLevelNodeAt(around, container);
+      }
+
+      // Move the start of a range to the start of a node,
+      // compensating for the fact that you can't call
+      // moveToElementText with text nodes.
+      function moveToNodeStart(range, node) {
+        if (node.nodeType == 3) {
+          var count = 0, cur = node.previousSibling;
+          while (cur && cur.nodeType == 3) {
+            count += cur.nodeValue.length;
+            cur = cur.previousSibling;
+          }
+          if (cur) {
+            try{range.moveToElementText(cur);}
+            catch(e){return false;}
+            range.collapse(false);
+          }
+          else range.moveToElementText(node.parentNode);
+          if (count) range.move("character", count);
+        }
+        else {
+          try{range.moveToElementText(node);}
+          catch(e){return false;}
+        }
+        return true;
+      }
+
+      // Do a binary search through the container object, comparing
+      // the start of each node to the selection
+      var start = 0, end = container.childNodes.length - 1;
+      while (start < end) {
+        var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
+        if (!node) return false; // Don't ask. IE6 manages this sometimes.
+        if (!moveToNodeStart(range2, node)) return false;
+        if (range.compareEndPoints("StartToStart", range2) == 1)
+          start = middle;
+        else
+          end = middle - 1;
+      }
+      return container.childNodes[start] || null;
+    };
+
+    // Place the cursor after this.start. This is only useful when
+    // manually moving the cursor instead of restoring it to its old
+    // position.
+    select.focusAfterNode = function(node, container) {
+      var range = container.ownerDocument.body.createTextRange();
+      range.moveToElementText(node || container);
+      range.collapse(!node);
+      range.select();
+    };
+
+    select.somethingSelected = function(win) {
+      var sel = win.document.selection;
+      return sel && (sel.createRange().text != "");
+    };
+
+    function insertAtCursor(window, html) {
+      var selection = window.document.selection;
+      if (selection) {
+        var range = selection.createRange();
+        range.pasteHTML(html);
+        range.collapse(false);
+        range.select();
+      }
+    }
+
+    // Used to normalize the effect of the enter key, since browsers
+    // do widely different things when pressing enter in designMode.
+    select.insertNewlineAtCursor = function(window) {
+      insertAtCursor(window, "<br>");
+    };
+
+    select.insertTabAtCursor = function(window) {
+      insertAtCursor(window, fourSpaces);
+    };
+
+    // Get the BR node at the start of the line on which the cursor
+    // currently is, and the offset into the line. Returns null as
+    // node if cursor is on first line.
+    select.cursorPos = function(container, start) {
+      var selection = container.ownerDocument.selection;
+      if (!selection) return null;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && !isBR(topNode))
+        topNode = topNode.previousSibling;
+
+      var range = selection.createRange(), range2 = range.duplicate();
+      range.collapse(start);
+      if (topNode) {
+        range2.moveToElementText(topNode);
+        range2.collapse(false);
+      }
+      else {
+        // When nothing is selected, we can get all kinds of funky errors here.
+        try { range2.moveToElementText(container); }
+        catch (e) { return null; }
+        range2.collapse(true);
+      }
+      range.setEndPoint("StartToStart", range2);
+
+      return {node: topNode, offset: range.text.length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      function rangeAt(pos) {
+        var range = container.ownerDocument.body.createTextRange();
+        if (!pos.node) {
+          range.moveToElementText(container);
+          range.collapse(true);
+        }
+        else {
+          range.moveToElementText(pos.node);
+          range.collapse(false);
+        }
+        range.move("character", pos.offset);
+        return range;
+      }
+
+      var range = rangeAt(from);
+      if (to && to != from)
+        range.setEndPoint("EndToEnd", rangeAt(to));
+      range.select();
+    }
+
+    // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
+    select.getBookmark = function (container) {
+      var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
+      if (from && to) return {from: from, to: to};
+    };
+
+    // Restore a stored selection.
+    select.setBookmark = function(container, mark) {
+      if (!mark) return;
+      select.setCursorPos(container, mark.from, mark.to);
+    };
+  }
+  // W3C model
+  else {
+    // Find the node right at the cursor, not one of its
+    // ancestors with a suitable offset. This goes down the DOM tree
+    // until a 'leaf' is reached (or is it *up* the DOM tree?).
+    function innerNode(node, offset) {
+      while (node.nodeType != 3 && !isBR(node)) {
+        var newNode = node.childNodes[offset] || node.nextSibling;
+        offset = 0;
+        while (!newNode && node.parentNode) {
+          node = node.parentNode;
+          newNode = node.nextSibling;
+        }
+        node = newNode;
+        if (!newNode) break;
+      }
+      return {node: node, offset: offset};
+    }
+
+    // Store start and end nodes, and offsets within these, and refer
+    // back to the selection object from those nodes, so that this
+    // object can be updated when the nodes are replaced before the
+    // selection is restored.
+    select.markSelection = function (win) {
+      var selection = win.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return (currentSelection = null);
+      var range = selection.getRangeAt(0);
+
+      currentSelection = {
+        start: innerNode(range.startContainer, range.startOffset),
+        end: innerNode(range.endContainer, range.endOffset),
+        window: win,
+        changed: false
+      };
+    };
+
+    select.selectMarked = function () {
+      var cs = currentSelection;
+      // on webkit-based browsers, it is apparently possible that the
+      // selection gets reset even when a node that is not one of the
+      // endpoints get messed with. the most common situation where
+      // this occurs is when a selection is deleted or overwitten. we
+      // check for that here.
+      function focusIssue() {
+        if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
+          var selection = cs.window.getSelection();
+          if (!selection || selection.rangeCount == 0) return true;
+          var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
+          return cs.start.node != point.node || cs.start.offset != point.offset;
+        }
+      }
+      if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
+      var win = cs.window, range = win.document.createRange();
+
+      function setPoint(point, which) {
+        if (point.node) {
+          // Some magic to generalize the setting of the start and end
+          // of a range.
+          if (point.offset == 0)
+            range["set" + which + "Before"](point.node);
+          else
+            range["set" + which](point.node, point.offset);
+        }
+        else {
+          range.setStartAfter(win.document.body.lastChild || win.document.body);
+        }
+      }
+
+      setPoint(cs.end, "End");
+      setPoint(cs.start, "Start");
+      selectRange(range, win);
+    };
+
+    // Helper for selecting a range object.
+    function selectRange(range, window) {
+      var selection = window.getSelection();
+      if (!selection) return;
+      selection.removeAllRanges();
+      selection.addRange(range);
+    }
+    function selectionRange(window) {
+      var selection = window.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return false;
+      else
+        return selection.getRangeAt(0);
+    }
+
+    // Finding the top-level node at the cursor in the W3C is, as you
+    // can see, quite an involved process.
+    select.selectionTopNode = function(container, start) {
+      var range = selectionRange(container.ownerDocument.defaultView);
+      if (!range) return false;
+
+      var node = start ? range.startContainer : range.endContainer;
+      var offset = start ? range.startOffset : range.endOffset;
+      // Work around (yet another) bug in Opera's selection model.
+      if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
+          container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
+        offset--;
+
+      // For text nodes, we look at the node itself if the cursor is
+      // inside, or at the node before it if the cursor is at the
+      // start.
+      if (node.nodeType == 3){
+        if (offset > 0)
+          return topLevelNodeAt(node, container);
+        else
+          return topLevelNodeBefore(node, container);
+      }
+      // Occasionally, browsers will return the HTML node as
+      // selection. If the offset is 0, we take the start of the frame
+      // ('after null'), otherwise, we take the last node.
+      else if (node.nodeName.toUpperCase() == "HTML") {
+        return (offset == 1 ? null : container.lastChild);
+      }
+      // If the given node is our 'container', we just look up the
+      // correct node by using the offset.
+      else if (node == container) {
+        return (offset == 0) ? null : node.childNodes[offset - 1];
+      }
+      // In any other case, we have a regular node. If the cursor is
+      // at the end of the node, we use the node itself, if it is at
+      // the start, we use the node before it, and in any other
+      // case, we look up the child before the cursor and use that.
+      else {
+        if (offset == node.childNodes.length)
+          return topLevelNodeAt(node, container);
+        else if (offset == 0)
+          return topLevelNodeBefore(node, container);
+        else
+          return topLevelNodeAt(node.childNodes[offset - 1], container);
+      }
+    };
+
+    select.focusAfterNode = function(node, container) {
+      var win = container.ownerDocument.defaultView,
+          range = win.document.createRange();
+      range.setStartBefore(container.firstChild || container);
+      // In Opera, setting the end of a range at the end of a line
+      // (before a BR) will cause the cursor to appear on the next
+      // line, so we set the end inside of the start node when
+      // possible.
+      if (node && !node.firstChild)
+        range.setEndAfter(node);
+      else if (node)
+        range.setEnd(node, node.childNodes.length);
+      else
+        range.setEndBefore(container.firstChild || container);
+      range.collapse(false);
+      selectRange(range, win);
+    };
+
+    select.somethingSelected = function(win) {
+      var range = selectionRange(win);
+      return range && !range.collapsed;
+    };
+
+    select.offsetInNode = function(win, node) {
+      var range = selectionRange(win);
+      if (!range) return 0;
+      range = range.cloneRange();
+      range.setStartBefore(node);
+      return range.toString().length;
+    };
+
+    function insertNodeAtCursor(window, node) {
+      var range = selectionRange(window);
+      if (!range) return;
+
+      range.deleteContents();
+      range.insertNode(node);
+
+      // work around weirdness where Opera will magically insert a new
+      // BR node when a BR node inside a span is moved around. makes
+      // sure the BR ends up outside of spans.
+      if (window.opera && isBR(node) && isSpan(node.parentNode)) {
+        var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
+        outer.insertBefore(node, p.nextSibling);
+        var textAfter = "";
+        for (; next && next.nodeType == 3; next = next.nextSibling) {
+          textAfter += next.nodeValue;
+          removeElement(next);
+        }
+        outer.insertBefore(makePartSpan(textAfter, window.document), node.nextSibling);
+      }
+      range = window.document.createRange();
+      range.selectNode(node);
+      range.collapse(false);
+      selectRange(range, window);
+    }
+
+    select.insertNewlineAtCursor = function(window) {
+      if (webkit)
+        document.execCommand('insertLineBreak');
+      else
+        insertNodeAtCursor(window, window.document.createElement("BR"));
+    };
+
+    select.insertTabAtCursor = function(window) {
+      insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
+    };
+
+    select.cursorPos = function(container, start) {
+      var range = selectionRange(window);
+      if (!range) return;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && !isBR(topNode))
+        topNode = topNode.previousSibling;
+
+      range = range.cloneRange();
+      range.collapse(start);
+      if (topNode)
+        range.setStartAfter(topNode);
+      else
+        range.setStartBefore(container);
+
+      return {node: topNode, offset: range.toString().length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      var win = container.ownerDocument.defaultView,
+          range = win.document.createRange();
+
+      function setPoint(node, offset, side) {
+        if (offset == 0 && node && !node.nextSibling) {
+          range["set" + side + "After"](node);
+          return true;
+        }
+
+        if (!node)
+          node = container.firstChild;
+        else
+          node = node.nextSibling;
+
+        if (!node) return;
+
+        if (offset == 0) {
+          range["set" + side + "Before"](node);
+          return true;
+        }
+
+        var backlog = []
+        function decompose(node) {
+          if (node.nodeType == 3)
+            backlog.push(node);
+          else
+            forEach(node.childNodes, decompose);
+        }
+        while (true) {
+          while (node && !backlog.length) {
+            decompose(node);
+            node = node.nextSibling;
+          }
+          var cur = backlog.shift();
+          if (!cur) return false;
+
+          var length = cur.nodeValue.length;
+          if (length >= offset) {
+            range["set" + side](cur, offset);
+            return true;
+          }
+          offset -= length;
+        }
+      }
+
+      to = to || from;
+      if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
+        selectRange(range, win);
+    };
+  }
+})();