ueberdosis / tiptap

The headless rich text editor framework for web artisans.
https://tiptap.dev
MIT License
26.6k stars 2.23k forks source link

[Bug]: Mod-z Not Blocked When editor.isEditable Is False #5547

Open jan-schweiger opened 2 weeks ago

jan-schweiger commented 2 weeks ago

Affected Packages

core, react, @tiptap/extension-history

Version(s)

2.6.6

Bug Description

  1. Edit the content
  2. Run editor.setEditable(false)
  3. Press CTRL + Z
  4. The undo operation is performed

However, this only happens for Undo but not for Redo.

Reproduce

I was also able to reproduce it by slightly adjusting the minimal setup: https://tiptap.dev/docs/examples/basics/minimal-setup

import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React from 'react'
import History from '@tiptap/extension-history'

function App() {
  const editor = useEditor({
    extensions: [
      Document,
      Paragraph,
      Text,
      History  // <-- Added History
    ],
    content: "<p>Tiptap content</p>",
  })

  return (
    <div className="App">
      <EditorContent editor={editor} />

      <button onClick={() => editor.setEditable(!editor.isEditable)}>
        Toggle isEdit
      </button>
    </div>
  );
}

export default App;

You just need to edit some content and click the button to experience the bug for yourself.

Thank you very much in advance for your help!

Browser Used

Chrome

Code Example URL

No response

Expected Behavior

Mod + Z shouldn't change the content when the editor is read-only.

Additional Context (Optional)

No response

Dependency Updates

jan-schweiger commented 2 weeks ago

I also tried the following quickfix. But strangely the Mod-z function is only called when the isEditable is true.

     History.extend({
        addKeyboardShortcuts() {
          return {
            'Mod-z': () => {
              console.log('Mod-z', this.editor.isEditable);
              return this.editor.isEditable ? this.editor.commands.undo() : false;
            },
            'Mod-Shift-z': () => {
              return this.editor.isEditable ? this.editor.commands.redo() : false;
            },
          };
        },
      }),
tanja-kovakic commented 2 weeks ago

I can also reproduce the bug. Just tried it out in my application. Can anyone have a look on this one?

nperez0111 commented 2 weeks ago

Maybe this is a browser default behavior? I wonder if the element is still a contenteditable when editable=false

rosenbauerwillbacker commented 2 weeks ago

I can also reproduce the bug.

@nperez0111 In my case, the content is initially set to editable=false. I however change the content programmatically. In this case everything works fine. CTRL + Z doesn't change the content back. Only, when I make the content editable and then make it read-only again, the bug occurs.

I personally do not think it is the browser's default behaviour, because the bug is only limited to CTRL + Z, but not CTRL + Shift + Z.

I tried to pinpoint the cause, but my knowledge of the inner workings of tiptap is unfortunately limited. Could maybe someone investigate this issue? This is unfortunately a severe problem for my application. Thank you!

jan-schweiger commented 1 week ago

Any updates on this one?

nperez0111 commented 1 week ago

Feel free to look into it and contribute a PR fixing it.

jan-schweiger commented 1 week ago

Thank you @nperez0111. I have already looked into the potential causes but haven't found a solution yet. I will give it another try over the weekend.

jan-schweiger commented 1 week ago

Unfortunately, I was unable to identify the root cause. As a temporary "fix" you can use the following code to clear the history whenever you programmatically set the editor to read-only again. This is definitely not the best solution, rather a dirty quickfix for production applications.

import { history } from '@tiptap/pm/history';

// clear history:
editor.unregisterPlugin('history');
editor.registerPlugin(history());