I'm trying to make a "annotation" tool - the user can surround some elements in a text (in a div). I need to keep the selection ranges positions of the RAW text.
With this div containing text to annotate:
<div id="annotate">foo bar baz</div>
Using document.getSelection(), and getRangeAt() it possible to use surroundContents() that works. But the problem is that, after having surrounding the first label, I cannot get the "real" text position of the second selection.
After surrounded elements:
<div id="annotate">foo <span class="tag">bar</span> baz</div>
If I select "baz", the anchorStart is 0 instead of 8. That's becauces the "anchorNode" is now a TextNode because there is now a span injected from the surroundContents() method.
I don't find any solution to get the selection position using only the "div" raw content.
NOTE: I tried Rangy library to check if it gives a solution, but no... I also checked how doccano interface does the job, but the code is a bit complicated to decrypt so the method is not easy to understand.
This is an example:
function annotate() {
let selection = document.getSelection();
let range = selection.getRangeAt(0);
let pre = document.querySelector("pre");
pre.innerHTML = `start: ${range.startOffset}`;
let annotation = document.createElement("span");
annotation.classList.add("box");
range.surroundContents(annotation);
selection.empty();
}
.box {
background-color: yellow
}
#annotator {
padding: 1em;
border: 1px solid black;
}
<p>Try first to select "bar" and annotate. Then select "baz" and annotate. You'll see that the startOffset will be 1.
</p>
<div id="annotator">foo bar baz</div>
<button onclick="annotate()">annotate</button>
<pre>results...</pre>
A special note: I currently make regexp "matchAll" iteration on the parent element "innerHTML" to get index of found tag, remove tag length, and continue. That works but it's not very efficient.