@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.
package/dev/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/dev/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'
@@ -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';
@@ -0,0 +1,91 @@
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
+
8
+ const renderToString = katex.renderToString
9
+
10
+ /**
11
+ * Create an extension for `micromark` to support math when serializing to
12
+ * HTML.
13
+ *
14
+ * > πŸ‘‰ **Note**: this uses KaTeX to render math.
15
+ *
16
+ * @param {Options | null | undefined} [options={}]
17
+ * Configuration (default: `{}`).
18
+ * @returns {HtmlExtension}
19
+ * Extension for `micromark` that can be passed in `htmlExtensions`, to
20
+ * support math when serializing to HTML.
21
+ */
22
+ export function mathHtml(options) {
23
+ return {
24
+ enter: {
25
+ mathFlow() {
26
+ this.lineEndingIfNeeded()
27
+ this.tag('<div class="math math-display">')
28
+ },
29
+ mathFlowFenceMeta() {
30
+ this.buffer()
31
+ },
32
+ mathText() {
33
+ // Double?
34
+ this.tag('<span class="math math-inline">')
35
+ this.buffer()
36
+ },
37
+ mathTextDisplay() {
38
+ this.tag('<div class="math math-display">')
39
+ this.buffer()
40
+ }
41
+ },
42
+ exit: {
43
+ mathFlow() {
44
+ const value = this.resume()
45
+ this.tag(math(value.replace(/(?:\r?\n|\r)$/, ''), true))
46
+ this.tag('</div>')
47
+ this.setData('mathFlowOpen')
48
+ this.setData('slurpOneLineEnding')
49
+ },
50
+ mathFlowFence() {
51
+ // After the first fence.
52
+ if (!this.getData('mathFlowOpen')) {
53
+ this.setData('mathFlowOpen', true)
54
+ this.setData('slurpOneLineEnding', true)
55
+ this.buffer()
56
+ }
57
+ },
58
+ mathFlowFenceMeta() {
59
+ this.resume()
60
+ },
61
+ mathFlowValue(token) {
62
+ this.raw(this.sliceSerialize(token))
63
+ },
64
+ mathText() {
65
+ const value = this.resume()
66
+ this.tag(math(value, false))
67
+ this.tag('</span>')
68
+ },
69
+ mathTextDisplay() {
70
+ const value = this.resume()
71
+ this.tag(math(value, true))
72
+ this.tag('</div>')
73
+ },
74
+ mathTextData(token) {
75
+ this.raw(this.sliceSerialize(token))
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {string} value
82
+ * Math text.
83
+ * @param {boolean} displayMode
84
+ * Whether the math is in display mode.
85
+ * @returns {string}
86
+ * HTML.
87
+ */
88
+ function math(value, displayMode) {
89
+ return renderToString(value, {...options, displayMode})
90
+ }
91
+ }
@@ -0,0 +1,3 @@
1
+ /** @type {Construct} */
2
+ export const mathFlow: Construct;
3
+ import type { Construct } from 'micromark-util-types';
@@ -0,0 +1,548 @@
1
+ /**
2
+ * @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
3
+ */
4
+
5
+ import {ok as assert} from 'devlop'
6
+ import {factorySpace} from 'micromark-factory-space'
7
+ import {markdownLineEnding} from 'micromark-util-character'
8
+ import {codes, constants, types} from 'micromark-util-symbol'
9
+
10
+ /** @type {Construct} */
11
+ export const mathFlow = {
12
+ tokenize: tokenizeMathFenced,
13
+ concrete: true,
14
+ name: 'mathFlow'
15
+ }
16
+
17
+ /** @type {Construct} */
18
+ const nonLazyContinuation = {
19
+ tokenize: tokenizeNonLazyContinuation,
20
+ partial: true
21
+ }
22
+
23
+ /**
24
+ * @this {TokenizeContext}
25
+ * @type {Tokenizer}
26
+ */
27
+ function tokenizeMathFenced(effects, ok, nok) {
28
+ const self = this
29
+ const tail = self.events[self.events.length - 1]
30
+ const initialSize =
31
+ tail && tail[1].type === types.linePrefix
32
+ ? tail[2].sliceSerialize(tail[1], true).length
33
+ : 0
34
+ let sizeOpen = 0
35
+ let backslashContentSeen = false
36
+ let isBackslashMath = false
37
+
38
+ return start
39
+
40
+ /**
41
+ * Start of math.
42
+ *
43
+ * ```markdown
44
+ * > | $$
45
+ * ^
46
+ * | \frac{1}{2}
47
+ * | $$
48
+ * ```
49
+ *
50
+ * @type {State}
51
+ */
52
+ function start(code) {
53
+ assert(
54
+ code === codes.dollarSign || code === codes.backslash,
55
+ 'expected `$` or `\\`'
56
+ )
57
+ effects.enter('mathFlow')
58
+ effects.enter('mathFlowFence')
59
+ effects.enter('mathFlowFenceSequence')
60
+
61
+ if (code === codes.backslash) {
62
+ effects.consume(code)
63
+ return sequenceOpenBackslash
64
+ }
65
+
66
+ return sequenceOpen(code)
67
+ }
68
+
69
+ /**
70
+ * In opening fence sequence.
71
+ *
72
+ * ```markdown
73
+ * > | $$
74
+ * ^
75
+ * | \frac{1}{2}
76
+ * | $$
77
+ * ```
78
+ *
79
+ * @type {State}
80
+ */
81
+ function sequenceOpen(code) {
82
+ if (code === codes.dollarSign) {
83
+ effects.consume(code)
84
+ sizeOpen++
85
+ return sequenceOpen
86
+ }
87
+
88
+ if (sizeOpen < 2) {
89
+ return nok(code)
90
+ }
91
+
92
+ effects.exit('mathFlowFenceSequence')
93
+ return factorySpace(effects, metaBefore, types.whitespace)(code)
94
+ }
95
+
96
+ /**
97
+ * After the backslash in an opening fence sequence.
98
+ *
99
+ * ```markdown
100
+ * > | \[
101
+ * ^
102
+ * | \frac{1}{2}
103
+ * | \]
104
+ * ```
105
+ *
106
+ * @type {State}
107
+ */
108
+ function sequenceOpenBackslash(code) {
109
+ if (code !== codes.leftSquareBracket) {
110
+ return nok(code)
111
+ }
112
+
113
+ isBackslashMath = true
114
+ effects.consume(code)
115
+ effects.exit('mathFlowFenceSequence')
116
+ effects.exit('mathFlowFence')
117
+
118
+ if (self.interrupt) {
119
+ return ok
120
+ }
121
+
122
+ return backslashContentStart
123
+ }
124
+
125
+ /**
126
+ * In opening fence, before meta.
127
+ *
128
+ * ```markdown
129
+ * > | $$asciimath
130
+ * ^
131
+ * | x < y
132
+ * | $$
133
+ * ```
134
+ *
135
+ * @type {State}
136
+ */
137
+
138
+ function metaBefore(code) {
139
+ if (code === codes.eof || markdownLineEnding(code)) {
140
+ return metaAfter(code)
141
+ }
142
+
143
+ effects.enter('mathFlowFenceMeta')
144
+ effects.enter(types.chunkString, {contentType: constants.contentTypeString})
145
+ return meta(code)
146
+ }
147
+
148
+ /**
149
+ * In meta.
150
+ *
151
+ * ```markdown
152
+ * > | $$asciimath
153
+ * ^
154
+ * | x < y
155
+ * | $$
156
+ * ```
157
+ *
158
+ * @type {State}
159
+ */
160
+ function meta(code) {
161
+ if (code === codes.eof || markdownLineEnding(code)) {
162
+ effects.exit(types.chunkString)
163
+ effects.exit('mathFlowFenceMeta')
164
+ return metaAfter(code)
165
+ }
166
+
167
+ if (code === codes.dollarSign) {
168
+ return nok(code)
169
+ }
170
+
171
+ effects.consume(code)
172
+ return meta
173
+ }
174
+
175
+ /**
176
+ * After meta.
177
+ *
178
+ * ```markdown
179
+ * > | $$
180
+ * ^
181
+ * | \frac{1}{2}
182
+ * | $$
183
+ * ```
184
+ *
185
+ * @type {State}
186
+ */
187
+ function metaAfter(code) {
188
+ // Guaranteed to be eol/eof.
189
+ effects.exit('mathFlowFence')
190
+
191
+ if (self.interrupt) {
192
+ return ok(code)
193
+ }
194
+
195
+ return effects.attempt(
196
+ nonLazyContinuation,
197
+ beforeNonLazyContinuation,
198
+ after
199
+ )(code)
200
+ }
201
+
202
+ /**
203
+ * After eol/eof in math, at a non-lazy closing fence or content.
204
+ *
205
+ * ```markdown
206
+ * | $$
207
+ * > | \frac{1}{2}
208
+ * ^
209
+ * > | $$
210
+ * ^
211
+ * ```
212
+ *
213
+ * @type {State}
214
+ */
215
+ function beforeNonLazyContinuation(code) {
216
+ return effects.attempt(
217
+ {tokenize: tokenizeClosingFence, partial: true},
218
+ after,
219
+ contentStart
220
+ )(code)
221
+ }
222
+
223
+ /**
224
+ * Before math content, definitely not before a closing fence.
225
+ *
226
+ * ```markdown
227
+ * | $$
228
+ * > | \frac{1}{2}
229
+ * ^
230
+ * | $$
231
+ * ```
232
+ *
233
+ * @type {State}
234
+ */
235
+ function contentStart(code) {
236
+ return (
237
+ initialSize
238
+ ? factorySpace(
239
+ effects,
240
+ beforeContentChunk,
241
+ types.linePrefix,
242
+ initialSize + 1
243
+ )
244
+ : beforeContentChunk
245
+ )(code)
246
+ }
247
+
248
+ /**
249
+ * Before math content, after optional prefix.
250
+ *
251
+ * ```markdown
252
+ * | $$
253
+ * > | \frac{1}{2}
254
+ * ^
255
+ * | $$
256
+ * ```
257
+ *
258
+ * @type {State}
259
+ */
260
+ function beforeContentChunk(code) {
261
+ if (code === codes.eof) {
262
+ return after(code)
263
+ }
264
+
265
+ if (markdownLineEnding(code)) {
266
+ return effects.attempt(
267
+ nonLazyContinuation,
268
+ beforeNonLazyContinuation,
269
+ after
270
+ )(code)
271
+ }
272
+
273
+ effects.enter('mathFlowValue')
274
+ return contentChunk(code)
275
+ }
276
+
277
+ /**
278
+ * In math content.
279
+ *
280
+ * ```markdown
281
+ * | $$
282
+ * > | \frac{1}{2}
283
+ * ^
284
+ * | $$
285
+ * ```
286
+ *
287
+ * @type {State}
288
+ */
289
+ function contentChunk(code) {
290
+ if (code === codes.eof || markdownLineEnding(code)) {
291
+ effects.exit('mathFlowValue')
292
+ return beforeContentChunk(code)
293
+ }
294
+
295
+ effects.consume(code)
296
+ return contentChunk
297
+ }
298
+
299
+ /**
300
+ * After math (ha!).
301
+ *
302
+ * ```markdown
303
+ * | $$
304
+ * | \frac{1}{2}
305
+ * > | $$
306
+ * ^
307
+ * ```
308
+ *
309
+ * @type {State}
310
+ */
311
+ function after(code) {
312
+ if (isBackslashMath && !backslashContentSeen) {
313
+ return nok(code)
314
+ }
315
+
316
+ effects.exit('mathFlow')
317
+ return ok(code)
318
+ }
319
+
320
+ /**
321
+ * Before math content or a closing backslash fence.
322
+ *
323
+ * @type {State}
324
+ */
325
+ function backslashContentStart(code) {
326
+ return effects.attempt(
327
+ {tokenize: tokenizeClosingBackslashFence, partial: true},
328
+ after,
329
+ backslashBeforeContentChunk
330
+ )(code)
331
+ }
332
+
333
+ /**
334
+ * Before math content, definitely not before a closing backslash fence.
335
+ *
336
+ * @type {State}
337
+ */
338
+ function backslashBeforeContentChunk(code) {
339
+ if (code === codes.eof) {
340
+ return after(code)
341
+ }
342
+
343
+ if (markdownLineEnding(code)) {
344
+ return effects.attempt(
345
+ nonLazyContinuation,
346
+ backslashContentStart,
347
+ after
348
+ )(code)
349
+ }
350
+
351
+ if (code === codes.backslash) {
352
+ effects.enter('mathFlowValue')
353
+ effects.consume(code)
354
+ backslashContentSeen = true
355
+ effects.exit('mathFlowValue')
356
+ return backslashBeforeContentChunk
357
+ }
358
+
359
+ effects.enter('mathFlowValue')
360
+ return backslashContentChunk(code)
361
+ }
362
+
363
+ /**
364
+ * In backslash-delimited math content.
365
+ *
366
+ * @type {State}
367
+ */
368
+ function backslashContentChunk(code) {
369
+ if (code === codes.eof || markdownLineEnding(code)) {
370
+ effects.exit('mathFlowValue')
371
+ return backslashBeforeContentChunk(code)
372
+ }
373
+
374
+ if (code === codes.backslash) {
375
+ effects.exit('mathFlowValue')
376
+ return backslashContentStart(code)
377
+ }
378
+
379
+ effects.consume(code)
380
+ backslashContentSeen = true
381
+ return backslashContentChunk
382
+ }
383
+
384
+ /** @type {Tokenizer} */
385
+ function tokenizeClosingFence(effects, ok, nok) {
386
+ let size = 0
387
+
388
+ assert(self.parser.constructs.disable.null, 'expected `disable.null`')
389
+ /**
390
+ * Before closing fence, at optional whitespace.
391
+ *
392
+ * ```markdown
393
+ * | $$
394
+ * | \frac{1}{2}
395
+ * > | $$
396
+ * ^
397
+ * ```
398
+ */
399
+ return factorySpace(
400
+ effects,
401
+ beforeSequenceClose,
402
+ types.linePrefix,
403
+ self.parser.constructs.disable.null.includes('codeIndented')
404
+ ? undefined
405
+ : constants.tabSize
406
+ )
407
+
408
+ /**
409
+ * In closing fence, after optional whitespace, at sequence.
410
+ *
411
+ * ```markdown
412
+ * | $$
413
+ * | \frac{1}{2}
414
+ * > | $$
415
+ * ^
416
+ * ```
417
+ *
418
+ * @type {State}
419
+ */
420
+ function beforeSequenceClose(code) {
421
+ effects.enter('mathFlowFence')
422
+ effects.enter('mathFlowFenceSequence')
423
+ return sequenceClose(code)
424
+ }
425
+
426
+ /**
427
+ * In closing fence sequence.
428
+ *
429
+ * ```markdown
430
+ * | $$
431
+ * | \frac{1}{2}
432
+ * > | $$
433
+ * ^
434
+ * ```
435
+ *
436
+ * @type {State}
437
+ */
438
+ function sequenceClose(code) {
439
+ if (code === codes.dollarSign) {
440
+ size++
441
+ effects.consume(code)
442
+ return sequenceClose
443
+ }
444
+
445
+ if (size < sizeOpen) {
446
+ return nok(code)
447
+ }
448
+
449
+ effects.exit('mathFlowFenceSequence')
450
+ return factorySpace(effects, afterSequenceClose, types.whitespace)(code)
451
+ }
452
+
453
+ /**
454
+ * After closing fence sequence, after optional whitespace.
455
+ *
456
+ * ```markdown
457
+ * | $$
458
+ * | \frac{1}{2}
459
+ * > | $$
460
+ * ^
461
+ * ```
462
+ *
463
+ * @type {State}
464
+ */
465
+ function afterSequenceClose(code) {
466
+ if (code === codes.eof || markdownLineEnding(code)) {
467
+ effects.exit('mathFlowFence')
468
+ return ok(code)
469
+ }
470
+
471
+ return nok(code)
472
+ }
473
+ }
474
+
475
+ /** @type {Tokenizer} */
476
+ function tokenizeClosingBackslashFence(effects, ok, nok) {
477
+ return beforeSequenceClose
478
+
479
+ /**
480
+ * In closing fence sequence.
481
+ *
482
+ * @type {State}
483
+ */
484
+ function beforeSequenceClose(code) {
485
+ if (code !== codes.backslash) {
486
+ return nok(code)
487
+ }
488
+
489
+ effects.enter('mathFlowFence')
490
+ effects.enter('mathFlowFenceSequence')
491
+ effects.consume(code)
492
+ return sequenceClose
493
+ }
494
+
495
+ /**
496
+ * After the backslash in a closing fence sequence.
497
+ *
498
+ * @type {State}
499
+ */
500
+ function sequenceClose(code) {
501
+ if (code !== codes.rightSquareBracket) {
502
+ return nok(code)
503
+ }
504
+
505
+ effects.consume(code)
506
+ effects.exit('mathFlowFenceSequence')
507
+ return factorySpace(effects, afterSequenceClose, types.whitespace)
508
+ }
509
+
510
+ /**
511
+ * After closing fence sequence, after optional whitespace.
512
+ *
513
+ * @type {State}
514
+ */
515
+ function afterSequenceClose(code) {
516
+ effects.exit('mathFlowFence')
517
+ return ok(code)
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * @this {TokenizeContext}
524
+ * @type {Tokenizer}
525
+ */
526
+ function tokenizeNonLazyContinuation(effects, ok, nok) {
527
+ const self = this
528
+
529
+ return start
530
+
531
+ /** @type {State} */
532
+ function start(code) {
533
+ if (code === null) {
534
+ return ok(code)
535
+ }
536
+
537
+ assert(markdownLineEnding(code), 'expected eol')
538
+ effects.enter(types.lineEnding)
539
+ effects.consume(code)
540
+ effects.exit(types.lineEnding)
541
+ return lineStart
542
+ }
543
+
544
+ /** @type {State} */
545
+ function lineStart(code) {
546
+ return self.parser.lazy[self.now().line] ? nok(code) : ok(code)
547
+ }
548
+ }