下面是 AI 写的一个自定义的撤销和重做功能,代码很好理解:
class TextUndoRedoManager {
constructor(textareaElement) {
this.textarea = textareaElement;
this.undoValueStack = []; // 撤销栈
this.redoValueStack = []; // 重做栈
this.currentValue = textareaElement.value;
this.undoStartStack = []; // 撤销栈
this.redoStartStack = []; // 重做栈
this.currentStart = textareaElement.selectionStart;
this.undoEndStack = []; // 撤销栈
this.redoEndStack = []; // 重做栈
this.currentEnd = textareaElement.selectionEnd;
// 监听输入事件,记录状态变化
this.textarea.addEventListener('input', () => {
this.recordState();
});
}
// 记录当前状态到撤销栈
recordState() {
// 将当前状态推入撤销栈
this.undoValueStack.push(this.currentValue);
this.undoStartStack.push(this.currentStart);
this.undoEndStack.push(this.currentEnd);
// console.log(this.undoValueStack, this.undoStartStack, this.undoEndStack)
// 更新当前状态为文本区域的最新值
this.currentValue = this.textarea.value;
this.currentStart = this.textarea.selectionStart;
this.currentEnd = this.textarea.selectionEnd;
// console.log(this.currentValue, this.currentStart, this.currentEnd)
// 清空重做栈(因为有了新的操作)
this.redoValueStack = [];
this.redoStartStack = [];
this.redoEndStack = [];
}
// 设置文本,并记录状态
setValue(newValue, newStart, newEnd) {
// 先将当前状态记录一次
this.recordState();
// 更新文本区域的值
this.textarea.value = newValue;
this.textarea.selectionStart = newStart;
this.textarea.selectionEnd = newEnd;
this.currentValue = newValue;
this.currentStart = newStart;
this.currentEnd = newEnd;
}
// 撤销
undo() {
if (this.undoValueStack.length > 0 && this.undoStartStack.length > 0 && this.undoEndStack.length > 0) {
// 将当前状态移入重做栈
this.redoValueStack.push(this.currentValue);
this.redoStartStack.push(this.currentStart);
this.redoEndStack.push(this.currentEnd);
// 从撤销栈弹出上一个状态
const previousValue = this.undoValueStack.pop();
const previousStart = this.undoStartStack.pop();
const previousEnd = this.undoEndStack.pop();
this.textarea.value = previousValue;
this.textarea.selectionStart = previousStart;
this.textarea.selectionEnd = previousEnd;
this.currentValue = previousValue;
this.currentStart = previousStart;
this.currentEnd = previousEnd;
// 可选:触发一个自定义事件,通知其他部分状态改变了
this.textarea.dispatchEvent(new Event('change'));
}
}
// 重做
redo() {
if (this.redoValueStack.length > 0 && this.redoStartStack.length > 0 && this.redoEndStack.length > 0) {
this.undoValueStack.push(this.currentValue);
this.undoStartStack.push(this.currentStart);
this.undoEndStack.push(this.currentEnd);
const nextValue = this.redoValueStack.pop();
const nextStart = this.redoStartStack.pop();
const nextEnd = this.redoEndStack.pop();
this.textarea.value = nextValue;
this.textarea.selectionStart = nextStart;
this.textarea.selectionEnd = nextEnd;
this.currentValue = nextValue;
this.currentStart = nextStart;
this.currentEnd = nextEnd;
this.textarea.dispatchEvent(new Event('change'));
}
}
}
使用方法如下:
// 首先使用 textarea 元素实例化该类,这样 textarea 在被修改时就会自动将历史加入 undoManager 中
const undoManager = new TextUndoRedoManager(messageInput);
// 监听 textarea 的键盘事件,响应 ctrl + z 等输入,并在其中调用对应的 undoManager 方法
messageInput.addEventListener('keydown', function(e) {
if (e.ctrlKey) {
if (e.key === 'Enter') {
// ctrl + enter 提交消息
e.preventDefault();
submitMessage();
} else if (e.key === "z") {
// ctrl + z 撤销
e.preventDefault();
undoManager.undo();
} else if (e.key === "y") {
// ctrl + y 重做
e.preventDefault();
undoManager.redo();
}
} else {
if (e.key === 'Tab') {
// Tab 键补全
e.preventDefault();
handleTabCompletion();
}
}
// 单独的 Enter 键保持默认行为(换行)
});
// 以后 JS 脚本中修改 textarea 时都通过 undoManager.setValue() 来修改,就可以将界面手工输入和脚本输入都加入撤销和重做的历史中,大功告成!
undoManager.setValue(cplMessage, cplOffset, cplOffset)
以上代码没有记录手工移动光标的历史,这个需要根据具体需求来。
浙公网安备 33010602011771号