我正在尝试编写一个Chrome扩展程序,该程序必须能够在输入字段的光标位置插入一个字符。
当输入是实际的HTMLInputElement
(insertAtCaretInput
从另一个堆栈答案中借用)时,这很容易:
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
作为输入字段的其他页面上使用,但我知道这不可能。
我做了一个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)。
感谢您的输入!我得出了类似的结果,甚至设法将光标移到了正确的位置,但是事实证明,当一种类型的字段中的某项内容运行良好时,另一种字段中的内容就会中断。边缘情况,例如尝试输入表情符号而没有其他文本,只会为处理这种简单情况创建太多代码。更不用说当FB决定更新他们的代码时会发生什么。Web开发的一个奇怪世界,在其中向输入字段添加几个字符变得很困难。
@ m3h0w确实是个奇怪的词!但是我可能已经设法使光标的位置正确。输入之后,光标位置0然后我做的
sel = document.getSelection();
,接下来我将光标移动一个字符(这并不意味着只有一个奇怪的Unicode字符的位置)sel.modify("move", "right", "character");
,然后做sel = document.getSelection();
一次比较sel.focusOffset < n
,其中n
期望的位置。的确如此,我继续进行操作sel.modify
直到偏移量正确为止。我认为它适用于Facebook领域。:)(可能需要无限循环的保护)