如何使用JavaScript将文本插入到textarea中而不干扰编辑历史记录?

8
基本上,我想要的是,如果用户在文本区域中选择一些文本并按下 ctrl + b,则所选文本应被星号包围。因此,我的要求如下:
1)文本区域内容:“hello this is some text”
2)用户选择一些文本并按下ctrl + b,“hello this is some text”(假设粗体部分是文本选择)
3)所以我希望文本区域的内容为:“hello this *is some* text”
4)如果用户按下ctrl + z(或任何撤消操作),则文本区域内容应恢复为“hello this is some text”。
我尝试过 如何使用JavaScript将文本插入到文本区域中?在光标位置插入文本(JavaScript) 等类似方法,但问题在于,在大多数浏览器上执行撤销操作(ctrl + z)时,我希望文本回到步骤1的值,但实际上并没有发生。我知道stackoverflow在其编辑器中实现了自己的撤销重做功能,但我希望不要那么复杂。 必须支持Chrome和Safari。
我考虑的一种方法是定位光标并发出合成键事件。我不知道它是否有效,以及是否存在问题。

1
下面的答案建议使用已弃用的execCommand。请参见https://dev59.com/xFIH5IYBdhLWcg3wNayz - 可能的一种方法是通过自己跟踪编辑状态(在HTML5的本地存储中)并在每个CTRL Z中加载文本区域的前一个状态。 - Avatar
或者您可以使用全局JS变量来存储不同的状态。例如,SCEditor使用undo.js插件来实现此功能,查看var undoStates = [];function storeState() - Avatar
2个回答

6

更新版本

为了在更改文本区域的整体值时保存用户历史记录,不要使用以下方式:

textarea.value = newText;

使用:

textarea.focus();
document.execCommand('selectAll',false);

var el = document.createElement('p');
el.innerText = newText;

document.execCommand('insertHTML',false,el.innerHTML);

在我的中档游戏电脑上,处理长度略超过4,000个字符的字符串需要约1秒。因此,您需要确定这是否符合您的使用情况。

旧版本

也许您熟悉execCommand,并想知道为什么我们不直接使用它:

textarea.focus();
document.execCommand('selectAll',false);
document.execCommand('insertText',false,newText);

在 Chromium 上,insertText 特别慢,尤其是对于较长的字符串。例如,对于一个超过 4,000 个字符的字符串,在我的中端游戏电脑上使用同样的字符串需要大约10秒钟

它确实也保存用户历史记录,且在不同历史状态之间移动仍然是可以接受的速度(约为0.5秒)。

这比更新版本明确要做的事情背景下更慢,这有意义吗?我认为没有。但这似乎就是 Chromium 今天的情况。如果旧版本变得更好,请在下面评论。

一般注意事项

不幸的是,在 Firefox 的 textarea 元素中,execCommand 不起作用。这可能会在即将发布的版本中修复。

在 Chromium 和 Firefox 中测试过。

如果我找到了更快的解决方案(和 / 或适用于 Firefox 的解决方案),我一定会分享。


有趣的是,如果你使用insertHTML而不是insertText,它会运行得更快。然而,如果任何内容可以被解释为HTML,它将被剪切掉。我会进行进一步测试。 - Josh Powlison
1
我也有过这样的经历,使用Chrome(而不是Firefox)的execCommand("insertText")非常缓慢(我使用contenteditable pre)。您的解决方案使其更好。瓶颈在于el.innerText = newText。可以使用el.appendChild(document.createTextNode(newText))来加快速度。 - chkas

2
我从http://www.javascriptsource.com/forms/undo-redo.html获取了以下代码,你也可以查看其工作示例。
虽然您可能需要实现自己的功能,例如加粗、斜体等。

function iObject() {
  this.i;
  return this;
}

var myObject=new iObject();
myObject.i=0;
var myObject2=new iObject();
myObject2.i=0;
store_text=new Array();

//store_text[0] store initial textarea value
store_text[0]="";

function countclik(tag) {
  myObject.i++;
  var y=myObject.i;
  var x=tag.value;
  store_text[y]=x;
}

function undo(tag) {
  if ((myObject2.i)<(myObject.i)) {
    myObject2.i++;
  } else {
    alert("Finish Undo Action");
  }
  var z=store_text.length;
  z=z-myObject2.i;
  if (store_text[z]) {
   tag.value=store_text[z];
  } else {
   tag.value=store_text[0];
  }
}

function redo(tag) {
  if((myObject2.i)>1) {
    myObject2.i--;
  } else {
    alert("Finish Redo Action");
  }
  var z=store_text.length;
  z=z-myObject2.i;
  if (store_text[z]) {
    tag.value=store_text[z];
  } else {
  tag.value=store_text[0];
  }
 }
<form name=form1>
  <input type="button"  class="red" value="Undo" onmousedown="undo(document.form1.text1);">
  <input type="button" class="red" value="Redo" onmousedown="redo(document.form1.text1);">
  <br>
  <textarea rows=7 cols=50  name=text1 onkeydown="countclik(document.form1.text1);" >
  </textarea>
</form>


<p><center>
<font face="arial, helvetica" size"-2">Free JavaScripts provided<br>
by <a href="http://javascriptsource.com">The JavaScript Source</a></font>
</center><p>

我过去使用过ckeditor,并建议您使用它。您也可以使用niceditor


非常感谢您的答复:)。但是如我在问题中提到的,我希望能够在不需要构建自己的撤销重做功能的情况下完成这个任务:)。 - sktguha
你可以使用像 CKEditor 这样的插件,我相信它会在添加功能方面减少很多你的工作 :) - iyerrama29

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接