Full documents inside node attributes?

First off – allow me to gush about how friggen awesome ProseMirror is briefly. It makes me feel like I can actually build incredibly rich experiences without going insane and is well thought through at every turn, so, thanks Marijn!

I am building a kind of rich content editor where a user is able to interpolate dynamic data into their content, similar to a Handlebars template. Users are able to write prose like the basic schema, but then add in “tokens” that stay abstract and unbound at authoring time, and then are rendered using data from a different source later. I have an inline token node that I’ve wired up to represent the binding, and I plan to create an inline expression node that lets authors operate on tokens in a code-like instead of prose-like fashion.

This is working fine for me so far, but, I realized I will likely need to let users use tokens to populate other node’s or mark’s attrs, instead of them just being child nodes. For example, a user should be able to create a link who’s anchor text is fixed but has an href pointing to data from a token, or a user should be able to create a heading who’s level is the result of an expression on a token.

The way I am inclined to model this would be to make a link’s href attribute hold a whole other document containing nodes to be evaluated at render time. To edit a link’s href the editor would pop open a sub-editor, similar to the footnotes example, that edits a separate document that is then toJSON’d and stored in the link’s markup. It’s different than the footnotes example however though because the sub-document isn’t stored just as child nodes of a node in the outer document, but in the attrs of a node or mark. That’d be necessary because the sub-document needs a name so it can be rendered out into the right place (‘href’ in the example), and so that there could be multiple sub documents for nodes that have several programmable attributes.

This plan makes me a little nervous however. Philosophically it seems to be stretching the definition of an attribute a bit far as most attrs seem to be simple, scalar values. Practically it would mean a lot of jerry-rigging of serialization/deserialization, decidedly harder schema validation, and I imagine some serious headaches around getting the history stack to work right between the two living documents, and that’s just what I have thought of so far.

Is there another way to model this that you fine folks could think of that might be better?

Technically, if it’s JSON, you can put it into an attribute.

But one advantage of having more complicated structure represented as child nodes rather than attributes is that it’ll allow you to update it piecewise with transaction steps, rather than replacing it wholesale (attributes are atomic values as far as document updates are concerned), which might make it easier to integrate such changes into the undo history or use collaborative editing.

You could use named child nodes (i.e. treat the content of a given node as a series of attributes, tagged by their node type), but yeah, marks can’t have child nodes, so that’d remain difficult.

I agree, it’d be ideal to represent stuff as child nodes some how. Perhaps I could make myself a node type that represents a rich attribute and doesn’t have a DOM representation and build some commands to update it instead of attrs.

That leads me to ask what might go wrong if I model links (and other things usually represented as marks) as inline nodes? I think I read somewhere that marks exist because they make the code for working with these spans across nodes much easier, but is there stuff that you can’t represent using the tree of nodes that you can with marks? Will I get backed into a corner where my links will be less useful?

The main problem with inline nodes with content (which I guess are how you were intending to model these links) is that browsers behave pretty terrible and unpredictable when the cursor is on their start/end boundary, and you’ll probably need a ton of custom code to make them work acceptably.