Tiptap editor, how to paste multiple empty lines?

Viewed 994

Using the basic version fo Tiptap editor:

<template>
  <div v-if="editor">
    <editor-content :editor="editor" />
  </div>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'

export default {
  components: {
    EditorContent,
  },

  data() {
    return {
      editor: null,
    }
  },

  mounted() {
    this.editor = new Editor({
      extensions: [
        Document,
        Paragraph,
        Text,
      ],
      content: `
        <p>The Paragraph extension is not required, but it’s very likely you want to use it. It’s needed to write paragraphs of text. </p>
      `,
    })
  },

  beforeUnmount() {
    this.editor.destroy()
  },
}
</script>

(which is available to test at https://tiptap.dev/api/nodes/paragraph)

I noticed that when pasting a text with multiple lines, such as :

Hello


This is a test with two break lines at the above and three after this:



Thanks!

The resulting is :

enter image description here

<p>Hello</p>
<p>This is a test with two break lines at the above and three after this:</p>
<p>Thanks!</p>

In this case, all the "multiple" break lines has been removed!

Is there a way to keep them, which means that once the paste is executed, the content should be:

<p>Hello</p>
<p></p>
<p></p>
<p>This is a test with two break lines at the above and three after this:</p>
<p></p>
<p></p>
<p></p>
<p>Thanks!</p>

(I'm ok with having <p><br /></p> instead of <p></p> by enabling the Hardbreak extension too, but in either case, it doesn't work).

2 Answers

I think you can use Paste Rules extension. You can look for empty lines with regex and replace them with <br>. Here is the example from TipTap docs:

// Check pasted content for the ~single tilde~ markdown syntax
import Strike from '@tiptap/extension-strike'
import { markPasteRule } from '@tiptap/core'

// Default:
// const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/g

// New:
const pasteRegex = /(?:^|\s)((?:~)((?:[^~]+))(?:~))/g

const CustomStrike = Strike.extend({
  addPasteRules() {
    return [
      markPasteRule({
        find: pasteRegex,
        type: this.type,
      }),
    ]
  },
})

Alternatively, you can try disabling the paste rules altogether to see if that will solve your issue.

new Editor({
  content: `<p>Example Text</p>`,
  extensions: [
    StarterKit,
  ],
  enablePasteRules: false,
})

I found the solution by opening a ticket on Tiptap.

It turns out that it is the fault of Prosemirror for not handling the multiple break lines, but someone on a related ticket from Tiptap made a suggestion that works great:

import {Slice, Fragment, Node} from 'prosemirror-model'

function clipboardTextParser(text, context, plain)
{
    const blocks = text.replace().split(/(?:\r\n?|\n)/);
    const nodes = [];

    blocks.forEach(line => {
        let nodeJson = {type: "paragraph"};
        if (line.length > 0) {
            nodeJson.content = [{type: "text", text: line}]
        }
        let node = Node.fromJSON(context.doc.type.schema, nodeJson);
        nodes.push(node);
    });

    const fragment = Fragment.fromArray(nodes);
    return Slice.maxOpen(fragment);
}

new Editor({
    editorProps: {
        clipboardTextParser: clipboardTextParser,
    },
})
Related