Prosemirror ready for widgets (islands of non-editable content)?

Hey, first of all, congrats with this! I have spend several years trying to create an open source document system for academic purposes ( http://www.fiduswriter.org ), but the hardest part of it has been to get the actual text input work just right. Until now, we couldn’t use any of the existing editors because there was always something lacking. So we wrote it all ourselves, and contenteditable being a moving target with no standardization meant that it was constantly breaking. Not it seems like Prosemirror could fulfill that part. The thing is we needed to have quite a lot of extra features, such as citation management and tracked changes (I contributed quite a lot of code to NYT’s ICE for that).

Now I wonder: Should I try to create such features on top of the current version of ProseMirror? I guess citations that behave like one non-editable area would be similar to the dinosaur demo. Or should one wait with such things until after you have done your research on schemas, etc. ?

As for tracked changes and comments (comments that are connected to a specific part of text, just like in Google Docs), I assume that these are things one should wait with until there is more clarity as to what API there will be, right? Tracked changes seems like it could be a module that would also interest others and therefore could possibly best be programmed as a plugin to ProseMirror.

1 Like

I think it’d be a good idea to wait for the API to stabilize, but if you’re in a hurry to prototype something, and don’t mind having your code break a lot on upgrading, you could try adjusting the code from the dinosaur demo and later porting it to the proper API.

Perhaps it would be helpful to try to define what is inside a single pm-editable element, and what should be handled by creating multiple pm-editable elements.

E.g. it seems a bit unnatural for a user to create a comment at the same time as editing the text which is the subject of the comment.

Rather, one might expect that some action would precipitate creating a new comment, and that action would then open a new pm-editable element alongside (not part of) the text being commented on.

This would suggest, that Prosemirror does not need to be made aware of all kinds of complex text structures and the different ways those parts relate to each other. Instead, you can constrain one pm-editable element to a single linear text flow (just like markdown) and let the higher level document manage how these are composed.

hth ps - this is also the way pub-server uses markdown fragments to generate complex web pages.

Another use of “islands” is media embeds: images, youtube, soundcloud players. These should be within the text flow but need a pattern to keep the cursor behavior sane (insert p between 2 videos) and not allow messing up the video embed.

I’m looking to embed arbitrary widgets, and my initial experimenting with the dino demo is going well. I’m attaching a double click event handler to the node generated by renderDOM.dino with the idea that I can trigger whichever content specific editor is appropriate.

This works great except that there doesn’t seem to be a good way to get a model.Pos from a rendered node so that I can update the model with my changes. I see the pm-span and pm-path attributes in the DOM, but my impression is that I shouldn’t try to work those out myself.

I did find the posFromDOM function in edit/selection. It seems to do exactly what I need, but obviously it’s not a publicly exported function. Is there another API or approach I’m missing?

I understand that ALL of this is in flux, so I’d consider “come back later” to be a perfectly reasonable response. :smile:

Thank you so much for this whole project!

I’m currently using this function as an alternative to forking the core to expose posFromDOM:

function nodeAndPosFromDOM(pm, element) {
	const before = posAtCoords(pm, element.getBoundingClientRect());
	const after = new Pos(before.path, before.offset + 1);
	const node = getSpan(pm.doc, after);
	return {node, before, after};
}

Assuming it actually proves to be correct in the long run, it’s obviously not very efficient. Fortunately this isn’t a hot spot, so I’m more worried about the correctness at this stage.

Feel free to import edit/selection and use posFromDOM for the time being. Eventually, something like that will be part of the official and documented API, but I’m still considering changes in the way DOM nodes and positions are associated with each other, so this might not be stable yet.

I made a custom attribute for my non-editable types:

const editableAttribute = new Attribute({
  default: 'false'
})
editableAttribute.parseDOM = (dom, options) => dom.getAttribute('contenteditable') || 'false'
editableAttribute.serializeDOM = (dom, editable) => dom ? dom.setAttribute('contenteditable', editable) : null

let editablePred = (_, data) => data.type.prototype.isNotEditable
spec = spec.addAttribute(editablePred, 'contenteditable', editableAttribute)

(context)

It works to make the expected html, but editing behavior around the blocks needs work. It needs to

  1. Suppress editing: the cursor doesn’t show up, but the blocks are still editable
  • (edit: added this, didn’t realize could still type without the cursor)
  1. Suppress the tooltip, which appears with double-click or drag select, and applies formatting
  2. Delete under a non-editable block combines the editable one into the non, and cursor disappears
  3. Clicking anywhere in a non-editable block should just select it. I’d like custom actions in the block tooltip.

Hints on the best way to do those 4 would be helpful, or I can just keep hacking on it.

You’ll want to wait until I get around to implementing locked nodes. The fact that they are a lot more involved than just setting contenteditable to false is the reason why they aren’t done yet.

I’ve been thinking about “locked nodes” as two separate features: non-editable nodes, and nodes that only editable inline, but the block structure is locked.

Locked nodes, as I’m planning them, only lock a single level – their immediate child content. Within that, the user can edit as normal.

So would a block node with a text node child result in the child text being non-editable? This is something that I’ve been trying to do. Has this feature made its way into version 0.18?

If not can you recommend an approach to having some un-editable nodes within a document?

No, by default anything that allows content is editable. But you can override this with a custom node view.