@tiptap/core 3.6.6 → 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/dist/index.cjs +3653 -3143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +736 -63
- package/dist/index.d.ts +736 -63
- package/dist/index.js +3681 -3181
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/Extendable.ts +44 -0
- package/src/ExtensionManager.ts +9 -0
- package/src/commands/insertContent.ts +15 -13
- package/src/commands/insertContentAt.ts +28 -26
- package/src/commands/setContent.ts +20 -18
- package/src/index.ts +3 -0
- package/src/pasteRules/nodePasteRule.ts +1 -1
- package/src/types.ts +156 -0
- package/src/utilities/index.ts +2 -0
- package/src/utilities/markdown/attributeUtils.ts +130 -0
- package/src/utilities/markdown/createAtomBlockMarkdownSpec.ts +141 -0
- package/src/utilities/markdown/createBlockMarkdownSpec.ts +225 -0
- package/src/utilities/markdown/createInlineMarkdownSpec.ts +236 -0
- package/src/utilities/markdown/index.ts +13 -0
- package/src/utilities/markdown/parseIndentedBlocks.ts +193 -0
- package/src/utilities/markdown/renderNestedMarkdownContent.ts +94 -0
- package/dist/jsx-runtime/jsx-runtime.d.cts +0 -23
- package/dist/jsx-runtime/jsx-runtime.d.ts +0 -23
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility for parsing indented markdown blocks with hierarchical nesting.
|
|
3
|
+
*
|
|
4
|
+
* This utility handles the complex logic of parsing markdown blocks that can contain
|
|
5
|
+
* nested content based on indentation levels, maintaining proper hierarchical structure
|
|
6
|
+
* for lists, task lists, and other indented block types.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface ParsedBlock {
|
|
10
|
+
type: string
|
|
11
|
+
raw: string
|
|
12
|
+
mainContent: string
|
|
13
|
+
indentLevel: number
|
|
14
|
+
nestedContent?: string
|
|
15
|
+
nestedTokens?: any[]
|
|
16
|
+
[key: string]: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BlockParserConfig {
|
|
20
|
+
/** Regex pattern to match block items */
|
|
21
|
+
itemPattern: RegExp
|
|
22
|
+
/** Function to extract data from regex match */
|
|
23
|
+
extractItemData: (match: RegExpMatchArray) => {
|
|
24
|
+
mainContent: string
|
|
25
|
+
indentLevel: number
|
|
26
|
+
[key: string]: any
|
|
27
|
+
}
|
|
28
|
+
/** Function to create the final token */
|
|
29
|
+
createToken: (data: any, nestedTokens?: any[]) => ParsedBlock
|
|
30
|
+
/** Base indentation to remove from nested content (default: 2 spaces) */
|
|
31
|
+
baseIndentSize?: number
|
|
32
|
+
/**
|
|
33
|
+
* Custom parser for nested content. If provided, this will be called instead
|
|
34
|
+
* of the default lexer.blockTokens() for parsing nested content.
|
|
35
|
+
* This allows recursive parsing of the same block type.
|
|
36
|
+
*/
|
|
37
|
+
customNestedParser?: (dedentedContent: string) => any[] | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parses markdown text into hierarchical indented blocks with proper nesting.
|
|
42
|
+
*
|
|
43
|
+
* This utility handles:
|
|
44
|
+
* - Line-by-line parsing with pattern matching
|
|
45
|
+
* - Hierarchical nesting based on indentation levels
|
|
46
|
+
* - Nested content collection and parsing
|
|
47
|
+
* - Empty line handling
|
|
48
|
+
* - Content dedenting for nested blocks
|
|
49
|
+
*
|
|
50
|
+
* The key difference from flat parsing is that this maintains the hierarchical
|
|
51
|
+
* structure where nested items become `nestedTokens` of their parent items,
|
|
52
|
+
* rather than being flattened into a single array.
|
|
53
|
+
*
|
|
54
|
+
* @param src - The markdown source text to parse
|
|
55
|
+
* @param config - Configuration object defining how to parse and create tokens
|
|
56
|
+
* @param lexer - Markdown lexer for parsing nested content
|
|
57
|
+
* @returns Parsed result with hierarchical items, or undefined if no matches
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const result = parseIndentedBlocks(src, {
|
|
62
|
+
* itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,
|
|
63
|
+
* extractItemData: (match) => ({
|
|
64
|
+
* indentLevel: match[1].length,
|
|
65
|
+
* mainContent: match[4],
|
|
66
|
+
* checked: match[3].toLowerCase() === 'x'
|
|
67
|
+
* }),
|
|
68
|
+
* createToken: (data, nestedTokens) => ({
|
|
69
|
+
* type: 'taskItem',
|
|
70
|
+
* checked: data.checked,
|
|
71
|
+
* text: data.mainContent,
|
|
72
|
+
* nestedTokens
|
|
73
|
+
* })
|
|
74
|
+
* }, lexer)
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function parseIndentedBlocks(
|
|
78
|
+
src: string,
|
|
79
|
+
config: BlockParserConfig,
|
|
80
|
+
lexer: {
|
|
81
|
+
inlineTokens: (src: string) => any[]
|
|
82
|
+
blockTokens: (src: string) => any[]
|
|
83
|
+
},
|
|
84
|
+
):
|
|
85
|
+
| {
|
|
86
|
+
items: ParsedBlock[]
|
|
87
|
+
raw: string
|
|
88
|
+
}
|
|
89
|
+
| undefined {
|
|
90
|
+
const lines = src.split('\n')
|
|
91
|
+
const items: ParsedBlock[] = []
|
|
92
|
+
let totalRaw = ''
|
|
93
|
+
let i = 0
|
|
94
|
+
const baseIndentSize = config.baseIndentSize || 2
|
|
95
|
+
|
|
96
|
+
while (i < lines.length) {
|
|
97
|
+
const currentLine = lines[i]
|
|
98
|
+
const itemMatch = currentLine.match(config.itemPattern)
|
|
99
|
+
|
|
100
|
+
if (!itemMatch) {
|
|
101
|
+
// Not a matching item - stop if we have items, otherwise this isn't our block type
|
|
102
|
+
if (items.length > 0) {
|
|
103
|
+
break
|
|
104
|
+
} else if (currentLine.trim() === '') {
|
|
105
|
+
i += 1
|
|
106
|
+
continue
|
|
107
|
+
} else {
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const itemData = config.extractItemData(itemMatch)
|
|
113
|
+
const { indentLevel, mainContent } = itemData
|
|
114
|
+
totalRaw = `${totalRaw}${currentLine}\n`
|
|
115
|
+
|
|
116
|
+
// Collect content for this item (including nested items)
|
|
117
|
+
const itemContent = [mainContent] // Start with the main text
|
|
118
|
+
i += 1
|
|
119
|
+
|
|
120
|
+
// Look ahead for nested content (indented more than current item)
|
|
121
|
+
while (i < lines.length) {
|
|
122
|
+
const nextLine = lines[i]
|
|
123
|
+
|
|
124
|
+
if (nextLine.trim() === '') {
|
|
125
|
+
// Empty line - might be end of nested content
|
|
126
|
+
const nextNonEmptyIndex = lines.slice(i + 1).findIndex(l => l.trim() !== '')
|
|
127
|
+
if (nextNonEmptyIndex === -1) {
|
|
128
|
+
// No more content
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const nextNonEmpty = lines[i + 1 + nextNonEmptyIndex]
|
|
133
|
+
const nextIndent = nextNonEmpty.match(/^(\s*)/)?.[1]?.length || 0
|
|
134
|
+
|
|
135
|
+
if (nextIndent > indentLevel) {
|
|
136
|
+
// Nested content continues after empty line
|
|
137
|
+
itemContent.push(nextLine)
|
|
138
|
+
totalRaw = `${totalRaw}${nextLine}\n`
|
|
139
|
+
i += 1
|
|
140
|
+
continue
|
|
141
|
+
} else {
|
|
142
|
+
// End of nested content
|
|
143
|
+
break
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const nextIndent = nextLine.match(/^(\s*)/)?.[1]?.length || 0
|
|
148
|
+
|
|
149
|
+
if (nextIndent > indentLevel) {
|
|
150
|
+
// This is nested content for the current item
|
|
151
|
+
itemContent.push(nextLine)
|
|
152
|
+
totalRaw = `${totalRaw}${nextLine}\n`
|
|
153
|
+
i += 1
|
|
154
|
+
} else {
|
|
155
|
+
// Same or less indentation - this belongs to parent level
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Parse nested content if present
|
|
161
|
+
let nestedTokens: any[] | undefined
|
|
162
|
+
const nestedContent = itemContent.slice(1)
|
|
163
|
+
|
|
164
|
+
if (nestedContent.length > 0) {
|
|
165
|
+
// Remove the base indentation from nested content
|
|
166
|
+
const dedentedNested = nestedContent
|
|
167
|
+
.map(nestedLine => nestedLine.slice(indentLevel + baseIndentSize)) // Remove base indent + 2 spaces
|
|
168
|
+
.join('\n')
|
|
169
|
+
|
|
170
|
+
if (dedentedNested.trim()) {
|
|
171
|
+
// Use custom nested parser if provided, otherwise fall back to default
|
|
172
|
+
if (config.customNestedParser) {
|
|
173
|
+
nestedTokens = config.customNestedParser(dedentedNested)
|
|
174
|
+
} else {
|
|
175
|
+
nestedTokens = lexer.blockTokens(dedentedNested)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Create the token using the provided factory function
|
|
181
|
+
const token = config.createToken(itemData, nestedTokens)
|
|
182
|
+
items.push(token)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (items.length === 0) {
|
|
186
|
+
return undefined
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
items,
|
|
191
|
+
raw: totalRaw.trim(),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { JSONContent } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Utility functions for rendering nested content in markdown.
|
|
5
|
+
*
|
|
6
|
+
* This module provides reusable utilities for extensions that need to render
|
|
7
|
+
* content with a prefix on the main line and properly indented nested content.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Utility function for rendering content with a main line prefix and nested indented content.
|
|
12
|
+
*
|
|
13
|
+
* This function handles the common pattern of rendering content with:
|
|
14
|
+
* 1. A main line with a prefix (like "- " for lists, "> " for blockquotes, etc.)
|
|
15
|
+
* 2. Nested content that gets indented properly
|
|
16
|
+
*
|
|
17
|
+
* @param node - The ProseMirror node representing the content
|
|
18
|
+
* @param h - The markdown renderer helper
|
|
19
|
+
* @param prefixOrGenerator - Either a string prefix or a function that generates the prefix from context
|
|
20
|
+
* @param ctx - Optional context object (used when prefixOrGenerator is a function)
|
|
21
|
+
* @returns The rendered markdown string
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // For a bullet list item with static prefix
|
|
26
|
+
* return renderNestedMarkdownContent(node, h, '- ')
|
|
27
|
+
*
|
|
28
|
+
* // For a task item with static prefix
|
|
29
|
+
* const prefix = `- [${node.attrs?.checked ? 'x' : ' '}] `
|
|
30
|
+
* return renderNestedMarkdownContent(node, h, prefix)
|
|
31
|
+
*
|
|
32
|
+
* // For a blockquote with static prefix
|
|
33
|
+
* return renderNestedMarkdownContent(node, h, '> ')
|
|
34
|
+
*
|
|
35
|
+
* // For content with dynamic prefix based on context
|
|
36
|
+
* return renderNestedMarkdownContent(node, h, ctx => {
|
|
37
|
+
* if (ctx.parentType === 'orderedList') {
|
|
38
|
+
* return `${ctx.index + 1}. `
|
|
39
|
+
* }
|
|
40
|
+
* return '- '
|
|
41
|
+
* }, ctx)
|
|
42
|
+
*
|
|
43
|
+
* // Custom extension example
|
|
44
|
+
* const CustomContainer = Node.create({
|
|
45
|
+
* name: 'customContainer',
|
|
46
|
+
* // ... other config
|
|
47
|
+
* markdown: {
|
|
48
|
+
* render: (node, h) => {
|
|
49
|
+
* const type = node.attrs?.type || 'info'
|
|
50
|
+
* return renderNestedMarkdownContent(node, h, `[${type}] `)
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function renderNestedMarkdownContent(
|
|
57
|
+
node: JSONContent,
|
|
58
|
+
h: {
|
|
59
|
+
renderChildren: (nodes: JSONContent[]) => string
|
|
60
|
+
indent: (text: string) => string
|
|
61
|
+
},
|
|
62
|
+
prefixOrGenerator: string | ((ctx: any) => string),
|
|
63
|
+
ctx?: any,
|
|
64
|
+
): string {
|
|
65
|
+
if (!node || !Array.isArray(node.content)) {
|
|
66
|
+
return ''
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Determine the prefix based on the input
|
|
70
|
+
const prefix = typeof prefixOrGenerator === 'function' ? prefixOrGenerator(ctx) : prefixOrGenerator
|
|
71
|
+
|
|
72
|
+
const [content, ...children] = node.content
|
|
73
|
+
|
|
74
|
+
// Render the main content (typically a paragraph)
|
|
75
|
+
const mainContent = h.renderChildren([content])
|
|
76
|
+
const output = [`${prefix}${mainContent}`]
|
|
77
|
+
|
|
78
|
+
// Handle nested children with proper indentation
|
|
79
|
+
if (children && children.length > 0) {
|
|
80
|
+
children.forEach(child => {
|
|
81
|
+
const childContent = h.renderChildren([child])
|
|
82
|
+
if (childContent) {
|
|
83
|
+
// Split the child content by lines and indent each line
|
|
84
|
+
const indentedChild = childContent
|
|
85
|
+
.split('\n')
|
|
86
|
+
.map(line => (line ? h.indent(line) : ''))
|
|
87
|
+
.join('\n')
|
|
88
|
+
output.push(indentedChild)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return output.join('\n')
|
|
94
|
+
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
type Attributes = Record<string, any>;
|
|
2
|
-
type DOMOutputSpecElement = 0 | Attributes | DOMOutputSpecArray;
|
|
3
|
-
/**
|
|
4
|
-
* Better describes the output of a `renderHTML` function in prosemirror
|
|
5
|
-
* @see https://prosemirror.net/docs/ref/#model.DOMOutputSpec
|
|
6
|
-
*/
|
|
7
|
-
type DOMOutputSpecArray = [string] | [string, Attributes] | [string, 0] | [string, Attributes, 0] | [string, Attributes, DOMOutputSpecArray | 0] | [string, DOMOutputSpecArray];
|
|
8
|
-
declare namespace JSX {
|
|
9
|
-
type Element = DOMOutputSpecArray;
|
|
10
|
-
interface IntrinsicElements {
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
}
|
|
13
|
-
interface ElementChildrenAttribute {
|
|
14
|
-
children: unknown;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
type JSXRenderer = (tag: 'slot' | string | ((props?: Attributes) => DOMOutputSpecArray | DOMOutputSpecElement), props?: Attributes, ...children: JSXRenderer[]) => DOMOutputSpecArray | DOMOutputSpecElement;
|
|
18
|
-
declare function Fragment(props: {
|
|
19
|
-
children: JSXRenderer[];
|
|
20
|
-
}): JSXRenderer[];
|
|
21
|
-
declare const h: JSXRenderer;
|
|
22
|
-
|
|
23
|
-
export { type Attributes, type DOMOutputSpecArray, type DOMOutputSpecElement, Fragment, JSX, type JSXRenderer, h as createElement, h, h as jsx, h as jsxDEV, h as jsxs };
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
type Attributes = Record<string, any>;
|
|
2
|
-
type DOMOutputSpecElement = 0 | Attributes | DOMOutputSpecArray;
|
|
3
|
-
/**
|
|
4
|
-
* Better describes the output of a `renderHTML` function in prosemirror
|
|
5
|
-
* @see https://prosemirror.net/docs/ref/#model.DOMOutputSpec
|
|
6
|
-
*/
|
|
7
|
-
type DOMOutputSpecArray = [string] | [string, Attributes] | [string, 0] | [string, Attributes, 0] | [string, Attributes, DOMOutputSpecArray | 0] | [string, DOMOutputSpecArray];
|
|
8
|
-
declare namespace JSX {
|
|
9
|
-
type Element = DOMOutputSpecArray;
|
|
10
|
-
interface IntrinsicElements {
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
}
|
|
13
|
-
interface ElementChildrenAttribute {
|
|
14
|
-
children: unknown;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
type JSXRenderer = (tag: 'slot' | string | ((props?: Attributes) => DOMOutputSpecArray | DOMOutputSpecElement), props?: Attributes, ...children: JSXRenderer[]) => DOMOutputSpecArray | DOMOutputSpecElement;
|
|
18
|
-
declare function Fragment(props: {
|
|
19
|
-
children: JSXRenderer[];
|
|
20
|
-
}): JSXRenderer[];
|
|
21
|
-
declare const h: JSXRenderer;
|
|
22
|
-
|
|
23
|
-
export { type Attributes, type DOMOutputSpecArray, type DOMOutputSpecElement, Fragment, JSX, type JSXRenderer, h as createElement, h, h as jsx, h as jsxDEV, h as jsxs };
|