@readme/markdown 13.7.1 → 13.7.2
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/components/TailwindStyle/index.tsx +14 -4
- package/dist/lib/mdxish.d.ts +1 -1
- package/dist/main.js +627 -418
- package/dist/main.node.js +627 -418
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/mdxish/mdxish-tables-to-jsx.d.ts +11 -0
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -12309,10 +12309,19 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
|
|
|
12309
12309
|
records.forEach(record => {
|
|
12310
12310
|
if (record.type === 'childList') {
|
|
12311
12311
|
record.addedNodes.forEach(node => {
|
|
12312
|
-
if (!(node instanceof HTMLElement)
|
|
12312
|
+
if (!(node instanceof HTMLElement))
|
|
12313
12313
|
return;
|
|
12314
|
-
|
|
12315
|
-
|
|
12314
|
+
// Check the added node itself
|
|
12315
|
+
if (node.classList.contains(tailwindPrefix)) {
|
|
12316
|
+
traverse(node, addClasses);
|
|
12317
|
+
shouldUpdate = true;
|
|
12318
|
+
}
|
|
12319
|
+
// Also check descendants — React may insert a parent node
|
|
12320
|
+
// whose children contain TailwindRoot elements
|
|
12321
|
+
node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
|
|
12322
|
+
traverse(child, addClasses);
|
|
12323
|
+
shouldUpdate = true;
|
|
12324
|
+
});
|
|
12316
12325
|
});
|
|
12317
12326
|
}
|
|
12318
12327
|
else if (record.type === 'attributes') {
|
|
@@ -70410,9 +70419,416 @@ const mdast = (text, opts = {}) => {
|
|
|
70410
70419
|
};
|
|
70411
70420
|
/* harmony default export */ const lib_mdast = (mdast);
|
|
70412
70421
|
|
|
70422
|
+
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
70423
|
+
/**
|
|
70424
|
+
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
70425
|
+
*
|
|
70426
|
+
* @param content - The markdown content to process
|
|
70427
|
+
* @returns Object containing protected content and arrays of original code blocks
|
|
70428
|
+
* @example
|
|
70429
|
+
* ```typescript
|
|
70430
|
+
* const input = 'Text with `inline code` and ```fenced block```';
|
|
70431
|
+
* protectCodeBlocks(input)
|
|
70432
|
+
* // Returns: {
|
|
70433
|
+
* // protectedCode: {
|
|
70434
|
+
* // codeBlocks: ['```fenced block```'],
|
|
70435
|
+
* // inlineCode: ['`inline code`']
|
|
70436
|
+
* // },
|
|
70437
|
+
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
70438
|
+
* // }
|
|
70439
|
+
* ```
|
|
70440
|
+
*/
|
|
70441
|
+
function protectCodeBlocks(content) {
|
|
70442
|
+
const codeBlocks = [];
|
|
70443
|
+
const inlineCode = [];
|
|
70444
|
+
let protectedContent = '';
|
|
70445
|
+
let remaining = content;
|
|
70446
|
+
let codeBlockStart = remaining.indexOf('```');
|
|
70447
|
+
while (codeBlockStart !== -1) {
|
|
70448
|
+
protectedContent += remaining.slice(0, codeBlockStart);
|
|
70449
|
+
remaining = remaining.slice(codeBlockStart);
|
|
70450
|
+
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
70451
|
+
if (codeBlockEnd === -1) {
|
|
70452
|
+
break;
|
|
70453
|
+
}
|
|
70454
|
+
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
70455
|
+
const index = codeBlocks.length;
|
|
70456
|
+
codeBlocks.push(match);
|
|
70457
|
+
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
70458
|
+
remaining = remaining.slice(codeBlockEnd + 3);
|
|
70459
|
+
codeBlockStart = remaining.indexOf('```');
|
|
70460
|
+
}
|
|
70461
|
+
protectedContent += remaining;
|
|
70462
|
+
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
70463
|
+
const index = inlineCode.length;
|
|
70464
|
+
inlineCode.push(match);
|
|
70465
|
+
return `___INLINE_CODE_${index}___`;
|
|
70466
|
+
});
|
|
70467
|
+
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
70468
|
+
}
|
|
70469
|
+
/**
|
|
70470
|
+
* Restores inline code by replacing placeholders with original content.
|
|
70471
|
+
*
|
|
70472
|
+
* @param content - Content with inline code placeholders
|
|
70473
|
+
* @param protectedCode - The protected code arrays
|
|
70474
|
+
* @returns Content with inline code restored
|
|
70475
|
+
*/
|
|
70476
|
+
function restoreInlineCode(content, protectedCode) {
|
|
70477
|
+
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
70478
|
+
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
70479
|
+
});
|
|
70480
|
+
}
|
|
70481
|
+
/**
|
|
70482
|
+
* Restores fenced code blocks by replacing placeholders with original content.
|
|
70483
|
+
*
|
|
70484
|
+
* @param content - Content with code block placeholders
|
|
70485
|
+
* @param protectedCode - The protected code arrays
|
|
70486
|
+
* @returns Content with code blocks restored
|
|
70487
|
+
*/
|
|
70488
|
+
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
70489
|
+
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
70490
|
+
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
70491
|
+
});
|
|
70492
|
+
}
|
|
70493
|
+
/**
|
|
70494
|
+
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
70495
|
+
*
|
|
70496
|
+
* @param content - Content with code placeholders
|
|
70497
|
+
* @param protectedCode - The protected code arrays
|
|
70498
|
+
* @returns Content with all code blocks and inline code restored
|
|
70499
|
+
* @example
|
|
70500
|
+
* ```typescript
|
|
70501
|
+
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
70502
|
+
* const protectedCode = {
|
|
70503
|
+
* codeBlocks: ['```js\ncode\n```'],
|
|
70504
|
+
* inlineCode: ['`inline`']
|
|
70505
|
+
* };
|
|
70506
|
+
* restoreCodeBlocks(content, protectedCode)
|
|
70507
|
+
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
70508
|
+
* ```
|
|
70509
|
+
*/
|
|
70510
|
+
function restoreCodeBlocks(content, protectedCode) {
|
|
70511
|
+
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
70512
|
+
restored = restoreInlineCode(restored, protectedCode);
|
|
70513
|
+
return restored;
|
|
70514
|
+
}
|
|
70515
|
+
|
|
70516
|
+
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
70517
|
+
|
|
70518
|
+
// Base64 encode (Node.js + browser compatible)
|
|
70519
|
+
function base64Encode(str) {
|
|
70520
|
+
if (typeof Buffer !== 'undefined') {
|
|
70521
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
70522
|
+
}
|
|
70523
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
70524
|
+
}
|
|
70525
|
+
// Base64 decode (Node.js + browser compatible)
|
|
70526
|
+
function base64Decode(str) {
|
|
70527
|
+
if (typeof Buffer !== 'undefined') {
|
|
70528
|
+
return Buffer.from(str, 'base64').toString('utf-8');
|
|
70529
|
+
}
|
|
70530
|
+
return decodeURIComponent(escape(atob(str)));
|
|
70531
|
+
}
|
|
70532
|
+
function escapeHtmlAttribute(value) {
|
|
70533
|
+
return value
|
|
70534
|
+
.replace(/&/g, '&')
|
|
70535
|
+
.replace(/"/g, '"')
|
|
70536
|
+
.replace(/</g, '<')
|
|
70537
|
+
.replace(/>/g, '>')
|
|
70538
|
+
.replace(/\n/g, ' ');
|
|
70539
|
+
}
|
|
70540
|
+
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
70541
|
+
// Using a prefix that won't conflict with regular string values
|
|
70542
|
+
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
70543
|
+
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
70544
|
+
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
70545
|
+
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
70546
|
+
/**
|
|
70547
|
+
* Evaluates a JavaScript expression using context variables.
|
|
70548
|
+
*
|
|
70549
|
+
* @param expression
|
|
70550
|
+
* @param context
|
|
70551
|
+
* @returns The evaluated result
|
|
70552
|
+
* @example
|
|
70553
|
+
* ```typescript
|
|
70554
|
+
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
70555
|
+
* evaluateExpression('baseUrl + path', context)
|
|
70556
|
+
* // Returns: 'https://example.com/api'
|
|
70557
|
+
* ```
|
|
70558
|
+
*/
|
|
70559
|
+
function evaluateExpression(expression, context) {
|
|
70560
|
+
const contextKeys = Object.keys(context);
|
|
70561
|
+
const contextValues = Object.values(context);
|
|
70562
|
+
// eslint-disable-next-line no-new-func
|
|
70563
|
+
const func = new Function(...contextKeys, `return ${expression}`);
|
|
70564
|
+
return func(...contextValues);
|
|
70565
|
+
}
|
|
70566
|
+
/**
|
|
70567
|
+
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
70568
|
+
*
|
|
70569
|
+
* @param content
|
|
70570
|
+
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
70571
|
+
* @example
|
|
70572
|
+
* ```typescript
|
|
70573
|
+
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
70574
|
+
* protectHTMLBlockContent(input)
|
|
70575
|
+
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
70576
|
+
* ```
|
|
70577
|
+
*/
|
|
70578
|
+
function protectHTMLBlockContent(content) {
|
|
70579
|
+
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
70580
|
+
const encoded = base64Encode(templateContent);
|
|
70581
|
+
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
70582
|
+
});
|
|
70583
|
+
}
|
|
70584
|
+
/**
|
|
70585
|
+
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
70586
|
+
*
|
|
70587
|
+
* @param content
|
|
70588
|
+
* @returns Content with JSX comments removed
|
|
70589
|
+
* @example
|
|
70590
|
+
* ```typescript
|
|
70591
|
+
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
70592
|
+
* // Returns: 'Text more text'
|
|
70593
|
+
* ```
|
|
70594
|
+
*/
|
|
70595
|
+
function removeJSXComments(content) {
|
|
70596
|
+
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
70597
|
+
}
|
|
70598
|
+
/**
|
|
70599
|
+
* Extracts content between balanced braces, handling nested braces.
|
|
70600
|
+
*
|
|
70601
|
+
* @param content
|
|
70602
|
+
* @param start
|
|
70603
|
+
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
70604
|
+
* @example
|
|
70605
|
+
* ```typescript
|
|
70606
|
+
* const input = 'foo{bar{baz}qux}end';
|
|
70607
|
+
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
70608
|
+
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
70609
|
+
* ```
|
|
70610
|
+
*/
|
|
70611
|
+
function extractBalancedBraces(content, start) {
|
|
70612
|
+
let depth = 1;
|
|
70613
|
+
let pos = start;
|
|
70614
|
+
while (pos < content.length && depth > 0) {
|
|
70615
|
+
const char = content[pos];
|
|
70616
|
+
if (char === '{')
|
|
70617
|
+
depth += 1;
|
|
70618
|
+
else if (char === '}')
|
|
70619
|
+
depth -= 1;
|
|
70620
|
+
pos += 1;
|
|
70621
|
+
}
|
|
70622
|
+
if (depth !== 0)
|
|
70623
|
+
return null;
|
|
70624
|
+
return { content: content.slice(start, pos - 1), end: pos };
|
|
70625
|
+
}
|
|
70626
|
+
/**
|
|
70627
|
+
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
70628
|
+
* Handles three cases:
|
|
70629
|
+
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
70630
|
+
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
70631
|
+
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
70632
|
+
*
|
|
70633
|
+
*/
|
|
70634
|
+
function escapeProblematicBraces(content) {
|
|
70635
|
+
// Skip HTML elements — their content should never be escaped because
|
|
70636
|
+
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
70637
|
+
const htmlElements = [];
|
|
70638
|
+
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
70639
|
+
const idx = htmlElements.length;
|
|
70640
|
+
htmlElements.push(match);
|
|
70641
|
+
return `___HTML_ELEM_${idx}___`;
|
|
70642
|
+
});
|
|
70643
|
+
const toEscape = new Set();
|
|
70644
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
70645
|
+
const chars = Array.from(safe);
|
|
70646
|
+
let strDelim = null;
|
|
70647
|
+
let strEscaped = false;
|
|
70648
|
+
// Stack of open braces with their state
|
|
70649
|
+
const openStack = [];
|
|
70650
|
+
// Track position of last newline (outside strings) to detect blank lines
|
|
70651
|
+
let lastNewlinePos = -2; // -2 means no recent newline
|
|
70652
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
70653
|
+
const ch = chars[i];
|
|
70654
|
+
// Track string delimiters inside expressions to ignore braces within them
|
|
70655
|
+
if (openStack.length > 0) {
|
|
70656
|
+
if (strDelim) {
|
|
70657
|
+
if (strEscaped)
|
|
70658
|
+
strEscaped = false;
|
|
70659
|
+
else if (ch === '\\')
|
|
70660
|
+
strEscaped = true;
|
|
70661
|
+
else if (ch === strDelim)
|
|
70662
|
+
strDelim = null;
|
|
70663
|
+
// eslint-disable-next-line no-continue
|
|
70664
|
+
continue;
|
|
70665
|
+
}
|
|
70666
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
70667
|
+
strDelim = ch;
|
|
70668
|
+
// eslint-disable-next-line no-continue
|
|
70669
|
+
continue;
|
|
70670
|
+
}
|
|
70671
|
+
// Track newlines to detect blank lines (paragraph boundaries)
|
|
70672
|
+
if (ch === '\n') {
|
|
70673
|
+
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
70674
|
+
if (lastNewlinePos >= 0) {
|
|
70675
|
+
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
70676
|
+
if (/^[ \t]*$/.test(between)) {
|
|
70677
|
+
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
70678
|
+
openStack.forEach(entry => {
|
|
70679
|
+
entry.hasBlankLine = true;
|
|
70680
|
+
});
|
|
70681
|
+
}
|
|
70682
|
+
}
|
|
70683
|
+
lastNewlinePos = i;
|
|
70684
|
+
}
|
|
70685
|
+
}
|
|
70686
|
+
// Skip already-escaped braces (count preceding backslashes)
|
|
70687
|
+
if (ch === '{' || ch === '}') {
|
|
70688
|
+
let bs = 0;
|
|
70689
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
70690
|
+
bs += 1;
|
|
70691
|
+
if (bs % 2 === 1) {
|
|
70692
|
+
// eslint-disable-next-line no-continue
|
|
70693
|
+
continue;
|
|
70694
|
+
}
|
|
70695
|
+
}
|
|
70696
|
+
if (ch === '{') {
|
|
70697
|
+
openStack.push({ pos: i, hasBlankLine: false });
|
|
70698
|
+
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
70699
|
+
}
|
|
70700
|
+
else if (ch === '}') {
|
|
70701
|
+
if (openStack.length > 0) {
|
|
70702
|
+
const entry = openStack.pop();
|
|
70703
|
+
// If expression spans paragraph boundary, escape both braces
|
|
70704
|
+
if (entry.hasBlankLine) {
|
|
70705
|
+
toEscape.add(entry.pos);
|
|
70706
|
+
toEscape.add(i);
|
|
70707
|
+
}
|
|
70708
|
+
}
|
|
70709
|
+
else {
|
|
70710
|
+
// Unbalanced closing brace (no matching open)
|
|
70711
|
+
toEscape.add(i);
|
|
70712
|
+
}
|
|
70713
|
+
}
|
|
70714
|
+
}
|
|
70715
|
+
// Any remaining open braces are unbalanced
|
|
70716
|
+
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
70717
|
+
// If there are no problematic braces, return safe content as-is;
|
|
70718
|
+
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
70719
|
+
let result = toEscape.size === 0
|
|
70720
|
+
? safe
|
|
70721
|
+
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
70722
|
+
// Restore HTML elements
|
|
70723
|
+
if (htmlElements.length > 0) {
|
|
70724
|
+
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
70725
|
+
}
|
|
70726
|
+
return result;
|
|
70727
|
+
}
|
|
70728
|
+
/**
|
|
70729
|
+
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
70730
|
+
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
70731
|
+
*
|
|
70732
|
+
* @param content
|
|
70733
|
+
* @param context
|
|
70734
|
+
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
70735
|
+
* @example
|
|
70736
|
+
* ```typescript
|
|
70737
|
+
* const context = { baseUrl: 'https://example.com' };
|
|
70738
|
+
* const input = '<a href={baseUrl}>Link</a>';
|
|
70739
|
+
* evaluateAttributeExpressions(input, context)
|
|
70740
|
+
* // Returns: '<a href="https://example.com">Link</a>'
|
|
70741
|
+
* ```
|
|
70742
|
+
*/
|
|
70743
|
+
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
70744
|
+
const attrStartRegex = /(\w+)=\{/g;
|
|
70745
|
+
let result = '';
|
|
70746
|
+
let lastEnd = 0;
|
|
70747
|
+
let match = attrStartRegex.exec(content);
|
|
70748
|
+
while (match !== null) {
|
|
70749
|
+
const attributeName = match[1];
|
|
70750
|
+
const braceStart = match.index + match[0].length;
|
|
70751
|
+
const extracted = extractBalancedBraces(content, braceStart);
|
|
70752
|
+
if (extracted) {
|
|
70753
|
+
// The expression might contain template literals in MDX component tag props
|
|
70754
|
+
// E.g. <Component greeting={`Hello World!`} />
|
|
70755
|
+
// that is marked as inline code. So we need to restore the inline codes
|
|
70756
|
+
// in the expression to evaluate it
|
|
70757
|
+
let expression = extracted.content;
|
|
70758
|
+
if (protectedCode) {
|
|
70759
|
+
expression = restoreInlineCode(expression, protectedCode);
|
|
70760
|
+
}
|
|
70761
|
+
const fullMatchEnd = extracted.end;
|
|
70762
|
+
result += content.slice(lastEnd, match.index);
|
|
70763
|
+
try {
|
|
70764
|
+
const evalResult = evaluateExpression(expression, context);
|
|
70765
|
+
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
70766
|
+
if (attributeName === 'style') {
|
|
70767
|
+
const cssString = Object.entries(evalResult)
|
|
70768
|
+
.map(([key, value]) => {
|
|
70769
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
70770
|
+
return `${cssKey}: ${value}`;
|
|
70771
|
+
})
|
|
70772
|
+
.join('; ');
|
|
70773
|
+
result += `style="${cssString}"`;
|
|
70774
|
+
}
|
|
70775
|
+
else {
|
|
70776
|
+
// These are arrays / objects attribute values
|
|
70777
|
+
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
70778
|
+
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
70779
|
+
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
70780
|
+
result += `${attributeName}="${jsonValue}"`;
|
|
70781
|
+
}
|
|
70782
|
+
}
|
|
70783
|
+
else if (attributeName === 'className') {
|
|
70784
|
+
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
70785
|
+
// This will be restored later in the pipeline
|
|
70786
|
+
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
70787
|
+
}
|
|
70788
|
+
else {
|
|
70789
|
+
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
70790
|
+
}
|
|
70791
|
+
}
|
|
70792
|
+
catch (_error) {
|
|
70793
|
+
result += content.slice(match.index, fullMatchEnd);
|
|
70794
|
+
}
|
|
70795
|
+
lastEnd = fullMatchEnd;
|
|
70796
|
+
attrStartRegex.lastIndex = fullMatchEnd;
|
|
70797
|
+
}
|
|
70798
|
+
match = attrStartRegex.exec(content);
|
|
70799
|
+
}
|
|
70800
|
+
result += content.slice(lastEnd);
|
|
70801
|
+
return result;
|
|
70802
|
+
}
|
|
70803
|
+
/**
|
|
70804
|
+
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
70805
|
+
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
70806
|
+
*
|
|
70807
|
+
* @param content
|
|
70808
|
+
* @param context
|
|
70809
|
+
* @returns Preprocessed content ready for markdown parsing
|
|
70810
|
+
*/
|
|
70811
|
+
function preprocessJSXExpressions(content, context = {}) {
|
|
70812
|
+
// Step 0: Base64 encode HTMLBlock content
|
|
70813
|
+
let processed = protectHTMLBlockContent(content);
|
|
70814
|
+
// Step 1: Protect code blocks and inline code
|
|
70815
|
+
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
70816
|
+
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
70817
|
+
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
70818
|
+
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
70819
|
+
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
70820
|
+
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
70821
|
+
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
70822
|
+
processed = escapeProblematicBraces(processed);
|
|
70823
|
+
// Step 4: Restore protected code blocks
|
|
70824
|
+
processed = restoreCodeBlocks(processed, protectedCode);
|
|
70825
|
+
return processed;
|
|
70826
|
+
}
|
|
70827
|
+
|
|
70413
70828
|
;// ./processor/utils.ts
|
|
70414
70829
|
|
|
70415
70830
|
|
|
70831
|
+
|
|
70416
70832
|
/**
|
|
70417
70833
|
* Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
|
|
70418
70834
|
* This currently sets all the values to a string since we process/compile the MDX on the fly
|
|
@@ -70468,7 +70884,17 @@ const getHPropKeys = (node) => {
|
|
|
70468
70884
|
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
70469
70885
|
if ('name' in attr) {
|
|
70470
70886
|
if (typeof attr.value === 'string') {
|
|
70471
|
-
|
|
70887
|
+
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
70888
|
+
try {
|
|
70889
|
+
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
70890
|
+
}
|
|
70891
|
+
catch {
|
|
70892
|
+
memo[attr.name] = attr.value;
|
|
70893
|
+
}
|
|
70894
|
+
}
|
|
70895
|
+
else {
|
|
70896
|
+
memo[attr.name] = attr.value;
|
|
70897
|
+
}
|
|
70472
70898
|
}
|
|
70473
70899
|
else if (attr.value === null) {
|
|
70474
70900
|
memo[attr.name] = true;
|
|
@@ -71242,10 +71668,10 @@ const hasFlowContent = (nodes) => {
|
|
|
71242
71668
|
* Process a Table node: re-parse text-only cell content, then output as
|
|
71243
71669
|
* a markdown table (phrasing-only) or keep as JSX <Table> (has flow content).
|
|
71244
71670
|
*/
|
|
71245
|
-
const processTableNode = (node, index, parent) => {
|
|
71671
|
+
const processTableNode = (node, index, parent, documentPosition) => {
|
|
71246
71672
|
if (node.name !== 'Table')
|
|
71247
71673
|
return;
|
|
71248
|
-
const
|
|
71674
|
+
const position = documentPosition ?? node.position;
|
|
71249
71675
|
const { align: alignAttr } = getAttrs(node);
|
|
71250
71676
|
const align = Array.isArray(alignAttr) ? alignAttr : null;
|
|
71251
71677
|
let tableHasFlowContent = false;
|
|
@@ -71368,7 +71794,7 @@ const mdxishTables = () => tree => {
|
|
|
71368
71794
|
const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
|
|
71369
71795
|
visit(parsed, isMDXElement, (tableNode) => {
|
|
71370
71796
|
if (tableNode.name === 'Table') {
|
|
71371
|
-
processTableNode(tableNode, index, parent);
|
|
71797
|
+
processTableNode(tableNode, index, parent, node.position);
|
|
71372
71798
|
// Stop after the outermost Table so nested Tables don't overwrite parent.children[index]
|
|
71373
71799
|
// we let it get handled naturally
|
|
71374
71800
|
return EXIT;
|
|
@@ -93843,412 +94269,6 @@ const mdxComponentHandlers = {
|
|
|
93843
94269
|
[NodeTypes.htmlBlock]: htmlBlockHandler,
|
|
93844
94270
|
};
|
|
93845
94271
|
|
|
93846
|
-
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
93847
|
-
/**
|
|
93848
|
-
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
93849
|
-
*
|
|
93850
|
-
* @param content - The markdown content to process
|
|
93851
|
-
* @returns Object containing protected content and arrays of original code blocks
|
|
93852
|
-
* @example
|
|
93853
|
-
* ```typescript
|
|
93854
|
-
* const input = 'Text with `inline code` and ```fenced block```';
|
|
93855
|
-
* protectCodeBlocks(input)
|
|
93856
|
-
* // Returns: {
|
|
93857
|
-
* // protectedCode: {
|
|
93858
|
-
* // codeBlocks: ['```fenced block```'],
|
|
93859
|
-
* // inlineCode: ['`inline code`']
|
|
93860
|
-
* // },
|
|
93861
|
-
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
93862
|
-
* // }
|
|
93863
|
-
* ```
|
|
93864
|
-
*/
|
|
93865
|
-
function protectCodeBlocks(content) {
|
|
93866
|
-
const codeBlocks = [];
|
|
93867
|
-
const inlineCode = [];
|
|
93868
|
-
let protectedContent = '';
|
|
93869
|
-
let remaining = content;
|
|
93870
|
-
let codeBlockStart = remaining.indexOf('```');
|
|
93871
|
-
while (codeBlockStart !== -1) {
|
|
93872
|
-
protectedContent += remaining.slice(0, codeBlockStart);
|
|
93873
|
-
remaining = remaining.slice(codeBlockStart);
|
|
93874
|
-
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
93875
|
-
if (codeBlockEnd === -1) {
|
|
93876
|
-
break;
|
|
93877
|
-
}
|
|
93878
|
-
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
93879
|
-
const index = codeBlocks.length;
|
|
93880
|
-
codeBlocks.push(match);
|
|
93881
|
-
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
93882
|
-
remaining = remaining.slice(codeBlockEnd + 3);
|
|
93883
|
-
codeBlockStart = remaining.indexOf('```');
|
|
93884
|
-
}
|
|
93885
|
-
protectedContent += remaining;
|
|
93886
|
-
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
93887
|
-
const index = inlineCode.length;
|
|
93888
|
-
inlineCode.push(match);
|
|
93889
|
-
return `___INLINE_CODE_${index}___`;
|
|
93890
|
-
});
|
|
93891
|
-
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
93892
|
-
}
|
|
93893
|
-
/**
|
|
93894
|
-
* Restores inline code by replacing placeholders with original content.
|
|
93895
|
-
*
|
|
93896
|
-
* @param content - Content with inline code placeholders
|
|
93897
|
-
* @param protectedCode - The protected code arrays
|
|
93898
|
-
* @returns Content with inline code restored
|
|
93899
|
-
*/
|
|
93900
|
-
function restoreInlineCode(content, protectedCode) {
|
|
93901
|
-
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
93902
|
-
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
93903
|
-
});
|
|
93904
|
-
}
|
|
93905
|
-
/**
|
|
93906
|
-
* Restores fenced code blocks by replacing placeholders with original content.
|
|
93907
|
-
*
|
|
93908
|
-
* @param content - Content with code block placeholders
|
|
93909
|
-
* @param protectedCode - The protected code arrays
|
|
93910
|
-
* @returns Content with code blocks restored
|
|
93911
|
-
*/
|
|
93912
|
-
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
93913
|
-
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
93914
|
-
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
93915
|
-
});
|
|
93916
|
-
}
|
|
93917
|
-
/**
|
|
93918
|
-
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
93919
|
-
*
|
|
93920
|
-
* @param content - Content with code placeholders
|
|
93921
|
-
* @param protectedCode - The protected code arrays
|
|
93922
|
-
* @returns Content with all code blocks and inline code restored
|
|
93923
|
-
* @example
|
|
93924
|
-
* ```typescript
|
|
93925
|
-
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
93926
|
-
* const protectedCode = {
|
|
93927
|
-
* codeBlocks: ['```js\ncode\n```'],
|
|
93928
|
-
* inlineCode: ['`inline`']
|
|
93929
|
-
* };
|
|
93930
|
-
* restoreCodeBlocks(content, protectedCode)
|
|
93931
|
-
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
93932
|
-
* ```
|
|
93933
|
-
*/
|
|
93934
|
-
function restoreCodeBlocks(content, protectedCode) {
|
|
93935
|
-
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
93936
|
-
restored = restoreInlineCode(restored, protectedCode);
|
|
93937
|
-
return restored;
|
|
93938
|
-
}
|
|
93939
|
-
|
|
93940
|
-
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
93941
|
-
|
|
93942
|
-
// Base64 encode (Node.js + browser compatible)
|
|
93943
|
-
function base64Encode(str) {
|
|
93944
|
-
if (typeof Buffer !== 'undefined') {
|
|
93945
|
-
return Buffer.from(str, 'utf-8').toString('base64');
|
|
93946
|
-
}
|
|
93947
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
93948
|
-
}
|
|
93949
|
-
// Base64 decode (Node.js + browser compatible)
|
|
93950
|
-
function base64Decode(str) {
|
|
93951
|
-
if (typeof Buffer !== 'undefined') {
|
|
93952
|
-
return Buffer.from(str, 'base64').toString('utf-8');
|
|
93953
|
-
}
|
|
93954
|
-
return decodeURIComponent(escape(atob(str)));
|
|
93955
|
-
}
|
|
93956
|
-
function escapeHtmlAttribute(value) {
|
|
93957
|
-
return value
|
|
93958
|
-
.replace(/&/g, '&')
|
|
93959
|
-
.replace(/"/g, '"')
|
|
93960
|
-
.replace(/</g, '<')
|
|
93961
|
-
.replace(/>/g, '>')
|
|
93962
|
-
.replace(/\n/g, ' ');
|
|
93963
|
-
}
|
|
93964
|
-
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
93965
|
-
// Using a prefix that won't conflict with regular string values
|
|
93966
|
-
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
93967
|
-
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
93968
|
-
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
93969
|
-
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
93970
|
-
/**
|
|
93971
|
-
* Evaluates a JavaScript expression using context variables.
|
|
93972
|
-
*
|
|
93973
|
-
* @param expression
|
|
93974
|
-
* @param context
|
|
93975
|
-
* @returns The evaluated result
|
|
93976
|
-
* @example
|
|
93977
|
-
* ```typescript
|
|
93978
|
-
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
93979
|
-
* evaluateExpression('baseUrl + path', context)
|
|
93980
|
-
* // Returns: 'https://example.com/api'
|
|
93981
|
-
* ```
|
|
93982
|
-
*/
|
|
93983
|
-
function evaluateExpression(expression, context) {
|
|
93984
|
-
const contextKeys = Object.keys(context);
|
|
93985
|
-
const contextValues = Object.values(context);
|
|
93986
|
-
// eslint-disable-next-line no-new-func
|
|
93987
|
-
const func = new Function(...contextKeys, `return ${expression}`);
|
|
93988
|
-
return func(...contextValues);
|
|
93989
|
-
}
|
|
93990
|
-
/**
|
|
93991
|
-
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
93992
|
-
*
|
|
93993
|
-
* @param content
|
|
93994
|
-
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
93995
|
-
* @example
|
|
93996
|
-
* ```typescript
|
|
93997
|
-
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
93998
|
-
* protectHTMLBlockContent(input)
|
|
93999
|
-
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
94000
|
-
* ```
|
|
94001
|
-
*/
|
|
94002
|
-
function protectHTMLBlockContent(content) {
|
|
94003
|
-
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
94004
|
-
const encoded = base64Encode(templateContent);
|
|
94005
|
-
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
94006
|
-
});
|
|
94007
|
-
}
|
|
94008
|
-
/**
|
|
94009
|
-
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
94010
|
-
*
|
|
94011
|
-
* @param content
|
|
94012
|
-
* @returns Content with JSX comments removed
|
|
94013
|
-
* @example
|
|
94014
|
-
* ```typescript
|
|
94015
|
-
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
94016
|
-
* // Returns: 'Text more text'
|
|
94017
|
-
* ```
|
|
94018
|
-
*/
|
|
94019
|
-
function removeJSXComments(content) {
|
|
94020
|
-
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
94021
|
-
}
|
|
94022
|
-
/**
|
|
94023
|
-
* Extracts content between balanced braces, handling nested braces.
|
|
94024
|
-
*
|
|
94025
|
-
* @param content
|
|
94026
|
-
* @param start
|
|
94027
|
-
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
94028
|
-
* @example
|
|
94029
|
-
* ```typescript
|
|
94030
|
-
* const input = 'foo{bar{baz}qux}end';
|
|
94031
|
-
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
94032
|
-
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
94033
|
-
* ```
|
|
94034
|
-
*/
|
|
94035
|
-
function extractBalancedBraces(content, start) {
|
|
94036
|
-
let depth = 1;
|
|
94037
|
-
let pos = start;
|
|
94038
|
-
while (pos < content.length && depth > 0) {
|
|
94039
|
-
const char = content[pos];
|
|
94040
|
-
if (char === '{')
|
|
94041
|
-
depth += 1;
|
|
94042
|
-
else if (char === '}')
|
|
94043
|
-
depth -= 1;
|
|
94044
|
-
pos += 1;
|
|
94045
|
-
}
|
|
94046
|
-
if (depth !== 0)
|
|
94047
|
-
return null;
|
|
94048
|
-
return { content: content.slice(start, pos - 1), end: pos };
|
|
94049
|
-
}
|
|
94050
|
-
/**
|
|
94051
|
-
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
94052
|
-
* Handles three cases:
|
|
94053
|
-
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
94054
|
-
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
94055
|
-
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
94056
|
-
*
|
|
94057
|
-
*/
|
|
94058
|
-
function escapeProblematicBraces(content) {
|
|
94059
|
-
// Skip HTML elements — their content should never be escaped because
|
|
94060
|
-
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
94061
|
-
const htmlElements = [];
|
|
94062
|
-
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
94063
|
-
const idx = htmlElements.length;
|
|
94064
|
-
htmlElements.push(match);
|
|
94065
|
-
return `___HTML_ELEM_${idx}___`;
|
|
94066
|
-
});
|
|
94067
|
-
const toEscape = new Set();
|
|
94068
|
-
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
94069
|
-
const chars = Array.from(safe);
|
|
94070
|
-
let strDelim = null;
|
|
94071
|
-
let strEscaped = false;
|
|
94072
|
-
// Stack of open braces with their state
|
|
94073
|
-
const openStack = [];
|
|
94074
|
-
// Track position of last newline (outside strings) to detect blank lines
|
|
94075
|
-
let lastNewlinePos = -2; // -2 means no recent newline
|
|
94076
|
-
for (let i = 0; i < chars.length; i += 1) {
|
|
94077
|
-
const ch = chars[i];
|
|
94078
|
-
// Track string delimiters inside expressions to ignore braces within them
|
|
94079
|
-
if (openStack.length > 0) {
|
|
94080
|
-
if (strDelim) {
|
|
94081
|
-
if (strEscaped)
|
|
94082
|
-
strEscaped = false;
|
|
94083
|
-
else if (ch === '\\')
|
|
94084
|
-
strEscaped = true;
|
|
94085
|
-
else if (ch === strDelim)
|
|
94086
|
-
strDelim = null;
|
|
94087
|
-
// eslint-disable-next-line no-continue
|
|
94088
|
-
continue;
|
|
94089
|
-
}
|
|
94090
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
94091
|
-
strDelim = ch;
|
|
94092
|
-
// eslint-disable-next-line no-continue
|
|
94093
|
-
continue;
|
|
94094
|
-
}
|
|
94095
|
-
// Track newlines to detect blank lines (paragraph boundaries)
|
|
94096
|
-
if (ch === '\n') {
|
|
94097
|
-
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
94098
|
-
if (lastNewlinePos >= 0) {
|
|
94099
|
-
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
94100
|
-
if (/^[ \t]*$/.test(between)) {
|
|
94101
|
-
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
94102
|
-
openStack.forEach(entry => {
|
|
94103
|
-
entry.hasBlankLine = true;
|
|
94104
|
-
});
|
|
94105
|
-
}
|
|
94106
|
-
}
|
|
94107
|
-
lastNewlinePos = i;
|
|
94108
|
-
}
|
|
94109
|
-
}
|
|
94110
|
-
// Skip already-escaped braces (count preceding backslashes)
|
|
94111
|
-
if (ch === '{' || ch === '}') {
|
|
94112
|
-
let bs = 0;
|
|
94113
|
-
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
94114
|
-
bs += 1;
|
|
94115
|
-
if (bs % 2 === 1) {
|
|
94116
|
-
// eslint-disable-next-line no-continue
|
|
94117
|
-
continue;
|
|
94118
|
-
}
|
|
94119
|
-
}
|
|
94120
|
-
if (ch === '{') {
|
|
94121
|
-
openStack.push({ pos: i, hasBlankLine: false });
|
|
94122
|
-
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
94123
|
-
}
|
|
94124
|
-
else if (ch === '}') {
|
|
94125
|
-
if (openStack.length > 0) {
|
|
94126
|
-
const entry = openStack.pop();
|
|
94127
|
-
// If expression spans paragraph boundary, escape both braces
|
|
94128
|
-
if (entry.hasBlankLine) {
|
|
94129
|
-
toEscape.add(entry.pos);
|
|
94130
|
-
toEscape.add(i);
|
|
94131
|
-
}
|
|
94132
|
-
}
|
|
94133
|
-
else {
|
|
94134
|
-
// Unbalanced closing brace (no matching open)
|
|
94135
|
-
toEscape.add(i);
|
|
94136
|
-
}
|
|
94137
|
-
}
|
|
94138
|
-
}
|
|
94139
|
-
// Any remaining open braces are unbalanced
|
|
94140
|
-
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
94141
|
-
// If there are no problematic braces, return safe content as-is;
|
|
94142
|
-
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
94143
|
-
let result = toEscape.size === 0
|
|
94144
|
-
? safe
|
|
94145
|
-
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
94146
|
-
// Restore HTML elements
|
|
94147
|
-
if (htmlElements.length > 0) {
|
|
94148
|
-
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
94149
|
-
}
|
|
94150
|
-
return result;
|
|
94151
|
-
}
|
|
94152
|
-
/**
|
|
94153
|
-
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
94154
|
-
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
94155
|
-
*
|
|
94156
|
-
* @param content
|
|
94157
|
-
* @param context
|
|
94158
|
-
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
94159
|
-
* @example
|
|
94160
|
-
* ```typescript
|
|
94161
|
-
* const context = { baseUrl: 'https://example.com' };
|
|
94162
|
-
* const input = '<a href={baseUrl}>Link</a>';
|
|
94163
|
-
* evaluateAttributeExpressions(input, context)
|
|
94164
|
-
* // Returns: '<a href="https://example.com">Link</a>'
|
|
94165
|
-
* ```
|
|
94166
|
-
*/
|
|
94167
|
-
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
94168
|
-
const attrStartRegex = /(\w+)=\{/g;
|
|
94169
|
-
let result = '';
|
|
94170
|
-
let lastEnd = 0;
|
|
94171
|
-
let match = attrStartRegex.exec(content);
|
|
94172
|
-
while (match !== null) {
|
|
94173
|
-
const attributeName = match[1];
|
|
94174
|
-
const braceStart = match.index + match[0].length;
|
|
94175
|
-
const extracted = extractBalancedBraces(content, braceStart);
|
|
94176
|
-
if (extracted) {
|
|
94177
|
-
// The expression might contain template literals in MDX component tag props
|
|
94178
|
-
// E.g. <Component greeting={`Hello World!`} />
|
|
94179
|
-
// that is marked as inline code. So we need to restore the inline codes
|
|
94180
|
-
// in the expression to evaluate it
|
|
94181
|
-
let expression = extracted.content;
|
|
94182
|
-
if (protectedCode) {
|
|
94183
|
-
expression = restoreInlineCode(expression, protectedCode);
|
|
94184
|
-
}
|
|
94185
|
-
const fullMatchEnd = extracted.end;
|
|
94186
|
-
result += content.slice(lastEnd, match.index);
|
|
94187
|
-
try {
|
|
94188
|
-
const evalResult = evaluateExpression(expression, context);
|
|
94189
|
-
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
94190
|
-
if (attributeName === 'style') {
|
|
94191
|
-
const cssString = Object.entries(evalResult)
|
|
94192
|
-
.map(([key, value]) => {
|
|
94193
|
-
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
94194
|
-
return `${cssKey}: ${value}`;
|
|
94195
|
-
})
|
|
94196
|
-
.join('; ');
|
|
94197
|
-
result += `style="${cssString}"`;
|
|
94198
|
-
}
|
|
94199
|
-
else {
|
|
94200
|
-
// These are arrays / objects attribute values
|
|
94201
|
-
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
94202
|
-
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
94203
|
-
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
94204
|
-
result += `${attributeName}="${jsonValue}"`;
|
|
94205
|
-
}
|
|
94206
|
-
}
|
|
94207
|
-
else if (attributeName === 'className') {
|
|
94208
|
-
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
94209
|
-
// This will be restored later in the pipeline
|
|
94210
|
-
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
94211
|
-
}
|
|
94212
|
-
else {
|
|
94213
|
-
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
94214
|
-
}
|
|
94215
|
-
}
|
|
94216
|
-
catch (_error) {
|
|
94217
|
-
result += content.slice(match.index, fullMatchEnd);
|
|
94218
|
-
}
|
|
94219
|
-
lastEnd = fullMatchEnd;
|
|
94220
|
-
attrStartRegex.lastIndex = fullMatchEnd;
|
|
94221
|
-
}
|
|
94222
|
-
match = attrStartRegex.exec(content);
|
|
94223
|
-
}
|
|
94224
|
-
result += content.slice(lastEnd);
|
|
94225
|
-
return result;
|
|
94226
|
-
}
|
|
94227
|
-
/**
|
|
94228
|
-
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
94229
|
-
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
94230
|
-
*
|
|
94231
|
-
* @param content
|
|
94232
|
-
* @param context
|
|
94233
|
-
* @returns Preprocessed content ready for markdown parsing
|
|
94234
|
-
*/
|
|
94235
|
-
function preprocessJSXExpressions(content, context = {}) {
|
|
94236
|
-
// Step 0: Base64 encode HTMLBlock content
|
|
94237
|
-
let processed = protectHTMLBlockContent(content);
|
|
94238
|
-
// Step 1: Protect code blocks and inline code
|
|
94239
|
-
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
94240
|
-
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
94241
|
-
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
94242
|
-
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
94243
|
-
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
94244
|
-
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
94245
|
-
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
94246
|
-
processed = escapeProblematicBraces(processed);
|
|
94247
|
-
// Step 4: Restore protected code blocks
|
|
94248
|
-
processed = restoreCodeBlocks(processed, protectedCode);
|
|
94249
|
-
return processed;
|
|
94250
|
-
}
|
|
94251
|
-
|
|
94252
94272
|
;// ./processor/transform/mdxish/evaluate-expressions.ts
|
|
94253
94273
|
|
|
94254
94274
|
|
|
@@ -97337,11 +97357,66 @@ const transformMagicBlockEmbed = (node) => {
|
|
|
97337
97357
|
position,
|
|
97338
97358
|
};
|
|
97339
97359
|
};
|
|
97360
|
+
const mdxish_jsx_to_mdast_isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
|
|
97361
|
+
/**
|
|
97362
|
+
* Converts a JSX <Table> element to an MDAST table node with alignment.
|
|
97363
|
+
* Returns null for header-less tables since MDAST always promotes the first row to <thead>.
|
|
97364
|
+
*/
|
|
97365
|
+
const transformTable = (jsx) => {
|
|
97366
|
+
let hasThead = false;
|
|
97367
|
+
visit(jsx, isMDXElement, (child) => {
|
|
97368
|
+
if (child.name === 'thead')
|
|
97369
|
+
hasThead = true;
|
|
97370
|
+
});
|
|
97371
|
+
if (!hasThead)
|
|
97372
|
+
return null;
|
|
97373
|
+
const { align: alignAttr } = getAttrs(jsx);
|
|
97374
|
+
const align = Array.isArray(alignAttr) ? alignAttr : null;
|
|
97375
|
+
const rows = [];
|
|
97376
|
+
visit(jsx, isMDXElement, (child) => {
|
|
97377
|
+
if (child.name !== 'thead' && child.name !== 'tbody')
|
|
97378
|
+
return;
|
|
97379
|
+
visit(child, isMDXElement, (row) => {
|
|
97380
|
+
if (row.name !== 'tr')
|
|
97381
|
+
return;
|
|
97382
|
+
const cells = [];
|
|
97383
|
+
visit(row, mdxish_jsx_to_mdast_isTableCell, (cell) => {
|
|
97384
|
+
const parsedChildren = cell.children.flatMap(parsedNode => {
|
|
97385
|
+
if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) {
|
|
97386
|
+
return parsedNode.children;
|
|
97387
|
+
}
|
|
97388
|
+
return [parsedNode];
|
|
97389
|
+
});
|
|
97390
|
+
cells.push({
|
|
97391
|
+
type: 'tableCell',
|
|
97392
|
+
children: parsedChildren,
|
|
97393
|
+
position: cell.position,
|
|
97394
|
+
});
|
|
97395
|
+
});
|
|
97396
|
+
rows.push({
|
|
97397
|
+
type: 'tableRow',
|
|
97398
|
+
children: cells,
|
|
97399
|
+
position: row.position,
|
|
97400
|
+
});
|
|
97401
|
+
});
|
|
97402
|
+
});
|
|
97403
|
+
const columnCount = rows[0]?.children?.length || 0;
|
|
97404
|
+
const alignArray = align && columnCount > 0
|
|
97405
|
+
? align.slice(0, columnCount).concat(Array.from({ length: Math.max(0, columnCount - align.length) }, () => null))
|
|
97406
|
+
: Array.from({ length: columnCount }, () => null);
|
|
97407
|
+
return {
|
|
97408
|
+
type: 'table',
|
|
97409
|
+
align: alignArray,
|
|
97410
|
+
position: jsx.position,
|
|
97411
|
+
children: rows,
|
|
97412
|
+
};
|
|
97413
|
+
};
|
|
97340
97414
|
const COMPONENT_MAP = {
|
|
97341
97415
|
Callout: transformCallout,
|
|
97342
97416
|
Embed: transformEmbed,
|
|
97343
97417
|
Image: transformImage,
|
|
97344
97418
|
Recipe: transformRecipe,
|
|
97419
|
+
Table: transformTable,
|
|
97345
97420
|
};
|
|
97346
97421
|
/**
|
|
97347
97422
|
* Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
|
|
@@ -97364,6 +97439,8 @@ const mdxishJsxToMdast = () => tree => {
|
|
|
97364
97439
|
if (!transformer)
|
|
97365
97440
|
return;
|
|
97366
97441
|
const newNode = transformer(node);
|
|
97442
|
+
if (!newNode)
|
|
97443
|
+
return;
|
|
97367
97444
|
// Replace the JSX node with the MDAST node
|
|
97368
97445
|
parent.children[index] = newNode;
|
|
97369
97446
|
});
|
|
@@ -97644,6 +97721,126 @@ function restoreSnakeCase(placeholderName, mapping) {
|
|
|
97644
97721
|
return matchingKey ? mapping[matchingKey] : placeholderName;
|
|
97645
97722
|
}
|
|
97646
97723
|
|
|
97724
|
+
;// ./processor/transform/mdxish/mdxish-tables-to-jsx.ts
|
|
97725
|
+
|
|
97726
|
+
|
|
97727
|
+
const mdxish_tables_to_jsx_alignToStyle = (align) => {
|
|
97728
|
+
if (!align || align === 'left')
|
|
97729
|
+
return null;
|
|
97730
|
+
return {
|
|
97731
|
+
type: 'mdxJsxAttribute',
|
|
97732
|
+
name: 'style',
|
|
97733
|
+
value: {
|
|
97734
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
97735
|
+
value: `{ textAlign: "${align}" }`,
|
|
97736
|
+
},
|
|
97737
|
+
};
|
|
97738
|
+
};
|
|
97739
|
+
const mdxish_tables_to_jsx_isTableCell = (node) => ['tableHead', 'tableCell'].includes(node.type);
|
|
97740
|
+
const mdxish_tables_to_jsx_isLiteral = (node) => 'value' in node;
|
|
97741
|
+
/**
|
|
97742
|
+
* Mdxish-specific version of `tablesToJsx`. Differs from the shared MDX version:
|
|
97743
|
+
*
|
|
97744
|
+
* - Excludes `html` nodes from triggering JSX conversion because raw HTML
|
|
97745
|
+
* inside JSX `<Table>` breaks remarkMdx parsing on the deserialization roundtrip.
|
|
97746
|
+
* - Skips empty cells instead of aborting the entire visit so that flow content
|
|
97747
|
+
* in later cells is still detected.
|
|
97748
|
+
*/
|
|
97749
|
+
const mdxishTablesToJsx = () => tree => {
|
|
97750
|
+
visit(tree, (node) => ['table', 'tableau'].includes(node.type), (table, index, parent) => {
|
|
97751
|
+
let hasFlowContent = false;
|
|
97752
|
+
visit(table, mdxish_tables_to_jsx_isTableCell, (cell) => {
|
|
97753
|
+
if (cell.children.length === 0)
|
|
97754
|
+
return;
|
|
97755
|
+
const content = cell.children.length === 1 && cell.children[0].type === 'paragraph'
|
|
97756
|
+
? cell.children[0].children[0]
|
|
97757
|
+
: cell.children[0];
|
|
97758
|
+
if (!content)
|
|
97759
|
+
return;
|
|
97760
|
+
visit(cell, 'break', (_, breakIndex, breakParent) => {
|
|
97761
|
+
breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' });
|
|
97762
|
+
});
|
|
97763
|
+
if (!(phrasing(content) || content.type === 'plain') && content.type !== 'escape') {
|
|
97764
|
+
if (content.type === 'html')
|
|
97765
|
+
return;
|
|
97766
|
+
hasFlowContent = true;
|
|
97767
|
+
return EXIT;
|
|
97768
|
+
}
|
|
97769
|
+
visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
|
|
97770
|
+
if (node.value.match(/\n/)) {
|
|
97771
|
+
hasFlowContent = true;
|
|
97772
|
+
return EXIT;
|
|
97773
|
+
}
|
|
97774
|
+
});
|
|
97775
|
+
});
|
|
97776
|
+
if (!hasFlowContent) {
|
|
97777
|
+
table.type = 'table';
|
|
97778
|
+
return;
|
|
97779
|
+
}
|
|
97780
|
+
const styles = table.align.map(mdxish_tables_to_jsx_alignToStyle);
|
|
97781
|
+
const head = {
|
|
97782
|
+
attributes: [],
|
|
97783
|
+
type: 'mdxJsxFlowElement',
|
|
97784
|
+
name: 'thead',
|
|
97785
|
+
children: [
|
|
97786
|
+
{
|
|
97787
|
+
attributes: [],
|
|
97788
|
+
type: 'mdxJsxFlowElement',
|
|
97789
|
+
name: 'tr',
|
|
97790
|
+
children: table.children[0].children.map((cell, cellIndex) => {
|
|
97791
|
+
return {
|
|
97792
|
+
attributes: [],
|
|
97793
|
+
type: 'mdxJsxFlowElement',
|
|
97794
|
+
name: 'th',
|
|
97795
|
+
children: cell.children,
|
|
97796
|
+
...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
|
|
97797
|
+
};
|
|
97798
|
+
}),
|
|
97799
|
+
},
|
|
97800
|
+
],
|
|
97801
|
+
};
|
|
97802
|
+
const body = {
|
|
97803
|
+
attributes: [],
|
|
97804
|
+
type: 'mdxJsxFlowElement',
|
|
97805
|
+
name: 'tbody',
|
|
97806
|
+
children: table.children.splice(1).map(row => {
|
|
97807
|
+
return {
|
|
97808
|
+
attributes: [],
|
|
97809
|
+
type: 'mdxJsxFlowElement',
|
|
97810
|
+
name: 'tr',
|
|
97811
|
+
children: row.children.map((cell, cellIndex) => {
|
|
97812
|
+
return {
|
|
97813
|
+
type: 'mdxJsxFlowElement',
|
|
97814
|
+
name: 'td',
|
|
97815
|
+
children: cell.children,
|
|
97816
|
+
...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
|
|
97817
|
+
};
|
|
97818
|
+
}),
|
|
97819
|
+
};
|
|
97820
|
+
}),
|
|
97821
|
+
};
|
|
97822
|
+
const attributes = [
|
|
97823
|
+
{
|
|
97824
|
+
type: 'mdxJsxAttribute',
|
|
97825
|
+
name: 'align',
|
|
97826
|
+
value: {
|
|
97827
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
97828
|
+
value: JSON.stringify(table.align),
|
|
97829
|
+
},
|
|
97830
|
+
},
|
|
97831
|
+
];
|
|
97832
|
+
const jsx = {
|
|
97833
|
+
type: 'mdxJsxFlowElement',
|
|
97834
|
+
name: 'Table',
|
|
97835
|
+
attributes: table.align.find(a => a) ? attributes : [],
|
|
97836
|
+
children: [head, body],
|
|
97837
|
+
};
|
|
97838
|
+
parent.children[index] = jsx;
|
|
97839
|
+
});
|
|
97840
|
+
return tree;
|
|
97841
|
+
};
|
|
97842
|
+
/* harmony default export */ const mdxish_tables_to_jsx = (mdxishTablesToJsx);
|
|
97843
|
+
|
|
97647
97844
|
;// ./processor/transform/mdxish/normalize-table-separator.ts
|
|
97648
97845
|
/**
|
|
97649
97846
|
* Preprocessor to normalize malformed GFM table separator syntax.
|
|
@@ -99812,6 +100009,8 @@ function loadComponents() {
|
|
|
99812
100009
|
|
|
99813
100010
|
|
|
99814
100011
|
|
|
100012
|
+
|
|
100013
|
+
|
|
99815
100014
|
|
|
99816
100015
|
|
|
99817
100016
|
|
|
@@ -99909,18 +100108,28 @@ function mdxishAstProcessor(mdContent, opts = {}) {
|
|
|
99909
100108
|
};
|
|
99910
100109
|
}
|
|
99911
100110
|
/**
|
|
99912
|
-
*
|
|
100111
|
+
* Registers the mdx-jsx serialization extension so remark-stringify
|
|
100112
|
+
* can convert JSX nodes (e.g. `<Table>`) to markdown.
|
|
100113
|
+
*/
|
|
100114
|
+
function mdxJsxStringify() {
|
|
100115
|
+
const data = this.data();
|
|
100116
|
+
const extensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
|
|
100117
|
+
extensions.push({ extensions: [mdxJsxToMarkdown()] });
|
|
100118
|
+
}
|
|
100119
|
+
/**
|
|
100120
|
+
* Serializes an Mdast back into a markdown string.
|
|
99913
100121
|
*/
|
|
99914
100122
|
function mdxishMdastToMd(mdast) {
|
|
99915
|
-
const
|
|
100123
|
+
const processor = unified()
|
|
99916
100124
|
.use(remarkGfm)
|
|
100125
|
+
.use(mdxish_tables_to_jsx)
|
|
99917
100126
|
.use(mdxishCompilers)
|
|
100127
|
+
.use(mdxJsxStringify)
|
|
99918
100128
|
.use(remarkStringify, {
|
|
99919
100129
|
bullet: '-',
|
|
99920
100130
|
emphasis: '_',
|
|
99921
|
-
})
|
|
99922
|
-
|
|
99923
|
-
return md;
|
|
100131
|
+
});
|
|
100132
|
+
return processor.stringify(processor.runSync(mdast));
|
|
99924
100133
|
}
|
|
99925
100134
|
/**
|
|
99926
100135
|
* Processes markdown content with MDX syntax support and returns a HAST.
|