safe-mdx 1.1.0 → 1.3.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/README.md +109 -3
- package/dist/dynamic-esm-component.d.ts +6 -0
- package/dist/dynamic-esm-component.d.ts.map +1 -0
- package/dist/dynamic-esm-component.js +58 -0
- package/dist/dynamic-esm-component.js.map +1 -0
- package/dist/esm-parser.d.ts +19 -0
- package/dist/esm-parser.d.ts.map +1 -0
- package/dist/esm-parser.js +69 -0
- package/dist/esm-parser.js.map +1 -0
- package/dist/esm-parser.test.d.ts +2 -0
- package/dist/esm-parser.test.d.ts.map +1 -0
- package/dist/esm-parser.test.js +124 -0
- package/dist/esm-parser.test.js.map +1 -0
- package/dist/safe-mdx.bench.js +6 -0
- package/dist/safe-mdx.bench.js.map +1 -1
- package/dist/safe-mdx.d.ts +12 -2
- package/dist/safe-mdx.d.ts.map +1 -1
- package/dist/safe-mdx.js +337 -103
- package/dist/safe-mdx.js.map +1 -1
- package/dist/safe-mdx.test.js +717 -24
- package/dist/safe-mdx.test.js.map +1 -1
- package/package.json +3 -1
- package/src/dynamic-esm-component.tsx +85 -0
- package/src/esm-parser.test.ts +141 -0
- package/src/esm-parser.ts +89 -0
- package/src/safe-mdx.bench.tsx +6 -0
- package/src/safe-mdx.test.tsx +777 -24
- package/src/safe-mdx.tsx +514 -131
package/dist/safe-mdx.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React, { Suspense, cloneElement } from 'react';
|
|
2
|
+
import Evaluate from 'eval-estree-expression';
|
|
2
3
|
import { Fragment } from 'react';
|
|
4
|
+
import { DynamicEsmComponent } from './dynamic-esm-component.js';
|
|
5
|
+
import { parseEsmImports, extractComponentInfo } from './esm-parser.js';
|
|
3
6
|
const HtmlToJsxConverter = React.lazy(() => import('./HtmlToJsxConverter.js').then((module) => ({
|
|
4
7
|
default: module.HtmlToJsxConverter,
|
|
5
8
|
})));
|
|
6
|
-
export const SafeMdxRenderer = React.memo(function SafeMdxRenderer({ components, markdown = '', mdast = null, renderNode, componentPropsSchema, createElement, }) {
|
|
9
|
+
export const SafeMdxRenderer = React.memo(function SafeMdxRenderer({ components, markdown = '', mdast = null, renderNode, componentPropsSchema, createElement, allowClientEsmImports = false, addMarkdownLineNumbers = false, }) {
|
|
7
10
|
const visitor = new MdastToJsx({
|
|
8
11
|
markdown,
|
|
9
12
|
mdast,
|
|
@@ -11,6 +14,8 @@ export const SafeMdxRenderer = React.memo(function SafeMdxRenderer({ components,
|
|
|
11
14
|
renderNode,
|
|
12
15
|
componentPropsSchema,
|
|
13
16
|
createElement,
|
|
17
|
+
allowClientEsmImports,
|
|
18
|
+
addMarkdownLineNumbers,
|
|
14
19
|
});
|
|
15
20
|
const result = visitor.run();
|
|
16
21
|
return result;
|
|
@@ -24,12 +29,17 @@ export class MdastToJsx {
|
|
|
24
29
|
renderNode;
|
|
25
30
|
componentPropsSchema;
|
|
26
31
|
createElement;
|
|
27
|
-
|
|
32
|
+
esmImports = new Map();
|
|
33
|
+
allowClientEsmImports;
|
|
34
|
+
addMarkdownLineNumbers;
|
|
35
|
+
constructor({ markdown: code = '', mdast, components = {}, renderNode, componentPropsSchema, createElement = React.createElement, allowClientEsmImports = false, addMarkdownLineNumbers = false, }) {
|
|
28
36
|
this.str = code;
|
|
29
37
|
this.mdast = mdast;
|
|
30
38
|
this.renderNode = renderNode;
|
|
31
39
|
this.componentPropsSchema = componentPropsSchema;
|
|
32
40
|
this.createElement = createElement;
|
|
41
|
+
this.allowClientEsmImports = allowClientEsmImports;
|
|
42
|
+
this.addMarkdownLineNumbers = addMarkdownLineNumbers;
|
|
33
43
|
this.c = {
|
|
34
44
|
...Object.fromEntries(nativeTags.map((tag) => {
|
|
35
45
|
return [tag, tag];
|
|
@@ -37,8 +47,22 @@ export class MdastToJsx {
|
|
|
37
47
|
...components,
|
|
38
48
|
};
|
|
39
49
|
}
|
|
50
|
+
addLineNumberToProps(props, node) {
|
|
51
|
+
if (!this.addMarkdownLineNumbers) {
|
|
52
|
+
return props || {};
|
|
53
|
+
}
|
|
54
|
+
const lineNumber = node.position?.start?.line;
|
|
55
|
+
if (lineNumber) {
|
|
56
|
+
return {
|
|
57
|
+
...props,
|
|
58
|
+
'data-markdown-line': lineNumber,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return props || {};
|
|
62
|
+
}
|
|
40
63
|
validateComponentProps(componentName, props, line) {
|
|
41
|
-
if (!this.componentPropsSchema ||
|
|
64
|
+
if (!this.componentPropsSchema ||
|
|
65
|
+
!this.componentPropsSchema[componentName]) {
|
|
42
66
|
return;
|
|
43
67
|
}
|
|
44
68
|
const schema = this.componentPropsSchema[componentName];
|
|
@@ -104,15 +128,32 @@ export class MdastToJsx {
|
|
|
104
128
|
if (!node.name) {
|
|
105
129
|
return [];
|
|
106
130
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
// Check if this is an ESM imported component (only if allowed)
|
|
132
|
+
const esmImportInfo = this.allowClientEsmImports
|
|
133
|
+
? this.esmImports.get(node.name)
|
|
134
|
+
: null;
|
|
135
|
+
let Component;
|
|
136
|
+
if (esmImportInfo) {
|
|
137
|
+
// Handle ESM imported component
|
|
138
|
+
const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
|
|
139
|
+
Component = DynamicEsmComponent;
|
|
140
|
+
let attrsList = this.getJsxAttrs(node, (err) => {
|
|
141
|
+
this.errors.push(err);
|
|
112
142
|
});
|
|
113
|
-
|
|
143
|
+
let attrs = Object.fromEntries(attrsList);
|
|
144
|
+
return this.createElement(Component, { ...attrs, importUrl, componentName }, this.mapJsxChildren(node));
|
|
114
145
|
}
|
|
115
|
-
|
|
146
|
+
else {
|
|
147
|
+
Component = accessWithDot(this.c, node.name);
|
|
148
|
+
if (!Component) {
|
|
149
|
+
this.errors.push({
|
|
150
|
+
message: `Unsupported jsx component ${node.name}`,
|
|
151
|
+
line: node.position?.start?.line,
|
|
152
|
+
});
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
let attrsList = this.getJsxAttrs(node, (err) => {
|
|
116
157
|
this.errors.push(err);
|
|
117
158
|
});
|
|
118
159
|
let attrs = Object.fromEntries(attrsList);
|
|
@@ -125,6 +166,225 @@ export class MdastToJsx {
|
|
|
125
166
|
}
|
|
126
167
|
}
|
|
127
168
|
}
|
|
169
|
+
transformJsxElement(jsxElement, onError, line) {
|
|
170
|
+
try {
|
|
171
|
+
// Handle JSX opening element
|
|
172
|
+
if (jsxElement.openingElement) {
|
|
173
|
+
const tagName = jsxElement.openingElement.name?.type === 'JSXIdentifier'
|
|
174
|
+
? jsxElement.openingElement.name.name
|
|
175
|
+
: null;
|
|
176
|
+
if (!tagName) {
|
|
177
|
+
onError?.({
|
|
178
|
+
message: 'JSX element missing component name',
|
|
179
|
+
line: line,
|
|
180
|
+
});
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// Check if this is an ESM imported component (only if allowed)
|
|
184
|
+
const esmImportInfo = this.allowClientEsmImports
|
|
185
|
+
? this.esmImports.get(tagName)
|
|
186
|
+
: null;
|
|
187
|
+
let Component;
|
|
188
|
+
if (esmImportInfo) {
|
|
189
|
+
// Handle ESM imported component
|
|
190
|
+
const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
|
|
191
|
+
Component = DynamicEsmComponent;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Get the component from the regular component map
|
|
195
|
+
Component = accessWithDot(this.c, tagName);
|
|
196
|
+
if (!Component) {
|
|
197
|
+
onError?.({
|
|
198
|
+
message: `Unsupported jsx component ${tagName} in attribute`,
|
|
199
|
+
line: line,
|
|
200
|
+
});
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Extract attributes
|
|
205
|
+
const props = {};
|
|
206
|
+
if (jsxElement.openingElement.attributes) {
|
|
207
|
+
for (const attr of jsxElement.openingElement.attributes) {
|
|
208
|
+
if (attr.type === 'JSXAttribute' &&
|
|
209
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
210
|
+
attr.name.name) {
|
|
211
|
+
if (attr.value) {
|
|
212
|
+
if (attr.value.type === 'Literal') {
|
|
213
|
+
props[attr.name.name] = attr.value.value;
|
|
214
|
+
}
|
|
215
|
+
else if (attr.value.type === 'JSXExpressionContainer') {
|
|
216
|
+
if (attr.value.expression.type === 'Literal') {
|
|
217
|
+
props[attr.name.name] =
|
|
218
|
+
attr.value.expression.value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
props[attr.name.name] = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Extract children
|
|
229
|
+
const children = [];
|
|
230
|
+
if (jsxElement.children) {
|
|
231
|
+
for (const child of jsxElement.children) {
|
|
232
|
+
if (child.type === 'JSXText') {
|
|
233
|
+
children.push(child.value);
|
|
234
|
+
}
|
|
235
|
+
else if (child.type === 'JSXElement') {
|
|
236
|
+
const childElement = this.transformJsxElement(child);
|
|
237
|
+
if (childElement) {
|
|
238
|
+
children.push(childElement);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Handle ESM imported components by adding required props
|
|
244
|
+
if (esmImportInfo) {
|
|
245
|
+
const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
|
|
246
|
+
return this.createElement(Component, { ...props, importUrl, componentName }, ...children);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
return this.createElement(Component, props, ...children);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
// Return null if transformation fails
|
|
255
|
+
onError?.({
|
|
256
|
+
message: `Failed to transform JSX element: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
257
|
+
line: line,
|
|
258
|
+
});
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
getJsxAttrs(node, onError = console.error) {
|
|
264
|
+
let attrsList = [];
|
|
265
|
+
for (const attr of node.attributes) {
|
|
266
|
+
if (attr.type === 'mdxJsxExpressionAttribute') {
|
|
267
|
+
// Handle spread expressions like {...{key: '1'}}
|
|
268
|
+
if (attr.data?.estree) {
|
|
269
|
+
try {
|
|
270
|
+
const program = attr.data.estree;
|
|
271
|
+
if (program.body?.length > 0 &&
|
|
272
|
+
program.body[0].type === 'ExpressionStatement') {
|
|
273
|
+
const expression = program.body[0].expression;
|
|
274
|
+
try {
|
|
275
|
+
const result = Evaluate.evaluate.sync(expression);
|
|
276
|
+
// Handle spread syntax - merge the evaluated object
|
|
277
|
+
if (typeof result === 'object' &&
|
|
278
|
+
result != null) {
|
|
279
|
+
const entries = Object.entries(result);
|
|
280
|
+
attrsList.push(...entries);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
onError({
|
|
285
|
+
message: `Failed to evaluate expression attribute: ${attr.value
|
|
286
|
+
.replace(/\n+/g, ' ')
|
|
287
|
+
.replace(/ +/g, ' ')}`,
|
|
288
|
+
line: attr.position?.start?.line,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
onError({
|
|
295
|
+
message: `Failed to evaluate expression attribute: ${attr.value
|
|
296
|
+
.replace(/\n+/g, ' ')
|
|
297
|
+
.replace(/ +/g, ' ')}`,
|
|
298
|
+
line: attr.position?.start?.line,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
onError({
|
|
304
|
+
message: `Expressions in jsx props are not supported (${attr.value
|
|
305
|
+
.replace(/\n+/g, ' ')
|
|
306
|
+
.replace(/ +/g, ' ')})`,
|
|
307
|
+
line: attr.position?.start?.line,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (attr.type !== 'mdxJsxAttribute') {
|
|
313
|
+
onError({
|
|
314
|
+
message: `non mdxJsxAttribute attribute is not supported: ${attr}`,
|
|
315
|
+
line: node.position?.start?.line,
|
|
316
|
+
});
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const v = attr.value;
|
|
320
|
+
if (typeof v === 'string' || typeof v === 'number') {
|
|
321
|
+
attrsList.push([attr.name, v]);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (v === null) {
|
|
325
|
+
attrsList.push([attr.name, true]);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (v?.type === 'mdxJsxAttributeValueExpression') {
|
|
329
|
+
// Manual parsing fallback for simple values
|
|
330
|
+
if (v.value === 'true') {
|
|
331
|
+
attrsList.push([attr.name, true]);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (v.value === 'false') {
|
|
335
|
+
attrsList.push([attr.name, false]);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (v.value === 'null') {
|
|
339
|
+
attrsList.push([attr.name, null]);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (v.value === 'undefined') {
|
|
343
|
+
attrsList.push([attr.name, undefined]);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (v.data?.estree) {
|
|
347
|
+
try {
|
|
348
|
+
// Extract the expression from the Program body
|
|
349
|
+
const program = v.data.estree;
|
|
350
|
+
if (program.body?.length > 0 &&
|
|
351
|
+
program.body[0].type === 'ExpressionStatement') {
|
|
352
|
+
const expression = program.body[0].expression;
|
|
353
|
+
// Check if this is a JSX element
|
|
354
|
+
if (expression.type === 'JSXElement') {
|
|
355
|
+
// Transform JSX element to React element
|
|
356
|
+
const jsxElement = this.transformJsxElement(expression, onError, attr.position?.start?.line);
|
|
357
|
+
if (jsxElement) {
|
|
358
|
+
attrsList.push([attr.name, jsxElement]);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
// Evaluate the expression synchronously
|
|
364
|
+
const result = Evaluate.evaluate.sync(expression);
|
|
365
|
+
attrsList.push([attr.name, result]);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
onError({
|
|
370
|
+
message: `Failed to evaluate expression attribute: ${attr.name}={${v.value}}`,
|
|
371
|
+
line: attr.position?.start?.line,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
// Fall back to the original manual parsing for backwards compatibility
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
onError({
|
|
381
|
+
message: `Expressions in jsx prop not evaluated: (${attr.name}={${v.value}})`,
|
|
382
|
+
line: attr.position?.start?.line,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return attrsList;
|
|
387
|
+
}
|
|
128
388
|
run() {
|
|
129
389
|
const res = this.mdastTransformer(this.mdast);
|
|
130
390
|
if (Array.isArray(res) && res.length === 1) {
|
|
@@ -145,9 +405,13 @@ export class MdastToJsx {
|
|
|
145
405
|
}
|
|
146
406
|
switch (node.type) {
|
|
147
407
|
case 'mdxjsEsm': {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
408
|
+
// Parse ESM imports and merge into our imports map (only if allowed)
|
|
409
|
+
if (this.allowClientEsmImports) {
|
|
410
|
+
const parsedImports = parseEsmImports(node, (err) => this.errors.push(err));
|
|
411
|
+
parsedImports.forEach((value, key) => {
|
|
412
|
+
this.esmImports.set(key, value);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
151
415
|
return [];
|
|
152
416
|
}
|
|
153
417
|
case 'mdxJsxTextElement':
|
|
@@ -175,6 +439,34 @@ export class MdastToJsx {
|
|
|
175
439
|
if (!node.value) {
|
|
176
440
|
return [];
|
|
177
441
|
}
|
|
442
|
+
// Check if we have an estree AST
|
|
443
|
+
if (node.data?.estree) {
|
|
444
|
+
try {
|
|
445
|
+
// Extract the expression from the Program body
|
|
446
|
+
const program = node.data.estree;
|
|
447
|
+
if (program.body?.length > 0 &&
|
|
448
|
+
program.body[0].type === 'ExpressionStatement') {
|
|
449
|
+
const expression = program.body[0].expression;
|
|
450
|
+
try {
|
|
451
|
+
// Evaluate the expression synchronously
|
|
452
|
+
const result = Evaluate.evaluate.sync(expression);
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
this.errors.push({
|
|
457
|
+
message: `Failed to evaluate expression: ${node.value}`,
|
|
458
|
+
line: node.position?.start?.line,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
this.errors.push({
|
|
465
|
+
message: `Failed to evaluate expression: ${node.value}`,
|
|
466
|
+
line: node.position?.start?.line,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
178
470
|
return [];
|
|
179
471
|
}
|
|
180
472
|
case 'yaml': {
|
|
@@ -186,16 +478,16 @@ export class MdastToJsx {
|
|
|
186
478
|
case 'heading': {
|
|
187
479
|
const level = node.depth;
|
|
188
480
|
const Tag = this.c[`h${level}`] ?? `h${level}`;
|
|
189
|
-
return this.createElement(Tag, node.data?.hProperties, this.mapMdastChildren(node));
|
|
481
|
+
return this.createElement(Tag, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
190
482
|
}
|
|
191
483
|
case 'paragraph': {
|
|
192
|
-
return this.createElement(this.c.p, node.data?.hProperties, this.mapMdastChildren(node));
|
|
484
|
+
return this.createElement(this.c.p, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
193
485
|
}
|
|
194
486
|
case 'blockquote': {
|
|
195
|
-
return this.createElement(this.c.blockquote, node.data?.hProperties, this.mapMdastChildren(node));
|
|
487
|
+
return this.createElement(this.c.blockquote, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
196
488
|
}
|
|
197
489
|
case 'thematicBreak': {
|
|
198
|
-
return this.createElement(this.c.hr, node.data?.hProperties);
|
|
490
|
+
return this.createElement(this.c.hr, this.addLineNumberToProps(node.data?.hProperties, node));
|
|
199
491
|
}
|
|
200
492
|
case 'code': {
|
|
201
493
|
if (!node.value) {
|
|
@@ -203,7 +495,7 @@ export class MdastToJsx {
|
|
|
203
495
|
}
|
|
204
496
|
const language = node.lang || '';
|
|
205
497
|
const code = node.value;
|
|
206
|
-
const codeBlock = (className) => this.createElement(this.c.pre, node.data?.hProperties, this.createElement(this.c.code, { className }, code));
|
|
498
|
+
const codeBlock = (className) => this.createElement(this.c.pre, this.addLineNumberToProps(node.data?.hProperties, node), this.createElement(this.c.code, { className }, code));
|
|
207
499
|
if (language) {
|
|
208
500
|
return codeBlock(`language-${language}`);
|
|
209
501
|
}
|
|
@@ -211,23 +503,26 @@ export class MdastToJsx {
|
|
|
211
503
|
}
|
|
212
504
|
case 'list': {
|
|
213
505
|
if (node.ordered) {
|
|
214
|
-
return this.createElement(this.c.ol, { start: node.start, ...node.data?.hProperties }, this.mapMdastChildren(node));
|
|
506
|
+
return this.createElement(this.c.ol, this.addLineNumberToProps({ start: node.start, ...node.data?.hProperties }, node), this.mapMdastChildren(node));
|
|
215
507
|
}
|
|
216
|
-
return this.createElement(this.c.ul, node.data?.hProperties, this.mapMdastChildren(node));
|
|
508
|
+
return this.createElement(this.c.ul, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
217
509
|
}
|
|
218
510
|
case 'listItem': {
|
|
219
511
|
// https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
|
|
220
512
|
if (node?.checked != null) {
|
|
221
|
-
return this.createElement(this.c.li,
|
|
513
|
+
return this.createElement(this.c.li, this.addLineNumberToProps({
|
|
514
|
+
'data-checked': node.checked,
|
|
515
|
+
...node.data?.hProperties,
|
|
516
|
+
}, node), this.mapMdastChildren(node));
|
|
222
517
|
}
|
|
223
|
-
return this.createElement(this.c.li, node.data?.hProperties, this.mapMdastChildren(node));
|
|
518
|
+
return this.createElement(this.c.li, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
224
519
|
}
|
|
225
520
|
case 'text': {
|
|
226
521
|
if (!node.value) {
|
|
227
522
|
return [];
|
|
228
523
|
}
|
|
229
524
|
if (node.data?.hProperties) {
|
|
230
|
-
return this.createElement(this.c.span, node.data.hProperties, node.value);
|
|
525
|
+
return this.createElement(this.c.span, this.addLineNumberToProps(node.data.hProperties, node), node.value);
|
|
231
526
|
}
|
|
232
527
|
return node.value;
|
|
233
528
|
}
|
|
@@ -235,65 +530,68 @@ export class MdastToJsx {
|
|
|
235
530
|
const src = node.url || '';
|
|
236
531
|
const alt = node.alt || '';
|
|
237
532
|
const title = node.title || '';
|
|
238
|
-
return this.createElement(this.c.img, {
|
|
533
|
+
return this.createElement(this.c.img, this.addLineNumberToProps({
|
|
239
534
|
src,
|
|
240
535
|
alt,
|
|
241
536
|
title,
|
|
242
|
-
...node.data?.hProperties
|
|
243
|
-
});
|
|
537
|
+
...node.data?.hProperties,
|
|
538
|
+
}, node));
|
|
244
539
|
}
|
|
245
540
|
case 'link': {
|
|
246
541
|
const href = node.url || '';
|
|
247
542
|
const title = node.title || '';
|
|
248
|
-
return this.createElement(this.c.a, { href, title, ...node.data?.hProperties }, this.mapMdastChildren(node));
|
|
543
|
+
return this.createElement(this.c.a, this.addLineNumberToProps({ href, title, ...node.data?.hProperties }, node), this.mapMdastChildren(node));
|
|
249
544
|
}
|
|
250
545
|
case 'strong': {
|
|
251
|
-
return this.createElement(this.c.strong, node.data?.hProperties, this.mapMdastChildren(node));
|
|
546
|
+
return this.createElement(this.c.strong, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
252
547
|
}
|
|
253
548
|
case 'emphasis': {
|
|
254
|
-
return this.createElement(this.c.em, node.data?.hProperties, this.mapMdastChildren(node));
|
|
549
|
+
return this.createElement(this.c.em, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
255
550
|
}
|
|
256
551
|
case 'delete': {
|
|
257
|
-
return this.createElement(this.c.del, node.data?.hProperties, this.mapMdastChildren(node));
|
|
552
|
+
return this.createElement(this.c.del, this.addLineNumberToProps(node.data?.hProperties, node), this.mapMdastChildren(node));
|
|
258
553
|
}
|
|
259
554
|
case 'inlineCode': {
|
|
260
555
|
if (!node.value) {
|
|
261
556
|
return [];
|
|
262
557
|
}
|
|
263
|
-
return this.createElement(this.c.code, node.data?.hProperties, node.value);
|
|
558
|
+
return this.createElement(this.c.code, this.addLineNumberToProps(node.data?.hProperties, node), node.value);
|
|
264
559
|
}
|
|
265
560
|
case 'break': {
|
|
266
|
-
return this.createElement(this.c.br, node.data?.hProperties);
|
|
561
|
+
return this.createElement(this.c.br, this.addLineNumberToProps(node.data?.hProperties, node));
|
|
267
562
|
}
|
|
268
563
|
case 'root': {
|
|
269
564
|
if (node.data?.hProperties) {
|
|
270
|
-
return this.createElement(this.c.div, node.data.hProperties, this.mapMdastChildren(node));
|
|
565
|
+
return this.createElement(this.c.div, this.addLineNumberToProps(node.data.hProperties, node), this.mapMdastChildren(node));
|
|
271
566
|
}
|
|
272
567
|
return this.createElement(Fragment, null, this.mapMdastChildren(node));
|
|
273
568
|
}
|
|
274
569
|
case 'table': {
|
|
275
570
|
const [head, ...body] = React.Children.toArray(this.mapMdastChildren(node));
|
|
276
|
-
return this.createElement(this.c.table, node.data?.hProperties, head && this.createElement(this.c.thead, null, head), !!body?.length &&
|
|
571
|
+
return this.createElement(this.c.table, this.addLineNumberToProps(node.data?.hProperties, node), head && this.createElement(this.c.thead, null, head), !!body?.length &&
|
|
572
|
+
this.createElement(this.c.tbody, null, body));
|
|
277
573
|
}
|
|
278
574
|
case 'tableRow': {
|
|
279
|
-
return this.createElement(this.c.tr, { className: '', ...node.data?.hProperties }, this.mapMdastChildren(node));
|
|
575
|
+
return this.createElement(this.c.tr, this.addLineNumberToProps({ className: '', ...node.data?.hProperties }, node), this.mapMdastChildren(node));
|
|
280
576
|
}
|
|
281
577
|
case 'tableCell': {
|
|
282
578
|
let content = this.mapMdastChildren(node);
|
|
283
|
-
return this.createElement(this.c.td, { className: '', ...node.data?.hProperties }, content);
|
|
579
|
+
return this.createElement(this.c.td, this.addLineNumberToProps({ className: '', ...node.data?.hProperties }, node), content);
|
|
284
580
|
}
|
|
285
581
|
case 'definition': {
|
|
286
582
|
return [];
|
|
287
583
|
}
|
|
288
584
|
case 'linkReference': {
|
|
289
585
|
let href = '';
|
|
586
|
+
let title = '';
|
|
290
587
|
mdastBfs(this.mdast, (child) => {
|
|
291
588
|
if (child.type === 'definition' &&
|
|
292
589
|
child.identifier === node.identifier) {
|
|
293
|
-
href = child.url;
|
|
590
|
+
href = child.url || '';
|
|
591
|
+
title = child.title || '';
|
|
294
592
|
}
|
|
295
593
|
});
|
|
296
|
-
return this.createElement(this.c.a, { href, ...node.data?.hProperties }, this.mapMdastChildren(node));
|
|
594
|
+
return this.createElement(this.c.a, this.addLineNumberToProps({ href, title, ...node.data?.hProperties }, node), this.mapMdastChildren(node));
|
|
297
595
|
}
|
|
298
596
|
case 'footnoteReference': {
|
|
299
597
|
return [];
|
|
@@ -311,7 +609,7 @@ export class MdastToJsx {
|
|
|
311
609
|
return this.createElement(Suspense, { fallback: null }, this.createElement(HtmlToJsxConverter, {
|
|
312
610
|
htmlText: text,
|
|
313
611
|
instance: this,
|
|
314
|
-
node
|
|
612
|
+
node,
|
|
315
613
|
}));
|
|
316
614
|
}
|
|
317
615
|
case 'imageReference': {
|
|
@@ -327,70 +625,6 @@ export class MdastToJsx {
|
|
|
327
625
|
}
|
|
328
626
|
}
|
|
329
627
|
}
|
|
330
|
-
export function getJsxAttrs(node, onError = console.error) {
|
|
331
|
-
let attrsList = node.attributes
|
|
332
|
-
.map((attr) => {
|
|
333
|
-
if (attr.type === 'mdxJsxExpressionAttribute') {
|
|
334
|
-
onError({
|
|
335
|
-
message: `Expressions in jsx props are not supported (${attr.value
|
|
336
|
-
.replace(/\n+/g, ' ')
|
|
337
|
-
.replace(/ +/g, ' ')})`,
|
|
338
|
-
line: attr.position?.start?.line,
|
|
339
|
-
});
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (attr.type !== 'mdxJsxAttribute') {
|
|
343
|
-
throw new Error(`non mdxJsxAttribute is not supported: ${attr}`);
|
|
344
|
-
}
|
|
345
|
-
const v = attr.value;
|
|
346
|
-
if (typeof v === 'string' || typeof v === 'number') {
|
|
347
|
-
return [attr.name, v];
|
|
348
|
-
}
|
|
349
|
-
if (v === null) {
|
|
350
|
-
return [attr.name, true];
|
|
351
|
-
}
|
|
352
|
-
if (v?.type === 'mdxJsxAttributeValueExpression') {
|
|
353
|
-
if (v.value === 'true') {
|
|
354
|
-
return [attr.name, true];
|
|
355
|
-
}
|
|
356
|
-
if (v.value === 'false') {
|
|
357
|
-
return [attr.name, false];
|
|
358
|
-
}
|
|
359
|
-
if (v.value === 'null') {
|
|
360
|
-
return [attr.name, null];
|
|
361
|
-
}
|
|
362
|
-
if (v.value === 'undefined') {
|
|
363
|
-
return [attr.name, undefined];
|
|
364
|
-
}
|
|
365
|
-
let quote = ['"', "'", '`'].find((q) => v.value.startsWith(q) && v.value.endsWith(q));
|
|
366
|
-
if (quote) {
|
|
367
|
-
let value = v.value;
|
|
368
|
-
if (quote !== '"') {
|
|
369
|
-
value = v.value.replace(new RegExp(quote, 'g'), '"');
|
|
370
|
-
}
|
|
371
|
-
return [attr.name, JSON.parse(value)];
|
|
372
|
-
}
|
|
373
|
-
const number = Number(v.value);
|
|
374
|
-
if (!isNaN(number)) {
|
|
375
|
-
return [attr.name, number];
|
|
376
|
-
}
|
|
377
|
-
const parsedJson = safeJsonParse(v.value);
|
|
378
|
-
if (parsedJson) {
|
|
379
|
-
return [attr.name, parsedJson];
|
|
380
|
-
}
|
|
381
|
-
onError({
|
|
382
|
-
message: `Expressions in jsx props are not supported (${attr.name}={${v.value}})`,
|
|
383
|
-
line: attr.position?.start?.line,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
console.log('unhandled attr', { attr }, attr.type);
|
|
388
|
-
}
|
|
389
|
-
return;
|
|
390
|
-
})
|
|
391
|
-
.filter(isTruthy);
|
|
392
|
-
return attrsList;
|
|
393
|
-
}
|
|
394
628
|
function isTruthy(val) {
|
|
395
629
|
return Boolean(val);
|
|
396
630
|
}
|