撤消仅适用于首次替换的文本?

在这里我使用Ctrl + Z来撤消被替换的文本,我有一个场景,在文本区域,我有一个多个单词的句子,我选择第一个单词并用星号替换,后来我选择另一个单词并替换为星星。 但是当它按Ctrl + Z时它只适用于最新选择的单词而不适用于之前的单词。

JavaScript的:

var selection = {}; function undo(e) { var evtobj = window.event? window.event : e; if (evtobj.keyCode == 90 && evtobj.ctrlKey && selection.text) { evtobj.preventDefault(); var txtarea = document.getElementById("mytextarea"); var allText = txtarea.value; var newText = allText.substring(0, selection.start) + selection.text + allText.substring(selection.finish, allText.length); txtarea.value = newText; } } function getSel() { // obtain the object reference for the textarea> var txtarea = document.getElementById("mytextarea"); // obtain the index of the first selected character var start = txtarea.selectionStart; // obtain the index of the last selected character var finish = txtarea.selectionEnd; //obtain all Text var allText = txtarea.value; selection.text = allText.substring(start, finish); selection.start = start; selection.finish = finish; // obtain the selected text var sel = allText.substring(start, finish); sel = sel.replace(/[\S]/g, "*"); //append te text; var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length); txtarea.value = newText; $('#newpost').offset({top: 0, left: 0}).hide(); } function closePopUp() { $('#newpost').offset({top: 0, left: 0}).hide(); } $(document).ready(function () { closePopUp(); var newpost = $('#newpost'); $('#mytextarea').on('select', function (e) { var txtarea = document.getElementById("mytextarea"); var start = txtarea.selectionStart; var finish = txtarea.selectionEnd; newpost.offset(getCursorXY(txtarea, start, 20)).show(); newpost.find('div').text('replace with stars'); }).on('input', () => selection.text = null); document.onkeydown = undo; }); var getCursorXY = function getCursorXY(input, selectionPoint, offset) { var inputX = input.offsetLeft, inputY = input.offsetTop; // create a dummy element that will be a clone of our input var div = document.createElement('div'); // get the computed style of the input and clone it onto the dummy element var copyStyle = getComputedStyle(input); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = copyStyle[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done) ; _iteratorNormalCompletion = true) { var prop = _step.value; div.style[prop] = copyStyle[prop]; } // we need a character that will replace whitespace when filling our dummy element // if it's a single line  } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var swap = '.'; var inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value; // set the div content to that of the textarea up until selection var textContent = inputValue.substr(0, selectionPoint); // set the text content of the dummy element div div.textContent = textContent; if (input.tagName === 'TEXTAREA') div.style.height = 'auto'; // if a single line input then the div needs to be single line and not break out like a text area if (input.tagName === 'INPUT') div.style.width = 'auto'; // create a marker element to obtain caret position var span = document.createElement('span'); // give the span the textContent of remaining content so that the recreated dummy element // is as close as possible span.textContent = inputValue.substr(selectionPoint) || '.'; // append the span marker to the div div.appendChild(span); // append the dummy element to the body document.body.appendChild(div); // get the marker position, this is the caret position top and left relative to the input var spanX = span.offsetLeft, spanY = span.offsetTop; // lastly, remove that dummy element // NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered document.body.removeChild(div); // return an object with the x and y of the caret. account for input positioning // so that you don't need to wrap the input return { left: inputX + spanX, top: inputY + spanY + offset }; }; 

这是我的Plunker 。

您必须手动控制所有textarea更改。

在这里,我创建了edits数组,每隔几秒就会更改textarea文本(您可以使用saveInterval变量控制它)。 您还可以使用maxHistorySize设置此数组的最大长度。 arrays已满时,旧的更改将丢失。

 var edits = [""]; var interval = true; var maxHistorySize = 10; var saveInterval = 3000; function undo(e) { var evtobj = window.event? window.event : e; if (evtobj.keyCode == 90 && evtobj.ctrlKey) { evtobj.preventDefault(); var txtarea = document.getElementById("mytextarea"); var previousText = edits.length === 1 ? edits[0] : edits.pop(); if (previousText !== undefined) { txtarea.value = previousText; } } } function getSel() { // obtain the object reference for the textarea> var txtarea = document.getElementById("mytextarea"); // obtain the index of the first selected character var start = txtarea.selectionStart; // obtain the index of the last selected character var finish = txtarea.selectionEnd; //obtain all Text var allText = txtarea.value; edits.push(allText); if (edits.length > maxHistorySize) edits.shift(); // obtain the selected text var sel = Array(finish - start + 1).join("*"); //append te text; var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length); txtarea.value = newText; $('#newpost').offset({top: 0, left: 0}).hide(); } function closePopUp() { $('#newpost').offset({top: 0, left: 0}).hide(); } $(document).ready(function () { closePopUp(); var newpost = $('#newpost'); $('#mytextarea').on('select', function (e) { var txtarea = document.getElementById("mytextarea"); var start = txtarea.selectionStart; var finish = txtarea.selectionEnd; newpost.offset(getCursorXY(txtarea, start, 20)).show(); newpost.find('div').text(Array(finish - start + 1).join("*")); }).on('input', function() { if (interval) { interval = false; edits.push($(this).val()); if (edits.length > maxHistorySize) edits.shift(); setTimeout(() => interval = true, saveInterval); } }); document.onkeydown = undo; }); const getCursorXY = (input, selectionPoint, offset) => { const { offsetLeft: inputX, offsetTop: inputY, } = input // create a dummy element that will be a clone of our input const div = document.createElement('div') // get the computed style of the input and clone it onto the dummy element const copyStyle = getComputedStyle(input) for (const prop of copyStyle) { div.style[prop] = copyStyle[prop] } // we need a character that will replace whitespace when filling our dummy element // if it's a single line  const swap = '.' const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value // set the div content to that of the textarea up until selection const textContent = inputValue.substr(0, selectionPoint) // set the text content of the dummy element div div.textContent = textContent if (input.tagName === 'TEXTAREA') div.style.height = 'auto' // if a single line input then the div needs to be single line and not break out like a text area if (input.tagName === 'INPUT') div.style.width = 'auto' // create a marker element to obtain caret position const span = document.createElement('span') // give the span the textContent of remaining content so that the recreated dummy element // is as close as possible span.textContent = inputValue.substr(selectionPoint) || '.' // append the span marker to the div div.appendChild(span) // append the dummy element to the body document.body.appendChild(div) // get the marker position, this is the caret position top and left relative to the input const { offsetLeft: spanX, offsetTop: spanY } = span // lastly, remove that dummy element // NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered document.body.removeChild(div) // return an object with the x and y of the caret. account for input positioning // so that you don't need to wrap the input return { left: inputX + spanX, top: inputY + spanY + offset, } } 
 #mytextarea {width: 600px; height: 200px; overflow:hidden; position:fixed} #newpost { position:absolute; background-color:#ffffdc; border:1px solid #DCDCDC; border-radius:10px; padding-right:5px; width: auto; height: 30px; } #newpost span { cursor:pointer; position: absolute; top: 0; right: 5px; font-size: 22px; } #newpost div { color:#0000ff; padding:10px; margin-right:10px; width: auto; cursor:pointer; } 
       
˟

您需要将逻辑从使用单个selection对象更改为使用selection对象数组,如下所示:

  1. 初始化为selections而不是selection

     var selections = []; 
  2. 调用getSel() ,请确保更新为push送到数组:

     function getSel() { // obtain the object reference for the textarea> var txtarea = document.getElementById("mytextarea"); // obtain the index of the first selected character var start = txtarea.selectionStart; // obtain the index of the last selected character var finish = txtarea.selectionEnd; //obtain all Text var allText = txtarea.value; selections.push({ start: start, finish: finish, text: allText.substring(start, finish) }); // obtain the selected text var sel = allText.substring(start, finish); sel = sel.replace(/[\S]/g, "*"); //append te text; var newText = allText.substring(0, start) + sel + allText.substring(finish, allText.length); txtarea.value = newText; $('#newpost').offset({top: 0, left: 0}).hide(); } 
  3. 因此,我们需要更新undofunction,如下所示:

     function undo(e) { var evtobj = window.event? window.event : e; if (evtobj.keyCode == 90 && evtobj.ctrlKey) { evtobj.preventDefault(); if (selections.length === 0) alert ("Can't do more undos"); else { var thisSelection = selections.pop(); var txtarea = document.getElementById("mytextarea"); var allText = txtarea.value; var newText = allText.substring(0, thisSelection.start) + thisSelection.text + allText.substring(thisSelection.finish, allText.length); txtarea.value = newText; } } } 
  4. 最后,从$('#mytextarea')删除on('input')事件监听器,因为我认为不需要

这里有所有变化