@readme/markdown 13.8.4 → 13.8.5
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/Callout/index.tsx +2 -1
- package/components/Callout/style.scss +2 -1
- package/components/TableOfContents/index.tsx +9 -10
- package/dist/lib/ast-processor.d.ts +2 -2
- package/dist/main.css +1 -1
- package/dist/main.css.map +1 -1
- package/dist/main.js +1188 -1157
- package/dist/main.node.js +1188 -1157
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/index.d.ts +2 -2
- package/package.json +1 -1
package/dist/main.node.js
CHANGED
|
@@ -19232,7 +19232,8 @@ const Callout = (props) => {
|
|
|
19232
19232
|
const children = external_react_.Children.toArray(props.children);
|
|
19233
19233
|
const icon = props.icon;
|
|
19234
19234
|
const isEmoji = icon && emoji_regex().test(icon);
|
|
19235
|
-
const
|
|
19235
|
+
const hasBody = children.length > 1;
|
|
19236
|
+
const heading = empty ? (hasBody ? external_react_.createElement("p", { className: 'callout-heading empty' }) : null) : children[0];
|
|
19236
19237
|
const theme = props.theme || (icon && themes[icon]) || 'default';
|
|
19237
19238
|
return (
|
|
19238
19239
|
// @ts-expect-error -- theme is not a valid attribute
|
|
@@ -24714,8 +24715,9 @@ function useScrollHighlight(navRef) {
|
|
|
24714
24715
|
const nav = navRef.current;
|
|
24715
24716
|
if (!nav)
|
|
24716
24717
|
return;
|
|
24717
|
-
const key = Array.from(nav.querySelectorAll('a
|
|
24718
|
-
.map(a => a.
|
|
24718
|
+
const key = Array.from(nav.querySelectorAll('a'))
|
|
24719
|
+
.map(a => a.hash)
|
|
24720
|
+
.filter(Boolean)
|
|
24719
24721
|
.join('\0');
|
|
24720
24722
|
setTocKey(key);
|
|
24721
24723
|
});
|
|
@@ -24737,9 +24739,11 @@ function useScrollHighlight(navRef) {
|
|
|
24737
24739
|
const scrollParent = TableOfContents_getScrollParent(headings[0]);
|
|
24738
24740
|
const isAtBottom = () => {
|
|
24739
24741
|
if (scrollParent instanceof Window) {
|
|
24740
|
-
return
|
|
24742
|
+
return document.documentElement.scrollHeight > window.innerHeight
|
|
24743
|
+
&& window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
24741
24744
|
}
|
|
24742
|
-
return scrollParent.
|
|
24745
|
+
return scrollParent.scrollHeight > scrollParent.clientHeight
|
|
24746
|
+
&& scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
24743
24747
|
};
|
|
24744
24748
|
const activate = (id) => {
|
|
24745
24749
|
if (id === activeId)
|
|
@@ -24789,15 +24793,12 @@ function useScrollHighlight(navRef) {
|
|
|
24789
24793
|
const onClick = (e) => {
|
|
24790
24794
|
if (!(e.target instanceof Element))
|
|
24791
24795
|
return;
|
|
24792
|
-
const anchor = e.target.closest('a
|
|
24793
|
-
if (!(anchor instanceof HTMLAnchorElement))
|
|
24796
|
+
const anchor = e.target.closest('a');
|
|
24797
|
+
if (!(anchor instanceof HTMLAnchorElement) || !anchor.hash)
|
|
24794
24798
|
return;
|
|
24795
24799
|
const id = decodeURIComponent(anchor.hash.slice(1));
|
|
24796
24800
|
if (!linkMap.has(id))
|
|
24797
24801
|
return;
|
|
24798
|
-
if (window.location.hash !== anchor.hash) {
|
|
24799
|
-
window.location.hash = anchor.hash;
|
|
24800
|
-
}
|
|
24801
24802
|
activate(id);
|
|
24802
24803
|
clickLocked = true;
|
|
24803
24804
|
let unlockTimer = null;
|
|
@@ -73340,267 +73341,1078 @@ const plain = (node, opts = {}) => {
|
|
|
73340
73341
|
};
|
|
73341
73342
|
/* harmony default export */ const lib_plain = (plain);
|
|
73342
73343
|
|
|
73343
|
-
;// ./
|
|
73344
|
-
const gemoji = (node) => `:${node.name}:`;
|
|
73345
|
-
/* harmony default export */ const compile_gemoji = (gemoji);
|
|
73344
|
+
;// ./lib/mdast.ts
|
|
73346
73345
|
|
|
73347
|
-
|
|
73348
|
-
const
|
|
73349
|
-
|
|
73346
|
+
const mdast = (text, opts = {}) => {
|
|
73347
|
+
const processor = ast_processor(opts);
|
|
73348
|
+
const tree = processor.parse(text);
|
|
73349
|
+
return processor.runSync(tree);
|
|
73350
|
+
};
|
|
73351
|
+
/* harmony default export */ const lib_mdast = (mdast);
|
|
73350
73352
|
|
|
73351
|
-
;// ./
|
|
73353
|
+
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
73352
73354
|
/**
|
|
73353
|
-
*
|
|
73354
|
-
* Works with both MDAST and HAST-like node structures.
|
|
73355
|
-
*
|
|
73356
|
-
* Placed this outside of the utils.ts file to avoid circular dependencies.
|
|
73355
|
+
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
73357
73356
|
*
|
|
73358
|
-
* @param
|
|
73359
|
-
* @returns
|
|
73357
|
+
* @param content - The markdown content to process
|
|
73358
|
+
* @returns Object containing protected content and arrays of original code blocks
|
|
73359
|
+
* @example
|
|
73360
|
+
* ```typescript
|
|
73361
|
+
* const input = 'Text with `inline code` and ```fenced block```';
|
|
73362
|
+
* protectCodeBlocks(input)
|
|
73363
|
+
* // Returns: {
|
|
73364
|
+
* // protectedCode: {
|
|
73365
|
+
* // codeBlocks: ['```fenced block```'],
|
|
73366
|
+
* // inlineCode: ['`inline code`']
|
|
73367
|
+
* // },
|
|
73368
|
+
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
73369
|
+
* // }
|
|
73370
|
+
* ```
|
|
73360
73371
|
*/
|
|
73361
|
-
|
|
73362
|
-
|
|
73363
|
-
|
|
73364
|
-
|
|
73365
|
-
|
|
73366
|
-
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73370
|
-
|
|
73371
|
-
|
|
73372
|
-
|
|
73373
|
-
|
|
73374
|
-
|
|
73375
|
-
|
|
73376
|
-
|
|
73377
|
-
}
|
|
73378
|
-
|
|
73372
|
+
function protectCodeBlocks(content) {
|
|
73373
|
+
const codeBlocks = [];
|
|
73374
|
+
const inlineCode = [];
|
|
73375
|
+
let protectedContent = '';
|
|
73376
|
+
let remaining = content;
|
|
73377
|
+
let codeBlockStart = remaining.indexOf('```');
|
|
73378
|
+
while (codeBlockStart !== -1) {
|
|
73379
|
+
protectedContent += remaining.slice(0, codeBlockStart);
|
|
73380
|
+
remaining = remaining.slice(codeBlockStart);
|
|
73381
|
+
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
73382
|
+
if (codeBlockEnd === -1) {
|
|
73383
|
+
break;
|
|
73384
|
+
}
|
|
73385
|
+
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
73386
|
+
const index = codeBlocks.length;
|
|
73387
|
+
codeBlocks.push(match);
|
|
73388
|
+
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
73389
|
+
remaining = remaining.slice(codeBlockEnd + 3);
|
|
73390
|
+
codeBlockStart = remaining.indexOf('```');
|
|
73379
73391
|
}
|
|
73380
|
-
|
|
73381
|
-
|
|
73382
|
-
|
|
73383
|
-
|
|
73384
|
-
|
|
73385
|
-
|
|
73386
|
-
|
|
73387
|
-
|
|
73388
|
-
|
|
73389
|
-
|
|
73390
|
-
|
|
73391
|
-
|
|
73392
|
-
|
|
73393
|
-
|
|
73394
|
-
|
|
73395
|
-
|
|
73396
|
-
|
|
73397
|
-
|
|
73398
|
-
const titleParser = unified().use(remarkParse).use(remarkGfm);
|
|
73399
|
-
// The title paragraph may contain custom AST nodes that `toMarkdown` doesn't
|
|
73400
|
-
// natively understand
|
|
73401
|
-
const toMarkdownExtensions = [
|
|
73402
|
-
gfmToMarkdown(),
|
|
73403
|
-
// For mdx variable syntaxes (e.g., {user.name})
|
|
73404
|
-
mdxExpressionToMarkdown(),
|
|
73405
|
-
// Important: This is required and would crash the parser if there's no variable and gemoji node handler
|
|
73406
|
-
{ handlers: { [NodeTypes.variable]: compile_variable, [NodeTypes.emoji]: compile_gemoji } },
|
|
73407
|
-
];
|
|
73408
|
-
const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
|
|
73409
|
-
const findFirst = (node) => {
|
|
73410
|
-
if ('children' in node)
|
|
73411
|
-
return findFirst(node.children[0]);
|
|
73412
|
-
if (node.type === 'text')
|
|
73413
|
-
return node;
|
|
73414
|
-
return null;
|
|
73415
|
-
};
|
|
73416
|
-
const wrapHeading = (node) => {
|
|
73417
|
-
const firstChild = node.children[0];
|
|
73418
|
-
return {
|
|
73419
|
-
type: 'heading',
|
|
73420
|
-
depth: 3,
|
|
73421
|
-
children: ('children' in firstChild ? firstChild.children : []),
|
|
73422
|
-
position: {
|
|
73423
|
-
start: firstChild.position.start,
|
|
73424
|
-
end: firstChild.position.end,
|
|
73425
|
-
},
|
|
73426
|
-
};
|
|
73427
|
-
};
|
|
73392
|
+
protectedContent += remaining;
|
|
73393
|
+
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
73394
|
+
const index = inlineCode.length;
|
|
73395
|
+
inlineCode.push(match);
|
|
73396
|
+
return `___INLINE_CODE_${index}___`;
|
|
73397
|
+
});
|
|
73398
|
+
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
73399
|
+
}
|
|
73428
73400
|
/**
|
|
73429
|
-
*
|
|
73430
|
-
*
|
|
73401
|
+
* Restores inline code by replacing placeholders with original content.
|
|
73402
|
+
*
|
|
73403
|
+
* @param content - Content with inline code placeholders
|
|
73404
|
+
* @param protectedCode - The protected code arrays
|
|
73405
|
+
* @returns Content with inline code restored
|
|
73431
73406
|
*/
|
|
73432
|
-
|
|
73433
|
-
|
|
73434
|
-
|
|
73435
|
-
|
|
73436
|
-
|
|
73437
|
-
return false;
|
|
73438
|
-
const firstTextChild = firstChild.children?.[0];
|
|
73439
|
-
return firstTextChild?.type === 'text';
|
|
73440
|
-
};
|
|
73407
|
+
function restoreInlineCode(content, protectedCode) {
|
|
73408
|
+
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
73409
|
+
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
73410
|
+
});
|
|
73411
|
+
}
|
|
73441
73412
|
/**
|
|
73442
|
-
*
|
|
73443
|
-
*
|
|
73413
|
+
* Restores fenced code blocks by replacing placeholders with original content.
|
|
73414
|
+
*
|
|
73415
|
+
* @param content - Content with code block placeholders
|
|
73416
|
+
* @param protectedCode - The protected code arrays
|
|
73417
|
+
* @returns Content with code blocks restored
|
|
73444
73418
|
*/
|
|
73445
|
-
|
|
73446
|
-
|
|
73447
|
-
|
|
73448
|
-
|
|
73449
|
-
|
|
73450
|
-
if (newlineIndex !== -1) {
|
|
73451
|
-
return { index: i, newlineIndex };
|
|
73452
|
-
}
|
|
73453
|
-
}
|
|
73454
|
-
}
|
|
73455
|
-
return null;
|
|
73456
|
-
};
|
|
73419
|
+
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
73420
|
+
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
73421
|
+
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
73422
|
+
});
|
|
73423
|
+
}
|
|
73457
73424
|
/**
|
|
73458
|
-
*
|
|
73459
|
-
*
|
|
73425
|
+
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
73426
|
+
*
|
|
73427
|
+
* @param content - Content with code placeholders
|
|
73428
|
+
* @param protectedCode - The protected code arrays
|
|
73429
|
+
* @returns Content with all code blocks and inline code restored
|
|
73430
|
+
* @example
|
|
73431
|
+
* ```typescript
|
|
73432
|
+
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
73433
|
+
* const protectedCode = {
|
|
73434
|
+
* codeBlocks: ['```js\ncode\n```'],
|
|
73435
|
+
* inlineCode: ['`inline`']
|
|
73436
|
+
* };
|
|
73437
|
+
* restoreCodeBlocks(content, protectedCode)
|
|
73438
|
+
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
73439
|
+
* ```
|
|
73460
73440
|
*/
|
|
73461
|
-
|
|
73462
|
-
|
|
73463
|
-
|
|
73464
|
-
|
|
73465
|
-
|
|
73466
|
-
|
|
73467
|
-
|
|
73468
|
-
|
|
73469
|
-
|
|
73470
|
-
|
|
73471
|
-
|
|
73472
|
-
|
|
73473
|
-
headingChildren.push({ type: 'text', value: beforeNewline });
|
|
73441
|
+
function restoreCodeBlocks(content, protectedCode) {
|
|
73442
|
+
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
73443
|
+
restored = restoreInlineCode(restored, protectedCode);
|
|
73444
|
+
return restored;
|
|
73445
|
+
}
|
|
73446
|
+
|
|
73447
|
+
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
73448
|
+
|
|
73449
|
+
// Base64 encode (Node.js + browser compatible)
|
|
73450
|
+
function base64Encode(str) {
|
|
73451
|
+
if (typeof Buffer !== 'undefined') {
|
|
73452
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
73474
73453
|
}
|
|
73475
|
-
|
|
73476
|
-
|
|
73477
|
-
|
|
73478
|
-
|
|
73479
|
-
|
|
73454
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
73455
|
+
}
|
|
73456
|
+
// Base64 decode (Node.js + browser compatible)
|
|
73457
|
+
function base64Decode(str) {
|
|
73458
|
+
if (typeof Buffer !== 'undefined') {
|
|
73459
|
+
return Buffer.from(str, 'base64').toString('utf-8');
|
|
73480
73460
|
}
|
|
73481
|
-
|
|
73482
|
-
|
|
73483
|
-
|
|
73461
|
+
return decodeURIComponent(escape(atob(str)));
|
|
73462
|
+
}
|
|
73463
|
+
function escapeHtmlAttribute(value) {
|
|
73464
|
+
return value
|
|
73465
|
+
.replace(/&/g, '&')
|
|
73466
|
+
.replace(/"/g, '"')
|
|
73467
|
+
.replace(/</g, '<')
|
|
73468
|
+
.replace(/>/g, '>')
|
|
73469
|
+
.replace(/\n/g, ' ');
|
|
73470
|
+
}
|
|
73471
|
+
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
73472
|
+
// Using a prefix that won't conflict with regular string values
|
|
73473
|
+
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
73474
|
+
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
73475
|
+
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
73476
|
+
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
73484
73477
|
/**
|
|
73485
|
-
*
|
|
73486
|
-
*
|
|
73478
|
+
* Evaluates a JavaScript expression using context variables.
|
|
73479
|
+
*
|
|
73480
|
+
* @param expression
|
|
73481
|
+
* @param context
|
|
73482
|
+
* @returns The evaluated result
|
|
73483
|
+
* @example
|
|
73484
|
+
* ```typescript
|
|
73485
|
+
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
73486
|
+
* evaluateExpression('baseUrl + path', context)
|
|
73487
|
+
* // Returns: 'https://example.com/api'
|
|
73488
|
+
* ```
|
|
73487
73489
|
*/
|
|
73488
|
-
|
|
73489
|
-
const
|
|
73490
|
-
|
|
73491
|
-
|
|
73492
|
-
}
|
|
73493
|
-
|
|
73494
|
-
|
|
73495
|
-
|
|
73496
|
-
|
|
73497
|
-
|
|
73498
|
-
|
|
73499
|
-
|
|
73500
|
-
|
|
73501
|
-
|
|
73502
|
-
|
|
73503
|
-
|
|
73504
|
-
|
|
73505
|
-
|
|
73506
|
-
|
|
73507
|
-
|
|
73508
|
-
|
|
73509
|
-
|
|
73510
|
-
|
|
73511
|
-
|
|
73512
|
-
|
|
73490
|
+
function evaluateExpression(expression, context) {
|
|
73491
|
+
const contextKeys = Object.keys(context);
|
|
73492
|
+
const contextValues = Object.values(context);
|
|
73493
|
+
// eslint-disable-next-line no-new-func
|
|
73494
|
+
const func = new Function(...contextKeys, `return ${expression}`);
|
|
73495
|
+
return func(...contextValues);
|
|
73496
|
+
}
|
|
73497
|
+
/**
|
|
73498
|
+
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
73499
|
+
*
|
|
73500
|
+
* @param content
|
|
73501
|
+
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
73502
|
+
* @example
|
|
73503
|
+
* ```typescript
|
|
73504
|
+
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
73505
|
+
* protectHTMLBlockContent(input)
|
|
73506
|
+
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
73507
|
+
* ```
|
|
73508
|
+
*/
|
|
73509
|
+
function protectHTMLBlockContent(content) {
|
|
73510
|
+
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
73511
|
+
const encoded = base64Encode(templateContent);
|
|
73512
|
+
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
73513
|
+
});
|
|
73514
|
+
}
|
|
73515
|
+
/**
|
|
73516
|
+
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
73517
|
+
*
|
|
73518
|
+
* @param content
|
|
73519
|
+
* @returns Content with JSX comments removed
|
|
73520
|
+
* @example
|
|
73521
|
+
* ```typescript
|
|
73522
|
+
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
73523
|
+
* // Returns: 'Text more text'
|
|
73524
|
+
* ```
|
|
73525
|
+
*/
|
|
73526
|
+
function removeJSXComments(content) {
|
|
73527
|
+
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
73528
|
+
}
|
|
73529
|
+
/**
|
|
73530
|
+
* Extracts content between balanced braces, handling nested braces.
|
|
73531
|
+
*
|
|
73532
|
+
* @param content
|
|
73533
|
+
* @param start
|
|
73534
|
+
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
73535
|
+
* @example
|
|
73536
|
+
* ```typescript
|
|
73537
|
+
* const input = 'foo{bar{baz}qux}end';
|
|
73538
|
+
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
73539
|
+
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
73540
|
+
* ```
|
|
73541
|
+
*/
|
|
73542
|
+
function extractBalancedBraces(content, start) {
|
|
73543
|
+
let depth = 1;
|
|
73544
|
+
let pos = start;
|
|
73545
|
+
while (pos < content.length && depth > 0) {
|
|
73546
|
+
const char = content[pos];
|
|
73547
|
+
if (char === '{')
|
|
73548
|
+
depth += 1;
|
|
73549
|
+
else if (char === '}')
|
|
73550
|
+
depth -= 1;
|
|
73551
|
+
pos += 1;
|
|
73513
73552
|
}
|
|
73514
|
-
|
|
73515
|
-
|
|
73516
|
-
|
|
73517
|
-
|
|
73518
|
-
|
|
73519
|
-
|
|
73520
|
-
|
|
73521
|
-
|
|
73522
|
-
|
|
73523
|
-
|
|
73524
|
-
|
|
73525
|
-
|
|
73526
|
-
|
|
73527
|
-
|
|
73528
|
-
|
|
73529
|
-
|
|
73530
|
-
|
|
73531
|
-
|
|
73532
|
-
|
|
73533
|
-
|
|
73534
|
-
|
|
73535
|
-
|
|
73536
|
-
|
|
73537
|
-
|
|
73538
|
-
|
|
73539
|
-
|
|
73540
|
-
|
|
73553
|
+
if (depth !== 0)
|
|
73554
|
+
return null;
|
|
73555
|
+
return { content: content.slice(start, pos - 1), end: pos };
|
|
73556
|
+
}
|
|
73557
|
+
/**
|
|
73558
|
+
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
73559
|
+
* Handles three cases:
|
|
73560
|
+
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
73561
|
+
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
73562
|
+
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
73563
|
+
*
|
|
73564
|
+
*/
|
|
73565
|
+
function escapeProblematicBraces(content) {
|
|
73566
|
+
// Skip HTML elements — their content should never be escaped because
|
|
73567
|
+
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
73568
|
+
const htmlElements = [];
|
|
73569
|
+
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
73570
|
+
const idx = htmlElements.length;
|
|
73571
|
+
htmlElements.push(match);
|
|
73572
|
+
return `___HTML_ELEM_${idx}___`;
|
|
73573
|
+
});
|
|
73574
|
+
const toEscape = new Set();
|
|
73575
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
73576
|
+
const chars = Array.from(safe);
|
|
73577
|
+
let strDelim = null;
|
|
73578
|
+
let strEscaped = false;
|
|
73579
|
+
// Stack of open braces with their state
|
|
73580
|
+
const openStack = [];
|
|
73581
|
+
// Track position of last newline (outside strings) to detect blank lines
|
|
73582
|
+
let lastNewlinePos = -2; // -2 means no recent newline
|
|
73583
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
73584
|
+
const ch = chars[i];
|
|
73585
|
+
// Track string delimiters inside expressions to ignore braces within them
|
|
73586
|
+
if (openStack.length > 0) {
|
|
73587
|
+
if (strDelim) {
|
|
73588
|
+
if (strEscaped)
|
|
73589
|
+
strEscaped = false;
|
|
73590
|
+
else if (ch === '\\')
|
|
73591
|
+
strEscaped = true;
|
|
73592
|
+
else if (ch === strDelim)
|
|
73593
|
+
strDelim = null;
|
|
73594
|
+
// eslint-disable-next-line no-continue
|
|
73595
|
+
continue;
|
|
73541
73596
|
}
|
|
73542
|
-
|
|
73543
|
-
|
|
73544
|
-
//
|
|
73545
|
-
|
|
73546
|
-
|
|
73547
|
-
|
|
73548
|
-
|
|
73549
|
-
|
|
73550
|
-
|
|
73551
|
-
.
|
|
73552
|
-
|
|
73553
|
-
|
|
73554
|
-
|
|
73555
|
-
|
|
73556
|
-
|
|
73557
|
-
|
|
73558
|
-
visit(parsedTitle, (n) => {
|
|
73559
|
-
delete n.position;
|
|
73560
|
-
});
|
|
73561
|
-
const heading = wrapHeading(node);
|
|
73562
|
-
heading.children = parsedTitle.children;
|
|
73563
|
-
delete heading.position;
|
|
73564
|
-
node.children[0] = heading;
|
|
73597
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
73598
|
+
strDelim = ch;
|
|
73599
|
+
// eslint-disable-next-line no-continue
|
|
73600
|
+
continue;
|
|
73601
|
+
}
|
|
73602
|
+
// Track newlines to detect blank lines (paragraph boundaries)
|
|
73603
|
+
if (ch === '\n') {
|
|
73604
|
+
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
73605
|
+
if (lastNewlinePos >= 0) {
|
|
73606
|
+
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
73607
|
+
if (/^[ \t]*$/.test(between)) {
|
|
73608
|
+
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
73609
|
+
openStack.forEach(entry => {
|
|
73610
|
+
entry.hasBlankLine = true;
|
|
73611
|
+
});
|
|
73612
|
+
}
|
|
73565
73613
|
}
|
|
73566
|
-
|
|
73567
|
-
|
|
73568
|
-
|
|
73569
|
-
|
|
73614
|
+
lastNewlinePos = i;
|
|
73615
|
+
}
|
|
73616
|
+
}
|
|
73617
|
+
// Skip already-escaped braces (count preceding backslashes)
|
|
73618
|
+
if (ch === '{' || ch === '}') {
|
|
73619
|
+
let bs = 0;
|
|
73620
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
73621
|
+
bs += 1;
|
|
73622
|
+
if (bs % 2 === 1) {
|
|
73623
|
+
// eslint-disable-next-line no-continue
|
|
73624
|
+
continue;
|
|
73625
|
+
}
|
|
73626
|
+
}
|
|
73627
|
+
if (ch === '{') {
|
|
73628
|
+
openStack.push({ pos: i, hasBlankLine: false });
|
|
73629
|
+
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
73630
|
+
}
|
|
73631
|
+
else if (ch === '}') {
|
|
73632
|
+
if (openStack.length > 0) {
|
|
73633
|
+
const entry = openStack.pop();
|
|
73634
|
+
// If expression spans paragraph boundary, escape both braces
|
|
73635
|
+
if (entry.hasBlankLine) {
|
|
73636
|
+
toEscape.add(entry.pos);
|
|
73637
|
+
toEscape.add(i);
|
|
73570
73638
|
}
|
|
73571
73639
|
}
|
|
73572
73640
|
else {
|
|
73573
|
-
|
|
73574
|
-
|
|
73575
|
-
node.children[0].position.start.column += match.length;
|
|
73641
|
+
// Unbalanced closing brace (no matching open)
|
|
73642
|
+
toEscape.add(i);
|
|
73576
73643
|
}
|
|
73577
73644
|
}
|
|
73578
|
-
|
|
73579
|
-
|
|
73580
|
-
|
|
73581
|
-
|
|
73582
|
-
|
|
73583
|
-
|
|
73584
|
-
|
|
73585
|
-
|
|
73586
|
-
|
|
73587
|
-
|
|
73588
|
-
|
|
73589
|
-
|
|
73590
|
-
|
|
73591
|
-
|
|
73592
|
-
|
|
73593
|
-
|
|
73594
|
-
|
|
73595
|
-
|
|
73596
|
-
|
|
73597
|
-
|
|
73598
|
-
|
|
73599
|
-
|
|
73600
|
-
|
|
73601
|
-
|
|
73602
|
-
|
|
73603
|
-
|
|
73645
|
+
}
|
|
73646
|
+
// Any remaining open braces are unbalanced
|
|
73647
|
+
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
73648
|
+
// If there are no problematic braces, return safe content as-is;
|
|
73649
|
+
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
73650
|
+
let result = toEscape.size === 0
|
|
73651
|
+
? safe
|
|
73652
|
+
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
73653
|
+
// Restore HTML elements
|
|
73654
|
+
if (htmlElements.length > 0) {
|
|
73655
|
+
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
73656
|
+
}
|
|
73657
|
+
return result;
|
|
73658
|
+
}
|
|
73659
|
+
/**
|
|
73660
|
+
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
73661
|
+
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
73662
|
+
*
|
|
73663
|
+
* @param content
|
|
73664
|
+
* @param context
|
|
73665
|
+
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
73666
|
+
* @example
|
|
73667
|
+
* ```typescript
|
|
73668
|
+
* const context = { baseUrl: 'https://example.com' };
|
|
73669
|
+
* const input = '<a href={baseUrl}>Link</a>';
|
|
73670
|
+
* evaluateAttributeExpressions(input, context)
|
|
73671
|
+
* // Returns: '<a href="https://example.com">Link</a>'
|
|
73672
|
+
* ```
|
|
73673
|
+
*/
|
|
73674
|
+
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
73675
|
+
const attrStartRegex = /(\w+)=\{/g;
|
|
73676
|
+
let result = '';
|
|
73677
|
+
let lastEnd = 0;
|
|
73678
|
+
let match = attrStartRegex.exec(content);
|
|
73679
|
+
while (match !== null) {
|
|
73680
|
+
const attributeName = match[1];
|
|
73681
|
+
const braceStart = match.index + match[0].length;
|
|
73682
|
+
const extracted = extractBalancedBraces(content, braceStart);
|
|
73683
|
+
if (extracted) {
|
|
73684
|
+
// The expression might contain template literals in MDX component tag props
|
|
73685
|
+
// E.g. <Component greeting={`Hello World!`} />
|
|
73686
|
+
// that is marked as inline code. So we need to restore the inline codes
|
|
73687
|
+
// in the expression to evaluate it
|
|
73688
|
+
let expression = extracted.content;
|
|
73689
|
+
if (protectedCode) {
|
|
73690
|
+
expression = restoreInlineCode(expression, protectedCode);
|
|
73691
|
+
}
|
|
73692
|
+
const fullMatchEnd = extracted.end;
|
|
73693
|
+
result += content.slice(lastEnd, match.index);
|
|
73694
|
+
try {
|
|
73695
|
+
const evalResult = evaluateExpression(expression, context);
|
|
73696
|
+
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
73697
|
+
if (attributeName === 'style') {
|
|
73698
|
+
const cssString = Object.entries(evalResult)
|
|
73699
|
+
.map(([key, value]) => {
|
|
73700
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
73701
|
+
return `${cssKey}: ${value}`;
|
|
73702
|
+
})
|
|
73703
|
+
.join('; ');
|
|
73704
|
+
result += `style="${cssString}"`;
|
|
73705
|
+
}
|
|
73706
|
+
else {
|
|
73707
|
+
// These are arrays / objects attribute values
|
|
73708
|
+
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
73709
|
+
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
73710
|
+
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
73711
|
+
result += `${attributeName}="${jsonValue}"`;
|
|
73712
|
+
}
|
|
73713
|
+
}
|
|
73714
|
+
else if (attributeName === 'className') {
|
|
73715
|
+
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
73716
|
+
// This will be restored later in the pipeline
|
|
73717
|
+
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
73718
|
+
}
|
|
73719
|
+
else {
|
|
73720
|
+
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
73721
|
+
}
|
|
73722
|
+
}
|
|
73723
|
+
catch (_error) {
|
|
73724
|
+
result += content.slice(match.index, fullMatchEnd);
|
|
73725
|
+
}
|
|
73726
|
+
lastEnd = fullMatchEnd;
|
|
73727
|
+
attrStartRegex.lastIndex = fullMatchEnd;
|
|
73728
|
+
}
|
|
73729
|
+
match = attrStartRegex.exec(content);
|
|
73730
|
+
}
|
|
73731
|
+
result += content.slice(lastEnd);
|
|
73732
|
+
return result;
|
|
73733
|
+
}
|
|
73734
|
+
/**
|
|
73735
|
+
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
73736
|
+
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
73737
|
+
*
|
|
73738
|
+
* @param content
|
|
73739
|
+
* @param context
|
|
73740
|
+
* @returns Preprocessed content ready for markdown parsing
|
|
73741
|
+
*/
|
|
73742
|
+
function preprocessJSXExpressions(content, context = {}) {
|
|
73743
|
+
// Step 0: Base64 encode HTMLBlock content
|
|
73744
|
+
let processed = protectHTMLBlockContent(content);
|
|
73745
|
+
// Step 1: Protect code blocks and inline code
|
|
73746
|
+
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
73747
|
+
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
73748
|
+
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
73749
|
+
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
73750
|
+
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
73751
|
+
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
73752
|
+
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
73753
|
+
processed = escapeProblematicBraces(processed);
|
|
73754
|
+
// Step 4: Restore protected code blocks
|
|
73755
|
+
processed = restoreCodeBlocks(processed, protectedCode);
|
|
73756
|
+
return processed;
|
|
73757
|
+
}
|
|
73758
|
+
|
|
73759
|
+
;// ./processor/utils.ts
|
|
73760
|
+
|
|
73761
|
+
|
|
73762
|
+
|
|
73763
|
+
/**
|
|
73764
|
+
* Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
|
|
73765
|
+
* This currently sets all the values to a string since we process/compile the MDX on the fly
|
|
73766
|
+
* through the editor, and it'll throw errors over malformed JSX. TODO: fix this.
|
|
73767
|
+
*
|
|
73768
|
+
* @template T
|
|
73769
|
+
* @param {Node} node
|
|
73770
|
+
* @returns {string} formatted hProperties as JSX attributes
|
|
73771
|
+
*/
|
|
73772
|
+
const formatHProps = (node) => {
|
|
73773
|
+
const hProps = getHProps(node);
|
|
73774
|
+
return formatProps(hProps);
|
|
73775
|
+
};
|
|
73776
|
+
/**
|
|
73777
|
+
* Formats an object of props as a string.
|
|
73778
|
+
*
|
|
73779
|
+
* @param {Object} props
|
|
73780
|
+
* @returns {string}
|
|
73781
|
+
*/
|
|
73782
|
+
const formatProps = (props) => {
|
|
73783
|
+
const keys = Object.keys(props);
|
|
73784
|
+
return keys.map(key => `${key}="${props[key]}"`).join(' ');
|
|
73785
|
+
};
|
|
73786
|
+
/**
|
|
73787
|
+
* Returns the hProperties of a node.
|
|
73788
|
+
*
|
|
73789
|
+
* @template T
|
|
73790
|
+
* @param {Node} node
|
|
73791
|
+
* @returns {T} hProperties
|
|
73792
|
+
*/
|
|
73793
|
+
const getHProps = (node) => {
|
|
73794
|
+
const hProps = node.data?.hProperties || {};
|
|
73795
|
+
return hProps;
|
|
73796
|
+
};
|
|
73797
|
+
/**
|
|
73798
|
+
* Returns array of hProperty keys.
|
|
73799
|
+
*
|
|
73800
|
+
* @template T
|
|
73801
|
+
* @param {Node} node
|
|
73802
|
+
* @returns {Array} array of hProperty keys
|
|
73803
|
+
*/
|
|
73804
|
+
const getHPropKeys = (node) => {
|
|
73805
|
+
const hProps = getHProps(node);
|
|
73806
|
+
return Object.keys(hProps) || [];
|
|
73807
|
+
};
|
|
73808
|
+
/**
|
|
73809
|
+
* Gets the attributes of an MDX element and returns them as an object of hProperties.
|
|
73810
|
+
*
|
|
73811
|
+
* @template T
|
|
73812
|
+
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
73813
|
+
* @returns {T} object of hProperties
|
|
73814
|
+
*/
|
|
73815
|
+
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
73816
|
+
if ('name' in attr) {
|
|
73817
|
+
if (typeof attr.value === 'string') {
|
|
73818
|
+
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
73819
|
+
try {
|
|
73820
|
+
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
73821
|
+
}
|
|
73822
|
+
catch {
|
|
73823
|
+
memo[attr.name] = attr.value;
|
|
73824
|
+
}
|
|
73825
|
+
}
|
|
73826
|
+
else {
|
|
73827
|
+
memo[attr.name] = attr.value;
|
|
73828
|
+
}
|
|
73829
|
+
}
|
|
73830
|
+
else if (attr.value === null) {
|
|
73831
|
+
memo[attr.name] = true;
|
|
73832
|
+
}
|
|
73833
|
+
else if (attr.value.value !== 'undefined') {
|
|
73834
|
+
memo[attr.name] = JSON.parse(attr.value.value);
|
|
73835
|
+
}
|
|
73836
|
+
}
|
|
73837
|
+
return memo;
|
|
73838
|
+
}, {});
|
|
73839
|
+
/**
|
|
73840
|
+
* Gets the children of an MDX element and returns them as an array of Text nodes.
|
|
73841
|
+
* Currently only being used by the HTML Block component, which only expects a single text node.
|
|
73842
|
+
*
|
|
73843
|
+
* @template T
|
|
73844
|
+
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
73845
|
+
* @returns {Array} array of child text nodes
|
|
73846
|
+
*/
|
|
73847
|
+
const utils_getChildren = (jsx) => jsx.children.reduce((memo, child, i) => {
|
|
73848
|
+
memo[i] = {
|
|
73849
|
+
type: 'text',
|
|
73850
|
+
value: child.value,
|
|
73851
|
+
position: child.position,
|
|
73852
|
+
};
|
|
73853
|
+
return memo;
|
|
73854
|
+
}, []);
|
|
73855
|
+
/**
|
|
73856
|
+
* Tests if a node is an MDX element.
|
|
73857
|
+
* TODO: Make this more extensible to all types of nodes. isElement(node, 'type' or ['type1', 'type2']), say
|
|
73858
|
+
*
|
|
73859
|
+
* @param {Node} node
|
|
73860
|
+
* @returns {(node is MdxJsxFlowElement | MdxJsxTextElement | MdxjsEsm)}
|
|
73861
|
+
*/
|
|
73862
|
+
const isMDXElement = (node) => {
|
|
73863
|
+
return ['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type);
|
|
73864
|
+
};
|
|
73865
|
+
/**
|
|
73866
|
+
* Tests if a node is an MDX ESM element (i.e. import or export).
|
|
73867
|
+
*
|
|
73868
|
+
* @param {Node} node
|
|
73869
|
+
* @returns {boolean}
|
|
73870
|
+
*/
|
|
73871
|
+
const isMDXEsm = (node) => {
|
|
73872
|
+
return node.type === 'mdxjsEsm';
|
|
73873
|
+
};
|
|
73874
|
+
/**
|
|
73875
|
+
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
73876
|
+
* and unindents the HTML.
|
|
73877
|
+
*
|
|
73878
|
+
* @param {string} html - HTML content from template literal
|
|
73879
|
+
* @returns {string} processed HTML
|
|
73880
|
+
*/
|
|
73881
|
+
function formatHtmlForMdxish(html) {
|
|
73882
|
+
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
73883
|
+
// from being parsed prematurely
|
|
73884
|
+
let processed = html;
|
|
73885
|
+
if (processed.startsWith('`') && processed.endsWith('`')) {
|
|
73886
|
+
processed = processed.slice(1, -1);
|
|
73887
|
+
}
|
|
73888
|
+
// Removes the leading/trailing newlines
|
|
73889
|
+
let cleaned = processed.replace(/^\s*\n|\n\s*$/g, '');
|
|
73890
|
+
// Convert literal \n sequences to actual newlines BEFORE processing backticks
|
|
73891
|
+
// This prevents the backtick unescaping regex from incorrectly matching \n sequences
|
|
73892
|
+
cleaned = cleaned.replace(/\\n/g, '\n');
|
|
73893
|
+
// Unescape backticks: \` -> ` (users escape backticks in template literals)
|
|
73894
|
+
// Handle both cases: \` (adjacent) and \ followed by ` (split by markdown parser)
|
|
73895
|
+
cleaned = cleaned.replace(/\\`/g, '`');
|
|
73896
|
+
// Also handle case where backslash and backtick got separated by markdown parsing
|
|
73897
|
+
// Pattern: backslash followed by any characters (but not \n which we already handled), then a backtick
|
|
73898
|
+
// This handles cases like: \example` -> `example` (replacing \ with ` at start)
|
|
73899
|
+
// Exclude \n sequences to avoid matching them incorrectly
|
|
73900
|
+
cleaned = cleaned.replace(/\\([^`\\n]*?)`/g, '`$1`');
|
|
73901
|
+
// Fix case where markdown parser consumed one backtick from triple backticks
|
|
73902
|
+
// Pattern: `` followed by a word (like ``javascript) should be ```javascript
|
|
73903
|
+
// This handles cases where code fences were parsed and one backtick was lost
|
|
73904
|
+
cleaned = cleaned.replace(/<(\w+[^>]*)>``(\w+)/g, '<$1>```$2');
|
|
73905
|
+
// Unescape dollar signs: \$ -> $ (users escape $ in template literals to prevent interpolation)
|
|
73906
|
+
cleaned = cleaned.replace(/\\\$/g, '$');
|
|
73907
|
+
return cleaned;
|
|
73908
|
+
}
|
|
73909
|
+
/**
|
|
73910
|
+
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
73911
|
+
* and unindents the HTML.
|
|
73912
|
+
*
|
|
73913
|
+
* @param {string} html
|
|
73914
|
+
* @returns {string} formatted HTML
|
|
73915
|
+
*/
|
|
73916
|
+
const formatHTML = (html) => {
|
|
73917
|
+
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
73918
|
+
// from being parsed prematurely
|
|
73919
|
+
if (html.startsWith('`') && html.endsWith('`')) {
|
|
73920
|
+
// eslint-disable-next-line no-param-reassign
|
|
73921
|
+
html = html.slice(1, -1);
|
|
73922
|
+
}
|
|
73923
|
+
// Removes the leading/trailing newlines
|
|
73924
|
+
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');
|
|
73925
|
+
// // Get the number of spaces in the first line to determine the tab size
|
|
73926
|
+
// const tab = cleaned.match(/^\s*/)[0].length;
|
|
73927
|
+
// // Remove the first indentation level from each line
|
|
73928
|
+
// const tabRegex = new RegExp(`^\\s{${tab}}`, 'gm');
|
|
73929
|
+
// const unindented = cleaned.replace(tabRegex, '');
|
|
73930
|
+
return cleaned;
|
|
73931
|
+
};
|
|
73932
|
+
/**
|
|
73933
|
+
* Reformat HTML for the markdown/mdx by adding an indentation to each line. This assures that the
|
|
73934
|
+
* HTML is indentend properly within the HTMLBlock component when rendered in the markdown/mdx.
|
|
73935
|
+
*
|
|
73936
|
+
* @param {string} html
|
|
73937
|
+
* @param {number} [indent=2]
|
|
73938
|
+
* @returns {string} re-formatted HTML
|
|
73939
|
+
*/
|
|
73940
|
+
const reformatHTML = (html) => {
|
|
73941
|
+
// Remove leading/trailing newlines
|
|
73942
|
+
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '').replaceAll(/(?<!\\(\\\\)*)`/g, '\\`');
|
|
73943
|
+
// // Create a tab/indent with the specified number of spaces
|
|
73944
|
+
// const tab = ' '.repeat(indent);
|
|
73945
|
+
// // Indent each line of the HTML (converts to an array, indents each line, then joins back)
|
|
73946
|
+
// const indented = cleaned.split('\n').map((line: string) => `${tab}${line}`).join('\n');
|
|
73947
|
+
return cleaned;
|
|
73948
|
+
};
|
|
73949
|
+
const toAttributes = (object, keys = []) => {
|
|
73950
|
+
const attributes = [];
|
|
73951
|
+
Object.entries(object).forEach(([name, v]) => {
|
|
73952
|
+
if (keys.length > 0 && !keys.includes(name))
|
|
73953
|
+
return;
|
|
73954
|
+
let value;
|
|
73955
|
+
if (typeof v === 'undefined' || v === null || v === '' || v === false) {
|
|
73956
|
+
return;
|
|
73957
|
+
}
|
|
73958
|
+
else if (typeof v === 'string') {
|
|
73959
|
+
value = v;
|
|
73960
|
+
}
|
|
73961
|
+
else {
|
|
73962
|
+
/* values can be null, undefined, string, or a expression, eg:
|
|
73963
|
+
*
|
|
73964
|
+
* ```
|
|
73965
|
+
* <Image src="..." border={false} size={width - 20} />
|
|
73966
|
+
* ```
|
|
73967
|
+
*
|
|
73968
|
+
* Parsing the expression seems to only be done by the library
|
|
73969
|
+
* `mdast-util-mdx-jsx`, and so the most straight forward way to parse
|
|
73970
|
+
* the expression and get the appropriate AST is with our `mdast`
|
|
73971
|
+
* function.
|
|
73972
|
+
*/
|
|
73973
|
+
const proxy = lib_mdast(`{${v}}`);
|
|
73974
|
+
const data = proxy.children[0].data;
|
|
73975
|
+
value = {
|
|
73976
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
73977
|
+
value: v.toString(),
|
|
73978
|
+
data,
|
|
73979
|
+
};
|
|
73980
|
+
}
|
|
73981
|
+
attributes.push({
|
|
73982
|
+
type: 'mdxJsxAttribute',
|
|
73983
|
+
name,
|
|
73984
|
+
value,
|
|
73985
|
+
});
|
|
73986
|
+
});
|
|
73987
|
+
return attributes;
|
|
73988
|
+
};
|
|
73989
|
+
/**
|
|
73990
|
+
* Checks if a named export exists in the MDX tree. Accepts either an mdast or
|
|
73991
|
+
* a hast tree.
|
|
73992
|
+
*
|
|
73993
|
+
* example:
|
|
73994
|
+
* ```
|
|
73995
|
+
* const mdx = `export const Foo = 'bar';`
|
|
73996
|
+
*
|
|
73997
|
+
* hasNamedExport(mdast(mdx), 'Foo') => true
|
|
73998
|
+
* ```
|
|
73999
|
+
*
|
|
74000
|
+
*/
|
|
74001
|
+
const hasNamedExport = (tree, name) => {
|
|
74002
|
+
let hasExport = false;
|
|
74003
|
+
// eslint-disable-next-line consistent-return
|
|
74004
|
+
visit(tree, 'mdxjsEsm', node => {
|
|
74005
|
+
if ('declaration' in node.data.estree.body[0] &&
|
|
74006
|
+
node.data.estree.body[0].declaration.type === 'VariableDeclaration') {
|
|
74007
|
+
const { declarations } = node.data.estree.body[0].declaration;
|
|
74008
|
+
hasExport = !!declarations.find(declaration => 'name' in declaration.id && declaration.id.name === name);
|
|
74009
|
+
return hasExport ? EXIT : CONTINUE;
|
|
74010
|
+
}
|
|
74011
|
+
});
|
|
74012
|
+
return hasExport;
|
|
74013
|
+
};
|
|
74014
|
+
/* Example mdast structures to find first export name in a mdxjsEsm node:
|
|
74015
|
+
There are three types of export declarations that we need to consider:
|
|
74016
|
+
1. VARIABLE DECLARATION
|
|
74017
|
+
"type": "mdxjsEsm",
|
|
74018
|
+
"value": "export const Foo = () => <div>Hello world</div>\nexport const Bar = () => <div>hello darkness my old friend</div>",
|
|
74019
|
+
"data": {
|
|
74020
|
+
"estree": {
|
|
74021
|
+
"type": "Program",
|
|
74022
|
+
"body": [
|
|
74023
|
+
{
|
|
74024
|
+
"type": "ExportNamedDeclaration",
|
|
74025
|
+
"declaration": {
|
|
74026
|
+
"type": "VariableDeclaration",
|
|
74027
|
+
"declarations": [
|
|
74028
|
+
{
|
|
74029
|
+
"type": "VariableDeclarator",
|
|
74030
|
+
"id": {
|
|
74031
|
+
"type": "Identifier",
|
|
74032
|
+
"name": "Foo" // --------> This is the export name
|
|
74033
|
+
},
|
|
74034
|
+
...
|
|
74035
|
+
|
|
74036
|
+
2/3. FUNCTION DECLARATION & CLASS DECLARATION
|
|
74037
|
+
"estree": {
|
|
74038
|
+
"type": "Program",
|
|
74039
|
+
"body": [
|
|
74040
|
+
{
|
|
74041
|
+
"type": "ExportNamedDeclaration",
|
|
74042
|
+
"declaration": {
|
|
74043
|
+
"type": "ClassDeclaration" | "FunctionDeclaration",
|
|
74044
|
+
"id": {
|
|
74045
|
+
"type": "Identifier",
|
|
74046
|
+
"name": "Foo" // --------> This is the export name
|
|
74047
|
+
},
|
|
74048
|
+
*/
|
|
74049
|
+
const getExports = (tree) => {
|
|
74050
|
+
const set = new Set();
|
|
74051
|
+
visit(tree, isMDXEsm, (node) => {
|
|
74052
|
+
// Once inside an mdxjsEsm node, we need to check for one or more declared exports within data.estree.body
|
|
74053
|
+
// This is because single newlines \n are not considered as a new block, so there may be more than one export statement in a single mdxjsEsm node
|
|
74054
|
+
const body = node.data?.estree.body;
|
|
74055
|
+
if (!body)
|
|
74056
|
+
return;
|
|
74057
|
+
body.forEach(child => {
|
|
74058
|
+
if (child.type === 'ExportNamedDeclaration') {
|
|
74059
|
+
// There are three types of ExportNamedDeclaration that we need to consider: VariableDeclaration, FunctionDeclaration, ClassDeclaration
|
|
74060
|
+
const declaration = child.declaration;
|
|
74061
|
+
// FunctionDeclaration and ClassDeclaration have the same structure
|
|
74062
|
+
if (declaration.type !== 'VariableDeclaration') {
|
|
74063
|
+
// Note: declaration.id.type is always 'Identifier' for FunctionDeclarations and ClassDeclarations
|
|
74064
|
+
set.add(declaration.id.name);
|
|
74065
|
+
}
|
|
74066
|
+
else {
|
|
74067
|
+
declaration.declarations.forEach(dec => {
|
|
74068
|
+
const id = dec.id;
|
|
74069
|
+
if (id.type === 'Identifier') {
|
|
74070
|
+
set.add(id.name);
|
|
74071
|
+
}
|
|
74072
|
+
});
|
|
74073
|
+
}
|
|
74074
|
+
}
|
|
74075
|
+
});
|
|
74076
|
+
});
|
|
74077
|
+
return Array.from(set);
|
|
74078
|
+
};
|
|
74079
|
+
|
|
74080
|
+
;// ./processor/compile/compatibility.ts
|
|
74081
|
+
|
|
74082
|
+
|
|
74083
|
+
|
|
74084
|
+
/*
|
|
74085
|
+
* Converts a (remark < v9) html node to a JSX string.
|
|
74086
|
+
*
|
|
74087
|
+
* First we replace html comments with the JSX equivalent. Then, we parse that
|
|
74088
|
+
* as html, and serialize it back as xml!
|
|
74089
|
+
*
|
|
74090
|
+
*/
|
|
74091
|
+
const compileHtml = (node) => {
|
|
74092
|
+
const string = node.value.replaceAll(/<!--(.*)-->/gms, '{/*$1*/}');
|
|
74093
|
+
return string;
|
|
74094
|
+
};
|
|
74095
|
+
const figureToImageBlock = (node) => {
|
|
74096
|
+
const { align, border, width, src, url, alt, title, ...image } = node.children.find((child) => child.type === 'image');
|
|
74097
|
+
const { className } = image.data.hProperties;
|
|
74098
|
+
const figcaption = node.children.find((child) => child.type === 'figcaption');
|
|
74099
|
+
const caption = figcaption ? toMarkdown(figcaption.children).trim() : null;
|
|
74100
|
+
const attributes = {
|
|
74101
|
+
...(align && { align }),
|
|
74102
|
+
...(alt && { alt }),
|
|
74103
|
+
...(className && { border: className === 'border' }),
|
|
74104
|
+
...(border && { border }),
|
|
74105
|
+
...(caption && { caption }),
|
|
74106
|
+
...(title && { title }),
|
|
74107
|
+
...(width && { width }),
|
|
74108
|
+
src: src || url,
|
|
74109
|
+
};
|
|
74110
|
+
return `<Image ${formatProps(attributes)} />`;
|
|
74111
|
+
};
|
|
74112
|
+
const embedToEmbedBlock = (node) => {
|
|
74113
|
+
const { html, ...embed } = node.data.hProperties;
|
|
74114
|
+
const attributes = {
|
|
74115
|
+
...embed,
|
|
74116
|
+
...(html && { html: encodeURIComponent(html) }),
|
|
74117
|
+
};
|
|
74118
|
+
return `<Embed ${formatProps(attributes)} />`;
|
|
74119
|
+
};
|
|
74120
|
+
const compatibility = (node) => {
|
|
74121
|
+
switch (node.type) {
|
|
74122
|
+
case NodeTypes.glossary: {
|
|
74123
|
+
// Glossary terms will no longer be serialized as special node types in the Editor but we want to ensure that we compile historical
|
|
74124
|
+
// data correctly
|
|
74125
|
+
const term = node.data?.hProperties?.term || node.children[0].value;
|
|
74126
|
+
return `<Glossary>${term}</Glossary>`;
|
|
74127
|
+
}
|
|
74128
|
+
case NodeTypes.reusableContent:
|
|
74129
|
+
return `<${node.tag} />`;
|
|
74130
|
+
case 'html':
|
|
74131
|
+
return compileHtml(node);
|
|
74132
|
+
case 'escape':
|
|
74133
|
+
return `\\${node.value}`;
|
|
74134
|
+
case 'figure':
|
|
74135
|
+
return figureToImageBlock(node);
|
|
74136
|
+
case 'embed':
|
|
74137
|
+
return embedToEmbedBlock(node);
|
|
74138
|
+
case 'i':
|
|
74139
|
+
return `:${node.data.hProperties.className[1]}:`;
|
|
74140
|
+
case 'yaml':
|
|
74141
|
+
return `---\n${node.value}\n---`;
|
|
74142
|
+
default:
|
|
74143
|
+
throw new Error('Unhandled node type!');
|
|
74144
|
+
}
|
|
74145
|
+
};
|
|
74146
|
+
/* harmony default export */ const compile_compatibility = (compatibility);
|
|
74147
|
+
|
|
74148
|
+
;// ./processor/compile/gemoji.ts
|
|
74149
|
+
const gemoji = (node) => `:${node.name}:`;
|
|
74150
|
+
/* harmony default export */ const compile_gemoji = (gemoji);
|
|
74151
|
+
|
|
74152
|
+
;// ./processor/compile/variable.ts
|
|
74153
|
+
const variable = (node) => `{user.${node.data?.hProperties?.name || ''}}`;
|
|
74154
|
+
/* harmony default export */ const compile_variable = (variable);
|
|
74155
|
+
|
|
74156
|
+
;// ./processor/transform/extract-text.ts
|
|
74157
|
+
/**
|
|
74158
|
+
* Extracts text content from a single AST node recursively.
|
|
74159
|
+
* Works with both MDAST and HAST-like node structures.
|
|
74160
|
+
*
|
|
74161
|
+
* Placed this outside of the utils.ts file to avoid circular dependencies.
|
|
74162
|
+
*
|
|
74163
|
+
* @param node - The node to extract text from (can be MDAST Node or HAST-like structure)
|
|
74164
|
+
* @returns The concatenated text content
|
|
74165
|
+
*/
|
|
74166
|
+
const extractText = (node) => {
|
|
74167
|
+
if (typeof node.value === 'string') {
|
|
74168
|
+
return node.value;
|
|
74169
|
+
}
|
|
74170
|
+
// When a blockquote contains only an image (no text), treat it as having content
|
|
74171
|
+
// so the blockquote is no longer treated as empty and preserved correctly.
|
|
74172
|
+
if (node.type === 'image') {
|
|
74173
|
+
return typeof node.alt === 'string' && node.alt ? node.alt : '[image]';
|
|
74174
|
+
}
|
|
74175
|
+
if (node.children && Array.isArray(node.children)) {
|
|
74176
|
+
return node.children
|
|
74177
|
+
.map(child => {
|
|
74178
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
74179
|
+
return extractText(child);
|
|
74180
|
+
}
|
|
74181
|
+
return '';
|
|
74182
|
+
})
|
|
74183
|
+
.join('');
|
|
74184
|
+
}
|
|
74185
|
+
return '';
|
|
74186
|
+
};
|
|
74187
|
+
|
|
74188
|
+
;// ./processor/transform/callouts.ts
|
|
74189
|
+
|
|
74190
|
+
|
|
74191
|
+
|
|
74192
|
+
|
|
74193
|
+
|
|
74194
|
+
|
|
74195
|
+
|
|
74196
|
+
|
|
74197
|
+
|
|
74198
|
+
|
|
74199
|
+
|
|
74200
|
+
|
|
74201
|
+
|
|
74202
|
+
|
|
74203
|
+
|
|
74204
|
+
const titleParser = unified().use(remarkParse).use(remarkGfm);
|
|
74205
|
+
// The title paragraph may contain custom AST nodes that `toMarkdown` doesn't
|
|
74206
|
+
// natively understand
|
|
74207
|
+
const toMarkdownExtensions = [
|
|
74208
|
+
gfmToMarkdown(),
|
|
74209
|
+
// For mdx variable syntaxes (e.g., {user.name})
|
|
74210
|
+
mdxExpressionToMarkdown(),
|
|
74211
|
+
// Important: This is required and would crash the parser if there's no variable, gemoji, or fa-icon node handler
|
|
74212
|
+
{
|
|
74213
|
+
handlers: {
|
|
74214
|
+
[NodeTypes.variable]: compile_variable,
|
|
74215
|
+
[NodeTypes.emoji]: compile_gemoji,
|
|
74216
|
+
[NodeTypes.i]: compile_compatibility,
|
|
74217
|
+
},
|
|
74218
|
+
},
|
|
74219
|
+
];
|
|
74220
|
+
const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
|
|
74221
|
+
const findFirst = (node) => {
|
|
74222
|
+
if ('children' in node)
|
|
74223
|
+
return findFirst(node.children[0]);
|
|
74224
|
+
if (node.type === 'text')
|
|
74225
|
+
return node;
|
|
74226
|
+
return null;
|
|
74227
|
+
};
|
|
74228
|
+
const wrapHeading = (node) => {
|
|
74229
|
+
const firstChild = node.children[0];
|
|
74230
|
+
return {
|
|
74231
|
+
type: 'heading',
|
|
74232
|
+
depth: 3,
|
|
74233
|
+
children: ('children' in firstChild ? firstChild.children : []),
|
|
74234
|
+
position: {
|
|
74235
|
+
start: firstChild.position.start,
|
|
74236
|
+
end: firstChild.position.end,
|
|
74237
|
+
},
|
|
74238
|
+
};
|
|
74239
|
+
};
|
|
74240
|
+
/**
|
|
74241
|
+
* Checks if a blockquote matches the expected callout structure:
|
|
74242
|
+
* blockquote > paragraph > text node
|
|
74243
|
+
*/
|
|
74244
|
+
const isCalloutStructure = (node) => {
|
|
74245
|
+
const firstChild = node.children?.[0];
|
|
74246
|
+
if (!firstChild || firstChild.type !== 'paragraph')
|
|
74247
|
+
return false;
|
|
74248
|
+
if (!('children' in firstChild))
|
|
74249
|
+
return false;
|
|
74250
|
+
const firstTextChild = firstChild.children?.[0];
|
|
74251
|
+
return firstTextChild?.type === 'text';
|
|
74252
|
+
};
|
|
74253
|
+
/**
|
|
74254
|
+
* Finds the first text node containing a newline in a paragraph's children.
|
|
74255
|
+
* Returns the index and the newline position within that text node.
|
|
74256
|
+
*/
|
|
74257
|
+
const findNewlineInParagraph = (paragraph) => {
|
|
74258
|
+
for (let i = 0; i < paragraph.children.length; i += 1) {
|
|
74259
|
+
const child = paragraph.children[i];
|
|
74260
|
+
if (child.type === 'text' && typeof child.value === 'string') {
|
|
74261
|
+
const newlineIndex = child.value.indexOf('\n');
|
|
74262
|
+
if (newlineIndex !== -1) {
|
|
74263
|
+
return { index: i, newlineIndex };
|
|
74264
|
+
}
|
|
74265
|
+
}
|
|
74266
|
+
}
|
|
74267
|
+
return null;
|
|
74268
|
+
};
|
|
74269
|
+
/**
|
|
74270
|
+
* Splits a paragraph at the first newline, separating heading content (before \n)
|
|
74271
|
+
* from body content (after \n). Mutates the paragraph to contain only heading children.
|
|
74272
|
+
*/
|
|
74273
|
+
const splitParagraphAtNewline = (paragraph) => {
|
|
74274
|
+
const splitPoint = findNewlineInParagraph(paragraph);
|
|
74275
|
+
if (!splitPoint)
|
|
74276
|
+
return null;
|
|
74277
|
+
const { index, newlineIndex } = splitPoint;
|
|
74278
|
+
const originalChildren = paragraph.children;
|
|
74279
|
+
const textNode = originalChildren[index];
|
|
74280
|
+
const beforeNewline = textNode.value.slice(0, newlineIndex);
|
|
74281
|
+
const afterNewline = textNode.value.slice(newlineIndex + 1);
|
|
74282
|
+
// Split paragraph: heading = children[0..index-1] + text before newline
|
|
74283
|
+
const headingChildren = originalChildren.slice(0, index);
|
|
74284
|
+
if (beforeNewline.length > 0 || headingChildren.length === 0) {
|
|
74285
|
+
headingChildren.push({ type: 'text', value: beforeNewline });
|
|
74286
|
+
}
|
|
74287
|
+
paragraph.children = headingChildren;
|
|
74288
|
+
// Body = text after newline + remaining children from original array
|
|
74289
|
+
const bodyChildren = [];
|
|
74290
|
+
if (afterNewline.length > 0) {
|
|
74291
|
+
bodyChildren.push({ type: 'text', value: afterNewline });
|
|
74292
|
+
}
|
|
74293
|
+
bodyChildren.push(...originalChildren.slice(index + 1));
|
|
74294
|
+
return bodyChildren.length > 0 ? bodyChildren : null;
|
|
74295
|
+
};
|
|
74296
|
+
/**
|
|
74297
|
+
* Removes the icon/match prefix from the first text node in a paragraph.
|
|
74298
|
+
* This is needed to clean up the raw AST after we've extracted the icon.
|
|
74299
|
+
*/
|
|
74300
|
+
const removeIconPrefix = (paragraph, prefixLength) => {
|
|
74301
|
+
const firstTextNode = findFirst(paragraph);
|
|
74302
|
+
if (firstTextNode && 'value' in firstTextNode && typeof firstTextNode.value === 'string') {
|
|
74303
|
+
firstTextNode.value = firstTextNode.value.slice(prefixLength);
|
|
74304
|
+
}
|
|
74305
|
+
};
|
|
74306
|
+
const processBlockquote = (node, index, parent, isMdxish = false) => {
|
|
74307
|
+
if (!isCalloutStructure(node)) {
|
|
74308
|
+
// Only stringify empty blockquotes (no extractable text content)
|
|
74309
|
+
// Preserve blockquotes with actual content (e.g., headings, lists, etc.)
|
|
74310
|
+
const content = extractText(node);
|
|
74311
|
+
const isEmpty = !content || content.trim() === '';
|
|
74312
|
+
if (isEmpty && index !== undefined && parent) {
|
|
74313
|
+
const textNode = {
|
|
74314
|
+
type: 'text',
|
|
74315
|
+
value: '>',
|
|
74316
|
+
};
|
|
74317
|
+
const paragraphNode = {
|
|
74318
|
+
type: 'paragraph',
|
|
74319
|
+
children: [textNode],
|
|
74320
|
+
position: node.position,
|
|
74321
|
+
};
|
|
74322
|
+
parent.children.splice(index, 1, paragraphNode);
|
|
74323
|
+
}
|
|
74324
|
+
return;
|
|
74325
|
+
}
|
|
74326
|
+
// isCalloutStructure ensures node.children[0] is a Paragraph with children
|
|
74327
|
+
const firstParagraph = node.children[0];
|
|
74328
|
+
const startText = lib_plain(firstParagraph).toString();
|
|
74329
|
+
const [match, icon] = startText.match(callouts_regex) || [];
|
|
74330
|
+
const firstParagraphOriginalEnd = firstParagraph.position.end;
|
|
74331
|
+
if (icon && match) {
|
|
74332
|
+
// Handle cases where heading and body are on the same line separated by a newline.
|
|
74333
|
+
// Example: "> ⚠️ **Bold heading**\nBody text here"
|
|
74334
|
+
const bodyChildren = splitParagraphAtNewline(firstParagraph);
|
|
74335
|
+
const didSplit = bodyChildren !== null;
|
|
74336
|
+
removeIconPrefix(firstParagraph, match.length);
|
|
74337
|
+
const firstText = findFirst(firstParagraph);
|
|
74338
|
+
const rawValue = firstText?.value ?? '';
|
|
74339
|
+
const hasContent = rawValue.trim().length > 0 || firstParagraph.children.length > 1;
|
|
74340
|
+
const empty = !hasContent;
|
|
74341
|
+
const theme = themes[icon] || 'default';
|
|
74342
|
+
if (hasContent || didSplit) {
|
|
74343
|
+
const headingMatch = rawValue.match(/^(#{1,6})\s*/);
|
|
74344
|
+
// # heading syntax is handled via direct AST manipulation so we can
|
|
74345
|
+
// set the depth while preserving the original inline children (bold, etc.)
|
|
74346
|
+
if (headingMatch) {
|
|
74347
|
+
firstText.value = rawValue.slice(headingMatch[0].length);
|
|
74348
|
+
const heading = wrapHeading(node);
|
|
74349
|
+
heading.depth = headingMatch[1].length;
|
|
74350
|
+
node.children[0] = heading;
|
|
74351
|
+
node.children[0].position.start.offset += match.length;
|
|
74352
|
+
node.children[0].position.start.column += match.length;
|
|
74353
|
+
}
|
|
74354
|
+
else if (isMdxish) {
|
|
74355
|
+
// Block-level title re-parsing is only needed for MDXish where HTML stays
|
|
74356
|
+
// as raw nodes. In MDX, remarkMdx has already converted HTML to JSX AST
|
|
74357
|
+
// nodes which toMarkdown can't serialize — and MDX doesn't need this
|
|
74358
|
+
// block-level title handling anyway.
|
|
74359
|
+
const headingText = toMarkdown({ type: 'root', children: [firstParagraph] }, {
|
|
74360
|
+
extensions: toMarkdownExtensions,
|
|
74361
|
+
})
|
|
74362
|
+
.trim()
|
|
74363
|
+
.replace(/^\\(?=[>#+\-*])/, '');
|
|
74364
|
+
const parsedTitle = titleParser.parse(headingText);
|
|
74365
|
+
const parsedFirstChild = parsedTitle.children[0];
|
|
74366
|
+
// Block-level syntax ("> quote", "- list") produces non-paragraph nodes;
|
|
74367
|
+
// inline text parses as a paragraph and falls through to wrapHeading().
|
|
74368
|
+
if (parsedFirstChild && parsedFirstChild.type !== 'paragraph') {
|
|
74369
|
+
// Strip positions from re-parsed nodes since they're relative to the heading text, not the original source
|
|
74370
|
+
visit(parsedTitle, (n) => {
|
|
74371
|
+
delete n.position;
|
|
74372
|
+
});
|
|
74373
|
+
const heading = wrapHeading(node);
|
|
74374
|
+
heading.children = parsedTitle.children;
|
|
74375
|
+
delete heading.position;
|
|
74376
|
+
node.children[0] = heading;
|
|
74377
|
+
}
|
|
74378
|
+
else {
|
|
74379
|
+
node.children[0] = wrapHeading(node);
|
|
74380
|
+
node.children[0].position.start.offset += match.length;
|
|
74381
|
+
node.children[0].position.start.column += match.length;
|
|
74382
|
+
}
|
|
74383
|
+
}
|
|
74384
|
+
else {
|
|
74385
|
+
node.children[0] = wrapHeading(node);
|
|
74386
|
+
node.children[0].position.start.offset += match.length;
|
|
74387
|
+
node.children[0].position.start.column += match.length;
|
|
74388
|
+
}
|
|
74389
|
+
}
|
|
74390
|
+
// Insert body content as a separate paragraph after the heading
|
|
74391
|
+
if (bodyChildren) {
|
|
74392
|
+
const headingPosition = node.children[0].position;
|
|
74393
|
+
node.children.splice(1, 0, {
|
|
74394
|
+
type: 'paragraph',
|
|
74395
|
+
children: bodyChildren,
|
|
74396
|
+
...(headingPosition && firstParagraphOriginalEnd
|
|
74397
|
+
? {
|
|
74398
|
+
position: {
|
|
74399
|
+
start: headingPosition.end,
|
|
74400
|
+
end: firstParagraphOriginalEnd,
|
|
74401
|
+
},
|
|
74402
|
+
}
|
|
74403
|
+
: {}),
|
|
74404
|
+
});
|
|
74405
|
+
}
|
|
74406
|
+
Object.assign(node, {
|
|
74407
|
+
type: NodeTypes.callout,
|
|
74408
|
+
data: {
|
|
74409
|
+
hName: 'Callout',
|
|
74410
|
+
hProperties: {
|
|
74411
|
+
icon,
|
|
74412
|
+
...(empty && { empty }),
|
|
74413
|
+
theme,
|
|
74414
|
+
},
|
|
74415
|
+
},
|
|
73604
74416
|
});
|
|
73605
74417
|
}
|
|
73606
74418
|
};
|
|
@@ -90683,857 +91495,121 @@ const emojiToName = {
|
|
|
90683
91495
|
'🇾🇪': 'yemen',
|
|
90684
91496
|
'🇾🇹': 'mayotte',
|
|
90685
91497
|
'🇿🇦': 'south_africa',
|
|
90686
|
-
'🇿🇲': 'zambia',
|
|
90687
|
-
'🇿🇼': 'zimbabwe',
|
|
90688
|
-
'🏴': 'england',
|
|
90689
|
-
'🏴': 'scotland',
|
|
90690
|
-
'🏴': 'wales'
|
|
90691
|
-
}
|
|
90692
|
-
|
|
90693
|
-
;// ./lib/owlmoji.ts
|
|
90694
|
-
|
|
90695
|
-
const owlmoji = [
|
|
90696
|
-
{
|
|
90697
|
-
emoji: '', // This `emoji` property doesn't get consumed, but is required for type consistency
|
|
90698
|
-
names: ['owlbert'],
|
|
90699
|
-
tags: ['owlbert'],
|
|
90700
|
-
description: 'an owlbert for any occasion',
|
|
90701
|
-
category: 'ReadMe',
|
|
90702
|
-
},
|
|
90703
|
-
{
|
|
90704
|
-
emoji: '',
|
|
90705
|
-
names: ['owlbert-books'],
|
|
90706
|
-
tags: ['owlbert'],
|
|
90707
|
-
description: 'owlbert carrying books',
|
|
90708
|
-
category: 'ReadMe',
|
|
90709
|
-
},
|
|
90710
|
-
{
|
|
90711
|
-
emoji: '',
|
|
90712
|
-
names: ['owlbert-mask'],
|
|
90713
|
-
tags: ['owlbert'],
|
|
90714
|
-
description: 'owlbert with a respirator',
|
|
90715
|
-
category: 'ReadMe',
|
|
90716
|
-
},
|
|
90717
|
-
{
|
|
90718
|
-
emoji: '',
|
|
90719
|
-
names: ['owlbert-reading'],
|
|
90720
|
-
tags: ['owlbert'],
|
|
90721
|
-
description: 'owlbert reading',
|
|
90722
|
-
category: 'ReadMe',
|
|
90723
|
-
},
|
|
90724
|
-
{
|
|
90725
|
-
emoji: '',
|
|
90726
|
-
names: ['owlbert-thinking'],
|
|
90727
|
-
tags: ['owlbert'],
|
|
90728
|
-
description: 'owlbert thinking',
|
|
90729
|
-
category: 'ReadMe',
|
|
90730
|
-
},
|
|
90731
|
-
];
|
|
90732
|
-
const owlmojiNames = owlmoji.flatMap(emoji => emoji.names);
|
|
90733
|
-
class Owlmoji {
|
|
90734
|
-
static kind = (name) => {
|
|
90735
|
-
if (name in nameToEmoji)
|
|
90736
|
-
return 'gemoji';
|
|
90737
|
-
else if (name.match(/^fa-/))
|
|
90738
|
-
return 'fontawesome';
|
|
90739
|
-
else if (owlmojiNames.includes(name))
|
|
90740
|
-
return 'owlmoji';
|
|
90741
|
-
return null;
|
|
90742
|
-
};
|
|
90743
|
-
static nameToEmoji = nameToEmoji;
|
|
90744
|
-
static owlmoji = gemoji_gemoji.concat(owlmoji);
|
|
90745
|
-
}
|
|
90746
|
-
|
|
90747
|
-
;// ./processor/transform/gemoji+.ts
|
|
90748
|
-
|
|
90749
|
-
|
|
90750
|
-
|
|
90751
|
-
const gemoji_regex = /(?<=^|\s):(?<name>\+1|[-\w]+):/g;
|
|
90752
|
-
const gemojiReplacer = (_, name) => {
|
|
90753
|
-
switch (Owlmoji.kind(name)) {
|
|
90754
|
-
case 'gemoji': {
|
|
90755
|
-
const node = {
|
|
90756
|
-
type: NodeTypes.emoji,
|
|
90757
|
-
value: Owlmoji.nameToEmoji[name],
|
|
90758
|
-
name,
|
|
90759
|
-
};
|
|
90760
|
-
return node;
|
|
90761
|
-
}
|
|
90762
|
-
case 'fontawesome': {
|
|
90763
|
-
const node = {
|
|
90764
|
-
type: NodeTypes.i,
|
|
90765
|
-
value: name,
|
|
90766
|
-
data: {
|
|
90767
|
-
hName: 'i',
|
|
90768
|
-
hProperties: {
|
|
90769
|
-
className: ['fa-regular', name],
|
|
90770
|
-
},
|
|
90771
|
-
},
|
|
90772
|
-
};
|
|
90773
|
-
return node;
|
|
90774
|
-
}
|
|
90775
|
-
case 'owlmoji': {
|
|
90776
|
-
const node = {
|
|
90777
|
-
type: 'image',
|
|
90778
|
-
title: `:${name}:`,
|
|
90779
|
-
alt: `:${name}:`,
|
|
90780
|
-
url: `/public/img/emojis/${name}.png`,
|
|
90781
|
-
data: {
|
|
90782
|
-
hProperties: {
|
|
90783
|
-
className: 'emoji',
|
|
90784
|
-
align: 'absmiddle',
|
|
90785
|
-
height: '20',
|
|
90786
|
-
width: '20',
|
|
90787
|
-
},
|
|
90788
|
-
},
|
|
90789
|
-
};
|
|
90790
|
-
return node;
|
|
90791
|
-
}
|
|
90792
|
-
default:
|
|
90793
|
-
return false;
|
|
90794
|
-
}
|
|
90795
|
-
};
|
|
90796
|
-
const gemojiTransformer = () => (tree) => {
|
|
90797
|
-
findAndReplace(tree, [gemoji_regex, gemojiReplacer]);
|
|
90798
|
-
return tree;
|
|
90799
|
-
};
|
|
90800
|
-
/* harmony default export */ const gemoji_ = (gemojiTransformer);
|
|
90801
|
-
|
|
90802
|
-
;// ./lib/mdast.ts
|
|
90803
|
-
|
|
90804
|
-
const mdast = (text, opts = {}) => {
|
|
90805
|
-
const processor = ast_processor(opts);
|
|
90806
|
-
const tree = processor.parse(text);
|
|
90807
|
-
return processor.runSync(tree);
|
|
90808
|
-
};
|
|
90809
|
-
/* harmony default export */ const lib_mdast = (mdast);
|
|
90810
|
-
|
|
90811
|
-
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
90812
|
-
/**
|
|
90813
|
-
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
90814
|
-
*
|
|
90815
|
-
* @param content - The markdown content to process
|
|
90816
|
-
* @returns Object containing protected content and arrays of original code blocks
|
|
90817
|
-
* @example
|
|
90818
|
-
* ```typescript
|
|
90819
|
-
* const input = 'Text with `inline code` and ```fenced block```';
|
|
90820
|
-
* protectCodeBlocks(input)
|
|
90821
|
-
* // Returns: {
|
|
90822
|
-
* // protectedCode: {
|
|
90823
|
-
* // codeBlocks: ['```fenced block```'],
|
|
90824
|
-
* // inlineCode: ['`inline code`']
|
|
90825
|
-
* // },
|
|
90826
|
-
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
90827
|
-
* // }
|
|
90828
|
-
* ```
|
|
90829
|
-
*/
|
|
90830
|
-
function protectCodeBlocks(content) {
|
|
90831
|
-
const codeBlocks = [];
|
|
90832
|
-
const inlineCode = [];
|
|
90833
|
-
let protectedContent = '';
|
|
90834
|
-
let remaining = content;
|
|
90835
|
-
let codeBlockStart = remaining.indexOf('```');
|
|
90836
|
-
while (codeBlockStart !== -1) {
|
|
90837
|
-
protectedContent += remaining.slice(0, codeBlockStart);
|
|
90838
|
-
remaining = remaining.slice(codeBlockStart);
|
|
90839
|
-
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
90840
|
-
if (codeBlockEnd === -1) {
|
|
90841
|
-
break;
|
|
90842
|
-
}
|
|
90843
|
-
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
90844
|
-
const index = codeBlocks.length;
|
|
90845
|
-
codeBlocks.push(match);
|
|
90846
|
-
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
90847
|
-
remaining = remaining.slice(codeBlockEnd + 3);
|
|
90848
|
-
codeBlockStart = remaining.indexOf('```');
|
|
90849
|
-
}
|
|
90850
|
-
protectedContent += remaining;
|
|
90851
|
-
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
90852
|
-
const index = inlineCode.length;
|
|
90853
|
-
inlineCode.push(match);
|
|
90854
|
-
return `___INLINE_CODE_${index}___`;
|
|
90855
|
-
});
|
|
90856
|
-
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
90857
|
-
}
|
|
90858
|
-
/**
|
|
90859
|
-
* Restores inline code by replacing placeholders with original content.
|
|
90860
|
-
*
|
|
90861
|
-
* @param content - Content with inline code placeholders
|
|
90862
|
-
* @param protectedCode - The protected code arrays
|
|
90863
|
-
* @returns Content with inline code restored
|
|
90864
|
-
*/
|
|
90865
|
-
function restoreInlineCode(content, protectedCode) {
|
|
90866
|
-
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
90867
|
-
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
90868
|
-
});
|
|
90869
|
-
}
|
|
90870
|
-
/**
|
|
90871
|
-
* Restores fenced code blocks by replacing placeholders with original content.
|
|
90872
|
-
*
|
|
90873
|
-
* @param content - Content with code block placeholders
|
|
90874
|
-
* @param protectedCode - The protected code arrays
|
|
90875
|
-
* @returns Content with code blocks restored
|
|
90876
|
-
*/
|
|
90877
|
-
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
90878
|
-
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
90879
|
-
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
90880
|
-
});
|
|
90881
|
-
}
|
|
90882
|
-
/**
|
|
90883
|
-
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
90884
|
-
*
|
|
90885
|
-
* @param content - Content with code placeholders
|
|
90886
|
-
* @param protectedCode - The protected code arrays
|
|
90887
|
-
* @returns Content with all code blocks and inline code restored
|
|
90888
|
-
* @example
|
|
90889
|
-
* ```typescript
|
|
90890
|
-
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
90891
|
-
* const protectedCode = {
|
|
90892
|
-
* codeBlocks: ['```js\ncode\n```'],
|
|
90893
|
-
* inlineCode: ['`inline`']
|
|
90894
|
-
* };
|
|
90895
|
-
* restoreCodeBlocks(content, protectedCode)
|
|
90896
|
-
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
90897
|
-
* ```
|
|
90898
|
-
*/
|
|
90899
|
-
function restoreCodeBlocks(content, protectedCode) {
|
|
90900
|
-
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
90901
|
-
restored = restoreInlineCode(restored, protectedCode);
|
|
90902
|
-
return restored;
|
|
90903
|
-
}
|
|
90904
|
-
|
|
90905
|
-
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
90906
|
-
|
|
90907
|
-
// Base64 encode (Node.js + browser compatible)
|
|
90908
|
-
function base64Encode(str) {
|
|
90909
|
-
if (typeof Buffer !== 'undefined') {
|
|
90910
|
-
return Buffer.from(str, 'utf-8').toString('base64');
|
|
90911
|
-
}
|
|
90912
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
90913
|
-
}
|
|
90914
|
-
// Base64 decode (Node.js + browser compatible)
|
|
90915
|
-
function base64Decode(str) {
|
|
90916
|
-
if (typeof Buffer !== 'undefined') {
|
|
90917
|
-
return Buffer.from(str, 'base64').toString('utf-8');
|
|
90918
|
-
}
|
|
90919
|
-
return decodeURIComponent(escape(atob(str)));
|
|
90920
|
-
}
|
|
90921
|
-
function escapeHtmlAttribute(value) {
|
|
90922
|
-
return value
|
|
90923
|
-
.replace(/&/g, '&')
|
|
90924
|
-
.replace(/"/g, '"')
|
|
90925
|
-
.replace(/</g, '<')
|
|
90926
|
-
.replace(/>/g, '>')
|
|
90927
|
-
.replace(/\n/g, ' ');
|
|
90928
|
-
}
|
|
90929
|
-
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
90930
|
-
// Using a prefix that won't conflict with regular string values
|
|
90931
|
-
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
90932
|
-
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
90933
|
-
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
90934
|
-
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
90935
|
-
/**
|
|
90936
|
-
* Evaluates a JavaScript expression using context variables.
|
|
90937
|
-
*
|
|
90938
|
-
* @param expression
|
|
90939
|
-
* @param context
|
|
90940
|
-
* @returns The evaluated result
|
|
90941
|
-
* @example
|
|
90942
|
-
* ```typescript
|
|
90943
|
-
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
90944
|
-
* evaluateExpression('baseUrl + path', context)
|
|
90945
|
-
* // Returns: 'https://example.com/api'
|
|
90946
|
-
* ```
|
|
90947
|
-
*/
|
|
90948
|
-
function evaluateExpression(expression, context) {
|
|
90949
|
-
const contextKeys = Object.keys(context);
|
|
90950
|
-
const contextValues = Object.values(context);
|
|
90951
|
-
// eslint-disable-next-line no-new-func
|
|
90952
|
-
const func = new Function(...contextKeys, `return ${expression}`);
|
|
90953
|
-
return func(...contextValues);
|
|
90954
|
-
}
|
|
90955
|
-
/**
|
|
90956
|
-
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
90957
|
-
*
|
|
90958
|
-
* @param content
|
|
90959
|
-
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
90960
|
-
* @example
|
|
90961
|
-
* ```typescript
|
|
90962
|
-
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
90963
|
-
* protectHTMLBlockContent(input)
|
|
90964
|
-
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
90965
|
-
* ```
|
|
90966
|
-
*/
|
|
90967
|
-
function protectHTMLBlockContent(content) {
|
|
90968
|
-
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
90969
|
-
const encoded = base64Encode(templateContent);
|
|
90970
|
-
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
90971
|
-
});
|
|
90972
|
-
}
|
|
90973
|
-
/**
|
|
90974
|
-
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
90975
|
-
*
|
|
90976
|
-
* @param content
|
|
90977
|
-
* @returns Content with JSX comments removed
|
|
90978
|
-
* @example
|
|
90979
|
-
* ```typescript
|
|
90980
|
-
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
90981
|
-
* // Returns: 'Text more text'
|
|
90982
|
-
* ```
|
|
90983
|
-
*/
|
|
90984
|
-
function removeJSXComments(content) {
|
|
90985
|
-
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
90986
|
-
}
|
|
90987
|
-
/**
|
|
90988
|
-
* Extracts content between balanced braces, handling nested braces.
|
|
90989
|
-
*
|
|
90990
|
-
* @param content
|
|
90991
|
-
* @param start
|
|
90992
|
-
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
90993
|
-
* @example
|
|
90994
|
-
* ```typescript
|
|
90995
|
-
* const input = 'foo{bar{baz}qux}end';
|
|
90996
|
-
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
90997
|
-
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
90998
|
-
* ```
|
|
90999
|
-
*/
|
|
91000
|
-
function extractBalancedBraces(content, start) {
|
|
91001
|
-
let depth = 1;
|
|
91002
|
-
let pos = start;
|
|
91003
|
-
while (pos < content.length && depth > 0) {
|
|
91004
|
-
const char = content[pos];
|
|
91005
|
-
if (char === '{')
|
|
91006
|
-
depth += 1;
|
|
91007
|
-
else if (char === '}')
|
|
91008
|
-
depth -= 1;
|
|
91009
|
-
pos += 1;
|
|
91010
|
-
}
|
|
91011
|
-
if (depth !== 0)
|
|
91012
|
-
return null;
|
|
91013
|
-
return { content: content.slice(start, pos - 1), end: pos };
|
|
91014
|
-
}
|
|
91015
|
-
/**
|
|
91016
|
-
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
91017
|
-
* Handles three cases:
|
|
91018
|
-
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
91019
|
-
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
91020
|
-
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
91021
|
-
*
|
|
91022
|
-
*/
|
|
91023
|
-
function escapeProblematicBraces(content) {
|
|
91024
|
-
// Skip HTML elements — their content should never be escaped because
|
|
91025
|
-
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
91026
|
-
const htmlElements = [];
|
|
91027
|
-
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
91028
|
-
const idx = htmlElements.length;
|
|
91029
|
-
htmlElements.push(match);
|
|
91030
|
-
return `___HTML_ELEM_${idx}___`;
|
|
91031
|
-
});
|
|
91032
|
-
const toEscape = new Set();
|
|
91033
|
-
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
91034
|
-
const chars = Array.from(safe);
|
|
91035
|
-
let strDelim = null;
|
|
91036
|
-
let strEscaped = false;
|
|
91037
|
-
// Stack of open braces with their state
|
|
91038
|
-
const openStack = [];
|
|
91039
|
-
// Track position of last newline (outside strings) to detect blank lines
|
|
91040
|
-
let lastNewlinePos = -2; // -2 means no recent newline
|
|
91041
|
-
for (let i = 0; i < chars.length; i += 1) {
|
|
91042
|
-
const ch = chars[i];
|
|
91043
|
-
// Track string delimiters inside expressions to ignore braces within them
|
|
91044
|
-
if (openStack.length > 0) {
|
|
91045
|
-
if (strDelim) {
|
|
91046
|
-
if (strEscaped)
|
|
91047
|
-
strEscaped = false;
|
|
91048
|
-
else if (ch === '\\')
|
|
91049
|
-
strEscaped = true;
|
|
91050
|
-
else if (ch === strDelim)
|
|
91051
|
-
strDelim = null;
|
|
91052
|
-
// eslint-disable-next-line no-continue
|
|
91053
|
-
continue;
|
|
91054
|
-
}
|
|
91055
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
91056
|
-
strDelim = ch;
|
|
91057
|
-
// eslint-disable-next-line no-continue
|
|
91058
|
-
continue;
|
|
91059
|
-
}
|
|
91060
|
-
// Track newlines to detect blank lines (paragraph boundaries)
|
|
91061
|
-
if (ch === '\n') {
|
|
91062
|
-
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
91063
|
-
if (lastNewlinePos >= 0) {
|
|
91064
|
-
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
91065
|
-
if (/^[ \t]*$/.test(between)) {
|
|
91066
|
-
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
91067
|
-
openStack.forEach(entry => {
|
|
91068
|
-
entry.hasBlankLine = true;
|
|
91069
|
-
});
|
|
91070
|
-
}
|
|
91071
|
-
}
|
|
91072
|
-
lastNewlinePos = i;
|
|
91073
|
-
}
|
|
91074
|
-
}
|
|
91075
|
-
// Skip already-escaped braces (count preceding backslashes)
|
|
91076
|
-
if (ch === '{' || ch === '}') {
|
|
91077
|
-
let bs = 0;
|
|
91078
|
-
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
91079
|
-
bs += 1;
|
|
91080
|
-
if (bs % 2 === 1) {
|
|
91081
|
-
// eslint-disable-next-line no-continue
|
|
91082
|
-
continue;
|
|
91083
|
-
}
|
|
91084
|
-
}
|
|
91085
|
-
if (ch === '{') {
|
|
91086
|
-
openStack.push({ pos: i, hasBlankLine: false });
|
|
91087
|
-
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
91088
|
-
}
|
|
91089
|
-
else if (ch === '}') {
|
|
91090
|
-
if (openStack.length > 0) {
|
|
91091
|
-
const entry = openStack.pop();
|
|
91092
|
-
// If expression spans paragraph boundary, escape both braces
|
|
91093
|
-
if (entry.hasBlankLine) {
|
|
91094
|
-
toEscape.add(entry.pos);
|
|
91095
|
-
toEscape.add(i);
|
|
91096
|
-
}
|
|
91097
|
-
}
|
|
91098
|
-
else {
|
|
91099
|
-
// Unbalanced closing brace (no matching open)
|
|
91100
|
-
toEscape.add(i);
|
|
91101
|
-
}
|
|
91102
|
-
}
|
|
91103
|
-
}
|
|
91104
|
-
// Any remaining open braces are unbalanced
|
|
91105
|
-
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
91106
|
-
// If there are no problematic braces, return safe content as-is;
|
|
91107
|
-
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
91108
|
-
let result = toEscape.size === 0
|
|
91109
|
-
? safe
|
|
91110
|
-
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
91111
|
-
// Restore HTML elements
|
|
91112
|
-
if (htmlElements.length > 0) {
|
|
91113
|
-
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
91114
|
-
}
|
|
91115
|
-
return result;
|
|
91116
|
-
}
|
|
91117
|
-
/**
|
|
91118
|
-
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
91119
|
-
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
91120
|
-
*
|
|
91121
|
-
* @param content
|
|
91122
|
-
* @param context
|
|
91123
|
-
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
91124
|
-
* @example
|
|
91125
|
-
* ```typescript
|
|
91126
|
-
* const context = { baseUrl: 'https://example.com' };
|
|
91127
|
-
* const input = '<a href={baseUrl}>Link</a>';
|
|
91128
|
-
* evaluateAttributeExpressions(input, context)
|
|
91129
|
-
* // Returns: '<a href="https://example.com">Link</a>'
|
|
91130
|
-
* ```
|
|
91131
|
-
*/
|
|
91132
|
-
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
91133
|
-
const attrStartRegex = /(\w+)=\{/g;
|
|
91134
|
-
let result = '';
|
|
91135
|
-
let lastEnd = 0;
|
|
91136
|
-
let match = attrStartRegex.exec(content);
|
|
91137
|
-
while (match !== null) {
|
|
91138
|
-
const attributeName = match[1];
|
|
91139
|
-
const braceStart = match.index + match[0].length;
|
|
91140
|
-
const extracted = extractBalancedBraces(content, braceStart);
|
|
91141
|
-
if (extracted) {
|
|
91142
|
-
// The expression might contain template literals in MDX component tag props
|
|
91143
|
-
// E.g. <Component greeting={`Hello World!`} />
|
|
91144
|
-
// that is marked as inline code. So we need to restore the inline codes
|
|
91145
|
-
// in the expression to evaluate it
|
|
91146
|
-
let expression = extracted.content;
|
|
91147
|
-
if (protectedCode) {
|
|
91148
|
-
expression = restoreInlineCode(expression, protectedCode);
|
|
91149
|
-
}
|
|
91150
|
-
const fullMatchEnd = extracted.end;
|
|
91151
|
-
result += content.slice(lastEnd, match.index);
|
|
91152
|
-
try {
|
|
91153
|
-
const evalResult = evaluateExpression(expression, context);
|
|
91154
|
-
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
91155
|
-
if (attributeName === 'style') {
|
|
91156
|
-
const cssString = Object.entries(evalResult)
|
|
91157
|
-
.map(([key, value]) => {
|
|
91158
|
-
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
91159
|
-
return `${cssKey}: ${value}`;
|
|
91160
|
-
})
|
|
91161
|
-
.join('; ');
|
|
91162
|
-
result += `style="${cssString}"`;
|
|
91163
|
-
}
|
|
91164
|
-
else {
|
|
91165
|
-
// These are arrays / objects attribute values
|
|
91166
|
-
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
91167
|
-
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
91168
|
-
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
91169
|
-
result += `${attributeName}="${jsonValue}"`;
|
|
91170
|
-
}
|
|
91171
|
-
}
|
|
91172
|
-
else if (attributeName === 'className') {
|
|
91173
|
-
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
91174
|
-
// This will be restored later in the pipeline
|
|
91175
|
-
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
91176
|
-
}
|
|
91177
|
-
else {
|
|
91178
|
-
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
91179
|
-
}
|
|
91180
|
-
}
|
|
91181
|
-
catch (_error) {
|
|
91182
|
-
result += content.slice(match.index, fullMatchEnd);
|
|
91183
|
-
}
|
|
91184
|
-
lastEnd = fullMatchEnd;
|
|
91185
|
-
attrStartRegex.lastIndex = fullMatchEnd;
|
|
91186
|
-
}
|
|
91187
|
-
match = attrStartRegex.exec(content);
|
|
91188
|
-
}
|
|
91189
|
-
result += content.slice(lastEnd);
|
|
91190
|
-
return result;
|
|
91498
|
+
'🇿🇲': 'zambia',
|
|
91499
|
+
'🇿🇼': 'zimbabwe',
|
|
91500
|
+
'🏴': 'england',
|
|
91501
|
+
'🏴': 'scotland',
|
|
91502
|
+
'🏴': 'wales'
|
|
91191
91503
|
}
|
|
91192
|
-
|
|
91193
|
-
|
|
91194
|
-
|
|
91195
|
-
|
|
91196
|
-
|
|
91197
|
-
|
|
91198
|
-
|
|
91199
|
-
|
|
91200
|
-
|
|
91201
|
-
|
|
91202
|
-
|
|
91203
|
-
|
|
91204
|
-
|
|
91205
|
-
|
|
91206
|
-
|
|
91207
|
-
|
|
91208
|
-
|
|
91209
|
-
|
|
91210
|
-
|
|
91211
|
-
|
|
91212
|
-
|
|
91213
|
-
|
|
91214
|
-
|
|
91504
|
+
|
|
91505
|
+
;// ./lib/owlmoji.ts
|
|
91506
|
+
|
|
91507
|
+
const owlmoji = [
|
|
91508
|
+
{
|
|
91509
|
+
emoji: '', // This `emoji` property doesn't get consumed, but is required for type consistency
|
|
91510
|
+
names: ['owlbert'],
|
|
91511
|
+
tags: ['owlbert'],
|
|
91512
|
+
description: 'an owlbert for any occasion',
|
|
91513
|
+
category: 'ReadMe',
|
|
91514
|
+
},
|
|
91515
|
+
{
|
|
91516
|
+
emoji: '',
|
|
91517
|
+
names: ['owlbert-books'],
|
|
91518
|
+
tags: ['owlbert'],
|
|
91519
|
+
description: 'owlbert carrying books',
|
|
91520
|
+
category: 'ReadMe',
|
|
91521
|
+
},
|
|
91522
|
+
{
|
|
91523
|
+
emoji: '',
|
|
91524
|
+
names: ['owlbert-mask'],
|
|
91525
|
+
tags: ['owlbert'],
|
|
91526
|
+
description: 'owlbert with a respirator',
|
|
91527
|
+
category: 'ReadMe',
|
|
91528
|
+
},
|
|
91529
|
+
{
|
|
91530
|
+
emoji: '',
|
|
91531
|
+
names: ['owlbert-reading'],
|
|
91532
|
+
tags: ['owlbert'],
|
|
91533
|
+
description: 'owlbert reading',
|
|
91534
|
+
category: 'ReadMe',
|
|
91535
|
+
},
|
|
91536
|
+
{
|
|
91537
|
+
emoji: '',
|
|
91538
|
+
names: ['owlbert-thinking'],
|
|
91539
|
+
tags: ['owlbert'],
|
|
91540
|
+
description: 'owlbert thinking',
|
|
91541
|
+
category: 'ReadMe',
|
|
91542
|
+
},
|
|
91543
|
+
];
|
|
91544
|
+
const owlmojiNames = owlmoji.flatMap(emoji => emoji.names);
|
|
91545
|
+
class Owlmoji {
|
|
91546
|
+
static kind = (name) => {
|
|
91547
|
+
if (name in nameToEmoji)
|
|
91548
|
+
return 'gemoji';
|
|
91549
|
+
else if (name.match(/^fa-/))
|
|
91550
|
+
return 'fontawesome';
|
|
91551
|
+
else if (owlmojiNames.includes(name))
|
|
91552
|
+
return 'owlmoji';
|
|
91553
|
+
return null;
|
|
91554
|
+
};
|
|
91555
|
+
static nameToEmoji = nameToEmoji;
|
|
91556
|
+
static owlmoji = gemoji_gemoji.concat(owlmoji);
|
|
91215
91557
|
}
|
|
91216
91558
|
|
|
91217
|
-
;// ./processor/
|
|
91559
|
+
;// ./processor/transform/gemoji+.ts
|
|
91218
91560
|
|
|
91219
91561
|
|
|
91220
91562
|
|
|
91221
|
-
|
|
91222
|
-
|
|
91223
|
-
|
|
91224
|
-
|
|
91225
|
-
|
|
91226
|
-
|
|
91227
|
-
|
|
91228
|
-
|
|
91229
|
-
*/
|
|
91230
|
-
const formatHProps = (node) => {
|
|
91231
|
-
const hProps = getHProps(node);
|
|
91232
|
-
return formatProps(hProps);
|
|
91233
|
-
};
|
|
91234
|
-
/**
|
|
91235
|
-
* Formats an object of props as a string.
|
|
91236
|
-
*
|
|
91237
|
-
* @param {Object} props
|
|
91238
|
-
* @returns {string}
|
|
91239
|
-
*/
|
|
91240
|
-
const formatProps = (props) => {
|
|
91241
|
-
const keys = Object.keys(props);
|
|
91242
|
-
return keys.map(key => `${key}="${props[key]}"`).join(' ');
|
|
91243
|
-
};
|
|
91244
|
-
/**
|
|
91245
|
-
* Returns the hProperties of a node.
|
|
91246
|
-
*
|
|
91247
|
-
* @template T
|
|
91248
|
-
* @param {Node} node
|
|
91249
|
-
* @returns {T} hProperties
|
|
91250
|
-
*/
|
|
91251
|
-
const getHProps = (node) => {
|
|
91252
|
-
const hProps = node.data?.hProperties || {};
|
|
91253
|
-
return hProps;
|
|
91254
|
-
};
|
|
91255
|
-
/**
|
|
91256
|
-
* Returns array of hProperty keys.
|
|
91257
|
-
*
|
|
91258
|
-
* @template T
|
|
91259
|
-
* @param {Node} node
|
|
91260
|
-
* @returns {Array} array of hProperty keys
|
|
91261
|
-
*/
|
|
91262
|
-
const getHPropKeys = (node) => {
|
|
91263
|
-
const hProps = getHProps(node);
|
|
91264
|
-
return Object.keys(hProps) || [];
|
|
91265
|
-
};
|
|
91266
|
-
/**
|
|
91267
|
-
* Gets the attributes of an MDX element and returns them as an object of hProperties.
|
|
91268
|
-
*
|
|
91269
|
-
* @template T
|
|
91270
|
-
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
91271
|
-
* @returns {T} object of hProperties
|
|
91272
|
-
*/
|
|
91273
|
-
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
91274
|
-
if ('name' in attr) {
|
|
91275
|
-
if (typeof attr.value === 'string') {
|
|
91276
|
-
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
91277
|
-
try {
|
|
91278
|
-
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
91279
|
-
}
|
|
91280
|
-
catch {
|
|
91281
|
-
memo[attr.name] = attr.value;
|
|
91282
|
-
}
|
|
91283
|
-
}
|
|
91284
|
-
else {
|
|
91285
|
-
memo[attr.name] = attr.value;
|
|
91286
|
-
}
|
|
91287
|
-
}
|
|
91288
|
-
else if (attr.value === null) {
|
|
91289
|
-
memo[attr.name] = true;
|
|
91290
|
-
}
|
|
91291
|
-
else if (attr.value.value !== 'undefined') {
|
|
91292
|
-
memo[attr.name] = JSON.parse(attr.value.value);
|
|
91293
|
-
}
|
|
91294
|
-
}
|
|
91295
|
-
return memo;
|
|
91296
|
-
}, {});
|
|
91297
|
-
/**
|
|
91298
|
-
* Gets the children of an MDX element and returns them as an array of Text nodes.
|
|
91299
|
-
* Currently only being used by the HTML Block component, which only expects a single text node.
|
|
91300
|
-
*
|
|
91301
|
-
* @template T
|
|
91302
|
-
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
91303
|
-
* @returns {Array} array of child text nodes
|
|
91304
|
-
*/
|
|
91305
|
-
const utils_getChildren = (jsx) => jsx.children.reduce((memo, child, i) => {
|
|
91306
|
-
memo[i] = {
|
|
91307
|
-
type: 'text',
|
|
91308
|
-
value: child.value,
|
|
91309
|
-
position: child.position,
|
|
91310
|
-
};
|
|
91311
|
-
return memo;
|
|
91312
|
-
}, []);
|
|
91313
|
-
/**
|
|
91314
|
-
* Tests if a node is an MDX element.
|
|
91315
|
-
* TODO: Make this more extensible to all types of nodes. isElement(node, 'type' or ['type1', 'type2']), say
|
|
91316
|
-
*
|
|
91317
|
-
* @param {Node} node
|
|
91318
|
-
* @returns {(node is MdxJsxFlowElement | MdxJsxTextElement | MdxjsEsm)}
|
|
91319
|
-
*/
|
|
91320
|
-
const isMDXElement = (node) => {
|
|
91321
|
-
return ['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type);
|
|
91322
|
-
};
|
|
91323
|
-
/**
|
|
91324
|
-
* Tests if a node is an MDX ESM element (i.e. import or export).
|
|
91325
|
-
*
|
|
91326
|
-
* @param {Node} node
|
|
91327
|
-
* @returns {boolean}
|
|
91328
|
-
*/
|
|
91329
|
-
const isMDXEsm = (node) => {
|
|
91330
|
-
return node.type === 'mdxjsEsm';
|
|
91331
|
-
};
|
|
91332
|
-
/**
|
|
91333
|
-
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
91334
|
-
* and unindents the HTML.
|
|
91335
|
-
*
|
|
91336
|
-
* @param {string} html - HTML content from template literal
|
|
91337
|
-
* @returns {string} processed HTML
|
|
91338
|
-
*/
|
|
91339
|
-
function formatHtmlForMdxish(html) {
|
|
91340
|
-
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
91341
|
-
// from being parsed prematurely
|
|
91342
|
-
let processed = html;
|
|
91343
|
-
if (processed.startsWith('`') && processed.endsWith('`')) {
|
|
91344
|
-
processed = processed.slice(1, -1);
|
|
91345
|
-
}
|
|
91346
|
-
// Removes the leading/trailing newlines
|
|
91347
|
-
let cleaned = processed.replace(/^\s*\n|\n\s*$/g, '');
|
|
91348
|
-
// Convert literal \n sequences to actual newlines BEFORE processing backticks
|
|
91349
|
-
// This prevents the backtick unescaping regex from incorrectly matching \n sequences
|
|
91350
|
-
cleaned = cleaned.replace(/\\n/g, '\n');
|
|
91351
|
-
// Unescape backticks: \` -> ` (users escape backticks in template literals)
|
|
91352
|
-
// Handle both cases: \` (adjacent) and \ followed by ` (split by markdown parser)
|
|
91353
|
-
cleaned = cleaned.replace(/\\`/g, '`');
|
|
91354
|
-
// Also handle case where backslash and backtick got separated by markdown parsing
|
|
91355
|
-
// Pattern: backslash followed by any characters (but not \n which we already handled), then a backtick
|
|
91356
|
-
// This handles cases like: \example` -> `example` (replacing \ with ` at start)
|
|
91357
|
-
// Exclude \n sequences to avoid matching them incorrectly
|
|
91358
|
-
cleaned = cleaned.replace(/\\([^`\\n]*?)`/g, '`$1`');
|
|
91359
|
-
// Fix case where markdown parser consumed one backtick from triple backticks
|
|
91360
|
-
// Pattern: `` followed by a word (like ``javascript) should be ```javascript
|
|
91361
|
-
// This handles cases where code fences were parsed and one backtick was lost
|
|
91362
|
-
cleaned = cleaned.replace(/<(\w+[^>]*)>``(\w+)/g, '<$1>```$2');
|
|
91363
|
-
// Unescape dollar signs: \$ -> $ (users escape $ in template literals to prevent interpolation)
|
|
91364
|
-
cleaned = cleaned.replace(/\\\$/g, '$');
|
|
91365
|
-
return cleaned;
|
|
91366
|
-
}
|
|
91367
|
-
/**
|
|
91368
|
-
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
91369
|
-
* and unindents the HTML.
|
|
91370
|
-
*
|
|
91371
|
-
* @param {string} html
|
|
91372
|
-
* @returns {string} formatted HTML
|
|
91373
|
-
*/
|
|
91374
|
-
const formatHTML = (html) => {
|
|
91375
|
-
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
91376
|
-
// from being parsed prematurely
|
|
91377
|
-
if (html.startsWith('`') && html.endsWith('`')) {
|
|
91378
|
-
// eslint-disable-next-line no-param-reassign
|
|
91379
|
-
html = html.slice(1, -1);
|
|
91380
|
-
}
|
|
91381
|
-
// Removes the leading/trailing newlines
|
|
91382
|
-
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');
|
|
91383
|
-
// // Get the number of spaces in the first line to determine the tab size
|
|
91384
|
-
// const tab = cleaned.match(/^\s*/)[0].length;
|
|
91385
|
-
// // Remove the first indentation level from each line
|
|
91386
|
-
// const tabRegex = new RegExp(`^\\s{${tab}}`, 'gm');
|
|
91387
|
-
// const unindented = cleaned.replace(tabRegex, '');
|
|
91388
|
-
return cleaned;
|
|
91389
|
-
};
|
|
91390
|
-
/**
|
|
91391
|
-
* Reformat HTML for the markdown/mdx by adding an indentation to each line. This assures that the
|
|
91392
|
-
* HTML is indentend properly within the HTMLBlock component when rendered in the markdown/mdx.
|
|
91393
|
-
*
|
|
91394
|
-
* @param {string} html
|
|
91395
|
-
* @param {number} [indent=2]
|
|
91396
|
-
* @returns {string} re-formatted HTML
|
|
91397
|
-
*/
|
|
91398
|
-
const reformatHTML = (html) => {
|
|
91399
|
-
// Remove leading/trailing newlines
|
|
91400
|
-
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '').replaceAll(/(?<!\\(\\\\)*)`/g, '\\`');
|
|
91401
|
-
// // Create a tab/indent with the specified number of spaces
|
|
91402
|
-
// const tab = ' '.repeat(indent);
|
|
91403
|
-
// // Indent each line of the HTML (converts to an array, indents each line, then joins back)
|
|
91404
|
-
// const indented = cleaned.split('\n').map((line: string) => `${tab}${line}`).join('\n');
|
|
91405
|
-
return cleaned;
|
|
91406
|
-
};
|
|
91407
|
-
const toAttributes = (object, keys = []) => {
|
|
91408
|
-
const attributes = [];
|
|
91409
|
-
Object.entries(object).forEach(([name, v]) => {
|
|
91410
|
-
if (keys.length > 0 && !keys.includes(name))
|
|
91411
|
-
return;
|
|
91412
|
-
let value;
|
|
91413
|
-
if (typeof v === 'undefined' || v === null || v === '' || v === false) {
|
|
91414
|
-
return;
|
|
91415
|
-
}
|
|
91416
|
-
else if (typeof v === 'string') {
|
|
91417
|
-
value = v;
|
|
91418
|
-
}
|
|
91419
|
-
else {
|
|
91420
|
-
/* values can be null, undefined, string, or a expression, eg:
|
|
91421
|
-
*
|
|
91422
|
-
* ```
|
|
91423
|
-
* <Image src="..." border={false} size={width - 20} />
|
|
91424
|
-
* ```
|
|
91425
|
-
*
|
|
91426
|
-
* Parsing the expression seems to only be done by the library
|
|
91427
|
-
* `mdast-util-mdx-jsx`, and so the most straight forward way to parse
|
|
91428
|
-
* the expression and get the appropriate AST is with our `mdast`
|
|
91429
|
-
* function.
|
|
91430
|
-
*/
|
|
91431
|
-
const proxy = lib_mdast(`{${v}}`);
|
|
91432
|
-
const data = proxy.children[0].data;
|
|
91433
|
-
value = {
|
|
91434
|
-
type: 'mdxJsxAttributeValueExpression',
|
|
91435
|
-
value: v.toString(),
|
|
91436
|
-
data,
|
|
91563
|
+
const gemoji_regex = /(?<=^|\s):(?<name>\+1|[-\w]+):/g;
|
|
91564
|
+
const gemojiReplacer = (_, name) => {
|
|
91565
|
+
switch (Owlmoji.kind(name)) {
|
|
91566
|
+
case 'gemoji': {
|
|
91567
|
+
const node = {
|
|
91568
|
+
type: NodeTypes.emoji,
|
|
91569
|
+
value: Owlmoji.nameToEmoji[name],
|
|
91570
|
+
name,
|
|
91437
91571
|
};
|
|
91572
|
+
return node;
|
|
91438
91573
|
}
|
|
91439
|
-
|
|
91440
|
-
|
|
91441
|
-
|
|
91442
|
-
|
|
91443
|
-
|
|
91444
|
-
|
|
91445
|
-
|
|
91446
|
-
|
|
91447
|
-
|
|
91448
|
-
|
|
91449
|
-
|
|
91450
|
-
|
|
91451
|
-
* example:
|
|
91452
|
-
* ```
|
|
91453
|
-
* const mdx = `export const Foo = 'bar';`
|
|
91454
|
-
*
|
|
91455
|
-
* hasNamedExport(mdast(mdx), 'Foo') => true
|
|
91456
|
-
* ```
|
|
91457
|
-
*
|
|
91458
|
-
*/
|
|
91459
|
-
const hasNamedExport = (tree, name) => {
|
|
91460
|
-
let hasExport = false;
|
|
91461
|
-
// eslint-disable-next-line consistent-return
|
|
91462
|
-
visit(tree, 'mdxjsEsm', node => {
|
|
91463
|
-
if ('declaration' in node.data.estree.body[0] &&
|
|
91464
|
-
node.data.estree.body[0].declaration.type === 'VariableDeclaration') {
|
|
91465
|
-
const { declarations } = node.data.estree.body[0].declaration;
|
|
91466
|
-
hasExport = !!declarations.find(declaration => 'name' in declaration.id && declaration.id.name === name);
|
|
91467
|
-
return hasExport ? EXIT : CONTINUE;
|
|
91574
|
+
case 'fontawesome': {
|
|
91575
|
+
const node = {
|
|
91576
|
+
type: NodeTypes.i,
|
|
91577
|
+
value: name,
|
|
91578
|
+
data: {
|
|
91579
|
+
hName: 'i',
|
|
91580
|
+
hProperties: {
|
|
91581
|
+
className: ['fa-regular', name],
|
|
91582
|
+
},
|
|
91583
|
+
},
|
|
91584
|
+
};
|
|
91585
|
+
return node;
|
|
91468
91586
|
}
|
|
91469
|
-
|
|
91470
|
-
|
|
91471
|
-
|
|
91472
|
-
|
|
91473
|
-
|
|
91474
|
-
|
|
91475
|
-
|
|
91476
|
-
|
|
91477
|
-
|
|
91478
|
-
|
|
91479
|
-
|
|
91480
|
-
|
|
91481
|
-
{
|
|
91482
|
-
"type": "ExportNamedDeclaration",
|
|
91483
|
-
"declaration": {
|
|
91484
|
-
"type": "VariableDeclaration",
|
|
91485
|
-
"declarations": [
|
|
91486
|
-
{
|
|
91487
|
-
"type": "VariableDeclarator",
|
|
91488
|
-
"id": {
|
|
91489
|
-
"type": "Identifier",
|
|
91490
|
-
"name": "Foo" // --------> This is the export name
|
|
91587
|
+
case 'owlmoji': {
|
|
91588
|
+
const node = {
|
|
91589
|
+
type: 'image',
|
|
91590
|
+
title: `:${name}:`,
|
|
91591
|
+
alt: `:${name}:`,
|
|
91592
|
+
url: `/public/img/emojis/${name}.png`,
|
|
91593
|
+
data: {
|
|
91594
|
+
hProperties: {
|
|
91595
|
+
className: 'emoji',
|
|
91596
|
+
align: 'absmiddle',
|
|
91597
|
+
height: '20',
|
|
91598
|
+
width: '20',
|
|
91491
91599
|
},
|
|
91492
|
-
...
|
|
91493
|
-
|
|
91494
|
-
2/3. FUNCTION DECLARATION & CLASS DECLARATION
|
|
91495
|
-
"estree": {
|
|
91496
|
-
"type": "Program",
|
|
91497
|
-
"body": [
|
|
91498
|
-
{
|
|
91499
|
-
"type": "ExportNamedDeclaration",
|
|
91500
|
-
"declaration": {
|
|
91501
|
-
"type": "ClassDeclaration" | "FunctionDeclaration",
|
|
91502
|
-
"id": {
|
|
91503
|
-
"type": "Identifier",
|
|
91504
|
-
"name": "Foo" // --------> This is the export name
|
|
91505
91600
|
},
|
|
91506
|
-
|
|
91507
|
-
|
|
91508
|
-
|
|
91509
|
-
|
|
91510
|
-
|
|
91511
|
-
|
|
91512
|
-
const body = node.data?.estree.body;
|
|
91513
|
-
if (!body)
|
|
91514
|
-
return;
|
|
91515
|
-
body.forEach(child => {
|
|
91516
|
-
if (child.type === 'ExportNamedDeclaration') {
|
|
91517
|
-
// There are three types of ExportNamedDeclaration that we need to consider: VariableDeclaration, FunctionDeclaration, ClassDeclaration
|
|
91518
|
-
const declaration = child.declaration;
|
|
91519
|
-
// FunctionDeclaration and ClassDeclaration have the same structure
|
|
91520
|
-
if (declaration.type !== 'VariableDeclaration') {
|
|
91521
|
-
// Note: declaration.id.type is always 'Identifier' for FunctionDeclarations and ClassDeclarations
|
|
91522
|
-
set.add(declaration.id.name);
|
|
91523
|
-
}
|
|
91524
|
-
else {
|
|
91525
|
-
declaration.declarations.forEach(dec => {
|
|
91526
|
-
const id = dec.id;
|
|
91527
|
-
if (id.type === 'Identifier') {
|
|
91528
|
-
set.add(id.name);
|
|
91529
|
-
}
|
|
91530
|
-
});
|
|
91531
|
-
}
|
|
91532
|
-
}
|
|
91533
|
-
});
|
|
91534
|
-
});
|
|
91535
|
-
return Array.from(set);
|
|
91601
|
+
};
|
|
91602
|
+
return node;
|
|
91603
|
+
}
|
|
91604
|
+
default:
|
|
91605
|
+
return false;
|
|
91606
|
+
}
|
|
91536
91607
|
};
|
|
91608
|
+
const gemojiTransformer = () => (tree) => {
|
|
91609
|
+
findAndReplace(tree, [gemoji_regex, gemojiReplacer]);
|
|
91610
|
+
return tree;
|
|
91611
|
+
};
|
|
91612
|
+
/* harmony default export */ const gemoji_ = (gemojiTransformer);
|
|
91537
91613
|
|
|
91538
91614
|
;// ./processor/transform/handle-missing-components.ts
|
|
91539
91615
|
|
|
@@ -92494,6 +92570,23 @@ const mdxishTables = () => tree => {
|
|
|
92494
92570
|
return;
|
|
92495
92571
|
try {
|
|
92496
92572
|
const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
|
|
92573
|
+
// since we use a subparser in `tableNodeProcessor` to parse `node.value`,
|
|
92574
|
+
// positions are relative to that substring. shifting them by the base
|
|
92575
|
+
// offset and line number makes them valid in the outer source coordinate space.
|
|
92576
|
+
// otherwise, consumers who directly slice based on position would read and grab the
|
|
92577
|
+
// wrong content
|
|
92578
|
+
const baseOffset = node.position?.start?.offset ?? 0;
|
|
92579
|
+
const baseLine = (node.position?.start?.line ?? 1) - 1;
|
|
92580
|
+
visit(parsed, child => {
|
|
92581
|
+
if (child.position?.start) {
|
|
92582
|
+
child.position.start.offset = (child.position.start.offset ?? 0) + baseOffset;
|
|
92583
|
+
child.position.start.line += baseLine;
|
|
92584
|
+
}
|
|
92585
|
+
if (child.position?.end) {
|
|
92586
|
+
child.position.end.offset = (child.position.end.offset ?? 0) + baseOffset;
|
|
92587
|
+
child.position.end.line += baseLine;
|
|
92588
|
+
}
|
|
92589
|
+
});
|
|
92497
92590
|
visit(parsed, isMDXElement, (tableNode) => {
|
|
92498
92591
|
if (tableNode.name === 'Table') {
|
|
92499
92592
|
processTableNode(tableNode, index, parent, node.position);
|
|
@@ -112904,74 +112997,6 @@ const codeTabs = (node, _, state, info) => {
|
|
|
112904
112997
|
};
|
|
112905
112998
|
/* harmony default export */ const compile_code_tabs = (codeTabs);
|
|
112906
112999
|
|
|
112907
|
-
;// ./processor/compile/compatibility.ts
|
|
112908
|
-
|
|
112909
|
-
|
|
112910
|
-
|
|
112911
|
-
/*
|
|
112912
|
-
* Converts a (remark < v9) html node to a JSX string.
|
|
112913
|
-
*
|
|
112914
|
-
* First we replace html comments with the JSX equivalent. Then, we parse that
|
|
112915
|
-
* as html, and serialize it back as xml!
|
|
112916
|
-
*
|
|
112917
|
-
*/
|
|
112918
|
-
const compileHtml = (node) => {
|
|
112919
|
-
const string = node.value.replaceAll(/<!--(.*)-->/gms, '{/*$1*/}');
|
|
112920
|
-
return string;
|
|
112921
|
-
};
|
|
112922
|
-
const figureToImageBlock = (node) => {
|
|
112923
|
-
const { align, border, width, src, url, alt, title, ...image } = node.children.find((child) => child.type === 'image');
|
|
112924
|
-
const { className } = image.data.hProperties;
|
|
112925
|
-
const figcaption = node.children.find((child) => child.type === 'figcaption');
|
|
112926
|
-
const caption = figcaption ? toMarkdown(figcaption.children).trim() : null;
|
|
112927
|
-
const attributes = {
|
|
112928
|
-
...(align && { align }),
|
|
112929
|
-
...(alt && { alt }),
|
|
112930
|
-
...(className && { border: className === 'border' }),
|
|
112931
|
-
...(border && { border }),
|
|
112932
|
-
...(caption && { caption }),
|
|
112933
|
-
...(title && { title }),
|
|
112934
|
-
...(width && { width }),
|
|
112935
|
-
src: src || url,
|
|
112936
|
-
};
|
|
112937
|
-
return `<Image ${formatProps(attributes)} />`;
|
|
112938
|
-
};
|
|
112939
|
-
const embedToEmbedBlock = (node) => {
|
|
112940
|
-
const { html, ...embed } = node.data.hProperties;
|
|
112941
|
-
const attributes = {
|
|
112942
|
-
...embed,
|
|
112943
|
-
...(html && { html: encodeURIComponent(html) }),
|
|
112944
|
-
};
|
|
112945
|
-
return `<Embed ${formatProps(attributes)} />`;
|
|
112946
|
-
};
|
|
112947
|
-
const compatibility = (node) => {
|
|
112948
|
-
switch (node.type) {
|
|
112949
|
-
case NodeTypes.glossary: {
|
|
112950
|
-
// Glossary terms will no longer be serialized as special node types in the Editor but we want to ensure that we compile historical
|
|
112951
|
-
// data correctly
|
|
112952
|
-
const term = node.data?.hProperties?.term || node.children[0].value;
|
|
112953
|
-
return `<Glossary>${term}</Glossary>`;
|
|
112954
|
-
}
|
|
112955
|
-
case NodeTypes.reusableContent:
|
|
112956
|
-
return `<${node.tag} />`;
|
|
112957
|
-
case 'html':
|
|
112958
|
-
return compileHtml(node);
|
|
112959
|
-
case 'escape':
|
|
112960
|
-
return `\\${node.value}`;
|
|
112961
|
-
case 'figure':
|
|
112962
|
-
return figureToImageBlock(node);
|
|
112963
|
-
case 'embed':
|
|
112964
|
-
return embedToEmbedBlock(node);
|
|
112965
|
-
case 'i':
|
|
112966
|
-
return `:${node.data.hProperties.className[1]}:`;
|
|
112967
|
-
case 'yaml':
|
|
112968
|
-
return `---\n${node.value}\n---`;
|
|
112969
|
-
default:
|
|
112970
|
-
throw new Error('Unhandled node type!');
|
|
112971
|
-
}
|
|
112972
|
-
};
|
|
112973
|
-
/* harmony default export */ const compile_compatibility = (compatibility);
|
|
112974
|
-
|
|
112975
113000
|
;// ./processor/compile/embed.ts
|
|
112976
113001
|
|
|
112977
113002
|
const embed_embed = (node) => {
|
|
@@ -119118,7 +119143,7 @@ function findJsxTableToken() {
|
|
|
119118
119143
|
return undefined;
|
|
119119
119144
|
}
|
|
119120
119145
|
function enterJsxTable(token) {
|
|
119121
|
-
jsx_table_contextMap.set(token, { chunks: [] });
|
|
119146
|
+
jsx_table_contextMap.set(token, { chunks: [], lastEndLine: token.start.line });
|
|
119122
119147
|
this.enter({ type: 'html', value: '' }, token);
|
|
119123
119148
|
}
|
|
119124
119149
|
function exitJsxTableData(token) {
|
|
@@ -119126,14 +119151,20 @@ function exitJsxTableData(token) {
|
|
|
119126
119151
|
if (!tableToken)
|
|
119127
119152
|
return;
|
|
119128
119153
|
const ctx = jsx_table_contextMap.get(tableToken);
|
|
119129
|
-
if (ctx)
|
|
119154
|
+
if (ctx) {
|
|
119155
|
+
const gap = token.start.line - ctx.lastEndLine;
|
|
119156
|
+
if (ctx.chunks.length > 0 && gap > 0) {
|
|
119157
|
+
ctx.chunks.push('\n'.repeat(gap));
|
|
119158
|
+
}
|
|
119130
119159
|
ctx.chunks.push(this.sliceSerialize(token));
|
|
119160
|
+
ctx.lastEndLine = token.end.line;
|
|
119161
|
+
}
|
|
119131
119162
|
}
|
|
119132
119163
|
function exitJsxTable(token) {
|
|
119133
119164
|
const ctx = jsx_table_contextMap.get(token);
|
|
119134
119165
|
const node = this.stack[this.stack.length - 1];
|
|
119135
119166
|
if (ctx) {
|
|
119136
|
-
node.value = ctx.chunks.join('
|
|
119167
|
+
node.value = ctx.chunks.join('');
|
|
119137
119168
|
jsx_table_contextMap.delete(token);
|
|
119138
119169
|
}
|
|
119139
119170
|
this.exit(token);
|