突出显示用户选择的文本片段而无需嵌套标签

时间:2019-03-13 09:21:59

标签: javascript angular

我有一个<div>some test phrase<div>,我需要允许用户选择不同的文本片段并以不同的颜色突出显示它们。另外,我需要允许用户删除突出显示的内容(但保留文本)。 我使用Angular,但是解决方案可能是纯JS。

我收到了针对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>

但是,如果用户在此之后选择some testtest phrase,则选择将相交并且标记标签将嵌套,而我需要它们像这样:<mark>some</mark><mark>test phrase</mark>。 因此,一般规则是:最后一个选择始终获胜,即其颜色始终位于最上方。我如何才能完成所有选择呢?

删除操作有时也不起作用,我也不知道为什么。

更新:

可以实现这种方法,但是如果有更好的方法可以使我感到惊讶。

Here is the code

1 个答案:

答案 0 :(得分:2)

我认为您可以从这里开始。如果满足您的要求,则应进行彻底测试。也许您还应该重构它,使其更适合您的需求。

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>