What does a read-only editor even mean?

editor (Computers), noun: a program used for writing and revising code, data, or text

read-only (Computers), adjective: of or relating to files or memory that can be read but cannot normally be changed

Several people have asked for a read-only feature to ProseMirror. Today, I spent some time thinking about how this should work. My conclusion was that I have no idea how this should work, and that the different people asking for it probably have different ideas in mind. Here are some possible use cases:

  • Simply show a document in a page, as plain content

  • Show a document in a page in such a way that it looks exactly like it would look inside of a ProseMirror editor, but without any editor functionality.

  • Have a regular editor, including menus and non-editing commands, but disallow all editing. Still allow updating the document through the API.

  • Have a regular editor, but disallow some kinds of editing.

The top two should probably stay entirely outside of ProseMirror. You can render the HTML of your document, and show that in your page, optionally applying ProseMirror’s stylesheets by wrapping it in a ProseMirror-content element.

The third one is unexpectedly hard to implement because distinguishing between user actions and API calls is not something you can easily do, since most user editing actions will go through API calls. Also, for ProseMirror’s approach to managing DOM and selection to make any sense, contentEditable needs to be enabled, which means the browser will consider the content to be editable and give you cut and paste menu items, etc (even if we end up intercepting and discarding their effect).

I am not really interested in going down the road I went with CodeMirror, which is full of checks and hacks to suppress user editing in read-only mode. It makes the code ugly, and has led to a whole string of bugs where a case was missed and users would be able to edit read-only content in some cases.

The last case, disallowing only some kinds of edits, is kind of like the third, but even messier.

In another thread a system based on filtering transform steps was proposed. Something like that could work (though it’d have to happen on the editor level, not on the transform level, since those aren’t necessarily tied to an editor). This might not always be a very helpful solution though:

  • You couldn’t really do fine-grained filtering, but you could deny or accept transforms based on some test, either by looking at the steps, or by doing a test on the resulting document.

  • The filtering will also apply to all API calls that result in a transform. To kludge around that we could add a filter: false option to apply to suppress it.

  • The menu wouldn’t be aware of the read-only state of the editor (and I’m not volunteering to maintain all the extra cruft needed to make it aware) so you’d have to explicitly define your own menu for your read-only interface.

What are your thoughts? Suggestions? Do you have a use case I didn’t take into account?

Like you said, ready only does not really cover what most people seem to associate with it. I’d propose the following:

Viewing (=read only)

No modifications possible.

Possible solution: Disable all interaction (contentEditable = false, API > /dev/null)

Annotating

Only highlights and comments are possible.

Possible solution:

  1. Remember a range for each highlight, comment. Do not change the document
  2. Add a marker within the document (and possibly meta data)

Suggesting

Text can be changed but will not be merged (i.e. not visible for others != the author).

Possible solution: ProseMirror itself would not handle this use case but the surrounding app would save the changes as a diff.

Editing

  • Full access

Case 2 for annotations is the only tricky one (from the ProseMirror side of things). Filtering transforms would certainly be a way here.

Just a thought: An alternative solution could be to maintain a shadow copy of the document in the background and manually apply only the markers to the copy. The main editor could be completely read only then (only the API to read selections would be needed here). I am not sure whether this creates a performance hit since the data is maintained twice. Probably the read only copy does not need to remember as much though. If it only exposes the position API, it does not need much meta data at all.

Sorry, the readonly term I introduced was misleading. I can’t imagine allowing any combination of actions to be permitted or restricted. Perhaps a valid use case might shed light on specifically what is needed in other cases.

My use case is described above as annotation and the implementation is [here] (http://pboysen.github.io/comments.html). I made modifications to the collab annotation example and added the hack I mentioned before. Hopefully there may be a way to do without the hack. I also set the commandset to empty and created an empty menubar for visual consistency with the full menubar. That is all I need. The same commenting functionality is in the full editing interface.

@marijn For this interface to be fully responsive I need to know when PM reflows the contents so I can reposition the comments. Is there something better than window.resize?

@marijn I noticed in the collab example that you used var range = this.pm.markRange(from, to, { className: "comment", id: id }); however the id is not saved probably because code changes. I don’t need it although it might have some use for other applications.

Hey,

before the discussion on read only mode, I had expected you wouldn’t do anything about this. So I thought I would implement cases 3 and 4 myself this way:

For case 3, try to add a transparent big block element over the editor and add some other hacks to make sure the user isn’t able to ever select the editor element. And on the server make sure not to accept anything sent in by the user who should be in “read-only” mode.

For case 4, listen to the transform event (possibly beforeTransform) and add reverse transforms for all those actions not permitted + make sure to send neither original nor reverse transform to collaborators. On the server a check not to accept any transform step not allowed to the user.

Is this entirely impossible? The first two cases I would indeed handle differently. Most likely by exporting to an entirely different site (Wordpress, etc.)

The value for having a read-only instance of ProseMirror probably comes when you introduce widgets and whatnot. Then when there are cases where you want to show the content, but not have it be editable it becomes more complicated.

I was able to deal with this situation with CodeMirror just by handling the content myself and just instantiating my widget instances myself. This might get more complicated if you are dealing with arbitrary widgets from third parties and it also creates more complicated code since you have to have two different ways to do the same thing (i.e., display your content).

So, I still see some value in having a read-only editor instance. For me, though, in my specific use case, I would be wanting something more like your second option. But I can see the other cases too.

Unfortunately, no, browsers don’t provide anything that helps detect reflows. You can listen to ProseMirror’s “draw” event.

You can get it from the range with .options.id (your options object is directly saved into the range object), but indeed, it doesn’t look like my code actually uses that property anymore.

These approaches might work, but are very hacky, so I’m going to try to provide a better interface.

I’ve pushed patch 07f768c61, which adds a filterTransform event which allows you to look at a transform and either cancel it or let it through. Doing pm.on("filterTransform", () => true) would cancel all transforms, giving you a read-only editor. Let me know how this works out for you.

Thanks for doing this! I will test it out.

I checked out Google Docs for the positioning of comments. If the range moves vertically, the comment will follow suit in a second or two. The only way I can think to implement that is a busy wait, looking for range movement. That doesn’t sound appealing. I looked at draw, but rechecking after every character isn’t much better. I’ll keep thinking.

As to the id, I was expecting it to in the dom. Anyway, I don’t think I need it.

Works like a charm. Very elegant solution!

The ideal here would be:

Change tracking isn’t really being discussed here. That’ll have to be done in a different way (edit-enabled editor, with the original text saved and, I think, marked ranges + inserted widgets used to highlight the differences in the text).

Tracking changes will have to be able to track the changes of X different users. It is custom that a record of the change is kept as well, and it would be preferable if some user(s) can edit the document without being tracked.

So would the way to do this with the proposed widgets be to monitor all transformations, and if a transformation deletes something, find out what that is and insert a widget containing that deleted text, unless there is another widget right next to it with which could be grown?

Another question is how one would record paragraph merging/separation operations. I was thinking of adding attributes to the paragraph node that record if a user has requested to merge it with the previous or next paragraph, or whether the start or end of a paragraph is the result of a split operation request. Alternatively, one could just switch off the ability to split paragraphs for users who’s edits are tracked.

No. You’d keep an original version and generate widgets based on the difference between a given version and that original.

I don’t know.

Ok, but then the user wouldn’t be to see the change tracking as it takes place, would he? Say the user first deletes one letter. A few seconds latter the user deletes several adjacent letters. The way it works in wordprocessors, the first letter would be crossed out red first, and then while the user continues deleting more letters, the crossed out line would also go through those letters.

You’d keep the diff up to date, presumably.

Has change tracking ever been implemented in a collaborative environment? Sounds messy as noted above and I personally wonder if it is worth it. I find Word’s track changes hard to read if there are a lot of changes.

What users do like is the ability to approve/disapprove changes suggested by others. There is a demo here. Select the comment style “Editing with suggestions”. Swipe some text and select the delete option. The comment will appear with Approve/Disapprove which presumably only those with permission to edit would see. Note that replace doesn’t work correctly yet. I have a note in this forum about that.

Feel free to flame this solution. It is just that what Word does is held up as the gold standard when it was started in a pre-Internet environment and users don’t want to change. Is there a better way to edit in this environment?

We made ICE [1] work with our multi-user editor. It worked – but there were too many other issues with the editor itself.

I couldn’t figure out how to do the tracked changes in your example. I selected "Editing with changes. Then I first tried editing the text, but it didn’t seem to track anything. Then I read through your description again and tried to select some text and looked through the menu to mark this as deleted. Then I tried marking it using my touch-screen screen. I saw no pop-up or anything that would allow me to do it.

Yes, the problem is a bit that users have a really hard time changing anything about their behavior. If we get them to change, the new way of doing things must be super intuitive.

[1] http://nytimes.github.io/ice/demo/

I am not tracking changes on purpose. If you want to make a comment or suggestion, select some text and click on the comment bubble in the upper right of the menubar. (I have a version where the prompt appears as soon as something is selected. That to me is a UX decision). Let me know if that works for you. I did not try it with a touch screen.

We are on the same page. Sometimes you have to build software for users when you wonder if it is worth it. Like a library model for track changes (which I see in industry) or worse, passing files in email which happens in a university environment. Old habits die hard.

BTW, I am impressed with the ICE demo. I hope you can get it to work in a PM environment!