diff --git a/src/range.coffee b/src/range.coffee index c0d44b37..07ee64de 100644 --- a/src/range.coffee +++ b/src/range.coffee @@ -133,42 +133,53 @@ class Range.BrowserRange @tainted = true r = {} - nr = {} - - for p in ['start', 'end'] - node = this[p + 'Container'] - offset = this[p + 'Offset'] - - if node.nodeType is Node.ELEMENT_NODE - # Get specified node. - it = node.childNodes[offset] - # If it doesn't exist, that means we need the end of the - # previous one. - node = it or node.childNodes[offset - 1] - - # if node doesn't have any children, it's a
or
or - # other self-closing tag, and we actually want the textNode - # that ends just before it - if node.nodeType is Node.ELEMENT_NODE and not node.firstChild - it = null # null out ref to node so offset is correctly calculated below. - node = node.previousSibling - - while node.nodeType isnt Node.TEXT_NODE - node = node.firstChild - - offset = if it then 0 else node.nodeValue.length - - r[p] = node - r[p + 'Offset'] = offset + # Look at the start + if @startContainer.nodeType is Node.ELEMENT_NODE + # We are dealing with element nodes + r.start = Util.getFirstTextNodeNotBefore @startContainer.childNodes[@startOffset] + r.startOffset = 0 + else + # We are dealing with simple text nodes + r.start = @startContainer + r.startOffset = @startOffset + + # Look at the end + if @endContainer.nodeType is Node.ELEMENT_NODE + # Get specified node. + node = @endContainer.childNodes[@endOffset] + + if node? # Does that node exist? + # Look for a text node either at the immediate beginning of node + n = node + while n? and (n.nodeType isnt Node.TEXT_NODE) + n = n.firstChild + if n? # Did we find a text node at the start of this element? + r.end = n + r.endOffset = 0 + + unless r.end? + # We need to find a text node in the previous node. + node = @endContainer.childNodes[@endOffset - 1] + r.end = Util.getLastTextNodeUpTo node + r.endOffset = r.end.nodeValue.length + + else # We are dealing with simple text nodes + r.end = @endContainer + r.endOffset = @endOffset + + # We have collected the initial data. + + # Now let's start to slice & dice the text elements! + nr = {} nr.start = if r.startOffset > 0 then r.start.splitText(r.startOffset) else r.start - if r.start is r.end - if (r.endOffset - r.startOffset) < nr.start.nodeValue.length + if r.start is r.end # is the whole selection inside one text element ? + if nr.start.nodeValue.length > (r.endOffset - r.startOffset) nr.start.splitText(r.endOffset - r.startOffset) nr.end = nr.start - else - if r.endOffset < r.end.nodeValue.length + else # no, the end of the selection is in a separate text element + if r.end.nodeValue.length > r.endOffset# does the end need to be cut? r.end.splitText(r.endOffset) nr.end = r.end diff --git a/src/util.coffee b/src/util.coffee index 6b4f3580..8b6f4243 100644 --- a/src/util.coffee +++ b/src/util.coffee @@ -33,6 +33,18 @@ Util.flatten = (array) -> flatten(array) +# Public: decides whether node A is an ancestor of node B. +# +# This function purposefully ignores the native browser function for this, +# because it acts weird in PhantomJS. +# Issue: https://github.com/ariya/phantomjs/issues/11479 +Util.contains = (parent, child) -> + node = child + while node? + if node is parent then return true + node = node.parentNode + return false + # Public: Finds all text nodes within the elements in the current collection. # # Returns a new jQuery collection of text nodes. @@ -59,6 +71,44 @@ Util.getTextNodes = (jq) -> jq.map -> Util.flatten(getTextNodes(this)) +# Public: determine the last text node inside or before the given node +Util.getLastTextNodeUpTo = (n) -> + switch n.nodeType + when Node.TEXT_NODE + return n # We have found our text node. + when Node.ELEMENT_NODE + # This is an element, we need to dig in + if n.lastChild? # Does it have children at all? + result = Util.getLastTextNodeUpTo n.lastChild + if result? then return result + else + # Not a text node, and not an element node. + # Could not find a text node in current node, go backwards + n = n.previousSibling + if n? + Util.getLastTextNodeUpTo n + else + null + +# Public: determine the first text node in or after the given jQuery node. +Util.getFirstTextNodeNotBefore = (n) -> + switch n.nodeType + when Node.TEXT_NODE + return n # We have found our text node. + when Node.ELEMENT_NODE + # This is an element, we need to dig in + if n.firstChild? # Does it have children at all? + result = Util.getFirstTextNodeNotBefore n.firstChild + if result? then return result + else + # Not a text or an element node. + # Could not find a text node in current node, go forward + n = n.nextSibling + if n? + Util.getFirstTextNodeNotBefore n + else + null + Util.xpathFromNode = (el, relativeRoot) -> try result = simpleXPathJQuery.call el, relativeRoot diff --git a/test/helpers/spec_helper.coffee b/test/helpers/spec_helper.coffee index 1f878a58..2ddf6699 100644 --- a/test/helpers/spec_helper.coffee +++ b/test/helpers/spec_helper.coffee @@ -15,7 +15,7 @@ class this.MockSelection @description = data[5] @commonAncestor = @startContainer - while not @commonAncestor.contains @endContainer + while not Util.contains(@commonAncestor, @endContainer) @commonAncestor = @commonAncestor.parentNode @commonAncestorXPath = Util.xpathFromNode($(@commonAncestor))[0]