【问题标题】:Undo / Redo isn't working when update selected text更新选定文本时撤消/重做不起作用
【发布时间】:2021-11-12 18:03:12
【问题描述】:

我试图创建一些按钮,当单击它时会添加降价字符,所以我使用Selection API 获取选定的文本并添加字符,然后将caret 返回到选定文本的结束位置,一切都正确完成.

当我尝试按下 undo Ctrl+z 时,它不会回到添加降价字符之前的最后一个文本我知道这是因为我更改了文本节点值.

有没有办法在不影响节点文本和应用撤消、重做的情况下做到这一点?

let text = document.getElementById('test'),
  btn = document.getElementById('btn')
//function to replace text by index
String.prototype.replaceAt = function(start, end, replacement) {
  let text = '';
  for (let i = 0; i < this.length; i++) {
    if (i >= start && i < end) {
      text += ''
      if (i === end - 1) {
        text += replacement
      }
    } else {
      text += this[i]
    }
  }
  return text
}

function addNewStr(callback) {
  var sel = window.getSelection()
  try {
    var range = sel.getRangeAt(0),
      r = document.createRange()
    //check if there's text is selected
    if (!sel.isCollapsed) {
      let startPos = sel.anchorOffset,
        endPos = sel.focusOffset,
        node = sel.anchorNode,
        value = sel.anchorNode.nodeValue,
        selectedText = value.substring(startPos, endPos),
        parent = node.parentNode
      //function to determine if selection start from left to right or right to left
      function checkPos(callback) {
        if (startPos > endPos) {
          return callback(startPos, endPos)
        } else {
          return callback(endPos, startPos)
        }
      }
      if (typeof callback === 'function') {
        //getting the new str from the callback
        var replacement = callback(selectedText),
          caretIndex;
        //apply changes
        node.nodeValue = checkPos(function(end, start) {
          return node.nodeValue.replaceAt(start, end, replacement)
        })
        //check if the returned text from the callback is less or bigger than selected text to move caret to the end of selected text
        if (replacement.length > selectedText.length) {
          caretIndex = checkPos(function(pos) {
            return pos + (replacement.length - selectedText.length);
          })
        } else if (selectedText.length > replacement.length) {
          caretIndex = checkPos(function(pos) {
            return (pos - selectedText.length) + (replacement.length);
          })
        }
        //back caret to the end of the new position
        r.setStart(parent.childNodes[0], caretIndex)
        r.collapse(true)
        sel.removeAllRanges()
        sel.addRange(r)
      }
    }
  } catch (err) {
    console.log("Nothing is selected")
  }
}
btn.addEventListener("click", function() {
  addNewStr(function(str) {
    return '__' + str + '__'
  })
})
<div contenteditable="true" id="test" placeholder="insertText">
  try to select me
</div>
<button id="btn">to strong</button>

【问题讨论】:

    标签: javascript html selection


    【解决方案1】:

    因为您正在以编程方式更改内容,所以您将不得不以编程方式撤消它。

    在您的按钮单击处理程序中:

    • 在修改之前捕获内容的现有状态
    • 创建一个将其重置为该状态的函数。
    • 将该函数推入一个数组(“撤消堆栈”)
    const undoStack = [];
    
    function onButtonClick (e) {
      // capture the existing state
      const textContent = text.innerText;
    
      // create a function to set the div content to its current value
      const undoFn = () => text.innerText = textContent;
    
      // push the undo function into the array
      undoStack.push(undoFn);
    
      // ...then do whatever the button does...
    }
    

    有了它,您可以监听ctrl-z 并调用最近的撤消函数:

    // convenience to determine whether a keyboard event should trigger an undo
    const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' && (ctrlKey || metaKey);
    
    // keydown event handler for undo
    const keydown = (e) => {
      if(isUndo(e) && undos.length) {
        e.preventDefault();
        undos.pop()();
      }
    }
    
    // listen for keydowns
    document.body.addEventListener('keydown', keydown);
    

    还有其他一些考虑因素,例如某些用户操作是否应该清除撤消堆栈,但这是基本思想。


    概念验证演示

    为了清楚起见,我已将您的内容修改代码替换为仅在每次点击时添加一个数字。

    const div = document.getElementById('test');
    const button = document.querySelector('button');
    
    const undos = [];
    
    button.addEventListener('click', e => {
      const text = div.innerText;
      undos.push(() => div.innerText = text);
      div.innerText += ` ${undos.length} `;
    });
    
    const isUndo = ({ctrlKey, metaKey, key}) => key === 'z' && (ctrlKey || metaKey);
    
    const keydown = (e) => {
        if(isUndo(e) && undos.length) {
        e.preventDefault();
        undos.pop()();
      }
    }
    
    document.body.addEventListener('keydown', keydown);
    <div contenteditable="true" id="test" placeholder="insertText">
      this is some text
    </div>
    <button id="btn">to strong</button>

    【讨论】:

    • 我一直在寻找能让浏览器正常执行而不创建撤消、重做按钮的东西,有什么东西可以让浏览器本机完成吗?
    • 您不必为它创建按钮。请参阅上面的演示。
    • 我知道它会在按下 Ctrl+z 时应用,我的意思是有一种方法可以让浏览器在本地制作它,而无需添加额外的撤消代码,重做
    猜你喜欢
    • 2014-05-26
    • 2016-09-19
    • 2021-06-27
    • 2016-10-28
    • 2014-03-06
    • 1970-01-01
    • 2015-11-25
    • 1970-01-01
    • 2018-09-25
    相关资源
    最近更新 更多