getSelection 添加 html,但防止嵌套 html

getSelection add html, but prevent nesting of html

我有一个内容可编辑字段,我可以在其中输入和 html 格式化 som 文本(select 文本并单击按钮在其周围添加 <span class="word"></span>)。

我使用以下函数:

function highlightSelection() {       
  if (window.getSelection) {
  
    let sel = window.getSelection();

    

    if (sel.rangeCount > 0) {

      if(sel.anchorNode.parentElement.classList.value) {
      

        let range = sel.getRangeAt(0).cloneRange();
        let newParent =  document.createElement('span');
        newParent.classList.add("word");
        range.surroundContents(newParent);
        sel.removeAllRanges();
        sel.addRange(range);
      } else {
        console.log("Already in span!");
        // end span - start new.
      
      }
    }
}

}

但如果我有:

    Hello my 
<span class="word">name is</span> 
Benny

并且我 select “是”并单击我的按钮我需要防止 html 嵌套,所以而不是

    Hello my 
<span class="word">name <span class="word">is</span></span> Benny

我需要:

    Hello my 
<span class="word">name</span> 
<span class="word">is</span> 
Benny

我尝试检查父级 class,看看它是否已设置,但我如何防止嵌套 html - 在插入符开始位置关闭跨度标记,添加和结束添加插入符号位置以便 html 不会嵌套?

还应考虑 selection 之后是否有包含在跨度中的元素:

所以:

Hello my 

    <span class="word">name is Benny</span>

select再次点击 IS 并点击我的按钮给出:

Hello my 

    <span class="word">name</span> 
    <span class="word">is</span> 
    <span class="word">Benny</span>

感谢任何帮助!

提前致谢。

一种方法是多次执行此操作。

首先你包装你的内容,就像你现在所做的那样几乎是盲目的。
然后,您检查此内容中是否有一些 .word 内容。如果是这样,您将其内容提取到您刚刚创建的新包装器中。
然后,检查新包装器本身是否在 .word 容器中。 如果是这样,您将获得选择之前的内容并将其包装在自己的新包装器中。您对选择后的内容执行相同的操作。
在这个阶段,我们可能在第一个容器中包含三个 .word 个容器。因此,我们必须提取初始内容并将其删除。我们的三个包装器现在是独立的。

function highlightSelection() {       
  if (window.getSelection) {
    const sel = window.getSelection();
    if (sel.rangeCount > 0) {
      const range = sel.getRangeAt(0).cloneRange();
      if (range.collapsed) { // nothing to do
        return;
      }
      // first pass, wrap (almost) carelessly
      wrapRangeInWordSpan(range);
      // second pass, find the nested .word
      // and wrap the content before and after it in their own spans
      const inner = document.querySelector(".word .word");
      if (!inner) {
        // there is a case I couldn't identify correctly
        // when selecting two .word start to end, where the empty spans stick around
        // we remove them here
        range.startContainer.parentNode.querySelectorAll(".word:empty")
          .forEach((node) => node.remove());
        return;
      }
      const parent = inner.closest(".word:not(:scope)");
      const extractingRange = document.createRange();
      // wrap the content before
      extractingRange.selectNode(parent);
      extractingRange.setEndBefore(inner);
      wrapRangeInWordSpan(extractingRange);
      // wrap the content after
      extractingRange.selectNode(parent);
      extractingRange.setStartAfter(inner);
      wrapRangeInWordSpan(extractingRange);

      // finally, extract all the contents from parent
      // to its own parent and remove it, now that it's empty
      moveContentBefore(parent)
    }
  }
}
document.querySelector("button").onclick = highlightSelection;

function wrapRangeInWordSpan(range) {
  if (
    !range.toString().length && // empty text content
    !range.cloneContents().querySelector("img") // and not an <img>
  ) {
    return; // empty range, do nothing (collapsed may not work)
  }
  const content = range.extractContents();
  const newParent = document.createElement('span');
  newParent.classList.add("word");
  newParent.appendChild(content); 
  range.insertNode(newParent);
  // if our content was wrapping .word spans,
  // move their content in the new parent
  // and remove them now that they're empty
  newParent.querySelectorAll(".word").forEach(moveContentBefore);
}
function moveContentBefore(parent) {
  const iterator = document.createNodeIterator(parent);
  let currentNode;
  // walk through all nodes
  while ((currentNode = iterator.nextNode())) {
    // move them to the grand-parent
    parent.before(currentNode);
  }
  // remove the now empty parent
  parent.remove();
}
.word {
  display: inline-block;
  border: 1px solid red;
}
[contenteditable] {
  white-space: pre; /* leading spaces will get ignored once highlighted */
}
<button>highlight</button>
<div contenteditable
  >Hello my <span class="word">name is</span> Benny</div>

但请注意,这只是一个粗略的概念证明,我没有进行任何繁重的测试,很可能会出现奇怪的情况,它会失败(内容可编辑是一场噩梦)。
此外,这不处理复制粘贴或拖放 HTML 内容的情况。

我将您的代码修改为可行的方法:

document.getElementById('btn').addEventListener('click', () => {
  if (window.getSelection) {
    var sel = window.getSelection(),
    range = sel.getRangeAt(0).cloneRange();
    
    sel.anchorNode.parentElement.className != 'word' ? 
      addSpan(range) : console.log('Already tagged');
   }
});

const addSpan = (range) => {
  var newParent = document.createElement('span');
  newParent.classList.add("word");
  range.surroundContents(newParent);
}
.word{color:orange}button{margin:10px 0}
<div id="text">lorem ipsum Benny</div>
<button id="btn">Add span tag to selection</button>