Schema - nested nodes

Hi, guys! :alien:

I have such kind of schema types:

  1. Paragraph (text input block)
export default {
  group: 'block',
  content: 'inline*',

  parseDOM: [  
    { tag: 'p' },  
  ],

  toDOM() {
    return  [
      'p',
      {},
      0,
    ];
  },
}
  1. Iframe
export default {
  group: 'block',
  content: 'block',
  attrs: {
    src: {},
  },

  parseDOM: [
    { tag: 'iframe' },
  ],

  toDOM(node) {
    const { src } = node.attrs;

    return  [
      'iframe',
      { src },
      0,
    ];
  },
};

I try to create a new schema type by using these types. What I have:

export default {
  group: 'block',
  content: 'iframe paragraph',
  attrs: {
    src: {},
  },
  parseDOM: [
    { tag: 'div' },
  ],

  toDOM(node) {
    const { src } = node.attrs; 

    return  [
      'div',
      [
        'iframe',
        { src },
      ],
      [
        'paragraph',
      ],
    ];
  },
};

And this is my result:
image
The iframe is inserted correctly. But instead of the paragraph (text input block) I get the “paragraph” tag What do I do incorrectly?
I’d be glad to get your help! Thanks!

The toDOM is to be the dom element for that node - not the node and its descendants - and I do not think that prosemirror will expect the name of prosemirror nodes from the schema which is why you see a “paragraph” element in the result. Instead if your node is made up of other prosemirror nodes then you would leave a placeholder for the content. e.g. return [ "div",0 ]

1 Like

Thanks for reply! I understood what you mean but if to return ['div', 0] in the result type, I’ll get an empty div x)

How are you creating the node? I mean if you are just defining the document I believe its expected that you have a valid document and having that node without the required children (iframe & paragraph) wouldn’t be valid. If you are creating the node with the create off the nodeType that won’t create any children as the documentation describes. The createAndFill would but then you have a descendant (iframe) that has a required attribute (src) so I don’t think it can automatically create that for you.

Afaik prosemirror doesn’t have a notion of a composite node where a node handles the creation of its descendant nodes as you seem to have here so with the original structure you would probably instead remove the src from the new container node and then its expected that when you create that node that you provide the required children (i.e. iframe). Now if you didn’t really need to contain the iframe node and could just contain an iframe htmlelement as part of its dom then perhaps you might augment your original container to be something like:

{
      group: 'block',
      content: 'paragraph',
      attrs: {
        src: {},
      },
      parseDOM: [
        { tag: 'div' },
      ],
 
      toDOM(node) {
        const { src } = node.attrs; 

        return  [
          'div',
          ['iframe',{ src }], 0
        ];
      }
    }

So this node is a container for a paragraph child node and the 0 in the toDOM indicates where the child would be placed. Though I didn’t look closely at the parseDOM and imagine more would be needed there to consume the div and iframe and only match that node if the dom node being parsed had both.

BTW if you still have issues then perhaps create a glitch that demonstrates the issue so it’s understood exactly what is going on.

I’m creating the node by using the createAndFill.
And I created the glitch for an example. I’d be happy if you could check it. I left a comment in the index.js file on 58 line :sweat_smile:

DOMOutputSpec interface also allows toDOM to return an actual DOM Node, and even better - and probably easier to understand, a {dom, contentDOM} object.

Of course in this case dom node should be an ancestor of contentDOM.

I overlooked the comment in the docs mentioning that when you use 0 to indicate where the children will be placed that must be the only child of it’s parent node. So in the example you have that could be something like:

        toDOM(node) {
          const { src } = node.attrs;

          return [
            'div',
            [
              'img',
              {
                src,
                style: 'height: 100px; width: auto',
              },
            ],
            [
              'div',
              0
            ]
          ];
        },

image

@andrews @kapouer Thank you guys! You really helped me

But I found the strange behaviour. If content: paragraph, we can freely navigate through the editor with the keyboard arrows but If content: inline* moving works only in one direction :face_with_raised_eyebrow: Wtf? xd

1 Like

Usually when I find a behavior not working in prosemirror the first thing I’ll try is the same html in a vanilla contentEditable div (e.g. in codepen, jsfiddle, etc). In this case you would find that the caret is trying to go before/after the img within the dom for your node but since that isn’t a valid content position within your node prosemirror is probably shifting it to a close valid position. So in this case just wrap the img with a non-contenteditable div. e.g.

          return [
            'div',
            [
              'div',
              { contentEditable: false },
              [
                'img',
                {
                  src,
                  style: 'height: 100px; width: auto',
                },
              ]
            ],
            [
              'p',
              0,
            ],
          ];

1 Like

It’s crazy xd This div was without the contentEditable attribute by default :ghost:

__
UPD: I remembered how it works. contentEditable: false cancels the parent contentEditable: true

Now there is only one focusable element in our example - the text input element. Maybe do you know how to also make a focusable state for the img (or another non a text element)? I need to show some tools by focusing on an image

I’m really grateful for your help

Sorry. Maybe create a new post describing your issue/question.

1 Like