Conversion of variables and functions 0.10 -> 0.12

Yes. Plugins can also filter transactions if you want do it in a modular way.

You can still initialize the editor with a placeholder document and then give it a fully new state (updateState) at a later point.

Thanks! Now it’s starting to make sense.

Another question: Is there a way one can distinguish between remote and local steps coming in in the dispatchTransaction method?

Not unless you somehow tagged the remote (or local) transactions, but you can do that with setMeta (on the transaction returned by receiveTransaction, for example).

Aha. Interesting. Thank you so much!

Some more things related to the collab module:

  1. Where can I find something equivalent to versionDoc? I figured out I can use getVersion() to get the version number of the last doc that has been confirmed. We use versionDoc to calculate a hash value which then is sent around to all participants. This works as another level of safety to make sure that everyone sees the same thing. But it is essential that we use the versionDoc and not just view.state.doc as that may include unconfirmed local changes.

  2. Where can I find a list of unconfirmed maps? I need this to place the caret of collaborators correctly. Say user 1 sends an update containing 2 steps and then adds information that the caret of user 1 is at position 13. User 2 receives this information, it applies the two steps and then has 3 unconfirmed steps of its own that are being rebased on top of the steps it just received (because it receives them from the server, it knows they are confirmed). In order to draw the caret from user 1 in the right place, it needs to map position 13 through the 3 unconfirmed steps.

If there are other ways of doing these two things, I would also be willing to change things. It really worked well using v 0.10 and we have done a lot of testing/practical use based on that setup, so if at all possible, I would like to do the same with v. 0.21.

And as for scheduleDOMupdate – I understand that this is no longer part of ProseMirror. We are currently using this some places where we need to make sure that reading and writing of the DOM happens in different phases. For example, we place comments and footnotes next to the place that is comment and where are linked from in the DOM, but avoid overlapping them. And we place carets of collaborators in a separate layer in the in the form of a small widget (this part should probably now be doable using widgets).

What would you recommend for such cases? Should we try to copy the code of scheduleDOMupdate from version 0.10 and build on top of that, or should we try to find a third party library or do you have an entirely different recommendation?

This isn’t kept anymore. But you could track it yourself by tracking the steps that come in from the server.

That’s what sendableSteps returns, no?

I think there are libraries that do this, but I don’t have experience with any.

The point of using the hash as an extra safety measure was until now mainly to have a secondary check whether the doc as it is known by the browser really corresponds to the doc of all the collaborators. So basically this was to smooth over any bug in either browser code, ProseMirror or our own code. If we are calculating our own versionDoc independently from what is known to the Editor instance, then it seems like part of the purpose would be gone.

Of course, it’s possible that it’s not needed anymore. I don’t actually have statistics on negative check results for version 0.10 (or 0.21), but for earlier version we actually it be invoked.

Would it not be possible to arrive at the versionDoc by taking the current doc and then reverting unconfirmed steps on it in some way?

Duh! Of course. I wasn’t thinking straight.

Thanks again. All very helpful.

Reverting a step requires knowing the document before it, so if you only have the step, you can’t do that. But you could, after calling receiveTransaction, do something like this:

let rebased = tr.getMeta("rebased")
let baseDoc = rebased > 0 ? tr.docs[tr.steps.length - rebased] : tr.doc

(Where tr is the transaction created by receiveTransaction – it’s "rebased" meta property is the amount of unconfirmed steps that are in it.)

Ok, that makes sense.

And just to make sure I have understood the transition from markRange to decorations correctly: Inline decorations are equivalent to what previously was markRange, but with the difference that I need to do the update for decorations myself whenever the document changes, right?

Reading a bit more on it – decorations definetely seem a bit harder to udnerstand than marked ranges. Probably not because they are much harder, but because the evrnt based model used in 0.10 is closer to what one is used from other editors in JS. Once there is more documentation it will probably be a lot clearer.

From what I can tell, decorations are specified through the decorations property of the editor view. I can’t see how one can add new ones or remove some, so my guess is that those decorations added to the property will automatically be used with the next DOM redraw. What I can’t figure out is that it seems like the decortions use position numbers from the doc, which is part of the state. But what happens if the view receives a new state with a new doc where the position numbers are different? Shouldn’t I map the positions forward? What if the doc is completely different so that no mapping can take place? Not clear to me.

Another thing is that it says about inline nodes “Creates an inline decoration, which adds the given attributes to each inline node between from and to.” which made me wonder if that means that it will not allow for a partial text node to be styled the way markRange did. Looking at the ranges test file [1] made me think that this should be possible.

[1] https://github.com/ProseMirror/prosemirror-view/blob/26cfaa17e485f9c549f8d31466d32b7a9ecdd4b3/test/test-decoration.js#L13

You’ll usually want to have a plugin maintain a given set of decorations as part of its state, and in its state apply method, map its set forward to match the new document.

Then a wholly new state is created, so old plugin state is also discarded.

Yes, that’s possible. Conceptually, text nodes aren’t really single nodes, but rather a span of text that, for practical reasons, is represented as a single object, even though each character might have different decorations applied to it.

After 5 weeks of development, 4159 new lines of code and 3694 line deletions, our development branch has finally been updated to ProseMirror 0.22. Thanks for all the help, @marijn!

As we are open soruce, maybe reading our code can help some other implementers as well. I have tried to stick to Marijn’s recommendations for how to best use the PM library, but I am sure there are still areas where we could improve our code, so feedback would also be greatly appreciated.

Looking at the function rebaseSteps, it seems like there are situations when not all unconfirmed steps can be rebased, yet the rebased [1] number is based on the number of unconfirmed steps before trying to rebase steps on top of the incoming steps. So I believe it’s in these situations that tr.docs[tr.steps.length - rebased] comes up with the wrong result for me.

This seems to be working though:

let rebased = tr.getMeta("rebased")
let docNumber = rebased + stepsLength
let confirmedDoc = docNumber === tr.docs.length ?
            tr.doc :
            tr.docs[docNumber]

Where stepsLength is the number of steps as they were received from the server.

This is still a bit complex and new for me, so further tests may show problems with this also.

[1] prosemirror-collab/src/collab.js at 039209c71bc10cdbfaab77f206a2dd07447e1565 ¡ ProseMirror/prosemirror-collab ¡ GitHub