【问题标题】:getSelection add html, but prevent nesting of htmlgetSelection 添加 html,但防止 html 嵌套
【发布时间】:2021-10-25 06:08:56
【问题描述】:

我有一个内容可编辑字段,我可以在其中输入和 html 格式的 som 文本(选择文本并单击一个按钮以在其周围添加 <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

并且我选择“is”并单击我的按钮,我需要防止 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

我尝试检查父类,看看它是否已设置,但是如何防止嵌套 html - 在插入符号开始位置关闭跨度标记,添加并在插入符号位置末尾添加,这样 html 就不会嵌套?

还要考虑span中是否有选择后的元素:

所以:

Hello my 

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

再次选择 IS 并单击我的按钮:

Hello my 

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

感谢任何帮助!

提前致谢。

【问题讨论】:

  • 我已经这样做了 - 但我没有找到有效的帮助或答案
  • 但是如果你点击edit然后[&lt;&gt;],你可以创建一个minimal reproducible example所以我们不必这样做
  • let text = sel.getRangeAt(0).cloneRange().text ?

标签: javascript getselection


【解决方案1】:

一种方法是多次通过。

首先你包装你的内容,几乎就像你现在正在做的那样。
然后,您检查此内容中是否有一些 .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 内容的情况。

【讨论】:

    【解决方案2】:

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

    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>

    【讨论】:

      猜你喜欢
      • 2010-12-30
      • 2022-11-04
      • 1970-01-01
      • 1970-01-01
      • 2014-06-19
      • 2011-04-04
      • 2022-11-24
      • 2015-10-28
      • 1970-01-01
      相关资源
      最近更新 更多