Created
November 26, 2013 16:49
-
-
Save simplyb/7661750 to your computer and use it in GitHub Desktop.
absolute ridiculousness
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function getSelection() | |
{ | |
// returns null, or a structure containing startPoint and endPoint, | |
// each of which has node (a magicdom node), index, and maxIndex. If the node | |
// is a text node, maxIndex is the length of the text; else maxIndex is 1. | |
// index is between 0 and maxIndex, inclusive. | |
if (browser.msie) | |
{ | |
var browserSelection; | |
try | |
{ | |
browserSelection = doc.selection; | |
} | |
catch (e) | |
{} | |
if (!browserSelection) return null; | |
var origSelectionRange; | |
try | |
{ | |
origSelectionRange = browserSelection.createRange(); | |
} | |
catch (e) | |
{} | |
if (!origSelectionRange) return null; | |
var selectionParent = origSelectionRange.parentElement(); | |
if (selectionParent.ownerDocument != doc) return null; | |
var newRange = function() | |
{ | |
return doc.body.createTextRange(); | |
}; | |
var rangeForElementNode = function(nd) | |
{ | |
var rng = newRange(); | |
// doesn't work on text nodes | |
rng.moveToElementText(nd); | |
return rng; | |
}; | |
var pointFromCollapsedRange = function(rng) | |
{ | |
var parNode = rng.parentElement(); | |
var elemBelow = -1; | |
var elemAbove = parNode.childNodes.length; | |
var rangeWithin = rangeForElementNode(parNode); | |
if (rng.compareEndPoints("StartToStart", rangeWithin) === 0) | |
{ | |
return { | |
node: parNode, | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
else if (rng.compareEndPoints("EndToEnd", rangeWithin) === 0) | |
{ | |
if (isBlockElement(parNode) && parNode.nextSibling) | |
{ | |
// caret after block is not consistent across browsers | |
// (same line vs next) so put caret before next node | |
return { | |
node: parNode.nextSibling, | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
return { | |
node: parNode, | |
index: 1, | |
maxIndex: 1 | |
}; | |
} | |
else if (parNode.childNodes.length === 0) | |
{ | |
return { | |
node: parNode, | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
for (var i = 0; i < parNode.childNodes.length; i++) | |
{ | |
var n = parNode.childNodes.item(i); | |
if (!isNodeText(n)) | |
{ | |
var nodeRange = rangeForElementNode(n); | |
var startComp = rng.compareEndPoints("StartToStart", nodeRange); | |
var endComp = rng.compareEndPoints("EndToEnd", nodeRange); | |
if (startComp >= 0 && endComp <= 0) | |
{ | |
var index = 0; | |
if (startComp > 0) | |
{ | |
index = 1; | |
} | |
return { | |
node: n, | |
index: index, | |
maxIndex: 1 | |
}; | |
} | |
else if (endComp > 0) | |
{ | |
if (i > elemBelow) | |
{ | |
elemBelow = i; | |
rangeWithin.setEndPoint("StartToEnd", nodeRange); | |
} | |
} | |
else if (startComp < 0) | |
{ | |
if (i < elemAbove) | |
{ | |
elemAbove = i; | |
rangeWithin.setEndPoint("EndToStart", nodeRange); | |
} | |
} | |
} | |
} | |
if ((elemAbove - elemBelow) == 1) | |
{ | |
if (elemBelow >= 0) | |
{ | |
return { | |
node: parNode.childNodes.item(elemBelow), | |
index: 1, | |
maxIndex: 1 | |
}; | |
} | |
else | |
{ | |
return { | |
node: parNode.childNodes.item(elemAbove), | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
} | |
var idx = 0; | |
var r = rng.duplicate(); | |
// infinite stateful binary search! call function for values 0 to inf, | |
// expecting the answer to be about 40. return index of smallest | |
// true value. | |
var indexIntoRange = binarySearchInfinite(40, function(i) | |
{ | |
// the search algorithm whips the caret back and forth, | |
// though it has to be moved relatively and may hit | |
// the end of the buffer | |
var delta = i - idx; | |
var moved = Math.abs(r.move("character", -delta)); | |
// next line is work-around for fact that when moving left, the beginning | |
// of a text node is considered to be after the start of the parent element: | |
if (r.move("character", -1)) r.move("character", 1); | |
if (delta < 0) idx -= moved; | |
else idx += moved; | |
return (r.compareEndPoints("StartToStart", rangeWithin) <= 0); | |
}); | |
// iterate over consecutive text nodes, point is in one of them | |
var textNode = elemBelow + 1; | |
var indexLeft = indexIntoRange; | |
while (textNode < elemAbove) | |
{ | |
var tn = parNode.childNodes.item(textNode); | |
if (indexLeft <= tn.nodeValue.length) | |
{ | |
return { | |
node: tn, | |
index: indexLeft, | |
maxIndex: tn.nodeValue.length | |
}; | |
} | |
indexLeft -= tn.nodeValue.length; | |
textNode++; | |
} | |
var tn = parNode.childNodes.item(textNode - 1); | |
return { | |
node: tn, | |
index: tn.nodeValue.length, | |
maxIndex: tn.nodeValue.length | |
}; | |
}; | |
var selection = {}; | |
if (origSelectionRange.compareEndPoints("StartToEnd", origSelectionRange) === 0) | |
{ | |
// collapsed | |
var pnt = pointFromCollapsedRange(origSelectionRange); | |
selection.startPoint = pnt; | |
selection.endPoint = { | |
node: pnt.node, | |
index: pnt.index, | |
maxIndex: pnt.maxIndex | |
}; | |
} | |
else | |
{ | |
var start = origSelectionRange.duplicate(); | |
start.collapse(true); | |
var end = origSelectionRange.duplicate(); | |
end.collapse(false); | |
selection.startPoint = pointFromCollapsedRange(start); | |
selection.endPoint = pointFromCollapsedRange(end); | |
/*if ((!selection.startPoint.node.isText) && (!selection.endPoint.node.isText)) { | |
console.log(selection.startPoint.node.uniqueId()+","+ | |
selection.startPoint.index+" / "+ | |
selection.endPoint.node.uniqueId()+","+ | |
selection.endPoint.index); | |
}*/ | |
} | |
return selection; | |
} | |
else | |
{ | |
// non-IE browser | |
var browserSelection = window.getSelection(); | |
if (browserSelection && browserSelection.type != "None" && browserSelection.rangeCount !== 0) | |
{ | |
var range = browserSelection.getRangeAt(0); | |
function isInBody(n) | |
{ | |
while (n && !(n.tagName && n.tagName.toLowerCase() == "body")) | |
{ | |
n = n.parentNode; | |
} | |
return !!n; | |
} | |
function pointFromRangeBound(container, offset) | |
{ | |
if (!isInBody(container)) | |
{ | |
// command-click in Firefox selects whole document, HEAD and BODY! | |
return { | |
node: root, | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
var n = container; | |
var childCount = n.childNodes.length; | |
if (isNodeText(n)) | |
{ | |
return { | |
node: n, | |
index: offset, | |
maxIndex: n.nodeValue.length | |
}; | |
} | |
else if (childCount === 0) | |
{ | |
return { | |
node: n, | |
index: 0, | |
maxIndex: 1 | |
}; | |
} | |
// treat point between two nodes as BEFORE the second (rather than after the first) | |
// if possible; this way point at end of a line block-element is treated as | |
// at beginning of next line | |
else if (offset == childCount) | |
{ | |
var nd = n.childNodes.item(childCount - 1); | |
var max = nodeMaxIndex(nd); | |
return { | |
node: nd, | |
index: max, | |
maxIndex: max | |
}; | |
} | |
else | |
{ | |
var nd = n.childNodes.item(offset); | |
var max = nodeMaxIndex(nd); | |
return { | |
node: nd, | |
index: 0, | |
maxIndex: max | |
}; | |
} | |
} | |
var selection = {}; | |
selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset); | |
selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset); | |
selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset)); | |
if(selection.startPoint.node.ownerDocument !== window.document){ | |
return null; | |
} | |
return selection; | |
} | |
else return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment