@ziloen/micromark-extension-math 3.1.0-patch.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @param {Options | null | undefined} [options={}]
3
+ * Configuration (default: `{}`).
4
+ * @returns {Construct}
5
+ * Construct.
6
+ */
7
+ export function mathText(options?: Options | null | undefined): Construct;
8
+ import type { Options } from '@ziloen/micromark-extension-math';
9
+ import type { Construct } from 'micromark-util-types';
@@ -0,0 +1,408 @@
1
+ /**
2
+ * @import {Options} from '@ziloen/micromark-extension-math'
3
+ * @import {Construct, Previous, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
4
+ */
5
+
6
+ // To do: next major: clean spaces in HTML compiler.
7
+ // This has to be coordinated together with `mdast-util-math`.
8
+
9
+ import {ok as assert} from 'devlop'
10
+ import {
11
+ asciiDigit,
12
+ markdownLineEnding,
13
+ markdownSpace
14
+ } from 'micromark-util-character'
15
+ import {codes, types} from 'micromark-util-symbol'
16
+
17
+ /**
18
+ * @param {Options | null | undefined} [options={}]
19
+ * Configuration (default: `{}`).
20
+ * @returns {Construct}
21
+ * Construct.
22
+ */
23
+ export function mathText(options) {
24
+ const options_ = options || {}
25
+ let single = options_.singleDollarTextMath
26
+
27
+ if (single === null || single === undefined) {
28
+ single = true
29
+ }
30
+
31
+ return {
32
+ tokenize: tokenizeMathText,
33
+ resolve: resolveMathText,
34
+ previous: previousCode,
35
+ name: 'mathText'
36
+ }
37
+
38
+ /**
39
+ * @this {TokenizeContext}
40
+ * @type {Tokenizer}
41
+ */
42
+ function tokenizeMathText(effects, ok, nok) {
43
+ const self = this
44
+ /** @type {number} */
45
+ let codeOpen = codes.dollarSign
46
+ /** @type {number} */
47
+ let codeClose = codes.dollarSign
48
+ /** @type {'mathText' | 'mathTextDisplay'} */
49
+ let type = 'mathText'
50
+ let sizeOpen = 0
51
+ /** @type {number} */
52
+ let size
53
+ /** @type {number | null | undefined} */
54
+ let previous
55
+ /** @type {Token} */
56
+ let tokenType
57
+ /** @type {Token} */
58
+ let token
59
+ let dataSeen = false
60
+
61
+ return start
62
+
63
+ /**
64
+ * Start of math (text).
65
+ *
66
+ * ```markdown
67
+ * > | $a$
68
+ * ^
69
+ * > | \$a$
70
+ * ^
71
+ * ```
72
+ *
73
+ * @type {State}
74
+ */
75
+ function start(code) {
76
+ assert(
77
+ code === codes.dollarSign || code === codes.backslash,
78
+ 'expected `$` or `\\`'
79
+ )
80
+ assert(
81
+ code === codes.backslash || previousCode.call(self, self.previous),
82
+ 'expected correct previous'
83
+ )
84
+
85
+ if (code === codes.backslash) {
86
+ codeOpen = codes.leftParenthesis
87
+ codeClose = codes.rightParenthesis
88
+ }
89
+
90
+ tokenType = effects.enter(type)
91
+ effects.enter('mathTextSequence')
92
+ effects.consume(code)
93
+ sizeOpen++
94
+ return code === codes.backslash ? sequenceOpenBackslash : sequenceOpen
95
+ }
96
+
97
+ /**
98
+ * In opening sequence.
99
+ *
100
+ * ```markdown
101
+ * > | $a$
102
+ * ^
103
+ * ```
104
+ *
105
+ * @type {State}
106
+ */
107
+
108
+ function sequenceOpen(code) {
109
+ if (code === codes.dollarSign) {
110
+ effects.consume(code)
111
+ sizeOpen++
112
+ return sequenceOpen
113
+ }
114
+
115
+ // Not enough markers in the sequence.
116
+ if (
117
+ (sizeOpen < 2 && !single) ||
118
+ (sizeOpen === 1 && !validSingleDollarOpen(code))
119
+ ) {
120
+ return nok(code)
121
+ }
122
+
123
+ effects.exit('mathTextSequence')
124
+ return between(code)
125
+ }
126
+
127
+ /**
128
+ * After the backslash in an opening sequence.
129
+ *
130
+ * ```markdown
131
+ * > | \(a\)
132
+ * ^
133
+ * > | \[a\]
134
+ * ^
135
+ * ```
136
+ *
137
+ * @type {State}
138
+ */
139
+ function sequenceOpenBackslash(code) {
140
+ if (code !== codes.leftParenthesis && code !== codes.leftSquareBracket) {
141
+ return nok(code)
142
+ }
143
+
144
+ codeOpen = code
145
+ codeClose =
146
+ code === codes.leftParenthesis
147
+ ? codes.rightParenthesis
148
+ : codes.rightSquareBracket
149
+ type = code === codes.leftParenthesis ? 'mathText' : 'mathTextDisplay'
150
+
151
+ tokenType.type = type
152
+ effects.consume(code)
153
+ effects.exit('mathTextSequence')
154
+ return between
155
+ }
156
+
157
+ /**
158
+ * Between something and something else.
159
+ *
160
+ * ```markdown
161
+ * > | $a$
162
+ * ^^
163
+ * ```
164
+ *
165
+ * @type {State}
166
+ */
167
+ function between(code) {
168
+ if (code === codes.eof) {
169
+ return nok(code)
170
+ }
171
+
172
+ if (code === codes.dollarSign && codeOpen === codes.dollarSign) {
173
+ token = effects.enter('mathTextSequence')
174
+ size = 0
175
+ return sequenceClose(code)
176
+ }
177
+
178
+ if (code === codes.backslash && codeOpen !== codes.dollarSign) {
179
+ token = effects.enter('mathTextSequence')
180
+ return sequenceCloseBackslash(code)
181
+ }
182
+
183
+ // Tabs don’t work, and virtual spaces don’t make sense.
184
+ if (code === codes.space) {
185
+ effects.enter('space')
186
+ effects.consume(code)
187
+ effects.exit('space')
188
+ previous = code
189
+ return between
190
+ }
191
+
192
+ if (markdownLineEnding(code)) {
193
+ effects.enter(types.lineEnding)
194
+ effects.consume(code)
195
+ effects.exit(types.lineEnding)
196
+ previous = code
197
+ return between
198
+ }
199
+
200
+ // Data.
201
+ effects.enter('mathTextData')
202
+ return data(code)
203
+ }
204
+
205
+ /**
206
+ * In data.
207
+ *
208
+ * ```markdown
209
+ * > | $a$
210
+ * ^
211
+ * ```
212
+ *
213
+ * @type {State}
214
+ */
215
+ function data(code) {
216
+ if (
217
+ code === codes.eof ||
218
+ code === codes.space ||
219
+ (code === codes.dollarSign && codeOpen === codes.dollarSign) ||
220
+ (code === codes.backslash && codeOpen !== codes.dollarSign) ||
221
+ markdownLineEnding(code)
222
+ ) {
223
+ effects.exit('mathTextData')
224
+ return between(code)
225
+ }
226
+
227
+ effects.consume(code)
228
+ previous = code
229
+ dataSeen = true
230
+ return data
231
+ }
232
+
233
+ /**
234
+ * In closing sequence.
235
+ *
236
+ * ```markdown
237
+ * > | `a`
238
+ * ^
239
+ * ```
240
+ *
241
+ * @type {State}
242
+ */
243
+
244
+ function sequenceClose(code) {
245
+ // More.
246
+ if (code === codes.dollarSign) {
247
+ effects.consume(code)
248
+ size++
249
+ return sequenceClose
250
+ }
251
+
252
+ // Done!
253
+ if (
254
+ size === sizeOpen &&
255
+ (sizeOpen !== 1 || validSingleDollarClose(previous, code))
256
+ ) {
257
+ effects.exit('mathTextSequence')
258
+ effects.exit(type)
259
+ return ok(code)
260
+ }
261
+
262
+ // More or less accents: mark as data.
263
+ token.type = 'mathTextData'
264
+ return data(code)
265
+ }
266
+
267
+ /**
268
+ * In a closing backslash sequence.
269
+ *
270
+ * ```markdown
271
+ * > | \(a\)
272
+ * ^
273
+ * ```
274
+ *
275
+ * @type {State}
276
+ */
277
+ function sequenceCloseBackslash(code) {
278
+ assert(code === codes.backslash, 'expected `\\`')
279
+ effects.consume(code)
280
+ return sequenceCloseBackslashEnd
281
+ }
282
+
283
+ /**
284
+ * After the backslash in a closing sequence.
285
+ *
286
+ * @type {State}
287
+ */
288
+ function sequenceCloseBackslashEnd(code) {
289
+ if (code === codeClose) {
290
+ if (!dataSeen) {
291
+ return nok(code)
292
+ }
293
+
294
+ effects.consume(code)
295
+ effects.exit('mathTextSequence')
296
+ effects.exit(type)
297
+ return ok
298
+ }
299
+
300
+ token.type = 'mathTextData'
301
+ previous = codes.backslash
302
+ return data(code)
303
+ }
304
+ }
305
+ }
306
+
307
+ /** @type {Resolver} */
308
+ function resolveMathText(events) {
309
+ let tailExitIndex = events.length - 4
310
+ let headEnterIndex = 3
311
+ /** @type {number} */
312
+ let index
313
+ /** @type {number | undefined} */
314
+ let enter
315
+
316
+ // If we start and end with an EOL or a space.
317
+ if (
318
+ (events[headEnterIndex][1].type === types.lineEnding ||
319
+ events[headEnterIndex][1].type === 'space') &&
320
+ (events[tailExitIndex][1].type === types.lineEnding ||
321
+ events[tailExitIndex][1].type === 'space')
322
+ ) {
323
+ index = headEnterIndex
324
+
325
+ // And we have data.
326
+ while (++index < tailExitIndex) {
327
+ if (events[index][1].type === 'mathTextData') {
328
+ // Then we have padding.
329
+ events[tailExitIndex][1].type = 'mathTextPadding'
330
+ events[headEnterIndex][1].type = 'mathTextPadding'
331
+ headEnterIndex += 2
332
+ tailExitIndex -= 2
333
+ break
334
+ }
335
+ }
336
+ }
337
+
338
+ // Merge adjacent spaces and data.
339
+ index = headEnterIndex - 1
340
+ tailExitIndex++
341
+
342
+ while (++index <= tailExitIndex) {
343
+ if (enter === undefined) {
344
+ if (
345
+ index !== tailExitIndex &&
346
+ events[index][1].type !== types.lineEnding
347
+ ) {
348
+ enter = index
349
+ }
350
+ } else if (
351
+ index === tailExitIndex ||
352
+ events[index][1].type === types.lineEnding
353
+ ) {
354
+ events[enter][1].type = 'mathTextData'
355
+
356
+ if (index !== enter + 2) {
357
+ events[enter][1].end = events[index - 1][1].end
358
+ events.splice(enter + 2, index - enter - 2)
359
+ tailExitIndex -= index - enter - 2
360
+ index = enter + 2
361
+ }
362
+
363
+ enter = undefined
364
+ }
365
+ }
366
+
367
+ return events
368
+ }
369
+
370
+ /**
371
+ * @this {TokenizeContext}
372
+ * @type {Previous}
373
+ */
374
+ function previousCode(code) {
375
+ // If there is a previous code, there will always be a tail.
376
+ return (
377
+ code !== codes.dollarSign ||
378
+ this.events[this.events.length - 1][1].type === types.characterEscape
379
+ )
380
+ }
381
+
382
+ /**
383
+ * @param {number | null} code
384
+ * Code.
385
+ * @returns {boolean}
386
+ * Whether `code` can follow an opening single dollar.
387
+ */
388
+ function validSingleDollarOpen(code) {
389
+ return code !== codes.eof && !markdownLineEnding(code) && !markdownSpace(code)
390
+ }
391
+
392
+ /**
393
+ * @param {number | null | undefined} before
394
+ * Code before the closing dollar.
395
+ * @param {number | null} after
396
+ * Code after the closing dollar.
397
+ * @returns {boolean}
398
+ * Whether a single dollar can close.
399
+ */
400
+ function validSingleDollarClose(before, after) {
401
+ return (
402
+ before !== undefined &&
403
+ before !== codes.eof &&
404
+ !markdownLineEnding(before) &&
405
+ !markdownSpace(before) &&
406
+ !asciiDigit(after)
407
+ )
408
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Create an extension for `micromark` to enable math syntax.
3
+ *
4
+ * @param {Options | null | undefined} [options={}]
5
+ * Configuration (default: `{}`).
6
+ * @returns {Extension}
7
+ * Extension for `micromark` that can be passed in `extensions`, to
8
+ * enable math syntax.
9
+ */
10
+ export function math(options?: Options | null | undefined): Extension;
11
+ import type { Options } from '@ziloen/micromark-extension-math';
12
+ import type { Extension } from 'micromark-util-types';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @import {Options} from '@ziloen/micromark-extension-math'
3
+ * @import {Extension} from 'micromark-util-types'
4
+ */
5
+
6
+ import {codes} from 'micromark-util-symbol'
7
+ import {mathFlow} from './math-flow.js'
8
+ import {mathText} from './math-text.js'
9
+
10
+ /**
11
+ * Create an extension for `micromark` to enable math syntax.
12
+ *
13
+ * @param {Options | null | undefined} [options={}]
14
+ * Configuration (default: `{}`).
15
+ * @returns {Extension}
16
+ * Extension for `micromark` that can be passed in `extensions`, to
17
+ * enable math syntax.
18
+ */
19
+ export function math(options) {
20
+ const text = mathText(options)
21
+
22
+ return {
23
+ flow: {[codes.dollarSign]: mathFlow, [codes.backslash]: mathFlow},
24
+ text: {[codes.dollarSign]: text, [codes.backslash]: text}
25
+ }
26
+ }
package/index.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ import type {KatexOptions} from 'katex'
2
+
3
+ export {mathHtml} from './lib/html.js'
4
+ export {math} from './lib/syntax.js'
5
+
6
+ /**
7
+ * Configuration for HTML output.
8
+ *
9
+ * > 👉 **Note**: passed to `katex.renderToString`.
10
+ * > `displayMode` is overwritten by this plugin, to `false` for math in
11
+ * > text (inline), and `true` for math in flow (block).
12
+ */
13
+ export interface HtmlOptions extends KatexOptions {
14
+ /**
15
+ * The field `displayMode` cannot be passed to `micromark-extension-math`.
16
+ * It is overwritten by it,
17
+ * to `false` for math in text (inline) and `true` for math in flow (block).
18
+ */
19
+ displayMode?: never
20
+ }
21
+
22
+ /**
23
+ * Configuration.
24
+ */
25
+ export interface Options {
26
+ /**
27
+ * Whether to support math (text) with a single dollar (default: `true`).
28
+ *
29
+ * Single dollars work in Pandoc and many other places, but often interfere
30
+ * with “normal” dollars in text.
31
+ * If you turn this off, you can use two or more dollars for text math.
32
+ */
33
+ singleDollarTextMath?: boolean | null | undefined
34
+ }
35
+
36
+ /**
37
+ * Augment types.
38
+ */
39
+ declare module 'micromark-util-types' {
40
+ /**
41
+ * Compile data.
42
+ */
43
+ interface CompileData {
44
+ mathFlowOpen?: boolean
45
+ }
46
+
47
+ /**
48
+ * Token types.
49
+ */
50
+ interface TokenTypeMap {
51
+ mathFlow: 'mathFlow'
52
+ mathFlowFence: 'mathFlowFence'
53
+ mathFlowFenceMeta: 'mathFlowFenceMeta'
54
+ mathFlowFenceSequence: 'mathFlowFenceSequence'
55
+ mathFlowValue: 'mathFlowValue'
56
+ mathText: 'mathText'
57
+ mathTextData: 'mathTextData'
58
+ mathTextDisplay: 'mathTextDisplay'
59
+ mathTextPadding: 'mathTextPadding'
60
+ mathTextSequence: 'mathTextSequence'
61
+ }
62
+ }
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Note: types exported from `index.d.ts`.
2
+ export { math } from './lib/syntax.js';
3
+ export { mathHtml } from './lib/html.js';
package/lib/html.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Create an extension for `micromark` to support math when serializing to
3
+ * HTML.
4
+ *
5
+ * > 👉 **Note**: this uses KaTeX to render math.
6
+ *
7
+ * @param {Options | null | undefined} [options={}]
8
+ * Configuration (default: `{}`).
9
+ * @returns {HtmlExtension}
10
+ * Extension for `micromark` that can be passed in `htmlExtensions`, to
11
+ * support math when serializing to HTML.
12
+ */
13
+ export function mathHtml(options?: Options | null | undefined): HtmlExtension;
14
+ import type { HtmlOptions as Options } from '@ziloen/micromark-extension-math';
15
+ import type { HtmlExtension } from 'micromark-util-types';
package/lib/html.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @import {HtmlOptions as Options} from '@ziloen/micromark-extension-math'
3
+ * @import {HtmlExtension} from 'micromark-util-types'
4
+ */
5
+
6
+ import katex from 'katex';
7
+ const renderToString = katex.renderToString;
8
+
9
+ /**
10
+ * Create an extension for `micromark` to support math when serializing to
11
+ * HTML.
12
+ *
13
+ * > 👉 **Note**: this uses KaTeX to render math.
14
+ *
15
+ * @param {Options | null | undefined} [options={}]
16
+ * Configuration (default: `{}`).
17
+ * @returns {HtmlExtension}
18
+ * Extension for `micromark` that can be passed in `htmlExtensions`, to
19
+ * support math when serializing to HTML.
20
+ */
21
+ export function mathHtml(options) {
22
+ return {
23
+ enter: {
24
+ mathFlow() {
25
+ this.lineEndingIfNeeded();
26
+ this.tag('<div class="math math-display">');
27
+ },
28
+ mathFlowFenceMeta() {
29
+ this.buffer();
30
+ },
31
+ mathText() {
32
+ // Double?
33
+ this.tag('<span class="math math-inline">');
34
+ this.buffer();
35
+ },
36
+ mathTextDisplay() {
37
+ this.tag('<div class="math math-display">');
38
+ this.buffer();
39
+ }
40
+ },
41
+ exit: {
42
+ mathFlow() {
43
+ const value = this.resume();
44
+ this.tag(math(value.replace(/(?:\r?\n|\r)$/, ''), true));
45
+ this.tag('</div>');
46
+ this.setData('mathFlowOpen');
47
+ this.setData('slurpOneLineEnding');
48
+ },
49
+ mathFlowFence() {
50
+ // After the first fence.
51
+ if (!this.getData('mathFlowOpen')) {
52
+ this.setData('mathFlowOpen', true);
53
+ this.setData('slurpOneLineEnding', true);
54
+ this.buffer();
55
+ }
56
+ },
57
+ mathFlowFenceMeta() {
58
+ this.resume();
59
+ },
60
+ mathFlowValue(token) {
61
+ this.raw(this.sliceSerialize(token));
62
+ },
63
+ mathText() {
64
+ const value = this.resume();
65
+ this.tag(math(value, false));
66
+ this.tag('</span>');
67
+ },
68
+ mathTextDisplay() {
69
+ const value = this.resume();
70
+ this.tag(math(value, true));
71
+ this.tag('</div>');
72
+ },
73
+ mathTextData(token) {
74
+ this.raw(this.sliceSerialize(token));
75
+ }
76
+ }
77
+ };
78
+
79
+ /**
80
+ * @param {string} value
81
+ * Math text.
82
+ * @param {boolean} displayMode
83
+ * Whether the math is in display mode.
84
+ * @returns {string}
85
+ * HTML.
86
+ */
87
+ function math(value, displayMode) {
88
+ return renderToString(value, {
89
+ ...options,
90
+ displayMode
91
+ });
92
+ }
93
+ }
@@ -0,0 +1,3 @@
1
+ /** @type {Construct} */
2
+ export const mathFlow: Construct;
3
+ import type { Construct } from 'micromark-util-types';