我试图创建一些在单击时会添加 Markdown 字符的按钮,所以我使用了 Selection API
获取选定的文本并添加字符,然后返回 caret
到所选文本的结束位置,一切都正确完成。
当我尝试按下撤消 Ctrl+z 时,它不会返回到添加 Markdown 字符之前的最后一个文本,我知道这是因为我更改了文本节点值。
有没有办法在不影响节点文本并应用撤消、重做的情况下做到这一点?
let text = document.getElementById('test'),
btn = document.getElementById('btn')
//function to replace text by index
String.prototype.replaceAt = function(start, end, replacement) {
let text = '';
for (let i = 0; i < this.length; i++) {
if (i >= start && i < end) {
text += ''
if (i === end - 1) {
text += replacement
}
} else {
text += this[i]
}
}
return text
}
function addNewStr(callback) {
var sel = window.getSelection()
try {
var range = sel.getRangeAt(0),
r = document.createRange()
//check if there's text is selected
if (!sel.isCollapsed) {
let startPos = sel.anchorOffset,
endPos = sel.focusOffset,
node = sel.anchorNode,
value = sel.anchorNode.nodeValue,
selectedText = value.substring(startPos, endPos),
parent = node.parentNode
//function to determine if selection start from left to right or right to left
function checkPos(callback) {
if (startPos > endPos) {
return callback(startPos, endPos)
} else {
return callback(endPos, startPos)
}
}
if (typeof callback === 'function') {
//getting the new str from the callback
var replacement = callback(selectedText),
caretIndex;
//apply changes
node.nodeValue = checkPos(function(end, start) {
return node.nodeValue.replaceAt(start, end, replacement)
})
//check if the returned text from the callback is less or bigger than selected text to move caret to the end of selected text
if (replacement.length > selectedText.length) {
caretIndex = checkPos(function(pos) {
return pos + (replacement.length - selectedText.length);
})
} else if (selectedText.length > replacement.length) {
caretIndex = checkPos(function(pos) {
return (pos - selectedText.length) + (replacement.length);
})
}
//back caret to the end of the new position
r.setStart(parent.childNodes[0], caretIndex)
r.collapse(true)
sel.removeAllRanges()
sel.addRange(r)
}
}
} catch (err) {
console.log("Nothing is selected")
}
}
btn.addEventListener("click", function() {
addNewStr(function(str) {
return '__' + str + '__'
})
})
<div contenteditable="true" id="test" placeholder="insertText">
try to select me
</div>
<button id="btn">to strong</button>
最佳答案
因为您正在以编程方式更改内容,所以您将不得不以编程方式撤消它。
在您的按钮单击处理程序中:
const undoStack = [];
function onButtonClick (e) {
// capture the existing state
const textContent = text.innerText;
// create a function to set the div content to its current value
const undoFn = () => text.innerText = textContent;
// push the undo function into the array
undoStack.push(undoFn);
// ...then do whatever the button does...
}
有了它,您可以收听 ctrl-z
并调用最近的撤消函数:// convenience to determine whether a keyboard event should trigger an undo
const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' && (ctrlKey || metaKey);
// keydown event handler for undo
const keydown = (e) => {
if(isUndo(e) && undos.length) {
e.preventDefault();
undos.pop()();
}
}
// listen for keydowns
document.body.addEventListener('keydown', keydown);
还有一些其他的考虑因素,例如某些用户操作是否应该清除撤消堆栈,但这是基本思想。概念验证演示
为了清楚起见,我已经替换了您的内容修改代码,只需在每次点击时添加一个数字。
const div = document.getElementById('test');
const button = document.querySelector('button');
const undos = [];
button.addEventListener('click', e => {
const text = div.innerText;
undos.push(() => div.innerText = text);
div.innerText += ` ${undos.length} `;
});
const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' && (ctrlKey || metaKey);
const keydown = (e) => {
if(isUndo(e) && undos.length) {
e.preventDefault();
undos.pop()();
}
}
document.body.addEventListener('keydown', keydown);
<div contenteditable="true" id="test" placeholder="insertText">
this is some text
</div>
<button id="btn">to strong</button>
关于javascript - 更新选定文本时撤消/重做不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69230546/