feat: use go tool

This commit is contained in:
2026-07-03 19:24:01 -04:00
parent 39548b4332
commit 4d0cab1fe0
1542 changed files with 252399 additions and 14 deletions
+61
View File
@@ -0,0 +1,61 @@
import type {KatexOptions} from 'katex'
export {mathHtml} from './lib/html.js'
export {math} from './lib/syntax.js'
/**
* Configuration for HTML output.
*
* > 👉 **Note**: passed to `katex.renderToString`.
* > `displayMode` is overwritten by this plugin, to `false` for math in
* > text (inline), and `true` for math in flow (block).
*/
export interface HtmlOptions extends KatexOptions {
/**
* The field `displayMode` cannot be passed to `micromark-extension-math`.
* It is overwritten by it,
* to `false` for math in text (inline) and `true` for math in flow (block).
*/
displayMode?: never
}
/**
* Configuration.
*/
export interface Options {
/**
* Whether to support math (text) with a single dollar (default: `true`).
*
* Single dollars work in Pandoc and many other places, but often interfere
* with “normal” dollars in text.
* If you turn this off, you can use two or more dollars for text math.
*/
singleDollarTextMath?: boolean | null | undefined
}
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* Compile data.
*/
interface CompileData {
mathFlowOpen?: boolean
}
/**
* Token types.
*/
interface TokenTypeMap {
mathFlow: 'mathFlow'
mathFlowFence: 'mathFlowFence'
mathFlowFenceMeta: 'mathFlowFenceMeta'
mathFlowFenceSequence: 'mathFlowFenceSequence'
mathFlowValue: 'mathFlowValue'
mathText: 'mathText'
mathTextData: 'mathTextData'
mathTextPadding: 'mathTextPadding'
mathTextSequence: 'mathTextSequence'
}
}
+3
View File
@@ -0,0 +1,3 @@
// Note: types exported from `index.d.ts`.
export {math} from './lib/syntax.js'
export {mathHtml} from './lib/html.js'
+15
View File
@@ -0,0 +1,15 @@
/**
* Create an extension for `micromark` to support math when serializing to
* HTML.
*
* > 👉 **Note**: this uses KaTeX to render math.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support math when serializing to HTML.
*/
export function mathHtml(options?: Options | null | undefined): HtmlExtension;
import type { HtmlOptions as Options } from 'micromark-extension-math';
import type { HtmlExtension } from 'micromark-util-types';
+82
View File
@@ -0,0 +1,82 @@
/**
* @import {HtmlOptions as Options} from 'micromark-extension-math'
* @import {HtmlExtension} from 'micromark-util-types'
*/
import katex from 'katex'
const renderToString = katex.renderToString
/**
* Create an extension for `micromark` to support math when serializing to
* HTML.
*
* > 👉 **Note**: this uses KaTeX to render math.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support math when serializing to HTML.
*/
export function mathHtml(options) {
return {
enter: {
mathFlow() {
this.lineEndingIfNeeded()
this.tag('<div class="math math-display">')
},
mathFlowFenceMeta() {
this.buffer()
},
mathText() {
// Double?
this.tag('<span class="math math-inline">')
this.buffer()
}
},
exit: {
mathFlow() {
const value = this.resume()
this.tag(math(value.replace(/(?:\r?\n|\r)$/, ''), true))
this.tag('</div>')
this.setData('mathFlowOpen')
this.setData('slurpOneLineEnding')
},
mathFlowFence() {
// After the first fence.
if (!this.getData('mathFlowOpen')) {
this.setData('mathFlowOpen', true)
this.setData('slurpOneLineEnding', true)
this.buffer()
}
},
mathFlowFenceMeta() {
this.resume()
},
mathFlowValue(token) {
this.raw(this.sliceSerialize(token))
},
mathText() {
const value = this.resume()
this.tag(math(value, false))
this.tag('</span>')
},
mathTextData(token) {
this.raw(this.sliceSerialize(token))
}
}
}
/**
* @param {string} value
* Math text.
* @param {boolean} displayMode
* Whether the math is in display mode.
* @returns {string}
* HTML.
*/
function math(value, displayMode) {
return renderToString(value, {...options, displayMode})
}
}
+3
View File
@@ -0,0 +1,3 @@
/** @type {Construct} */
export const mathFlow: Construct;
import type { Construct } from 'micromark-util-types';
+394
View File
@@ -0,0 +1,394 @@
/**
* @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes, constants, types} from 'micromark-util-symbol'
/** @type {Construct} */
export const mathFlow = {
tokenize: tokenizeMathFenced,
concrete: true,
name: 'mathFlow'
}
/** @type {Construct} */
const nonLazyContinuation = {
tokenize: tokenizeNonLazyContinuation,
partial: true
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeMathFenced(effects, ok, nok) {
const self = this
const tail = self.events[self.events.length - 1]
const initialSize =
tail && tail[1].type === types.linePrefix
? tail[2].sliceSerialize(tail[1], true).length
: 0
let sizeOpen = 0
return start
/**
* Start of math.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function start(code) {
assert(code === codes.dollarSign, 'expected `$`')
effects.enter('mathFlow')
effects.enter('mathFlowFence')
effects.enter('mathFlowFenceSequence')
return sequenceOpen(code)
}
/**
* In opening fence sequence.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === codes.dollarSign) {
effects.consume(code)
sizeOpen++
return sequenceOpen
}
if (sizeOpen < 2) {
return nok(code)
}
effects.exit('mathFlowFenceSequence')
return factorySpace(effects, metaBefore, types.whitespace)(code)
}
/**
* In opening fence, before meta.
*
* ```markdown
* > | $$asciimath
* ^
* | x < y
* | $$
* ```
*
* @type {State}
*/
function metaBefore(code) {
if (code === codes.eof || markdownLineEnding(code)) {
return metaAfter(code)
}
effects.enter('mathFlowFenceMeta')
effects.enter(types.chunkString, {contentType: constants.contentTypeString})
return meta(code)
}
/**
* In meta.
*
* ```markdown
* > | $$asciimath
* ^
* | x < y
* | $$
* ```
*
* @type {State}
*/
function meta(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit(types.chunkString)
effects.exit('mathFlowFenceMeta')
return metaAfter(code)
}
if (code === codes.dollarSign) {
return nok(code)
}
effects.consume(code)
return meta
}
/**
* After meta.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function metaAfter(code) {
// Guaranteed to be eol/eof.
effects.exit('mathFlowFence')
if (self.interrupt) {
return ok(code)
}
return effects.attempt(
nonLazyContinuation,
beforeNonLazyContinuation,
after
)(code)
}
/**
* After eol/eof in math, at a non-lazy closing fence or content.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* > | $$
* ^
* ```
*
* @type {State}
*/
function beforeNonLazyContinuation(code) {
return effects.attempt(
{tokenize: tokenizeClosingFence, partial: true},
after,
contentStart
)(code)
}
/**
* Before math content, definitely not before a closing fence.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function contentStart(code) {
return (
initialSize
? factorySpace(
effects,
beforeContentChunk,
types.linePrefix,
initialSize + 1
)
: beforeContentChunk
)(code)
}
/**
* Before math content, after optional prefix.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function beforeContentChunk(code) {
if (code === codes.eof) {
return after(code)
}
if (markdownLineEnding(code)) {
return effects.attempt(
nonLazyContinuation,
beforeNonLazyContinuation,
after
)(code)
}
effects.enter('mathFlowValue')
return contentChunk(code)
}
/**
* In math content.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function contentChunk(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('mathFlowValue')
return beforeContentChunk(code)
}
effects.consume(code)
return contentChunk
}
/**
* After math (ha!).
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function after(code) {
effects.exit('mathFlow')
return ok(code)
}
/** @type {Tokenizer} */
function tokenizeClosingFence(effects, ok, nok) {
let size = 0
assert(self.parser.constructs.disable.null, 'expected `disable.null`')
/**
* Before closing fence, at optional whitespace.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*/
return factorySpace(
effects,
beforeSequenceClose,
types.linePrefix,
self.parser.constructs.disable.null.includes('codeIndented')
? undefined
: constants.tabSize
)
/**
* In closing fence, after optional whitespace, at sequence.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function beforeSequenceClose(code) {
effects.enter('mathFlowFence')
effects.enter('mathFlowFenceSequence')
return sequenceClose(code)
}
/**
* In closing fence sequence.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
if (code === codes.dollarSign) {
size++
effects.consume(code)
return sequenceClose
}
if (size < sizeOpen) {
return nok(code)
}
effects.exit('mathFlowFenceSequence')
return factorySpace(effects, afterSequenceClose, types.whitespace)(code)
}
/**
* After closing fence sequence, after optional whitespace.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function afterSequenceClose(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('mathFlowFence')
return ok(code)
}
return nok(code)
}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeNonLazyContinuation(effects, ok, nok) {
const self = this
return start
/** @type {State} */
function start(code) {
if (code === null) {
return ok(code)
}
assert(markdownLineEnding(code), 'expected eol')
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return lineStart
}
/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code)
}
}
+9
View File
@@ -0,0 +1,9 @@
/**
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Construct}
* Construct.
*/
export function mathText(options?: Options | null | undefined): Construct;
import type { Options } from 'micromark-extension-math';
import type { Construct } from 'micromark-util-types';
+267
View File
@@ -0,0 +1,267 @@
/**
* @import {Options} from 'micromark-extension-math'
* @import {Construct, Previous, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
// To do: next major: clean spaces in HTML compiler.
// This has to be coordinated together with `mdast-util-math`.
import {ok as assert} from 'devlop'
import {markdownLineEnding} from 'micromark-util-character'
import {codes, types} from 'micromark-util-symbol'
/**
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Construct}
* Construct.
*/
export function mathText(options) {
const options_ = options || {}
let single = options_.singleDollarTextMath
if (single === null || single === undefined) {
single = true
}
return {
tokenize: tokenizeMathText,
resolve: resolveMathText,
previous,
name: 'mathText'
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeMathText(effects, ok, nok) {
const self = this
let sizeOpen = 0
/** @type {number} */
let size
/** @type {Token} */
let token
return start
/**
* Start of math (text).
*
* ```markdown
* > | $a$
* ^
* > | \$a$
* ^
* ```
*
* @type {State}
*/
function start(code) {
assert(code === codes.dollarSign, 'expected `$`')
assert(previous.call(self, self.previous), 'expected correct previous')
effects.enter('mathText')
effects.enter('mathTextSequence')
return sequenceOpen(code)
}
/**
* In opening sequence.
*
* ```markdown
* > | $a$
* ^
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === codes.dollarSign) {
effects.consume(code)
sizeOpen++
return sequenceOpen
}
// Not enough markers in the sequence.
if (sizeOpen < 2 && !single) {
return nok(code)
}
effects.exit('mathTextSequence')
return between(code)
}
/**
* Between something and something else.
*
* ```markdown
* > | $a$
* ^^
* ```
*
* @type {State}
*/
function between(code) {
if (code === codes.eof) {
return nok(code)
}
if (code === codes.dollarSign) {
token = effects.enter('mathTextSequence')
size = 0
return sequenceClose(code)
}
// Tabs dont work, and virtual spaces dont make sense.
if (code === codes.space) {
effects.enter('space')
effects.consume(code)
effects.exit('space')
return between
}
if (markdownLineEnding(code)) {
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return between
}
// Data.
effects.enter('mathTextData')
return data(code)
}
/**
* In data.
*
* ```markdown
* > | $a$
* ^
* ```
*
* @type {State}
*/
function data(code) {
if (
code === codes.eof ||
code === codes.space ||
code === codes.dollarSign ||
markdownLineEnding(code)
) {
effects.exit('mathTextData')
return between(code)
}
effects.consume(code)
return data
}
/**
* In closing sequence.
*
* ```markdown
* > | `a`
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
// More.
if (code === codes.dollarSign) {
effects.consume(code)
size++
return sequenceClose
}
// Done!
if (size === sizeOpen) {
effects.exit('mathTextSequence')
effects.exit('mathText')
return ok(code)
}
// More or less accents: mark as data.
token.type = 'mathTextData'
return data(code)
}
}
}
/** @type {Resolver} */
function resolveMathText(events) {
let tailExitIndex = events.length - 4
let headEnterIndex = 3
/** @type {number} */
let index
/** @type {number | undefined} */
let enter
// If we start and end with an EOL or a space.
if (
(events[headEnterIndex][1].type === types.lineEnding ||
events[headEnterIndex][1].type === 'space') &&
(events[tailExitIndex][1].type === types.lineEnding ||
events[tailExitIndex][1].type === 'space')
) {
index = headEnterIndex
// And we have data.
while (++index < tailExitIndex) {
if (events[index][1].type === 'mathTextData') {
// Then we have padding.
events[tailExitIndex][1].type = 'mathTextPadding'
events[headEnterIndex][1].type = 'mathTextPadding'
headEnterIndex += 2
tailExitIndex -= 2
break
}
}
}
// Merge adjacent spaces and data.
index = headEnterIndex - 1
tailExitIndex++
while (++index <= tailExitIndex) {
if (enter === undefined) {
if (
index !== tailExitIndex &&
events[index][1].type !== types.lineEnding
) {
enter = index
}
} else if (
index === tailExitIndex ||
events[index][1].type === types.lineEnding
) {
events[enter][1].type = 'mathTextData'
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end
events.splice(enter + 2, index - enter - 2)
tailExitIndex -= index - enter - 2
index = enter + 2
}
enter = undefined
}
}
return events
}
/**
* @this {TokenizeContext}
* @type {Previous}
*/
function previous(code) {
// If there is a previous code, there will always be a tail.
return (
code !== codes.dollarSign ||
this.events[this.events.length - 1][1].type === types.characterEscape
)
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Create an extension for `micromark` to enable math syntax.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable math syntax.
*/
export function math(options?: Options | null | undefined): Extension;
import type { Options } from 'micromark-extension-math';
import type { Extension } from 'micromark-util-types';
+24
View File
@@ -0,0 +1,24 @@
/**
* @import {Options} from 'micromark-extension-math'
* @import {Extension} from 'micromark-util-types'
*/
import {codes} from 'micromark-util-symbol'
import {mathFlow} from './math-flow.js'
import {mathText} from './math-text.js'
/**
* Create an extension for `micromark` to enable math syntax.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable math syntax.
*/
export function math(options) {
return {
flow: {[codes.dollarSign]: mathFlow},
text: {[codes.dollarSign]: mathText(options)}
}
}
+61
View File
@@ -0,0 +1,61 @@
import type {KatexOptions} from 'katex'
export {mathHtml} from './lib/html.js'
export {math} from './lib/syntax.js'
/**
* Configuration for HTML output.
*
* > 👉 **Note**: passed to `katex.renderToString`.
* > `displayMode` is overwritten by this plugin, to `false` for math in
* > text (inline), and `true` for math in flow (block).
*/
export interface HtmlOptions extends KatexOptions {
/**
* The field `displayMode` cannot be passed to `micromark-extension-math`.
* It is overwritten by it,
* to `false` for math in text (inline) and `true` for math in flow (block).
*/
displayMode?: never
}
/**
* Configuration.
*/
export interface Options {
/**
* Whether to support math (text) with a single dollar (default: `true`).
*
* Single dollars work in Pandoc and many other places, but often interfere
* with “normal” dollars in text.
* If you turn this off, you can use two or more dollars for text math.
*/
singleDollarTextMath?: boolean | null | undefined
}
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* Compile data.
*/
interface CompileData {
mathFlowOpen?: boolean
}
/**
* Token types.
*/
interface TokenTypeMap {
mathFlow: 'mathFlow'
mathFlowFence: 'mathFlowFence'
mathFlowFenceMeta: 'mathFlowFenceMeta'
mathFlowFenceSequence: 'mathFlowFenceSequence'
mathFlowValue: 'mathFlowValue'
mathText: 'mathText'
mathTextData: 'mathTextData'
mathTextPadding: 'mathTextPadding'
mathTextSequence: 'mathTextSequence'
}
}
+3
View File
@@ -0,0 +1,3 @@
// Note: types exported from `index.d.ts`.
export { math } from './lib/syntax.js';
export { mathHtml } from './lib/html.js';
+15
View File
@@ -0,0 +1,15 @@
/**
* Create an extension for `micromark` to support math when serializing to
* HTML.
*
* > 👉 **Note**: this uses KaTeX to render math.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support math when serializing to HTML.
*/
export function mathHtml(options?: Options | null | undefined): HtmlExtension;
import type { HtmlOptions as Options } from 'micromark-extension-math';
import type { HtmlExtension } from 'micromark-util-types';
+84
View File
@@ -0,0 +1,84 @@
/**
* @import {HtmlOptions as Options} from 'micromark-extension-math'
* @import {HtmlExtension} from 'micromark-util-types'
*/
import katex from 'katex';
const renderToString = katex.renderToString;
/**
* Create an extension for `micromark` to support math when serializing to
* HTML.
*
* > 👉 **Note**: this uses KaTeX to render math.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support math when serializing to HTML.
*/
export function mathHtml(options) {
return {
enter: {
mathFlow() {
this.lineEndingIfNeeded();
this.tag('<div class="math math-display">');
},
mathFlowFenceMeta() {
this.buffer();
},
mathText() {
// Double?
this.tag('<span class="math math-inline">');
this.buffer();
}
},
exit: {
mathFlow() {
const value = this.resume();
this.tag(math(value.replace(/(?:\r?\n|\r)$/, ''), true));
this.tag('</div>');
this.setData('mathFlowOpen');
this.setData('slurpOneLineEnding');
},
mathFlowFence() {
// After the first fence.
if (!this.getData('mathFlowOpen')) {
this.setData('mathFlowOpen', true);
this.setData('slurpOneLineEnding', true);
this.buffer();
}
},
mathFlowFenceMeta() {
this.resume();
},
mathFlowValue(token) {
this.raw(this.sliceSerialize(token));
},
mathText() {
const value = this.resume();
this.tag(math(value, false));
this.tag('</span>');
},
mathTextData(token) {
this.raw(this.sliceSerialize(token));
}
}
};
/**
* @param {string} value
* Math text.
* @param {boolean} displayMode
* Whether the math is in display mode.
* @returns {string}
* HTML.
*/
function math(value, displayMode) {
return renderToString(value, {
...options,
displayMode
});
}
}
+3
View File
@@ -0,0 +1,3 @@
/** @type {Construct} */
export const mathFlow: Construct;
import type { Construct } from 'micromark-util-types';
+345
View File
@@ -0,0 +1,345 @@
/**
* @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const mathFlow = {
tokenize: tokenizeMathFenced,
concrete: true,
name: 'mathFlow'
};
/** @type {Construct} */
const nonLazyContinuation = {
tokenize: tokenizeNonLazyContinuation,
partial: true
};
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeMathFenced(effects, ok, nok) {
const self = this;
const tail = self.events[self.events.length - 1];
const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
let sizeOpen = 0;
return start;
/**
* Start of math.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function start(code) {
effects.enter('mathFlow');
effects.enter('mathFlowFence');
effects.enter('mathFlowFenceSequence');
return sequenceOpen(code);
}
/**
* In opening fence sequence.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === 36) {
effects.consume(code);
sizeOpen++;
return sequenceOpen;
}
if (sizeOpen < 2) {
return nok(code);
}
effects.exit('mathFlowFenceSequence');
return factorySpace(effects, metaBefore, "whitespace")(code);
}
/**
* In opening fence, before meta.
*
* ```markdown
* > | $$asciimath
* ^
* | x < y
* | $$
* ```
*
* @type {State}
*/
function metaBefore(code) {
if (code === null || markdownLineEnding(code)) {
return metaAfter(code);
}
effects.enter('mathFlowFenceMeta');
effects.enter("chunkString", {
contentType: "string"
});
return meta(code);
}
/**
* In meta.
*
* ```markdown
* > | $$asciimath
* ^
* | x < y
* | $$
* ```
*
* @type {State}
*/
function meta(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("chunkString");
effects.exit('mathFlowFenceMeta');
return metaAfter(code);
}
if (code === 36) {
return nok(code);
}
effects.consume(code);
return meta;
}
/**
* After meta.
*
* ```markdown
* > | $$
* ^
* | \frac{1}{2}
* | $$
* ```
*
* @type {State}
*/
function metaAfter(code) {
// Guaranteed to be eol/eof.
effects.exit('mathFlowFence');
if (self.interrupt) {
return ok(code);
}
return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
}
/**
* After eol/eof in math, at a non-lazy closing fence or content.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* > | $$
* ^
* ```
*
* @type {State}
*/
function beforeNonLazyContinuation(code) {
return effects.attempt({
tokenize: tokenizeClosingFence,
partial: true
}, after, contentStart)(code);
}
/**
* Before math content, definitely not before a closing fence.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function contentStart(code) {
return (initialSize ? factorySpace(effects, beforeContentChunk, "linePrefix", initialSize + 1) : beforeContentChunk)(code);
}
/**
* Before math content, after optional prefix.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function beforeContentChunk(code) {
if (code === null) {
return after(code);
}
if (markdownLineEnding(code)) {
return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
}
effects.enter('mathFlowValue');
return contentChunk(code);
}
/**
* In math content.
*
* ```markdown
* | $$
* > | \frac{1}{2}
* ^
* | $$
* ```
*
* @type {State}
*/
function contentChunk(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit('mathFlowValue');
return beforeContentChunk(code);
}
effects.consume(code);
return contentChunk;
}
/**
* After math (ha!).
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function after(code) {
effects.exit('mathFlow');
return ok(code);
}
/** @type {Tokenizer} */
function tokenizeClosingFence(effects, ok, nok) {
let size = 0;
/**
* Before closing fence, at optional whitespace.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*/
return factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
/**
* In closing fence, after optional whitespace, at sequence.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function beforeSequenceClose(code) {
effects.enter('mathFlowFence');
effects.enter('mathFlowFenceSequence');
return sequenceClose(code);
}
/**
* In closing fence sequence.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
if (code === 36) {
size++;
effects.consume(code);
return sequenceClose;
}
if (size < sizeOpen) {
return nok(code);
}
effects.exit('mathFlowFenceSequence');
return factorySpace(effects, afterSequenceClose, "whitespace")(code);
}
/**
* After closing fence sequence, after optional whitespace.
*
* ```markdown
* | $$
* | \frac{1}{2}
* > | $$
* ^
* ```
*
* @type {State}
*/
function afterSequenceClose(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit('mathFlowFence');
return ok(code);
}
return nok(code);
}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeNonLazyContinuation(effects, ok, nok) {
const self = this;
return start;
/** @type {State} */
function start(code) {
if (code === null) {
return ok(code);
}
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return lineStart;
}
/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
}
}
+9
View File
@@ -0,0 +1,9 @@
/**
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Construct}
* Construct.
*/
export function mathText(options?: Options | null | undefined): Construct;
import type { Options } from 'micromark-extension-math';
import type { Construct } from 'micromark-util-types';
+232
View File
@@ -0,0 +1,232 @@
/**
* @import {Options} from 'micromark-extension-math'
* @import {Construct, Previous, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
// To do: next major: clean spaces in HTML compiler.
// This has to be coordinated together with `mdast-util-math`.
import { markdownLineEnding } from 'micromark-util-character';
/**
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Construct}
* Construct.
*/
export function mathText(options) {
const options_ = options || {};
let single = options_.singleDollarTextMath;
if (single === null || single === undefined) {
single = true;
}
return {
tokenize: tokenizeMathText,
resolve: resolveMathText,
previous,
name: 'mathText'
};
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeMathText(effects, ok, nok) {
const self = this;
let sizeOpen = 0;
/** @type {number} */
let size;
/** @type {Token} */
let token;
return start;
/**
* Start of math (text).
*
* ```markdown
* > | $a$
* ^
* > | \$a$
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter('mathText');
effects.enter('mathTextSequence');
return sequenceOpen(code);
}
/**
* In opening sequence.
*
* ```markdown
* > | $a$
* ^
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === 36) {
effects.consume(code);
sizeOpen++;
return sequenceOpen;
}
// Not enough markers in the sequence.
if (sizeOpen < 2 && !single) {
return nok(code);
}
effects.exit('mathTextSequence');
return between(code);
}
/**
* Between something and something else.
*
* ```markdown
* > | $a$
* ^^
* ```
*
* @type {State}
*/
function between(code) {
if (code === null) {
return nok(code);
}
if (code === 36) {
token = effects.enter('mathTextSequence');
size = 0;
return sequenceClose(code);
}
// Tabs dont work, and virtual spaces dont make sense.
if (code === 32) {
effects.enter('space');
effects.consume(code);
effects.exit('space');
return between;
}
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return between;
}
// Data.
effects.enter('mathTextData');
return data(code);
}
/**
* In data.
*
* ```markdown
* > | $a$
* ^
* ```
*
* @type {State}
*/
function data(code) {
if (code === null || code === 32 || code === 36 || markdownLineEnding(code)) {
effects.exit('mathTextData');
return between(code);
}
effects.consume(code);
return data;
}
/**
* In closing sequence.
*
* ```markdown
* > | `a`
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
// More.
if (code === 36) {
effects.consume(code);
size++;
return sequenceClose;
}
// Done!
if (size === sizeOpen) {
effects.exit('mathTextSequence');
effects.exit('mathText');
return ok(code);
}
// More or less accents: mark as data.
token.type = 'mathTextData';
return data(code);
}
}
}
/** @type {Resolver} */
function resolveMathText(events) {
let tailExitIndex = events.length - 4;
let headEnterIndex = 3;
/** @type {number} */
let index;
/** @type {number | undefined} */
let enter;
// If we start and end with an EOL or a space.
if ((events[headEnterIndex][1].type === "lineEnding" || events[headEnterIndex][1].type === 'space') && (events[tailExitIndex][1].type === "lineEnding" || events[tailExitIndex][1].type === 'space')) {
index = headEnterIndex;
// And we have data.
while (++index < tailExitIndex) {
if (events[index][1].type === 'mathTextData') {
// Then we have padding.
events[tailExitIndex][1].type = 'mathTextPadding';
events[headEnterIndex][1].type = 'mathTextPadding';
headEnterIndex += 2;
tailExitIndex -= 2;
break;
}
}
}
// Merge adjacent spaces and data.
index = headEnterIndex - 1;
tailExitIndex++;
while (++index <= tailExitIndex) {
if (enter === undefined) {
if (index !== tailExitIndex && events[index][1].type !== "lineEnding") {
enter = index;
}
} else if (index === tailExitIndex || events[index][1].type === "lineEnding") {
events[enter][1].type = 'mathTextData';
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end;
events.splice(enter + 2, index - enter - 2);
tailExitIndex -= index - enter - 2;
index = enter + 2;
}
enter = undefined;
}
}
return events;
}
/**
* @this {TokenizeContext}
* @type {Previous}
*/
function previous(code) {
// If there is a previous code, there will always be a tail.
return code !== 36 || this.events[this.events.length - 1][1].type === "characterEscape";
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Create an extension for `micromark` to enable math syntax.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable math syntax.
*/
export function math(options?: Options | null | undefined): Extension;
import type { Options } from 'micromark-extension-math';
import type { Extension } from 'micromark-util-types';
+27
View File
@@ -0,0 +1,27 @@
/**
* @import {Options} from 'micromark-extension-math'
* @import {Extension} from 'micromark-util-types'
*/
import { mathFlow } from './math-flow.js';
import { mathText } from './math-text.js';
/**
* Create an extension for `micromark` to enable math syntax.
*
* @param {Options | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable math syntax.
*/
export function math(options) {
return {
flow: {
[36]: mathFlow
},
text: {
[36]: mathText(options)
}
};
}
+22
View File
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+121
View File
@@ -0,0 +1,121 @@
{
"name": "micromark-extension-math",
"version": "3.1.0",
"description": "micromark extension to support math (`$C_L$`)",
"license": "MIT",
"keywords": [
"micromark",
"micromark-extension",
"math",
"katex",
"latex",
"tex",
"markdown",
"unified"
],
"repository": "micromark/micromark-extension-math",
"bugs": "https://github.com/micromark/micromark-extension-math/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"sideEffects": false,
"type": "module",
"exports": {
"development": "./dev/index.js",
"default": "./index.js"
},
"files": [
"dev/",
"lib/",
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/katex": "^0.16.0",
"devlop": "^1.0.0",
"katex": "^0.16.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"c8": "^10.0.0",
"micromark": "^4.0.0",
"micromark-build": "^2.0.0",
"prettier": "^3.0.0",
"remark-cli": "^12.0.0",
"remark-preset-wooorm": "^10.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.58.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
"format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
"test-api-prod": "node --conditions production test/index.js",
"test-api-dev": "node --conditions development test/index.js",
"test-api": "npm run test-api-dev && npm run test-api-prod",
"test-coverage": "c8 --100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"bracketSpacing": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"remark-preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"ignoreCatch": true,
"strict": true
},
"xo": {
"overrides": [
{
"files": [
"**/*.d.ts"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true
}
],
"@typescript-eslint/consistent-type-definitions": [
"error",
"interface"
]
}
}
],
"prettier": true,
"rules": {
"logical-assignment-operators": "off",
"unicorn/no-this-assignment": "off",
"unicorn/prefer-at": "off"
}
}
}
+429
View File
@@ -0,0 +1,429 @@
# micromark-extension-math
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Size][size-badge]][size]
[![Sponsors][sponsors-badge]][collective]
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
[micromark][] extensions to support math (`$C_L$`).
## Contents
* [What is this?](#what-is-this)
* [When to use this](#when-to-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`math(options?)`](#mathoptions)
* [`mathHtml(options?)`](#mathhtmloptions)
* [`HtmlOptions`](#htmloptions)
* [`Options`](#options)
* [Authoring](#authoring)
* [HTML](#html)
* [CSS](#css)
* [Syntax](#syntax)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package contains two extensions that add support for math syntax
in markdown to [`micromark`][micromark].
As there is no spec for math in markdown, this extension follows how code
(fenced and text) works in Commonmark, but uses dollars.
## When to use this
This project is useful when you want to support math in markdown.
Extending markdown with a syntax extension makes the markdown less portable.
LaTeX equations are also quite hard.
But this mechanism works well when you want authors, that have some LaTeX
experience, to be able to embed rich diagrams of math in scientific text.
You can use these extensions when you are working with [`micromark`][micromark]
already.
When you need a syntax tree, you can combine this package with
[`mdast-util-math`][mdast-util-math].
All these packages are used [`remark-math`][remark-math], which focusses on
making it easier to transform content by abstracting these internals away.
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
[npm][]:
```sh
npm install micromark-extension-math
```
In Deno with [`esm.sh`][esmsh]:
```js
import {math, mathHtml} from 'https://esm.sh/micromark-extension-math@3'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import {math, mathHtml} from 'https://esm.sh/micromark-extension-math@3?bundle'
</script>
```
## Use
Say our document `example.md` contains:
```markdown
Lift($L$) can be determined by Lift Coefficient ($C_L$) like the following equation.
$$
L = \frac{1}{2} \rho v^2 S C_L
$$
```
…and our module `example.js` looks as follows:
```js
import fs from 'node:fs/promises'
import {micromark} from 'micromark'
import {math, mathHtml} from 'micromark-extension-math'
const output = micromark(await fs.readFile('example.md'), {
extensions: [math()],
htmlExtensions: [mathHtml()]
})
console.log(output)
```
…now running `node example.js` yields (abbreviated):
```html
<p>Lift(<span class="math math-inline"><span class="katex"></span></span>) can be determined by Lift Coefficient (<span class="math math-inline"><span class="katex"></span></span>) like the following equation.</p>
<div class="math math-display"><span class="katex-display"><span class="katex"></span></span></div>
```
## API
This package exports the identifiers [`math`][api-math] and
[`mathHtml`][api-math-html].
There is no default export.
The export map supports the [`development` condition][development].
Run `node --conditions development module.js` to get instrumented dev code.
Without this condition, production code is loaded.
### `math(options?)`
Create an extension for `micromark` to enable math syntax.
###### Parameters
* `options` ([`Options`][api-options], default: `{}`)
— configuration
###### Returns
Extension for `micromark` that can be passed in `extensions`, to enable math
syntax ([`Extension`][micromark-extension]).
### `mathHtml(options?)`
Create an extension for `micromark` to support math when serializing to HTML.
> 👉 **Note**: this uses KaTeX to render math.
###### Parameters
* `options` ([`HtmlOptions`][api-html-options], default: `{}`)
— configuration
###### Returns
Extension for `micromark` that can be passed in `htmlExtensions`, to support
math when serializing to HTML ([`HtmlExtension`][micromark-html-extension]).
### `HtmlOptions`
Configuration for HTML output (optional).
> 👉 **Note**: passed to [`katex.renderToString`][katex-options].
> `displayMode` is overwritten by this plugin, to `false` for math in text
> (inline), and `true` for math in flow (block).
###### Type
```ts
type Options = Omit<import('katex').KatexOptions, 'displayMode'>
```
### `Options`
Configuration (TypeScript type).
###### Fields
* `singleDollarTextMath` (`boolean`, default: `true`)
— whether to support math (text, inline) with a single dollar.
Single dollars work in Pandoc and many other places, but often interfere
with “normal” dollars in text.
If you turn this off, you use two or more dollars for text math.
## Authoring
When authoring markdown with math, keep in mind that math doesnt work in most
places.
Notably, GitHub currently has a really weird crappy client-side regex-based
thing.
But on your own (math-heavy?) site it can be great!
You can use code (fenced) with an info string of `math` to improve this, as
that works in many places.
## HTML
Math (flow) does not relate to HTML elements.
`MathML`, which is sort of like SVG but for math, exists but it doesnt work
well and isnt widely supported.
Instead, this uses [KaTeX][], which generates MathML as a fallback but also
generates a bunch of divs and spans so math look pretty.
The KaTeX result is wrapped in `<div>` (for flow, block) and `<span>` (for text,
inline) elements, with two classes: `math` and either `math-display` or
`math-inline`.
When turning markdown into HTML, each line ending in math (text) is turned
into a space.
## CSS
The HTML produced by KaTeX requires CSS to render correctly.
You should use `katex.css` somewhere on the page where the math is shown to
style it properly.
At the time of writing, the last version is:
<!-- To do: update and copy paste the one from: https://katex.org/docs/browser -->
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
```
## Syntax
Math forms with the following BNF:
```bnf
; Restriction: the number of markers in the closing sequence must be equal
; to the number of markers in the opening sequence.
math_text ::= sequence_text 1*byte sequence_text
math_flow ::= fence_open *( eol *line ) [ eol fence_close ]
; Restriction: not preceded or followed by the marker.
sequence_text ::= 1*'$'
fence_open ::= sequence_flow meta
; Restriction: the number of markers in the closing fence sequence must be
; equal to or greater than the number of markers in the opening fence
; sequence.
fence_close ::= sequence_flow *space_or_tab
sequence_flow ::= 2*'$'
; Restriction: the marker cannot occur in `meta`
meta ::= 1*line
; Character groups for informational purposes.
byte ::= 0x00..=0xFFFF
eol ::= '\n' | '\r' | '\r\n'
line ::= byte - eol
```
The above grammar shows that it is not possible to create empty math (text).
It is possible to include the sequence marker (dollar) in math (text), by
wrapping it in bigger or smaller sequences:
```markdown
Include more: $a$$b$ or include less: $$a$b$$.
```
It is also possible to include just one marker:
```markdown
Include just one: $$ $ $$.
```
Sequences are “gready”, in that they cannot be preceded or followed by more
markers.
To illustrate:
```markdown
Not math: $$x$.
Not math: $x$$.
Escapes work, this is math: \$$x$.
Escapes work, this is math: $x$\$.
```
Yields:
```html
<p>Not math: $$x$.</p>
<p>Not math: $x$$.</p>
<p>Escapes work, this is math: $<span></span>.</p>
<p>Escapes work, this is math: <span></span>$.</p>
```
That is because, when turning markdown into HTML, the first and last space,
if both exist and there is also a non-space in the math, are removed.
Line endings, at that stage, are considered as spaces.
As the math (flow) construct occurs in flow, like all flow constructs, it must
be followed by an eol (line ending) or eof (end of file).
The above grammar does not show how indentation of each line is handled.
To parse math (flow), let `x` be the number of `space_or_tab` characters
before the opening fence sequence, after interpreting tabs based on how many
virtual spaces they represent.
Each line of text is then allowed (not required) to be indented with up
to `x` spaces or tabs, which are then ignored as an indent instead of being
considered as part of the content.
This indent does not affect the closing fence.
It can be indented up to a separate 3 real or virtual spaces.
A bigger indent makes it part of the content instead of a fence.
The `meta` part is interpreted as the [string][micromark-content-types] content
type.
That means that character escapes and character references are allowed.
The optional `meta` part is ignored: it is not used when parsing or
rendering.
## Types
This package is fully typed with [TypeScript][].
It exports the additional types [`HtmlOptions`][api-html-options]
and [`Options`][api-options].
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line,
`micromark-extension-math@^3`, compatible with Node.js 16.
This package works with `micromark` version `3` and later.
## Security
This package is safe assuming that you trust KaTeX.
Any vulnerability in it could open you to a [cross-site scripting (XSS)][xss]
attack.
## Related
* [`remark-math`][remark-math]
— remark (and rehype) plugins to support math
* [`mdast-util-math`][mdast-util-math]
— mdast utility to support math
## Contribute
See [`contributing.md` in `micromark/.github`][contributing] for ways to get
started.
See [`support.md`][support] for ways to get help.
This project has a [code of conduct][coc].
By interacting with this repository, organization, or community you agree to
abide by its terms.
## License
[MIT][license] © [Titus Wormer][author]
<!-- Definitions -->
[build-badge]: https://github.com/micromark/micromark-extension-math/workflows/main/badge.svg
[build]: https://github.com/micromark/micromark-extension-math/actions
[coverage-badge]: https://img.shields.io/codecov/c/github/micromark/micromark-extension-math.svg
[coverage]: https://codecov.io/github/micromark/micromark-extension-math
[downloads-badge]: https://img.shields.io/npm/dm/micromark-extension-math.svg
[downloads]: https://www.npmjs.com/package/micromark-extension-math
[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=micromark-extension-math
[size]: https://bundlejs.com/?q=micromark-extension-math
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
[collective]: https://opencollective.com/unified
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
[chat]: https://github.com/micromark/micromark/discussions
[npm]: https://docs.npmjs.com/cli/install
[esmsh]: https://esm.sh
[license]: license
[author]: https://wooorm.com
[contributing]: https://github.com/micromark/.github/blob/main/contributing.md
[support]: https://github.com/micromark/.github/blob/main/support.md
[coc]: https://github.com/micromark/.github/blob/main/code-of-conduct.md
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[typescript]: https://www.typescriptlang.org
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions
[micromark]: https://github.com/micromark/micromark
[micromark-content-types]: https://github.com/micromark/micromark#content-types
[micromark-html-extension]: https://github.com/micromark/micromark#htmlextension
[micromark-extension]: https://github.com/micromark/micromark#syntaxextension
[mdast-util-math]: https://github.com/syntax-tree/mdast-util-math
[remark-math]: https://github.com/remarkjs/remark-math
[katex]: https://katex.org
[katex-options]: https://katex.org/docs/options.html
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[api-math]: #mathoptions
[api-math-html]: #mathhtmloptions
[api-options]: #options
[api-html-options]: #htmloptions