如何撤消脚本对 contenteditable div 所做的更改

How to undo changes made from script on contenteditable div

让我们尽情享受 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() 阻止(在这个例子中不需要,但在我的真实世界的例子中我很难避免它)那么一切都会更糟。 因为撤消会恢复之前的用户操作。

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

相反,我想告诉浏览器一些类似的事情,有一个原子更改应该由一个用户撤销来恢复。这可能吗?

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

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

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

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

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&nbsp;</div>

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