manipulação de interseção entre marcas de seleção

9

Eu tenho um botão de marca na interface do usuário, clicando no qual qualquer seleção de usuário é marcada em vermelho. Sem problemas aqui. Eu alcanço issodocument.execCommand("insertHTML")

Mas tenho um requisito adicional de que, se a nova seleção for criada, que é a interseção das marcações antigas, a marcação vermelha da antiga seleção deve desaparecer.

Como um exemplo:

Na imagem a seguir: this e testing estão marcados. Agora, se eu selecionar o teste dele desde o início e a marca de clique, as marcações antigas disso e teste devem desaparecer e apenas o teste deve ser marcado porque há um cruzamento.

insira a descrição da imagem aqui

código:

const button = document.getElementById("button");

button.addEventListener('click', ()=>{
	const s = window.getSelection();
  const selectionStr = s.toString();
  document.execCommand("insertHTML", false, `<span class="bg-red">${selectionStr}<span>`);
})
.bg-red {  
background: red;
}
<div contenteditable="true">
 this is testing  this is testing  this is testing
</div>

<button id="button">mark</button>

asdasd
fonte

Respostas:

4

Você pode encontrar o startContainer& endContainerdo texto selecionado getRangeAte compará-lo com contentContainer. E se eles não forem iguais contentContainer, remova a bg-redclasse.

const button = document.getElementById("button");

button.addEventListener('click', ()=>{
  let contentContainer = document.getElementById("contentContainer");
  const selection = window.getSelection();
  if (selection.rangeCount > 0){
    let startContainer =  selection.getRangeAt(0).startContainer.parentNode;
    let endContainer =  selection.getRangeAt(0).endContainer.parentNode;
    if(startContainer != contentContainer)
      startContainer.classList.remove('bg-red')
    if(endContainer != contentContainer)
      endContainer.classList.remove('bg-red')
  }
  const selectionStr = selection.toString();
  document.execCommand("insertHTML", false, `<span class="bg-red">${selectionStr}<span>`); 
})
.bg-red {  
  background: red;
}
<div id="contentContainer" contenteditable="true">
 this is testing  this is testing  this is testing
</div>

<button id="button">mark</button>

Bahador Raghibizadeh
fonte
4

Se você não precisar de suporte do IE, poderá usar selection.containsNode : https://developer.mozilla.org/en-US/docs/Web/API/Selection/containsNode

Isso permite sinalizar os nós que estão contidos na seleção. Você precisa definir o sinalizador parcialContainment como true, para detectar nós que são selecionados apenas parcialmente.

Portanto, pela primeira vez, você sinaliza os nós contidos na seleção com um nome de classe específico. Então você executa seu execCommand para aplicar seu estilo. Em seguida, você excluiu as tags que foram sinalizadas anteriormente configurando outerHTML nesses nós como innerHTML . Você manterá o estilo que acabou de aplicar, mas removerá os anteriores. Como isso:

const button = document.getElementById("button");

button.addEventListener('click', () => {
  const s = window.getSelection();
  const selectionStr = s.toString();

  tagIntersection(s)
  document.execCommand("insertHTML", false, `<span class="bg-red">${selectionStr}<span>`);
  removeIntersection(s)
})

function tagIntersection(s) {
  const redSpans = document.getElementsByClassName('bg-red')
  for (let i = 0; i < redSpans.length; i++) {
    if (s.containsNode(redSpans[i], true)) {
      redSpans[i].classList.add('to-remove');
    }
  }
}

function removeIntersection(s) {
  // using querySelector because getElements returns a live nodelist 
  // which is a problem when you manipulate the nodes
  const toRemovespans = document.querySelectorAll('.to-remove')
  for (let i = 0; i < toRemovespans.length; i++) {
    toRemovespans[i].outerHTML = toRemovespans[i].innerHTML;
  }
}
.bg-red {
  background: red;
}
<div contenteditable="true" id="editableDiv">
  this is testing this is testing this is testing
</div>

<button id="button">mark</button>

Julien Grégoire
fonte
0

Tentei detectar se 'getSelection'.anchorNode.parentNode tem a classe bg-red e a substitui, mas muitos efeitos colaterais aparecem.

Então, a solução com regexp ...

(* 1) - Se você inspecionar o código do elemento editável por conteúdo, verá sua estrutura: Cada nova linha é uma div separada. Você deve substituir cada símbolo de nova linha \npor HTML "quebra de linha", para permitir seleções de várias linhas.

(* 2) - O navegador insere suas próprias correções no HTML e evita substituições corretas. Fui forçado a inserir qualquer sequência não HTML, que provavelmente não aparecerá no texto. E depois de todas as substituições - insira HTML válido.

(* 3) - Recomendo analisar todas as expressões regulares aqui → https://regex101.com/r/j88wc0/1 A idéia principal é substituir todas as aparências duplas de <span class="bg-red"> anything instead of closing span <span class="bg-red">or </span> anything instead of starting span tag </span>.

Observe que esse código atualiza todo o HTML interno, sempre que você pressiona o botão. Não funcionará tão bem com texto de alguns milhares de linhas)

let button = document.getElementById("button");
let block = document.getElementById("block");

button.addEventListener('click', function(){
  let s = window.getSelection();
  let str = s.toString().replace(/\n/g,'</span></div><div><span class="bg-red">'); // (*1)

  document.execCommand("insertHTML", false, `bubufication`); // (*2)

  block.innerHTML = block.innerHTML
    .replace(/bubufication/, `<span class="bg-red">${str}</span>`)
    .replace(/<span class="bg-red">(.*?)<\/span><span class="bg-red">/g,'$1<span class="bg-red">')
    .replace(/<span class="bg-red">(((?!<\/span>).)*?<span class="bg-red">)/g,"$1")
    .replace(/(<\/span>((?!<span class="bg-red">).)*)<\/span>/g,"$1"); // (*3)
});
.bg-red {
  background-color: red;
}
<div id="block" contenteditable="true">
  <div>this is testing  this is testing  this is testing</div>
  <div>this is testing  this is testing  this is testing</div>
</div>

<button id="button">mark</button>

OPTIMUS PRIME
fonte