【问题标题】:Programmatically change input value in Facebook's editable div input area以编程方式更改 Facebook 可编辑 div 输入区域中的输入值
【发布时间】:2022-02-27 00:05:06
【问题描述】:

我正在尝试编写一个 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><span> 元素出现并在奇怪的时间合并)我无法弄清楚如何可靠地做到这一点。当我开始与输入交互时,新字符消失或改变位置或光标跳到不可预知的位置。

Facebook 的 HTML 结构示例(Chrome 桌面页面、新帖子或消息输入字段)可编辑 <div> 包含字符串 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;

并且由于触发按键事件的元素是&lt;div&gt;,然后保存&lt;span&gt;元素,然后保存带有实际输入的文本节点,我找到最深的&lt;span&gt;元素并在该元素上执行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 的幕后框架尝试处理新值时会发生奇怪的事情。我尝试了各种技巧,重新定位光标并插入 &lt;span&gt; 元素,类似于 Facebook 在插入时操作 dom 时似乎发生的情况,但最终,所有这些都以某种方式失败。我想这是因为输入区域的状态被保存在某个地方并且与我的修改不同步。

您认为有可能可靠地做到这一点吗?如果可以,如何做到?理想情况下,答案不会特定于 Facebook,但也适用于使用其他元素而不是 HTMLInputElement 作为输入字段的其他页面,但我知道这可能是不可能的。

【问题讨论】:

  • 你能添加一张图片你想改变什么输入字段吗?
  • @Anton 这是创建帖子输入和底部显示的小消息/聊天窗口输入。它们的工作方式似乎相似,因此您可以假设使其与 Create Post 一起使用就足够简单了

标签: javascript html facebook google-chrome-extension


【解决方案1】:

我在 Whatsapp web 上遇到了类似的问题
尝试调度输入事件

target.dispatchEvent(new InputEvent('input', {bubbles: true}));

【讨论】:

  • 我试过了,它似乎既没有被 Whatsapp 也没有被 Facebook 接收。当然在构造函数的字典中有一个数据字段。
  • 你确定 targetElement 是 ContentEditable div 吗?
  • 试试这个函数:function findFirstContentEditable(el){ if(el.children.length==0) return el.children.isContentEditable?el.children:null; for (var i = 0; i
  • 使用该事件确实有帮助,但这只是冰山一角,因为 Facebook 以奇怪的方式合并它在 div 中创建的 span 元素构成输入元素。
  • 是的,但这是我唯一的问题,因为我刚刚使用了docunent.activeElement
【解决方案2】:

我制作了一个Firefox extension,它能够将记住的笔记从上下文菜单粘贴到输入字段中。然而,Facebook Messenger 字段是大量脚本化的 div 和 span - 不是输入字段。我一直在努力让它们工作并按照@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 并调度事件。

对于一个空字段来说更棘手。通常,当用户写一些东西时,&lt;br data-text="true"&gt; 会随着用户的输入变为&lt;span data-text="true"&gt;。 我已经尝试以编程方式进行(添加带有 innerText 的跨度,删除 br)但它破坏了 Messenger 输入。对我有用的是添加一个跨度,调度事件然后删除它!之后,Facebook 像往常一样删除了br,并在我的输入中添加了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 )。

【讨论】:

  • 感谢您的意见!我得到了类似的结果,甚至设法将光标移动到正确的位置,但结果证明,当某项在一种类型的字段中运行良好时,它会在另一种类型的字段中中断。而边缘情况,比如尝试在没有任何其他文本的情况下输入表情符号,只会创建太多代码来处理这种简单的情况。更不用说当 FB 决定更新他们的代码时会发生什么。 Web 开发的奇怪世界,在输入字段中添加几个字符变得很困难。
  • @m3h0w 真是奇怪的词!但我可能已经设法让光标的位置正确。输入后光标位于位置0。然后我执行sel = document.getSelection();,接下来我将光标移动一个字符(这并不意味着奇怪的unicode字符只有一个位置)sel.modify("move", "right", "character");,然后再次执行sel = document.getSelection();比较sel.focusOffset &lt; n,其中n 是所需位置。虽然这是真的,但我继续使用sel.modify 直到偏移量正确。我认为它适用于 Facebook 领域。 :)(可能需要防止无限循环)
  • 男人真棒!我花了很长时间试图更新 Facebook 的“输入”。你救了我的命!非常感谢
【解决方案3】:

@raandremsil@ATP 的答案大多对我有用。 但是,我遇到了一个不工作的案例。

我的扩展程序会显示一个文本列表,当用户单击某个项目时,这些文本将插入到输入中。直到我在修改内容和调度事件之前专注于该字段,它才能正常工作。

const myNewText = 'whatever text I get a click on my list';
elementToDispatchEventFrom.focus();  // <- I had to add this line, focus the field before editing (`elementToDispatchEventFrom` is from raandremsil's answer)
deepestChild.contextText = myNewText; // Edit the text
elementToDispatchEventFrom.dispatchEvent(new InputEvent('input', {bubbles: true})); // Dispatch event to simulate user input

2022 年 2 月 27 日更新:

由于某种原因,这停止了工作。这是我现在要替换输入中的文本,假设它就在光标位置之前。

const selection = window.getSelection()!;
const cursorPosition = selection.focusOffset;

const focusNode = selection.focusNode!;
const doc = focusNode.ownerDocument!;

const range = new Range();
range.setStart(focusNode, cursorPosition - textToReplace.length);
range.setEnd(focusNode, cursorPosition);

selection.removeAllRanges();
selection.addRange(range);

doc.execCommand('insertText', false, myNewText); // Careful, for some reason, this does not work if `myNewText` ends with the space.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    相关资源
    最近更新 更多