snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:11:02 +05:00
commit 083a46e93b
2115 changed files with 245958 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import { BulletList, ListItem } from '@tiptap/extension-list'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { afterEach, describe, expect, it } from 'vite-plus/test'
import { BlockMath } from './BlockMath.js'
import { InlineMath } from './InlineMath.js'
describe('BlockMath', () => {
let editor: Editor
afterEach(() => {
editor?.destroy()
})
describe('input rule', () => {
it('replaces an empty host paragraph instead of leaving it behind', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, BlockMath],
content: {
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: '$$$x^2$$' }] }],
},
})
editor.commands.setTextSelection(editor.state.doc.content.size)
editor.view.someProp('handleTextInput', f =>
f(editor.view, editor.state.selection.from, editor.state.selection.from, '$'),
)
expect(editor.getJSON()).toEqual({
type: 'doc',
content: [{ type: 'blockMath', attrs: { latex: 'x^2' } }],
})
})
it('inserts a sibling block math node inside a list item without breaking the schema', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, BulletList, ListItem, BlockMath],
content: {
type: 'doc',
content: [
{
type: 'bulletList',
content: [
{
type: 'listItem',
content: [{ type: 'paragraph', content: [{ type: 'text', text: '$$$x^2$$' }] }],
},
],
},
],
},
})
editor.commands.setTextSelection(editor.state.doc.content.size - 3)
editor.view.someProp('handleTextInput', f =>
f(editor.view, editor.state.selection.from, editor.state.selection.from, '$'),
)
expect(editor.getJSON()).toEqual({
type: 'doc',
content: [
{
type: 'bulletList',
content: [
{
type: 'listItem',
content: [{ type: 'paragraph' }, { type: 'blockMath', attrs: { latex: 'x^2' } }],
},
],
},
],
})
})
it('does not fire when the match would not start at the textblock start', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, BlockMath],
content: {
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hello $$$x^2$$' }] }],
},
})
editor.commands.setTextSelection(editor.state.doc.content.size)
const handled = editor.view.someProp('handleTextInput', f =>
f(editor.view, editor.state.selection.from, editor.state.selection.from, '$'),
)
expect(handled).toBeFalsy()
expect(editor.getJSON()).toEqual({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hello $$$x^2$$' }] }],
})
})
})
describe('updateBlockMath', () => {
function createEditor(latex: string) {
return new Editor({
extensions: [Document, Paragraph, Text, BlockMath],
content: {
type: 'doc',
content: [{ type: 'blockMath', attrs: { latex } }],
},
})
}
it('clears the latex attribute when given an empty string', () => {
editor = createEditor('x^2')
editor.commands.updateBlockMath({ latex: '', pos: 0 })
expect(editor.state.doc.firstChild?.attrs.latex).toBe('')
})
it('matches updateInlineMath behavior when clearing latex with an empty string', () => {
const blockEditor = createEditor('x^2')
blockEditor.commands.updateBlockMath({ latex: '', pos: 0 })
const blockEmpty = blockEditor.state.doc.firstChild?.attrs.latex
blockEditor.destroy()
const inlineEditor = new Editor({
extensions: [Document, Paragraph, Text, InlineMath],
content: {
type: 'doc',
content: [
{
type: 'paragraph',
content: [{ type: 'inlineMath', attrs: { latex: 'x^2' } }],
},
],
},
})
const inlinePos = 1
inlineEditor.commands.updateInlineMath({ latex: '', pos: inlinePos })
const inlineEmpty = inlineEditor.state.doc.nodeAt(inlinePos)?.attrs.latex
inlineEditor.destroy()
expect(blockEmpty).toBe(inlineEmpty)
})
})
})

View File

@@ -0,0 +1,302 @@
import { InputRule, mergeAttributes, Node } from '@tiptap/core'
import type { Node as PMNode } from '@tiptap/pm/model'
import katex, { type KatexOptions } from 'katex'
/**
* Configuration options for the BlockMath extension.
*/
export type BlockMathOptions = {
/**
* KaTeX specific options
* @see https://katex.org/docs/options.html
* @example
* ```ts
* katexOptions: {
* displayMode: true,
* throwOnError: false,
* },
*/
katexOptions?: KatexOptions
/**
* Optional click handler for block math nodes.
* Called when a user clicks on a block math expression in the editor.
*
* @param node - The ProseMirror node representing the block math element
* @param pos - The position of the node within the document
* @example
* ```ts
* onClick: (node, pos) => {
* console.log('Block math clicked:', node.attrs.latex, 'at position:', pos)
* },
* ```
*/
onClick?: (node: PMNode, pos: number) => void
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
insertBlockMath: {
/**
* Inserts a math block node with LaTeX string.
* @param options - Options for inserting block math.
* @returns ReturnType
*/
insertBlockMath: (options: { latex: string; pos?: number }) => ReturnType
/**
* Deletes a block math node.
* @returns ReturnType
*/
deleteBlockMath: (options?: { pos?: number }) => ReturnType
/**
* Update block math node with optional LaTeX string.
* @param options - Options for updating block math.
* @returns ReturnType
*/
updateBlockMath: (options?: { latex: string; pos?: number }) => ReturnType
}
}
}
/**
* BlockMath is a Tiptap extension for rendering block mathematical expressions using KaTeX.
* It allows users to insert LaTeX formatted math expressions block within text.
* It supports rendering, input rules for LaTeX syntax, and click handling for interaction.
*
* @example
* ```javascript
* import { BlockMath } from '@tiptap/extension-mathematics'
* import { Editor } from '@tiptap/core'
*
* const editor = new Editor({
* extensions: [
* BlockMath.configure({
* onClick: (node, pos) => {
* console.log('Block math clicked:', node.attrs.latex, 'at position:', pos)
* },
* }),
* ],
* })
*/
export const BlockMath = Node.create<BlockMathOptions>({
name: 'blockMath',
group: 'block',
atom: true,
addOptions() {
return {
onClick: undefined,
katexOptions: undefined,
}
},
addAttributes() {
return {
latex: {
default: '',
parseHTML: element => element.getAttribute('data-latex'),
renderHTML: attributes => {
return {
'data-latex': attributes.latex,
}
},
},
}
},
addCommands() {
return {
insertBlockMath:
options =>
({ commands, editor }) => {
const { latex, pos } = options
if (!latex) {
return false
}
return commands.insertContentAt(pos ?? editor.state.selection.from, {
type: this.name,
attrs: { latex },
})
},
deleteBlockMath:
options =>
({ editor, tr }) => {
const pos = options?.pos ?? editor.state.selection.$from.pos
const node = editor.state.doc.nodeAt(pos)
if (!node || node.type.name !== this.name) {
return false
}
tr.delete(pos, pos + node.nodeSize)
return true
},
updateBlockMath:
options =>
({ editor, tr }) => {
const latex = options?.latex
let pos = options?.pos
if (pos === undefined) {
pos = editor.state.selection.$from.pos
}
const node = editor.state.doc.nodeAt(pos)
if (!node || node.type.name !== this.name) {
return false
}
tr.setNodeMarkup(pos, this.type, {
...node.attrs,
latex: latex ?? node.attrs.latex,
})
return true
},
}
},
parseHTML() {
return [
{
tag: 'div[data-type="block-math"]',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'block-math' })]
},
parseMarkdown: (token: any) => {
return {
type: 'blockMath',
attrs: {
latex: token.latex,
},
}
},
renderMarkdown: node => {
const latex = node.attrs?.latex || ''
const output = ['$$', latex, '$$']
return output.join('\n')
},
markdownTokenizer: {
name: 'blockMath',
level: 'block',
start: (src: string) => src.indexOf('$$'),
tokenize: (src: string) => {
// Match $$latex$$ syntax for block math
const match = src.match(/^\$\$([^$]+)\$\$/)
if (!match) {
return undefined
}
const [fullMatch, latex] = match
return {
type: 'blockMath',
raw: fullMatch,
latex: latex.trim(),
}
},
},
addInputRules() {
return [
new InputRule({
find: /^\$\$\$([^$]+)\$\$\$$/,
handler: ({ state, range, match }) => {
const [, latex] = match
const { tr } = state
const $from = state.doc.resolve(range.from)
const node = this.type.create({ latex })
const consumesHostTextblock =
$from.depth > 0 &&
$from.parent.isTextblock &&
range.from === $from.start() &&
range.to === $from.end()
// Whether the node containing the host textblock can replace it with a blockMath node.
const canReplaceHostTextblock =
consumesHostTextblock &&
$from.node(-1).canReplaceWith($from.index(-1), $from.indexAfter(-1), this.type)
const replacementRange = canReplaceHostTextblock
? { from: $from.before(), to: $from.after() }
: range
tr.replaceWith(replacementRange.from, replacementRange.to, node)
},
}),
]
},
addNodeView() {
const { katexOptions } = this.options
return ({ node, getPos }) => {
const wrapper = document.createElement('div')
const innerWrapper = document.createElement('div')
wrapper.className = 'tiptap-mathematics-render'
if (this.editor.isEditable) {
wrapper.classList.add('tiptap-mathematics-render--editable')
}
innerWrapper.className = 'block-math-inner'
wrapper.dataset.type = 'block-math'
wrapper.setAttribute('data-latex', node.attrs.latex)
wrapper.appendChild(innerWrapper)
function renderMath() {
try {
katex.render(node.attrs.latex, innerWrapper, katexOptions)
wrapper.classList.remove('block-math-error')
} catch {
wrapper.textContent = node.attrs.latex
wrapper.classList.add('block-math-error')
}
}
const handleClick = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
const pos = getPos()
if (pos == null) {
return
}
if (this.options.onClick) {
this.options.onClick(node, pos)
}
}
if (this.options.onClick) {
wrapper.addEventListener('click', handleClick)
}
renderMath()
return {
dom: wrapper,
destroy() {
wrapper.removeEventListener('click', handleClick)
},
}
}
},
})

View File

@@ -0,0 +1,80 @@
import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { afterEach, describe, expect, it } from 'vite-plus/test'
import { InlineMath } from './InlineMath.js'
describe('InlineMath', () => {
let editor: Editor
afterEach(() => {
editor?.destroy()
})
it('preserves previous character to input rule match', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, InlineMath],
content: {
type: 'doc',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Hello $$x$' }],
},
],
},
})
editor.commands.setTextSelection(editor.state.doc.nodeSize)
editor.view.someProp('handleTextInput', f =>
f(editor.view, editor.state.selection.from, editor.state.selection.from, '$'),
)
expect(editor.getJSON()).toEqual({
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
// Ensure previous character is preserved (e.g., space)
text: 'Hello ',
},
{
type: 'inlineMath',
attrs: { latex: 'x' },
},
],
},
],
})
})
it('ensure unmatched triple $ expressions', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, InlineMath],
content: {
type: 'doc',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Hello $$$x$' }],
},
],
},
})
editor.commands.setTextSelection(editor.state.doc.nodeSize)
const handled = editor.view.someProp('handleTextInput', f =>
f(editor.view, editor.state.selection.from, editor.state.selection.from, '$'),
)
// Expect no input rule to match
expect(handled).toBeFalsy()
})
})

View File

@@ -0,0 +1,288 @@
import { InputRule, mergeAttributes, Node } from '@tiptap/core'
import type { Node as PMNode } from '@tiptap/pm/model'
import katex, { type KatexOptions } from 'katex'
/**
* Configuration options for the InlineMath extension.
*/
export type InlineMathOptions = {
/**
* KaTeX specific options
* @see https://katex.org/docs/options.html
* @example
* ```ts
* katexOptions: {
* displayMode: false,
* throwOnError: false,
* macros: {
* '\\RR': '\\mathbb{R}',
* '\\ZZ': '\\mathbb{Z}'
* }
* }
* ```
*/
katexOptions?: KatexOptions
/**
* Optional click handler for inline math nodes.
* Called when a user clicks on an inline math expression in the editor.
*
* @param node - The ProseMirror node representing the inline math element
* @param pos - The position of the node within the document
* @example
* ```ts
* onClick: (node, pos) => {
* console.log('Inline math clicked:', node.attrs.latex, 'at position:', pos)
* }
* ```
*/
onClick?: (node: PMNode, pos: number) => void
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
inlineMath: {
/**
* Insert a inline math node with LaTeX string.
* @param options - Options for inserting inline math.
* @returns ReturnType
*/
insertInlineMath: (options: { latex: string; pos?: number }) => ReturnType
/**
* Delete an inline math node.
* @returns ReturnType
*/
deleteInlineMath: (options?: { pos?: number }) => ReturnType
/**
* Update inline math node with optional LaTeX string.
* @param options - Options for updating inline math.
* @returns ReturnType
*/
updateInlineMath: (options?: { latex?: string; pos?: number }) => ReturnType
}
}
}
/**
* InlineMath is a Tiptap extension for rendering inline mathematical expressions using KaTeX.
* It allows users to insert LaTeX formatted math expressions inline within text.
* It supports rendering, input rules for LaTeX syntax, and click handling for interaction.
*
* @example
* ```javascript
* import { InlineMath } from '@tiptap/extension-mathematics'
* import { Editor } from '@tiptap/core'
*
* const editor = new Editor({
* extensions: [
* InlineMath.configure({
* onClick: (node, pos) => {
* console.log('Inline math clicked:', node.attrs.latex, 'at position:', pos)
* },
* }),
* ],
* })
*/
export const InlineMath = Node.create<InlineMathOptions>({
name: 'inlineMath',
group: 'inline',
inline: true,
atom: true,
addOptions() {
return {
onClick: undefined,
katexOptions: undefined,
}
},
addAttributes() {
return {
latex: {
default: '',
parseHTML: element => element.getAttribute('data-latex'),
renderHTML: attributes => {
return {
'data-latex': attributes.latex,
}
},
},
}
},
addCommands() {
return {
insertInlineMath:
options =>
({ editor, tr }) => {
const latex = options.latex
const from = options?.pos ?? editor.state.selection.from
if (!latex) {
return false
}
tr.replaceWith(from, from, this.type.create({ latex }))
return true
},
deleteInlineMath:
options =>
({ editor, tr }) => {
const pos = options?.pos ?? editor.state.selection.$from.pos
const node = editor.state.doc.nodeAt(pos)
if (!node || node.type.name !== this.name) {
return false
}
tr.delete(pos, pos + node.nodeSize)
return true
},
updateInlineMath:
options =>
({ editor, tr }) => {
const latex = options?.latex
let pos = options?.pos
if (pos === undefined) {
pos = editor.state.selection.$from.pos
}
const node = editor.state.doc.nodeAt(pos)
if (!node || node.type.name !== this.name) {
return false
}
tr.setNodeMarkup(pos, this.type, { ...node.attrs, latex })
return true
},
}
},
parseHTML() {
return [
{
tag: 'span[data-type="inline-math"]',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['span', mergeAttributes(HTMLAttributes, { 'data-type': 'inline-math' })]
},
parseMarkdown: (token: any) => {
return {
type: 'inlineMath',
attrs: {
latex: token.latex,
},
}
},
renderMarkdown: node => {
const latex = node.attrs?.latex || ''
return `$${latex}$`
},
markdownTokenizer: {
name: 'inlineMath',
level: 'inline',
start: (src: string) => src.indexOf('$'),
tokenize: (src: string) => {
// Match $latex$ syntax for inline math (but not $$)
const match = src.match(/^\$([^$]+)\$(?!\$)/)
if (!match) {
return undefined
}
const [fullMatch, latex] = match
return {
type: 'inlineMath',
raw: fullMatch,
latex: latex.trim(),
}
},
},
addInputRules() {
return [
new InputRule({
find: /(?<!\$)(\$\$([^$\n]+?)\$\$)(?!\$)/,
handler: ({ state, range, match }) => {
const latex = match[2]
const { tr } = state
const start = range.from
const end = range.to
tr.replaceWith(start, end, this.type.create({ latex }))
},
}),
]
},
addNodeView() {
const { katexOptions } = this.options
return ({ node, getPos }) => {
const wrapper = document.createElement('span')
wrapper.className = 'tiptap-mathematics-render'
if (this.editor.isEditable) {
wrapper.classList.add('tiptap-mathematics-render--editable')
}
wrapper.dataset.type = 'inline-math'
wrapper.setAttribute('data-latex', node.attrs.latex)
function renderMath() {
try {
katex.render(node.attrs.latex, wrapper, katexOptions)
wrapper.classList.remove('inline-math-error')
} catch {
wrapper.textContent = node.attrs.latex
wrapper.classList.add('inline-math-error')
}
}
const handleClick = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
const pos = getPos()
if (pos == null) {
return
}
if (this.options.onClick) {
this.options.onClick(node, pos)
}
}
if (this.options.onClick) {
wrapper.addEventListener('click', handleClick)
}
renderMath()
return {
dom: wrapper,
destroy() {
wrapper.removeEventListener('click', handleClick)
},
}
}
},
})

View File

@@ -0,0 +1,2 @@
export * from './BlockMath.js'
export * from './InlineMath.js'

View File

@@ -0,0 +1,8 @@
import { Mathematics } from './mathematics.js'
export * from './extensions/index.js'
export * from './mathematics.js'
export * from './types.js'
export * from './utils.js'
export default Mathematics

View File

@@ -0,0 +1,77 @@
import { Extension } from '@tiptap/core'
import { BlockMath, InlineMath } from './extensions/index.js'
import type { MathematicsOptions } from './types.js'
/**
* Mathematics extension for Tiptap that provides both inline and block math support using KaTeX.
* This extension combines the InlineMath and BlockMath extensions to provide a complete
* mathematical expression solution for rich text editing. It supports LaTeX syntax,
* custom rendering options, and interactive math nodes.
*
* @example
* ```typescript
* import { Editor } from '@tiptap/core'
* import { Mathematics } from '@tiptap/extension-mathematics'
* import { migrateMathStrings } from '@tiptap/extension-mathematics/utils'
*
* const editor = new Editor({
* extensions: [
* Mathematics.configure({
* inlineOptions: {
* onClick: (node, pos) => {
* console.log('Inline math clicked:', node.attrs.latex)
* }
* },
* blockOptions: {
* onClick: (node, pos) => {
* console.log('Block math clicked:', node.attrs.latex)
* }
* },
* katexOptions: {
* displayMode: false,
* throwOnError: false,
* macros: {
* '\\RR': '\\mathbb{R}',
* '\\ZZ': '\\mathbb{Z}'
* }
* }
* })
* ],
* content: `
* <p>Inline math: $E = mc^2$</p>
* <div data-type="block-math" data-latex="\\sum_{i=1}^{n} x_i = X"></div>
* `,
* onCreate({ editor }) {
* // Optional: Migrate existing math strings to math nodes
* migrateMathStrings(editor)
* }
* })
* ```
*/
export const Mathematics = Extension.create<MathematicsOptions>({
name: 'Mathematics',
addOptions() {
return {
inlineOptions: undefined,
blockOptions: undefined,
katexOptions: undefined,
}
},
addExtensions() {
return [
BlockMath.configure({
...this.options.blockOptions,
katexOptions: this.options.katexOptions,
}),
InlineMath.configure({
...this.options.inlineOptions,
katexOptions: this.options.katexOptions,
}),
]
},
})
export default Mathematics

View File

@@ -0,0 +1,24 @@
import type { Editor } from '@tiptap/core'
import type { KatexOptions } from 'katex'
import type { BlockMathOptions, InlineMathOptions } from './extensions/index.js'
/**
* Configuration options for the Mathematics extension.
* This type defines the available customization options for both inline and block math rendering.
*/
export type MathematicsOptions = {
/** Configuration options specific to inline math nodes */
inlineOptions?: Omit<InlineMathOptions, 'katexOptions'>
/** Configuration options specific to block math nodes */
blockOptions?: Omit<BlockMathOptions, 'katexOptions'>
/** KaTeX-specific rendering options passed to the KaTeX library */
katexOptions?: KatexOptions
}
/**
* Extended mathematics options that include an editor instance.
* This type combines the base mathematics options with an editor reference,
* typically used internally by the extension for operations that require editor access.
*/
export type MathematicsOptionsWithEditor = MathematicsOptions & { editor: Editor }

View File

@@ -0,0 +1,105 @@
import type { Editor } from '@tiptap/core'
import type { Transaction } from '@tiptap/pm/state'
/**
* Regular expression to match LaTeX math strings wrapped in single dollar signs.
* This should not catch dollar signs which are not part of a math expression,
* like those used for currency or other purposes.
* It ensures that the dollar signs are not preceded or followed by digits,
* allowing for proper identification of inline math expressions.
*
* - `$x^2 + y^2 = z^2$` will match
* - `This is $inline math$ in text.` will match
* - `This is $100$ dollars.` will not match (as it is not a math expression)
* - `This is $x^2 + y^2 = z^2$ and $100$ dollars.` will match both math expressions
*/
export const mathMigrationRegex = /\$(?!\d+\$)(.+?)\$(?!\d)/g
/**
* Creates a transaction that migrates existing math strings in the document to new math nodes.
* This function traverses the document and replaces LaTeX math syntax (wrapped in single dollar signs)
* with proper inline math nodes, preserving the mathematical content.
*
* @param editor - The editor instance containing the schema and configuration
* @param tr - The transaction to modify with the migration operations
* @returns The modified transaction with math string replacements
*
* @example
* ```typescript
* const editor = new Editor({ ... })
* const tr = editor.state.tr
* const updatedTr = createMathMigrateTransaction(editor, tr)
* editor.view.dispatch(updatedTr)
* ```
*/
export function createMathMigrateTransaction(
editor: Editor,
tr: Transaction,
regex: RegExp = mathMigrationRegex,
) {
// we traverse the document and replace all math nodes with the new math nodes
tr.doc.descendants((node, pos) => {
if (!node.isText || !node.text || !node.text.includes('$')) {
return
}
const { text } = node
const match = node.text.match(regex)
if (!match) {
return
}
match.forEach(mathMatch => {
const start = text.indexOf(mathMatch)
const end = start + mathMatch.length
const from = tr.mapping.map(pos + start)
const $from = tr.doc.resolve(from)
const parent = $from.parent
const index = $from.index()
const { inlineMath } = editor.schema.nodes
if (!parent.canReplaceWith(index, index + 1, inlineMath)) {
return
}
// Replace the math syntax with a new math node
tr.replaceWith(
tr.mapping.map(pos + start),
tr.mapping.map(pos + end),
inlineMath.create({ latex: mathMatch.slice(1, -1) }),
)
})
})
// don't add to history
tr.setMeta('addToHistory', false)
return tr
}
/**
* Migrates existing math strings in the editor document to math nodes.
* This function creates and dispatches a transaction that converts LaTeX math syntax
* (text wrapped in single dollar signs) into proper inline math nodes. The migration
* happens immediately and is not added to the editor's history.
*
* @param editor - The editor instance to perform the migration on
*
* @example
* ```typescript
* const editor = new Editor({
* extensions: [Mathematics],
* content: 'This is inline math: $x^2 + y^2 = z^2$ in text.'
* })
*
* // Math strings will be automatically migrated to math nodes
* migrateMathStrings(editor)
* ```
*/
export function migrateMathStrings(editor: Editor, regex: RegExp = mathMigrationRegex) {
const tr = createMathMigrateTransaction(editor, editor.state.tr, regex)
editor.view.dispatch(tr)
}