Closed xy2z closed 4 years ago
Did you find out how to clear the history?
I ended up doing my custom history. Not ideal but I couldn't find any other way.
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)
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.
Sent a simple PR for this https://github.com/ProseMirror/prosemirror-history/pull/5
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.
@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
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.
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())
It would be good to encapsulate this functionality, preferably also handling the collaboration plugin state (#691). How about Editor.loadDocument(doc, version)
?
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.
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
andmenu.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;
},
};
},
});
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);
};
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
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);
}
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.
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)
}
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.
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);
}
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');
}
},
});
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?
As stated by mattersj you can set an "addToHistory"
metadata property of false
on a transaction to prevent it from being rolled back by undo like so:
editor.chain().setMeta('addToHistory', false).insertContent(text).run();
Do note that setContent()
replaces the entire content of the editor and resets the history stack. This means all previous undo/redo history is lost.
ProseMirror History TipTap setMeta() TipTap Chain Commands TipTap Content Commands
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 errorTypeError: Cannot read property 'matchesNode' of null
Anyone know how?