Javascript-форум (https://javascript.ru/forum/)
-   Events/DOM/Window (https://javascript.ru/forum/events/)
-   -   Как найти тег, соответствующий выделению? (https://javascript.ru/forum/events/80575-kak-najjti-teg-sootvetstvuyushhijj-vydeleniyu.html)

Kiten 22.06.2020 21:25

Как найти тег, соответствующий выделению?
 
Всем привет!
Буду благодарен, если мне помогут разъяснить одну ситуацию. Я уже задал вопрос на стаке, но что-то эти индусы меня не поняли, или не заметили..
В общем, такая проблема: разрабатываю функционал, который форматирует выделенный текст. Я приведу в адаптированном виде часть своего кода, которая оборачивает выделенный текст в тег "h1".
Функция активируется при клике по кнопке. При первом клике выделенный текст превращается в заголовок, а при повторном клике теги удаляются, и мы видим предыдущий текст.
Проблема в том, что если убрать выделение (например выделить что-то другое), и снова выделить заголовок, и нажать кнопку-триггер, то результат будет иной - теги создаются снова, и заголовок становится больше.
Я оперирую методами Selection и Range. Получается, что при первом вызове создается range, выделенный текст оборачивается в тег h1, и все это входит в selection. Но при повторном выделении того же самого текста теги h1 выпадают из selection, и срабатывает та часть функции, которая создает теги (вместо той, которая их удаляет).

Для наглядности приведу ссылку на JsFiddle:
https://jsfiddle.net/narzantaria/2ex6bnq3/2/

А также приведу код. Вот листинг html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="h1-trigger">Turn selection</button>
  <button id="check">Check selection status</button>
  <div id="editor">This is the text where we can select some word by double-clicking, and press the "h1-trigger button". The function wraps the selection with h1 tags. When clicked again it will unwrap the selection and return to previous text. But if we unselect the wrapped word (h1 heading), select by double-clich again, and press the button, it does another result. The second button shows the selection status with console log. Why re-selection doesn't contain the created tahs?</div> 
</body>
</html>

И JavaScript:
document.getElementById('h1-trigger').addEventListener('click', function () {
  headerFormatter();
});

document.getElementById('check').addEventListener('click', function () {
  let sel = window.getSelection();
  let range = sel.getRangeAt(0).cloneRange();
  let rangeProxy = sel.getRangeAt(0).cloneContents();
  console.log(sel);
  console.log(range);
  console.log(rangeProxy);
});

function headerFormatter() {
  let newTag = document.createElement('h1');
  if (window.getSelection) {
    let sel = window.getSelection();
    if (sel.rangeCount) {
      let range = sel.getRangeAt(0).cloneRange();
      // create an object from range for querying tags
      let rangeProxy = sel.getRangeAt(0).cloneContents();
      if (rangeProxy.querySelector('h1') || rangeProxy.querySelector('h2') || rangeProxy.querySelector('h3')) {
        let tagContent = rangeProxy.querySelector('h1').innerHTML;
        // compare selection length with queried tag length
        if (range.startOffset == 1) {
          tagContent = tagContent.replace(/(<([^>]+)>)/ig, "");
          range.deleteContents();
          range.insertNode(document.createTextNode(tagContent));
          sel.removeAllRanges();
          sel.addRange(range);
          return;
        }
        else {
          let rangeToString = range.toString().replace(/(<([^>]+)>)/ig, "");
          range.deleteContents();
          range.insertNode(document.createTextNode(rangeToString));
          sel.removeAllRanges();
          sel.addRange(range);
          return;
        }
      } else {
        range.surroundContents(newTag);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  }
}

P.S. Я знаю про Document.execCommand() и formatBlock, но мне нужно обрабатывать именно выделение, а не весь блок. Удачи всем, и мне тоже!)

Белый шум 23.06.2020 20:38

Проверял только в хроме.

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="h1-trigger">Turn selection</button>
  <button id="check">Check selection status</button>
  <div id="editor">This is the text where we can select some word by double-clicking, and press the "h1-trigger button". The function wraps the selection with h1 tags. When clicked again it will unwrap the selection and return to previous text. But if we unselect the wrapped word (h1 heading), select by double-click again, and press the button, it does another result. The second button shows the selection status with console log. Why re-selection doesn't contain the created tags?</div> 
</body>
<script>
document.getElementById('h1-trigger').addEventListener('click', function () {
  headerFormatter();
});

document.getElementById('check').addEventListener('click', function () {
  let sel = window.getSelection();
  let range = sel.getRangeAt(0).cloneRange();
  let rangeProxy = sel.getRangeAt(0).cloneContents();
  console.log(sel);
  console.log(range);
  console.log(rangeProxy);
});

function headerFormatter() {
  if( !window.getSelection ) { return; }
    let sel = window.getSelection();
    if( !sel.rangeCount ) { return; }
    let range = sel.getRangeAt(0).cloneRange();
      
    let node, start, end;
    if( range.startContainer.nodeName == "#text" ){
      	node = range.startContainer.parentNode;
        start = range.startOffset;
        end = range.endOffset;
    } else {
      	node = range.startContainer.childNodes[ range.startOffset ];
        start = 0;
        end = node.childNodes[0].length;
    }
    //console.log( node.closest("h1") );
    if( node.closest("h1") && node.closest("h1").closest("#editor") )
    {
        let childNodes = node.closest("h1").parentNode.childNodes;
        for( var i=0; i<childNodes.length; i++ ){
          if( childNodes[i] == node ) {--i; break;}
        }
        if( childNodes[i].nodeName == "#text" ){
          start += childNodes[i].length;
          end += childNodes[i].length;
          node = node.closest("h1");
          node.outerHTML = node.innerHTML; //удалить тег
          range.setStart(childNodes[i], start);
          range.setEnd(childNodes[i], end);
          sel.removeAllRanges();
          sel.addRange(range);
          //console.log(range);
        }
    } else {
      	if( !range.collapsed ){
          let newTag = document.createElement('h1');
          range.surroundContents(newTag);
          sel.removeAllRanges();
          sel.addRange(range);
        }
    }
}
</script>
</html>

Kiten 24.06.2020 14:13

Я тоже сделал:
function headerFormatter(arg) {
  let newTag = document.createElement(arg);
  if (window.getSelection) {
    let sel = window.getSelection();
    if (sel.rangeCount) {
      let range = sel.getRangeAt(0).cloneRange();
      // create an object from range for querying tags
      let rangeProxy = sel.getRangeAt(0).cloneContents();
      if (
        rangeProxy.querySelector('h1') ||
        rangeProxy.querySelector('h2') ||
        rangeProxy.querySelector('h3')
      ) {
        let tagContent = rangeProxy.querySelector(arg).innerHTML;
        // compare selection length with queried tag length
        if (range.startOffset == 1) {
          tagContent = tagContent.replace(/(<([^>]+)>)/ig, "");
          range.deleteContents();
          range.insertNode(document.createTextNode(tagContent));
          sel.removeAllRanges();
          sel.addRange(range);
          return;
        }
        else {
          let rangeToString = range.toString().replace(/(<([^>]+)>)/ig, "");
          range.deleteContents();
          range.insertNode(document.createTextNode(rangeToString));
          sel.removeAllRanges();
          sel.addRange(range);
          return;
        }
      }
      if (
        range.commonAncestorContainer.parentNode.nodeName == 'H1' ||
        range.commonAncestorContainer.parentNode.nodeName == 'H2' ||
        range.commonAncestorContainer.parentNode.nodeName == 'H3'
      ) {
        if (range.commonAncestorContainer.parentNode.innerHTML == sel.toString()) {
          let parent = range.commonAncestorContainer.parentNode;
          parent.remove();
          range.deleteContents();
          range.insertNode(document.createTextNode(parent.innerHTML));
          sel.removeAllRanges();
          sel.addRange(range);
        }
        console.log(range.endOffset);
        console.log(sel.toString().length);
        if (range.endOffset == range.commonAncestorContainer.parentNode.innerHTML.length) {
          let parent = range.commonAncestorContainer.parentNode;
          let first = document.createElement("h2");
          let second = document.createTextNode(parent.innerHTML.slice(range.startOffset, range.endOffset));
          first.appendChild(document.createTextNode(parent.innerHTML.slice(0, range.startOffset)));
          newNode = document.createElement("p");
          newNode.appendChild(document.createTextNode("New Node Inserted Here"));
          parent.remove();         
          range.insertNode(second);
          let secondRange = sel.getRangeAt(0).cloneRange();
          sel.removeAllRanges();
          sel.addRange(secondRange);
          secondRange.insertNode(first);          
        }
      }
      else {
        range.surroundContents(newTag);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  }
}


Но возможно, ваш способ более лаконичный, и в духе современного JavaScript.
Спасибо вам большое!


Часовой пояс GMT +3, время: 09:22.