@tiptap/markdown 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +18 -0
- package/dist/index.cjs +761 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +243 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +761 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/Extension.ts +213 -0
- package/src/MarkdownManager.ts +788 -0
- package/src/index.ts +3 -0
- package/src/types.ts +1 -0
- package/src/utils.ts +152 -0
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ContentType = 'json' | 'html' | 'markdown'
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { Content } from '@tiptap/core'
|
|
2
|
+
import type { Fragment, Node } from '@tiptap/pm/model'
|
|
3
|
+
|
|
4
|
+
import type { ContentType } from './types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wraps each line of the content with the given prefix.
|
|
8
|
+
* @param prefix The prefix to wrap each line with.
|
|
9
|
+
* @param content The content to wrap.
|
|
10
|
+
* @returns The content with each line wrapped with the prefix.
|
|
11
|
+
*/
|
|
12
|
+
export function wrapInMarkdownBlock(prefix: string, content: string) {
|
|
13
|
+
// split content lines
|
|
14
|
+
const lines = content.split('\n')
|
|
15
|
+
|
|
16
|
+
// add empty strings between every line
|
|
17
|
+
const output = lines
|
|
18
|
+
// add empty lines between each block
|
|
19
|
+
.flatMap(line => [line, ''])
|
|
20
|
+
// add the prefix to each line
|
|
21
|
+
.map(line => `${prefix}${line}`)
|
|
22
|
+
.join('\n')
|
|
23
|
+
|
|
24
|
+
return output.slice(0, output.length - 1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Identifies marks that need to be closed (active but not in current node).
|
|
29
|
+
* Returns the mark types in reverse order for proper closing sequence.
|
|
30
|
+
*/
|
|
31
|
+
export function findMarksToClose(activeMarks: Map<string, any>, currentMarks: Map<string, any>): string[] {
|
|
32
|
+
const marksToClose: string[] = []
|
|
33
|
+
Array.from(activeMarks.keys()).forEach(markType => {
|
|
34
|
+
if (!currentMarks.has(markType)) {
|
|
35
|
+
marksToClose.push(markType)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return marksToClose.reverse()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Identifies marks that need to be opened (in current node but not active).
|
|
43
|
+
*/
|
|
44
|
+
export function findMarksToOpen(
|
|
45
|
+
activeMarks: Map<string, any>,
|
|
46
|
+
currentMarks: Map<string, any>,
|
|
47
|
+
): Array<{ type: string; mark: any }> {
|
|
48
|
+
const marksToOpen: Array<{ type: string; mark: any }> = []
|
|
49
|
+
Array.from(currentMarks.entries()).forEach(([markType, mark]) => {
|
|
50
|
+
if (!activeMarks.has(markType)) {
|
|
51
|
+
marksToOpen.push({ type: markType, mark })
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
return marksToOpen
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Determines which marks need to be closed at the end of the current text node.
|
|
59
|
+
* This handles cases where marks end at node boundaries or when transitioning
|
|
60
|
+
* to nodes with different mark sets.
|
|
61
|
+
*/
|
|
62
|
+
export function findMarksToCloseAtEnd(
|
|
63
|
+
activeMarks: Map<string, any>,
|
|
64
|
+
currentMarks: Map<string, any>,
|
|
65
|
+
nextNode: any,
|
|
66
|
+
markSetsEqual: (a: Map<string, any>, b: Map<string, any>) => boolean,
|
|
67
|
+
): string[] {
|
|
68
|
+
const isLastNode = !nextNode
|
|
69
|
+
const nextNodeHasNoMarks = nextNode && nextNode.type === 'text' && (!nextNode.marks || nextNode.marks.length === 0)
|
|
70
|
+
const nextNodeHasDifferentMarks =
|
|
71
|
+
nextNode &&
|
|
72
|
+
nextNode.type === 'text' &&
|
|
73
|
+
nextNode.marks &&
|
|
74
|
+
!markSetsEqual(currentMarks, new Map(nextNode.marks.map((mark: any) => [mark.type, mark])))
|
|
75
|
+
|
|
76
|
+
const marksToCloseAtEnd: string[] = []
|
|
77
|
+
if (isLastNode || nextNodeHasNoMarks || nextNodeHasDifferentMarks) {
|
|
78
|
+
if (nextNode && nextNode.type === 'text' && nextNode.marks) {
|
|
79
|
+
const nextMarks = new Map(nextNode.marks.map((mark: any) => [mark.type, mark]))
|
|
80
|
+
Array.from(activeMarks.keys()).forEach(markType => {
|
|
81
|
+
if (!nextMarks.has(markType)) {
|
|
82
|
+
marksToCloseAtEnd.push(markType)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
} else if (isLastNode || nextNodeHasNoMarks) {
|
|
86
|
+
// Close all active marks
|
|
87
|
+
marksToCloseAtEnd.push(...Array.from(activeMarks.keys()))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return marksToCloseAtEnd.reverse()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Closes active marks before rendering a non-text node.
|
|
96
|
+
* Returns the closing markdown syntax and clears the active marks.
|
|
97
|
+
*/
|
|
98
|
+
export function closeMarksBeforeNode(
|
|
99
|
+
activeMarks: Map<string, any>,
|
|
100
|
+
getMarkClosing: (markType: string, mark: any) => string,
|
|
101
|
+
): string {
|
|
102
|
+
let beforeMarkdown = ''
|
|
103
|
+
Array.from(activeMarks.keys())
|
|
104
|
+
.reverse()
|
|
105
|
+
.forEach(markType => {
|
|
106
|
+
const mark = activeMarks.get(markType)
|
|
107
|
+
const closeMarkdown = getMarkClosing(markType, mark)
|
|
108
|
+
if (closeMarkdown) {
|
|
109
|
+
beforeMarkdown = closeMarkdown + beforeMarkdown
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
activeMarks.clear()
|
|
113
|
+
return beforeMarkdown
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reopens marks after rendering a non-text node.
|
|
118
|
+
* Returns the opening markdown syntax and updates the active marks.
|
|
119
|
+
*/
|
|
120
|
+
export function reopenMarksAfterNode(
|
|
121
|
+
marksToReopen: Map<string, any>,
|
|
122
|
+
activeMarks: Map<string, any>,
|
|
123
|
+
getMarkOpening: (markType: string, mark: any) => string,
|
|
124
|
+
): string {
|
|
125
|
+
let afterMarkdown = ''
|
|
126
|
+
Array.from(marksToReopen.entries()).forEach(([markType, mark]) => {
|
|
127
|
+
const openMarkdown = getMarkOpening(markType, mark)
|
|
128
|
+
if (openMarkdown) {
|
|
129
|
+
afterMarkdown += openMarkdown
|
|
130
|
+
}
|
|
131
|
+
activeMarks.set(markType, mark)
|
|
132
|
+
})
|
|
133
|
+
return afterMarkdown
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Assumes the content type based off the content.
|
|
138
|
+
* @param content The content to assume the type for.
|
|
139
|
+
* @param contentType The content type that should be prioritized.
|
|
140
|
+
*/
|
|
141
|
+
export function assumeContentType(
|
|
142
|
+
content: (Content | Fragment | Node) | string,
|
|
143
|
+
contentType: ContentType,
|
|
144
|
+
): ContentType {
|
|
145
|
+
// if not a string, we assume it will be a json content object
|
|
146
|
+
if (typeof content !== 'string') {
|
|
147
|
+
return 'json'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// otherwise we let the content type be what it is
|
|
151
|
+
return contentType
|
|
152
|
+
}
|