safe-mdx 0.0.6 → 0.2.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/LICENSE +21 -0
- package/README.md +60 -5
- package/dist/safe-mdx.d.ts +31 -7
- package/dist/safe-mdx.d.ts.map +1 -1
- package/dist/safe-mdx.js +134 -35
- package/dist/safe-mdx.js.map +1 -1
- package/dist/safe-mdx.test.js +537 -234
- package/dist/safe-mdx.test.js.map +1 -1
- package/dist/streaming.d.ts +2 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +50 -0
- package/dist/streaming.js.map +1 -0
- package/package.json +8 -8
- package/src/safe-mdx.test.tsx +550 -236
- package/src/safe-mdx.tsx +234 -40
- package/src/streaming.tsx +60 -0
- package/src/plugins.ts +0 -87
package/src/safe-mdx.tsx
CHANGED
|
@@ -3,6 +3,9 @@ import { htmlToJsx } from 'html-to-jsx-transform'
|
|
|
3
3
|
import { Node, Parent, RootContent } from 'mdast'
|
|
4
4
|
import remarkFrontmatter from 'remark-frontmatter'
|
|
5
5
|
|
|
6
|
+
import { collapseWhiteSpace } from 'collapse-white-space'
|
|
7
|
+
import { visit } from 'unist-util-visit'
|
|
8
|
+
|
|
6
9
|
import { Root, Yaml } from 'mdast'
|
|
7
10
|
import { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx'
|
|
8
11
|
import { remark } from 'remark'
|
|
@@ -10,34 +13,45 @@ import remarkGfm from 'remark-gfm'
|
|
|
10
13
|
import remarkMdx from 'remark-mdx'
|
|
11
14
|
|
|
12
15
|
import { Fragment, ReactNode } from 'react'
|
|
13
|
-
import {
|
|
16
|
+
import { completeJsxTags } from './streaming.js'
|
|
14
17
|
|
|
15
18
|
type MyRootContent = RootContent | Root
|
|
16
19
|
|
|
17
|
-
const processor = remark()
|
|
18
|
-
.use(remarkMdx)
|
|
19
|
-
.use(remarkMarkAndUnravel)
|
|
20
|
-
.use(remarkFrontmatter, ['yaml', 'toml'])
|
|
21
|
-
.use(remarkGfm)
|
|
22
|
-
.use(() => {
|
|
23
|
-
return (tree, file) => {
|
|
24
|
-
file.data.ast = tree
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
20
|
export function mdxParse(code: string) {
|
|
29
|
-
const file =
|
|
21
|
+
const file = mdxProcessor.processSync(code)
|
|
30
22
|
return file.data.ast as Root
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
declare module 'mdast' {
|
|
26
|
+
export interface Data {
|
|
27
|
+
hProperties?: {
|
|
28
|
+
id?: string
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CustomTransformer = (
|
|
34
|
+
node: MyRootContent,
|
|
35
|
+
transform: (node: MyRootContent) => ReactNode,
|
|
36
|
+
) => ReactNode | undefined
|
|
34
37
|
|
|
35
38
|
export function SafeMdxRenderer({
|
|
36
39
|
components,
|
|
37
40
|
code = '',
|
|
38
41
|
mdast = null as any,
|
|
42
|
+
customTransformer,
|
|
43
|
+
}: {
|
|
44
|
+
components?: ComponentsMap
|
|
45
|
+
code?: string
|
|
46
|
+
mdast?: MyRootContent
|
|
47
|
+
customTransformer?: CustomTransformer
|
|
39
48
|
}) {
|
|
40
|
-
const visitor = new MdastToJsx({
|
|
49
|
+
const visitor = new MdastToJsx({
|
|
50
|
+
code,
|
|
51
|
+
mdast,
|
|
52
|
+
components,
|
|
53
|
+
customTransformer,
|
|
54
|
+
})
|
|
41
55
|
const result = visitor.run()
|
|
42
56
|
return result
|
|
43
57
|
}
|
|
@@ -48,13 +62,25 @@ export class MdastToJsx {
|
|
|
48
62
|
jsxStr: string = ''
|
|
49
63
|
c: ComponentsMap
|
|
50
64
|
errors: { message: string }[] = []
|
|
65
|
+
customTransformer?: CustomTransformer
|
|
66
|
+
|
|
51
67
|
constructor({
|
|
52
68
|
code = '',
|
|
53
69
|
mdast = undefined as any,
|
|
54
70
|
components = {} as ComponentsMap,
|
|
71
|
+
customTransformer,
|
|
72
|
+
}: {
|
|
73
|
+
code?: string
|
|
74
|
+
mdast?: MyRootContent
|
|
75
|
+
components?: ComponentsMap
|
|
76
|
+
customTransformer?: (
|
|
77
|
+
node: MyRootContent,
|
|
78
|
+
transform: (node: MyRootContent) => ReactNode,
|
|
79
|
+
) => ReactNode | undefined
|
|
55
80
|
}) {
|
|
56
81
|
this.str = code
|
|
57
82
|
this.mdast = mdast || mdxParse(code)
|
|
83
|
+
this.customTransformer = customTransformer
|
|
58
84
|
|
|
59
85
|
this.c = {
|
|
60
86
|
...Object.fromEntries(
|
|
@@ -150,6 +176,17 @@ export class MdastToJsx {
|
|
|
150
176
|
return []
|
|
151
177
|
}
|
|
152
178
|
|
|
179
|
+
// Check for custom transformer first, giving it higher priority
|
|
180
|
+
if (this.customTransformer) {
|
|
181
|
+
const customResult = this.customTransformer(
|
|
182
|
+
node,
|
|
183
|
+
this.mdastTransformer.bind(this),
|
|
184
|
+
)
|
|
185
|
+
if (customResult !== undefined) {
|
|
186
|
+
return customResult
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
153
190
|
switch (node.type) {
|
|
154
191
|
case 'mdxjsEsm': {
|
|
155
192
|
const start = node.position?.start?.offset
|
|
@@ -192,16 +229,24 @@ export class MdastToJsx {
|
|
|
192
229
|
}
|
|
193
230
|
case 'heading': {
|
|
194
231
|
const level = node.depth
|
|
195
|
-
|
|
196
232
|
const Tag = this.c[`h${level}`] ?? `h${level}`
|
|
197
|
-
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<Tag {...node.data?.hProperties}>
|
|
236
|
+
{this.mapMdastChildren(node)}
|
|
237
|
+
</Tag>
|
|
238
|
+
)
|
|
198
239
|
}
|
|
199
240
|
case 'paragraph': {
|
|
200
|
-
return
|
|
241
|
+
return (
|
|
242
|
+
<this.c.p {...node.data?.hProperties}>
|
|
243
|
+
{this.mapMdastChildren(node)}
|
|
244
|
+
</this.c.p>
|
|
245
|
+
)
|
|
201
246
|
}
|
|
202
247
|
case 'blockquote': {
|
|
203
248
|
return (
|
|
204
|
-
<this.c.blockquote>
|
|
249
|
+
<this.c.blockquote {...node.data?.hProperties}>
|
|
205
250
|
{this.mapMdastChildren(node)}
|
|
206
251
|
</this.c.blockquote>
|
|
207
252
|
)
|
|
@@ -216,7 +261,7 @@ export class MdastToJsx {
|
|
|
216
261
|
const language = node.lang || ''
|
|
217
262
|
const code = node.value
|
|
218
263
|
const codeBlock = (className?: string) => (
|
|
219
|
-
<this.c.pre>
|
|
264
|
+
<this.c.pre {...node.data?.hProperties}>
|
|
220
265
|
<this.c.code className={className}>{code}</this.c.code>
|
|
221
266
|
</this.c.pre>
|
|
222
267
|
)
|
|
@@ -241,23 +286,37 @@ export class MdastToJsx {
|
|
|
241
286
|
case 'list': {
|
|
242
287
|
if (node.ordered) {
|
|
243
288
|
return (
|
|
244
|
-
<this.c.ol
|
|
289
|
+
<this.c.ol
|
|
290
|
+
start={node.start!}
|
|
291
|
+
{...node.data?.hProperties}
|
|
292
|
+
>
|
|
245
293
|
{this.mapMdastChildren(node)}
|
|
246
294
|
</this.c.ol>
|
|
247
295
|
)
|
|
248
296
|
}
|
|
249
|
-
return
|
|
297
|
+
return (
|
|
298
|
+
<this.c.ul {...node.data?.hProperties}>
|
|
299
|
+
{this.mapMdastChildren(node)}
|
|
300
|
+
</this.c.ul>
|
|
301
|
+
)
|
|
250
302
|
}
|
|
251
303
|
case 'listItem': {
|
|
252
304
|
// https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
|
|
253
305
|
if (node?.checked != null) {
|
|
254
306
|
return (
|
|
255
|
-
<this.c.li
|
|
307
|
+
<this.c.li
|
|
308
|
+
data-checked={node.checked}
|
|
309
|
+
{...node.data?.hProperties}
|
|
310
|
+
>
|
|
256
311
|
{this.mapMdastChildren(node)}
|
|
257
312
|
</this.c.li>
|
|
258
313
|
)
|
|
259
314
|
}
|
|
260
|
-
return
|
|
315
|
+
return (
|
|
316
|
+
<this.c.li {...node.data?.hProperties}>
|
|
317
|
+
{this.mapMdastChildren(node)}
|
|
318
|
+
</this.c.li>
|
|
319
|
+
)
|
|
261
320
|
}
|
|
262
321
|
case 'text': {
|
|
263
322
|
if (!node.value) {
|
|
@@ -269,33 +328,54 @@ export class MdastToJsx {
|
|
|
269
328
|
const src = node.url || ''
|
|
270
329
|
const alt = node.alt || ''
|
|
271
330
|
const title = node.title || ''
|
|
272
|
-
return
|
|
331
|
+
return (
|
|
332
|
+
<this.c.img
|
|
333
|
+
src={src}
|
|
334
|
+
alt={alt}
|
|
335
|
+
title={title}
|
|
336
|
+
{...node.data?.hProperties}
|
|
337
|
+
/>
|
|
338
|
+
)
|
|
273
339
|
}
|
|
274
340
|
case 'link': {
|
|
275
341
|
const href = node.url || ''
|
|
276
342
|
const title = node.title || ''
|
|
277
343
|
return (
|
|
278
|
-
<this.c.a {...{ href, title }}>
|
|
344
|
+
<this.c.a {...{ href, title }} {...node.data?.hProperties}>
|
|
279
345
|
{this.mapMdastChildren(node)}
|
|
280
346
|
</this.c.a>
|
|
281
347
|
)
|
|
282
348
|
}
|
|
283
349
|
case 'strong': {
|
|
284
350
|
return (
|
|
285
|
-
<this.c.strong
|
|
351
|
+
<this.c.strong {...node.data?.hProperties}>
|
|
352
|
+
{this.mapMdastChildren(node)}
|
|
353
|
+
</this.c.strong>
|
|
286
354
|
)
|
|
287
355
|
}
|
|
288
356
|
case 'emphasis': {
|
|
289
|
-
return
|
|
357
|
+
return (
|
|
358
|
+
<this.c.em {...node.data?.hProperties}>
|
|
359
|
+
{this.mapMdastChildren(node)}
|
|
360
|
+
</this.c.em>
|
|
361
|
+
)
|
|
290
362
|
}
|
|
291
363
|
case 'delete': {
|
|
292
|
-
return
|
|
364
|
+
return (
|
|
365
|
+
<this.c.del {...node.data?.hProperties}>
|
|
366
|
+
{this.mapMdastChildren(node)}
|
|
367
|
+
</this.c.del>
|
|
368
|
+
)
|
|
293
369
|
}
|
|
294
370
|
case 'inlineCode': {
|
|
295
371
|
if (!node.value) {
|
|
296
372
|
return []
|
|
297
373
|
}
|
|
298
|
-
return
|
|
374
|
+
return (
|
|
375
|
+
<this.c.code {...node.data?.hProperties}>
|
|
376
|
+
{node.value}
|
|
377
|
+
</this.c.code>
|
|
378
|
+
)
|
|
299
379
|
}
|
|
300
380
|
case 'break': {
|
|
301
381
|
return <this.c.br />
|
|
@@ -308,7 +388,7 @@ export class MdastToJsx {
|
|
|
308
388
|
this.mapMdastChildren(node),
|
|
309
389
|
)
|
|
310
390
|
return (
|
|
311
|
-
<this.c.table>
|
|
391
|
+
<this.c.table {...node.data?.hProperties}>
|
|
312
392
|
{head && <this.c.thead>{head}</this.c.thead>}
|
|
313
393
|
{!!body?.length && <this.c.tbody>{body}</this.c.tbody>}
|
|
314
394
|
</this.c.table>
|
|
@@ -316,15 +396,18 @@ export class MdastToJsx {
|
|
|
316
396
|
}
|
|
317
397
|
case 'tableRow': {
|
|
318
398
|
return (
|
|
319
|
-
<this.c.tr className=''>
|
|
399
|
+
<this.c.tr className='' {...node.data?.hProperties}>
|
|
320
400
|
{this.mapMdastChildren(node)}
|
|
321
401
|
</this.c.tr>
|
|
322
402
|
)
|
|
323
403
|
}
|
|
324
404
|
case 'tableCell': {
|
|
325
405
|
let content = this.mapMdastChildren(node)
|
|
326
|
-
|
|
327
|
-
|
|
406
|
+
return (
|
|
407
|
+
<this.c.td className='' {...node.data?.hProperties}>
|
|
408
|
+
{content}
|
|
409
|
+
</this.c.td>
|
|
410
|
+
)
|
|
328
411
|
}
|
|
329
412
|
case 'definition': {
|
|
330
413
|
return []
|
|
@@ -341,7 +424,7 @@ export class MdastToJsx {
|
|
|
341
424
|
})
|
|
342
425
|
|
|
343
426
|
return (
|
|
344
|
-
<this.c.a href={href}>
|
|
427
|
+
<this.c.a href={href} {...node.data?.hProperties}>
|
|
345
428
|
{this.mapMdastChildren(node)}
|
|
346
429
|
</this.c.a>
|
|
347
430
|
)
|
|
@@ -403,10 +486,9 @@ export function getJsxAttrs(
|
|
|
403
486
|
.map((attr) => {
|
|
404
487
|
if (attr.type === 'mdxJsxExpressionAttribute') {
|
|
405
488
|
onError({
|
|
406
|
-
message: `Expressions in jsx props are not supported (${attr.value
|
|
407
|
-
/\n+/g,
|
|
408
|
-
' '
|
|
409
|
-
)})`,
|
|
489
|
+
message: `Expressions in jsx props are not supported (${attr.value
|
|
490
|
+
.replace(/\n+/g, ' ')
|
|
491
|
+
.replace(/ +/g, ' ')})`,
|
|
410
492
|
})
|
|
411
493
|
return
|
|
412
494
|
}
|
|
@@ -553,6 +635,22 @@ const nativeTags = [
|
|
|
553
635
|
'video',
|
|
554
636
|
'code',
|
|
555
637
|
'pre',
|
|
638
|
+
'figure',
|
|
639
|
+
'canvas',
|
|
640
|
+
'details',
|
|
641
|
+
'dl',
|
|
642
|
+
'dt',
|
|
643
|
+
'dd',
|
|
644
|
+
'fieldset',
|
|
645
|
+
'footer',
|
|
646
|
+
'header',
|
|
647
|
+
'legend',
|
|
648
|
+
'main',
|
|
649
|
+
'mark',
|
|
650
|
+
'nav',
|
|
651
|
+
'progress',
|
|
652
|
+
'summary',
|
|
653
|
+
'time',
|
|
556
654
|
] as const
|
|
557
655
|
|
|
558
656
|
const supportedLanguages = [
|
|
@@ -836,4 +934,100 @@ const supportedLanguages = [
|
|
|
836
934
|
] as const
|
|
837
935
|
const supportedLanguagesSet = new Set(supportedLanguages)
|
|
838
936
|
|
|
839
|
-
type ComponentsMap = { [k in (typeof nativeTags)[number]]?: any }
|
|
937
|
+
type ComponentsMap = { [k in (typeof nativeTags)[number]]?: any } & {
|
|
938
|
+
[key: string]: any
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* https://github.com/mdx-js/mdx/blob/b3351fadcb6f78833a72757b7135dcfb8ab646fe/packages/mdx/lib/plugin/remark-mark-and-unravel.js
|
|
943
|
+
* A tiny plugin that unravels `<p><h1>x</h1></p>` but also
|
|
944
|
+
* `<p><Component /></p>` (so it has no knowledge of "HTML").
|
|
945
|
+
*
|
|
946
|
+
* It also marks JSX as being explicitly JSX, so when a user passes a `h1`
|
|
947
|
+
* component, it is used for `# heading` but not for `<h1>heading</h1>`.
|
|
948
|
+
*
|
|
949
|
+
*/
|
|
950
|
+
export function remarkMarkAndUnravel() {
|
|
951
|
+
return function (tree: Root) {
|
|
952
|
+
visit(tree, function (node, index, parent) {
|
|
953
|
+
let offset = -1
|
|
954
|
+
let all = true
|
|
955
|
+
let oneOrMore = false
|
|
956
|
+
|
|
957
|
+
if (
|
|
958
|
+
parent &&
|
|
959
|
+
typeof index === 'number' &&
|
|
960
|
+
node.type === 'paragraph'
|
|
961
|
+
) {
|
|
962
|
+
const children = node.children
|
|
963
|
+
|
|
964
|
+
while (++offset < children.length) {
|
|
965
|
+
const child = children[offset]
|
|
966
|
+
|
|
967
|
+
if (
|
|
968
|
+
child.type === 'mdxJsxTextElement' ||
|
|
969
|
+
child.type === 'mdxTextExpression'
|
|
970
|
+
) {
|
|
971
|
+
oneOrMore = true
|
|
972
|
+
} else if (
|
|
973
|
+
child.type === 'text' &&
|
|
974
|
+
collapseWhiteSpace(child.value, {
|
|
975
|
+
style: 'html',
|
|
976
|
+
trim: true,
|
|
977
|
+
}) === ''
|
|
978
|
+
) {
|
|
979
|
+
// Empty.
|
|
980
|
+
} else {
|
|
981
|
+
all = false
|
|
982
|
+
break
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (all && oneOrMore) {
|
|
987
|
+
offset = -1
|
|
988
|
+
|
|
989
|
+
const newChildren: RootContent[] = []
|
|
990
|
+
|
|
991
|
+
while (++offset < children.length) {
|
|
992
|
+
const child = children[offset]
|
|
993
|
+
|
|
994
|
+
if (child.type === 'mdxJsxTextElement') {
|
|
995
|
+
// @ts-expect-error: mutate because it is faster; content model is fine.
|
|
996
|
+
child.type = 'mdxJsxFlowElement'
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (child.type === 'mdxTextExpression') {
|
|
1000
|
+
// @ts-expect-error: mutate because it is faster; content model is fine.
|
|
1001
|
+
child.type = 'mdxFlowExpression'
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (
|
|
1005
|
+
child.type === 'text' &&
|
|
1006
|
+
/^[\t\r\n ]+$/.test(String(child.value))
|
|
1007
|
+
) {
|
|
1008
|
+
// Empty.
|
|
1009
|
+
} else {
|
|
1010
|
+
newChildren.push(child)
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
parent.children.splice(index, 1, ...newChildren)
|
|
1015
|
+
return index
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
})
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const mdxProcessor = remark()
|
|
1023
|
+
.use(remarkMdx)
|
|
1024
|
+
.use(remarkFrontmatter, ['yaml', 'toml'])
|
|
1025
|
+
.use(remarkGfm)
|
|
1026
|
+
.use(remarkMarkAndUnravel)
|
|
1027
|
+
.use(() => {
|
|
1028
|
+
return (tree, file) => {
|
|
1029
|
+
file.data.ast = tree
|
|
1030
|
+
}
|
|
1031
|
+
})
|
|
1032
|
+
|
|
1033
|
+
export { completeJsxTags }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// taken from https://www.prompt-kit.com/docs/jsx-preview
|
|
2
|
+
|
|
3
|
+
function matchJsxTag(code: string) {
|
|
4
|
+
if (code.trim() === '') {
|
|
5
|
+
return null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\s*([^>]*?)(\/)?>/
|
|
9
|
+
const match = code.match(tagRegex)
|
|
10
|
+
|
|
11
|
+
if (!match || typeof match.index === 'undefined') {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const [fullMatch, tagName, attributes, selfClosing] = match
|
|
16
|
+
|
|
17
|
+
const type = selfClosing
|
|
18
|
+
? 'self-closing'
|
|
19
|
+
: fullMatch.startsWith('</')
|
|
20
|
+
? 'closing'
|
|
21
|
+
: 'opening'
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
tag: fullMatch,
|
|
25
|
+
tagName,
|
|
26
|
+
type,
|
|
27
|
+
attributes: attributes.trim(),
|
|
28
|
+
startIndex: match.index,
|
|
29
|
+
endIndex: match.index + fullMatch.length,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function completeJsxTags(code: string) {
|
|
34
|
+
const stack: string[] = []
|
|
35
|
+
let result = ''
|
|
36
|
+
let currentPosition = 0
|
|
37
|
+
|
|
38
|
+
while (currentPosition < code.length) {
|
|
39
|
+
const match = matchJsxTag(code.slice(currentPosition))
|
|
40
|
+
if (!match) break
|
|
41
|
+
const { tagName, type, endIndex } = match
|
|
42
|
+
|
|
43
|
+
if (type === 'opening') {
|
|
44
|
+
stack.push(tagName)
|
|
45
|
+
} else if (type === 'closing') {
|
|
46
|
+
stack.pop()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
result += code.slice(currentPosition, currentPosition + endIndex)
|
|
50
|
+
currentPosition += endIndex
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
result +
|
|
55
|
+
stack
|
|
56
|
+
.reverse()
|
|
57
|
+
.map((tag) => `</${tag}>`)
|
|
58
|
+
.join('')
|
|
59
|
+
)
|
|
60
|
+
}
|
package/src/plugins.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Root, RootContent } from 'mdast'
|
|
2
|
-
import { collapseWhiteSpace } from 'collapse-white-space'
|
|
3
|
-
import { visit } from 'unist-util-visit'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* https://github.com/mdx-js/mdx/blob/b3351fadcb6f78833a72757b7135dcfb8ab646fe/packages/mdx/lib/plugin/remark-mark-and-unravel.js
|
|
8
|
-
* A tiny plugin that unravels `<p><h1>x</h1></p>` but also
|
|
9
|
-
* `<p><Component /></p>` (so it has no knowledge of "HTML").
|
|
10
|
-
*
|
|
11
|
-
* It also marks JSX as being explicitly JSX, so when a user passes a `h1`
|
|
12
|
-
* component, it is used for `# heading` but not for `<h1>heading</h1>`.
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
export function remarkMarkAndUnravel() {
|
|
16
|
-
return function (tree: Root) {
|
|
17
|
-
|
|
18
|
-
visit(tree, function (node, index, parent) {
|
|
19
|
-
let offset = -1
|
|
20
|
-
let all = true
|
|
21
|
-
let oneOrMore = false
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
parent &&
|
|
26
|
-
typeof index === 'number' &&
|
|
27
|
-
node.type === 'paragraph'
|
|
28
|
-
) {
|
|
29
|
-
const children = node.children
|
|
30
|
-
|
|
31
|
-
while (++offset < children.length) {
|
|
32
|
-
const child = children[offset]
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
child.type === 'mdxJsxTextElement' ||
|
|
36
|
-
child.type === 'mdxTextExpression'
|
|
37
|
-
) {
|
|
38
|
-
oneOrMore = true
|
|
39
|
-
} else if (
|
|
40
|
-
child.type === 'text' &&
|
|
41
|
-
collapseWhiteSpace(child.value, {
|
|
42
|
-
style: 'html',
|
|
43
|
-
trim: true,
|
|
44
|
-
}) === ''
|
|
45
|
-
) {
|
|
46
|
-
// Empty.
|
|
47
|
-
} else {
|
|
48
|
-
all = false
|
|
49
|
-
break
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (all && oneOrMore) {
|
|
54
|
-
offset = -1
|
|
55
|
-
|
|
56
|
-
const newChildren: RootContent[] = []
|
|
57
|
-
|
|
58
|
-
while (++offset < children.length) {
|
|
59
|
-
const child = children[offset]
|
|
60
|
-
|
|
61
|
-
if (child.type === 'mdxJsxTextElement') {
|
|
62
|
-
// @ts-expect-error: mutate because it is faster; content model is fine.
|
|
63
|
-
child.type = 'mdxJsxFlowElement'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (child.type === 'mdxTextExpression') {
|
|
67
|
-
// @ts-expect-error: mutate because it is faster; content model is fine.
|
|
68
|
-
child.type = 'mdxFlowExpression'
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
child.type === 'text' &&
|
|
73
|
-
/^[\t\r\n ]+$/.test(String(child.value))
|
|
74
|
-
) {
|
|
75
|
-
// Empty.
|
|
76
|
-
} else {
|
|
77
|
-
newChildren.push(child)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
parent.children.splice(index, 1, ...newChildren)
|
|
82
|
-
return index
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
}
|