Skip to content

Commit

Permalink
Merge pull request #5657 from nextcloud/backport/5654/stable28
Browse files Browse the repository at this point in the history
[stable28] fix: improve node and mark copy-paste behavior
  • Loading branch information
max-nextcloud authored Apr 11, 2024
2 parents b32c6a3 + 5d573d8 commit 4a9a654
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 20 deletions.
49 changes: 39 additions & 10 deletions src/extensions/Markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const Markdown = Extension.create({
clipboardTextSerializer: (slice) => {
const traverseNodes = (slice) => {
if (slice.content.childCount > 1) {
return createMarkdownSerializer(this.editor.schema).serialize(slice.content)
return clipboardSerializer(this.editor.schema).serialize(slice.content)
} else if (slice.isLeaf) {
return slice.textContent
} else {
Expand All @@ -128,28 +128,57 @@ const Markdown = Extension.create({
})

const createMarkdownSerializer = ({ nodes, marks }) => {
const defaultNodes = convertNames(defaultMarkdownSerializer.nodes)
const defaultMarks = convertNames(defaultMarkdownSerializer.marks)
return {
serializer: new MarkdownSerializer(
{ ...defaultNodes, ...extractToMarkdown(nodes) },
{ ...defaultMarks, ...extractToMarkdown(marks) },
extractNodesToMarkdown(nodes),
extractMarksToMarkdown(marks),
),
serialize(content, options) {
return this.serializer.serialize(content, { ...options, tightLists: true })
},
}
}

const clipboardSerializer = ({ nodes, marks }) => {
return {
serializer: new MarkdownSerializer(
extractNodesToMarkdown(nodes),
extractToPlaintext(marks),
),
serialize(content, options) {
return this.serializer.serialize(content, { ...options, tightLists: true })
},
}
}

const extractToPlaintext = (marks) => {
const blankMark = { open: '', close: '', mixable: true, expelEnclosingWhitespace: true }
const defaultMarks = convertNames(defaultMarkdownSerializer.marks)
const markEntries = Object.entries({ ...defaultMarks, ...marks })
.map(([name, _mark]) => [name, blankMark])

return Object.fromEntries(markEntries)
}

const extractToMarkdown = (nodesOrMarks) => {
return Object
const nodeOrMarkEntries = Object
.entries(nodesOrMarks)
.map(([name, nodeOrMark]) => [name, nodeOrMark.spec.toMarkdown])
.filter(([, toMarkdown]) => toMarkdown)
.reduce((items, [name, toMarkdown]) => ({
...items,
[name]: toMarkdown,
}), {})

return Object.fromEntries(nodeOrMarkEntries)
}

const extractNodesToMarkdown = (nodes) => {
const defaultNodes = convertNames(defaultMarkdownSerializer.nodes)
const nodesToMarkdown = extractToMarkdown(nodes)
return { ...defaultNodes, ...nodesToMarkdown }
}

const extractMarksToMarkdown = (marks) => {
const defaultMarks = convertNames(defaultMarkdownSerializer.marks)
const marksToMarkdown = extractToMarkdown(marks)
return { ...defaultMarks, ...marksToMarkdown }
}

const convertNames = (object) => {
Expand Down
58 changes: 48 additions & 10 deletions src/tests/extensions/Markdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Image from './../../nodes/Image.js'
import ImageInline from './../../nodes/ImageInline.js'
import TaskList from './../../nodes/TaskList.js'
import TaskItem from './../../nodes/TaskItem.js'
import Underline from './../../marks/Underline.js'
import { Italic, Strong, Underline, Link} from './../../marks/index.js'
import TiptapImage from '@tiptap/extension-image'
import { getExtensionField } from '@tiptap/core'
import { __serializeForClipboard as serializeForClipboard } from '@tiptap/pm/view'
Expand Down Expand Up @@ -85,9 +85,7 @@ describe('Markdown extension integrated in the editor', () => {
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)
const text = copyEditorContent(editor)
expect(text).toBe('\n- [ ] Hello')
})

Expand All @@ -96,9 +94,7 @@ describe('Markdown extension integrated in the editor', () => {
content: '<pre><code>Hello</code></pre>',
extensions: [Markdown, CodeBlock],
})
editor.commands.selectAll()
const slice = editor.state.selection.content()
const { text } = serializeForClipboard(editor.view, slice)
const text = copyEditorContent(editor)
expect(text).toBe('Hello')
})

Expand All @@ -107,10 +103,52 @@ describe('Markdown extension integrated in the editor', () => {
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)
const text = copyEditorContent(editor)
expect(text).toBe('\n- [ ] Hello')
})

it('copies address from blockquote to markdown', () => {
const editor = createCustomEditor({
content: '<blockquote><p>Hermannsreute 44A</p></blockquote>',
extensions: [Markdown, Blockquote],
})
const text = copyEditorContent(editor)
expect(text).toBe('Hermannsreute 44A')
})

// See https://github.com/nextcloud/text/issues/5660
it.failing('copy version number without escape character', () => {
const editor = createCustomEditor({
content: '<p>Hello</p><p>28.0.4</p>',
extensions: [Markdown],
})
const text = copyEditorContent(editor)
expect(text).toBe('Hello\n\n28.0.4')
})

it('strips bold, italic, and other marks from paragraph', () => {
const editor = createCustomEditor({
content: '<p><strong>Hello</strong></p><p><span style="text-decoration: underline;">lonely </span><em>world</em></p>',
extensions: [Markdown, Italic, Strong, Underline],
})
const text = copyEditorContent(editor)
expect(text).toBe('Hello\n\nlonely world')
})

it('strips href and link formatting from email address', () => {
const editor = createCustomEditor({
content: '<p>Hello</p><p><a href="mailto:example@example.com">example@example.com</a></p>',
extensions: [Markdown, Link],
})
const text = copyEditorContent(editor)
expect(text).toBe('Hello\n\nexample@example.com')
})

})

function copyEditorContent(editor) {
editor.commands.selectAll()
const slice = editor.state.selection.content()
const { text } = serializeForClipboard(editor.view, slice)
return text
}

0 comments on commit 4a9a654

Please sign in to comment.