From aed8f5c7852a6b7d9346be39b89219a8d5db39b8 Mon Sep 17 00:00:00 2001
From: Elizabeth Danzberger
Date: Wed, 10 Apr 2024 10:56:04 -0400
Subject: [PATCH 1/2] fix: improve node and mark copy-paste behavior
Signed-off-by: Elizabeth Danzberger
---
src/extensions/Markdown.js | 49 ++++++++++++++++++-----
src/tests/extensions/Markdown.spec.js | 57 ++++++++++++++++++++++-----
2 files changed, 86 insertions(+), 20 deletions(-)
diff --git a/src/extensions/Markdown.js b/src/extensions/Markdown.js
index 6070a6465fa..4bce626b90a 100644
--- a/src/extensions/Markdown.js
+++ b/src/extensions/Markdown.js
@@ -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 {
@@ -128,12 +128,22 @@ 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 })
@@ -141,15 +151,34 @@ const createMarkdownSerializer = ({ nodes, marks }) => {
}
}
+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) => {
diff --git a/src/tests/extensions/Markdown.spec.js b/src/tests/extensions/Markdown.spec.js
index a1bcc081b6d..6308348d32c 100644
--- a/src/tests/extensions/Markdown.spec.js
+++ b/src/tests/extensions/Markdown.spec.js
@@ -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'
@@ -85,9 +85,7 @@ describe('Markdown extension integrated in the editor', () => {
content: '
',
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')
})
@@ -96,9 +94,7 @@ describe('Markdown extension integrated in the editor', () => {
content: 'Hello
',
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')
})
@@ -107,10 +103,51 @@ describe('Markdown extension integrated in the editor', () => {
content: '
',
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: 'Hermannsreute 44A
',
+ extensions: [Markdown, Blockquote],
+ })
+ const text = copyEditorContent(editor)
+ expect(text).toBe('Hermannsreute 44A')
+ })
+
+ it('copy version number without escape character', () => {
+ const editor = createCustomEditor({
+ content: 'Hello
28.0.4
',
+ 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: 'Hello
lonely world
',
+ 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: 'Hello
example@example.com
',
+ 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
+}
From 5d573d8e6c8835c3d8dc3818e6a3fbb002d029ff Mon Sep 17 00:00:00 2001
From: Max
Date: Thu, 11 Apr 2024 16:01:48 +0200
Subject: [PATCH 2/2] tests(markdown): mark version number test as failing
https://github.com/nextcloud/text/issues/5660 is keeping track of it.
Signed-off-by: Max
---
src/tests/extensions/Markdown.spec.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/tests/extensions/Markdown.spec.js b/src/tests/extensions/Markdown.spec.js
index 6308348d32c..be11f40d0aa 100644
--- a/src/tests/extensions/Markdown.spec.js
+++ b/src/tests/extensions/Markdown.spec.js
@@ -116,7 +116,8 @@ describe('Markdown extension integrated in the editor', () => {
expect(text).toBe('Hermannsreute 44A')
})
- it('copy version number without escape character', () => {
+ // See https://github.com/nextcloud/text/issues/5660
+ it.failing('copy version number without escape character', () => {
const editor = createCustomEditor({
content: 'Hello
28.0.4
',
extensions: [Markdown],