diff --git a/src/extensions/Markdown.js b/src/extensions/Markdown.js index ba1b097003d..6070a6465fa 100644 --- a/src/extensions/Markdown.js +++ b/src/extensions/Markdown.js @@ -108,7 +108,17 @@ const Markdown = Extension.create({ return parser.parseSlice(dom, { preserveWhitespace: true, context: $context }) }, clipboardTextSerializer: (slice) => { - return createMarkdownSerializer(this.editor.schema).serialize(slice.content) + const traverseNodes = (slice) => { + if (slice.content.childCount > 1) { + return createMarkdownSerializer(this.editor.schema).serialize(slice.content) + } else if (slice.isLeaf) { + return slice.textContent + } else { + return traverseNodes(slice.content.firstChild) + } + } + + return traverseNodes(slice) }, transformPastedHTML, }, diff --git a/src/tests/extensions/Markdown.spec.js b/src/tests/extensions/Markdown.spec.js index bde2e828245..a1bcc081b6d 100644 --- a/src/tests/extensions/Markdown.spec.js +++ b/src/tests/extensions/Markdown.spec.js @@ -1,5 +1,7 @@ import { Markdown } from './../../extensions/index.js' import { createMarkdownSerializer } from './../../extensions/Markdown.js' +import CodeBlock from '@tiptap/extension-code-block' +import Blockquote from '@tiptap/extension-blockquote' import Image from './../../nodes/Image.js' import ImageInline from './../../nodes/ImageInline.js' import TaskList from './../../nodes/TaskList.js' @@ -7,6 +9,7 @@ import TaskItem from './../../nodes/TaskItem.js' import Underline from './../../marks/Underline.js' import TiptapImage from '@tiptap/extension-image' import { getExtensionField } from '@tiptap/core' +import { __serializeForClipboard as serializeForClipboard } from '@tiptap/pm/view' import { createCustomEditor } from '../helpers.js' describe('Markdown extension unit', () => { @@ -77,4 +80,37 @@ describe('Markdown extension integrated in the editor', () => { expect(serializer.serialize(editor.state.doc)).toBe('inline image ![Hello](test) inside text') }) + it('copies task lists to plaintext like markdown', () => { + const editor = createCustomEditor({ + content: '<p><ul class="contains-task-list"><li><input type="checkbox">Hello</li></ul></p>', + extensions: [Markdown, TaskList, TaskItem], + }) + editor.commands.selectAll() + const slice = editor.state.selection.content() + const { text } = serializeForClipboard(editor.view, slice) + expect(text).toBe('\n- [ ] Hello') + }) + + it('copies code block content to plaintext according to their spec', () => { + const editor = createCustomEditor({ + content: '<pre><code>Hello</code></pre>', + extensions: [Markdown, CodeBlock], + }) + editor.commands.selectAll() + const slice = editor.state.selection.content() + const { text } = serializeForClipboard(editor.view, slice) + expect(text).toBe('Hello') + }) + + it('copies nested task list nodes to markdown like syntax', () => { + const editor = createCustomEditor({ + content: '<blockquote><p><ul class="contains-task-list"><li><input type="checkbox">Hello</li></ul></blockquote>', + extensions: [Markdown, Blockquote, TaskList, TaskItem], + }) + editor.commands.selectAll() + const slice = editor.state.selection.content() + const { text } = serializeForClipboard(editor.view, slice) + expect(text).toBe('\n- [ ] Hello') + }) + })