You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

textarea文本替换后Ctrl+Z仅能撤销最近操作的技术问询

解决Textarea多替换后Ctrl+Z仅能撤销最后一次的问题

我明白你的困扰——在textarea里多次把选中单词替换成星号后,按Ctrl+Z只能恢复最后一次替换的单词,之前的都找不回来对吧?咱们来看看问题出在哪,再把它搞定。

问题根源

你当前的代码里只用了一个selection对象来存储替换信息,每次新的替换都会覆盖掉上一次的记录。这就导致撤销操作只能找到最后一次的替换数据,自然没法恢复更早的内容。

解决方案:引入撤销历史栈

我们需要用一个数组来维护所有的替换操作历史,每次执行替换时,把当前的替换关键信息(比如替换的起始/结束位置、原文本)存入数组;撤销时,从数组末尾取出最近的一条记录,用它来恢复文本。

下面是修改后的完整代码,我会标注关键改动:

// 把单个selection对象改成数组,存储所有撤销历史
let undoHistory = [];

function undo(e) {
  const evtobj = window.event ? window.event : e;
  if (evtobj.keyCode === 90 && evtobj.ctrlKey && undoHistory.length > 0) {
    evtobj.preventDefault();
    const txtarea = document.getElementById("mytextarea");
    // 取出最后一条历史记录
    const lastAction = undoHistory.pop();
    const allText = txtarea.value;
    // 用历史记录恢复文本
    const newText = allText.substring(0, lastAction.start) + lastAction.text + allText.substring(lastAction.finish);
    txtarea.value = newText;
    // 恢复光标位置(优化体验,贴近原生撤销)
    txtarea.selectionStart = lastAction.start;
    txtarea.selectionEnd = lastAction.finish;
  }
}

function getSel() {
  const txtarea = document.getElementById("mytextarea");
  const start = txtarea.selectionStart;
  const finish = txtarea.selectionEnd;
  const allText = txtarea.value;
  const selectedText = allText.substring(start, finish);
  
  // 把本次替换的信息存入撤销历史
  undoHistory.push({
    start: start,
    finish: finish,
    text: selectedText
  });

  const replacedText = selectedText.replace(/[\S]/g, "*");
  const newText = allText.substring(0, start) + replacedText + allText.substring(finish);
  txtarea.value = newText;
  $('#newpost').offset({top: 0, left: 0}).hide();
}

function closePopUp() {
  $('#newpost').offset({top: 0, left: 0}).hide();
}

$(document).ready(function () {
  closePopUp();
  const newpost = $('#newpost');
  $('#mytextarea')
    .on('select', function (e) {
      const txtarea = document.getElementById("mytextarea");
      const start = txtarea.selectionStart;
      const finish = txtarea.selectionEnd;
      newpost.offset(getCursorXY(txtarea, start, 20)).show();
      newpost.find('div').text('replace with stars');
    })
    .on('input', () => {
      // 当用户手动输入时,清空撤销历史(和原生Ctrl+Z行为一致)
      undoHistory = [];
    });
  document.onkeydown = undo;
});

var getCursorXY = function getCursorXY(input, selectionPoint, offset) {
  var inputX = input.offsetLeft, inputY = input.offsetTop;
  var div = document.createElement('div');
  var copyStyle = getComputedStyle(input);
  try {
    for (var prop of copyStyle) {
      div.style[prop] = copyStyle[prop];
    }
  } catch (err) {
    // 捕获迭代器异常,不影响核心功能
  }
  var swap = '.';
  var inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value;
  var textContent = inputValue.substr(0, selectionPoint);
  div.textContent = textContent;
  if (input.tagName === 'TEXTAREA') div.style.height = 'auto';
  if (input.tagName === 'INPUT') div.style.width = 'auto';
  var span = document.createElement('span');
  span.textContent = inputValue.substr(selectionPoint) || '.';
  div.appendChild(span);
  document.body.appendChild(div);
  var spanX = span.offsetLeft, spanY = span.offsetTop;
  document.body.removeChild(div);
  return { left: inputX + spanX, top: inputY + spanY + offset };
};

关键改动说明

  • 撤销历史数组:用undoHistory数组替代原来的单个selection对象,每次替换时把start(起始位置)、finish(结束位置)、text(原选中文本)存入数组,保留所有操作记录。
  • 撤销逻辑更新:撤销时从数组末尾取出最近的操作记录恢复文本,同时添加了光标位置恢复的优化,让操作体验更贴近浏览器原生撤销行为。
  • 手动输入清空历史:当用户在textarea里手动输入内容时,自动清空撤销历史——这和原生Ctrl+Z的逻辑一致,手动输入后之前的替换操作就不再可撤销。

这样修改后,你每次替换单词后,按Ctrl+Z就能依次恢复之前所有被替换的单词啦。

内容的提问来源于stack exchange,提问作者anub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:58:09