是否有一种灵活的方法来修改可编辑元素的内容? [英] Is there a flexible way to modify the contents of an editable element?
问题描述
主题:
我正在创建一个 Google Chrome 扩展程序,它通过内容脚本和事件页面与网页交互.
如果用户单击被 editable 的元素,我会显示上下文菜单选项rel="noreferrer">chrome.contextMenus
API.
这些选项就像常用输入文本的快捷方式.当用户单击一个选项时,一些文本会放置在光标位置的元素内.如果用户突出显示了文本,则将其删除.
<小时>问题:
并非所有可编辑元素都可以以相同方式进行修改.
如果元素是一个简单的textarea
,则可以通过实现此解决方案来实现所需的结果:
但是,我不能假设我正在与普通的 textarea
进行交互.
可能的细微差别包括:
文本区域隐藏在
Iframe
中,这使查找要与之交互的元素的过程变得复杂(document.activeElement
可能会返回iframe
,而不是实际包含文本的元素).根本不是
/
,而是一个
contentEditable
..value
方法在这种情况下不起作用.所以我正在寻找一种灵活的方法来做到这一点,它可以优雅地处理所有边缘情况.
<小时>我尝试过的一些解决方案:
- 选项 1:
我最初计划将值存储在系统剪贴板中.然后,我可以想象只使用document.execCommand('paste')
来修改元素.但是,在尝试之后,这种方法似乎与我最初的方法具有相同的缺点.(参见这个问题)
此外,这种方法会在操作前删除剪贴板中的任何内容.这是不可取的,任何使用这种方法的解决方案都必须提供解决方法. - 选项 2:
我考虑过的另一个选择是为字符串中的每个字符调度键盘事件.但是,使用此解决方案,您仍然会遇到Iframe
问题,并且它不允许您使用特殊的 Unicode 字符.┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
解决方案您的问题由两个子问题组成:
- 确定上下文菜单操作的目标元素.
- 在插入符号处插入自定义文本片段(删除任何存在的选择).
子问题1:识别目标元素
- 如果 crbug.com/39507 已解析,然后获取元素很容易.此功能请求已将近 5 年没有任何进展,因此不要对它寄予厚望.替代方法要求您将问题 1 分解为另外两个子问题:识别目标框架,然后选择目标 DOM 元素.
有几个 API 可以帮助识别框架(使用它们的组合,选择最适合您情况的组合):
contextMenus.onClicked
事件提供了选项卡的 ID (tab.id
) 作为 实例中的属性tabs.Tab 和框架的 URL(frameUrl
) 在一个单独的对象中.chrome.tabs.executeScript
方法可以直接在frame中运行脚本.
(目前只有顶级框架或所有框架,针对特定框架的工作正在进行中 - 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:在插入符号处插入文本片段
如果目标元素是
或
,那么你可以简单地使用
//假设:elem 是一个 input 或 textarea 元素.var YOURSTRING = '随便';var start = elem.selectionStart;var end = elem.selectionEnd;elem.value = elem.value.slice(0, start) + YOURSTRING + elem.value.substr(end);//在选定文本后设置光标elem.selectionStart = start + YOURSTRING.length;elem.selectionEnd = elem.selectionStart;
否则,您需要知道是否有内容可编辑元素,如果有,则删除任何选择(如果存在),最后将您想要的文本放在那里.
var elem = document.activeElement;if (elem && elem.isContentEditable) {//之前定义的你的字符串var newNode = document.createTextNode(YOURSTRING);var sel = window.getSelection();//删除之前的选择,如果有的话.sel.deleteFromDocument();//如果选择中没有范围,则添加一个新的,使用//将插入符号设置为输入元素的末尾以避免下一个错误://无法在 'Selection' 上执行 'getRangeAt':0 不是有效索引."如果(sel.rangeCount === 0){sel.addRange(document.createRange());sel.getRangeAt(0).collapse(elem, 1);}//插入新文本var range = sel.getRangeAt(0);range.insertNode(newNode);//现在,将光标设置到文本节点的末尾sel.collapse(newNode, 1);}
上一个示例中使用的 Web 平台 API 的相关文档:
Subject:
I am creating a Google Chrome extension that interacts with web pages via a content script and an event page.
I have context menu options that appear if a user clicks on an element that is categorized as
editable
by thechrome.contextMenus
API.The options act like shortcuts for commonly entered text. When the user clicks an option, some text is placed inside the element at the position of the cursor. If the user has text highlighted, it is deleted.
Problem:
Not all editable elements can be modified the same way.
If the element is a simple
textarea
the desired result can be achieved by implementing this solution:However, I can not assume that I am interacting with a normal
textarea
.Possible nuances include:
The text area being hidden inside an
Iframe
, which complicates the process of finding the element to interact with (document.activeElement
may return theIframe
, rather than the element that actually contains the text).The
<textarea>
not being a<textarea>
/<input>
at all, but rather acontentEditable <div>
. The.value
approach will not work in this case.
So I am searching for a flexible way to do this that can handle all edge cases elegantly.
Some solutions I have tried:
- option 1 :
I originally planned on storing the value in the system clipboard. Then, I could conceivably just usedocument.execCommand('paste')
to modify the element. However, after trying it, this approach seems to have the same drawbacks as my initial approach. (See this question)
Additionally, this approach would delete whatever was in the clipboard before the operation. This is undesirable and any solution that utilizes this approach must provide a work around. - option 2 :
Another option that I have considered is dispatching keyboard events for each character in the string. However, with this solution, you still run into theIframe
problem and it doesn't allow you do use special Unicode characters. ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
解决方案Your problem consists of two subproblems:
- Identify the target element of the contextmenu action.
- Insert a custom text fragment at the caret (remove any selection if it is present).
Subproblem 1: Identifying the target element
- If crbug.com/39507 is resolved, then getting the element is easy. This feature request is almost 5 year old without any progress, so don't get your hopes high on this one. Alternative methods require you to break problem 1 in two more subproblems: Identifying the target frame, and then select the target DOM element.
There are several APIs that help with identifying the frame (use a combination of them, choose whichever combination fits best in your situation):
- The
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
) in a separate object. - The
chrome.tabs.executeScript
method can directly run a script in a frame.
(currently only the top-level frame or all frames, targetting a specific frame is work in progress - crbug.com/63979, planned for Chrome 42).
Until targetting a specific frame is implemented, you could insert the content script in every frame and compare the URL withframeUrl
(or use a combination of the next methods). - Assuming that you have already inserted a content script with a
chrome.runtime.onMessage
listener, usechrome.tabs.sendMessage
to send a message to a specific frame identified byframeId
(since Chrome 41). - Use the
chrome.webNavigation.getAllFrames
method to get a list of all frames in a tab for a giventabId
, then get theframeId
of the target frame by filtering the list of frames by a knownframeUrl
. - (in the future (Chrome 42?),
contextMenus.onClicked
will get theframeId
).
Okay, assuming that you have the correct frame, you can simply use
document.activeElement
to get the target element, because input elements get focused upon click.Subproblem 2: Inserting a text fragment at the caret
If the target element is a
<textarea>
or<input>
, then you can simply use// 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;
Otherwise, you need to know whether there is a content editable element, and if so, remove any selection if existent, and finally put your desired text over there.
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); }
Relevant documentation for the web platform APIs used in the last example:
这篇关于是否有一种灵活的方法来修改可编辑元素的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文- 选项 1: