Skip to content

Commit

Permalink
Change to replace wrapping elements
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
wooorm committed Sep 19, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 4223ed9 commit f173346
Showing 20 changed files with 304 additions and 105 deletions.
59 changes: 44 additions & 15 deletions packages/rehype-katex/lib/index.js
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
import {fromHtmlIsomorphic} from 'hast-util-from-html-isomorphic'
import {toText} from 'hast-util-to-text'
import katex from 'katex'
import {visit} from 'unist-util-visit'
import {SKIP, visitParents} from 'unist-util-visit-parents'

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

if (!inline && !displayMode) {
// Any class is fine.
if (!languageMath && !mathDisplay && !mathInline) {
return
}

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

/** @type {string} */
// If this was generated with ` ```math `, replace the `<pre>` and use
// display.
if (
element.tagName === 'code' &&
languageMath &&
parent &&
parent.type === 'element' &&
parent.tagName === 'pre'
) {
scope = parent
parent = parents[parents.length - 2]
displayMode = true
}

/* c8 ignore next -- verbose to test. */
if (!parent) return

const value = toText(scope, {whitespace: 'pre'})

/** @type {Array<ElementContent> | string | undefined} */
let result

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

file.message('Could not render math with KaTeX', {
/* c8 ignore next -- verbose to test */
ancestors: parent ? [parent, element] : [element],
ancestors: [...parents, element],
cause,
place: element.position,
ruleId,
@@ -91,7 +116,7 @@ export default function rehypeKatex(options) {
// Generate similar markup if this is an other error.
// See: <https://github.com/KaTeX/KaTeX/blob/5dc7af0/docs/error.md>.
else {
element.children = [
result = [
{
type: 'element',
tagName: 'span',
@@ -103,14 +128,18 @@ export default function rehypeKatex(options) {
children: [{type: 'text', value}]
}
]
return
}
}

const root = fromHtmlIsomorphic(result, {fragment: true})
// Cast because there will not be `doctypes` in KaTeX result.
const content = /** @type {Array<ElementContent>} */ (root.children)
element.children = content
if (typeof result === 'string') {
const root = fromHtmlIsomorphic(result, {fragment: true})
// Cast as we don’t expect `doctypes` in KaTeX result.
result = /** @type {Array<ElementContent>} */ (root.children)
}

const index = parent.children.indexOf(scope)
parent.children.splice(index, 1, ...result)
return SKIP
})
}
}
7 changes: 5 additions & 2 deletions packages/rehype-katex/package.json
Original file line number Diff line number Diff line change
@@ -43,14 +43,17 @@
"hast-util-from-html-isomorphic": "^2.0.0",
"hast-util-to-text": "^4.0.0",
"katex": "^0.16.0",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.0",
"vfile": "^6.0.0"
},
"scripts": {
"test-api": "node --conditions development test.js",
"test": "npm run build && npm run test-api"
},
"xo": {
"prettier": true
"prettier": true,
"rules": {
"unicorn/prefer-at": "off"
}
}
}
84 changes: 44 additions & 40 deletions packages/rehype-katex/test.js
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ test('rehype-katex', async function (t) {
.use(rehypeStringify)
.process(
[
'<p>Inline math <code class="math-inline">\\alpha</code>.</p>',
'<p>Inline math <span class="math-inline">\\alpha</span>.</p>',
'<p>Block math:</p>',
'<div class="math-display">\\gamma</div>'
].join('\n')
@@ -37,19 +37,35 @@ test('rehype-katex', async function (t) {
.use(rehypeStringify)
.process(
[
'<p>Inline math <code class="math-inline">' +
katex.renderToString('\\alpha') +
'</code>.</p>',
'<p>Inline math ' + katex.renderToString('\\alpha') + '.</p>',
'<p>Block math:</p>',
'<div class="math-display">' +
katex.renderToString('\\gamma', {displayMode: true}) +
'</div>'
katex.renderToString('\\gamma', {displayMode: true})
].join('\n')
)
)
)
})

await t.test('should support markdown fenced code', async function () {
assert.deepEqual(
String(
await unified()
.use(remarkParse)
// @ts-expect-error: to do: remove when `remark-rehype` is released.
.use(remarkRehype)
.use(rehypeKatex)
.use(rehypeStringify)
.process('```math\n\\gamma\n```')
),
String(
await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(katex.renderToString('\\gamma\n', {displayMode: true}))
)
)
})

await t.test('should integrate with `remark-math`', async function () {
assert.deepEqual(
String(
@@ -78,13 +94,9 @@ test('rehype-katex', async function (t) {
.use(rehypeStringify)
.process(
[
'<p>Inline math <code class="language-math math-inline">' +
katex.renderToString('\\alpha') +
'</code>.</p>',
'<p>Inline math ' + katex.renderToString('\\alpha') + '.</p>',
'<p>Block math:</p>',
'<pre><code class="language-math math-display">' +
katex.renderToString('\\gamma', {displayMode: true}) +
'</code></pre>'
katex.renderToString('\\gamma', {displayMode: true})
].join('\n')
)
)
@@ -109,9 +121,9 @@ test('rehype-katex', async function (t) {
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<p>Double math <code class="math-inline math-display">' +
'<p>Double math ' +
katex.renderToString('\\alpha', {displayMode: true}) +
'</code>.</p>'
'.</p>'
)
)
)
@@ -127,17 +139,13 @@ test('rehype-katex', async function (t) {
.use(rehypeParse, {fragment: true})
.use(rehypeKatex, {macros})
.use(rehypeStringify)
.process('<code class="math-inline">\\RR</code>')
.process('<span class="math-inline">\\RR</span>')
),
String(
await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<code class="math-inline">' +
katex.renderToString('\\RR', {macros}) +
'</code>'
)
.process(katex.renderToString('\\RR', {macros}))
)
)
})
@@ -147,7 +155,7 @@ test('rehype-katex', async function (t) {
.use(rehypeParse, {fragment: true})
.use(rehypeKatex, {errorColor: 'orange'})
.use(rehypeStringify)
.process('<code class="math-inline">\\alpa</code>')
.process('<span class="math-inline">\\alpa</span>')

assert.deepEqual(
String(file),
@@ -156,12 +164,10 @@ test('rehype-katex', async function (t) {
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<code class="math-inline">' +
katex.renderToString('\\alpa', {
errorColor: 'orange',
throwOnError: false
}) +
'</code>'
katex.renderToString('\\alpa', {
errorColor: 'orange',
throwOnError: false
})
)
)
)
@@ -170,11 +176,11 @@ test('rehype-katex', async function (t) {
const message = file.messages[0]
assert(message)
assert(message.cause)
assert(message.ancestors)
assert.match(
String(message.cause),
/KaTeX parse error: Undefined control sequence/
)
assert(message.ancestors)
assert.equal(message.ancestors.length, 2)
assert.deepEqual(
{...file.messages[0], cause: undefined, ancestors: []},
@@ -204,14 +210,14 @@ test('rehype-katex', async function (t) {
.use(rehypeParse, {fragment: true})
.use(rehypeKatex, {errorColor: 'orange', strict: 'ignore'})
.use(rehypeStringify)
.process('<code class="math-inline">ê&amp;</code>')
.process('<span class="math-inline">ê&amp;</span>')
),
String(
await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<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>'
'<span class="katex-error" title="ParseError: KaTeX parse error: Expected \'EOF\', got \'&\' at position 2: ê&̲" style="color:orange">ê&amp;</span>'
)
)
)
@@ -225,20 +231,18 @@ test('rehype-katex', async function (t) {
.use(rehypeKatex, {errorColor: 'orange', strict: 'ignore'})
.use(rehypeStringify)
.process(
'<div class="math math-display">\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}</div>'
'<div class="math-display">\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}</div>'
)
),
String(
await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<div class="math math-display">' +
katex.renderToString(
'\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}',
{displayMode: true}
) +
'</div>'
katex.renderToString(
'\\begin{split}\n f(-2) &= \\sqrt{-2+4} \\\\\n &= x % Test Comment\n\\end{split}',
{displayMode: true}
)
)
)
)
@@ -252,15 +256,15 @@ test('rehype-katex', async function (t) {
.use(rehypeKatex)
.use(rehypeStringify)
.process(
'<code class="math-display">\\begin{split}\n\\end{{split}}\n</code>'
'<span class="math-display">\\begin{split}\n\\end{{split}}\n</span>'
)
),
String(
await unified()
.use(rehypeParse, {fragment: true})
.use(rehypeStringify)
.process(
'<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>'
'<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>'
)
)
)
5 changes: 2 additions & 3 deletions packages/rehype-mathjax/lib/browser.js
Original file line number Diff line number Diff line change
@@ -13,10 +13,9 @@ const rehypeMathJaxBrowser = createPlugin(function (options) {
const inline = tex.inlineMath || [['\\(', '\\)']]

return {
render(node, options) {
render(value, options) {
const delimiters = (options.display ? display : inline)[0]
node.children.unshift({type: 'text', value: delimiters[0]})
node.children.push({type: 'text', value: delimiters[1]})
return [{type: 'text', value: delimiters[0] + value + delimiters[1]}]
}
}
})
Loading

0 comments on commit f173346

Please sign in to comment.