如何在替换 contenteditable div 的 html 后将插入符号放在原来的位置?

How to place the caret where it previously was after replacing the html of a contenteditable div?

我有一个 contenteditable div,当用户点击 space 或输入时,我用可点击的 link 替换主题标签。用户记下:

I love but there is no fish at home|

然后他意识到自己犯了一个错误,然后决定回去写

I love #sushi | but there is no fish at home

#sushi 

被替换为:

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

注意 |显示当用户按下 spacebar 时我希望插入符号所在的位置。我当前的 "placeCaretAtEnd" 函数将插入符号放在 div 的末尾,而不是我刚刚用 sushi 替换的 link 后面。有没有办法改变我当前的功能,将插入符放在 link 后面,我刚刚在上面显示的位置替换了文本,以便用户可以继续不小心输入?所以在原始 html:

I love < a> #sushi< /a> | but there is no fish at home

/**
* 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=#></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>

rangy.js 有一个 text range module 可以让您将选择保存为所选字符的索引,然后恢复它。由于您的修改不会改变 innerText,这看起来非常适合:

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

手动执行此操作需要仔细遍历 contenteditable 中的 DOM 并仔细计算索引;这不是几行代码就能完成的。

您的 placeCaretAtEnd 不可能将插入符号放在您插入的 link 之后,因为它 "know" 不是哪个(可能是多个)link 是的。您必须事先保存此信息。

/**
* 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=#></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>