In v0.12 what's the right for "user annotation" use case?


While migrating our code from 0.10 to 0.12 we want to re-write our “user annotation” feature and are looking for the correct method within the new API but are not sure.

We first thought to use a custom mark (annotationMark) with a unique hash as attribute (for linking to an external annotation object) but :

  • marks do not overlap (so if a user annotate over other users annotations it will override them, which is logic for strong marks but not for our use case)

We then look at Decorators but they not seems to be serialized (if we are right).

My question in a nutshell : what would be the correct API tool for this “unique user annotation that should be persisted in lossless json” use case ?

Thanks Matthieu

1 Like

Probably decorations. You’ll have to do the serialization your own way, but that’s not a very complicated problem. Marks can also work, if you really want to treat these as part of the document, but they aren’t really suited for representing cross-block, continuous ranges – they are attached to specific nodes, and can get split or duplicated by editing operations.

ok, thank you for your fast and usefull feedback

What about the problem of overlapping marks of the same type? How much would I need to rewrite to allow for this?

Overlapping marks of same type is not a problem isn’t it ? They are merged. FYI concerning my question we went down the Decoration road with custom serialization.

Yes, but one is removed when the other one is added, right? So if two different users refer to the same piece of text, the first comment referer is removed when the second one is added. Or maybe this has been changed since 0.10? Unfortunately decorations don’t work for us for other reasons, so we need to treat it as part of the text. I’m thinking now that the easiest way to achieve what we need would be to do something like:

  1. redefine our CommentMark so that instead of having an id attribute that just contains one value, it has an id attribute that contains an array of values.

  2. write two new methods: addCommentLink(from, to, commentId) and removeCommentLink(from, to, commentId) more or less like this

    addCommentId(from, to, commentId) {,to, node => { if (!node.isInline) { return } if (node…hasCommentMark) { … add transformation replace comment mark with a comment mark that contains the current ids + commentId } else { … add transformation that adds a new comment mark with id = [commentId] } }) }

Does this look reasonable?

(sorry, the server threw a 500 error when I tried to add indentation to the pseudo code.)

In our understanding this is correct for marks. Then this is not acceptable for “user annotations” use case.

We were at that precise crossroad and choose the ‘Decoration’ path. Because marking text or decorating it is 2 differents concepts & behaviors and annotations are much closer of the decoration technique in our view.

From our experience there are more dangers down the road when you mis-use a tool and its goals. For example when yo will serialize your document to HTML or Docx you want to keep your marks but not your decorations. So if you make your annotations as marks you’ll have to filter them out. So it adds a lot of noise in your processes.

So your suggestion does not seem reasonable for this reason. But again, that’s our opinion and use case.

Edit : To be really precise the “Decoration” path for annotation is not 100% cool. We are re-writing this part of the app (Redux involved) to be more solid. It is quite hard to maintain sync between PM and a separate “annotations store” and our code contained too much “pause the flow and wait for this to happen” code.

Sorry, I didn’t mean to say you should change anything. In our case, the conversion mechanism to docx/odt is not bothered by extra marks. We have had extensive testing on it, and it’s not an issue for us.

It was meant as a question: if you choose to go the marks route, would the above solution be a reasonable way to do it?

Please don’t ! I didn’t take it this way :slight_smile:

Your suggestion is similar to what we’ve thought when we wanted really hard to go the mark way.

That shouldn’t be the case. If you write a plugin that keeps a decoration set and maps it through transform actions as they come in, your data should always be in sync.

1 Like

I’m thinking about making which marks may coexist more configurable. I’ll keep the use case of wanting multiple marks of the same type to overlap in mind.

1 Like

This is also what I ran into when I first tried it (with marked ranges at the time). Back then positions were still something more complex than just a number and we had (and have) a server that doesn’t understand any of the structure of the document and only gets a full copy of the document every few minutes. So we ran into situations where one user added a decorator in version 50 of the document, and the range info had to be transmitted to other participants, but a full version of the doc wouldn’t be transmitted before version 100. And some of the other participants wouldn’t get every websocket message, so they would be somewhere around version 48-50 of the document, or they may be at version 51 with 1 step waiting in the queue for confirmation, and then we really needed to add an extra empty step number for the addition of the marked range, so that we had a way to confirm that it would be sent everywhere, etc. . And then suddenly all users disconnect without a final version ever having been stored to the server. So we have version 0 stored on the server, and 51 diff steps, and one decorator/marked range that has to be added exactly after step 51, and then maybe another 30 diff steps… It seemed rather complex in comparison with using marks.

I remember Marijn told me at the time that he didn’t get why I was having trouble with this, so it’s entirely possible that there are things I didn’t get at the time. Since then there are more reasons why we consider it to be part of the document itself. For example, one of the students has been experimenting with also serializing the comments when creating a docx. Our serializer basically walks over the json as it comes out of PM. We still use markedranges (which will be decorators once we upgrade) when a user initially creates a comment before it is to be shared with peers. Only when the user clicks on “save” for the first time, we turn it into a mark that becomes part of the document. But now we have the usecase where several users need to be able to comment on the same piece of content and they have to be able to overlap in their comments…

Ok, as fas as I can tell, the json format shouldn’t have problems with allowing multiple instances of marks of the same type. Alternatively, one could use the same mark and add several values to the same mark. It seems like the second should be possible without “breaking” anything in current ProseMirror. So would you suggest we do the following now:

  1. Try to add functions to allow:

     {type: 'text', marks: [
  2. Try to modify our code so that comment markers are stored as:

     {type: 'text', marks: [{type:'comment',attrs:{id:[1,2]}}]
  3. Wait until you figure out what this restrictions system will look like

1 Like

We do not have (yet) implemented collaborative editing. Reading your feedback I fear some headache with our “Decoration path”.

For sure, a mark that behave differently than actual mark (more like decoration then) would be a nice, simpler, solution.

1 Like