Skip to content

Commit b069a35

Browse files
committed
WIP: chore(type): markdown extension
Signed-off-by: Max <max@nextcloud.com>
1 parent 59e04f7 commit b069a35

3 files changed

Lines changed: 49 additions & 31 deletions

File tree

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
/* eslint-disable jsdoc/require-param-type */
76
/* eslint-disable jsdoc/require-param-description */
87

98
/*
@@ -24,13 +23,28 @@
2423
* https://github.com/ProseMirror/prosemirror-markdown#class-markdownserializer
2524
*/
2625

26+
import type { MarkType, Node, NodeType, Schema, Slice } from '@tiptap/pm/model'
27+
2728
import { Extension, getExtensionField } from '@tiptap/core'
2829
import { DOMParser } from '@tiptap/pm/model'
2930
import { Plugin, PluginKey } from '@tiptap/pm/state'
3031
import { defaultMarkdownSerializer, MarkdownSerializer } from 'prosemirror-markdown'
3132
import markdownit from '../markdownit/index.js'
3233
import transformPastedHTML from './transformPastedHTML.ts'
3334

35+
declare module '@tiptap/core' {
36+
interface MarkConfig {
37+
toMarkdown: {
38+
default: null
39+
}
40+
}
41+
interface NodeConfig {
42+
toMarkdown: {
43+
default: null
44+
}
45+
}
46+
}
47+
3448
const Markdown = Extension.create({
3549
name: 'markdown',
3650

@@ -76,7 +90,7 @@ const Markdown = Extension.create({
7690
},
7791
clipboardTextParser(str, $context, _, view) {
7892
const parser = DOMParser.fromSchema(view.state.schema)
79-
const doc = document.cloneNode(false)
93+
const doc = document.cloneNode(false) as Document
8094
const dom = doc.createElement('div')
8195
if (shiftKey) {
8296
// Treat double newlines as paragraph breaks when pasting as plaintext
@@ -96,20 +110,22 @@ const Markdown = Extension.create({
96110
})
97111
},
98112
clipboardTextSerializer: (slice) => {
99-
const traverseNodes = (slice) => {
113+
const traverseNodes = (slice: Slice | Node) => {
100114
if (
101115
slice.content.childCount > 1
102-
|| slice.content.firstChild?.childCount > 1
116+
|| (slice.content.firstChild?.childCount ?? 0) > 1
103117
) {
104118
// Selected several nodes or several children of one block node
105119
return clipboardSerializer(this.editor.schema).serialize(slice.content)
106-
} else if (slice.isLeaf) {
107-
return slice.textContent
108-
} else {
120+
} else if (slice.content.firstChild?.isLeaf) {
121+
return slice.content.firstChild.textContent
122+
} else if (slice.content.firstChild) {
109123
// Only one block node selected, copy it's child content
110124
// Required to not copy wrapping block node when selecting e.g. one table
111125
// cell, one list item or the content of block quotes/callouts.
112126
return traverseNodes(slice.content.firstChild)
127+
} else {
128+
return '' // empty fragment
113129
}
114130
}
115131

@@ -129,15 +145,17 @@ const Markdown = Extension.create({
129145
* @param schema.nodes
130146
* @param schema.marks
131147
*/
132-
function createMarkdownSerializer({ nodes, marks }) {
148+
function createMarkdownSerializer(schema: {
149+
nodes: Record<string, NodeType>
150+
marks: Record<string, MarkType>
151+
}) {
133152
return {
134153
serializer: new MarkdownSerializer(
135-
extractNodesToMarkdown(nodes),
136-
extractMarksToMarkdown(marks),
154+
extractNodesToMarkdown(schema.nodes),
155+
extractMarksToMarkdown(schema.marks),
137156
),
138-
serialize(content, options) {
157+
serialize(content: Node) {
139158
return this.serializer.serialize(content, {
140-
...options,
141159
tightLists: true,
142160
})
143161
},
@@ -146,19 +164,16 @@ function createMarkdownSerializer({ nodes, marks }) {
146164

147165
/**
148166
*
149-
* @param root0
150-
* @param root0.nodes
151-
* @param root0.marks
167+
* @param schema or the editorc
152168
*/
153-
function clipboardSerializer({ nodes, marks }) {
169+
function clipboardSerializer(schema: Schema) {
154170
return {
155171
serializer: new MarkdownSerializer(
156-
extractNodesToMarkdown(nodes),
157-
extractToPlaintext(marks),
172+
extractNodesToMarkdown(schema.nodes),
173+
extractToPlaintext(schema.marks),
158174
),
159-
serialize(content, options) {
175+
serialize(content: Node) {
160176
return this.serializer.serialize(content, {
161-
...options,
162177
tightLists: true,
163178
})
164179
},
@@ -169,7 +184,7 @@ function clipboardSerializer({ nodes, marks }) {
169184
*
170185
* @param marks
171186
*/
172-
function extractToPlaintext(marks) {
187+
function extractToPlaintext(marks: Record<string, MarkType>) {
173188
const blankMark = {
174189
open: '',
175190
close: '',
@@ -186,7 +201,7 @@ function extractToPlaintext(marks) {
186201
*
187202
* @param nodesOrMarks
188203
*/
189-
function extractToMarkdown(nodesOrMarks) {
204+
function extractToMarkdown(nodesOrMarks: Record<string, (NodeType | MarkType)>) {
190205
const nodeOrMarkEntries = Object.entries(nodesOrMarks)
191206
.map(([name, nodeOrMark]) => [name, nodeOrMark.spec.toMarkdown])
192207
.filter(([, toMarkdown]) => toMarkdown)
@@ -198,7 +213,7 @@ function extractToMarkdown(nodesOrMarks) {
198213
*
199214
* @param nodes
200215
*/
201-
function extractNodesToMarkdown(nodes) {
216+
function extractNodesToMarkdown(nodes: Record<string, NodeType>) {
202217
const defaultNodes = convertNames(defaultMarkdownSerializer.nodes)
203218
const nodesToMarkdown = extractToMarkdown(nodes)
204219
return { ...defaultNodes, ...nodesToMarkdown }
@@ -208,21 +223,24 @@ function extractNodesToMarkdown(nodes) {
208223
*
209224
* @param marks
210225
*/
211-
function extractMarksToMarkdown(marks) {
226+
function extractMarksToMarkdown(marks: Record<string, MarkType>) {
212227
const defaultMarks = convertNames(defaultMarkdownSerializer.marks)
213228
const marksToMarkdown = extractToMarkdown(marks)
214229
return { ...defaultMarks, ...marksToMarkdown }
215230
}
216231

232+
type NodeSerializerSpecs = typeof defaultMarkdownSerializer.nodes
233+
type MarkSerializerSpecs = typeof defaultMarkdownSerializer.marks
234+
217235
/**
218236
*
219-
* @param object
237+
* @param specs
220238
*/
221-
function convertNames(object) {
222-
const convert = (name) => {
239+
function convertNames(specs: NodeSerializerSpecs | MarkSerializerSpecs) {
240+
const convert = (name: string) => {
223241
return name.replace(/_(\w)/g, (_m, letter) => letter.toUpperCase())
224242
}
225-
return Object.fromEntries(Object.entries(object).map(([name, value]) => [convert(name), value]))
243+
return Object.fromEntries(Object.entries(specs).map(([name, value]) => [convert(name), value]))
226244
}
227245

228246
export { createMarkdownSerializer }
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import { getExtensionField } from '@tiptap/core'
77
import markdownit from '../../markdownit/index.js'
88
import { MathBlock, MathInline } from '../../nodes/Mathematics.js'
9-
import editorTest from '../testHelpers/editorTest.js'
9+
import testEditor from '../testHelpers/testEditor.ts'
1010

11-
const test = editorTest.override('extensions', [MathBlock, MathInline])
11+
const test = testEditor.override('extensions', [MathBlock, MathInline])
1212

1313
describe('Mathematics nodes', () => {
1414
describe('Node structure', () => {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Editor, Extensions } from '@tiptap/core'
33
import { test as baseTest } from 'vitest'
44
import { createMarkdownSerializer } from '../../extensions/Markdown.js'
55
import markdownit from '../../markdownit/index.js'
6-
import createCustomEditor from './createCustomEditor.ts'
6+
import createCustomEditor from './createCustomEditor.js'
77

88
export default baseTest.extend<{
99
editor: Editor

0 commit comments

Comments
Β (0)