Skip to content

Instantly share code, notes, and snippets.

@simplyb
Created November 26, 2013 16:49
Show Gist options
  • Save simplyb/7661750 to your computer and use it in GitHub Desktop.
Save simplyb/7661750 to your computer and use it in GitHub Desktop.
absolute ridiculousness
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