javascript - 更新选定文本时撤消/重做不起作用

标签 javascript html selection

我试图创建一些在单击时会添加 Markdown 字符的按钮,所以我使用了 Selection API获取选定的文本并添加字符,然后返回 caret到所选文本的结束位置,一切都正确完成。
当我尝试按下撤消 Ctrl+z 时,它不会返回到添加 Markdown 字符之前的最后一个文本,我知道这是因为我更改了文本节点值。
有没有办法在不影响节点文本并应用撤消、重做的情况下做到这一点?

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>

最佳答案

因为您正在以编程方式更改内容,所以您将不得不以编程方式撤消它。
在您的按钮单击处理程序中:

  • 在修改之前捕获内容的现有状态
  • 创建一个将其重置为该状态的函数。
  • 将该函数插入一个数组(“撤消堆栈”)

  • 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>

    关于javascript - 更新选定文本时撤消/重做不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69230546/

    相关文章:

    jquery - 尝试使用transitionend事件而不是setTimeout

    html - 如何删除特定标签但保留允许的标签

    javascript - 在 HTML 文件输入元素中仅显示 XML 文件

    javascript - 在 IE10+ 上下载时 HTML Canvas 图像位深度发生变化

    javascript - 如何使用谷歌翻译进行阿拉伯语翻译从右到左的布局和文本更改

    javascript - 如何从 DOM 中删除 div 及其内容

    emacs - 如何在 Emacs 中选择或突出显示一个 block ?

    javascript - 清除 Firefox 中的选择

    javascript - 用于单个元素或类似内容的 <noscript> 标记

    javascript - jQuery:捕捉链接内的点击