Nested inline nodes

For a note taking application I am building (for personal use for now) I have designed a custom language for tagging pieces of text, a markup language. The tags are used to give more meaning to text and allow for customized styling of those pieces of text.

This was initially implemented using CodeMirror, because ProseMirror did not exist at the time. A tag could be as simple as {book: Some Title} and by default it would be displayed just as the text itself, but I made it so that you could render them as special widgets that were either inline or block, such that their styling could be fully customized. The book tag could for example be rendered with a little thumbnail of its cover and a link to a website with its summary or whatever. And if you selected one of these widgets, they would automatically be converted back to their textual representation.

This approach works well for the simpler kind of widgets, i.e. leaf widgets, but for structural widgets that also introduce a layout, e.g. as is the case for tables, this approach no longer is as sufficient. You could embed additional CodeMirror instances to work around it, but this does not really scale and would get complex really fast. The other approach would be to try and stay within the one CodeMirror instance by leveraging readonly marks for the parts that are only used for styling the widget, but this would not allow for arbitrary HTML widgets and would also be very complex to keep correct. So I just kept thing simple and only allowed the editing of widgets within their textual representation. This meant that if you had a complex table structure, that as soon as you placed the cursor on the rendered widget, the whole thing would just collapse back to its textual representation.

With ProseMirror in beta I tried to readdress this and see whether I could get these more complex editable widgets to work. However from what I have gathered so far by experimenting and reading other topics it seems like my use case is not really supported.

The markup language consists of only text and tags. Tag attributes are also tags. If a tag, or one of its attribute tags, contains multi-line text it has to be a block tag. If the text within a tag should not be interpreted for other tags it has to be a literal tag. Otherwise it is an inline tag. Here are some examples of what the textual representation looks like:

text {inline{@attr: attr text}: tagged text} text

text {block:}
  {@attr: attr text}
  tagged text
  tagged text

text {literal=}
  {@attr: attr text}
  {ignored: literal text}
  literal text

I would love to enhance the textual representation with what ProseMirror has to offer. First I looked at decorators, but they seem more appropriate for things like syntax highlighting. What I really am after is the ability to style my tags with arbitrary HTML, but continue the editor as it were, in some fragments. So I created a schema instead, which mostly worked. However I then found the issue about inline nodes with content. So how does that affect my inline tags, which in turn can have other inline tags within them? Does this mean I cannot implement what I hoped to implement with ProseMirror?

And if nested inline nodes are no longer a problem, how do I best approach styling them? Say my inline tag has the schema content label attrTag* (markedText | inlineTag)*. What if I want something after the label, but only for inline tags. I do not see how I can achieve this with the array notation, so I tried creating a DOM node instead, but that seems the wrong approach when you want something that is editable or has children. I also looked at node views, but they too seem made for just inline leaf nodes.

I love to be proved wrong, but it seems I am out of luck and my use case simply is not supported. If this is the case, does anyone have a suggestion of potential alternatives that I could use?

1 Like

The current story for non-leaf inline nodes is that they are supported, but you have to define a custom node view for them, and you are responsible for creating a piece of DOM that will act appropriately when edited. This approach sounds like the most appropriate one for your use case – define your tags as nodes in your schema, and write a node view that renders them, either embedding another ProseMirror instance for the content, or allowing their child nodes to be rendered by the outer ProseMirror, you’ll have to see what works best.

A node view with a ProseMirror instance embedded within, I understand, that basically is what you did in the footnote example, but allowing a node view to render their children by the outer ProseMirror instance, I do not understand. Is there an example of this as well? From what I understand a node view is an escape from normal rendering, so it is more flexible, but you are responsible for updating ProseMirror with the changes made inside the node view (e.g. the CodeMirror example). So I am unsure how to approach rendering the nodes by the outer ProseMirror instance in the case of a node view.

I thought embedding an instance would be too expensive, but thinking about it some more, I now see how I would only ever need one embedded instance. I could just remove the embedded instance once I am done editing a fragment. I just have to make sure that the embedded instance correctly communicates every change back to the outer instance. That approach would even work for my CodeMirror version. Nevertheless I would love to know what you meant with letting the outer instance render the child nodes.

PS: Just wanted to let you know I respect and love your work! To be honest I first wanted to use Ace instead of CodeMirror and SlateJS instead of ProseMirror, but in both cases I ended up with the latter because your projects are just more pragmatic and have APIs that do not hide stuff, are feature rich, and which allow you to cater it to almost any use case. And maybe even most importantly have an author that is really committed!

If you provide a contentDOM property on your node view, content rendering and updating will be handled by the library.

ProseMirror instances are relatively lightweight (compared to things like CodeMirror), but yeah, depending on how many of these you have, you might want to optimize this.

That’s really great to hear!

I seemed to have misunderstood that property, but after rereading the description, I understand it to mean that it is like being able to define what the 0 is in array notation, i.e. what DOM node of the DOM nodes you use in the node view will hold the DOM of the children.

It still feels somewhat non-intuitive to me, to only have one location that can hold children, because if some children are very coupled to their parent (semantically and in the way they are rendered) and you could want to add some logic that involves both the parent node and such a child node, you would want to place such logic in the parent node, but in the current situation the parent and child are worlds on their own, so you have to somehow reintroduce this relation between them with workarounds. In my example the label and attrTag* are tightly coupled to the concept of a tag, only the tag contents (markedText | inlineTag)* nicely fit with the concept of contentDOM. I hope you understand what I am getting at. Anyway I can think of a few ways to work around this if this really becomes an issue in my use case.

It is clear to me know that there is nothing fundamental keeping me from implementing what I hope to implement with ProseMirror, it will just require some experimentation to find out what approach works out best. Thank you!