gpt4 book ai didi

javascript - 如何在修改 DOM 后使自定义文本编辑器使用撤消和重做快捷方式?

转载 作者:行者123 更新时间:2023-12-05 06:48:15 24 4
gpt4 key购买 nike

我正在使用 contenteditable 构建一个小型自定义编辑器div 上的属性。

默认情况下,当用户粘贴 HTML 内容时,HTML 会插入到 contenteditable 中div,我不想要。

为了解决这个问题,我使用了一个像这个问题建议的自定义粘贴处理程序:

Stop pasting html style in a contenteditable div only paste the plain text

editor.addEventListener("paste", (e) => {
e.preventDefault();

const text = e.clipboardData.getData('text/plain');

document.execCommand("insertText", false, text.replaceAll("\n", "<div><br></div>"));
})

到目前为止一切顺利。因为 execCommand 已弃用,我使用了替代方法,基本上是使用范围手动插入文本(去除 HTML)。

execCommand() is now obsolete, what's the alternative?

replace selected text in contenteditable div

editor.addEventListener("paste", (e) => {
e.preventDefault();

const text = (e.originalEvent || e).clipboardData.getData("text/plain");

const selection = window.getSelection();
selection.deleteFromDocument();

const range = selection.getRangeAt(0);
range.insertNode(document.createTextNode(text));
range.collapse();
});

直到我注意到撤消和重做命令在粘贴某些内容后不再起作用之前,它一直运行良好。这是因为在自定义粘贴事件中完成了 DOM 修改,这会破坏交易。


在寻找解决方案时,我发现了以下问题:

Allowing contenteditable to undo after dom modification

那里的第一个答案建议使用 execCommand ,已弃用,因此这不是一个好的解决方案

第二个答案建议构建自定义撤消重做堆栈来手动处理所有这些事件。所以我选择了第二种解决方案。


我使用一个简单的数组来构建自定义撤消重做处理程序来存储版本。为此,我使用了 beforeinput 监听撤消和重做事件的事件。

editor.addEventListener("beforeinput", (e) => {
if (e.inputType === "historyUndo") {
e.preventDefault();
console.log("undo");
};

if (e.inputType === "historyRedo") {
e.preventDefault();
console.log("redo");
};
});

这对于撤消非常有效,但是,由于我阻止了默认设置,浏览器撤消/重做堆栈永远不会处于重做状态,所以 beforeinputcmd + z 时永远不会触发监听器或 ctrl + z在 window 上。

我也尝试过使用 input结果相同的事件。

我已经搜索了其他解决方案来使用 JavaScript 处理撤消/重做事件,但是使用 keydown 构建自定义处理程序在这里没有选择,因为每个操作系统都使用不同的快捷方式,尤其是移动设备!

问题

现在,有多种可能的解决方案可以解决我的问题,因此非常欢迎任何可行的解决方案。

有没有办法手动处理粘贴事件并将粘贴的明文插入文档,同时保留撤消/重做功能或手动实现对此类功能的替换?

例子

尝试粘贴一些东西然后撤消,这是行不通的。

const editor = document.getElementById("editor");

editor.addEventListener("paste", (e) => {
e.preventDefault();

const text = (e.originalEvent || e).clipboardData.getData("text/plain").replaceAll("\n", "<div><br></div>");

const selection = window.getSelection();
selection.deleteFromDocument();

const range = selection.getRangeAt(0);
range.insertNode(document.createTextNode(text));
range.collapse();
});
#editor {
border: 1px solid black;
height: 100px;
}
<div id="editor" contenteditable="true"></div>

编辑

修改 ClipboardEvent不起作用,因为它是只读的。调度新事件也不起作用,因为该事件不受信任,因此浏览器不会粘贴内容。

const event = new ClipboardEvent("paste", {
clipboardData: new DataTransfer(),
});

event.clipboardData.setData("text/plain", "blah");

editor.dispatchEvent(event);

最佳答案

我面临同样的问题并使用 this writeup 中讨论的方法解决了它, 用他的原始代码 here .这是一个非常聪明的技巧。最后,他的方法仍然使用 execCommand,但这是对 execCommand 的一种非常狭窄和隐藏的使用,只是为了将整数索引压入 native 撤消堆栈,以便您的“撤消数据”可以在单独的堆栈上进行跟踪。它还具有能够将正常的撤消操作(如恢复键入的输入)与您推送到单独的撤消堆栈的撤消操作穿插的好处。

我不得不修改他的撤消构造函数以使用“输入”元素而不是 contentEditable“div”。因为我已经在我自己的 contentEditable 'div' 中监听 'input',所以我需要对隐藏的输入 stopImmediatePropagation

constructor(callback, zero=null) {
this._duringUpdate = false;
this._stack = [zero];

// Using an input element rather than contentEditable div because parent is already a
// contentEditable div
this._ctrl = document.createElement('input');
this._ctrl.setAttribute('aria-hidden', 'true');
this._ctrl.setAttribute('id', 'hiddenInput');
this._ctrl.style.opacity = 0;
this._ctrl.style.position = 'fixed';
this._ctrl.style.top = '-1000px';
this._ctrl.style.pointerEvents = 'none';
this._ctrl.tabIndex = -1;

this._ctrl.textContent = '0';
this._ctrl.style.visibility = 'hidden'; // hide element while not used

this._ctrl.addEventListener('focus', (ev) => {
window.setTimeout(() => void this._ctrl.blur(), 1);
});
this._ctrl.addEventListener('input', (ev) => {
ev.stopImmediatePropagation(); // We don't want this event to be seen by the parent
if (!this._duringUpdate) {
callback(this.data);
this._ctrl.textContent = this._depth - 1;
} else {
this._ctrl.textContent = ev.data;
}
const s = window.getSelection();
if (s.containsNode(this._ctrl, true)) {
s.removeAllRanges();
}
});
}

我创建了撤消程序和一种捕获 undoerData 的方法,我正在使用它进行粘贴和其他需要在操作发生时访问该范围的操作。为了将其用于粘贴,我在我自己的 contentEditable div(下面称之为 editor)中监听粘贴事件。

const undoer = new Undoer(_undoOperation, null);

const _undoerData = function(operation, data) {
const undoData = {
operation: operation,
range: _rangeProxy(), // <- Capture document.currentSelection() as a range
data: data
}
return undoData;
};

editor.addEventListener('paste', function(e) {
e.preventDefault();
var pastedText = undefined;
if (e.clipboardData && e.clipboardData.getData) {
pastedText = e.clipboardData.getData('text/plain');
}
const undoerData = _undoerData('pasteText', pastedText);
undoer.push(undoerData, editor);
_doOperation(undoerData);
});

然后 _doOperation_undoOperation 开启操作并在 undoerData 中获取信息以采取适当的行动:

const _doOperation = function(undoerData) {
switch (undoerData.operation) {
case 'pasteText':
let pastedText = undoerData.data;
let range = undoerData.range;
// Do whatever is needed to paste the pastedText at the location
// identified in range
break;
default:
// Throw an error or do something reasonable
};
};

const _undoOperation = function(undoerData) {
switch (undoerData.operation) {
case 'pasteText':
let pastedText = undoerData.data;
let range = undoerData.range;
// Do whatever is needed to remove the pastedText at the location
// identified in range
break;
default:
// Throw an error or do something reasonable
};
};

通过此方案和实现的 pasteText 操作,您现在可以复制文本、粘贴 - 输入 - 删除文本 - 输入 - 重新定位光标 - 粘贴,然后撤消 6 次,在第 1 次和第 6 次撤消时调用 _undoOperation其他人只是按照他们应该的方式工作。

关于javascript - 如何在修改 DOM 后使自定义文本编辑器使用撤消和重做快捷方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66854679/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com