Paste Transformation Questions

I’ve been playing with transformPastedHTML a bit, and I’ve run into a couple of curious points, which I thought I’d raise here.

Let’s say I wanted to create a plugin that ensured that the word “special” was always presented with an appropriate level of excitement. Such a plugin would be trivial to create.

// v1
const SpecialPastePlugin = new Plugin({
  props: {
    transformPastedHTML(html) {
      return html.replace(/special/i, 'SPECIAL!');
    },
  },
})

Let’s also say that I wanted to create a separate plugin that emphasized capital letters in pasted content. This is also trivial to create.

//v1
const CapitalEmphasisPastePlugin = new Plugin({
  props: {
    transformPastedHTML(html) {
      return html.replace(/[A-Z]+?/g, '<em>$&</em>');
    },
  },
})

Now, let’s suppose I wanted to use them both.

window.view = new EditorView(document.querySelector("#editor"), {
  state: EditorState.create({
    doc: DOMParser.fromSchema(mySchema).parse(document.querySelector("#content")),
    plugins: exampleSetup({schema: mySchema})
      .concat(SpecialPastePlugin)
      .concat(CapitalEmphasisPastePlugin)
  })
})

This fails, because transformPastedHTML is filtered through view.someProp, which only passes data through plugins until one prop returns “truthy”. In this case, the SpecialPastePlugin is the only one that will ever execute.

Once again, easily solved.

// v2
const SpecialPastePlugin = new Plugin({
  props: {
    transformPastedHTML(html) {
      if (!html.match(/special/i)) return null;
      return html.replace(/special/i, 'SPECIAL!');
    },
  },
})

const CapitalEmphasisPastePlugin = new Plugin({
  props: {
    transformPastedHTML(html) {
      if (!html.match(/[A-Z]+?/g)) return null;
      return html.replace(/[A-Z]+?/g, '<em>$&</em>');
    },
  },
})

All better, right? Not quite – now the CapitalEmphasisPastePlugin gets called, but it’s passed null instead of HTML! This seems to imply that there is currently no way to allow two plugins to provide transformPastedHTML (or similar) props.

What’s more, because of the nature of view.someProp, sequential transformations are also fundamentally impossible. (Specifically, there is no way to achieve a result of “SPECIAL!” by combining these two plugins.)

As a tertiary complication, it’s possible to pass transformPastedHTML directly to the EditorView at construction time, but the provided function will preclude any functions provided by plugins.

As a whole, these observations seem to make creating robust, composable plugins difficult to impossible. I look forward to learning I’m wrong. :slight_smile:

That’s unintentional—notice how the use of transformPastedText (correctly) has braces around the arrow function’s body, whereas transformPastedHTML is missing those and thus accidentally returns a value for the first instance of the prop.

I’m currently working on another branch, but I’ll create a patch for this later. (Or you could submit a pull request.)

This patch fixes this.

Excellent – I’m so glad to hear that this was both unintentional and easy to fix! Thanks!