@tiptap/extension-mathematics 3.0.0-beta.17 → 3.0.0-beta.19

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.
@@ -1,33 +1,61 @@
1
1
  import { InputRule, mergeAttributes, Node } from '@tiptap/core'
2
2
  import type { Node as PMNode } from '@tiptap/pm/model'
3
- import katex from 'katex'
3
+ import katex, { type KatexOptions } from 'katex'
4
4
 
5
+ /**
6
+ * Configuration options for the BlockMath extension.
7
+ */
5
8
  export type BlockMathOptions = {
9
+ /**
10
+ * KaTeX specific options
11
+ * @see https://katex.org/docs/options.html
12
+ * @example
13
+ * ```ts
14
+ * katexOptions: {
15
+ * displayMode: true,
16
+ * throwOnError: false,
17
+ * },
18
+ */
19
+ katexOptions?: KatexOptions
20
+
21
+ /**
22
+ * Optional click handler for block math nodes.
23
+ * Called when a user clicks on a block math expression in the editor.
24
+ *
25
+ * @param node - The ProseMirror node representing the block math element
26
+ * @param pos - The position of the node within the document
27
+ * @example
28
+ * ```ts
29
+ * onClick: (node, pos) => {
30
+ * console.log('Block math clicked:', node.attrs.latex, 'at position:', pos)
31
+ * },
32
+ * ```
33
+ */
6
34
  onClick?: (node: PMNode, pos: number) => void
7
35
  }
8
36
 
9
37
  declare module '@tiptap/core' {
10
38
  interface Commands<ReturnType> {
11
- blockMath: {
39
+ insertBlockMath: {
12
40
  /**
13
- * Set block math node with LaTeX string.
14
- * @param options - Options for setting block math.
41
+ * Inserts a math block node with LaTeX string.
42
+ * @param options - Options for inserting block math.
15
43
  * @returns ReturnType
16
44
  */
17
- setBlockMath: (options: { latex: string; pos?: number }) => ReturnType
45
+ insertBlockMath: (options: { latex: string; pos?: number }) => ReturnType
18
46
 
19
47
  /**
20
- * Unset block math node.
48
+ * Deletes a block math node.
21
49
  * @returns ReturnType
22
50
  */
23
- unsetBlockMath: (options?: { pos?: number }) => ReturnType
51
+ deleteBlockMath: (options?: { pos?: number }) => ReturnType
24
52
 
25
53
  /**
26
54
  * Update block math node with optional LaTeX string.
27
55
  * @param options - Options for updating block math.
28
56
  * @returns ReturnType
29
57
  */
30
- updateBlockMath: (options?: { latex?: string; pos?: number }) => ReturnType
58
+ updateBlockMath: (options?: { latex: string; pos?: number }) => ReturnType
31
59
  }
32
60
  }
33
61
  }
@@ -39,7 +67,7 @@ declare module '@tiptap/core' {
39
67
  *
40
68
  * @example
41
69
  * ```javascript
42
- * import { BlockMath } from 'your-extension-path'
70
+ * import { BlockMath } from '@tiptap/extension-mathematics'
43
71
  * import { Editor } from '@tiptap/core'
44
72
  *
45
73
  * const editor = new Editor({
@@ -52,7 +80,7 @@ declare module '@tiptap/core' {
52
80
  * ],
53
81
  * })
54
82
  */
55
- export const BlockMath = Node.create({
83
+ export const BlockMath = Node.create<BlockMathOptions>({
56
84
  name: 'blockMath',
57
85
 
58
86
  group: 'block',
@@ -62,6 +90,7 @@ export const BlockMath = Node.create({
62
90
  addOptions() {
63
91
  return {
64
92
  onClick: undefined,
93
+ katexOptions: undefined,
65
94
  }
66
95
  },
67
96
 
@@ -81,17 +110,22 @@ export const BlockMath = Node.create({
81
110
 
82
111
  addCommands() {
83
112
  return {
84
- setBlockMath:
113
+ insertBlockMath:
85
114
  options =>
86
115
  ({ commands, editor }) => {
87
116
  const { latex, pos } = options
117
+
118
+ if (!latex) {
119
+ return false
120
+ }
121
+
88
122
  return commands.insertContentAt(pos ?? editor.state.selection.from, {
89
123
  type: this.name,
90
124
  attrs: { latex },
91
125
  })
92
126
  },
93
127
 
94
- unsetBlockMath:
128
+ deleteBlockMath:
95
129
  options =>
96
130
  ({ editor, tr }) => {
97
131
  const pos = options?.pos ?? editor.state.selection.$from.pos
@@ -160,10 +194,17 @@ export const BlockMath = Node.create({
160
194
  },
161
195
 
162
196
  addNodeView() {
197
+ const { katexOptions } = this.options
198
+
163
199
  return ({ node, getPos }) => {
164
200
  const wrapper = document.createElement('div')
165
201
  const innerWrapper = document.createElement('div')
166
- wrapper.className = 'Tiptap-mathematics-render Tiptap-mathematics-render--editable'
202
+ wrapper.className = 'tiptap-mathematics-render'
203
+
204
+ if (this.editor.isEditable) {
205
+ wrapper.classList.add('tiptap-mathematics-render--editable')
206
+ }
207
+
167
208
  innerWrapper.className = 'block-math-inner'
168
209
  wrapper.dataset.type = 'block-math'
169
210
  wrapper.setAttribute('data-latex', node.attrs.latex)
@@ -171,7 +212,7 @@ export const BlockMath = Node.create({
171
212
 
172
213
  function renderMath() {
173
214
  try {
174
- katex.render(node.attrs.latex, innerWrapper)
215
+ katex.render(node.attrs.latex, innerWrapper, katexOptions)
175
216
  wrapper.classList.remove('block-math-error')
176
217
  } catch {
177
218
  wrapper.textContent = node.attrs.latex
@@ -1,8 +1,41 @@
1
1
  import { InputRule, mergeAttributes, Node } from '@tiptap/core'
2
2
  import type { Node as PMNode } from '@tiptap/pm/model'
3
- import katex from 'katex'
3
+ import katex, { type KatexOptions } from 'katex'
4
4
 
5
+ /**
6
+ * Configuration options for the InlineMath extension.
7
+ */
5
8
  export type InlineMathOptions = {
9
+ /**
10
+ * KaTeX specific options
11
+ * @see https://katex.org/docs/options.html
12
+ * @example
13
+ * ```ts
14
+ * katexOptions: {
15
+ * displayMode: false,
16
+ * throwOnError: false,
17
+ * macros: {
18
+ * '\\RR': '\\mathbb{R}',
19
+ * '\\ZZ': '\\mathbb{Z}'
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ katexOptions?: KatexOptions
25
+
26
+ /**
27
+ * Optional click handler for inline math nodes.
28
+ * Called when a user clicks on an inline math expression in the editor.
29
+ *
30
+ * @param node - The ProseMirror node representing the inline math element
31
+ * @param pos - The position of the node within the document
32
+ * @example
33
+ * ```ts
34
+ * onClick: (node, pos) => {
35
+ * console.log('Inline math clicked:', node.attrs.latex, 'at position:', pos)
36
+ * }
37
+ * ```
38
+ */
6
39
  onClick?: (node: PMNode, pos: number) => void
7
40
  }
8
41
 
@@ -10,17 +43,17 @@ declare module '@tiptap/core' {
10
43
  interface Commands<ReturnType> {
11
44
  inlineMath: {
12
45
  /**
13
- * Set inline math node with LaTeX string.
14
- * @param options - Options for setting inline math.
46
+ * Insert a inline math node with LaTeX string.
47
+ * @param options - Options for inserting inline math.
15
48
  * @returns ReturnType
16
49
  */
17
- setInlineMath: (options: { latex: string; pos?: number }) => ReturnType
50
+ insertInlineMath: (options: { latex: string; pos?: number }) => ReturnType
18
51
 
19
52
  /**
20
- * Unset inline math node.
53
+ * Delete an inline math node.
21
54
  * @returns ReturnType
22
55
  */
23
- unsetInlineMath: (options?: { pos?: number }) => ReturnType
56
+ deleteInlineMath: (options?: { pos?: number }) => ReturnType
24
57
 
25
58
  /**
26
59
  * Update inline math node with optional LaTeX string.
@@ -39,7 +72,7 @@ declare module '@tiptap/core' {
39
72
  *
40
73
  * @example
41
74
  * ```javascript
42
- * import { InlineMath } from 'your-extension-path'
75
+ * import { InlineMath } from '@tiptap/extension-mathematics'
43
76
  * import { Editor } from '@tiptap/core'
44
77
  *
45
78
  * const editor = new Editor({
@@ -64,6 +97,7 @@ export const InlineMath = Node.create<InlineMathOptions>({
64
97
  addOptions() {
65
98
  return {
66
99
  onClick: undefined,
100
+ katexOptions: undefined,
67
101
  }
68
102
  },
69
103
 
@@ -83,21 +117,22 @@ export const InlineMath = Node.create<InlineMathOptions>({
83
117
 
84
118
  addCommands() {
85
119
  return {
86
- setInlineMath:
120
+ insertInlineMath:
87
121
  options =>
88
122
  ({ editor, tr }) => {
89
- const latex = options?.latex
90
- const pos = options?.pos ?? editor.state.selection.$from.pos
123
+ const latex = options.latex
124
+
125
+ const from = options?.pos ?? editor.state.selection.from
91
126
 
92
127
  if (!latex) {
93
128
  return false
94
129
  }
95
130
 
96
- tr.replaceWith(pos, pos, this.type.create({ latex }))
131
+ tr.replaceWith(from, from, this.type.create({ latex }))
97
132
  return true
98
133
  },
99
134
 
100
- unsetInlineMath:
135
+ deleteInlineMath:
101
136
  options =>
102
137
  ({ editor, tr }) => {
103
138
  const pos = options?.pos ?? editor.state.selection.$from.pos
@@ -163,15 +198,22 @@ export const InlineMath = Node.create<InlineMathOptions>({
163
198
  },
164
199
 
165
200
  addNodeView() {
201
+ const { katexOptions } = this.options
202
+
166
203
  return ({ node, getPos }) => {
167
204
  const wrapper = document.createElement('span')
168
- wrapper.className = 'Tiptap-mathematics-render Tiptap-mathematics-render--editable'
205
+ wrapper.className = 'tiptap-mathematics-render'
206
+
207
+ if (this.editor.isEditable) {
208
+ wrapper.classList.add('tiptap-mathematics-render--editable')
209
+ }
210
+
169
211
  wrapper.dataset.type = 'inline-math'
170
212
  wrapper.setAttribute('data-latex', node.attrs.latex)
171
213
 
172
214
  function renderMath() {
173
215
  try {
174
- katex.render(node.attrs.latex, wrapper)
216
+ katex.render(node.attrs.latex, wrapper, katexOptions)
175
217
  wrapper.classList.remove('inline-math-error')
176
218
  } catch {
177
219
  wrapper.textContent = node.attrs.latex
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Math } from './mathematics.js'
1
+ import { Mathematics } from './mathematics.js'
2
2
 
3
3
  export * from './extensions/index.js'
4
4
  export * from './mathematics.js'
5
5
  export * from './types.js'
6
+ export * from './utils.js'
6
7
 
7
- export default Math
8
+ export default Mathematics
@@ -3,7 +3,53 @@ import { Extension } from '@tiptap/core'
3
3
  import { BlockMath, InlineMath } from './extensions/index.js'
4
4
  import type { MathematicsOptions } from './types.js'
5
5
 
6
- export const Math = Extension.create<MathematicsOptions>({
6
+ /**
7
+ * Mathematics extension for Tiptap that provides both inline and block math support using KaTeX.
8
+ * This extension combines the InlineMath and BlockMath extensions to provide a complete
9
+ * mathematical expression solution for rich text editing. It supports LaTeX syntax,
10
+ * custom rendering options, and interactive math nodes.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { Editor } from '@tiptap/core'
15
+ * import { Mathematics } from '@tiptap/extension-mathematics'
16
+ * import { migrateMathStrings } from '@tiptap/extension-mathematics/utils'
17
+ *
18
+ * const editor = new Editor({
19
+ * extensions: [
20
+ * Mathematics.configure({
21
+ * inlineOptions: {
22
+ * onClick: (node, pos) => {
23
+ * console.log('Inline math clicked:', node.attrs.latex)
24
+ * }
25
+ * },
26
+ * blockOptions: {
27
+ * onClick: (node, pos) => {
28
+ * console.log('Block math clicked:', node.attrs.latex)
29
+ * }
30
+ * },
31
+ * katexOptions: {
32
+ * displayMode: false,
33
+ * throwOnError: false,
34
+ * macros: {
35
+ * '\\RR': '\\mathbb{R}',
36
+ * '\\ZZ': '\\mathbb{Z}'
37
+ * }
38
+ * }
39
+ * })
40
+ * ],
41
+ * content: `
42
+ * <p>Inline math: $E = mc^2$</p>
43
+ * <div data-type="block-math" data-latex="\\sum_{i=1}^{n} x_i = X"></div>
44
+ * `,
45
+ * onCreate({ editor }) {
46
+ * // Optional: Migrate existing math strings to math nodes
47
+ * migrateMathStrings(editor)
48
+ * }
49
+ * })
50
+ * ```
51
+ */
52
+ export const Mathematics = Extension.create<MathematicsOptions>({
7
53
  name: 'Mathematics',
8
54
 
9
55
  addOptions() {
@@ -15,10 +61,11 @@ export const Math = Extension.create<MathematicsOptions>({
15
61
  },
16
62
 
17
63
  addExtensions() {
18
- return [BlockMath.configure(this.options.blockOptions), InlineMath.configure(this.options.inlineOptions)]
64
+ return [
65
+ BlockMath.configure({ ...this.options.blockOptions, katexOptions: this.options.katexOptions }),
66
+ InlineMath.configure({ ...this.options.inlineOptions, katexOptions: this.options.katexOptions }),
67
+ ]
19
68
  },
20
69
  })
21
70
 
22
- export const Mathematics = Math
23
-
24
- export default Math
71
+ export default Mathematics
package/src/types.ts CHANGED
@@ -3,10 +3,22 @@ import type { KatexOptions } from 'katex'
3
3
 
4
4
  import type { BlockMathOptions, InlineMathOptions } from './extensions'
5
5
 
6
+ /**
7
+ * Configuration options for the Mathematics extension.
8
+ * This type defines the available customization options for both inline and block math rendering.
9
+ */
6
10
  export type MathematicsOptions = {
7
- inlineOptions?: InlineMathOptions
8
- blockOptions?: BlockMathOptions
11
+ /** Configuration options specific to inline math nodes */
12
+ inlineOptions?: Omit<InlineMathOptions, 'katexOptions'>
13
+ /** Configuration options specific to block math nodes */
14
+ blockOptions?: Omit<BlockMathOptions, 'katexOptions'>
15
+ /** KaTeX-specific rendering options passed to the KaTeX library */
9
16
  katexOptions?: KatexOptions
10
17
  }
11
18
 
19
+ /**
20
+ * Extended mathematics options that include an editor instance.
21
+ * This type combines the base mathematics options with an editor reference,
22
+ * typically used internally by the extension for operations that require editor access.
23
+ */
12
24
  export type MathematicsOptionsWithEditor = MathematicsOptions & { editor: Editor }
package/src/utils.ts ADDED
@@ -0,0 +1,101 @@
1
+ import type { Editor } from '@tiptap/core'
2
+ import type { Transaction } from '@tiptap/pm/state'
3
+
4
+ /**
5
+ * Regular expression to match LaTeX math strings wrapped in single dollar signs.
6
+ * This should not catch dollar signs which are not part of a math expression,
7
+ * like those used for currency or other purposes.
8
+ * It ensures that the dollar signs are not preceded or followed by digits,
9
+ * allowing for proper identification of inline math expressions.
10
+ *
11
+ * - `$x^2 + y^2 = z^2$` will match
12
+ * - `This is $inline math$ in text.` will match
13
+ * - `This is $100$ dollars.` will not match (as it is not a math expression)
14
+ * - `This is $x^2 + y^2 = z^2$ and $100$ dollars.` will match both math expressions
15
+ */
16
+ export const mathMigrationRegex = /(?<!\d)\$(?!\$)(?:[^$\n]|\\\$)*?(?<!\\)\$(?!\d)/g
17
+
18
+ /**
19
+ * Creates a transaction that migrates existing math strings in the document to new math nodes.
20
+ * This function traverses the document and replaces LaTeX math syntax (wrapped in single dollar signs)
21
+ * with proper inline math nodes, preserving the mathematical content.
22
+ *
23
+ * @param editor - The editor instance containing the schema and configuration
24
+ * @param tr - The transaction to modify with the migration operations
25
+ * @returns The modified transaction with math string replacements
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const editor = new Editor({ ... })
30
+ * const tr = editor.state.tr
31
+ * const updatedTr = createMathMigrateTransaction(editor, tr)
32
+ * editor.view.dispatch(updatedTr)
33
+ * ```
34
+ */
35
+ export function createMathMigrateTransaction(editor: Editor, tr: Transaction, regex: RegExp = mathMigrationRegex) {
36
+ // we traverse the document and replace all math nodes with the new math nodes
37
+ tr.doc.descendants((node, pos) => {
38
+ if (!node.isText || !node.text || !node.text.includes('$')) {
39
+ return
40
+ }
41
+
42
+ const { text } = node
43
+
44
+ const match = node.text.match(regex)
45
+ if (!match) {
46
+ return
47
+ }
48
+
49
+ match.forEach(mathMatch => {
50
+ const start = text.indexOf(mathMatch)
51
+ const end = start + mathMatch.length
52
+
53
+ const from = tr.mapping.map(pos + start)
54
+
55
+ const $from = tr.doc.resolve(from)
56
+ const parent = $from.parent
57
+ const index = $from.index()
58
+
59
+ const { inlineMath } = editor.schema.nodes
60
+
61
+ if (!parent.canReplaceWith(index, index + 1, inlineMath)) {
62
+ return
63
+ }
64
+
65
+ // Replace the math syntax with a new math node
66
+ tr.replaceWith(
67
+ tr.mapping.map(pos + start),
68
+ tr.mapping.map(pos + end),
69
+ inlineMath.create({ latex: mathMatch.slice(1, -1) }),
70
+ )
71
+ })
72
+ })
73
+
74
+ // don't add to history
75
+ tr.setMeta('addToHistory', false)
76
+ return tr
77
+ }
78
+
79
+ /**
80
+ * Migrates existing math strings in the editor document to math nodes.
81
+ * This function creates and dispatches a transaction that converts LaTeX math syntax
82
+ * (text wrapped in single dollar signs) into proper inline math nodes. The migration
83
+ * happens immediately and is not added to the editor's history.
84
+ *
85
+ * @param editor - The editor instance to perform the migration on
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const editor = new Editor({
90
+ * extensions: [Mathematics],
91
+ * content: 'This is inline math: $x^2 + y^2 = z^2$ in text.'
92
+ * })
93
+ *
94
+ * // Math strings will be automatically migrated to math nodes
95
+ * migrateMathStrings(editor)
96
+ * ```
97
+ */
98
+ export function migrateMathStrings(editor: Editor, regex: RegExp = mathMigrationRegex) {
99
+ const tr = createMathMigrateTransaction(editor, editor.state.tr, regex)
100
+ editor.view.dispatch(tr)
101
+ }