JSからtextareaの文字列を変更した時、Undoに対応できるようにする #dev #javascript

mimemoのmarkdownエディタを作っていく上で、リストの補完などのアイデアとコードは一部esareaのものを下敷きにして書かせていただきました(多謝)。

ただ、esarea(及びesaのmarkdownエディタ)は、一部のブラウザで補完したあとにCtrl+Zなどでの「取り消し」ができなくなるようでしたので、これはエディタとして致命的、ということでmimemoでは頑張って(?)Undo対応させたので、その辺フィードバックも兼ねたハウツーメモです。

JSからtextarea書き換えた時にUndo対応させる

2018/01/13追記: Firefoxでtextareaの仕様が変わったようで、下のコードでFirefoxはundo対応できません。

方法

  • Chrome、SafariはexecCommand('insertText')で書き換える形にするとUndoできる形で書き換えられる
  • Firefoxは何もしないでも元々Undo対応していて、逆にexecCommand使うとうまく動かなかったりする Firefoxはver55あたり(?)からJSで書き換えるとそれ以前の状態に戻すUndoができなくなり、さらにexecCommand('insertText')がtextareaに対しては使えないので、現状Undo対応する方法はなさそうです。 何かご存知の方は教えてください……
  • MS Edgeは何もしないでも元々Undo対応している模様
  • IEではexecCommandで'ms-beginUndoUnit','ms-endUndoUnit'で挟んで書き換えるとUndo対応できる

簡単なデモ

実際に使ってるコード(抜粋)

class EditorInput{
  // ...
  // ※this.inputElmが対象になるtextarea
  replace(str, fromIdx, toIdx) {
    let inserted = false;

    if (str) {
      let expectedLen = this.inputElm.value.length - Math.abs(toIdx - fromIdx) + str.length;
      this.inputElm.focus();
      this.inputElm.selectionStart = fromIdx;
      this.inputElm.selectionEnd = toIdx;
      try {
        inserted = document.execCommand('insertText', false, str);
      } catch (e) {
        inserted = false
      }
      if (inserted && (this.inputElm.value.length !== expectedLen || this.inputElm.value.substr(fromIdx, str.length) !== str)) {
        //firefoxでなぜかうまくいってないくせにinsertedがtrueになるので失敗を検知してfalseに…
        inserted = false;
      }
    }

    if (!inserted) {
      try {
        document.execCommand('ms-beginUndoUnit');
      } catch (e) {}
      let value = this.inputElm.value;
      this.inputElm.value = '' + value.substring(0, fromIdx) + str + value.substring(toIdx);
      try {
        document.execCommand('ms-endUndoUnit');
      } catch (e) {}
    }
  }
  // ...
}

END

Close