Skip to content

Commit f86561e

Browse files
authored
Merge pull request #8367 from nextcloud/backport/8342/stable33
[stable33] chore(cleanup): replace markdownit-katex with @mdit/tex
2 parents 0e2d9c9 + 2b6f3b9 commit f86561e

10 files changed

Lines changed: 136 additions & 109 deletions

File tree

package-lock.json

Lines changed: 36 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
"extends @nextcloud/browserslist-config"
3030
],
3131
"dependencies": {
32-
"@iktakahiro/markdown-it-katex": "^4.0.1",
3332
"@mdi/svg": "^7.4.47",
33+
"@mdit/plugin-tex": "^0.24.1",
3434
"@nextcloud/auth": "^2.5.3",
3535
"@nextcloud/axios": "^2.5.2",
3636
"@nextcloud/browser-storage": "^0.5.0",
@@ -44,7 +44,7 @@
4444
"@nextcloud/notify_push": "^1.3.1",
4545
"@nextcloud/router": "^3.1.0",
4646
"@nextcloud/sharing": "^0.4.0",
47-
"@nextcloud/vue": "^8.35.2",
47+
"@nextcloud/vue": "^8.37.0",
4848
"@quartzy/markdown-it-mentions": "^0.2.0",
4949
"@tiptap/core": "^3.15.3",
5050
"@tiptap/extension-blockquote": "^3.15.3",
@@ -118,7 +118,7 @@
118118
"@vue/test-utils": "^1.3.0 <2",
119119
"@vue/tsconfig": "^0.5.1",
120120
"@vueuse/core": "^11.3.0",
121-
"cypress": "^15.9.0",
121+
"cypress": "^15.12.0",
122122
"cypress-split": "^1.24.28",
123123
"cypress-vite": "^1.8.0",
124124
"eslint-config-prettier": "^10.1.8",

src/markdownit/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import callouts from './callouts.js'
1313
import details from './details.ts'
1414
import hardbreak from './hardbreak.js'
1515
import keepSyntax from './keepSyntax.js'
16-
import mathematics from './mathematics.js'
16+
import mathematics from './mathematics.ts'
1717
import preview from './preview.js'
1818
import splitMixedLists from './splitMixedLists.js'
1919
import taskLists from './taskLists.ts'

src/markdownit/mathematics.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/markdownit/mathematics.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Turn tex expressions into divs and spans with data-latex set.
8+
*
9+
* These will then be picked up by the mathematics nodes.
10+
*/
11+
12+
import { tex } from '@mdit/plugin-tex'
13+
import type MarkdownIt from 'markdown-it'
14+
15+
const escapeHtml = (unsafe: string) =>
16+
unsafe
17+
.replace(/&/g, '&amp;')
18+
.replace(/</g, '&lt;')
19+
.replace(/>/g, '&gt;')
20+
.replace(/"/g, '&quot;')
21+
.replace(/'/g, '&#039;')
22+
23+
const renderBlock = (content: string) =>
24+
`<div data-type="block-math" data-latex="${escapeHtml(content)}" />`
25+
26+
const renderInline = (content: string) =>
27+
`<span data-type="inline-math" data-latex="${escapeHtml(content)}">${escapeHtml(content)}</span>`
28+
29+
const render = (content: string, displayMode: boolean) =>
30+
displayMode ? renderBlock(content) : renderInline(content)
31+
32+
export default (md: MarkdownIt) => md.use(tex, { render })

src/nodes/Mathematics.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,6 @@ const MathInline = TiptapInlineMath.extend({
1919
return [
2020
// Tiptap's default format
2121
{ tag: 'span[data-type="inline-math"]' },
22-
// KaTeX HTML from markdown-it
23-
{
24-
tag: 'span.katex',
25-
priority: 50,
26-
getAttrs: (element) => {
27-
// Extract LaTeX from annotation tag
28-
const annotation = element.querySelector('annotation')
29-
if (annotation) {
30-
return { latex: annotation.textContent.trim() }
31-
}
32-
return false
33-
},
34-
},
3522
]
3623
},
3724

@@ -63,19 +50,6 @@ const MathBlock = TiptapBlockMath.extend({
6350
return [
6451
// Tiptap's default format
6552
{ tag: 'div[data-type="block-math"]' },
66-
// KaTeX HTML from markdown-it
67-
{
68-
tag: 'p.katex-block',
69-
priority: 100,
70-
getAttrs: (element) => {
71-
// Extract LaTeX from annotation tag
72-
const annotation = element.querySelector('annotation')
73-
if (annotation) {
74-
return { latex: annotation.textContent.trim() }
75-
}
76-
return false
77-
},
78-
},
7953
]
8054
},
8155

src/nodes/MathematicsView.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
-->
55

66
<template>
7-
<NodeViewWrapper :as="isBlock ? 'div' : 'span'" :class="wrapperClass">
7+
<NodeViewWrapper
8+
:as="isBlock ? 'div' : 'span'"
9+
:data-type="isBlock ? 'block-math' : 'inline-math'"
10+
:data-latex="node.attrs.latex"
11+
:class="wrapperClass">
812
<span ref="mathEl" @click="onMathClick"></span>
913
<ShowMathModal
1014
v-if="showModal"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import MarkdownIt from 'markdown-it'
7+
import { describe, expect, it } from 'vitest'
8+
import mathematics from '../../markdownit/mathematics'
9+
import stripIndent from './stripIndent.js'
10+
11+
describe('mathematics extension', () => {
12+
it('renders inline math', () => {
13+
const rendered = MarkdownIt('commonmark')
14+
.use(mathematics)
15+
.render('Here is some math: $1 + 1 = 2$.')
16+
expect(stripIndent(rendered)).toBe(
17+
stripIndent(`
18+
<p>Here is some math:
19+
<span data-type="inline-math" data-latex="1 + 1 = 2">1 + 1 = 2</span>.</p>`),
20+
)
21+
})
22+
23+
it('renders block math', () => {
24+
const rendered = MarkdownIt('commonmark').use(mathematics).render(`
25+
Here is some more math:
26+
$$
27+
1 + 1 + 1 = 3
28+
$$
29+
.`)
30+
expect(stripIndent(rendered)).toBe(
31+
stripIndent(`
32+
<p>Here is some more math:</p>
33+
<div data-type="block-math" data-latex="1 + 1 + 1 = 3" />
34+
<p>.</p>`),
35+
)
36+
})
37+
38+
it('escapes html', () => {
39+
const rendered = MarkdownIt('commonmark')
40+
.use(mathematics)
41+
.render('Here is some html: $<div>aaargh</div>$.')
42+
expect(stripIndent(rendered)).toBe(
43+
stripIndent(`
44+
<p>Here is some html:
45+
<span data-type="inline-math" data-latex="&lt;div&gt;aaargh&lt;/div&gt;">&lt;div&gt;aaargh&lt;/div&gt;</span>.</p>`),
46+
)
47+
})
48+
})

src/tests/nodes/Mathematics.spec.js

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ import { createRichEditor } from '../../EditorFactory.js'
99
import { createMarkdownSerializer } from '../../extensions/Markdown.js'
1010
import markdownit from '../../markdownit/index.js'
1111
import { MathBlock, MathInline } from '../../nodes/Mathematics.js'
12-
import {
13-
markdownThroughEditor,
14-
markdownThroughEditorHtml,
15-
} from '../testHelpers/markdown.js'
12+
import { markdownThroughEditor } from '../testHelpers/markdown.js'
1613

1714
const test = baseTest.extend({
1815
editor: async ({ task: _ }, use) => {
@@ -81,20 +78,6 @@ describe('Mathematics nodes', () => {
8178
})
8279
})
8380

84-
describe('HTML parsing - Pasted KaTeX content', () => {
85-
test('inline katex HTML to markdown', () => {
86-
const html =
87-
'<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span></span></p>'
88-
expect(markdownThroughEditorHtml(html)).toBe('$E=mc^2$')
89-
})
90-
91-
test('block katex HTML to markdown', () => {
92-
const html =
93-
'<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2\n</annotation></semantics></math></span></span></span></p>'
94-
expect(markdownThroughEditorHtml(html)).toBe('$$\nE=mc^2\n$$')
95-
})
96-
})
97-
9881
describe('Menu commands', () => {
9982
test('insertMathInline with empty selection', ({ editor }) => {
10083
// Insert empty inline math
@@ -156,28 +139,30 @@ describe('Mathematics nodes', () => {
156139
})
157140

158141
describe('Markdown-it rendering', () => {
159-
test('renders inline math to katex HTML', () => {
142+
test('renders inline math to HTML span element', () => {
160143
const rendered = markdownit.render('$E=mc^2$')
161-
expect(rendered).toContain('katex')
144+
expect(rendered).toContain('span')
145+
expect(rendered).toContain('inline-math')
162146
expect(rendered).toContain('E=mc^2')
163147
})
164148

165-
test('renders block math to katex HTML', () => {
149+
test('renders block math to HTML div element', () => {
166150
const rendered = markdownit.render('$$\nE=mc^2\n$$')
167-
expect(rendered).toContain('katex-block')
168-
expect(rendered).toContain('katex-display')
151+
expect(rendered).toContain('div')
152+
expect(rendered).toContain('block-math')
169153
expect(rendered).toContain('E=mc^2')
170154
})
171155
})
172156

173157
describe('Serialization to markdown', () => {
174158
test('serializes inline math node', ({ editor }) => {
175159
editor.commands.insertInlineMath({ latex: 'E=mc^2' })
160+
editor.commands.insertContent(' some more text.')
176161

177162
const serializer = createMarkdownSerializer(editor.schema)
178163
const markdown = serializer.serialize(editor.state.doc)
179164

180-
expect(markdown).toBe('$E=mc^2$')
165+
expect(markdown).toBe('$E=mc^2$ some more text.')
181166
})
182167

183168
test('serializes block math node', ({ editor }) => {

0 commit comments

Comments
 (0)