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>
我有一个内容可编辑字段,我可以在其中输入和 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>