Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to clear history? #491

Closed
xy2z opened this issue Oct 14, 2019 · 21 comments
Closed

How to clear history? #491

xy2z opened this issue Oct 14, 2019 · 21 comments

Comments

@xy2z
Copy link

xy2z commented Oct 14, 2019

Is it possible to clear the history?

The problem is that I'm changing the content in the editor dynamically when user selects a new "file", but then the history from the last file will still be available.
Basically, when a user changes file, and hit undo, the content from the last file will be put into the editor. So I want to clear history every time the user changes files.

I've tried recreating the component using the "hack" :key="componentKey" - but it just gave an error TypeError: Cannot read property 'matchesNode' of null

Anyone know how?

@RuneHanssens
Copy link

Did you find out how to clear the history?

@xy2z
Copy link
Author

xy2z commented Mar 5, 2020

I ended up doing my custom history. Not ideal but I couldn't find any other way.

@RuneHanssens
Copy link

RuneHanssens commented Mar 6, 2020

Just destroy your editor and create a new one seems to do the trick.

  private editor = new Editor(this.editorOptions);

  private get editorOptions() {
    return {
       ...
    };
  }

  clearHistory() {
    this.editor.destroy();
    this.editor = new Editor(this.editorOptions);
  }

And I didn't notice any stuttering by recreating the editor (yet)

@philippkuehn
Copy link
Contributor

There is not such a feature in prosemirror-history. Related: https://discuss.prosemirror.net/t/reset-history-plugin-state/1883

Feel free to create a PR for prosemirror.

@OrkhanAlikhanov
Copy link

Sent a simple PR for this ProseMirror/prosemirror-history#5

@marijnh
Copy link

marijnh commented Mar 18, 2020

When loading a new file, you should create a fresh ProseMirror state. I don't intend to merge that PR until someone comes up with a more compelling use case.

@OrkhanAlikhanov
Copy link

@marijnh Thanks for the quick response. That makes sense. Actually, I added it according to your suggestion here https://discuss.prosemirror.net/t/reset-history-plugin-state/1883

@marijnh
Copy link

marijnh commented Mar 18, 2020

Ah right, seems I did suggest that last year. But there's other weird effects you'll get when trying to start a new document without starting a new state (decorations might stick around, other plugins might still drag around irrelevant state), so unless someone has a more solid reason for needing this, I think providing it just steers people in the wrong direction.

@OrkhanAlikhanov
Copy link

As per the suggestion of the author we should set brand new state when loading new document. I am doing it in this way if anyone needs:

this.editor.options.content = jsonDocOrHTML
this.editor.view.updateState(this.editor.createState())

@brechtm
Copy link

brechtm commented May 7, 2020

It would be good to encapsulate this functionality, preferably also handling the collaboration plugin state (#691). How about Editor.loadDocument(doc, version)?

@mattersj
Copy link

mattersj commented Jun 11, 2020

this.editor.options.content = jsonDocOrHTML
this.editor.view.updateState(this.editor.createState())

So updating current editor state is not the best solution in my case. It breaks menu positioning so menu.left and menu.bottom always return 0. I believe that I found the right-way solution:

const { doc, tr } = this.editor.state;
const document = this.editor.createDocument(yourHTMLOrJSONContentHere);
const selection = TextSelection.create(doc, 0, doc.content.size);
const transaction = tr
  .setSelection(selection)
  .replaceSelectionWith(document, false)
  .setMeta('preventUpdate', true) // true by default but you can set it to false
  .setMeta('addToHistory', false); // Finally we prevent pushing content to the history

this.editor.view.dispatch(transaction);

TextSelection should be imported from 'tiptap' as well.

Note: this code only prevents pushing new content to the history stack but DOES NOT clear the entire history. It can be useful when you need to replace content after fetching it from API.

@longlongago2
Copy link

longlongago2 commented Dec 8, 2021

this.editor.options.content = jsonDocOrHTML
this.editor.view.updateState(this.editor.createState())

So updating current editor state is not the best solution in my case. It breaks menu positioning so menu.left and menu.bottom always return 0. I believe that I found the right-way solution:

const { doc, tr } = this.editor.state;
const document = this.editor.createDocument(yourHTMLOrJSONContentHere);
const selection = TextSelection.create(doc, 0, doc.content.size);
const transaction = tr
  .setSelection(selection)
  .replaceSelectionWith(document, false)
  .setMeta('preventUpdate', true) // true by default but you can set it to false
  .setMeta('addToHistory', false); // Finally we prevent pushing content to the history

this.editor.view.dispatch(transaction);

TextSelection should be imported from 'tiptap' as well.

Note: this code only prevents pushing new content to the history stack but DOES NOT clear the entire history. It can be useful when you need to replace content after fetching it from API.

We can create an extension like this:

import { Extension } from '@tiptap/core';

export default Extension.create({
  name: 'loaddoc',
  addCommands() {
    return {
      loadContent:
        (content) => ({ tr, dispatch, commands }) => {
          commands.setContent(content, false, { preserveWhitespace: 'full' });
          if (dispatch) {
            tr.setMeta('addToHistory', false); // Finally we prevent pushing content to the history
          }
          return true;
        },
    };
  },
});

@puopg
Copy link

puopg commented Aug 9, 2022

Is it possible to do this like this in the current version of tiptap?:

@marijnh Curious if you see any issues with this?

reset = (editor: Editor) => {
    // Create a new document using this method, since it's what createDocument does, but that utility is not accessible.
    const doc = editor.schema.nodeFromJSON({
      type: "doc",
      content: [{ type: "paragraph" }],
    });

    // Create a new editor state, and pass the existing plugins on the current editor into the new state
    const newEditorState = EditorState.create({
      doc,
      selection: undefined,
      plugins: editor.extensionManager.plugins,
    });

   editor.view.updateState(newEditorState);
  };

@Jettonn
Copy link

Jettonn commented Sep 28, 2022

I don't know if this is the best approach but after fetching content from api and using
editor.setContent({ type: 'doc', content })
to set content to editor. I cleared the history using this trick:
editor.state.history$.prevRanges = null; editor.state.history$.done.eventCount = 0

@bassem-mf
Copy link

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

@samyarkd
Copy link

samyarkd commented Dec 5, 2022

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

Thank you this one worked great and clean.

@floydnant
Copy link

Previous two answers work great, though I'm setting the document directly in the state (saves one transaction I guess):

import { Editor, createDocument } from '@tiptap/core'
import { EditorState } from 'prosemirror-state'

function resetEditorState(editor: Editor, content: string) {
    const newState = EditorState.create({
        doc: createDocument(content, editor.schema),
        schema: editor.schema,
        plugins: editor.state.plugins,
    })
    editor.view.updateState(newState)
}

@stevobm
Copy link
Contributor

stevobm commented Sep 29, 2023

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

I tried this, but when I clicked the content of editor, the cursor sometimes would jump to the beginning of the line... A little problem though.

@milimyname
Copy link

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

I tried this, but when I clicked the content of editor, the cursor sometimes would jump to the beginning of the line... A little problem though.

Here is the solution. I hope it helps for future seekers.


	function resetEditorContent() {
		// Capture the current selection
		const currentSelection = editor.state.selection;

		// Reset the content
		editor.commands.setContent(editor.getJSON());

		// Create a new editor state while preserving the old selection
		const newEditorState = EditorStatePrsomirror.create({
			doc: editor.state.doc,
			plugins: editor.state.plugins,
			selection: currentSelection
		});

		// Update the editor state
		editor.view.updateState(newEditorState);
	}

@Amar-Gill
Copy link

Amar-Gill commented Mar 24, 2024

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

I tried this, but when I clicked the content of editor, the cursor sometimes would jump to the beginning of the line... A little problem though.

Here is the solution. I hope it helps for future seekers.


	function resetEditorContent() {
		// Capture the current selection
		const currentSelection = editor.state.selection;

		// Reset the content
		editor.commands.setContent(editor.getJSON());

		// Create a new editor state while preserving the old selection
		const newEditorState = EditorStatePrsomirror.create({
			doc: editor.state.doc,
			plugins: editor.state.plugins,
			selection: currentSelection
		});

		// Update the editor state
		editor.view.updateState(newEditorState);
	}

This solution unfortunately did not work for me, and I am unable to figure out why. I am loading new content into existing editor instance after fetching the content from backend. I am using the vue-3 package although not sure if that matters.

But I did find an alternative solution that works for me. It's involves unregistering the history plugin, and re-registering it with a new instance of the plugin:

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

const editor = useEditor({ ...});

await useFetch('/content', {
  onResponse(ctx) {
    if (ctx.response.ok) {
      editor.value.commands.setContent(ctx.response._data.content, false); // <-- data from backend

      // unregister and re-register history plugin to clear data when fetching new content
      editor.value?.unregisterPlugin('history');
      editor.value?.registerPlugin(history());

      editor.value.commands.focus('end');
    }
  },
});

@rajatkulkarni95
Copy link

The previous answers did not work for me because of missing types and/or members. The following code worked for me and I could not see any side effects in my application.

import { Editor } from "@tiptap/core";
import { EditorState } from 'prosemirror-state';

function resetEditorContent(editor: Editor, newContent: string) {
    editor.commands.setContent(newContent);

    // The following code clears the history. Hopefully without side effects.
    const newEditorState = EditorState.create({
        doc: editor.state.doc,
        plugins: editor.state.plugins,
        schema: editor.state.schema
    });
    editor.view.updateState(newEditorState);
}

I tried this, but when I clicked the content of editor, the cursor sometimes would jump to the beginning of the line... A little problem though.

Here is the solution. I hope it helps for future seekers.


	function resetEditorContent() {
		// Capture the current selection
		const currentSelection = editor.state.selection;

		// Reset the content
		editor.commands.setContent(editor.getJSON());

		// Create a new editor state while preserving the old selection
		const newEditorState = EditorStatePrsomirror.create({
			doc: editor.state.doc,
			plugins: editor.state.plugins,
			selection: currentSelection
		});

		// Update the editor state
		editor.view.updateState(newEditorState);
	}

This solution unfortunately did not work for me, and I am unable to figure out why. I am loading new content into existing editor instance after fetching the content from backend. I am using the vue-3 package although not sure if that matters.

But I did find an alternative solution that works for me. It's involves unregistering the history plugin, and re-registering it with a new instance of the plugin:

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

const editor = useEditor({ ...});

await useFetch('/content', {
  onResponse(ctx) {
    if (ctx.response.ok) {
      editor.value.commands.setContent(ctx.response._data.content, false); // <-- data from backend

      // unregister and re-register history plugin to clear data when fetching new content
      editor.value?.unregisterPlugin('history');
      editor.value?.registerPlugin(history());

      editor.value.commands.focus('end');
    }
  },
});

I do this --

  editor.commands.setContent(newContent, false);

  editor?.unregisterPlugin("history");
  editor?.registerPlugin(history());
};

but I get a RangeError: Adding different instances of a keyed plugin (history$) intermittently. With the older new EditorState I get a TypeError: null is not an object (evaluating 'this.docView.matchesNode')

Any idea?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests