Let's have contenteditable div. Browser itself manage undo on it. But when additional content changes (or touching selection ranges) are made from script (in addition to user action) then it stops behave as user expected.
In other words when user hit Ctrl+Z then div content is not reverted to previous state.
See following simplified artificial example:
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()
}
})
All written 'a' letters are doubled.
Undo is ok as long as there is no 'a' typed. When user typed 'a' (appended to text as double 'aa') and hits Ctrl+Z, then he expects both 'a' will be removed and cursor moves back to original position.
Instead only one 'a' is reverted on undo and second one added by script remain.
If event is also prevented by preventDefault() (which is not needed in this example, but in my real world example i can hardly avoid it) then all is worse. Because undo reverts previous user action.
I could images that whole undo/redo stuff will be managed by script, but it means implementation of whole undo/redo logic. That's too complicated, possible fragile and with possible many glitches.
Instead I would like tell browser something like that there is atomic change which should be reverted by one user undo. Is this possible?
You can store the "revisions" in an array, then push the innerHTML of the div to it whenever you programmatically change the innerHTML of it.
Then, you can set the innerHTML of the div to the last item in the revisions array whenever the user uses the Ctrl + Z shortcut.
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>
The benefit of this solution is that it will not conflict with the browser's native Ctrl + Z shortcut behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With