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

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/extension-mathematics",
3
3
  "description": "latex math extension for tiptap",
4
- "version": "3.0.0-beta.16",
4
+ "version": "3.0.0-beta.17",
5
5
  "homepage": "https://tiptap.dev/api/extensions/mathematics",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -37,13 +37,13 @@
37
37
  ],
38
38
  "devDependencies": {
39
39
  "@types/katex": "^0.16.7",
40
- "@tiptap/core": "3.0.0-beta.16",
41
- "@tiptap/pm": "3.0.0-beta.16"
40
+ "@tiptap/core": "3.0.0-beta.17",
41
+ "@tiptap/pm": "3.0.0-beta.17"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "katex": "^0.16.4",
45
- "@tiptap/core": "3.0.0-beta.16",
46
- "@tiptap/pm": "3.0.0-beta.16"
45
+ "@tiptap/core": "3.0.0-beta.17",
46
+ "@tiptap/pm": "3.0.0-beta.17"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsup",
@@ -0,0 +1,210 @@
1
+ import { InputRule, mergeAttributes, Node } from '@tiptap/core'
2
+ import type { Node as PMNode } from '@tiptap/pm/model'
3
+ import katex from 'katex'
4
+
5
+ export type BlockMathOptions = {
6
+ onClick?: (node: PMNode, pos: number) => void
7
+ }
8
+
9
+ declare module '@tiptap/core' {
10
+ interface Commands<ReturnType> {
11
+ blockMath: {
12
+ /**
13
+ * Set block math node with LaTeX string.
14
+ * @param options - Options for setting block math.
15
+ * @returns ReturnType
16
+ */
17
+ setBlockMath: (options: { latex: string; pos?: number }) => ReturnType
18
+
19
+ /**
20
+ * Unset block math node.
21
+ * @returns ReturnType
22
+ */
23
+ unsetBlockMath: (options?: { pos?: number }) => ReturnType
24
+
25
+ /**
26
+ * Update block math node with optional LaTeX string.
27
+ * @param options - Options for updating block math.
28
+ * @returns ReturnType
29
+ */
30
+ updateBlockMath: (options?: { latex?: string; pos?: number }) => ReturnType
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * BlockMath is a Tiptap extension for rendering block mathematical expressions using KaTeX.
37
+ * It allows users to insert LaTeX formatted math expressions block within text.
38
+ * It supports rendering, input rules for LaTeX syntax, and click handling for interaction.
39
+ *
40
+ * @example
41
+ * ```javascript
42
+ * import { BlockMath } from 'your-extension-path'
43
+ * import { Editor } from '@tiptap/core'
44
+ *
45
+ * const editor = new Editor({
46
+ * extensions: [
47
+ * BlockMath.configure({
48
+ * onClick: (node, pos) => {
49
+ * console.log('Block math clicked:', node.attrs.latex, 'at position:', pos)
50
+ * },
51
+ * }),
52
+ * ],
53
+ * })
54
+ */
55
+ export const BlockMath = Node.create({
56
+ name: 'blockMath',
57
+
58
+ group: 'block',
59
+
60
+ atom: true,
61
+
62
+ addOptions() {
63
+ return {
64
+ onClick: undefined,
65
+ }
66
+ },
67
+
68
+ addAttributes() {
69
+ return {
70
+ latex: {
71
+ default: '',
72
+ parseHTML: element => element.getAttribute('data-latex'),
73
+ renderHTML: attributes => {
74
+ return {
75
+ 'data-latex': attributes.latex,
76
+ }
77
+ },
78
+ },
79
+ }
80
+ },
81
+
82
+ addCommands() {
83
+ return {
84
+ setBlockMath:
85
+ options =>
86
+ ({ commands, editor }) => {
87
+ const { latex, pos } = options
88
+ return commands.insertContentAt(pos ?? editor.state.selection.from, {
89
+ type: this.name,
90
+ attrs: { latex },
91
+ })
92
+ },
93
+
94
+ unsetBlockMath:
95
+ options =>
96
+ ({ editor, tr }) => {
97
+ const pos = options?.pos ?? editor.state.selection.$from.pos
98
+ const node = editor.state.doc.nodeAt(pos)
99
+
100
+ if (!node || node.type.name !== this.name) {
101
+ return false
102
+ }
103
+
104
+ tr.delete(pos, pos + node.nodeSize)
105
+ return true
106
+ },
107
+
108
+ updateBlockMath:
109
+ options =>
110
+ ({ editor, tr }) => {
111
+ const latex = options?.latex
112
+ let pos = options?.pos
113
+
114
+ if (pos === undefined) {
115
+ pos = editor.state.selection.$from.pos
116
+ }
117
+
118
+ const node = editor.state.doc.nodeAt(pos)
119
+
120
+ if (!node || node.type.name !== this.name) {
121
+ return false
122
+ }
123
+
124
+ tr.setNodeMarkup(pos, this.type, {
125
+ ...node.attrs,
126
+ latex: latex || node.attrs.latex,
127
+ })
128
+
129
+ return true
130
+ },
131
+ }
132
+ },
133
+
134
+ parseHTML() {
135
+ return [
136
+ {
137
+ tag: 'div[data-type="block-math"]',
138
+ },
139
+ ]
140
+ },
141
+
142
+ renderHTML({ HTMLAttributes }) {
143
+ return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'block-math' })]
144
+ },
145
+
146
+ addInputRules() {
147
+ return [
148
+ new InputRule({
149
+ find: /^\$\$\$([^$]+)\$\$\$$/,
150
+ handler: ({ state, range, match }) => {
151
+ const [, latex] = match
152
+ const { tr } = state
153
+ const start = range.from
154
+ const end = range.to
155
+
156
+ tr.replaceWith(start, end, this.type.create({ latex }))
157
+ },
158
+ }),
159
+ ]
160
+ },
161
+
162
+ addNodeView() {
163
+ return ({ node, getPos }) => {
164
+ const wrapper = document.createElement('div')
165
+ const innerWrapper = document.createElement('div')
166
+ wrapper.className = 'Tiptap-mathematics-render Tiptap-mathematics-render--editable'
167
+ innerWrapper.className = 'block-math-inner'
168
+ wrapper.dataset.type = 'block-math'
169
+ wrapper.setAttribute('data-latex', node.attrs.latex)
170
+ wrapper.appendChild(innerWrapper)
171
+
172
+ function renderMath() {
173
+ try {
174
+ katex.render(node.attrs.latex, innerWrapper)
175
+ wrapper.classList.remove('block-math-error')
176
+ } catch {
177
+ wrapper.textContent = node.attrs.latex
178
+ wrapper.classList.add('block-math-error')
179
+ }
180
+ }
181
+
182
+ const handleClick = (event: MouseEvent) => {
183
+ event.preventDefault()
184
+ event.stopPropagation()
185
+ const pos = getPos()
186
+
187
+ if (pos == null) {
188
+ return
189
+ }
190
+
191
+ if (this.options.onClick) {
192
+ this.options.onClick(node, pos)
193
+ }
194
+ }
195
+
196
+ if (this.options.onClick) {
197
+ wrapper.addEventListener('click', handleClick)
198
+ }
199
+
200
+ renderMath()
201
+
202
+ return {
203
+ dom: wrapper,
204
+ destroy() {
205
+ wrapper.removeEventListener('click', handleClick)
206
+ },
207
+ }
208
+ }
209
+ },
210
+ })
@@ -0,0 +1,210 @@
1
+ import { InputRule, mergeAttributes, Node } from '@tiptap/core'
2
+ import type { Node as PMNode } from '@tiptap/pm/model'
3
+ import katex from 'katex'
4
+
5
+ export type InlineMathOptions = {
6
+ onClick?: (node: PMNode, pos: number) => void
7
+ }
8
+
9
+ declare module '@tiptap/core' {
10
+ interface Commands<ReturnType> {
11
+ inlineMath: {
12
+ /**
13
+ * Set inline math node with LaTeX string.
14
+ * @param options - Options for setting inline math.
15
+ * @returns ReturnType
16
+ */
17
+ setInlineMath: (options: { latex: string; pos?: number }) => ReturnType
18
+
19
+ /**
20
+ * Unset inline math node.
21
+ * @returns ReturnType
22
+ */
23
+ unsetInlineMath: (options?: { pos?: number }) => ReturnType
24
+
25
+ /**
26
+ * Update inline math node with optional LaTeX string.
27
+ * @param options - Options for updating inline math.
28
+ * @returns ReturnType
29
+ */
30
+ updateInlineMath: (options?: { latex?: string; pos?: number }) => ReturnType
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * InlineMath is a Tiptap extension for rendering inline mathematical expressions using KaTeX.
37
+ * It allows users to insert LaTeX formatted math expressions inline within text.
38
+ * It supports rendering, input rules for LaTeX syntax, and click handling for interaction.
39
+ *
40
+ * @example
41
+ * ```javascript
42
+ * import { InlineMath } from 'your-extension-path'
43
+ * import { Editor } from '@tiptap/core'
44
+ *
45
+ * const editor = new Editor({
46
+ * extensions: [
47
+ * InlineMath.configure({
48
+ * onClick: (node, pos) => {
49
+ * console.log('Inline math clicked:', node.attrs.latex, 'at position:', pos)
50
+ * },
51
+ * }),
52
+ * ],
53
+ * })
54
+ */
55
+ export const InlineMath = Node.create<InlineMathOptions>({
56
+ name: 'inlineMath',
57
+
58
+ group: 'inline',
59
+
60
+ inline: true,
61
+
62
+ atom: true,
63
+
64
+ addOptions() {
65
+ return {
66
+ onClick: undefined,
67
+ }
68
+ },
69
+
70
+ addAttributes() {
71
+ return {
72
+ latex: {
73
+ default: '',
74
+ parseHTML: element => element.getAttribute('data-latex'),
75
+ renderHTML: attributes => {
76
+ return {
77
+ 'data-latex': attributes.latex,
78
+ }
79
+ },
80
+ },
81
+ }
82
+ },
83
+
84
+ addCommands() {
85
+ return {
86
+ setInlineMath:
87
+ options =>
88
+ ({ editor, tr }) => {
89
+ const latex = options?.latex
90
+ const pos = options?.pos ?? editor.state.selection.$from.pos
91
+
92
+ if (!latex) {
93
+ return false
94
+ }
95
+
96
+ tr.replaceWith(pos, pos, this.type.create({ latex }))
97
+ return true
98
+ },
99
+
100
+ unsetInlineMath:
101
+ options =>
102
+ ({ editor, tr }) => {
103
+ const pos = options?.pos ?? editor.state.selection.$from.pos
104
+ const node = editor.state.doc.nodeAt(pos)
105
+
106
+ if (!node || node.type.name !== this.name) {
107
+ return false
108
+ }
109
+
110
+ tr.delete(pos, pos + node.nodeSize)
111
+ return true
112
+ },
113
+
114
+ updateInlineMath:
115
+ options =>
116
+ ({ editor, tr }) => {
117
+ const latex = options?.latex
118
+ let pos = options?.pos
119
+
120
+ if (pos === undefined) {
121
+ pos = editor.state.selection.$from.pos
122
+ }
123
+
124
+ const node = editor.state.doc.nodeAt(pos)
125
+
126
+ if (!node || node.type.name !== this.name) {
127
+ return false
128
+ }
129
+
130
+ tr.setNodeMarkup(pos, this.type, { ...node.attrs, latex })
131
+
132
+ return true
133
+ },
134
+ }
135
+ },
136
+
137
+ parseHTML() {
138
+ return [
139
+ {
140
+ tag: 'span[data-type="inline-math"]',
141
+ },
142
+ ]
143
+ },
144
+
145
+ renderHTML({ HTMLAttributes }) {
146
+ return ['span', mergeAttributes(HTMLAttributes, { 'data-type': 'inline-math' })]
147
+ },
148
+
149
+ addInputRules() {
150
+ return [
151
+ new InputRule({
152
+ find: /(?<!\$)\$\$([^$\n]+)\$\$(?!\$)$/,
153
+ handler: ({ state, range, match }) => {
154
+ const [, latex] = match
155
+ const { tr } = state
156
+ const start = range.from
157
+ const end = range.to
158
+
159
+ tr.replaceWith(start, end, this.type.create({ latex }))
160
+ },
161
+ }),
162
+ ]
163
+ },
164
+
165
+ addNodeView() {
166
+ return ({ node, getPos }) => {
167
+ const wrapper = document.createElement('span')
168
+ wrapper.className = 'Tiptap-mathematics-render Tiptap-mathematics-render--editable'
169
+ wrapper.dataset.type = 'inline-math'
170
+ wrapper.setAttribute('data-latex', node.attrs.latex)
171
+
172
+ function renderMath() {
173
+ try {
174
+ katex.render(node.attrs.latex, wrapper)
175
+ wrapper.classList.remove('inline-math-error')
176
+ } catch {
177
+ wrapper.textContent = node.attrs.latex
178
+ wrapper.classList.add('inline-math-error')
179
+ }
180
+ }
181
+
182
+ const handleClick = (event: MouseEvent) => {
183
+ event.preventDefault()
184
+ event.stopPropagation()
185
+ const pos = getPos()
186
+
187
+ if (pos == null) {
188
+ return
189
+ }
190
+
191
+ if (this.options.onClick) {
192
+ this.options.onClick(node, pos)
193
+ }
194
+ }
195
+
196
+ if (this.options.onClick) {
197
+ wrapper.addEventListener('click', handleClick)
198
+ }
199
+
200
+ renderMath()
201
+
202
+ return {
203
+ dom: wrapper,
204
+ destroy() {
205
+ wrapper.removeEventListener('click', handleClick)
206
+ },
207
+ }
208
+ }
209
+ },
210
+ })
@@ -0,0 +1,2 @@
1
+ export * from './BlockMath.js'
2
+ export * from './InlineMath.js'
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Mathematics } from './mathematics.js'
1
+ import { Math } from './mathematics.js'
2
2
 
3
+ export * from './extensions/index.js'
3
4
  export * from './mathematics.js'
4
- export * from './MathematicsPlugin.js'
5
5
  export * from './types.js'
6
6
 
7
- export default Mathematics
7
+ export default Math
@@ -1,31 +1,24 @@
1
1
  import { Extension } from '@tiptap/core'
2
- import type { EditorState } from '@tiptap/pm/state'
3
2
 
4
- import { MathematicsPlugin } from './MathematicsPlugin.js'
3
+ import { BlockMath, InlineMath } from './extensions/index.js'
5
4
  import type { MathematicsOptions } from './types.js'
6
5
 
7
- export const defaultShouldRender = (state: EditorState, pos: number) => {
8
- const $pos = state.doc.resolve(pos)
9
- const isInCodeBlock = $pos.parent.type.name === 'codeBlock'
10
-
11
- return !isInCodeBlock
12
- }
13
-
14
- export const Mathematics = Extension.create<MathematicsOptions>({
6
+ export const Math = Extension.create<MathematicsOptions>({
15
7
  name: 'Mathematics',
16
8
 
17
9
  addOptions() {
18
10
  return {
19
- // eslint-disable-next-line no-useless-escape
20
- regex: /\$([^\$]*)\$/gi,
11
+ inlineOptions: undefined,
12
+ blockOptions: undefined,
21
13
  katexOptions: undefined,
22
- shouldRender: defaultShouldRender,
23
14
  }
24
15
  },
25
16
 
26
- addProseMirrorPlugins() {
27
- return [MathematicsPlugin({ ...this.options, editor: this.editor })]
17
+ addExtensions() {
18
+ return [BlockMath.configure(this.options.blockOptions), InlineMath.configure(this.options.inlineOptions)]
28
19
  },
29
20
  })
30
21
 
31
- export default Mathematics
22
+ export const Mathematics = Math
23
+
24
+ export default Math
package/src/types.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { Editor } from '@tiptap/core'
2
- import type { Node } from '@tiptap/pm/model'
3
- import type { EditorState } from '@tiptap/pm/state'
4
2
  import type { KatexOptions } from 'katex'
5
3
 
4
+ import type { BlockMathOptions, InlineMathOptions } from './extensions'
5
+
6
6
  export type MathematicsOptions = {
7
- regex: RegExp
7
+ inlineOptions?: InlineMathOptions
8
+ blockOptions?: BlockMathOptions
8
9
  katexOptions?: KatexOptions
9
- shouldRender: (state: EditorState, pos: number, node: Node) => boolean
10
10
  }
11
11
 
12
12
  export type MathematicsOptionsWithEditor = MathematicsOptions & { editor: Editor }