safe-mdx 0.0.5 → 0.1.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/dist/safe-mdx.js CHANGED
@@ -2,75 +2,37 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { cloneElement } from 'react';
3
3
  import { htmlToJsx } from 'html-to-jsx-transform';
4
4
  import remarkFrontmatter from 'remark-frontmatter';
5
+ import { collapseWhiteSpace } from 'collapse-white-space';
6
+ import { visit } from 'unist-util-visit';
5
7
  import { remark } from 'remark';
6
8
  import remarkGfm from 'remark-gfm';
7
9
  import remarkMdx from 'remark-mdx';
8
10
  import { Fragment } from 'react';
9
- const mdxParser = remark()
10
- .use(remarkFrontmatter, ['yaml', 'toml'])
11
- .use(remarkGfm)
12
- .use(remarkMdx);
13
- void React;
14
- export function SafeMdxRenderer({ components, code = '', mdast = null, }) {
15
- const visitor = new MdastToJsx({ code, mdast, components });
11
+ export function mdxParse(code) {
12
+ const file = mdxProcessor.processSync(code);
13
+ return file.data.ast;
14
+ }
15
+ export function SafeMdxRenderer({ components, code = '', mdast = null, customTransformer, }) {
16
+ const visitor = new MdastToJsx({
17
+ code,
18
+ mdast,
19
+ components,
20
+ customTransformer,
21
+ });
16
22
  const result = visitor.run();
17
23
  return result;
18
24
  }
19
- const nativeTags = [
20
- 'blockquote',
21
- 'strong',
22
- 'em',
23
- 'del',
24
- 'hr',
25
- 'a',
26
- 'b',
27
- 'br',
28
- 'button',
29
- 'div',
30
- 'form',
31
- 'h1',
32
- 'h2',
33
- 'h3',
34
- 'h4',
35
- 'head',
36
- 'iframe',
37
- 'img',
38
- 'input',
39
- 'label',
40
- 'li',
41
- 'link',
42
- 'ol',
43
- 'p',
44
- 'path',
45
- 'picture',
46
- 'script',
47
- 'section',
48
- 'source',
49
- 'span',
50
- 'sub',
51
- 'sup',
52
- 'svg',
53
- 'table',
54
- 'tbody',
55
- 'td',
56
- 'tfoot',
57
- 'th',
58
- 'thead',
59
- 'tr',
60
- 'ul',
61
- 'video',
62
- 'code',
63
- 'pre',
64
- ];
65
25
  export class MdastToJsx {
66
26
  mdast;
67
27
  str;
68
28
  jsxStr = '';
69
29
  c;
70
30
  errors = [];
71
- constructor({ code = '', mdast = undefined, components = {}, }) {
31
+ customTransformer;
32
+ constructor({ code = '', mdast = undefined, components = {}, customTransformer, }) {
72
33
  this.str = code;
73
- this.mdast = mdast || mdxParser.parse(code);
34
+ this.mdast = mdast || mdxParse(code);
35
+ this.customTransformer = customTransformer;
74
36
  this.c = {
75
37
  ...Object.fromEntries(nativeTags.map((tag) => {
76
38
  return [tag, tag];
@@ -151,6 +113,13 @@ export class MdastToJsx {
151
113
  if (!node) {
152
114
  return [];
153
115
  }
116
+ // Check for custom transformer first, giving it higher priority
117
+ if (this.customTransformer) {
118
+ const customResult = this.customTransformer(node, this.mdastTransformer.bind(this));
119
+ if (customResult !== undefined) {
120
+ return customResult;
121
+ }
122
+ }
154
123
  switch (node.type) {
155
124
  case 'mdxjsEsm': {
156
125
  const start = node.position?.start?.offset;
@@ -194,13 +163,13 @@ export class MdastToJsx {
194
163
  case 'heading': {
195
164
  const level = node.depth;
196
165
  const Tag = this.c[`h${level}`] ?? `h${level}`;
197
- return _jsx(Tag, { children: this.mapMdastChildren(node) });
166
+ return (_jsx(Tag, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
198
167
  }
199
168
  case 'paragraph': {
200
- return _jsx(this.c.p, { children: this.mapMdastChildren(node) });
169
+ return (_jsx(this.c.p, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
201
170
  }
202
171
  case 'blockquote': {
203
- return (_jsx(this.c.blockquote, { children: this.mapMdastChildren(node) }));
172
+ return (_jsx(this.c.blockquote, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
204
173
  }
205
174
  case 'thematicBreak': {
206
175
  return _jsx(this.c.hr, {});
@@ -211,20 +180,32 @@ export class MdastToJsx {
211
180
  }
212
181
  const language = node.lang || '';
213
182
  const code = node.value;
214
- return (_jsx(this.c.pre, { children: _jsx(this.c.code, { children: code }) }));
183
+ const codeBlock = (className) => (_jsx(this.c.pre, { ...node.data?.hProperties, children: _jsx(this.c.code, { className: className, children: code }) }));
184
+ if (language) {
185
+ if (supportedLanguagesSet.has(language)) {
186
+ return codeBlock(`language-${language}`);
187
+ }
188
+ else {
189
+ this.errors.push({
190
+ message: `Unsupported language ${language}`,
191
+ });
192
+ return codeBlock();
193
+ }
194
+ }
195
+ return codeBlock();
215
196
  }
216
197
  case 'list': {
217
198
  if (node.ordered) {
218
- return (_jsx(this.c.ol, { start: node.start, children: this.mapMdastChildren(node) }));
199
+ return (_jsx(this.c.ol, { start: node.start, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
219
200
  }
220
- return _jsx(this.c.ul, { children: this.mapMdastChildren(node) });
201
+ return (_jsx(this.c.ul, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
221
202
  }
222
203
  case 'listItem': {
223
204
  // https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
224
205
  if (node?.checked != null) {
225
- return (_jsx(this.c.li, { "data-checked": node.checked, children: this.mapMdastChildren(node) }));
206
+ return (_jsx(this.c.li, { "data-checked": node.checked, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
226
207
  }
227
- return _jsx(this.c.li, { children: this.mapMdastChildren(node) });
208
+ return (_jsx(this.c.li, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
228
209
  }
229
210
  case 'text': {
230
211
  if (!node.value) {
@@ -236,27 +217,27 @@ export class MdastToJsx {
236
217
  const src = node.url || '';
237
218
  const alt = node.alt || '';
238
219
  const title = node.title || '';
239
- return _jsx(this.c.img, { src: src, alt: alt, title: title });
220
+ return (_jsx(this.c.img, { src: src, alt: alt, title: title, ...node.data?.hProperties }));
240
221
  }
241
222
  case 'link': {
242
223
  const href = node.url || '';
243
224
  const title = node.title || '';
244
- return (_jsx(this.c.a, { href, title, children: this.mapMdastChildren(node) }));
225
+ return (_jsx(this.c.a, { href, title, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
245
226
  }
246
227
  case 'strong': {
247
- return (_jsx(this.c.strong, { children: this.mapMdastChildren(node) }));
228
+ return (_jsx(this.c.strong, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
248
229
  }
249
230
  case 'emphasis': {
250
- return _jsx(this.c.em, { children: this.mapMdastChildren(node) });
231
+ return (_jsx(this.c.em, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
251
232
  }
252
233
  case 'delete': {
253
- return _jsx(this.c.del, { children: this.mapMdastChildren(node) });
234
+ return (_jsx(this.c.del, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
254
235
  }
255
236
  case 'inlineCode': {
256
237
  if (!node.value) {
257
238
  return [];
258
239
  }
259
- return _jsx(this.c.code, { children: node.value });
240
+ return (_jsx(this.c.code, { ...node.data?.hProperties, children: node.value }));
260
241
  }
261
242
  case 'break': {
262
243
  return _jsx(this.c.br, {});
@@ -266,14 +247,14 @@ export class MdastToJsx {
266
247
  }
267
248
  case 'table': {
268
249
  const [head, ...body] = React.Children.toArray(this.mapMdastChildren(node));
269
- return (_jsxs(this.c.table, { children: [head && _jsx(this.c.thead, { children: head }), !!body?.length && _jsx(this.c.tbody, { children: body })] }));
250
+ return (_jsxs(this.c.table, { ...node.data?.hProperties, children: [head && _jsx(this.c.thead, { children: head }), !!body?.length && _jsx(this.c.tbody, { children: body })] }));
270
251
  }
271
252
  case 'tableRow': {
272
- return (_jsx(this.c.tr, { className: '', children: this.mapMdastChildren(node) }));
253
+ return (_jsx(this.c.tr, { className: '', ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
273
254
  }
274
255
  case 'tableCell': {
275
256
  let content = this.mapMdastChildren(node);
276
- return _jsx(this.c.td, { className: '', children: content });
257
+ return (_jsx(this.c.td, { className: '', ...node.data?.hProperties, children: content }));
277
258
  }
278
259
  case 'definition': {
279
260
  return [];
@@ -286,7 +267,7 @@ export class MdastToJsx {
286
267
  href = child.url;
287
268
  }
288
269
  });
289
- return (_jsx(this.c.a, { href: href, children: this.mapMdastChildren(node) }));
270
+ return (_jsx(this.c.a, { href: href, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
290
271
  }
291
272
  case 'footnoteReference': {
292
273
  return [];
@@ -335,7 +316,9 @@ export function getJsxAttrs(node, onError = console.error) {
335
316
  .map((attr) => {
336
317
  if (attr.type === 'mdxJsxExpressionAttribute') {
337
318
  onError({
338
- message: `Expressions in jsx props are not supported (${attr.value.replace(/\n+/g, ' ')})`,
319
+ message: `Expressions in jsx props are not supported (${attr.value
320
+ .replace(/\n+/g, ' ')
321
+ .replace(/ +/g, ' ')})`,
339
322
  });
340
323
  return;
341
324
  }
@@ -426,4 +409,421 @@ function safeJsonParse(str) {
426
409
  return null;
427
410
  }
428
411
  }
412
+ const nativeTags = [
413
+ 'blockquote',
414
+ 'strong',
415
+ 'em',
416
+ 'del',
417
+ 'hr',
418
+ 'a',
419
+ 'b',
420
+ 'br',
421
+ 'button',
422
+ 'div',
423
+ 'form',
424
+ 'h1',
425
+ 'h2',
426
+ 'h3',
427
+ 'h4',
428
+ 'head',
429
+ 'iframe',
430
+ 'img',
431
+ 'input',
432
+ 'label',
433
+ 'li',
434
+ 'link',
435
+ 'ol',
436
+ 'p',
437
+ 'path',
438
+ 'picture',
439
+ 'script',
440
+ 'section',
441
+ 'source',
442
+ 'span',
443
+ 'sub',
444
+ 'sup',
445
+ 'svg',
446
+ 'table',
447
+ 'tbody',
448
+ 'td',
449
+ 'tfoot',
450
+ 'th',
451
+ 'thead',
452
+ 'tr',
453
+ 'ul',
454
+ 'video',
455
+ 'code',
456
+ 'pre',
457
+ 'figure',
458
+ 'canvas',
459
+ 'details',
460
+ 'dl',
461
+ 'dt',
462
+ 'dd',
463
+ 'fieldset',
464
+ 'footer',
465
+ 'header',
466
+ 'legend',
467
+ 'main',
468
+ 'mark',
469
+ 'nav',
470
+ 'progress',
471
+ 'summary',
472
+ 'time',
473
+ ];
474
+ const supportedLanguages = [
475
+ 'abap',
476
+ 'abnf',
477
+ 'actionscript',
478
+ 'ada',
479
+ 'agda',
480
+ 'al',
481
+ 'antlr4',
482
+ 'apacheconf',
483
+ 'apex',
484
+ 'apl',
485
+ 'applescript',
486
+ 'aql',
487
+ 'arduino',
488
+ 'arff',
489
+ 'asciidoc',
490
+ 'asm6502',
491
+ 'asmatmel',
492
+ 'aspnet',
493
+ 'autohotkey',
494
+ 'autoit',
495
+ 'avisynth',
496
+ 'avro-idl',
497
+ 'bash',
498
+ 'basic',
499
+ 'batch',
500
+ 'bbcode',
501
+ 'bicep',
502
+ 'birb',
503
+ 'bison',
504
+ 'bnf',
505
+ 'brainfuck',
506
+ 'brightscript',
507
+ 'bro',
508
+ 'bsl',
509
+ 'c',
510
+ 'cfscript',
511
+ 'chaiscript',
512
+ 'cil',
513
+ 'clike',
514
+ 'clojure',
515
+ 'cmake',
516
+ 'cobol',
517
+ 'coffeescript',
518
+ 'concurnas',
519
+ 'coq',
520
+ 'cpp',
521
+ 'crystal',
522
+ 'csharp',
523
+ 'cshtml',
524
+ 'csp',
525
+ 'css-extras',
526
+ 'css',
527
+ 'csv',
528
+ 'cypher',
529
+ 'd',
530
+ 'dart',
531
+ 'dataweave',
532
+ 'dax',
533
+ 'dhall',
534
+ 'diff',
535
+ 'django',
536
+ 'dns-zone-file',
537
+ 'docker',
538
+ 'dot',
539
+ 'ebnf',
540
+ 'editorconfig',
541
+ 'eiffel',
542
+ 'ejs',
543
+ 'elixir',
544
+ 'elm',
545
+ 'erb',
546
+ 'erlang',
547
+ 'etlua',
548
+ 'excel-formula',
549
+ 'factor',
550
+ 'false',
551
+ 'firestore-security-rules',
552
+ 'flow',
553
+ 'fortran',
554
+ 'fsharp',
555
+ 'ftl',
556
+ 'gap',
557
+ 'gcode',
558
+ 'gdscript',
559
+ 'gedcom',
560
+ 'gherkin',
561
+ 'git',
562
+ 'glsl',
563
+ 'gml',
564
+ 'gn',
565
+ 'go-module',
566
+ 'go',
567
+ 'graphql',
568
+ 'groovy',
569
+ 'haml',
570
+ 'handlebars',
571
+ 'haskell',
572
+ 'haxe',
573
+ 'hcl',
574
+ 'hlsl',
575
+ 'hoon',
576
+ 'hpkp',
577
+ 'hsts',
578
+ 'http',
579
+ 'ichigojam',
580
+ 'icon',
581
+ 'icu-message-format',
582
+ 'idris',
583
+ 'iecst',
584
+ 'ignore',
585
+ 'inform7',
586
+ 'ini',
587
+ 'io',
588
+ 'j',
589
+ 'java',
590
+ 'javadoc',
591
+ 'javadoclike',
592
+ 'javascript',
593
+ 'javastacktrace',
594
+ 'jexl',
595
+ 'jolie',
596
+ 'jq',
597
+ 'js-extras',
598
+ 'js-templates',
599
+ 'jsdoc',
600
+ 'json',
601
+ 'json5',
602
+ 'jsonp',
603
+ 'jsstacktrace',
604
+ 'jsx',
605
+ 'julia',
606
+ 'keepalived',
607
+ 'keyman',
608
+ 'kotlin',
609
+ 'kumir',
610
+ 'kusto',
611
+ 'latex',
612
+ 'latte',
613
+ 'less',
614
+ 'lilypond',
615
+ 'liquid',
616
+ 'lisp',
617
+ 'livescript',
618
+ 'llvm',
619
+ 'log',
620
+ 'lolcode',
621
+ 'lua',
622
+ 'magma',
623
+ 'makefile',
624
+ 'markdown',
625
+ 'markup-templating',
626
+ 'markup',
627
+ 'matlab',
628
+ 'maxscript',
629
+ 'mel',
630
+ 'mermaid',
631
+ 'mizar',
632
+ 'mongodb',
633
+ 'monkey',
634
+ 'moonscript',
635
+ 'n1ql',
636
+ 'n4js',
637
+ 'nand2tetris-hdl',
638
+ 'naniscript',
639
+ 'nasm',
640
+ 'neon',
641
+ 'nevod',
642
+ 'nginx',
643
+ 'nim',
644
+ 'nix',
645
+ 'nsis',
646
+ 'objectivec',
647
+ 'ocaml',
648
+ 'opencl',
649
+ 'openqasm',
650
+ 'oz',
651
+ 'parigp',
652
+ 'parser',
653
+ 'pascal',
654
+ 'pascaligo',
655
+ 'pcaxis',
656
+ 'peoplecode',
657
+ 'perl',
658
+ 'php-extras',
659
+ 'php',
660
+ 'phpdoc',
661
+ 'plsql',
662
+ 'powerquery',
663
+ 'powershell',
664
+ 'processing',
665
+ 'prolog',
666
+ 'promql',
667
+ 'properties',
668
+ 'protobuf',
669
+ 'psl',
670
+ 'pug',
671
+ 'puppet',
672
+ 'pure',
673
+ 'purebasic',
674
+ 'purescript',
675
+ 'python',
676
+ 'q',
677
+ 'qml',
678
+ 'qore',
679
+ 'qsharp',
680
+ 'r',
681
+ 'racket',
682
+ 'reason',
683
+ 'regex',
684
+ 'rego',
685
+ 'renpy',
686
+ 'rest',
687
+ 'rip',
688
+ 'roboconf',
689
+ 'robotframework',
690
+ 'ruby',
691
+ 'rust',
692
+ 'sas',
693
+ 'sass',
694
+ 'scala',
695
+ 'scheme',
696
+ 'scss',
697
+ 'shell-session',
698
+ 'smali',
699
+ 'smalltalk',
700
+ 'smarty',
701
+ 'sml',
702
+ 'solidity',
703
+ 'solution-file',
704
+ 'soy',
705
+ 'sparql',
706
+ 'splunk-spl',
707
+ 'sqf',
708
+ 'sql',
709
+ 'squirrel',
710
+ 'stan',
711
+ 'stylus',
712
+ 'swift',
713
+ 'systemd',
714
+ 't4-cs',
715
+ 't4-templating',
716
+ 't4-vb',
717
+ 'tap',
718
+ 'tcl',
719
+ 'textile',
720
+ 'toml',
721
+ 'tremor',
722
+ 'tsx',
723
+ 'tt2',
724
+ 'turtle',
725
+ 'twig',
726
+ 'typescript',
727
+ 'typoscript',
728
+ 'unrealscript',
729
+ 'uorazor',
730
+ 'uri',
731
+ 'v',
732
+ 'vala',
733
+ 'vbnet',
734
+ 'velocity',
735
+ 'verilog',
736
+ 'vhdl',
737
+ 'vim',
738
+ 'visual-basic',
739
+ 'warpscript',
740
+ 'wasm',
741
+ 'web-idl',
742
+ 'wiki',
743
+ 'wolfram',
744
+ 'wren',
745
+ 'xeora',
746
+ 'xml-doc',
747
+ 'xojo',
748
+ 'xquery',
749
+ 'yaml',
750
+ 'yang',
751
+ 'zig',
752
+ ];
753
+ const supportedLanguagesSet = new Set(supportedLanguages);
754
+ /**
755
+ * https://github.com/mdx-js/mdx/blob/b3351fadcb6f78833a72757b7135dcfb8ab646fe/packages/mdx/lib/plugin/remark-mark-and-unravel.js
756
+ * A tiny plugin that unravels `<p><h1>x</h1></p>` but also
757
+ * `<p><Component /></p>` (so it has no knowledge of "HTML").
758
+ *
759
+ * It also marks JSX as being explicitly JSX, so when a user passes a `h1`
760
+ * component, it is used for `# heading` but not for `<h1>heading</h1>`.
761
+ *
762
+ */
763
+ export function remarkMarkAndUnravel() {
764
+ return function (tree) {
765
+ visit(tree, function (node, index, parent) {
766
+ let offset = -1;
767
+ let all = true;
768
+ let oneOrMore = false;
769
+ if (parent &&
770
+ typeof index === 'number' &&
771
+ node.type === 'paragraph') {
772
+ const children = node.children;
773
+ while (++offset < children.length) {
774
+ const child = children[offset];
775
+ if (child.type === 'mdxJsxTextElement' ||
776
+ child.type === 'mdxTextExpression') {
777
+ oneOrMore = true;
778
+ }
779
+ else if (child.type === 'text' &&
780
+ collapseWhiteSpace(child.value, {
781
+ style: 'html',
782
+ trim: true,
783
+ }) === '') {
784
+ // Empty.
785
+ }
786
+ else {
787
+ all = false;
788
+ break;
789
+ }
790
+ }
791
+ if (all && oneOrMore) {
792
+ offset = -1;
793
+ const newChildren = [];
794
+ while (++offset < children.length) {
795
+ const child = children[offset];
796
+ if (child.type === 'mdxJsxTextElement') {
797
+ // @ts-expect-error: mutate because it is faster; content model is fine.
798
+ child.type = 'mdxJsxFlowElement';
799
+ }
800
+ if (child.type === 'mdxTextExpression') {
801
+ // @ts-expect-error: mutate because it is faster; content model is fine.
802
+ child.type = 'mdxFlowExpression';
803
+ }
804
+ if (child.type === 'text' &&
805
+ /^[\t\r\n ]+$/.test(String(child.value))) {
806
+ // Empty.
807
+ }
808
+ else {
809
+ newChildren.push(child);
810
+ }
811
+ }
812
+ parent.children.splice(index, 1, ...newChildren);
813
+ return index;
814
+ }
815
+ }
816
+ });
817
+ };
818
+ }
819
+ const mdxProcessor = remark()
820
+ .use(remarkMdx)
821
+ .use(remarkFrontmatter, ['yaml', 'toml'])
822
+ .use(remarkGfm)
823
+ .use(remarkMarkAndUnravel)
824
+ .use(() => {
825
+ return (tree, file) => {
826
+ file.data.ast = tree;
827
+ };
828
+ });
429
829
  //# sourceMappingURL=safe-mdx.js.map