@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.node.js
CHANGED
|
@@ -24905,10 +24905,19 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
|
|
|
24905
24905
|
records.forEach(record => {
|
|
24906
24906
|
if (record.type === 'childList') {
|
|
24907
24907
|
record.addedNodes.forEach(node => {
|
|
24908
|
-
if (!(node instanceof HTMLElement)
|
|
24908
|
+
if (!(node instanceof HTMLElement))
|
|
24909
24909
|
return;
|
|
24910
|
-
|
|
24911
|
-
|
|
24910
|
+
// Check the added node itself
|
|
24911
|
+
if (node.classList.contains(tailwindPrefix)) {
|
|
24912
|
+
traverse(node, addClasses);
|
|
24913
|
+
shouldUpdate = true;
|
|
24914
|
+
}
|
|
24915
|
+
// Also check descendants — React may insert a parent node
|
|
24916
|
+
// whose children contain TailwindRoot elements
|
|
24917
|
+
node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
|
|
24918
|
+
traverse(child, addClasses);
|
|
24919
|
+
shouldUpdate = true;
|
|
24920
|
+
});
|
|
24912
24921
|
});
|
|
24913
24922
|
}
|
|
24914
24923
|
else if (record.type === 'attributes') {
|
|
@@ -90604,9 +90613,416 @@ const mdast = (text, opts = {}) => {
|
|
|
90604
90613
|
};
|
|
90605
90614
|
/* harmony default export */ const lib_mdast = (mdast);
|
|
90606
90615
|
|
|
90616
|
+
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
90617
|
+
/**
|
|
90618
|
+
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
90619
|
+
*
|
|
90620
|
+
* @param content - The markdown content to process
|
|
90621
|
+
* @returns Object containing protected content and arrays of original code blocks
|
|
90622
|
+
* @example
|
|
90623
|
+
* ```typescript
|
|
90624
|
+
* const input = 'Text with `inline code` and ```fenced block```';
|
|
90625
|
+
* protectCodeBlocks(input)
|
|
90626
|
+
* // Returns: {
|
|
90627
|
+
* // protectedCode: {
|
|
90628
|
+
* // codeBlocks: ['```fenced block```'],
|
|
90629
|
+
* // inlineCode: ['`inline code`']
|
|
90630
|
+
* // },
|
|
90631
|
+
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
90632
|
+
* // }
|
|
90633
|
+
* ```
|
|
90634
|
+
*/
|
|
90635
|
+
function protectCodeBlocks(content) {
|
|
90636
|
+
const codeBlocks = [];
|
|
90637
|
+
const inlineCode = [];
|
|
90638
|
+
let protectedContent = '';
|
|
90639
|
+
let remaining = content;
|
|
90640
|
+
let codeBlockStart = remaining.indexOf('```');
|
|
90641
|
+
while (codeBlockStart !== -1) {
|
|
90642
|
+
protectedContent += remaining.slice(0, codeBlockStart);
|
|
90643
|
+
remaining = remaining.slice(codeBlockStart);
|
|
90644
|
+
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
90645
|
+
if (codeBlockEnd === -1) {
|
|
90646
|
+
break;
|
|
90647
|
+
}
|
|
90648
|
+
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
90649
|
+
const index = codeBlocks.length;
|
|
90650
|
+
codeBlocks.push(match);
|
|
90651
|
+
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
90652
|
+
remaining = remaining.slice(codeBlockEnd + 3);
|
|
90653
|
+
codeBlockStart = remaining.indexOf('```');
|
|
90654
|
+
}
|
|
90655
|
+
protectedContent += remaining;
|
|
90656
|
+
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
90657
|
+
const index = inlineCode.length;
|
|
90658
|
+
inlineCode.push(match);
|
|
90659
|
+
return `___INLINE_CODE_${index}___`;
|
|
90660
|
+
});
|
|
90661
|
+
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
90662
|
+
}
|
|
90663
|
+
/**
|
|
90664
|
+
* Restores inline code by replacing placeholders with original content.
|
|
90665
|
+
*
|
|
90666
|
+
* @param content - Content with inline code placeholders
|
|
90667
|
+
* @param protectedCode - The protected code arrays
|
|
90668
|
+
* @returns Content with inline code restored
|
|
90669
|
+
*/
|
|
90670
|
+
function restoreInlineCode(content, protectedCode) {
|
|
90671
|
+
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
90672
|
+
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
90673
|
+
});
|
|
90674
|
+
}
|
|
90675
|
+
/**
|
|
90676
|
+
* Restores fenced code blocks by replacing placeholders with original content.
|
|
90677
|
+
*
|
|
90678
|
+
* @param content - Content with code block placeholders
|
|
90679
|
+
* @param protectedCode - The protected code arrays
|
|
90680
|
+
* @returns Content with code blocks restored
|
|
90681
|
+
*/
|
|
90682
|
+
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
90683
|
+
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
90684
|
+
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
90685
|
+
});
|
|
90686
|
+
}
|
|
90687
|
+
/**
|
|
90688
|
+
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
90689
|
+
*
|
|
90690
|
+
* @param content - Content with code placeholders
|
|
90691
|
+
* @param protectedCode - The protected code arrays
|
|
90692
|
+
* @returns Content with all code blocks and inline code restored
|
|
90693
|
+
* @example
|
|
90694
|
+
* ```typescript
|
|
90695
|
+
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
90696
|
+
* const protectedCode = {
|
|
90697
|
+
* codeBlocks: ['```js\ncode\n```'],
|
|
90698
|
+
* inlineCode: ['`inline`']
|
|
90699
|
+
* };
|
|
90700
|
+
* restoreCodeBlocks(content, protectedCode)
|
|
90701
|
+
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
90702
|
+
* ```
|
|
90703
|
+
*/
|
|
90704
|
+
function restoreCodeBlocks(content, protectedCode) {
|
|
90705
|
+
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
90706
|
+
restored = restoreInlineCode(restored, protectedCode);
|
|
90707
|
+
return restored;
|
|
90708
|
+
}
|
|
90709
|
+
|
|
90710
|
+
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
90711
|
+
|
|
90712
|
+
// Base64 encode (Node.js + browser compatible)
|
|
90713
|
+
function base64Encode(str) {
|
|
90714
|
+
if (typeof Buffer !== 'undefined') {
|
|
90715
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
90716
|
+
}
|
|
90717
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
90718
|
+
}
|
|
90719
|
+
// Base64 decode (Node.js + browser compatible)
|
|
90720
|
+
function base64Decode(str) {
|
|
90721
|
+
if (typeof Buffer !== 'undefined') {
|
|
90722
|
+
return Buffer.from(str, 'base64').toString('utf-8');
|
|
90723
|
+
}
|
|
90724
|
+
return decodeURIComponent(escape(atob(str)));
|
|
90725
|
+
}
|
|
90726
|
+
function escapeHtmlAttribute(value) {
|
|
90727
|
+
return value
|
|
90728
|
+
.replace(/&/g, '&')
|
|
90729
|
+
.replace(/"/g, '"')
|
|
90730
|
+
.replace(/</g, '<')
|
|
90731
|
+
.replace(/>/g, '>')
|
|
90732
|
+
.replace(/\n/g, ' ');
|
|
90733
|
+
}
|
|
90734
|
+
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
90735
|
+
// Using a prefix that won't conflict with regular string values
|
|
90736
|
+
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
90737
|
+
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
90738
|
+
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
90739
|
+
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
90740
|
+
/**
|
|
90741
|
+
* Evaluates a JavaScript expression using context variables.
|
|
90742
|
+
*
|
|
90743
|
+
* @param expression
|
|
90744
|
+
* @param context
|
|
90745
|
+
* @returns The evaluated result
|
|
90746
|
+
* @example
|
|
90747
|
+
* ```typescript
|
|
90748
|
+
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
90749
|
+
* evaluateExpression('baseUrl + path', context)
|
|
90750
|
+
* // Returns: 'https://example.com/api'
|
|
90751
|
+
* ```
|
|
90752
|
+
*/
|
|
90753
|
+
function evaluateExpression(expression, context) {
|
|
90754
|
+
const contextKeys = Object.keys(context);
|
|
90755
|
+
const contextValues = Object.values(context);
|
|
90756
|
+
// eslint-disable-next-line no-new-func
|
|
90757
|
+
const func = new Function(...contextKeys, `return ${expression}`);
|
|
90758
|
+
return func(...contextValues);
|
|
90759
|
+
}
|
|
90760
|
+
/**
|
|
90761
|
+
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
90762
|
+
*
|
|
90763
|
+
* @param content
|
|
90764
|
+
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
90765
|
+
* @example
|
|
90766
|
+
* ```typescript
|
|
90767
|
+
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
90768
|
+
* protectHTMLBlockContent(input)
|
|
90769
|
+
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
90770
|
+
* ```
|
|
90771
|
+
*/
|
|
90772
|
+
function protectHTMLBlockContent(content) {
|
|
90773
|
+
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
90774
|
+
const encoded = base64Encode(templateContent);
|
|
90775
|
+
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
90776
|
+
});
|
|
90777
|
+
}
|
|
90778
|
+
/**
|
|
90779
|
+
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
90780
|
+
*
|
|
90781
|
+
* @param content
|
|
90782
|
+
* @returns Content with JSX comments removed
|
|
90783
|
+
* @example
|
|
90784
|
+
* ```typescript
|
|
90785
|
+
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
90786
|
+
* // Returns: 'Text more text'
|
|
90787
|
+
* ```
|
|
90788
|
+
*/
|
|
90789
|
+
function removeJSXComments(content) {
|
|
90790
|
+
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
90791
|
+
}
|
|
90792
|
+
/**
|
|
90793
|
+
* Extracts content between balanced braces, handling nested braces.
|
|
90794
|
+
*
|
|
90795
|
+
* @param content
|
|
90796
|
+
* @param start
|
|
90797
|
+
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
90798
|
+
* @example
|
|
90799
|
+
* ```typescript
|
|
90800
|
+
* const input = 'foo{bar{baz}qux}end';
|
|
90801
|
+
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
90802
|
+
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
90803
|
+
* ```
|
|
90804
|
+
*/
|
|
90805
|
+
function extractBalancedBraces(content, start) {
|
|
90806
|
+
let depth = 1;
|
|
90807
|
+
let pos = start;
|
|
90808
|
+
while (pos < content.length && depth > 0) {
|
|
90809
|
+
const char = content[pos];
|
|
90810
|
+
if (char === '{')
|
|
90811
|
+
depth += 1;
|
|
90812
|
+
else if (char === '}')
|
|
90813
|
+
depth -= 1;
|
|
90814
|
+
pos += 1;
|
|
90815
|
+
}
|
|
90816
|
+
if (depth !== 0)
|
|
90817
|
+
return null;
|
|
90818
|
+
return { content: content.slice(start, pos - 1), end: pos };
|
|
90819
|
+
}
|
|
90820
|
+
/**
|
|
90821
|
+
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
90822
|
+
* Handles three cases:
|
|
90823
|
+
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
90824
|
+
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
90825
|
+
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
90826
|
+
*
|
|
90827
|
+
*/
|
|
90828
|
+
function escapeProblematicBraces(content) {
|
|
90829
|
+
// Skip HTML elements — their content should never be escaped because
|
|
90830
|
+
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
90831
|
+
const htmlElements = [];
|
|
90832
|
+
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
90833
|
+
const idx = htmlElements.length;
|
|
90834
|
+
htmlElements.push(match);
|
|
90835
|
+
return `___HTML_ELEM_${idx}___`;
|
|
90836
|
+
});
|
|
90837
|
+
const toEscape = new Set();
|
|
90838
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
90839
|
+
const chars = Array.from(safe);
|
|
90840
|
+
let strDelim = null;
|
|
90841
|
+
let strEscaped = false;
|
|
90842
|
+
// Stack of open braces with their state
|
|
90843
|
+
const openStack = [];
|
|
90844
|
+
// Track position of last newline (outside strings) to detect blank lines
|
|
90845
|
+
let lastNewlinePos = -2; // -2 means no recent newline
|
|
90846
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
90847
|
+
const ch = chars[i];
|
|
90848
|
+
// Track string delimiters inside expressions to ignore braces within them
|
|
90849
|
+
if (openStack.length > 0) {
|
|
90850
|
+
if (strDelim) {
|
|
90851
|
+
if (strEscaped)
|
|
90852
|
+
strEscaped = false;
|
|
90853
|
+
else if (ch === '\\')
|
|
90854
|
+
strEscaped = true;
|
|
90855
|
+
else if (ch === strDelim)
|
|
90856
|
+
strDelim = null;
|
|
90857
|
+
// eslint-disable-next-line no-continue
|
|
90858
|
+
continue;
|
|
90859
|
+
}
|
|
90860
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
90861
|
+
strDelim = ch;
|
|
90862
|
+
// eslint-disable-next-line no-continue
|
|
90863
|
+
continue;
|
|
90864
|
+
}
|
|
90865
|
+
// Track newlines to detect blank lines (paragraph boundaries)
|
|
90866
|
+
if (ch === '\n') {
|
|
90867
|
+
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
90868
|
+
if (lastNewlinePos >= 0) {
|
|
90869
|
+
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
90870
|
+
if (/^[ \t]*$/.test(between)) {
|
|
90871
|
+
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
90872
|
+
openStack.forEach(entry => {
|
|
90873
|
+
entry.hasBlankLine = true;
|
|
90874
|
+
});
|
|
90875
|
+
}
|
|
90876
|
+
}
|
|
90877
|
+
lastNewlinePos = i;
|
|
90878
|
+
}
|
|
90879
|
+
}
|
|
90880
|
+
// Skip already-escaped braces (count preceding backslashes)
|
|
90881
|
+
if (ch === '{' || ch === '}') {
|
|
90882
|
+
let bs = 0;
|
|
90883
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
90884
|
+
bs += 1;
|
|
90885
|
+
if (bs % 2 === 1) {
|
|
90886
|
+
// eslint-disable-next-line no-continue
|
|
90887
|
+
continue;
|
|
90888
|
+
}
|
|
90889
|
+
}
|
|
90890
|
+
if (ch === '{') {
|
|
90891
|
+
openStack.push({ pos: i, hasBlankLine: false });
|
|
90892
|
+
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
90893
|
+
}
|
|
90894
|
+
else if (ch === '}') {
|
|
90895
|
+
if (openStack.length > 0) {
|
|
90896
|
+
const entry = openStack.pop();
|
|
90897
|
+
// If expression spans paragraph boundary, escape both braces
|
|
90898
|
+
if (entry.hasBlankLine) {
|
|
90899
|
+
toEscape.add(entry.pos);
|
|
90900
|
+
toEscape.add(i);
|
|
90901
|
+
}
|
|
90902
|
+
}
|
|
90903
|
+
else {
|
|
90904
|
+
// Unbalanced closing brace (no matching open)
|
|
90905
|
+
toEscape.add(i);
|
|
90906
|
+
}
|
|
90907
|
+
}
|
|
90908
|
+
}
|
|
90909
|
+
// Any remaining open braces are unbalanced
|
|
90910
|
+
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
90911
|
+
// If there are no problematic braces, return safe content as-is;
|
|
90912
|
+
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
90913
|
+
let result = toEscape.size === 0
|
|
90914
|
+
? safe
|
|
90915
|
+
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
90916
|
+
// Restore HTML elements
|
|
90917
|
+
if (htmlElements.length > 0) {
|
|
90918
|
+
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
90919
|
+
}
|
|
90920
|
+
return result;
|
|
90921
|
+
}
|
|
90922
|
+
/**
|
|
90923
|
+
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
90924
|
+
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
90925
|
+
*
|
|
90926
|
+
* @param content
|
|
90927
|
+
* @param context
|
|
90928
|
+
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
90929
|
+
* @example
|
|
90930
|
+
* ```typescript
|
|
90931
|
+
* const context = { baseUrl: 'https://example.com' };
|
|
90932
|
+
* const input = '<a href={baseUrl}>Link</a>';
|
|
90933
|
+
* evaluateAttributeExpressions(input, context)
|
|
90934
|
+
* // Returns: '<a href="https://example.com">Link</a>'
|
|
90935
|
+
* ```
|
|
90936
|
+
*/
|
|
90937
|
+
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
90938
|
+
const attrStartRegex = /(\w+)=\{/g;
|
|
90939
|
+
let result = '';
|
|
90940
|
+
let lastEnd = 0;
|
|
90941
|
+
let match = attrStartRegex.exec(content);
|
|
90942
|
+
while (match !== null) {
|
|
90943
|
+
const attributeName = match[1];
|
|
90944
|
+
const braceStart = match.index + match[0].length;
|
|
90945
|
+
const extracted = extractBalancedBraces(content, braceStart);
|
|
90946
|
+
if (extracted) {
|
|
90947
|
+
// The expression might contain template literals in MDX component tag props
|
|
90948
|
+
// E.g. <Component greeting={`Hello World!`} />
|
|
90949
|
+
// that is marked as inline code. So we need to restore the inline codes
|
|
90950
|
+
// in the expression to evaluate it
|
|
90951
|
+
let expression = extracted.content;
|
|
90952
|
+
if (protectedCode) {
|
|
90953
|
+
expression = restoreInlineCode(expression, protectedCode);
|
|
90954
|
+
}
|
|
90955
|
+
const fullMatchEnd = extracted.end;
|
|
90956
|
+
result += content.slice(lastEnd, match.index);
|
|
90957
|
+
try {
|
|
90958
|
+
const evalResult = evaluateExpression(expression, context);
|
|
90959
|
+
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
90960
|
+
if (attributeName === 'style') {
|
|
90961
|
+
const cssString = Object.entries(evalResult)
|
|
90962
|
+
.map(([key, value]) => {
|
|
90963
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
90964
|
+
return `${cssKey}: ${value}`;
|
|
90965
|
+
})
|
|
90966
|
+
.join('; ');
|
|
90967
|
+
result += `style="${cssString}"`;
|
|
90968
|
+
}
|
|
90969
|
+
else {
|
|
90970
|
+
// These are arrays / objects attribute values
|
|
90971
|
+
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
90972
|
+
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
90973
|
+
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
90974
|
+
result += `${attributeName}="${jsonValue}"`;
|
|
90975
|
+
}
|
|
90976
|
+
}
|
|
90977
|
+
else if (attributeName === 'className') {
|
|
90978
|
+
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
90979
|
+
// This will be restored later in the pipeline
|
|
90980
|
+
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
90981
|
+
}
|
|
90982
|
+
else {
|
|
90983
|
+
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
90984
|
+
}
|
|
90985
|
+
}
|
|
90986
|
+
catch (_error) {
|
|
90987
|
+
result += content.slice(match.index, fullMatchEnd);
|
|
90988
|
+
}
|
|
90989
|
+
lastEnd = fullMatchEnd;
|
|
90990
|
+
attrStartRegex.lastIndex = fullMatchEnd;
|
|
90991
|
+
}
|
|
90992
|
+
match = attrStartRegex.exec(content);
|
|
90993
|
+
}
|
|
90994
|
+
result += content.slice(lastEnd);
|
|
90995
|
+
return result;
|
|
90996
|
+
}
|
|
90997
|
+
/**
|
|
90998
|
+
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
90999
|
+
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
91000
|
+
*
|
|
91001
|
+
* @param content
|
|
91002
|
+
* @param context
|
|
91003
|
+
* @returns Preprocessed content ready for markdown parsing
|
|
91004
|
+
*/
|
|
91005
|
+
function preprocessJSXExpressions(content, context = {}) {
|
|
91006
|
+
// Step 0: Base64 encode HTMLBlock content
|
|
91007
|
+
let processed = protectHTMLBlockContent(content);
|
|
91008
|
+
// Step 1: Protect code blocks and inline code
|
|
91009
|
+
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
91010
|
+
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
91011
|
+
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
91012
|
+
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
91013
|
+
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
91014
|
+
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
91015
|
+
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
91016
|
+
processed = escapeProblematicBraces(processed);
|
|
91017
|
+
// Step 4: Restore protected code blocks
|
|
91018
|
+
processed = restoreCodeBlocks(processed, protectedCode);
|
|
91019
|
+
return processed;
|
|
91020
|
+
}
|
|
91021
|
+
|
|
90607
91022
|
;// ./processor/utils.ts
|
|
90608
91023
|
|
|
90609
91024
|
|
|
91025
|
+
|
|
90610
91026
|
/**
|
|
90611
91027
|
* Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
|
|
90612
91028
|
* This currently sets all the values to a string since we process/compile the MDX on the fly
|
|
@@ -90662,7 +91078,17 @@ const getHPropKeys = (node) => {
|
|
|
90662
91078
|
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
90663
91079
|
if ('name' in attr) {
|
|
90664
91080
|
if (typeof attr.value === 'string') {
|
|
90665
|
-
|
|
91081
|
+
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
91082
|
+
try {
|
|
91083
|
+
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
91084
|
+
}
|
|
91085
|
+
catch {
|
|
91086
|
+
memo[attr.name] = attr.value;
|
|
91087
|
+
}
|
|
91088
|
+
}
|
|
91089
|
+
else {
|
|
91090
|
+
memo[attr.name] = attr.value;
|
|
91091
|
+
}
|
|
90666
91092
|
}
|
|
90667
91093
|
else if (attr.value === null) {
|
|
90668
91094
|
memo[attr.name] = true;
|
|
@@ -91436,10 +91862,10 @@ const hasFlowContent = (nodes) => {
|
|
|
91436
91862
|
* Process a Table node: re-parse text-only cell content, then output as
|
|
91437
91863
|
* a markdown table (phrasing-only) or keep as JSX <Table> (has flow content).
|
|
91438
91864
|
*/
|
|
91439
|
-
const processTableNode = (node, index, parent) => {
|
|
91865
|
+
const processTableNode = (node, index, parent, documentPosition) => {
|
|
91440
91866
|
if (node.name !== 'Table')
|
|
91441
91867
|
return;
|
|
91442
|
-
const
|
|
91868
|
+
const position = documentPosition ?? node.position;
|
|
91443
91869
|
const { align: alignAttr } = getAttrs(node);
|
|
91444
91870
|
const align = Array.isArray(alignAttr) ? alignAttr : null;
|
|
91445
91871
|
let tableHasFlowContent = false;
|
|
@@ -91562,7 +91988,7 @@ const mdxishTables = () => tree => {
|
|
|
91562
91988
|
const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
|
|
91563
91989
|
visit(parsed, isMDXElement, (tableNode) => {
|
|
91564
91990
|
if (tableNode.name === 'Table') {
|
|
91565
|
-
processTableNode(tableNode, index, parent);
|
|
91991
|
+
processTableNode(tableNode, index, parent, node.position);
|
|
91566
91992
|
// Stop after the outermost Table so nested Tables don't overwrite parent.children[index]
|
|
91567
91993
|
// we let it get handled naturally
|
|
91568
91994
|
return EXIT;
|
|
@@ -114037,412 +114463,6 @@ const mdxComponentHandlers = {
|
|
|
114037
114463
|
[NodeTypes.htmlBlock]: htmlBlockHandler,
|
|
114038
114464
|
};
|
|
114039
114465
|
|
|
114040
|
-
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
114041
|
-
/**
|
|
114042
|
-
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
114043
|
-
*
|
|
114044
|
-
* @param content - The markdown content to process
|
|
114045
|
-
* @returns Object containing protected content and arrays of original code blocks
|
|
114046
|
-
* @example
|
|
114047
|
-
* ```typescript
|
|
114048
|
-
* const input = 'Text with `inline code` and ```fenced block```';
|
|
114049
|
-
* protectCodeBlocks(input)
|
|
114050
|
-
* // Returns: {
|
|
114051
|
-
* // protectedCode: {
|
|
114052
|
-
* // codeBlocks: ['```fenced block```'],
|
|
114053
|
-
* // inlineCode: ['`inline code`']
|
|
114054
|
-
* // },
|
|
114055
|
-
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
114056
|
-
* // }
|
|
114057
|
-
* ```
|
|
114058
|
-
*/
|
|
114059
|
-
function protectCodeBlocks(content) {
|
|
114060
|
-
const codeBlocks = [];
|
|
114061
|
-
const inlineCode = [];
|
|
114062
|
-
let protectedContent = '';
|
|
114063
|
-
let remaining = content;
|
|
114064
|
-
let codeBlockStart = remaining.indexOf('```');
|
|
114065
|
-
while (codeBlockStart !== -1) {
|
|
114066
|
-
protectedContent += remaining.slice(0, codeBlockStart);
|
|
114067
|
-
remaining = remaining.slice(codeBlockStart);
|
|
114068
|
-
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
114069
|
-
if (codeBlockEnd === -1) {
|
|
114070
|
-
break;
|
|
114071
|
-
}
|
|
114072
|
-
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
114073
|
-
const index = codeBlocks.length;
|
|
114074
|
-
codeBlocks.push(match);
|
|
114075
|
-
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
114076
|
-
remaining = remaining.slice(codeBlockEnd + 3);
|
|
114077
|
-
codeBlockStart = remaining.indexOf('```');
|
|
114078
|
-
}
|
|
114079
|
-
protectedContent += remaining;
|
|
114080
|
-
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
114081
|
-
const index = inlineCode.length;
|
|
114082
|
-
inlineCode.push(match);
|
|
114083
|
-
return `___INLINE_CODE_${index}___`;
|
|
114084
|
-
});
|
|
114085
|
-
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
114086
|
-
}
|
|
114087
|
-
/**
|
|
114088
|
-
* Restores inline code by replacing placeholders with original content.
|
|
114089
|
-
*
|
|
114090
|
-
* @param content - Content with inline code placeholders
|
|
114091
|
-
* @param protectedCode - The protected code arrays
|
|
114092
|
-
* @returns Content with inline code restored
|
|
114093
|
-
*/
|
|
114094
|
-
function restoreInlineCode(content, protectedCode) {
|
|
114095
|
-
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
114096
|
-
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
114097
|
-
});
|
|
114098
|
-
}
|
|
114099
|
-
/**
|
|
114100
|
-
* Restores fenced code blocks by replacing placeholders with original content.
|
|
114101
|
-
*
|
|
114102
|
-
* @param content - Content with code block placeholders
|
|
114103
|
-
* @param protectedCode - The protected code arrays
|
|
114104
|
-
* @returns Content with code blocks restored
|
|
114105
|
-
*/
|
|
114106
|
-
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
114107
|
-
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
114108
|
-
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
114109
|
-
});
|
|
114110
|
-
}
|
|
114111
|
-
/**
|
|
114112
|
-
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
114113
|
-
*
|
|
114114
|
-
* @param content - Content with code placeholders
|
|
114115
|
-
* @param protectedCode - The protected code arrays
|
|
114116
|
-
* @returns Content with all code blocks and inline code restored
|
|
114117
|
-
* @example
|
|
114118
|
-
* ```typescript
|
|
114119
|
-
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
114120
|
-
* const protectedCode = {
|
|
114121
|
-
* codeBlocks: ['```js\ncode\n```'],
|
|
114122
|
-
* inlineCode: ['`inline`']
|
|
114123
|
-
* };
|
|
114124
|
-
* restoreCodeBlocks(content, protectedCode)
|
|
114125
|
-
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
114126
|
-
* ```
|
|
114127
|
-
*/
|
|
114128
|
-
function restoreCodeBlocks(content, protectedCode) {
|
|
114129
|
-
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
114130
|
-
restored = restoreInlineCode(restored, protectedCode);
|
|
114131
|
-
return restored;
|
|
114132
|
-
}
|
|
114133
|
-
|
|
114134
|
-
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
114135
|
-
|
|
114136
|
-
// Base64 encode (Node.js + browser compatible)
|
|
114137
|
-
function base64Encode(str) {
|
|
114138
|
-
if (typeof Buffer !== 'undefined') {
|
|
114139
|
-
return Buffer.from(str, 'utf-8').toString('base64');
|
|
114140
|
-
}
|
|
114141
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
114142
|
-
}
|
|
114143
|
-
// Base64 decode (Node.js + browser compatible)
|
|
114144
|
-
function base64Decode(str) {
|
|
114145
|
-
if (typeof Buffer !== 'undefined') {
|
|
114146
|
-
return Buffer.from(str, 'base64').toString('utf-8');
|
|
114147
|
-
}
|
|
114148
|
-
return decodeURIComponent(escape(atob(str)));
|
|
114149
|
-
}
|
|
114150
|
-
function escapeHtmlAttribute(value) {
|
|
114151
|
-
return value
|
|
114152
|
-
.replace(/&/g, '&')
|
|
114153
|
-
.replace(/"/g, '"')
|
|
114154
|
-
.replace(/</g, '<')
|
|
114155
|
-
.replace(/>/g, '>')
|
|
114156
|
-
.replace(/\n/g, ' ');
|
|
114157
|
-
}
|
|
114158
|
-
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
114159
|
-
// Using a prefix that won't conflict with regular string values
|
|
114160
|
-
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
114161
|
-
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
114162
|
-
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
114163
|
-
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
114164
|
-
/**
|
|
114165
|
-
* Evaluates a JavaScript expression using context variables.
|
|
114166
|
-
*
|
|
114167
|
-
* @param expression
|
|
114168
|
-
* @param context
|
|
114169
|
-
* @returns The evaluated result
|
|
114170
|
-
* @example
|
|
114171
|
-
* ```typescript
|
|
114172
|
-
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
114173
|
-
* evaluateExpression('baseUrl + path', context)
|
|
114174
|
-
* // Returns: 'https://example.com/api'
|
|
114175
|
-
* ```
|
|
114176
|
-
*/
|
|
114177
|
-
function evaluateExpression(expression, context) {
|
|
114178
|
-
const contextKeys = Object.keys(context);
|
|
114179
|
-
const contextValues = Object.values(context);
|
|
114180
|
-
// eslint-disable-next-line no-new-func
|
|
114181
|
-
const func = new Function(...contextKeys, `return ${expression}`);
|
|
114182
|
-
return func(...contextValues);
|
|
114183
|
-
}
|
|
114184
|
-
/**
|
|
114185
|
-
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
114186
|
-
*
|
|
114187
|
-
* @param content
|
|
114188
|
-
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
114189
|
-
* @example
|
|
114190
|
-
* ```typescript
|
|
114191
|
-
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
114192
|
-
* protectHTMLBlockContent(input)
|
|
114193
|
-
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
114194
|
-
* ```
|
|
114195
|
-
*/
|
|
114196
|
-
function protectHTMLBlockContent(content) {
|
|
114197
|
-
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
114198
|
-
const encoded = base64Encode(templateContent);
|
|
114199
|
-
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
114200
|
-
});
|
|
114201
|
-
}
|
|
114202
|
-
/**
|
|
114203
|
-
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
114204
|
-
*
|
|
114205
|
-
* @param content
|
|
114206
|
-
* @returns Content with JSX comments removed
|
|
114207
|
-
* @example
|
|
114208
|
-
* ```typescript
|
|
114209
|
-
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
114210
|
-
* // Returns: 'Text more text'
|
|
114211
|
-
* ```
|
|
114212
|
-
*/
|
|
114213
|
-
function removeJSXComments(content) {
|
|
114214
|
-
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
114215
|
-
}
|
|
114216
|
-
/**
|
|
114217
|
-
* Extracts content between balanced braces, handling nested braces.
|
|
114218
|
-
*
|
|
114219
|
-
* @param content
|
|
114220
|
-
* @param start
|
|
114221
|
-
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
114222
|
-
* @example
|
|
114223
|
-
* ```typescript
|
|
114224
|
-
* const input = 'foo{bar{baz}qux}end';
|
|
114225
|
-
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
114226
|
-
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
114227
|
-
* ```
|
|
114228
|
-
*/
|
|
114229
|
-
function extractBalancedBraces(content, start) {
|
|
114230
|
-
let depth = 1;
|
|
114231
|
-
let pos = start;
|
|
114232
|
-
while (pos < content.length && depth > 0) {
|
|
114233
|
-
const char = content[pos];
|
|
114234
|
-
if (char === '{')
|
|
114235
|
-
depth += 1;
|
|
114236
|
-
else if (char === '}')
|
|
114237
|
-
depth -= 1;
|
|
114238
|
-
pos += 1;
|
|
114239
|
-
}
|
|
114240
|
-
if (depth !== 0)
|
|
114241
|
-
return null;
|
|
114242
|
-
return { content: content.slice(start, pos - 1), end: pos };
|
|
114243
|
-
}
|
|
114244
|
-
/**
|
|
114245
|
-
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
114246
|
-
* Handles three cases:
|
|
114247
|
-
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
114248
|
-
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
114249
|
-
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
114250
|
-
*
|
|
114251
|
-
*/
|
|
114252
|
-
function escapeProblematicBraces(content) {
|
|
114253
|
-
// Skip HTML elements — their content should never be escaped because
|
|
114254
|
-
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
114255
|
-
const htmlElements = [];
|
|
114256
|
-
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
114257
|
-
const idx = htmlElements.length;
|
|
114258
|
-
htmlElements.push(match);
|
|
114259
|
-
return `___HTML_ELEM_${idx}___`;
|
|
114260
|
-
});
|
|
114261
|
-
const toEscape = new Set();
|
|
114262
|
-
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
114263
|
-
const chars = Array.from(safe);
|
|
114264
|
-
let strDelim = null;
|
|
114265
|
-
let strEscaped = false;
|
|
114266
|
-
// Stack of open braces with their state
|
|
114267
|
-
const openStack = [];
|
|
114268
|
-
// Track position of last newline (outside strings) to detect blank lines
|
|
114269
|
-
let lastNewlinePos = -2; // -2 means no recent newline
|
|
114270
|
-
for (let i = 0; i < chars.length; i += 1) {
|
|
114271
|
-
const ch = chars[i];
|
|
114272
|
-
// Track string delimiters inside expressions to ignore braces within them
|
|
114273
|
-
if (openStack.length > 0) {
|
|
114274
|
-
if (strDelim) {
|
|
114275
|
-
if (strEscaped)
|
|
114276
|
-
strEscaped = false;
|
|
114277
|
-
else if (ch === '\\')
|
|
114278
|
-
strEscaped = true;
|
|
114279
|
-
else if (ch === strDelim)
|
|
114280
|
-
strDelim = null;
|
|
114281
|
-
// eslint-disable-next-line no-continue
|
|
114282
|
-
continue;
|
|
114283
|
-
}
|
|
114284
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
114285
|
-
strDelim = ch;
|
|
114286
|
-
// eslint-disable-next-line no-continue
|
|
114287
|
-
continue;
|
|
114288
|
-
}
|
|
114289
|
-
// Track newlines to detect blank lines (paragraph boundaries)
|
|
114290
|
-
if (ch === '\n') {
|
|
114291
|
-
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
114292
|
-
if (lastNewlinePos >= 0) {
|
|
114293
|
-
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
114294
|
-
if (/^[ \t]*$/.test(between)) {
|
|
114295
|
-
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
114296
|
-
openStack.forEach(entry => {
|
|
114297
|
-
entry.hasBlankLine = true;
|
|
114298
|
-
});
|
|
114299
|
-
}
|
|
114300
|
-
}
|
|
114301
|
-
lastNewlinePos = i;
|
|
114302
|
-
}
|
|
114303
|
-
}
|
|
114304
|
-
// Skip already-escaped braces (count preceding backslashes)
|
|
114305
|
-
if (ch === '{' || ch === '}') {
|
|
114306
|
-
let bs = 0;
|
|
114307
|
-
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
114308
|
-
bs += 1;
|
|
114309
|
-
if (bs % 2 === 1) {
|
|
114310
|
-
// eslint-disable-next-line no-continue
|
|
114311
|
-
continue;
|
|
114312
|
-
}
|
|
114313
|
-
}
|
|
114314
|
-
if (ch === '{') {
|
|
114315
|
-
openStack.push({ pos: i, hasBlankLine: false });
|
|
114316
|
-
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
114317
|
-
}
|
|
114318
|
-
else if (ch === '}') {
|
|
114319
|
-
if (openStack.length > 0) {
|
|
114320
|
-
const entry = openStack.pop();
|
|
114321
|
-
// If expression spans paragraph boundary, escape both braces
|
|
114322
|
-
if (entry.hasBlankLine) {
|
|
114323
|
-
toEscape.add(entry.pos);
|
|
114324
|
-
toEscape.add(i);
|
|
114325
|
-
}
|
|
114326
|
-
}
|
|
114327
|
-
else {
|
|
114328
|
-
// Unbalanced closing brace (no matching open)
|
|
114329
|
-
toEscape.add(i);
|
|
114330
|
-
}
|
|
114331
|
-
}
|
|
114332
|
-
}
|
|
114333
|
-
// Any remaining open braces are unbalanced
|
|
114334
|
-
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
114335
|
-
// If there are no problematic braces, return safe content as-is;
|
|
114336
|
-
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
114337
|
-
let result = toEscape.size === 0
|
|
114338
|
-
? safe
|
|
114339
|
-
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
114340
|
-
// Restore HTML elements
|
|
114341
|
-
if (htmlElements.length > 0) {
|
|
114342
|
-
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
114343
|
-
}
|
|
114344
|
-
return result;
|
|
114345
|
-
}
|
|
114346
|
-
/**
|
|
114347
|
-
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
114348
|
-
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
114349
|
-
*
|
|
114350
|
-
* @param content
|
|
114351
|
-
* @param context
|
|
114352
|
-
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
114353
|
-
* @example
|
|
114354
|
-
* ```typescript
|
|
114355
|
-
* const context = { baseUrl: 'https://example.com' };
|
|
114356
|
-
* const input = '<a href={baseUrl}>Link</a>';
|
|
114357
|
-
* evaluateAttributeExpressions(input, context)
|
|
114358
|
-
* // Returns: '<a href="https://example.com">Link</a>'
|
|
114359
|
-
* ```
|
|
114360
|
-
*/
|
|
114361
|
-
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
114362
|
-
const attrStartRegex = /(\w+)=\{/g;
|
|
114363
|
-
let result = '';
|
|
114364
|
-
let lastEnd = 0;
|
|
114365
|
-
let match = attrStartRegex.exec(content);
|
|
114366
|
-
while (match !== null) {
|
|
114367
|
-
const attributeName = match[1];
|
|
114368
|
-
const braceStart = match.index + match[0].length;
|
|
114369
|
-
const extracted = extractBalancedBraces(content, braceStart);
|
|
114370
|
-
if (extracted) {
|
|
114371
|
-
// The expression might contain template literals in MDX component tag props
|
|
114372
|
-
// E.g. <Component greeting={`Hello World!`} />
|
|
114373
|
-
// that is marked as inline code. So we need to restore the inline codes
|
|
114374
|
-
// in the expression to evaluate it
|
|
114375
|
-
let expression = extracted.content;
|
|
114376
|
-
if (protectedCode) {
|
|
114377
|
-
expression = restoreInlineCode(expression, protectedCode);
|
|
114378
|
-
}
|
|
114379
|
-
const fullMatchEnd = extracted.end;
|
|
114380
|
-
result += content.slice(lastEnd, match.index);
|
|
114381
|
-
try {
|
|
114382
|
-
const evalResult = evaluateExpression(expression, context);
|
|
114383
|
-
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
114384
|
-
if (attributeName === 'style') {
|
|
114385
|
-
const cssString = Object.entries(evalResult)
|
|
114386
|
-
.map(([key, value]) => {
|
|
114387
|
-
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
114388
|
-
return `${cssKey}: ${value}`;
|
|
114389
|
-
})
|
|
114390
|
-
.join('; ');
|
|
114391
|
-
result += `style="${cssString}"`;
|
|
114392
|
-
}
|
|
114393
|
-
else {
|
|
114394
|
-
// These are arrays / objects attribute values
|
|
114395
|
-
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
114396
|
-
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
114397
|
-
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
114398
|
-
result += `${attributeName}="${jsonValue}"`;
|
|
114399
|
-
}
|
|
114400
|
-
}
|
|
114401
|
-
else if (attributeName === 'className') {
|
|
114402
|
-
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
114403
|
-
// This will be restored later in the pipeline
|
|
114404
|
-
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
114405
|
-
}
|
|
114406
|
-
else {
|
|
114407
|
-
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
114408
|
-
}
|
|
114409
|
-
}
|
|
114410
|
-
catch (_error) {
|
|
114411
|
-
result += content.slice(match.index, fullMatchEnd);
|
|
114412
|
-
}
|
|
114413
|
-
lastEnd = fullMatchEnd;
|
|
114414
|
-
attrStartRegex.lastIndex = fullMatchEnd;
|
|
114415
|
-
}
|
|
114416
|
-
match = attrStartRegex.exec(content);
|
|
114417
|
-
}
|
|
114418
|
-
result += content.slice(lastEnd);
|
|
114419
|
-
return result;
|
|
114420
|
-
}
|
|
114421
|
-
/**
|
|
114422
|
-
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
114423
|
-
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
114424
|
-
*
|
|
114425
|
-
* @param content
|
|
114426
|
-
* @param context
|
|
114427
|
-
* @returns Preprocessed content ready for markdown parsing
|
|
114428
|
-
*/
|
|
114429
|
-
function preprocessJSXExpressions(content, context = {}) {
|
|
114430
|
-
// Step 0: Base64 encode HTMLBlock content
|
|
114431
|
-
let processed = protectHTMLBlockContent(content);
|
|
114432
|
-
// Step 1: Protect code blocks and inline code
|
|
114433
|
-
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
114434
|
-
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
114435
|
-
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
114436
|
-
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
114437
|
-
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
114438
|
-
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
114439
|
-
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
114440
|
-
processed = escapeProblematicBraces(processed);
|
|
114441
|
-
// Step 4: Restore protected code blocks
|
|
114442
|
-
processed = restoreCodeBlocks(processed, protectedCode);
|
|
114443
|
-
return processed;
|
|
114444
|
-
}
|
|
114445
|
-
|
|
114446
114466
|
;// ./processor/transform/mdxish/evaluate-expressions.ts
|
|
114447
114467
|
|
|
114448
114468
|
|
|
@@ -117531,11 +117551,66 @@ const transformMagicBlockEmbed = (node) => {
|
|
|
117531
117551
|
position,
|
|
117532
117552
|
};
|
|
117533
117553
|
};
|
|
117554
|
+
const mdxish_jsx_to_mdast_isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
|
|
117555
|
+
/**
|
|
117556
|
+
* Converts a JSX <Table> element to an MDAST table node with alignment.
|
|
117557
|
+
* Returns null for header-less tables since MDAST always promotes the first row to <thead>.
|
|
117558
|
+
*/
|
|
117559
|
+
const transformTable = (jsx) => {
|
|
117560
|
+
let hasThead = false;
|
|
117561
|
+
visit(jsx, isMDXElement, (child) => {
|
|
117562
|
+
if (child.name === 'thead')
|
|
117563
|
+
hasThead = true;
|
|
117564
|
+
});
|
|
117565
|
+
if (!hasThead)
|
|
117566
|
+
return null;
|
|
117567
|
+
const { align: alignAttr } = getAttrs(jsx);
|
|
117568
|
+
const align = Array.isArray(alignAttr) ? alignAttr : null;
|
|
117569
|
+
const rows = [];
|
|
117570
|
+
visit(jsx, isMDXElement, (child) => {
|
|
117571
|
+
if (child.name !== 'thead' && child.name !== 'tbody')
|
|
117572
|
+
return;
|
|
117573
|
+
visit(child, isMDXElement, (row) => {
|
|
117574
|
+
if (row.name !== 'tr')
|
|
117575
|
+
return;
|
|
117576
|
+
const cells = [];
|
|
117577
|
+
visit(row, mdxish_jsx_to_mdast_isTableCell, (cell) => {
|
|
117578
|
+
const parsedChildren = cell.children.flatMap(parsedNode => {
|
|
117579
|
+
if (parsedNode.type === 'paragraph' && 'children' in parsedNode && parsedNode.children) {
|
|
117580
|
+
return parsedNode.children;
|
|
117581
|
+
}
|
|
117582
|
+
return [parsedNode];
|
|
117583
|
+
});
|
|
117584
|
+
cells.push({
|
|
117585
|
+
type: 'tableCell',
|
|
117586
|
+
children: parsedChildren,
|
|
117587
|
+
position: cell.position,
|
|
117588
|
+
});
|
|
117589
|
+
});
|
|
117590
|
+
rows.push({
|
|
117591
|
+
type: 'tableRow',
|
|
117592
|
+
children: cells,
|
|
117593
|
+
position: row.position,
|
|
117594
|
+
});
|
|
117595
|
+
});
|
|
117596
|
+
});
|
|
117597
|
+
const columnCount = rows[0]?.children?.length || 0;
|
|
117598
|
+
const alignArray = align && columnCount > 0
|
|
117599
|
+
? align.slice(0, columnCount).concat(Array.from({ length: Math.max(0, columnCount - align.length) }, () => null))
|
|
117600
|
+
: Array.from({ length: columnCount }, () => null);
|
|
117601
|
+
return {
|
|
117602
|
+
type: 'table',
|
|
117603
|
+
align: alignArray,
|
|
117604
|
+
position: jsx.position,
|
|
117605
|
+
children: rows,
|
|
117606
|
+
};
|
|
117607
|
+
};
|
|
117534
117608
|
const COMPONENT_MAP = {
|
|
117535
117609
|
Callout: transformCallout,
|
|
117536
117610
|
Embed: transformEmbed,
|
|
117537
117611
|
Image: transformImage,
|
|
117538
117612
|
Recipe: transformRecipe,
|
|
117613
|
+
Table: transformTable,
|
|
117539
117614
|
};
|
|
117540
117615
|
/**
|
|
117541
117616
|
* Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
|
|
@@ -117558,6 +117633,8 @@ const mdxishJsxToMdast = () => tree => {
|
|
|
117558
117633
|
if (!transformer)
|
|
117559
117634
|
return;
|
|
117560
117635
|
const newNode = transformer(node);
|
|
117636
|
+
if (!newNode)
|
|
117637
|
+
return;
|
|
117561
117638
|
// Replace the JSX node with the MDAST node
|
|
117562
117639
|
parent.children[index] = newNode;
|
|
117563
117640
|
});
|
|
@@ -117838,6 +117915,126 @@ function restoreSnakeCase(placeholderName, mapping) {
|
|
|
117838
117915
|
return matchingKey ? mapping[matchingKey] : placeholderName;
|
|
117839
117916
|
}
|
|
117840
117917
|
|
|
117918
|
+
;// ./processor/transform/mdxish/mdxish-tables-to-jsx.ts
|
|
117919
|
+
|
|
117920
|
+
|
|
117921
|
+
const mdxish_tables_to_jsx_alignToStyle = (align) => {
|
|
117922
|
+
if (!align || align === 'left')
|
|
117923
|
+
return null;
|
|
117924
|
+
return {
|
|
117925
|
+
type: 'mdxJsxAttribute',
|
|
117926
|
+
name: 'style',
|
|
117927
|
+
value: {
|
|
117928
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
117929
|
+
value: `{ textAlign: "${align}" }`,
|
|
117930
|
+
},
|
|
117931
|
+
};
|
|
117932
|
+
};
|
|
117933
|
+
const mdxish_tables_to_jsx_isTableCell = (node) => ['tableHead', 'tableCell'].includes(node.type);
|
|
117934
|
+
const mdxish_tables_to_jsx_isLiteral = (node) => 'value' in node;
|
|
117935
|
+
/**
|
|
117936
|
+
* Mdxish-specific version of `tablesToJsx`. Differs from the shared MDX version:
|
|
117937
|
+
*
|
|
117938
|
+
* - Excludes `html` nodes from triggering JSX conversion because raw HTML
|
|
117939
|
+
* inside JSX `<Table>` breaks remarkMdx parsing on the deserialization roundtrip.
|
|
117940
|
+
* - Skips empty cells instead of aborting the entire visit so that flow content
|
|
117941
|
+
* in later cells is still detected.
|
|
117942
|
+
*/
|
|
117943
|
+
const mdxishTablesToJsx = () => tree => {
|
|
117944
|
+
visit(tree, (node) => ['table', 'tableau'].includes(node.type), (table, index, parent) => {
|
|
117945
|
+
let hasFlowContent = false;
|
|
117946
|
+
visit(table, mdxish_tables_to_jsx_isTableCell, (cell) => {
|
|
117947
|
+
if (cell.children.length === 0)
|
|
117948
|
+
return;
|
|
117949
|
+
const content = cell.children.length === 1 && cell.children[0].type === 'paragraph'
|
|
117950
|
+
? cell.children[0].children[0]
|
|
117951
|
+
: cell.children[0];
|
|
117952
|
+
if (!content)
|
|
117953
|
+
return;
|
|
117954
|
+
visit(cell, 'break', (_, breakIndex, breakParent) => {
|
|
117955
|
+
breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' });
|
|
117956
|
+
});
|
|
117957
|
+
if (!(phrasing(content) || content.type === 'plain') && content.type !== 'escape') {
|
|
117958
|
+
if (content.type === 'html')
|
|
117959
|
+
return;
|
|
117960
|
+
hasFlowContent = true;
|
|
117961
|
+
return EXIT;
|
|
117962
|
+
}
|
|
117963
|
+
visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
|
|
117964
|
+
if (node.value.match(/\n/)) {
|
|
117965
|
+
hasFlowContent = true;
|
|
117966
|
+
return EXIT;
|
|
117967
|
+
}
|
|
117968
|
+
});
|
|
117969
|
+
});
|
|
117970
|
+
if (!hasFlowContent) {
|
|
117971
|
+
table.type = 'table';
|
|
117972
|
+
return;
|
|
117973
|
+
}
|
|
117974
|
+
const styles = table.align.map(mdxish_tables_to_jsx_alignToStyle);
|
|
117975
|
+
const head = {
|
|
117976
|
+
attributes: [],
|
|
117977
|
+
type: 'mdxJsxFlowElement',
|
|
117978
|
+
name: 'thead',
|
|
117979
|
+
children: [
|
|
117980
|
+
{
|
|
117981
|
+
attributes: [],
|
|
117982
|
+
type: 'mdxJsxFlowElement',
|
|
117983
|
+
name: 'tr',
|
|
117984
|
+
children: table.children[0].children.map((cell, cellIndex) => {
|
|
117985
|
+
return {
|
|
117986
|
+
attributes: [],
|
|
117987
|
+
type: 'mdxJsxFlowElement',
|
|
117988
|
+
name: 'th',
|
|
117989
|
+
children: cell.children,
|
|
117990
|
+
...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
|
|
117991
|
+
};
|
|
117992
|
+
}),
|
|
117993
|
+
},
|
|
117994
|
+
],
|
|
117995
|
+
};
|
|
117996
|
+
const body = {
|
|
117997
|
+
attributes: [],
|
|
117998
|
+
type: 'mdxJsxFlowElement',
|
|
117999
|
+
name: 'tbody',
|
|
118000
|
+
children: table.children.splice(1).map(row => {
|
|
118001
|
+
return {
|
|
118002
|
+
attributes: [],
|
|
118003
|
+
type: 'mdxJsxFlowElement',
|
|
118004
|
+
name: 'tr',
|
|
118005
|
+
children: row.children.map((cell, cellIndex) => {
|
|
118006
|
+
return {
|
|
118007
|
+
type: 'mdxJsxFlowElement',
|
|
118008
|
+
name: 'td',
|
|
118009
|
+
children: cell.children,
|
|
118010
|
+
...(styles[cellIndex] && { attributes: [styles[cellIndex]] }),
|
|
118011
|
+
};
|
|
118012
|
+
}),
|
|
118013
|
+
};
|
|
118014
|
+
}),
|
|
118015
|
+
};
|
|
118016
|
+
const attributes = [
|
|
118017
|
+
{
|
|
118018
|
+
type: 'mdxJsxAttribute',
|
|
118019
|
+
name: 'align',
|
|
118020
|
+
value: {
|
|
118021
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
118022
|
+
value: JSON.stringify(table.align),
|
|
118023
|
+
},
|
|
118024
|
+
},
|
|
118025
|
+
];
|
|
118026
|
+
const jsx = {
|
|
118027
|
+
type: 'mdxJsxFlowElement',
|
|
118028
|
+
name: 'Table',
|
|
118029
|
+
attributes: table.align.find(a => a) ? attributes : [],
|
|
118030
|
+
children: [head, body],
|
|
118031
|
+
};
|
|
118032
|
+
parent.children[index] = jsx;
|
|
118033
|
+
});
|
|
118034
|
+
return tree;
|
|
118035
|
+
};
|
|
118036
|
+
/* harmony default export */ const mdxish_tables_to_jsx = (mdxishTablesToJsx);
|
|
118037
|
+
|
|
117841
118038
|
;// ./processor/transform/mdxish/normalize-table-separator.ts
|
|
117842
118039
|
/**
|
|
117843
118040
|
* Preprocessor to normalize malformed GFM table separator syntax.
|
|
@@ -120006,6 +120203,8 @@ function loadComponents() {
|
|
|
120006
120203
|
|
|
120007
120204
|
|
|
120008
120205
|
|
|
120206
|
+
|
|
120207
|
+
|
|
120009
120208
|
|
|
120010
120209
|
|
|
120011
120210
|
|
|
@@ -120103,18 +120302,28 @@ function mdxishAstProcessor(mdContent, opts = {}) {
|
|
|
120103
120302
|
};
|
|
120104
120303
|
}
|
|
120105
120304
|
/**
|
|
120106
|
-
*
|
|
120305
|
+
* Registers the mdx-jsx serialization extension so remark-stringify
|
|
120306
|
+
* can convert JSX nodes (e.g. `<Table>`) to markdown.
|
|
120307
|
+
*/
|
|
120308
|
+
function mdxJsxStringify() {
|
|
120309
|
+
const data = this.data();
|
|
120310
|
+
const extensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
|
|
120311
|
+
extensions.push({ extensions: [mdxJsxToMarkdown()] });
|
|
120312
|
+
}
|
|
120313
|
+
/**
|
|
120314
|
+
* Serializes an Mdast back into a markdown string.
|
|
120107
120315
|
*/
|
|
120108
120316
|
function mdxishMdastToMd(mdast) {
|
|
120109
|
-
const
|
|
120317
|
+
const processor = unified()
|
|
120110
120318
|
.use(remarkGfm)
|
|
120319
|
+
.use(mdxish_tables_to_jsx)
|
|
120111
120320
|
.use(mdxishCompilers)
|
|
120321
|
+
.use(mdxJsxStringify)
|
|
120112
120322
|
.use(remarkStringify, {
|
|
120113
120323
|
bullet: '-',
|
|
120114
120324
|
emphasis: '_',
|
|
120115
|
-
})
|
|
120116
|
-
|
|
120117
|
-
return md;
|
|
120325
|
+
});
|
|
120326
|
+
return processor.stringify(processor.runSync(mdast));
|
|
120118
120327
|
}
|
|
120119
120328
|
/**
|
|
120120
120329
|
* Processes markdown content with MDX syntax support and returns a HAST.
|