tiptap: PasteRule does not work for nodes

Describe the bug When attempting to add a pasteRule for a Node, it does not work as the pasteRule helper function have a call to a Mark node (note how we call addToSet on the created node, which may not be a mark but a node):

nodes.push(child.cut(start, end).mark(type.create(attrs).addToSet(child.marks)));

Steps to Reproduce / Codesandbox Example

  1. Add a paste rule to any node such as a media embed
pasteRules({type}) {
  return [pasteRule(/SOME_YOUTUBE_REGEX/g, type, (url) => ({src: url}))]
}
  1. try to paste a heading: https://www.youtube.com/watch?v=H08tGjXNHO4
  2. See error in console: Uncaught TypeError: type.create(...).addToSet is not a function

Expected behavior a new MediaEmbed node should be created with the given attrs

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 6
  • Comments: 22 (19 by maintainers)

Most upvoted comments

I’m also facing issue w/ implementing a nodePasteRule and wondered if anyone has gotten something similar to @kfirba solution working w/ TipTap 2 and the addPasteRules method? So far I’ve tried altering the code shown to accept a config object rather than just arguments to be closer to the TipTap nodeInputRule & markPasteRule, but I’m getting Uncaught TypeError: find is not a function. Thanks for making TipTap and any further suggestions.

Uncaught TypeError: find is not a function
    at pasteRuleMatcherHandler (tiptap-core.esm.js:2452:1)
    at tiptap-core.esm.js:2485:1
    at Fragment.nodesBetween (index.es.js:80:1)
    at Node.nodesBetween (index.es.js:1071:1)
    at run (tiptap-core.esm.js:2478:1)
    at Plugin.appendTransaction (tiptap-core.esm.js:2571:1)
    at EditorState.applyTransaction (index.es.js:842:1)
    at EditorState.apply (index.es.js:807:1)
    at Editor.dispatchTransaction (tiptap-core.esm.js:3448:1)
    at EditorView.dispatch (index.es.js:5247:29)

@philippkuehn I’ve created a simple helper function to solve that:

export function nodePasteRule(regexp, type, getAttrs) {
  const handler = fragment => {
    const nodes = [];

    fragment.forEach(child => {
      if (child.isText) {
        const {text} = child;
        let pos = 0;
        let match;

        // eslint-disable-next-line
        while ((match = regexp.exec(text)) !== null) {
          if (match[0]) {
            const start = match.index;
            const end = start + match[0].length;
            const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;

            // adding text before markdown to nodes
            if (start > 0) {
              nodes.push(child.cut(pos, start));
            }

            // create the node
            nodes.push(type.create(attrs));

            pos = end;
          }
        }

        // adding rest of text to nodes
        if (pos < text.length) {
          nodes.push(child.cut(pos));
        }
      } else {
        nodes.push(child.copy(handler(child.content)));
      }
    });

    return Fragment.fromArray(nodes);
  };

  return new Plugin({
    props: {
      transformPasted: slice => new Slice(handler(slice.content), slice.openStart, slice.openEnd),
    },
  });
}

Basically it is a very similar function as markPasteRule but it just creates the node with the given attribute instead of trying to add a mark ~and does not contain a while loop. Even in the markPasteRule I’m not sure why there is a while loop when match is only every assigned once?~ It seems like the while loop is for the regexp.exec call due to it attempting to match from the lastIndex: https://stackoverflow.com/questions/1520800/why-does-a-regexp-with-global-flag-give-wrong-results

I guess that if you want you could extract the common logic between those 2 methods and just pass a handler to create the node/mark.

What do you think?

@hanspagel any direction on where to look if one wants to implement embeds on paste today? thanks!

@Alecyrus I think you get an endless loop as you didn’t provide the g flag for your regex. Try replacing your regex to this:

new RegExp(`^(#{1,3})(.*)`, 'g')

Can you show some input => output example?

Also, if you could provide a reproduction it would be best.