如何确定光标是否出现在 contenteditable div 中的某些字符之后

How to determine if cursor comes after certain characters in a contenteditable div

我正在 contenteditable div 中构建一个 autosuggest/autocomplete 功能,但我在确定光标何时出现在不符合自动完成条件的字符之后时遇到了问题。我想做的是 运行 我的自动建议 ajax 请求仅当用户输入“@”句柄时,例如“@someUser”。问题是 contenteditable divs 包含实际的 html,所以我在尝试确定光标前的最后一个字符是否是我批准的字符之一时出错了。我批准的字符是:A-z0-9。我正在使用这个正则表达式:/(&nbsp; $|&nbsp;$|\s$)/,但它只检查空格。我不能简单地否定我批准的字符(比如 [^A-z0-9]),因为 contenteditable 的 HTML 会导致漏报(contenteditable div 可以有类似 <div>test</div> 作为它 innerHTML)。我创建了 this demo 来尝试展示这个问题。 这是它的代码:

document.querySelector('button').addEventListener('click', handleClick);

function handleClick(e) {
  const inputField = document.querySelector('#edit-me');
  const text = inputField.innerHTML;
  if (!text) {
    alert('no text');
  }
  const afterInvalidChar = isAfterInvalidChar(text, getCaretIndex(inputField));
  if (afterInvalidChar) {
    alert('cursor is not after an accepted char');
  } else {
    alert('cursor is after an accepted char');
  }
}

function isAfterInvalidChar(text, caretPosition) {
    // first get text from the beginning until the caret
    let termToSearch = text.slice(0, caretPosition);
    console.log(termToSearch);
    alert('content before cursor: ' + termToSearch);
    const rgxToCheckEnding = /(&nbsp; $|&nbsp;$|\s$)/; // <-- this is where I'm tripping up, that regex only checks for spaces, and it's still not great
    if (rgxToCheckEnding.test(termToSearch)) {
        // the cursor is after a space, but I also need to check for anything
        // that's not one of my accepted contents
        return true;
    } else {
      return false;
    }
}

// source: 
function getCaretIndex (node) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange(),
        caretIndex,
        tmp = document.createElement("div");

    preCaretRange.selectNodeContents(node);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    tmp.appendChild(preCaretRange.cloneContents());
    caretIndex = tmp.innerHTML.length;
    return caretIndex;
}
#edit-me {
  width: 200px;
  height: 200px;
  background-color: lightblue;
}
<h2>Cursor of contenteditable div</h2>
<p>Accepted chars: <code>A-z0-9</code></p>
<button type="button">is cursor after an accepted char?</button>
<div contenteditable id="edit-me"><div>

一个可接受的解决方案应该与 contenteditable div 中的以下文本一起使用(只是基本测试,但你明白了):

如何才能让它只对我批准的字符起作用?如果能完成工作,我愿意接受完全不同的策略。谢谢!

问题是您需要解码 HTML 个实体。
您可以使用此功能轻松完成

function decodeHTML(htmlString) {
    var txt = document.createElement('div');
    txt.innerHTML = htmlString;
    return txt.textContent;
};

基本上它所做的是创建一个虚构的 div 并将 html 代码放在 div 的内容中,当您请求内容时使用textContent。 现在您所要做的就是将 termToSearch 更改为 decodeHTML(text.slice(0, caretPosition)); 并使您的正则表达式检查结尾

请注意,该函数不会实际生成 div 标签,但仍会 return 解码值。这样做的原因是因为 document.createElement() 只是创建一个 HTML 元素的对象而不是一个实际的元素。