Translating nodes and transaction steps between editors with different schemas

We’re working on a way of generically modelling form elements in a way which preserves first-class interactions with text fields. The approach we’re taking is similar to the footnote example, in that we’re instantiating entirely separate editors for child nodes that represent text content, and passing node updates across the editor boundary via a NodeView.

We’ve gotten by having a generic schema in the child editor, but this has been working, I think, by coincidence, and we’ve a few bugs that have been explained by this problem. We’ve fixed it by having a properly-formed schema for parent and child, and translating incoming nodes (for updating children) and outgoing transactions (for updating the parent) into the recipients schema.

The way we’re doing this is odd, and I suspect, a bit slow – we’re serialising the node or transaction step in question, and re-parsing it with the appropriate schema. So when passing a node to a child, we serialise it and parse it with the child schema, at which point everything works correctly.

Might there be a better way of doing this translation, that doesn’t involve an intermediate representation (e.g. JSON, DOM nodes?)

Not really, no. Though you should usually be able to use a single schema, I think. You could define special separate nodes to act as top (document) nodes for the separate editors, but share all the common parts of the schema by having them exist in a single schema.

Interesting, yes, it’d be ideal to use the same one. I think the errors were likely related to us using separate schemas in that identical node types (e.g. paragraphs, textNodes) would have different references; I’ll try deriving the inner schema from the outer schema, with a different topNode.

That doesn’t work — each schema will allocate its own node and mark types. You really need a single Schema object, with a special node in it (not used as a child anywhere) that you’ll use as the top node in your other editor.

How do you specify a top node in an editor, if not via a schema?

E.g. schema has topNode parentTop, parent uses schema, child uses same schema but somehow has topNode childTop?

You can put any node into a state’s doc init field, not just the one that’s designated as the top node in the schema (that’s mostly there for parsing whole documents with DOMParser).

Hm, this causes problems, but I think I’ve misunderstood your advice above.

Suppose we’ve a schema that looks like

doc(paragraph | parent) → parentchildparagraph

and we pass a node of type child to our child editor.

If we use the parent schema for the child editor, most editing operations just work. However, some (in particular inserting a hard_break via the bindings in exampleSetup) fail with odd errors like Cannot read property 'nodeSize' of undefined. Looking at the trace, it looks like the depth calculations in replaceRange are incorrect, which seems like a valid confusion – possibly the schema expects this node to exist at a different depth relative to the document than it occupies?

So perhaps you’re suggesting solving by adding a special node that permits a child node as its content, that exists at the top level of the document – and then passing that node (presumably we’d have to instantiate it manually) as the initial document – which we’d then need to update with our content, somehow – and then child content would be valid according to the parent schema? Sorry, there are some gymnastics here that I’m unsure about, but keen to understand!

E.g. in a schema that defines the nodes doc, parent, child, paragraph, customTopNode

parent content: docparentchildparagraph

child content: customTopNodechildparagraph

That might be a bug in the example-setup. If you have a minimal setup that demonstrates it I can take a look. Depths aren’t generally static per node, so commands generally shouldn’t be hard-coding them.

Ah, just cracked it, I think – there was a level of indirection amending the schema I hadn’t seen. Passing the parent schema directly into the child, and avoiding defining a schema in the child editor, works fine.

This is interesting, as it’s not how I expected this to work, so to clarify, this is my current understanding:

  • nodes can determine the validity of their own content
  • the editor could determine the validity of its content by checking to see whether its doc node corresponded to the topNode of the schema, and then checking the validity of that node
  • in practice, the editor either doesn’t do any checks for validity – or perhaps it does, but considers the node passed on init to be the topNode, and ignores the schema?

As ever, thanks very much for your help!

That’s just not a requirement. So there’s no question of validity there.

Oh! That’s my confusion, then – the schema represents a tree w/ a topNode at its root, and I assumed that in order for content to be valid from the editor state’s perspective, it had to fulfil the schema from the root downward. But this isn’t required, any subset of that tree would be valid.