有没有一种灵活的方法来修改可编辑元素的内容?

Is there a flexible way to modify the contents of an editable element?

主题:

我正在创建一个 Google Chrome 扩展,它通过内容脚本和事件页面与网页交互。

如果用户单击被 chrome.contextMenus API 归类为 editable 的元素,我会显示上下文菜单选项。

这些选项就像常用输入文本的快捷方式。当用户单击一个选项时,一些文本将放置在光标位置的元素内。如果用户突出显示了文本,则会将其删除。


问题:

并非所有可编辑元素都可以用相同的方式修改。

如果元素是简单的textarea,则可以通过实施此解决方案获得所需的结果:

但是,我不能假设我正在与正常人互动 textarea

可能的细微差别包括:

所以我正在寻找一种可以优雅地处理所有边缘情况的灵活方法。


我尝试过的一些解决方案:

您的问题由两个子问题组成:

  1. 确定上下文菜单操作的目标元素。
  2. 在插入符号处插入自定义文本片段(删除任何 selection 如果存在)。

子问题 1:识别目标元素

  • 如果crbug.com/39507解决了,那么获取元素就很容易了。这个功能请求已经将近 5 年了,没有任何进展,所以不要对这个抱有太大希望。 替代方法要求您在另外两个子问题中解决问题 1:识别目标框架,然后 select 目标 DOM 元素。

有几个 API 可以帮助识别框架(使用它们的组合,选择最适合您情况的组合):

  • contextMenus.onClicked event provides the tab's ID (tab.id) as a property in an instance of tabs.Tab and the frame's URL (frameUrl) 在单独的对象中。
  • chrome.tabs.executeScript方法可以直接运行一个框架中的脚本
    (目前只有顶级框架或所有框架,针对特定框架的工作正在进行中 - crbug.com/63979,计划 Chrome 42)。
    在实现针对特定帧之前,您可以在每个帧中插入内容脚本并将 URL 与 frameUrl 进行比较(或使用下一种方法的组合)。
  • 假设您已经插入了带有 chrome.runtime.onMessage listener, use chrome.tabs.sendMessage 的内容脚本以将消息发送到由 frameId 标识的特定框架(自 Chrome 41)。
  • 使用chrome.webNavigation.getAllFrames方法获取给定tabId选项卡中所有框架的列表,然后通过筛选框架列表获取目标框架的frameId由一个已知​​的 frameUrl.
  • (以后(Chrome42?),contextMenus.onClicked会得到frameId).

好的,假设您有正确的框架,您可以简单地使用 document.activeElement 来获取目标元素,因为输入元素会在点击时获得焦点。

子问题 2:在插入符处插入文本片段

如果目标元素是 <textarea><input>,那么您可以简单地使用

// Assume: elem is an input or textarea element.
var YOURSTRING = 'whatever';
var start = elem.selectionStart;
var end = elem.selectionEnd;
elem.value = elem.value.slice(0, start) + YOURSTRING + elem.value.substr(end);
// Set cursor after selected text
elem.selectionStart = start + YOURSTRING.length;
elem.selectionEnd = elem.selectionStart;

否则,你需要知道是否有内容可编辑元素,如果有,删除任何 selection 如果存在,最后把你想要的文本放在那里。

var elem = document.activeElement;
if (elem && elem.isContentEditable) {
    // YOURSTRING as defined before
    var newNode = document.createTextNode(YOURSTRING);

    var sel = window.getSelection();
    // Remove previous selection, if any.
    sel.deleteFromDocument();
    // If there is no range in the selection, add a new one, with the
    // caret set to the end of the input element to avoid the next error:
    //"Failed to execute 'getRangeAt' on 'Selection': 0 is not a valid index."
    if (sel.rangeCount === 0) {
        sel.addRange(document.createRange());
        sel.getRangeAt(0).collapse(elem, 1);
    }
    // Insert new text
    var range = sel.getRangeAt(0);
    range.insertNode(newNode);
    // Now, set the cursor to the end of your text node
    sel.collapse(newNode, 1);
}

上一个例子中使用的web平台API的相关文档: