obsługa przecięcia między znakami wyboru

9

Mam przycisk zaznaczenia w interfejsie użytkownika, kliknięcie którego, każdy wybór użytkownika jest zaznaczony na czerwono. Tutaj nie ma problemów. Osiągam to przezdocument.execCommand("insertHTML")

Mam jednak dodatkowy wymóg, aby po utworzeniu nowego zaznaczenia, który jest przecięciem starych oznaczeń zaznaczeń, czerwone oznaczenie dawnych zaznaczeń powinno zniknąć.

Jako przykład:

Na poniższym obrazku: to i testowanie są zaznaczone. Teraz, jeśli wybiorę jego znak startu i kliknięcia, stare oznaczenia tego i testowania powinny zniknąć, a tylko jego znaki powinny zostać zaznaczone, ponieważ istnieje skrzyżowanie.

wprowadź opis zdjęcia tutaj

kod:

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
źródło

Odpowiedzi:

4

Możesz znaleźć startContainer& endContainerz zaznaczonego tekstu za pomocą getRangeAti porównać je contentContainer. A jeśli nie są równe contentContainer, usuń bg-redklasę.

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
źródło
4

Jeśli nie potrzebujesz obsługi IE, możesz użyć selection.containsNode : https://developer.mozilla.org/en-US/docs/Web/API/Selection/containsNode

Umożliwia to oflagowanie węzłów zawartych w zaznaczeniu. Musisz ustawić flagę replaceContainment na wartość true, aby wykryła tylko częściowo wybrane węzły.

Tak więc po raz pierwszy oflagujesz węzły, które są zawarte przez selekcję o określonej nazwie klasy. Następnie wykonaj polecenie execCommand, aby zastosować swój styl. Następnie usunąłeś znaczniki, które zostały wcześniej oflagowane, ustawiając outerHTML w tych węzłach na innerHTML . Zachujesz styl, który właśnie zastosowałeś, ale usuniesz poprzednie. Lubię to:

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
źródło
0

Próbowałem wykryć, czy „getSelection ”.anchorNode.parentNode ma klasę bg-red i zastąpię ją, ale pojawia się zbyt wiele efektów ubocznych.

Tak więc rozwiązanie z wyrażeniem regularnym ...

(* 1) - Jeśli sprawdzisz kod elementu edytowalnego, zobaczysz jego strukturę: Każda nowa linia to osobny div. Musisz zastąpić każdy symbol nowej linii \nkodem HTML dzielącym linie, aby umożliwić wybór wielu linii.

(* 2) - Przeglądarka wstawia własne poprawki do HTML i zapobiega prawidłowym zamianom. Byłem zmuszony wstawić dowolny ciąg inny niż HTML, który prawdopodobnie nie pojawi się w tekście. A po wszystkich zamianach - wstaw prawidłowy HTML.

(* 3) - Polecam przeanalizować wszystkie wyrażenia regularne tutaj → https://regex101.com/r/j88wc0/1 Główną ideą jest zastąpienie wszystkich podwójnych wyglądów <span class="bg-red"> anything instead of closing span <span class="bg-red">lub </span> anything instead of starting span tag </span>.

Zauważ, że ten kod odświeża cały innerHTML za każdym razem, gdy naciśniesz przycisk. Nie będzie tak dobrze działać z tekstem z kilku tysięcy wierszy)

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
źródło