site/node_modules/rehype-katex/lib/index.js
2024-10-14 08:09:33 +02:00

146 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @typedef {import('hast').ElementContent} ElementContent
* @typedef {import('hast').Root} Root
*
* @typedef {import('katex').KatexOptions} KatexOptions
*
* @typedef {import('vfile').VFile} VFile
*/
/**
* @typedef {Omit<KatexOptions, 'displayMode' | 'throwOnError'>} Options
*/
import {fromHtmlIsomorphic} from 'hast-util-from-html-isomorphic'
import {toText} from 'hast-util-to-text'
import katex from 'katex'
import {SKIP, visitParents} from 'unist-util-visit-parents'
/** @type {Readonly<Options>} */
const emptyOptions = {}
/** @type {ReadonlyArray<unknown>} */
const emptyClasses = []
/**
* Render elements with a `language-math` (or `math-display`, `math-inline`)
* class with KaTeX.
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
export default function rehypeKatex(options) {
const settings = options || emptyOptions
/**
* Transform.
*
* @param {Root} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return function (tree, file) {
visitParents(tree, 'element', function (element, parents) {
const classes = Array.isArray(element.properties.className)
? element.properties.className
: emptyClasses
// 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
// Any class is fine.
if (!languageMath && !mathDisplay && !mathInline) {
return
}
let parent = parents[parents.length - 1]
let scope = element
// 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 {
result = katex.renderToString(value, {
...settings,
displayMode,
throwOnError: true
})
} catch (error) {
const cause = /** @type {Error} */ (error)
const ruleId = cause.name.toLowerCase()
file.message('Could not render math with KaTeX', {
ancestors: [...parents, element],
cause,
place: element.position,
ruleId,
source: 'rehype-katex'
})
// KaTeX can handle `ParseError` itself, but not others.
if (ruleId === 'parseerror') {
result = katex.renderToString(value, {
...settings,
displayMode,
strict: 'ignore',
throwOnError: false
})
}
// Generate similar markup if this is an other error.
// See: <https://github.com/KaTeX/KaTeX/blob/5dc7af0/docs/error.md>.
else {
result = [
{
type: 'element',
tagName: 'span',
properties: {
className: ['katex-error'],
style: 'color:' + (settings.errorColor || '#cc0000'),
title: String(error)
},
children: [{type: 'text', value}]
}
]
}
}
if (typeof result === 'string') {
const root = fromHtmlIsomorphic(result, {fragment: true})
// Cast as we dont 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
})
}
}