Warm tip: This article is reproduced from serverfault.com, please click

javascript-在Facebook的可编辑div输入区域中以编程方式更改输入值

(javascript - Programmatically change input value in Facebook's editable div input area)

发布于 2020-11-27 16:43:39

我正在尝试编写一个Chrome扩展程序,该程序必须能够在输入字段的光标位置插入一个字符。

当输入是实际的HTMLInputElementinsertAtCaretInput从另一个堆栈答案中借用)时,这很容易

function insertAtCaretInput(text) {
  text = text || '';
  if (document.selection) {
    // IE
    this.focus();
    var sel = document.selection.createRange();
    sel.text = text;
  } else if (this.selectionStart || this.selectionStart === 0) {
    // Others
    var startPos = this.selectionStart;
    var endPos = this.selectionEnd;
    this.value = this.value.substring(0, startPos) + text + this.value.substring(endPos, this.value.length);
    this.selectionStart = startPos + text.length;
    this.selectionEnd = startPos + text.length;
  } else {
    this.value += text;
  }
}

HTMLInputElement.prototype.insertAtCaret = insertAtCaretInput;

onKeyDown(e){
  ...
  targetElement = e.target;
  target.insertAtCaret(charToInsert);
  ...
}

但是,当输入实际上在HTML结构中以不同的方式表示时(例如,Facebook的<div>with<span>元素在奇怪的时间出现并合并),我不知道该如何可靠地做到这一点。当我开始与输入进行交互时,新字符会消失或更改位置,或者光标会跳到不可预测的位置。

用于Facebook(Chrome桌面页面,新帖子或消息输入字段)可编辑的HTML结构示例,<div>其中包含string Test

<div data-offset-key="87o4u-0-0" class="_1mf _1mj">
  <span>
    <span data-offset-key="87o4u-0-0">
      <span data-text="true">Test
      </span>
    </span>
  </span>
  <span data-offset-key="87o4u-1-0">
    <span data-text="true"> 
    </span>
  </span>
</div>

到目前为止,这是我最成功的尝试。我像这样扩展span元素(insertTextAtCursor也是从另一个答案中借来的):

function insertTextAtCursor(text) {
  let selection = window.getSelection();
  let range = selection.getRangeAt(0);
  range.deleteContents();
  let node = document.createTextNode(text);
  range.insertNode(node);

  for (let position = 0; position != text.length; position++) {
    selection.modify('move', 'right', 'character');
  }
}

HTMLSpanElement.prototype.insertAtCaret = insertTextAtCursor;

并且由于触发按键事件的元素是a <div>,然后包含<span>元素,这些元素随后包含带有实际输入的文本节点,因此我找到了最深的<span>元素并对该元素执行insertAtCaret:

function findDeepestChild(parent) {
  var result = { depth: 0, element: parent };

  [].slice.call(parent.childNodes).forEach(function (child) {
    var childResult = findDeepestChild(child);
    if (childResult.depth + 1 > result.depth) {
      result = {
        depth: 1 + childResult.depth,
        element: childResult.element,
        parent: childResult.element.parentNode,
      };
    }
  });

  return result;
}

onKeyDown(e){
  ...
  targetElement = findDeepestChild(e.target).parent; // deepest child is a text node
  target.insertAtCaret(charToInsert);
  ...
}

上面的代码可以成功插入字符,但是当Facebook的后台框架尝试处理新值时,会发生奇怪的事情。我尝试了各种技巧,重新定位光标并插入<span>元素,类似于Facebook在插入内容上操纵dom时似乎正在发生的事情,但最终,所有这些都以一种或另一种方式失败。我想这是因为输入区域的状态保存在某处,并且与我的修改不同步。

你认为可以可靠地执行此操作吗?如果可以,如何执行?理想情况下,答案不是特定于Facebook,而是也可以在使用其他元素而不是HTMLInputElement作为输入字段的其他页面上使用,但我知道这不可能。

Questioner
m3h0w
Viewed
0
raandremsil 2020-12-06 20:22:33

我做了一个Firefox扩展程序,它能够将上下文菜单中的已记笔记粘贴到输入字段中。但是,Facebook Messenger字段是脚本化的div和spans-不是输入字段。我一直在努力让他们工作,并按照@ user9977151的建议派遣一个活动对我有帮助!

但是,需要从特定元素调度它,并且你还需要检查Facebook Messenger输入字段是否为空。

空字段将如下所示:

<div class="" data-block="true" data-editor="e1m9r" data-offset-key="6hbkl-0-0">
    <div data-offset-key="6hbkl-0-0" class="_1mf _1mj">
        <span data-offset-key="6hbkl-0-0">
            <br data-text="true">
        </span>
    </div>
</div>

而且不是那样的空

<div class="" data-block="true" data-editor="e1m9r" data-offset-key="6hbkl-0-0">
    <div data-offset-key="6hbkl-0-0" class="_1mf _1mj">
        <span data-offset-key="6hbkl-0-0">
            <span data-text="true">
                Some input
            </span>
        </span>
    </div>
</div>

该事件需要从

<span data-offset-key="6hbkl-0-0">

当你向非空字段添加内容时很简单-只需更改innerText并调度事件即可。

对于空字段,这更棘手。通常,当用户写东西时,<br data-text="true"><span data-text="true">随着用户的输入变化我尝试以编程方式执行此操作(使用innerText添加跨度,删除br),但它破坏了Messenger输入。对我有用的是添加跨度,调度事件然后将其删除!之后,Facebookbr像往常一样删除并添加span了我的输入。

Facebook似乎以某种方式将用户按键存储在其内存中,然后自行输入。

我的代码是

if(document.body.parentElement.id == "facebook"){
    var dc = getDeepestChild(actEl);
    var elementToDispatchEventFrom = dc.parentElement;
    let newEl;
    if(dc.nodeName.toLowerCase() == "br"){
        // attempt to paste into empty messenger field
        // by creating new element and setting it's value
        newEl = document.createElement("span");
        newEl.setAttribute("data-text", "true");
        dc.parentElement.appendChild(newEl);
        newEl.innerText = message.content;
    }else{
        // attempt to paste into not empty messenger field
        // by changing existing content
        let sel = document.getSelection();
        selStart = sel.anchorOffset;
        selStartCopy = selStart;
        selEnd = sel.focusOffset;

        intendedValue = dc.textContent.slice(0,selStart) + message.content + dc.textContent.slice(selEnd);
        dc.textContent = intendedValue;
        elementToDispatchEventFrom = elementToDispatchEventFrom.parentElement;
    }
    // simulate user's input
    elementToDispatchEventFrom.dispatchEvent(new InputEvent('input', {bubbles: true}));
    // remove new element if it exists
    // otherwise there will be two of them after
    // Facebook adds it itself!
    if (newEl) newEl.remove();
}else ...

在哪里

function getDeepestChild(element){
  if(element.lastChild){
    return getDeepestChild(element.lastChild)
  }else{
    return element;
  }
}

并且message.content是一个字符串,我想将其粘贴到Messenger的领域。

此解决方案可以更改Messenger字段的内容,但会将光标移动到该字段的开头-而且我不确定是否可以保持光标的位置不变(因为没有可以更改的selectionStart和selectionEnd)。