【问题标题】:How to undo changes made from script on contenteditable div如何撤消对 contenteditable div 脚本所做的更改
【发布时间】:2021-12-19 18:05:48
【问题描述】:

让我们有内容可编辑的 div。浏览器本身管理它的撤消。 但是,当脚本(除了用户操作之外)进行其他内容更改(或触摸选择范围)时,它会停止按用户预期运行。

换句话说,当用户按下 Ctrl+Z 时,div 内容不会恢复到之前的状态。

看下面的简化仿真例子:

https://codepen.io/farin/pen/WNEMVEB

const editor = document.getElementById("editor")
editor.addEventListener("keydown", ev => {  
  if (ev.key === 'a') {
    const sel = window.getSelection()
    const range = window.getSelection().getRangeAt(0)
    const node = range.startContainer;
    const value = node.nodeValue       
    node.nodeValue = value + 'aa'        
    range.setStart(node, value.length + 2)
    range.setEnd(node, value.length + 2)
    ev.preventDefault()
  }
})

所有书写的“a”字母都翻倍。

只要没有输入“a”,就可以撤消。 当用户键入“a”(作为双 'aa' 附加到文本中)并按 Ctrl+Z 时,他预计两个 'a' 都将被删除并且光标会移回原始位置。

取而代之的是,撤消时只恢复一个“a”,而脚本添加的第二个保留。

如果事件也被 preventDefault() 阻止(在本示例中不需要,但在我的真实示例中我几乎无法避免它),那么一切都会变得更糟。 因为撤消会还原以前的用户操作。

我可以想象整个撤消/重做的东西将由脚本管理,但这意味着实现整个撤消/重做逻辑。这太复杂了,可能很脆弱,而且可能有很多故障。

相反,我想告诉浏览器存在原子更改,应该由一个用户撤消还原。这可能吗?

【问题讨论】:

  • 这并不能直接回答您的问题,但是根据我的经验,尝试使用原始内容可编辑 div 构建强大的用户体验是一个问题的兔子洞——您遇到了其中一个问题,但是你可能还不知道还有几十个。我建议放弃并使用像 quilldraft.js 这样的库。
  • @AndrewStegmaier 我目前正在使用羽毛笔。我的情况不需要工具栏。只有自定义操作存在,作为 quill 插件实现。它面临着 quill 的一些限制,我考虑使用纯内容可编辑 div。我有概念证明,直接执行操作效果更好,唯一的问题是撤消/重做。 (我的用例类似于在 facebook 或 twiitter 上发布 creatin,很少有丰富的内容(fb 有提及,twitter 有链接)
  • @AndrewStegmaier 换句话说,我永远不想构建自己的全功能编辑器。但在这种特定情况下,我不确定。使用 quill,我面临一些恼人的故障和 api 限制(比如我可以事后更新内容,wchich 会为用户带来明显的延迟)
  • @AndrewStegmaier 你是对的 :) 但在我看来,正确的解决方案似乎是 TipTap。真的很喜欢它的多功能和简单的界面。所以最终它变成了从 Quill 到 TipTap 的迁移。它解决了我使用 Quill 的问题,并通过可编辑的纯内容避免了地狱。

标签: html editor contenteditable undo


【解决方案1】:

您可以将“修订”存储在一个数组中,然后在您以编程方式更改它的innerHTML 时将divinnerHTML 推送到它。

然后,只要用户使用 Ctrl + Z 快捷方式,您就可以将 div 的 innerHTML 设置为修订数组中的最后一项。

const previousRevisions = []

function saveState() {
  previousRevisions.push(editor.innerHTML)
}

function undoEdit() {
  if (previousRevisions.length > 0) {
    editor.innerHTML = previousRevisions.pop();
  }
}

const editor = document.getElementById("editor")

editor.addEventListener("keydown", ev => {
  if (ev.key === 'a') {
    saveState()
    const sel = window.getSelection()
    const range = window.getSelection().getRangeAt(0)
    const node = range.startContainer;
    const value = node.nodeValue
    node.nodeValue = value + 'a'
    range.setStart(node, value.length + 1)
    range.setEnd(node, value.length + 1)
  } else if (ev.ctrlKey && ev.key == 'z') {
    undoEdit()
  }
})
#editor{width:600px;min-height:250px;border:1px solid black;font-size:24px;margin:0 auto;padding:10px;font-family:monospace;word-break:break-all}
<div id="editor" contenteditable="true">type here </div>

这种方案的好处是不会和浏览器原生的Ctrl + Z快捷键行为冲突。

【讨论】:

  • 看起来不错(有一些增强功能,例如保存插入符号位置...等)
【解决方案2】:

将父级设为 div(如果不是)并使其在每次用户点击 a 时在其中添加跨度,因此新跨度并将其 ​​id 设置为 span-keyword 将具有 aa 作为值/文本。然后检查用户光标是否在它的开头并检查它前面是否没有其他文本并且用户没有在其中执行其他操作。如果没有文本并且没有发生其他操作,请执行以下操作:

document.getElementById('span-keyword').remove();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 2011-01-15
    • 2016-03-07
    • 1970-01-01
    • 1970-01-01
    • 2010-09-26
    相关资源
    最近更新 更多