Issue #114 is one of the oldest open issues on the tracker now. I’ve been meaning to look into it ages ago, but other things kept getting in between.
The problem is this: A lot of the code in ProseMirror assumes the document structure to be roughly a tree of blocks, of which some leafs are textblocks that may have inline children, which are always leaf (child-less) nodes.
But there’s nothing about the way ProseMirror schemas are defined that actually enforces this. In principle, all nodes can have children. This topic is about the question of how to deal with inline nodes with children.
The two main use cases for this kind of structure that people have presented so far are pieces of text that have a different meaning (such as @-mentions, hashtags, or other autolinked syntax), and footnotes that are written inline with the content around them.
I spent some time today experimenting with this. After changing a lot of uses of the isTextblock
property of nodes to use the (new) inlinContent
property instead, and fixing a few other places that used bad assumptions, I have an inline node that contains text roughly working, but the following issues came up:
-
Browsers normalize cursor positions on the boundaries of inline nodes to a single position. I.e. you can’t set the selection directly after the end of an inline node, since the browser will normalize it to the position before the end. (Depending on the browser, some seem to normalize the other way.)
-
Drawing of cursors on such boundaries is also rather glitchy. I.e. the DOM selection might report the cursor being inside the node, but the actual cursor gets drawn outside of it.
-
If you want to treat the content of such nodes a lot like normal text, most of the issues that originally motivated me to use a flat representation for inline content come up again
-
What does it mean when the footnote node as a whole is marked bold? And when it is, some text inside of it could be marked bold again.
-
What should delete/backspace around the edges do? I expect most people will want it to do what it would do in flat text (delete the character before/after), but making that work across nested nodes is quite awkward.
-
Should the cursor still be considered as being inside the grandparent textblock when in such a node? (For example when determining the effect of changing the block type to a heading.)
-
When inserting text or pasting at the boundaries of such a node, should the content be included in the node or not?
-
The DOM selection issues could be partially avoided by saying that, unlike block nodes, the start and end of inline non-leaf nodes don’t get separate cursor positions. I.e. there’d be, in the selection/position model, no difference between being directly in front of such a node or directly at its start (and similar for the end). But that produces a lot of ambiguity around the behavior of editing actions in such positions, which is also not ideal.
Furthermore, I believe there is a lot of value in having a representation that closely corresponds to the thing it should represent, and makes implementing commands and keeping things consistent straightforward.
Thus, since the two use cases we’ve got so far (mentions, footnotes) both seem like they’d want to treat these nodes as much as plain old text as possible, maybe we should explicitly disallow non-leaf inline nodes and look for a different solution. If something is supposed to be inline, it probably should behave like other inline stuff, and be represented in the same way.
What we currently have in this space are marks, bits of data associated with stretches of inline nodes to make them emphasized or linked or whatever. There are a few problems with trying to use marks to represent @-mentions or footnotes:
-
Pasting into them or pressing enter inside of them splits them, since they are simply associated with the text, and that text is allowed to move anywhere.
-
They can overlay with all other marks, and contain the exact set of node types that the parent container may contain, so you can’t constrain what kind of content they cover.
-
They are split into pieces when drawn, one per child node. So styling them with a border, padding, or border-radius isn’t going to look good.
It might be possible to add a feature to the schema system that addresses those last two issues—though I haven’t quite worked out how it’d work yet. This’d be less ‘regular’ than simply reusing the tree structure for such a feature, but, again, there’s a number of good reasons to prefer flat structure for paragraph-level content. Before working on that, however, I’d like to look into the first issue, and I’d like to ask around if someone has another use case.
So, what should happen when you press enter or paste a bunch of random stuff in the middle of a mention, hash tag, or whatever special marked-up elements your app includes in the document? How about a footnote? Is editing footnotes inline a good idea in general?
Do any other things that you’d want to represent as an inline node with content come to mind? Do these fit a text-like editing method, when it comes to changing their content, or are they different?
Are you aware of an implementation of highlighted syntax elements or of inline footnotes that works well, which we might take inspiration from?
(cc @frederik, @johanneswilm, @bradleyayers, @rsaccon, @bolerio)