Replacing a state's doc

How can I ‘load’ a new doc (ie a remote ‘checkpoint’/revision’) into an existing view/state ?

I want to replace the entire doc with a new one. I basically want to reinitialize the view with an identical state/config that just has it’s doc modified to a different one. How can this be achieved ?

I can’t create a new state because I can’t get the config of the old one. I can serialize/replace/deserialize but that seems awkward. Same for select all and replace.

Thx!

Why can’t you get the config of the old one? If you have your EditorView, you can do the following:

    let newState = EditorState.create({schema: view.state.schema, doc, plugins: view.state.plugins});
    view.updateState(newState);

Where I use DOMParser to parse some html into a doc node.

thanks ! the problem with this is that the new state’s plugins are not initialized, that is their init method is not called which I think cause all kinds of problems (for example they may still hold a reference to the old state)

I think maybe the solution is to call reconfigure using view.state.plugins but I’m not sure that’s correct.

Plugin instances don’t ever hold any references to a specific state (unless you’re doing something weird) – they are just descriptions of how the plugin behaves. If they keep state, they do it by declaring state fields, which hang off the EditorState object, not the Plugin.

I am thinking for example about collab. When resetting the doc I want collab to reset as well, that is for sendableSteps to return null even if there was something in it before. What is the best way to reset the doc in that situation ?

Exactly, which is why you’d create an entirely new state value.

@marijn @wes-r and others :slight_smile:

What if I need to change collab plugin version property when initializing a new doc? I need to filter out collab plugin from view.state.plugins and create it again with needed version property? something like

const plugins = view.state.plugins.filter(p => p.key !== 'collab');
plugins.push(collab({ version: 20 }));
let newState = EditorState.create({schema: view.state.schema, doc, plugins});

?

Right. Or even better, create a function that produces the array of plugins for your state, and parameterize it with the things you might want to change, so that you can call the same function when creating the original state and when creating a new one.

@marijn

If I need to filter out collab plugin from plugins array, what is the correct way to do it?

key is not a property of Plugin, so I can’t really use it.

And is it ok to completely update document structure from inside the plugin?

Recreate the array of plugins, don’t filter it (unless you preserved the actual object collab() returned, in which case I guess you could look for that).

I do not know what you mean by that.

I too think there is a need for this.

When you call updateState(EditorState.create({ doc }))

You are getting a state that is not entirely based on the existing state, like you do when you use a transaction. Even if you do endeavour to pass in all the same plugins etc.

The problem is you lose the state of the plugins. (like selection, and history)

Perhaps state.reconfigure's config could accept a new doc.

This is especially important with data flow in web components, where the state will be broadcast out of the component (imagine an onchange sending out the doc.toJSON()). But then, it will flow back into the component. At which point, you don’t want to re-initialise all the plugins. You literally just want to update the doc.

I think it comes down to there being no public way to clone a state without knowing every possible thing you need to clone

updateDoc(doc) {
  this.view.updateState(
    EditorState.create({
      doc,
      schema: this.view.state.schema,
      selection: this.view.state.selection,
      storedMarks: this.view.state.storedMarks,
      plugins: this.view.state.plugins
    })
  );
}

// Would prefer:
updateDoc(doc) {
  this.view.state.reconfigure({ doc });
}

That’s the idea. If you do want to preserve plugin state, you can just create transaction that replaces the entire document content with the new document’s content, no?

Mmm, I think I follow, I’d need to see an example of that though, I can’t quite envisage what it would look like.

state.tr.replace(0, state.doc.content.size, new Slice(newDoc.content, 0, 0)) should do it.

state.doc.content.length should be state.doc.content.size

Another simpler one:

state.tr.replaceWith(0, state.doc.content.size, newDoc.content)

Using tr.replaceWith you can pass in a node/fragment because it wraps that with a slice for you under the hood.

Indeed! I’ve edited my reply.

That… seems to work but isn’t something that’s guaranteed to continue working in the future.

Thanks for the info. Any particular reason to deprecate&remove that public method?

No one’s going to remove it, but it’s documented as taking a node, and the fact that fragments also currently work is an accident.

Thanks all.