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

IT技术 javascript html dom text google-chrome-extension
2021-03-05 10:25:10

主题:

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

如果用户单击editablechrome.contextMenusAPI分类的元素,我会显示上下文菜单选项

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


问题:

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

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

但是,我不能假设我正在与正常的textarea.

可能的细微差别包括:

  • 文本区域隐藏在 中Iframe,这使查找要与之交互的元素的过程变得复杂(document.activeElement可能返回Iframe,而不是实际包含文本的元素)。

  • <textarea>不会是一个<textarea>/<input>所有,而是一个contentEditable <div>.value在这种情况下,方法将不起作用。

所以我正在寻找一种灵活的方法来做到这一点,它可以优雅地处理所有边缘情况。


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

  • 选项 1:
    我最初计划将值存储在系统剪贴板中。然后,我可以想象只是document.execCommand('paste')用来修改元素。但是,在尝试之后,这种方法似乎与我最初的方法具有相同的缺点。(见这个问题

    此外,这种方法会在操作之前删除剪贴板中的任何内容。这是不可取的,任何使用这种方法的解决方案都必须提供解决方法。

  • 选项 2:
    我考虑过的另一个选项是为字符串中的每个字符调度键盘事件。但是,使用此解决方案,您仍然会遇到Iframe问题,并且它不允许您使用特殊的 Unicode 字符。┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
1个回答

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

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

子问题 1:识别目标元素

  • 如果crbug.com/39507已解析,则获取元素很容易。此功能请求已将近 5 年没有任何进展,因此不要对它寄予厚望。替代方法要求您将问题 1 分解为另外两个子问题:识别目标框架,然后选择目标 DOM 元素。

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

  • contextMenus.onClicked事件提供选项卡的 ID ( tab.id) 作为tabs.Tab实例中的一个属性,frameUrl在单独的对象中提供框架的 URL ( )。
  • chrome.tabs.executeScript方法可以直接在框架中运行脚本。
    (目前只有顶级框架或所有框架,针对特定框架的工作正在进行中 - crbug.com/63979,计划用于 Chrome 42)。
    在实现针对特定框架之前,您可以在每个框架中插入内容脚本并将 URL 与frameUrl(或使用下一个方法的组合)进行比较。
  • 假设您已经插入了一个带有chrome.runtime.onMessage侦听器的内容脚本,用于chrome.tabs.sendMessage向由frameId(自 Chrome 41 起)标识的特定框架发送消息
  • 使用该chrome.webNavigation.getAllFrames方法获取给定选项卡中所有帧的列表tabId,然后frameId通过按已知 过滤帧列表来获取目标帧的frameUrl
  • (将来(Chrome 42?),contextMenus.onClicked将获得frameId)。

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

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

如果目标元素是一个<textarea>or <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;

否则,您需要知道是否有内容可编辑元素,如果有,则删除任何选择(如果存在),最后将您想要的文本放在那里。

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 的相关文档:

以下 npm 包非常适合插入文本:github.com/grassator/insert-text-at-cursor
2021-04-25 10:25:10
假设我有一个框架 ID,如何获取该框架的文档?
2021-05-10 10:25:10
Rob,再次感谢您绝对出色的回答以及有关最近和未来 API 功能的重要信息。在授予赏金之前,我会等到 OP 确认此答案对他有效 - 并不是您真的需要它!
2021-05-16 10:25:10