What does a read-only editor even mean?

I am not sure I follow. The user would be typing in the version of the document that already has various parts marked as deleted (turned into widgets), right? So if we have original doc0 (with no tracked changes) and we are typing in doc1 (which has some parts marked as deleted [turned into uneditable widgets] and some other parts marked as added). The user now deletes one more letter, which we receive as a transformation to be applied to doc1 and we get doc2. But we can’t really use that transformation information on doc0, because with the widgets applied, the node structure of doc0 and doc1 will be quite different, right?

But maybe you thinking of widgets not as normal prosemirror document nodes, but rather as something that is inserted without changing the document structure. For example by adding an empty markrange, and then filling the empty span that will create with other elements which contain the deleted text? Something in that direction?

Ah, now I get it! I can see how this can work for some cases. I think there are other cases where one wants to enforce using tracked changes for some users. And some users won’t be able to adjust to this changed way of doing things.

Yes, indeed. (But note that this doesn’t exist yet, though it is planned.)

I’d be a big fan of this type of functionality.

Something like annotations would require some sub-classing and would be managed externally by the app.

Google Docs has a great implementation of all of the above which is really, really useful in my opinion.

I’m exploring a use case that’s close to this, but with one change: I need to make the annotations / comments one-sided, and only visible to the user who created them.

Theoretically, I could tie a user ID to each annotation, and hide all other annotations via CSS; however, I’d much prefer that the annotations and their data simply aren’t accessible to the other collaborators. Is there an approach to this that I could take with the existing API?

Yes – model the comments as marked ranges, so that they aren’t part of the document, and simply don’t send them to users that don’t have access to them.

That makes sense!

I’m still feeling my way through the documentation, but conceptually, how would the comments update their range when another user makes an edit?

The collab demo from the website repository uses comments, so you should probably check it out: collab demo code.
In terms of updating the ranges, this is the important bit: code

I would be interested in an update on this topic in regards to the latest ProseMirror version. A lot of things have changed and filtering actions does not seem to be a viable option anymore, as we are letting the browser handle more events in the contenteditable than in older versions of ProseMirror.

What I’m currently trying to achieve is the following read-only mode:

  • The user should be able to select and copy content
  • The user should not be able to edit the document content in any way
  • The user should be able to select content and add annotations to the selected range
  • The editor should show changes from other collaborators who can edit the document in real-time
  • The user should not see any menus (this one is easy as menus are completely decoupled now)
  • Ideally the user should not see a caret

My current experimental approach looks like this:

  • Set the ProseMirror wrapper to contentEditable = false (needed to hide the caret)
  • Remove plugins that are used for editing (e.g. keymaps, input rules, history, etc.)
  • Ignore all actions of type transform, which do not have a collabState field set (ignore everything but real-time updates originating from other users). This is needed as there are still events that are not prevented by setting contentEditable to false:
    • Editing events like paste and for example custom click handlers that generate transforms

What do you think about this approach? Are there better ways to achieve the kind of read-only mode that I want?
The only thing that is currently not exposed by ProseMirror is the option to set contentEditable to false. This can be achieved by using editorView.docView.dom.contentEditable = "false". If this is the right approach it would be nice to expose a contentEditable prop.

One thing missing here is the ability to add annotations to the active selection as the selection isn’t tracked by ProseMirror as soon as we’re not using contenteditable.

Another question is if an “official” read-only mode provided by ProseMirror would be better to achieve these things. In that case I would expect ProseMirror to prevent all editing that is created through browser events while still accepting transforms through the API.

Filtering out transform actions is indeed the recommended approach to making an editor read-only. But since 0.15 you’ll still see edits flash and then disappear when you make them, which is non-optimal. Yet turning off contentEditable breaks selection, as you noted.

We can go in two directions, depending on whether we want to have a caret in readOnly mode. Either change the way events are handled to suppress DOM editing when readOnly is enabled, or disable contenteditable entirely, and handle the correspondence between the selection state and the DOM selection differently in that case.

I’m leaning towards the second. Does anyone feel having a cursor when the content isn’t editable is a useful feature?

During my testing I noticed that edits do not disappear immediately in 0.15. For example writing text like “hello” inside a paragraph, stays in the editor until I change the selection. I tested this in Chrome and Firefox on Linux.

I’m also leaning towards the second approach. For the use cases that I have in mind, I do not want to show a cursor in read-only mode, as it makes the user think that the document can be edited.

If you decide to ignore an action, you have to call updateState with the old state to ensure consistency. That’s awkward, so I’m working on removing this requirement right now. (This was already a problem for some types of changes in 0.14.)

Ah ok, I did not know that! Indeed, it fixes the problem, but great to hear that you’re working on removing this requirement.

See this patch.

1 Like

I am also for the second approach, and at my current hack I also set contenteditable to false for hiding caret at what is supposed to be non-editable nodes. It would be great to have a prosemirror provided way on how to deal with non-editable nodes.

Second approach would be preferred. I’m currently having my page render two divs of content, one that has an editor and one that has a copy of the content. When a user hits “edit”, I switch which div is visible. (I don’t currently have the requirement of annotations or collab).

I’ve pushed some changes that add an editable prop that you can set to false to disable editing. Testing would be appreciated.

Thanks for the fast implementation! I’m currently testing it and noticed the following issue:

Setting the editable prop to true or false does not work and it needs to be set to a function accepting the editor state. I think both options make sense, but I’m not sure if the current behavior is by design. Either we need to update the docs or we need to add the possibility of using a boolean in addition to being able to use a function.

Ah, right, the fact that it expects a function is intentional (so that you can easily make it depend on some piece of state).

We are also in the situation that we still need changes come through and usually only block 95% of editing actions, so we still need a caret. I understand that you are now going for the option without a caret, but the filtering (with flicker) will continue to work, right?

Right, the filtering continues to work.

How do you decide on which actions to block? Are these scoped to certain node types for different users? In that case one approach could be to let the node decide at render time whether to set its contenteditable attribute to false. You could save the information of which nodes to block inside the schema.cached object and access it inside the toDOM method of the node. That way you won’t have any caret inside the nodes that the user should not be able to edit and there won’t be any flickering.