Created
May 6, 2016 00:43
-
-
Save ephys/db6b7bca4978e2dbccbcbb5a31ba116e to your computer and use it in GitHub Desktop.
Creates a Range around a piece of text in a DOM tree.
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 () { | |
'use strict'; | |
/* | |
Result: | |
========= NEW TEST ========= | |
VM1096:14 Before wrap: <p><strong>Hello</strong> <em>World</em></p> | |
VM1096:19 Wrapping: Hello World | |
VM1096:25 After wrap: <p><section><strong>Hello</strong> <em>World</em></section></p> | |
VM1096:13 ========= NEW TEST ========= | |
VM1096:14 Before wrap: Hello<br> <em>World</em> | |
VM1096:19 Wrapping: Hello World | |
VM1096:25 After wrap: <section>Hello<br> <em>World</em></section> | |
VM1096:13 ========= NEW TEST ========= | |
VM1096:14 Before wrap: <p> Hello World </p> | |
VM1096:19 Wrapping: Hello World | |
VM1096:25 After wrap: <p> <section>Hello World</section> </p> | |
VM1096:13 ========= NEW TEST ========= | |
VM1096:14 Before wrap: <p>Hello World</p> | |
VM1096:19 Wrapping: Hello World | |
VM1096:25 After wrap: <p><section>Hello World</section></p> | |
*/ | |
const tests = [ | |
['<p><strong>Hello</strong> <em>World</em></p>', 'Hello World'], | |
['Hello<br> <em>World</em>', 'Hello World'], | |
['<p> Hello World </p>', 'Hello World'], | |
['<p>Hello World</p>', 'Hello World'] | |
]; | |
tests.forEach(([html, needle]) => { | |
try { | |
console.log('========= NEW TEST ========='); | |
console.log('Before wrap:', html); | |
const test = document.createElement('div'); | |
test.innerHTML = html; | |
console.log('Wrapping:', needle); | |
const range = find(test, needle); | |
const wrapper = document.createElement('section'); | |
range.surroundContents(wrapper); | |
console.log('After wrap:', test.innerHTML); | |
} catch (e) { | |
console.error(e); | |
} | |
}); | |
/** | |
* Creates a range that wraps the part of the node matching str, ignoring tags. | |
* | |
* @param {!Node} node The node containing the text to wrap. | |
* @param {!String} str The string to wrap. | |
* @return {Range} The range or null if the text is not part of the node. | |
*/ | |
function find(node, str) { | |
// TODO startAt parameter | |
const strPos = node.textContent.indexOf(str); | |
if (strPos === -1) { | |
return null; | |
} | |
node.normalize(); | |
// Get the text node that contains the start of [str] | |
const startPos = { pos: strPos }; | |
let startNode = _findPos(node, startPos); // pos is in an object so it can be modified. | |
if (!startNode) { | |
return null; | |
} | |
// Get the text node that contains the end of [str] | |
const endPos = { pos: strPos + str.length }; | |
let endNode = _findPos(node, endPos); | |
if (!endNode) { | |
return null; | |
} | |
if (startNode !== endNode) { | |
// edge case, for <div>matched text</div>, the range should start before <div>, not before "matched text" | |
if (startNode.nextSibling == null && startNode.previousSibling == null && startNode.length <= str.length) { | |
startNode = startNode.parentNode; | |
if (!startNode.previousSibling) { | |
startNode.parentNode.insertBefore(document.createTextNode(''), startNode); | |
} | |
startNode = startNode.previousSibling; | |
startNode.pos = 0; | |
} | |
// edge case, for <div>matched text</div>, the range should end after </div>, not after "matched text" | |
if (endNode.nextSibling == null && endNode.previousSibling == null && endNode.length === endPos.pos) { | |
endNode = endNode.parentNode; | |
if (!endNode.nextSibling) { | |
endNode.parentNode.appendChild(document.createTextNode('')); | |
} | |
endNode = endNode.nextSibling; | |
endPos.pos = 0; | |
} | |
} | |
const range = document.createRange(); | |
range.setStart(startNode, startPos.pos); | |
range.setEnd(endNode, endPos.pos); | |
return range; | |
} | |
/** | |
* Returns the text node which is part of {node} and contains the character with index {args.pos} if {node.textContent}. | |
* Returns Null if {node.textContent.length < args.pos}. | |
* Note: args.pos will be set to the offset of the start of the string inside the text node. | |
*/ | |
function _findPos(node, args) { | |
const pos = args.pos; | |
if (pos < 0) { | |
return null; | |
} | |
// Text node | |
if (node.nodeType === 3) { | |
if (node.length >= args.pos) { | |
return node; | |
} else { | |
args.pos -= node.length; | |
} | |
} | |
// character order in .textContent is generated In-Order, so are we: Children first, starting from left. | |
if (node.firstChild) { | |
const result = _findPos(node.firstChild, args); | |
if (result) { | |
return result; | |
} | |
} | |
if (node.nextSibling) { | |
const result = _findPos(node.nextSibling, args); | |
if (result) { | |
return result; | |
} | |
} | |
return null; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment