Skip to content

Commit f173346

Browse files
committed
Change to replace wrapping elements
Previously, spans and divs were generated by `remark-math`. They were left as-is by `rehype-katex` and `rehype-mathjax`. With fc32531, (`<pre>` and) `<code>` elements are generated by `remark-math`. That is to allow folks to use normal markdown, as in ` ```math `, to generate math blocks. However, `<span>` and `<div>` do not contribute to how a document is displayed by browsers, but `<pre>` and `<code>` do. To solve that, this commit *replaces* those elements instead of changing their contents, when using `rehype-katex` or `rehype-mathjax`.
1 parent 4223ed9 commit f173346

20 files changed

+304
-105
lines changed

packages/rehype-katex/lib/index.js

+44-15
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import {fromHtmlIsomorphic} from 'hast-util-from-html-isomorphic'
1515
import {toText} from 'hast-util-to-text'
1616
import katex from 'katex'
17-
import {visit} from 'unist-util-visit'
17+
import {SKIP, visitParents} from 'unist-util-visit-parents'
1818

1919
/** @type {Readonly<Options>} */
2020
const emptyOptions = {}
@@ -44,20 +44,46 @@ export default function rehypeKatex(options) {
4444
* Nothing.
4545
*/
4646
return function (tree, file) {
47-
visit(tree, 'element', function (element, _, parent) {
47+
visitParents(tree, 'element', function (element, parents) {
4848
const classes = Array.isArray(element.properties.className)
4949
? element.properties.className
5050
: emptyClasses
51-
const inline = classes.includes('math-inline')
52-
const displayMode = classes.includes('math-display')
51+
// This class can be generated from markdown with ` ```math `.
52+
const languageMath = classes.includes('language-math')
53+
// This class is used by `remark-math` for flow math (block, `$$\nmath\n$$`).
54+
const mathDisplay = classes.includes('math-display')
55+
// This class is used by `remark-math` for text math (inline, `$math$`).
56+
const mathInline = classes.includes('math-inline')
57+
let displayMode = mathDisplay
5358

54-
if (!inline && !displayMode) {
59+
// Any class is fine.
60+
if (!languageMath && !mathDisplay && !mathInline) {
5561
return
5662
}
5763

58-
const value = toText(element, {whitespace: 'pre'})
64+
let parent = parents[parents.length - 1]
65+
let scope = element
5966

60-
/** @type {string} */
67+
// If this was generated with ` ```math `, replace the `<pre>` and use
68+
// display.
69+
if (
70+
element.tagName === 'code' &&
71+
languageMath &&
72+
parent &&
73+
parent.type === 'element' &&
74+
parent.tagName === 'pre'
75+
) {
76+
scope = parent
77+
parent = parents[parents.length - 2]
78+
displayMode = true
79+
}
80+
81+
/* c8 ignore next -- verbose to test. */
82+
if (!parent) return
83+
84+
const value = toText(scope, {whitespace: 'pre'})
85+
86+
/** @type {Array<ElementContent> | string | undefined} */
6187
let result
6288

6389
try {
@@ -71,8 +97,7 @@ export default function rehypeKatex(options) {
7197
const ruleId = cause.name.toLowerCase()
7298

7399
file.message('Could not render math with KaTeX', {
74-
/* c8 ignore next -- verbose to test */
75-
ancestors: parent ? [parent, element] : [element],
100+
ancestors: [...parents, element],
76101
cause,
77102
place: element.position,
78103
ruleId,
@@ -91,7 +116,7 @@ export default function rehypeKatex(options) {
91116
// Generate similar markup if this is an other error.
92117
// See: <https://github.com/KaTeX/KaTeX/blob/5dc7af0/docs/error.md>.
93118
else {
94-
element.children = [
119+
result = [
95120
{
96121
type: 'element',
97122
tagName: 'span',
@@ -103,14 +128,18 @@ export default function rehypeKatex(options) {
103128
children: [{type: 'text', value}]
104129
}
105130
]
106-
return
107131
}
108132
}
109133

110-
const root = fromHtmlIsomorphic(result, {fragment: true})
111-
// Cast because there will not be `doctypes` in KaTeX result.
112-
const content = /** @type {Array<ElementContent>} */ (root.children)
113-
element.children = content
134+
if (typeof result === 'string') {
135+
const root = fromHtmlIsomorphic(result, {fragment: true})
136+
// Cast as we don’t expect `doctypes` in KaTeX result.
137+
result = /** @type {Array<ElementContent>} */ (root.children)
138+
}
139+
140+
const index = parent.children.indexOf(scope)
141+
parent.children.splice(index, 1, ...result)
142+
return SKIP
114143
})
115144
}
116145
}

packages/rehype-katex/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,17 @@
4343
"hast-util-from-html-isomorphic": "^2.0.0",
4444
"hast-util-to-text": "^4.0.0",
4545
"katex": "^0.16.0",
46-
"unist-util-visit": "^5.0.0",
46+
"unist-util-visit-parents": "^6.0.0",
4747
"vfile": "^6.0.0"
4848
},
4949
"scripts": {
5050
"test-api": "node --conditions development test.js",
5151
"test": "npm run build && npm run test-api"
5252
},
5353
"xo": {
54-
"prettier": true
54+
"prettier": true,
55+
"rules": {
56+
"unicorn/prefer-at": "off"
57+
}
5558
}
5659
}

packages/rehype-katex/test.js

+44-40
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ test('rehype-katex', async function (t) {
2525
.use(rehypeStringify)
2626
.process(
2727
[
28-
'<p>Inline math <code class="math-inline">\\alpha</code>.</p>',
28+
'<p>Inline math <span class="math-inline">\\alpha</span>.</p>',
2929
'<p>Block math:</p>',
3030
'<div class="math-display">\\gamma</div>'
3131
].join('\n')
@@ -37,19 +37,35 @@ test('rehype-katex', async function (t) {
3737
.use(rehypeStringify)
3838
.process(
3939
[
40-
'<p>Inline math <code class="math-inline">' +
41-
katex.renderToString('\\alpha') +
42-
'</code>.</p>',
40+
'<p>Inline math ' + katex.renderToString('\\alpha') + '.</p>',
4341
'<p>Block math:</p>',
44-
'<div class="math-display">' +
45-
katex.renderToString('\\gamma', {displayMode: true}) +
46-
'</div>'
42+
katex.renderToString('\\gamma', {displayMode: true})
4743
].join('\n')
4844
)
4945
)
5046
)
5147
})
5248

49+
await t.test('should support markdown fenced code', async function () {
50+
assert.deepEqual(
51+
String(
52+
await unified()
53+
.use(remarkParse)
54+
// @ts-expect-error: to do: remove when `remark-rehype` is released.
55+
.use(remarkRehype)
56+
.use(rehypeKatex)
57+
.use(rehypeStringify)
58+
.process('```math\n\\gamma\n```')
59+
),
60+
String(
61+
await unified()
62+
.use(rehypeParse, {fragment: true})
63+
.use(rehypeStringify)
64+
.process(katex.renderToString('\\gamma\n', {displayMode: true}))
65+
)
66+
)
67+
})
68+
5369
await t.test('should integrate with `remark-math`', async function () {
5470
assert.deepEqual(
5571
String(
@@ -78,13 +94,9 @@ test('rehype-katex', async function (t) {
7894
.use(rehypeStringify)
7995
.process(
8096
[
81-
'<p>Inline math <code class="language-math math-inline">' +
82-
katex.renderToString('\\alpha') +
83-
'</code>.</p>',
97+
'<p>Inline math ' + katex.renderToString('\\alpha') + '.</p>',
8498
'<p>Block math:</p>',
85-
'<pre><code class="language-math math-display">' +
86-
katex.renderToString('\\gamma', {displayMode: true}) +
87-
'</code></pre>'
99+
katex.renderToString('\\gamma', {displayMode: true})
88100
].join('\n')
89101
)
90102
)
@@ -109,9 +121,9 @@ test('rehype-katex', async function (t) {
109121
.use(rehypeParse, {fragment: true})
110122
.use(rehypeStringify)
111123
.process(
112-
'<p>Double math <code class="math-inline math-display">' +
124+
'<p>Double math ' +
113125
katex.renderToString('\\alpha', {displayMode: true}) +
114-
'</code>.</p>'
126+
'.</p>'
115127
)
116128
)
117129
)
@@ -127,17 +139,13 @@ test('rehype-katex', async function (t) {
127139
.use(rehypeParse, {fragment: true})
128140
.use(rehypeKatex, {macros})
129141
.use(rehypeStringify)
130-
.process('<code class="math-inline">\\RR</code>')
142+
.process('<span class="math-inline">\\RR</span>')
131143
),
132144
String(
133145
await unified()
134146
.use(rehypeParse, {fragment: true})
135147
.use(rehypeStringify)
136-
.process(
137-
'<code class="math-inline">' +
138-
katex.renderToString('\\RR', {macros}) +
139-
'</code>'
140-
)
148+
.process(katex.renderToString('\\RR', {macros}))
141149
)
142150
)
143151
})
@@ -147,7 +155,7 @@ test('rehype-katex', async function (t) {
147155
.use(rehypeParse, {fragment: true})
148156
.use(rehypeKatex, {errorColor: 'orange'})
149157
.use(rehypeStringify)
150-
.process('<code class="math-inline">\\alpa</code>')
158+
.process('<span class="math-inline">\\alpa</span>')
151159

152160
assert.deepEqual(
153161
String(file),
@@ -156,12 +164,10 @@ test('rehype-katex', async function (t) {
156164
.use(rehypeParse, {fragment: true})
157165
.use(rehypeStringify)
158166
.process(
159-
'<code class="math-inline">' +
160-
katex.renderToString('\\alpa', {
161-
errorColor: 'orange',
162-
throwOnError: false
163-
}) +
164-
'</code>'
167+
katex.renderToString('\\alpa', {
168+
errorColor: 'orange',
169+
throwOnError: false
170+
})
165171
)
166172
)
167173
)
@@ -170,11 +176,11 @@ test('rehype-katex', async function (t) {
170176
const message = file.messages[0]
171177
assert(message)
172178
assert(message.cause)
173-
assert(message.ancestors)
174179
assert.match(
175180
String(message.cause),
176181
/KaTeX parse error: Undefined control sequence/
177182
)
183+
assert(message.ancestors)
178184
assert.equal(message.ancestors.length, 2)
179185
assert.deepEqual(
180186
{...file.messages[0], cause: undefined, ancestors: []},
@@ -204,14 +210,14 @@ test('rehype-katex', async function (t) {
204210
.use(rehypeParse, {fragment: true})
205211
.use(rehypeKatex, {errorColor: 'orange', strict: 'ignore'})
206212
.use(rehypeStringify)
207-
.process('<code class="math-inline">ê&amp;</code>')
213+
.process('<span class="math-inline">ê&amp;</span>')
208214
),
209215
String(
210216
await unified()
211217
.use(rehypeParse, {fragment: true})
212218
.use(rehypeStringify)
213219
.process(
214-
'<code class="math-inline"><span class="katex-error" title="ParseError: KaTeX parse error: Expected \'EOF\', got \'&\' at position 2: ê&̲" style="color:orange">ê&amp;</span></code>'
220+
'<span class="katex-error" title="ParseError: KaTeX parse error: Expected \'EOF\', got \'&\' at position 2: ê&̲" style="color:orange">ê&amp;</span>'
215221
)
216222
)
217223
)
@@ -225,20 +231,18 @@ test('rehype-katex', async function (t) {
225231
.use(rehypeKatex, {errorColor: 'orange', strict: 'ignore'})
226232
.use(rehypeStringify)
227233
.process(
228-
'<div class="math math-display">\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}</div>'
234+
'<div class="math-display">\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}</div>'
229235
)
230236
),
231237
String(
232238
await unified()
233239
.use(rehypeParse, {fragment: true})
234240
.use(rehypeStringify)
235241
.process(
236-
'<div class="math math-display">' +
237-
katex.renderToString(
238-
'\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}',
239-
{displayMode: true}
240-
) +
241-
'</div>'
242+
katex.renderToString(
243+
'\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}',
244+
{displayMode: true}
245+
)
242246
)
243247
)
244248
)
@@ -252,15 +256,15 @@ test('rehype-katex', async function (t) {
252256
.use(rehypeKatex)
253257
.use(rehypeStringify)
254258
.process(
255-
'<code class="math-display">\\begin{split}\n\\end{{split}}\n</code>'
259+
'<span class="math-display">\\begin{split}\n\\end{{split}}\n</span>'
256260
)
257261
),
258262
String(
259263
await unified()
260264
.use(rehypeParse, {fragment: true})
261265
.use(rehypeStringify)
262266
.process(
263-
'<code class="math-display"><span class="katex-error" style="color:#cc0000" title="Error: Expected node of type textord, but got node of type ordgroup">\\begin{split}\n\\end{{split}}\n</span></code>'
267+
'<span class="katex-error" style="color:#cc0000" title="Error: Expected node of type textord, but got node of type ordgroup">\\begin{split}\n\\end{{split}}\n</span>'
264268
)
265269
)
266270
)

packages/rehype-mathjax/lib/browser.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ const rehypeMathJaxBrowser = createPlugin(function (options) {
1313
const inline = tex.inlineMath || [['\\(', '\\)']]
1414

1515
return {
16-
render(node, options) {
16+
render(value, options) {
1717
const delimiters = (options.display ? display : inline)[0]
18-
node.children.unshift({type: 'text', value: delimiters[0]})
19-
node.children.push({type: 'text', value: delimiters[1]})
18+
return [{type: 'text', value: delimiters[0] + value + delimiters[1]}]
2019
}
2120
}
2221
})

0 commit comments

Comments
 (0)