Reputation: 797
I have a <div>some test phrase<div>
and I need to allow user to select different fragments of text and highlight them in different colors. Also I need to allow user to delete the the highlighting (but keep the text).
I use Angular, but the solution might be in pure JS.
I've got the partial solution received in response to my previous question:
function mark() {
var rng = document.getSelection().getRangeAt(0);
var cnt = rng.extractContents();
var node = document.createElement('MARK');
node.style.backgroundColor = "orange";
node.appendChild(cnt);
rng.insertNode(node);
}
document.addEventListener('keyup', mark);
document.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if (tgt.tagName === 'MARK') {
if (e.ctrlKey) {
var txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
}
document.addEventListener('click', unmark);
::selection {
background: orange;
}
<p>some test phrase</p>
However, if user selects some test
and test phrase
after that, the selections will intersect and the mark tags will be nesting, while I need them to be like this: <mark>some</mark><mark>test phrase</mark>
.
So, the general rule is: the last selection always wins, i.e. its color is always on top. How could I achieve this for any number of selections done?
Also deletion seems not to be working from time to time and I don't know why.
Kind of implemented this, but I won't be surprised if there is a better way to do this.
Upvotes: 0
Views: 172
Reputation: 798
I think you can start with this. You should thouroughly test it if it satisfy your case. Perhaps you should also refactor it to better fit you needs.
function mark() {
let selection = document.getSelection();
if(selection.type !== 'Range') { return;}
let pos = window.placeOfSelections;
let ranges = [];
let start = 0;
Array.prototype.forEach.call(pos.childNodes, function(chD)
{
ranges.push([start, start + chD.textContent.length, chD.nodeName === 'MARK']);
start += chD.textContent.length;
});
let text = pos.textContent;
let range = selection.getRangeAt(0);
let firstNode = range.startContainer;
let lastNode = range.endContainer;
selection.removeAllRanges();
let firstNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === firstNode || node.firstChild === firstNode);
let lastNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === lastNode || node.firstChild === lastNode);
let newSelectionStart = ranges[firstNodeIndex][0] + range.startOffset;
let newSelectionEnd = ranges[lastNodeIndex][0] + range.endOffset;
pos.innerHTML = text;
range.setStart(pos.childNodes[0], newSelectionStart);
range.setEnd(pos.childNodes[0], newSelectionEnd);
let node = document.createElement('MARK');
let cnt = range.extractContents();
node.appendChild(cnt);
range.insertNode(node);
let marks = ranges.filter(r => r[2]);
while(marks.length != 0)
{
let startEnd = marks.shift();
if(startEnd[0]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
continue;
}
if(startEnd[0]>= newSelectionStart && startEnd[0] <= newSelectionEnd)
{
startEnd[0] = newSelectionEnd;
}
else
if(startEnd[1]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
startEnd[1] = newSelectionStart;
}
else
if(startEnd[0] <=newSelectionStart && startEnd[1] >= newSelectionEnd)
{
marks.push([newSelectionEnd, startEnd[1]]);
startEnd[1] = newSelectionStart;
}
let tnStart = 0, tnEnd = 0;
let textNode = Array.prototype.find.call(pos.childNodes, function(tn)
{
tnEnd += tn.textContent.length;
if(tnStart <= startEnd[0] && startEnd[1] <= tnEnd )
{
return true;
}
tnStart += tn.textContent.length ;
});
range.setStart(textNode, startEnd[0] - tnStart);
range.setEnd(textNode, startEnd[1] - tnStart);
node = document.createElement('MARK');
node.appendChild(range.extractContents());
range.insertNode(node);
}
}
window.placeOfSelections.addEventListener('keyup', mark);
window.placeOfSelections.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if ((tgt.tagName === 'MARK' || (e.parentNode && e.parentNode.tagName === "MARK")) && e.ctrlKey) {
let txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
window.placeOfSelections.addEventListener('mousedown', unmark);
mark {background-color: #BCE937 ;}
<p id="placeOfSelections">some test phrase</p>
Upvotes: 2