Figure and Editable Caption


#1

This question has been asked a few times before but I haven’t seen a definitive answer.

What’s the best way to do an editable caption inside an element (like an image or figure) in terms of the schema and dom structure?


#2

I experimented with that kind of thing and here is how far I got:

if you define the figure in the schema as block, all goes (mostly) as expected, at the dom structure just put image and figcaption inside a figure dom element and you will be able to edit the caption. What did not work out of the box for me, was the ability to select the figure node, when clicking anywhere inside the figure, but outside the figcaption (so I did some ugly hack processing the raw event to work around).

if you define the figure as inline, then all kind of unexpected things will happen, @marijn mentioned some months ago that he hasn’t yet finalized / smoothed out the code related to inline non-leaf nodes


#3

Thanks, that helps a lot! Do you think you could post how that related part of your schema looks like?


#4

Sure, here are the relevant parts:

figure: {
    content: "text<_>*",
    group: "block",
    attrs: {
      // ....
      // non-relevant
      // ....
    },
    draggable: true,
    selectable: true,
    parseDOM: [{tag: "figure", getAttrs(dom) {
      // ....
      // non-relevant
      // ....
    }}],
    toDOM(node) {
      // ....
      // create myImageAsReactContainer and others
      // ....
      return ['figure', {
        class: myClass,
        style: myStyle,
        // more attributes
      }, myImageAsReactContainer, ['figcaption', {}, 0]]
    }
  },

What issues did you run into ?


#5

Here’s another, possibly more complete example doing something similar.

const imageNodeDesc = {
  figure: {
    attrs: {src: {}},
    content: "inline<_>*",
    parseDOM: [{
      tag: "figure",
      contentElement: "figcaption", // Helps the parser figure out where the child nodes are
      getAttrs(dom) {
        let img = dom.querySelector("img")
        return {src: img && img.parentNode == dom ? img.src : "default_image.jpg"}
      }
    }],
    toDOM(node) {
      return ["figure", ["img", {src: node.attrs.src}], ["figcaption", 0]]
    },
    draggable: true,
    group: "block",
  }
}

To cause the node to be selected on click, even though it isn’t a leaf node, you could use the handleClickOn prop, passing something like…

function handleFigureClick(view, pos, node, posBefore) {
  if (node.type.name != "figure" && pos != posBefore) return false
  view.props.onAction(new NodeSelection(view.state.doc.resolve(pos).action())
  return true
}

#6

what does this content expression mean?


#7

Nothing, in current versions of the library. It used to be possible to specify allowed marks per child node, which is what the angle brackets did, but that never worked right and marks are now specified per parent node.