safe-mdx 0.0.0 → 0.0.1
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/README.md +45 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/safe-mdx.d.ts +31 -0
- package/dist/safe-mdx.d.ts.map +1 -1
- package/dist/safe-mdx.js +67 -29
- package/dist/safe-mdx.js.map +1 -1
- package/dist/safe-mdx.test.d.ts +2 -0
- package/dist/safe-mdx.test.d.ts.map +1 -0
- package/dist/safe-mdx.test.js +1551 -0
- package/dist/safe-mdx.test.js.map +1 -0
- package/package.json +3 -1
- package/src/index.ts +1 -0
- package/src/safe-mdx.test.tsx +1566 -0
- package/src/safe-mdx.tsx +67 -43
package/src/safe-mdx.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React, { cloneElement } from 'react'
|
|
1
2
|
import { htmlToJsx } from 'html-to-jsx-transform'
|
|
2
3
|
import { Node, Parent } from 'mdast'
|
|
3
4
|
import remarkFrontmatter from 'remark-frontmatter'
|
|
@@ -17,6 +18,8 @@ const mdxParser = remark()
|
|
|
17
18
|
.use(remarkGfm)
|
|
18
19
|
.use(remarkMdx) as any
|
|
19
20
|
|
|
21
|
+
void React
|
|
22
|
+
|
|
20
23
|
export function SafeMdxRenderer({
|
|
21
24
|
components,
|
|
22
25
|
code = '',
|
|
@@ -32,7 +35,6 @@ const nativeTags = [
|
|
|
32
35
|
'strong',
|
|
33
36
|
'em',
|
|
34
37
|
'del',
|
|
35
|
-
'br',
|
|
36
38
|
'hr',
|
|
37
39
|
'a',
|
|
38
40
|
'b',
|
|
@@ -74,18 +76,21 @@ const nativeTags = [
|
|
|
74
76
|
'pre',
|
|
75
77
|
] as const
|
|
76
78
|
|
|
77
|
-
type ComponentsMap = { [k in (typeof nativeTags)[number]]
|
|
79
|
+
type ComponentsMap = { [k in (typeof nativeTags)[number]]?: any }
|
|
78
80
|
|
|
79
|
-
class MdastToJsx {
|
|
81
|
+
export class MdastToJsx {
|
|
80
82
|
mdast: MyRootContent
|
|
81
83
|
str: string
|
|
82
84
|
jsxStr: string = ''
|
|
83
85
|
c: ComponentsMap
|
|
84
|
-
errors: string[] = []
|
|
85
|
-
constructor({
|
|
86
|
+
errors: { message: string }[] = []
|
|
87
|
+
constructor({
|
|
88
|
+
code = '',
|
|
89
|
+
mdast = undefined as any,
|
|
90
|
+
components = {} as ComponentsMap,
|
|
91
|
+
}) {
|
|
86
92
|
this.str = code
|
|
87
93
|
this.mdast = mdast || mdxParser.parse(code)
|
|
88
|
-
// TODO add tags and their allowed import sources
|
|
89
94
|
this.c = {
|
|
90
95
|
...Object.fromEntries(
|
|
91
96
|
nativeTags.map((tag) => {
|
|
@@ -96,27 +101,31 @@ class MdastToJsx {
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
mapMdastChildren(node: any) {
|
|
99
|
-
const res = node.children
|
|
100
|
-
this.mdastTransformer(child)
|
|
101
|
-
|
|
104
|
+
const res = node.children
|
|
105
|
+
?.flatMap((child) => this.mdastTransformer(child))
|
|
106
|
+
.filter(Boolean)
|
|
102
107
|
if (Array.isArray(res)) {
|
|
103
108
|
if (res.length === 1) {
|
|
104
109
|
return res[0]
|
|
105
110
|
} else {
|
|
106
|
-
return res.map((x, i) =>
|
|
111
|
+
return res.map((x, i) =>
|
|
112
|
+
React.isValidElement(x) ? cloneElement(x, { key: i }) : x,
|
|
113
|
+
)
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
return res || null
|
|
110
117
|
}
|
|
111
118
|
mapJsxChildren(node: any) {
|
|
112
|
-
const res = node.children
|
|
113
|
-
this.jsxTransformer(child)
|
|
114
|
-
|
|
119
|
+
const res = node.children
|
|
120
|
+
?.flatMap((child, i) => this.jsxTransformer(child))
|
|
121
|
+
.filter(Boolean)
|
|
115
122
|
if (Array.isArray(res)) {
|
|
116
123
|
if (res.length === 1) {
|
|
117
124
|
return res[0]
|
|
118
125
|
} else {
|
|
119
|
-
return res.map((x, i) =>
|
|
126
|
+
return res.map((x, i) =>
|
|
127
|
+
React.isValidElement(x) ? cloneElement(x, { key: i }) : x,
|
|
128
|
+
)
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
return res || null
|
|
@@ -136,7 +145,9 @@ class MdastToJsx {
|
|
|
136
145
|
const Component = accessWithDot(this.c, node.name)
|
|
137
146
|
|
|
138
147
|
if (!Component) {
|
|
139
|
-
this.errors.push(
|
|
148
|
+
this.errors.push({
|
|
149
|
+
message: `Unsupported jsx component ${node.name}`,
|
|
150
|
+
})
|
|
140
151
|
return null
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -173,8 +184,6 @@ class MdastToJsx {
|
|
|
173
184
|
const start = node.position?.start?.offset
|
|
174
185
|
const end = node.position?.end?.offset
|
|
175
186
|
let text = this.str.slice(start, end)
|
|
176
|
-
// console.log('esm', node)
|
|
177
|
-
const tree = node.data?.estree
|
|
178
187
|
|
|
179
188
|
return []
|
|
180
189
|
}
|
|
@@ -307,11 +316,7 @@ class MdastToJsx {
|
|
|
307
316
|
return <this.c.br />
|
|
308
317
|
}
|
|
309
318
|
case 'root': {
|
|
310
|
-
return (
|
|
311
|
-
<this.c.div className=''>
|
|
312
|
-
{this.mapMdastChildren(node)}
|
|
313
|
-
</this.c.div>
|
|
314
|
-
)
|
|
319
|
+
return <Fragment>{this.mapMdastChildren(node)}</Fragment>
|
|
315
320
|
}
|
|
316
321
|
case 'table': {
|
|
317
322
|
const align = node.align
|
|
@@ -334,6 +339,23 @@ class MdastToJsx {
|
|
|
334
339
|
case 'definition': {
|
|
335
340
|
return []
|
|
336
341
|
}
|
|
342
|
+
case 'linkReference': {
|
|
343
|
+
let href = ''
|
|
344
|
+
mdastBfs(this.mdast, (child: any) => {
|
|
345
|
+
if (
|
|
346
|
+
child.type === 'definition' &&
|
|
347
|
+
child.identifier === node.identifier
|
|
348
|
+
) {
|
|
349
|
+
href = child.url
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<this.c.a href={href}>
|
|
355
|
+
{this.mapMdastChildren(node)}
|
|
356
|
+
</this.c.a>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
337
359
|
case 'footnoteReference': {
|
|
338
360
|
return []
|
|
339
361
|
}
|
|
@@ -342,8 +364,6 @@ class MdastToJsx {
|
|
|
342
364
|
return []
|
|
343
365
|
}
|
|
344
366
|
case 'html': {
|
|
345
|
-
// console.log('html', node)
|
|
346
|
-
|
|
347
367
|
const start = node.position?.start?.offset
|
|
348
368
|
const end = node.position?.end?.offset
|
|
349
369
|
const text = this.str.slice(start, end)
|
|
@@ -386,11 +406,14 @@ class MdastToJsx {
|
|
|
386
406
|
getJsxAttrs(node: MdxJsxFlowElement | MdxJsxTextElement) {
|
|
387
407
|
let attrsList = node.attributes
|
|
388
408
|
.map((attr) => {
|
|
389
|
-
// TODO what is mdxJsxExpressionAttribute
|
|
390
409
|
if (attr.type === 'mdxJsxExpressionAttribute') {
|
|
391
|
-
|
|
392
|
-
`
|
|
393
|
-
|
|
410
|
+
this.errors.push({
|
|
411
|
+
message: `Expressions in jsx props are not supported (${attr.value.replace(
|
|
412
|
+
/\n+/g,
|
|
413
|
+
' ',
|
|
414
|
+
)})`,
|
|
415
|
+
})
|
|
416
|
+
return
|
|
394
417
|
}
|
|
395
418
|
if (attr.type !== 'mdxJsxAttribute') {
|
|
396
419
|
throw new Error(
|
|
@@ -406,8 +429,6 @@ class MdastToJsx {
|
|
|
406
429
|
return [attr.name, true]
|
|
407
430
|
}
|
|
408
431
|
if (v?.type === 'mdxJsxAttributeValueExpression') {
|
|
409
|
-
// logger.json({value})
|
|
410
|
-
// if it's a number, just return it
|
|
411
432
|
if (v.value === 'true') {
|
|
412
433
|
return [attr.name, true]
|
|
413
434
|
}
|
|
@@ -420,13 +441,15 @@ class MdastToJsx {
|
|
|
420
441
|
if (v.value === 'undefined') {
|
|
421
442
|
return [attr.name, undefined]
|
|
422
443
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
444
|
+
let quote = ['"', "'", '`'].find(
|
|
445
|
+
(q) => v.value.startsWith(q) && v.value.endsWith(q),
|
|
446
|
+
)
|
|
447
|
+
if (quote) {
|
|
448
|
+
let value = v.value
|
|
449
|
+
if (quote !== '"') {
|
|
450
|
+
value = v.value.replace(new RegExp(quote, 'g'), '"')
|
|
451
|
+
}
|
|
452
|
+
return [attr.name, JSON.parse(value)]
|
|
430
453
|
}
|
|
431
454
|
|
|
432
455
|
const number = Number(v.value)
|
|
@@ -434,11 +457,9 @@ class MdastToJsx {
|
|
|
434
457
|
return [attr.name, number]
|
|
435
458
|
}
|
|
436
459
|
|
|
437
|
-
this.errors.push(
|
|
438
|
-
`Expressions in jsx props are not supported (${attr.name}={${v.value}})`,
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
// return [attr.name, `{${v.value}}`]
|
|
460
|
+
this.errors.push({
|
|
461
|
+
message: `Expressions in jsx props are not supported (${attr.name}={${v.value}})`,
|
|
462
|
+
})
|
|
442
463
|
} else {
|
|
443
464
|
console.log('unhandled attr', { attr }, attr.type)
|
|
444
465
|
}
|
|
@@ -462,7 +483,10 @@ function accessWithDot(obj, path: string) {
|
|
|
462
483
|
.reduce((o, i) => o[i], obj)
|
|
463
484
|
}
|
|
464
485
|
|
|
465
|
-
function mdastBfs(
|
|
486
|
+
export function mdastBfs(
|
|
487
|
+
node: Parent | Node,
|
|
488
|
+
cb?: (node: Node | Parent) => any,
|
|
489
|
+
) {
|
|
466
490
|
const queue = [node]
|
|
467
491
|
const result: any[] = []
|
|
468
492
|
while (queue.length) {
|