diff --git a/__tests__/clipboard.spec.js b/__tests__/clipboard.spec.js new file mode 100644 index 0000000..496d3c2 --- /dev/null +++ b/__tests__/clipboard.spec.js @@ -0,0 +1,85 @@ +import { Editor } from "@tiptap/core"; +import StarterKit from "@tiptap/starter-kit"; +import { Markdown } from "../src"; +import { clipboardEvent } from "./utils/dom"; + + +describe('clipboard', () => { + describe('paste', () => { + test('transform', () => { + const editor = new Editor({ + extensions: [ + StarterKit, + Markdown.configure({ + transformPastedText: true, + }), + ], + }); + + const event = clipboardEvent('paste'); + event.clipboardData.setData('text/plain', `# My title`); + + editor.view.dom.dispatchEvent(event); + + expect(editor.getHTML()).toContain('

My title

') + }); + + test('does not transform', () => { + const editor = new Editor({ + extensions: [ + StarterKit, + Markdown.configure({ + transformPastedText: false, + }), + ], + }); + + const event = clipboardEvent('paste'); + event.clipboardData.setData('text/plain', `# My title`); + + editor.view.dom.dispatchEvent(event); + + expect(editor.getHTML()).not.toContain('

My title

') + }); + }); + + describe('copy', () => { + test('transform', () => { + const editor = new Editor({ + content: '# My title', + extensions: [ + StarterKit, + Markdown.configure({ + transformCopiedText: true, + }), + ], + }); + + const event = clipboardEvent('copy'); + + editor.commands.selectAll(); + editor.view.dom.dispatchEvent(event); + + expect(event.clipboardData.getData('text/plain')).toBe('# My title'); + }); + + test('does not transform', () => { + const editor = new Editor({ + content: '# My title', + extensions: [ + StarterKit, + Markdown.configure({ + transformCopiedText: false, + }), + ], + }); + + const event = clipboardEvent('copy'); + + editor.commands.selectAll(); + editor.view.dom.dispatchEvent(event); + + expect(event.clipboardData.getData('text/plain')).toBe('My title'); + }); + }); +}) diff --git a/__tests__/utils/dom.js b/__tests__/utils/dom.js new file mode 100644 index 0000000..a93f94d --- /dev/null +++ b/__tests__/utils/dom.js @@ -0,0 +1,14 @@ + + +export function clipboardEvent(name) { + const clipboardData = { + data: {}, + getData(format) { return this.data[format] }, + setData(format, content) { return this.data[format] = content }, + clearData(format) { delete this.data[format] }, + }; + const event = new Event(name); + event.clipboardData = clipboardData; + + return event; +} diff --git a/__tests__/utils/setup-dom.js b/__tests__/utils/setup-dom.js new file mode 100644 index 0000000..7128b3f --- /dev/null +++ b/__tests__/utils/setup-dom.js @@ -0,0 +1,7 @@ + +document.createRange = () => { + return Object.assign(new Range(), { + getClientRects: () => [], + getBoundingClientRect: () => ({}), + }); +}; diff --git a/example/src/components/Editor.vue b/example/src/components/Editor.vue index c19c169..ee4f639 100644 --- a/example/src/components/Editor.vue +++ b/example/src/components/Editor.vue @@ -49,7 +49,7 @@ }, methods: { updateMarkdownOutput() { - console.log(this.editor.storage.markdown); + // console.log(this.editor.storage.markdown); this.markdown = this.editor.storage.markdown.getMarkdown(); }, handleInput() { @@ -60,6 +60,8 @@ this.editor = new Editor({ extensions: [ Markdown.configure({ + transformPastedText: true, + transformCopiedText: true, }), StarterKit.configure({ codeBlock: false, diff --git a/example/src/content.md b/example/src/content.md index 32ddef7..c0eb4a3 100644 --- a/example/src/content.md +++ b/example/src/content.md @@ -1,9 +1,14 @@ ---- +## Paragraph + +Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Duis leo. Fusce neque. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. + +Nulla consequat massa quis enim. Phasellus blandit leo ut odio. Phasellus dolor. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. + +## Task lists - [ ] fzfzefz - [x] fzfezfezf ---- # h1 Heading 8-) ## h2 Heading @@ -21,8 +26,6 @@ ___ *** -Aenean ut eros et nisl sagittis vestibulum. Donec vitae orci sed dolor rutrum auctor. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. - ## Emphasis **This is bold text** diff --git a/jest.config.js b/jest.config.js index 2356dfc..b3fdbac 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,5 @@ +const path = require('path'); + /* * For a detailed explanation regarding each configuration property, visit: * https://jestjs.io/docs/configuration @@ -125,7 +127,7 @@ module.exports = { // runner: "jest-runner", // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], + setupFiles: [path.resolve(__dirname, '__tests__/utils/setup-dom.js')], // A list of paths to modules that run some code to configure or set up the testing framework before each test // setupFilesAfterEnv: [], diff --git a/src/Markdown.js b/src/Markdown.js index fb79598..057873b 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -1,8 +1,8 @@ -import { Extension } from '@tiptap/core'; +import { Extension, extensions } from '@tiptap/core'; import { MarkdownTightLists } from "./extensions/tiptap/tight-lists"; import { MarkdownSerializer } from "./serialize/MarkdownSerializer"; import { MarkdownParser } from "./parse/MarkdownParser"; -import { extensions } from '@tiptap/core'; +import { MarkdownClipboard } from "./extensions/tiptap/clipboard"; export const Markdown = Extension.create({ name: 'markdown', @@ -15,6 +15,8 @@ export const Markdown = Extension.create({ bulletListMarker: '-', linkify: false, breaks: false, + transformPastedText: false, + transformCopiedText: false, } }, addCommands() { @@ -63,6 +65,10 @@ export const Markdown = Extension.create({ tight: this.options.tightLists, tightClass: this.options.tightListClass, }), + MarkdownClipboard.configure({ + transformPastedText: this.options.transformPastedText, + transformCopiedText: this.options.transformCopiedText, + }), ] - } + }, }); diff --git a/src/extensions/tiptap/clipboard.js b/src/extensions/tiptap/clipboard.js new file mode 100644 index 0000000..22f9cbc --- /dev/null +++ b/src/extensions/tiptap/clipboard.js @@ -0,0 +1,40 @@ +import { Extension } from "@tiptap/core"; +import { Plugin, PluginKey } from '@tiptap/pm/state'; +import { DOMParser } from '@tiptap/pm/model'; +import { elementFromString } from "../../util/dom"; + +export const MarkdownClipboard = Extension.create({ + name: 'markdownClipboard', + addOptions() { + return { + transformPastedText: false, + transformCopiedText: false, + } + }, + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey('markdownClipboard'), + props: { + clipboardTextParser: (text, context, plainText) => { + if(plainText || !this.options.transformPastedText) { + return null; // pasting with shift key prevents formatting + } + const parsed = this.editor.storage.markdown.parser.parse(text, { inline: true }); + return DOMParser.fromSchema(this.editor.schema) + .parseSlice(elementFromString(parsed), { preserveWhitespace: true }); + }, + /** + * @param {import('prosemirror-model').Slice} slice + */ + clipboardTextSerializer: (slice) => { + if(!this.options.transformCopiedText) { + return null; + } + return this.editor.storage.markdown.serializer.serialize(slice.content); + }, + }, + }) + ] + } +})