当更新所选文本时,撤销/重做功能无法正常工作。

4

我试图创建一些按钮,当点击它们时,它们将添加 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>

1个回答

2

由于您正在通过编程方式更改内容,因此您需要通过编程方式撤消它。

在按钮单击处理程序中:

  • 在修改内容之前捕获内容的现有状态
  • 创建一个将其重置为该状态的函数。
  • 将该函数推入数组(“撤消堆栈”)中
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>


我正在寻找一些可以让浏览器正常工作而不需要创建撤销、重做按钮的东西,是否有一些可以通过浏览器本身实现的方法? - JSX
您不必为此创建按钮。请参见上面的演示。 - ray
我知道当按下 Ctrl+z 时它会被应用,我的意思是有没有一种方法可以使浏览器在不添加额外的代码的情况下本地化地实现撤销和重做。 - JSX

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