我在混合内容(HTML 和文本)的字符串中获取准确的起始位置时遇到了挑战。用户将内容输入到 contenteditable div 中。我无法使用文本区域,因为 HTML 标签可以添加到内容中,而文本区域除了文本之外不接受任何内容。
在应用程序中,用户输入文本,然后他们有机会选择要突出显示的文本(应用程序向所选文本添加
<span class="hilite"></span>
标签)和“标签”(<span>{{selected text}}<sup>{{tagname}}</sup></span>
标签),以便字符串看起来像这个:
a very <span class="hilite">unhappy</span> person <span>made life very difficult<sup>problem</sup></span> for another person
项目似乎是由浏览器添加的(contenteditable div的独特功能?)任何时候有多个连续的空格,这可以显示为“
”(常规空格在前)或“
”
”(最后一个常规空格)或一系列
空格,开头或结尾有一个常规空格。
因为这是用户驱动的输入,所以永远不知道在字符串中会找到多少个标签或
空格。无论哪种方式,目标都是计算 literal html 字符串中直到所选文本开头的每个字符。
例如,如果用户想要“标记”“人”的第二个实例,则用户突出显示该单词,然后右键单击将弹出一个包含所选文本的模式。在右键单击时,我需要获得所选文本在当时存在的整个字符串的上下文中的准确位置。当然,我不知道有多少个 span、sups 或
元素,或者可能有多少个“person”实例。
这是我的右键单击的基本事件处理程序(使用 jQuery):
$('.editableDiv').on('contextmenu', function(e) {
e.preventDefault();
let fullString = $(this).html();
let selectedText = window.getSelection();
let textStart = {{what goes here is the problem}}
$.ajax(
{{send arguments to server,
do stuff,
return the amended string
replace the contents of the $(this) with the returned string}}
);
});
具体问题是如何准确设置textStart的值。
JavaScript 的
string.indexOf(person)
不起作用,因为它只能找到 person 的第一个实例,而且我永远不会知道自从用户输入文本并选择要操作的文本以来会有多少个实例。
我也尝试过像这样遍历 DOM(用上面的 selectedText 调用,如
selection
):
function findTheStartPosition(selection) {
let range = selection.getRangeAt(0);
let startContainer = range.startContainer;
let start = 0;
for (let node of Array.from(startContainer.parentElement.childNodes)) {
if (node === startContainer) {
break;
}
if (node.nodeType === Node.TEXT_NODE) {
start += node.textContent.length;
} else if (node.nodeType === Node.ELEMENT_NODE) {
let tempDiv = document.createElement('div');
tempDiv.appendChild(node.cloneNode(true));
start += tempDiv.innerHTML.length;
}
}
start += range.startOffset;
return start;
}
这效果很好,除非有
元素,因为 range.startOffset
将所有
字符计为单个空格,因此位置不准确。
我试图避免剥离所有标签和空格的主字符串,因为它需要跟踪剥离的内容以及剥离的每个项目的长度(包括每个
的 6 个字符的计数),然后必须重建字符串以保留标签(但不保留多余的空格)....噩梦。
我需要的是一种简单、可靠的方法来获取用户选择的文本在字符串中的起始位置。
我建议:
ProseMirror 是一个(构建框架)编辑器,它在底层使用
contenteditable
。
您也许可以修改 ProseMirror 基本示例 来满足您的需求。通过 let {from, to} = state.selection
可以访问
选择的开始和结束。 (查看 tooltips 示例。)这些示例支持插入 HTML 对象,例如图像和
<HR>
。
我认为 ProseMirror 比普通的
contenteditable
<div>
有几个优点。但它们并非没有成本,例如增加的复杂性和陡峭的学习曲线。 (对于复杂的利基用例,您可能必须定义自己的文档语法。)
所以另一种选择是简单地模拟 ProseMirror 的方法:
contenteditable
<div>
分开跟踪文档内容。这可以避免您遇到的插入空格的
问题。 (如果 contenteditable
在所有平台/浏览器上的行为完全相同,我也会感到惊讶)。ProseMirror 提供了一套用于构建丰富的工具和概念 文本编辑器,使用受以下启发的用户界面 所见即所得,但要努力避免其中的陷阱 编辑风格。
ProseMirror 的主要原则是您的代码获得完全控制 文档以及它会发生什么。该文档不是 blob HTML,而是一个自定义数据结构,仅包含以下元素 您明确允许它包含您指定的关系。 所有更新都通过一个点,您可以在其中检查它们并 对他们做出反应。
核心库不是一个容易插入的组件——我们正在优先考虑 模块化和可定制性胜过简单性,希望在 未来,人们将根据以下内容分发嵌入式编辑器 散文镜。因此,这更像是乐高套装,而不是火柴盒汽车。