How to create a nodetype consist of an image and text with inline editing experience?

Hi, I just learned about Prosemirror after I came across this article from Times on medium https://open.nytimes.com/building-a-text-editor-for-a-digital-first-newsroom-f1cb8367fc21

I’d like to create similar features in the article and I’m stuck with creating the lede_media nodetype which consist of an image and a text description with inline editing experience. Here is the approach i took, define a nodetype with content of an image and a text node.

    imageWrapper: {
        group: 'block',
        content: 'image p',
        marks: '',
        draggable: true,
        attrs: {
          displayMode: {default: 'medium'}
        },
        toDOM: (node) => {
          return ['div', node.attrs]
        }
      },

when the menu is clicked, create the imagewrapper node with a image and a paragraph and insert to the document.

  const image = schema.nodes.image.create({src: url})
  const title = schema.nodes.p.createAndFill()
  const imageWrapper = schema.nodes.imageWrapper.create(null, [image, title])
  const newTransaction = editor.state.tr
  newTransaction.replaceWith(pos, pos, imageWrapper)

I’m getting following error message, can’t someone explain to me why the schema is invalid?

Uncaught SyntaxError: Only non-generatable nodes (image) in a required position (in content expression 'image p')
    at TokenStream.err (index.js?b0cf:1646)
    at checkForDeadEnds (index.js?b0cf:1862)
    at Function.parse (index.js?b0cf:1475)
    at new Schema (index.js?b0cf:2354)

Maybe something like Embedded code editor is way to go but managing all the state changes with customized code seems overkill to me for a simple feature like this. I would appreciate any pointer on how to implement this feature. thanks in advance

ProseMirror needs to be able to ‘fix’ documents to conform to the schema, for example if editing somehow yields an image wrapper node with only a paragraph inside of it, it must generate an image to satisfy the schema constraint. But because images contain attributes without a default value, it can’t.

One solution would be to model the node as a captioned image instead of a wrapper, with its own image src (an possibly alt etc) attribute, and inline* content, rendering as something like ["div", ["img", {....}], ["p", 0]]. That way, the node can’t get in a state where the image is missing, since it’s not part of its editable content.

Alternatively, use 'image? p' as content expression, so that the image isn’t required and doesn’t ever need to be generated.

thanks for explaining why the schema is invalid. Using image? p? did the trick for me.

now i have to figure out how to handle following cases

  1. disable the marks on p element because it’s a caption.
  2. if image is deleted, change the p element to top level
  3. if both image and p element are deleted, remove the wrapper image element entirely
  4. place cursor focus on the p element

I think most of these problems will solve themselves when you use the other approach I described (put the image data into the outer node’s attrs)—you can control the marks on the node’s content with the marks field in the node spec, and the image can’t be deleted without also deleting its caption.

Setting focus is a matter of finding the textblock’s start, creating a TextSelection there, and dispatching a transaction that sets the selection to it.

1 Like