@tiptap/core 3.20.0 → 3.20.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/core",
3
3
  "description": "headless rich text editor",
4
- "version": "3.20.0",
4
+ "version": "3.20.2",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -52,10 +52,10 @@
52
52
  "jsx-dev-runtime"
53
53
  ],
54
54
  "devDependencies": {
55
- "@tiptap/pm": "^3.20.0"
55
+ "@tiptap/pm": "^3.20.2"
56
56
  },
57
57
  "peerDependencies": {
58
- "@tiptap/pm": "^3.20.0"
58
+ "@tiptap/pm": "^3.20.2"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
package/src/types.ts CHANGED
@@ -898,6 +898,8 @@ export type MarkdownParseHelpers = {
898
898
  parseInline: (tokens: MarkdownToken[]) => JSONContent[]
899
899
  /** Parse an array of block-level tokens */
900
900
  parseChildren: (tokens: MarkdownToken[]) => JSONContent[]
901
+ /** Parse block-level tokens while preserving implicit empty paragraphs from blank lines */
902
+ parseBlockChildren?: (tokens: MarkdownToken[]) => JSONContent[]
901
903
  /** Create a text node with optional marks */
902
904
  createTextNode: (text: string, marks?: Array<{ type: string; attrs?: any }>) => JSONContent
903
905
  /** Create any node type with attributes and content */
@@ -945,6 +947,7 @@ export type RenderContext = {
945
947
  level: number
946
948
  meta?: Record<string, any>
947
949
  parentType?: string | null
950
+ previousNode?: JSONContent | null
948
951
  }
949
952
 
950
953
  /** Extension contract for markdown parsing/serialization. */
@@ -1004,6 +1007,9 @@ export type MarkdownRendererHelpers = {
1004
1007
  */
1005
1008
  renderChildren: (nodes: JSONContent | JSONContent[], separator?: string) => string
1006
1009
 
1010
+ /** Render a single child node with its sibling index preserved */
1011
+ renderChild?: (node: JSONContent, index: number) => string
1012
+
1007
1013
  /**
1008
1014
  * Render a text token to a markdown string
1009
1015
  * @param prefix The prefix to add before the content
@@ -57,6 +57,7 @@ export function renderNestedMarkdownContent(
57
57
  node: JSONContent,
58
58
  h: {
59
59
  renderChildren: (nodes: JSONContent[]) => string
60
+ renderChild?: (node: JSONContent, index: number) => string
60
61
  indent: (text: string) => string
61
62
  },
62
63
  prefixOrGenerator: string | ((ctx: any) => string),
@@ -73,22 +74,23 @@ export function renderNestedMarkdownContent(
73
74
 
74
75
  // Render the main content (typically a paragraph)
75
76
  const mainContent = h.renderChildren([content])
76
- const output = [`${prefix}${mainContent}`]
77
+ let output = `${prefix}${mainContent}`
77
78
 
78
79
  // Handle nested children with proper indentation
79
80
  if (children && children.length > 0) {
80
- children.forEach(child => {
81
- const childContent = h.renderChildren([child])
82
- if (childContent) {
81
+ children.forEach((child, index) => {
82
+ const childContent = h.renderChild?.(child, index + 1) ?? h.renderChildren([child])
83
+ if (childContent !== undefined && childContent !== null) {
83
84
  // Split the child content by lines and indent each line
84
85
  const indentedChild = childContent
85
86
  .split('\n')
86
- .map(line => (line ? h.indent(line) : ''))
87
+ .map(line => (line ? h.indent(line) : h.indent('')))
87
88
  .join('\n')
88
- output.push(indentedChild)
89
+
90
+ output += child.type === 'paragraph' ? `\n\n${indentedChild}` : `\n${indentedChild}`
89
91
  }
90
92
  })
91
93
  }
92
94
 
93
- return output.join('\n')
95
+ return output
94
96
  }
@@ -1,3 +1,76 @@
1
+ /** Splits a CSS style string into declarations, ignoring semicolons inside quotes/parentheses. */
2
+ function splitStyleDeclarations(styles: string): string[] {
3
+ const result: string[] = []
4
+
5
+ let current = ''
6
+ let inSingleQuote = false
7
+ let inDoubleQuote = false
8
+ let parenDepth = 0
9
+
10
+ const length = styles.length
11
+ for (let i = 0; i < length; i += 1) {
12
+ const char = styles[i]
13
+ if (char === "'" && !inDoubleQuote) {
14
+ inSingleQuote = !inSingleQuote
15
+ current += char
16
+ continue
17
+ }
18
+ if (char === '"' && !inSingleQuote) {
19
+ inDoubleQuote = !inDoubleQuote
20
+ current += char
21
+ continue
22
+ }
23
+ if (!inSingleQuote && !inDoubleQuote) {
24
+ if (char === '(') {
25
+ parenDepth += 1
26
+ current += char
27
+ continue
28
+ }
29
+ if (char === ')' && parenDepth > 0) {
30
+ parenDepth -= 1
31
+ current += char
32
+ continue
33
+ }
34
+ if (char === ';' && parenDepth === 0) {
35
+ result.push(current)
36
+ current = ''
37
+ continue
38
+ }
39
+ }
40
+ current += char
41
+ }
42
+ if (current) {
43
+ result.push(current)
44
+ }
45
+
46
+ return result
47
+ }
48
+
49
+ /** Yields property/value pairs from a style string. */
50
+ function parseStyleEntries(styles: string | undefined): [property: string, value: string][] {
51
+ const pairs: [string, string][] = []
52
+
53
+ const declarations = splitStyleDeclarations(styles || '')
54
+ const numDeclarations = declarations.length
55
+
56
+ for (let i = 0; i < numDeclarations; i += 1) {
57
+ const declaration = declarations[i]
58
+
59
+ const firstColonIndex = declaration.indexOf(':')
60
+ if (firstColonIndex === -1) {
61
+ continue
62
+ }
63
+
64
+ const property = declaration.slice(0, firstColonIndex).trim()
65
+ const value = declaration.slice(firstColonIndex + 1).trim()
66
+ if (property && value) {
67
+ pairs.push([property, value])
68
+ }
69
+ }
70
+
71
+ return pairs
72
+ }
73
+
1
74
  export function mergeAttributes(...objects: Record<string, any>[]): Record<string, any> {
2
75
  return objects
3
76
  .filter(item => !!item)
@@ -21,32 +94,7 @@ export function mergeAttributes(...objects: Record<string, any>[]): Record<strin
21
94
 
22
95
  mergedAttributes[key] = [...existingClasses, ...insertClasses].join(' ')
23
96
  } else if (key === 'style') {
24
- const newStyles: string[] = value
25
- ? value
26
- .split(';')
27
- .map((style: string) => style.trim())
28
- .filter(Boolean)
29
- : []
30
- const existingStyles: string[] = mergedAttributes[key]
31
- ? mergedAttributes[key]
32
- .split(';')
33
- .map((style: string) => style.trim())
34
- .filter(Boolean)
35
- : []
36
-
37
- const styleMap = new Map<string, string>()
38
-
39
- existingStyles.forEach(style => {
40
- const [property, val] = style.split(':').map(part => part.trim())
41
-
42
- styleMap.set(property, val)
43
- })
44
-
45
- newStyles.forEach(style => {
46
- const [property, val] = style.split(':').map(part => part.trim())
47
-
48
- styleMap.set(property, val)
49
- })
97
+ const styleMap = new Map([...parseStyleEntries(mergedAttributes[key]), ...parseStyleEntries(value)])
50
98
 
51
99
  mergedAttributes[key] = Array.from(styleMap.entries())
52
100
  .map(([property, val]) => `${property}: ${val}`)