Как поместить курсор на место, где он был раньше, после замены HTML-кода в содержательном элементе div?

У меня есть контентный div, в котором я заменяю хэштеги интерактивными ссылками, когда пользователь нажимает на пробел или при вводе. Пользователь записывает:

Люблю, но дома нет рыбы |

Затем он понимает, что совершил ошибку, а затем решает вернуться и написать

Я люблю суши | но дома нет рыбы

#sushi 

заменяется на:

 <a href="https://google.com/sushi>#sushi</a>

Обратите внимание, что | показывает положение, в котором я хочу, чтобы курсор находился, когда пользователь нажимает клавишу пробела. Моя текущая функция "placeCaretAtEnd" помещает курсор в конец div, а НЕ за ссылкой, которую я только что заменил sushi. Есть ли способ изменить мою текущую функцию, чтобы поместить курсор позади ссылки, которую я только что заменил в тексте в позиции, показанной выше, чтобы пользователь мог продолжать печатать без опаски? Итак, в сыром html:

Я люблю ‹a> #sushi‹ / a> | но дома нет рыбы

/**
* Trigger when someone releases a key on the field where you can post remarks, posts or reactions
*/
$(document).on("keyup", ".post-input-field", function (event) {

       // if the user has pressed the spacebar (32) or the enter key (13)
       if (event.keyCode === 32 || event.keyCode === 13) {
          let html = $(this).html();
          html = html.replace(/(^|\s)(#\w+)/g, " <a href=#>$2</a>").replace("<br>", ""); 
          $(this).html(html);
          placeCaretAtEnd($(this)[0]);
       }
  });

 /**
 * Place the caret at the end of the textfield
 * @param {object} el - the DOM element where the caret should be placed 
 */
function placeCaretAtEnd(el) {

    el.focus();
    if (typeof window.getSelection != "undefined"
        && typeof document.createRange != "undefined") {
        var range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (typeof document.body.createTextRange != "undefined"){
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(false);
        textRange.select();
    }
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div contenteditable="true" class="post-input-field">
I love (replace this with #sushi and type space) but there is no fish at home
</div>


person IT-Girl    schedule 16.08.2019    source источник


Ответы (1)


rangy.js имеет модуль текстового диапазона, который позволяет сохранять выделение как индексы выбранных символов, а затем восстановить его. Поскольку ваши модификации не изменяют innerText, похоже, это идеально подходит:

      var sel = rangy.getSelection();
      var savedSel = sel.saveCharacterRanges(this);
      $(this).html(html);
      sel.restoreCharacterRanges(this, savedSel);

Реализация этого вручную требует тщательного обхода DOM внутри contenteditable и тщательной арифметики с индексами; это невозможно сделать с помощью нескольких строк кода.

Ваш placeCaretAtEnd не может поместить курсор после вставленной ссылки, поскольку он не «знает», какая (из возможно нескольких) это ссылка. Вы должны заранее сохранить эту информацию.

/**
* Trigger when someone releases a key on the field where you can post remarks, posts or reactions
*/
$(document).on("keyup", ".post-input-field", function (event) {

       // if the user has pressed the spacebar (32) or the enter key (13)
       if (event.keyCode === 32 || event.keyCode === 13) {
          let html = $(this).html();
          html = html.replace(/(^|\s)(#\w+)/g, " <a href=#>$2</a>").replace("<br>", ""); 
          var sel = rangy.getSelection();
          var savedSel = sel.saveCharacterRanges(this);
          $(this).html(html);
          sel.restoreCharacterRanges(this, savedSel);
       }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.js"></script>
<div contenteditable="true" class="post-input-field">
I love (replace this with #sushi and type space) but there is no fish at home
</div>

person Nickolay    schedule 18.08.2019