@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.js
CHANGED
|
@@ -11578,7 +11578,8 @@ const Callout = (props) => {
|
|
|
11578
11578
|
const children = external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.Children.toArray(props.children);
|
|
11579
11579
|
const icon = props.icon;
|
|
11580
11580
|
const isEmoji = icon && emoji_regex().test(icon);
|
|
11581
|
-
const
|
|
11581
|
+
const hasBody = children.length > 1;
|
|
11582
|
+
const heading = empty ? (hasBody ? external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.createElement("p", { className: 'callout-heading empty' }) : null) : children[0];
|
|
11582
11583
|
const theme = props.theme || (icon && themes[icon]) || 'default';
|
|
11583
11584
|
return (
|
|
11584
11585
|
// @ts-expect-error -- theme is not a valid attribute
|
|
@@ -12118,8 +12119,9 @@ function useScrollHighlight(navRef) {
|
|
|
12118
12119
|
const nav = navRef.current;
|
|
12119
12120
|
if (!nav)
|
|
12120
12121
|
return;
|
|
12121
|
-
const key = Array.from(nav.querySelectorAll('a
|
|
12122
|
-
.map(a => a.
|
|
12122
|
+
const key = Array.from(nav.querySelectorAll('a'))
|
|
12123
|
+
.map(a => a.hash)
|
|
12124
|
+
.filter(Boolean)
|
|
12123
12125
|
.join('\0');
|
|
12124
12126
|
setTocKey(key);
|
|
12125
12127
|
});
|
|
@@ -12141,9 +12143,11 @@ function useScrollHighlight(navRef) {
|
|
|
12141
12143
|
const scrollParent = getScrollParent(headings[0]);
|
|
12142
12144
|
const isAtBottom = () => {
|
|
12143
12145
|
if (scrollParent instanceof Window) {
|
|
12144
|
-
return
|
|
12146
|
+
return document.documentElement.scrollHeight > window.innerHeight
|
|
12147
|
+
&& window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
12145
12148
|
}
|
|
12146
|
-
return scrollParent.
|
|
12149
|
+
return scrollParent.scrollHeight > scrollParent.clientHeight
|
|
12150
|
+
&& scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
12147
12151
|
};
|
|
12148
12152
|
const activate = (id) => {
|
|
12149
12153
|
if (id === activeId)
|
|
@@ -12193,15 +12197,12 @@ function useScrollHighlight(navRef) {
|
|
|
12193
12197
|
const onClick = (e) => {
|
|
12194
12198
|
if (!(e.target instanceof Element))
|
|
12195
12199
|
return;
|
|
12196
|
-
const anchor = e.target.closest('a
|
|
12197
|
-
if (!(anchor instanceof HTMLAnchorElement))
|
|
12200
|
+
const anchor = e.target.closest('a');
|
|
12201
|
+
if (!(anchor instanceof HTMLAnchorElement) || !anchor.hash)
|
|
12198
12202
|
return;
|
|
12199
12203
|
const id = decodeURIComponent(anchor.hash.slice(1));
|
|
12200
12204
|
if (!linkMap.has(id))
|
|
12201
12205
|
return;
|
|
12202
|
-
if (window.location.hash !== anchor.hash) {
|
|
12203
|
-
window.location.hash = anchor.hash;
|
|
12204
|
-
}
|
|
12205
12206
|
activate(id);
|
|
12206
12207
|
clickLocked = true;
|
|
12207
12208
|
let unlockTimer = null;
|
|
@@ -53146,267 +53147,1078 @@ const plain = (node, opts = {}) => {
|
|
|
53146
53147
|
};
|
|
53147
53148
|
/* harmony default export */ const lib_plain = (plain);
|
|
53148
53149
|
|
|
53149
|
-
;// ./
|
|
53150
|
-
const gemoji = (node) => `:${node.name}:`;
|
|
53151
|
-
/* harmony default export */ const compile_gemoji = (gemoji);
|
|
53150
|
+
;// ./lib/mdast.ts
|
|
53152
53151
|
|
|
53153
|
-
|
|
53154
|
-
const
|
|
53155
|
-
|
|
53152
|
+
const mdast = (text, opts = {}) => {
|
|
53153
|
+
const processor = ast_processor(opts);
|
|
53154
|
+
const tree = processor.parse(text);
|
|
53155
|
+
return processor.runSync(tree);
|
|
53156
|
+
};
|
|
53157
|
+
/* harmony default export */ const lib_mdast = (mdast);
|
|
53156
53158
|
|
|
53157
|
-
;// ./
|
|
53159
|
+
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
53158
53160
|
/**
|
|
53159
|
-
*
|
|
53160
|
-
* Works with both MDAST and HAST-like node structures.
|
|
53161
|
-
*
|
|
53162
|
-
* Placed this outside of the utils.ts file to avoid circular dependencies.
|
|
53161
|
+
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
53163
53162
|
*
|
|
53164
|
-
* @param
|
|
53165
|
-
* @returns
|
|
53163
|
+
* @param content - The markdown content to process
|
|
53164
|
+
* @returns Object containing protected content and arrays of original code blocks
|
|
53165
|
+
* @example
|
|
53166
|
+
* ```typescript
|
|
53167
|
+
* const input = 'Text with `inline code` and ```fenced block```';
|
|
53168
|
+
* protectCodeBlocks(input)
|
|
53169
|
+
* // Returns: {
|
|
53170
|
+
* // protectedCode: {
|
|
53171
|
+
* // codeBlocks: ['```fenced block```'],
|
|
53172
|
+
* // inlineCode: ['`inline code`']
|
|
53173
|
+
* // },
|
|
53174
|
+
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
53175
|
+
* // }
|
|
53176
|
+
* ```
|
|
53166
53177
|
*/
|
|
53167
|
-
|
|
53168
|
-
|
|
53169
|
-
|
|
53170
|
-
|
|
53171
|
-
|
|
53172
|
-
|
|
53173
|
-
|
|
53174
|
-
|
|
53175
|
-
|
|
53176
|
-
|
|
53177
|
-
|
|
53178
|
-
|
|
53179
|
-
|
|
53180
|
-
|
|
53181
|
-
|
|
53182
|
-
|
|
53183
|
-
}
|
|
53184
|
-
|
|
53178
|
+
function protectCodeBlocks(content) {
|
|
53179
|
+
const codeBlocks = [];
|
|
53180
|
+
const inlineCode = [];
|
|
53181
|
+
let protectedContent = '';
|
|
53182
|
+
let remaining = content;
|
|
53183
|
+
let codeBlockStart = remaining.indexOf('```');
|
|
53184
|
+
while (codeBlockStart !== -1) {
|
|
53185
|
+
protectedContent += remaining.slice(0, codeBlockStart);
|
|
53186
|
+
remaining = remaining.slice(codeBlockStart);
|
|
53187
|
+
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
53188
|
+
if (codeBlockEnd === -1) {
|
|
53189
|
+
break;
|
|
53190
|
+
}
|
|
53191
|
+
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
53192
|
+
const index = codeBlocks.length;
|
|
53193
|
+
codeBlocks.push(match);
|
|
53194
|
+
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
53195
|
+
remaining = remaining.slice(codeBlockEnd + 3);
|
|
53196
|
+
codeBlockStart = remaining.indexOf('```');
|
|
53185
53197
|
}
|
|
53186
|
-
|
|
53187
|
-
|
|
53188
|
-
|
|
53189
|
-
|
|
53190
|
-
|
|
53191
|
-
|
|
53192
|
-
|
|
53193
|
-
|
|
53194
|
-
|
|
53195
|
-
|
|
53196
|
-
|
|
53197
|
-
|
|
53198
|
-
|
|
53199
|
-
|
|
53200
|
-
|
|
53201
|
-
|
|
53202
|
-
|
|
53203
|
-
|
|
53204
|
-
const titleParser = unified().use(remarkParse).use(remarkGfm);
|
|
53205
|
-
// The title paragraph may contain custom AST nodes that `toMarkdown` doesn't
|
|
53206
|
-
// natively understand
|
|
53207
|
-
const toMarkdownExtensions = [
|
|
53208
|
-
gfmToMarkdown(),
|
|
53209
|
-
// For mdx variable syntaxes (e.g., {user.name})
|
|
53210
|
-
mdxExpressionToMarkdown(),
|
|
53211
|
-
// Important: This is required and would crash the parser if there's no variable and gemoji node handler
|
|
53212
|
-
{ handlers: { [NodeTypes.variable]: compile_variable, [NodeTypes.emoji]: compile_gemoji } },
|
|
53213
|
-
];
|
|
53214
|
-
const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
|
|
53215
|
-
const findFirst = (node) => {
|
|
53216
|
-
if ('children' in node)
|
|
53217
|
-
return findFirst(node.children[0]);
|
|
53218
|
-
if (node.type === 'text')
|
|
53219
|
-
return node;
|
|
53220
|
-
return null;
|
|
53221
|
-
};
|
|
53222
|
-
const wrapHeading = (node) => {
|
|
53223
|
-
const firstChild = node.children[0];
|
|
53224
|
-
return {
|
|
53225
|
-
type: 'heading',
|
|
53226
|
-
depth: 3,
|
|
53227
|
-
children: ('children' in firstChild ? firstChild.children : []),
|
|
53228
|
-
position: {
|
|
53229
|
-
start: firstChild.position.start,
|
|
53230
|
-
end: firstChild.position.end,
|
|
53231
|
-
},
|
|
53232
|
-
};
|
|
53233
|
-
};
|
|
53198
|
+
protectedContent += remaining;
|
|
53199
|
+
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
53200
|
+
const index = inlineCode.length;
|
|
53201
|
+
inlineCode.push(match);
|
|
53202
|
+
return `___INLINE_CODE_${index}___`;
|
|
53203
|
+
});
|
|
53204
|
+
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
53205
|
+
}
|
|
53234
53206
|
/**
|
|
53235
|
-
*
|
|
53236
|
-
*
|
|
53207
|
+
* Restores inline code by replacing placeholders with original content.
|
|
53208
|
+
*
|
|
53209
|
+
* @param content - Content with inline code placeholders
|
|
53210
|
+
* @param protectedCode - The protected code arrays
|
|
53211
|
+
* @returns Content with inline code restored
|
|
53237
53212
|
*/
|
|
53238
|
-
|
|
53239
|
-
|
|
53240
|
-
|
|
53241
|
-
|
|
53242
|
-
|
|
53243
|
-
return false;
|
|
53244
|
-
const firstTextChild = firstChild.children?.[0];
|
|
53245
|
-
return firstTextChild?.type === 'text';
|
|
53246
|
-
};
|
|
53213
|
+
function restoreInlineCode(content, protectedCode) {
|
|
53214
|
+
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
53215
|
+
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
53216
|
+
});
|
|
53217
|
+
}
|
|
53247
53218
|
/**
|
|
53248
|
-
*
|
|
53249
|
-
*
|
|
53219
|
+
* Restores fenced code blocks by replacing placeholders with original content.
|
|
53220
|
+
*
|
|
53221
|
+
* @param content - Content with code block placeholders
|
|
53222
|
+
* @param protectedCode - The protected code arrays
|
|
53223
|
+
* @returns Content with code blocks restored
|
|
53250
53224
|
*/
|
|
53251
|
-
|
|
53252
|
-
|
|
53253
|
-
|
|
53254
|
-
|
|
53255
|
-
|
|
53256
|
-
if (newlineIndex !== -1) {
|
|
53257
|
-
return { index: i, newlineIndex };
|
|
53258
|
-
}
|
|
53259
|
-
}
|
|
53260
|
-
}
|
|
53261
|
-
return null;
|
|
53262
|
-
};
|
|
53225
|
+
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
53226
|
+
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
53227
|
+
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
53228
|
+
});
|
|
53229
|
+
}
|
|
53263
53230
|
/**
|
|
53264
|
-
*
|
|
53265
|
-
*
|
|
53231
|
+
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
53232
|
+
*
|
|
53233
|
+
* @param content - Content with code placeholders
|
|
53234
|
+
* @param protectedCode - The protected code arrays
|
|
53235
|
+
* @returns Content with all code blocks and inline code restored
|
|
53236
|
+
* @example
|
|
53237
|
+
* ```typescript
|
|
53238
|
+
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
53239
|
+
* const protectedCode = {
|
|
53240
|
+
* codeBlocks: ['```js\ncode\n```'],
|
|
53241
|
+
* inlineCode: ['`inline`']
|
|
53242
|
+
* };
|
|
53243
|
+
* restoreCodeBlocks(content, protectedCode)
|
|
53244
|
+
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
53245
|
+
* ```
|
|
53266
53246
|
*/
|
|
53267
|
-
|
|
53268
|
-
|
|
53269
|
-
|
|
53270
|
-
|
|
53271
|
-
|
|
53272
|
-
|
|
53273
|
-
|
|
53274
|
-
|
|
53275
|
-
|
|
53276
|
-
|
|
53277
|
-
|
|
53278
|
-
|
|
53279
|
-
headingChildren.push({ type: 'text', value: beforeNewline });
|
|
53247
|
+
function restoreCodeBlocks(content, protectedCode) {
|
|
53248
|
+
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
53249
|
+
restored = restoreInlineCode(restored, protectedCode);
|
|
53250
|
+
return restored;
|
|
53251
|
+
}
|
|
53252
|
+
|
|
53253
|
+
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
53254
|
+
|
|
53255
|
+
// Base64 encode (Node.js + browser compatible)
|
|
53256
|
+
function base64Encode(str) {
|
|
53257
|
+
if (typeof Buffer !== 'undefined') {
|
|
53258
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
53280
53259
|
}
|
|
53281
|
-
|
|
53282
|
-
|
|
53283
|
-
|
|
53284
|
-
|
|
53285
|
-
|
|
53260
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
53261
|
+
}
|
|
53262
|
+
// Base64 decode (Node.js + browser compatible)
|
|
53263
|
+
function base64Decode(str) {
|
|
53264
|
+
if (typeof Buffer !== 'undefined') {
|
|
53265
|
+
return Buffer.from(str, 'base64').toString('utf-8');
|
|
53286
53266
|
}
|
|
53287
|
-
|
|
53288
|
-
|
|
53289
|
-
|
|
53267
|
+
return decodeURIComponent(escape(atob(str)));
|
|
53268
|
+
}
|
|
53269
|
+
function escapeHtmlAttribute(value) {
|
|
53270
|
+
return value
|
|
53271
|
+
.replace(/&/g, '&')
|
|
53272
|
+
.replace(/"/g, '"')
|
|
53273
|
+
.replace(/</g, '<')
|
|
53274
|
+
.replace(/>/g, '>')
|
|
53275
|
+
.replace(/\n/g, ' ');
|
|
53276
|
+
}
|
|
53277
|
+
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
53278
|
+
// Using a prefix that won't conflict with regular string values
|
|
53279
|
+
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
53280
|
+
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
53281
|
+
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
53282
|
+
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
53290
53283
|
/**
|
|
53291
|
-
*
|
|
53292
|
-
*
|
|
53284
|
+
* Evaluates a JavaScript expression using context variables.
|
|
53285
|
+
*
|
|
53286
|
+
* @param expression
|
|
53287
|
+
* @param context
|
|
53288
|
+
* @returns The evaluated result
|
|
53289
|
+
* @example
|
|
53290
|
+
* ```typescript
|
|
53291
|
+
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
53292
|
+
* evaluateExpression('baseUrl + path', context)
|
|
53293
|
+
* // Returns: 'https://example.com/api'
|
|
53294
|
+
* ```
|
|
53293
53295
|
*/
|
|
53294
|
-
|
|
53295
|
-
const
|
|
53296
|
-
|
|
53297
|
-
|
|
53298
|
-
}
|
|
53299
|
-
|
|
53300
|
-
|
|
53301
|
-
|
|
53302
|
-
|
|
53303
|
-
|
|
53304
|
-
|
|
53305
|
-
|
|
53306
|
-
|
|
53307
|
-
|
|
53308
|
-
|
|
53309
|
-
|
|
53310
|
-
|
|
53311
|
-
|
|
53312
|
-
|
|
53313
|
-
|
|
53314
|
-
|
|
53315
|
-
|
|
53316
|
-
|
|
53317
|
-
|
|
53318
|
-
|
|
53296
|
+
function evaluateExpression(expression, context) {
|
|
53297
|
+
const contextKeys = Object.keys(context);
|
|
53298
|
+
const contextValues = Object.values(context);
|
|
53299
|
+
// eslint-disable-next-line no-new-func
|
|
53300
|
+
const func = new Function(...contextKeys, `return ${expression}`);
|
|
53301
|
+
return func(...contextValues);
|
|
53302
|
+
}
|
|
53303
|
+
/**
|
|
53304
|
+
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
53305
|
+
*
|
|
53306
|
+
* @param content
|
|
53307
|
+
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
53308
|
+
* @example
|
|
53309
|
+
* ```typescript
|
|
53310
|
+
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
53311
|
+
* protectHTMLBlockContent(input)
|
|
53312
|
+
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
53313
|
+
* ```
|
|
53314
|
+
*/
|
|
53315
|
+
function protectHTMLBlockContent(content) {
|
|
53316
|
+
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
53317
|
+
const encoded = base64Encode(templateContent);
|
|
53318
|
+
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
53319
|
+
});
|
|
53320
|
+
}
|
|
53321
|
+
/**
|
|
53322
|
+
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
53323
|
+
*
|
|
53324
|
+
* @param content
|
|
53325
|
+
* @returns Content with JSX comments removed
|
|
53326
|
+
* @example
|
|
53327
|
+
* ```typescript
|
|
53328
|
+
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
53329
|
+
* // Returns: 'Text more text'
|
|
53330
|
+
* ```
|
|
53331
|
+
*/
|
|
53332
|
+
function removeJSXComments(content) {
|
|
53333
|
+
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
53334
|
+
}
|
|
53335
|
+
/**
|
|
53336
|
+
* Extracts content between balanced braces, handling nested braces.
|
|
53337
|
+
*
|
|
53338
|
+
* @param content
|
|
53339
|
+
* @param start
|
|
53340
|
+
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
53341
|
+
* @example
|
|
53342
|
+
* ```typescript
|
|
53343
|
+
* const input = 'foo{bar{baz}qux}end';
|
|
53344
|
+
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
53345
|
+
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
53346
|
+
* ```
|
|
53347
|
+
*/
|
|
53348
|
+
function extractBalancedBraces(content, start) {
|
|
53349
|
+
let depth = 1;
|
|
53350
|
+
let pos = start;
|
|
53351
|
+
while (pos < content.length && depth > 0) {
|
|
53352
|
+
const char = content[pos];
|
|
53353
|
+
if (char === '{')
|
|
53354
|
+
depth += 1;
|
|
53355
|
+
else if (char === '}')
|
|
53356
|
+
depth -= 1;
|
|
53357
|
+
pos += 1;
|
|
53319
53358
|
}
|
|
53320
|
-
|
|
53321
|
-
|
|
53322
|
-
|
|
53323
|
-
|
|
53324
|
-
|
|
53325
|
-
|
|
53326
|
-
|
|
53327
|
-
|
|
53328
|
-
|
|
53329
|
-
|
|
53330
|
-
|
|
53331
|
-
|
|
53332
|
-
|
|
53333
|
-
|
|
53334
|
-
|
|
53335
|
-
|
|
53336
|
-
|
|
53337
|
-
|
|
53338
|
-
|
|
53339
|
-
|
|
53340
|
-
|
|
53341
|
-
|
|
53342
|
-
|
|
53343
|
-
|
|
53344
|
-
|
|
53345
|
-
|
|
53346
|
-
|
|
53359
|
+
if (depth !== 0)
|
|
53360
|
+
return null;
|
|
53361
|
+
return { content: content.slice(start, pos - 1), end: pos };
|
|
53362
|
+
}
|
|
53363
|
+
/**
|
|
53364
|
+
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
53365
|
+
* Handles three cases:
|
|
53366
|
+
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
53367
|
+
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
53368
|
+
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
53369
|
+
*
|
|
53370
|
+
*/
|
|
53371
|
+
function escapeProblematicBraces(content) {
|
|
53372
|
+
// Skip HTML elements — their content should never be escaped because
|
|
53373
|
+
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
53374
|
+
const htmlElements = [];
|
|
53375
|
+
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
53376
|
+
const idx = htmlElements.length;
|
|
53377
|
+
htmlElements.push(match);
|
|
53378
|
+
return `___HTML_ELEM_${idx}___`;
|
|
53379
|
+
});
|
|
53380
|
+
const toEscape = new Set();
|
|
53381
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
53382
|
+
const chars = Array.from(safe);
|
|
53383
|
+
let strDelim = null;
|
|
53384
|
+
let strEscaped = false;
|
|
53385
|
+
// Stack of open braces with their state
|
|
53386
|
+
const openStack = [];
|
|
53387
|
+
// Track position of last newline (outside strings) to detect blank lines
|
|
53388
|
+
let lastNewlinePos = -2; // -2 means no recent newline
|
|
53389
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
53390
|
+
const ch = chars[i];
|
|
53391
|
+
// Track string delimiters inside expressions to ignore braces within them
|
|
53392
|
+
if (openStack.length > 0) {
|
|
53393
|
+
if (strDelim) {
|
|
53394
|
+
if (strEscaped)
|
|
53395
|
+
strEscaped = false;
|
|
53396
|
+
else if (ch === '\\')
|
|
53397
|
+
strEscaped = true;
|
|
53398
|
+
else if (ch === strDelim)
|
|
53399
|
+
strDelim = null;
|
|
53400
|
+
// eslint-disable-next-line no-continue
|
|
53401
|
+
continue;
|
|
53347
53402
|
}
|
|
53348
|
-
|
|
53349
|
-
|
|
53350
|
-
//
|
|
53351
|
-
|
|
53352
|
-
|
|
53353
|
-
|
|
53354
|
-
|
|
53355
|
-
|
|
53356
|
-
|
|
53357
|
-
.
|
|
53358
|
-
|
|
53359
|
-
|
|
53360
|
-
|
|
53361
|
-
|
|
53362
|
-
|
|
53363
|
-
|
|
53364
|
-
visit(parsedTitle, (n) => {
|
|
53365
|
-
delete n.position;
|
|
53366
|
-
});
|
|
53367
|
-
const heading = wrapHeading(node);
|
|
53368
|
-
heading.children = parsedTitle.children;
|
|
53369
|
-
delete heading.position;
|
|
53370
|
-
node.children[0] = heading;
|
|
53403
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
53404
|
+
strDelim = ch;
|
|
53405
|
+
// eslint-disable-next-line no-continue
|
|
53406
|
+
continue;
|
|
53407
|
+
}
|
|
53408
|
+
// Track newlines to detect blank lines (paragraph boundaries)
|
|
53409
|
+
if (ch === '\n') {
|
|
53410
|
+
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
53411
|
+
if (lastNewlinePos >= 0) {
|
|
53412
|
+
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
53413
|
+
if (/^[ \t]*$/.test(between)) {
|
|
53414
|
+
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
53415
|
+
openStack.forEach(entry => {
|
|
53416
|
+
entry.hasBlankLine = true;
|
|
53417
|
+
});
|
|
53418
|
+
}
|
|
53371
53419
|
}
|
|
53372
|
-
|
|
53373
|
-
|
|
53374
|
-
|
|
53375
|
-
|
|
53420
|
+
lastNewlinePos = i;
|
|
53421
|
+
}
|
|
53422
|
+
}
|
|
53423
|
+
// Skip already-escaped braces (count preceding backslashes)
|
|
53424
|
+
if (ch === '{' || ch === '}') {
|
|
53425
|
+
let bs = 0;
|
|
53426
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
53427
|
+
bs += 1;
|
|
53428
|
+
if (bs % 2 === 1) {
|
|
53429
|
+
// eslint-disable-next-line no-continue
|
|
53430
|
+
continue;
|
|
53431
|
+
}
|
|
53432
|
+
}
|
|
53433
|
+
if (ch === '{') {
|
|
53434
|
+
openStack.push({ pos: i, hasBlankLine: false });
|
|
53435
|
+
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
53436
|
+
}
|
|
53437
|
+
else if (ch === '}') {
|
|
53438
|
+
if (openStack.length > 0) {
|
|
53439
|
+
const entry = openStack.pop();
|
|
53440
|
+
// If expression spans paragraph boundary, escape both braces
|
|
53441
|
+
if (entry.hasBlankLine) {
|
|
53442
|
+
toEscape.add(entry.pos);
|
|
53443
|
+
toEscape.add(i);
|
|
53376
53444
|
}
|
|
53377
53445
|
}
|
|
53378
53446
|
else {
|
|
53379
|
-
|
|
53380
|
-
|
|
53381
|
-
node.children[0].position.start.column += match.length;
|
|
53447
|
+
// Unbalanced closing brace (no matching open)
|
|
53448
|
+
toEscape.add(i);
|
|
53382
53449
|
}
|
|
53383
53450
|
}
|
|
53384
|
-
|
|
53385
|
-
|
|
53386
|
-
|
|
53387
|
-
|
|
53388
|
-
|
|
53389
|
-
|
|
53390
|
-
|
|
53391
|
-
|
|
53392
|
-
|
|
53393
|
-
|
|
53394
|
-
|
|
53395
|
-
|
|
53396
|
-
|
|
53397
|
-
|
|
53398
|
-
|
|
53399
|
-
|
|
53400
|
-
|
|
53401
|
-
|
|
53402
|
-
|
|
53403
|
-
|
|
53404
|
-
|
|
53405
|
-
|
|
53406
|
-
|
|
53407
|
-
|
|
53408
|
-
|
|
53409
|
-
|
|
53451
|
+
}
|
|
53452
|
+
// Any remaining open braces are unbalanced
|
|
53453
|
+
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
53454
|
+
// If there are no problematic braces, return safe content as-is;
|
|
53455
|
+
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
53456
|
+
let result = toEscape.size === 0
|
|
53457
|
+
? safe
|
|
53458
|
+
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
53459
|
+
// Restore HTML elements
|
|
53460
|
+
if (htmlElements.length > 0) {
|
|
53461
|
+
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
53462
|
+
}
|
|
53463
|
+
return result;
|
|
53464
|
+
}
|
|
53465
|
+
/**
|
|
53466
|
+
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
53467
|
+
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
53468
|
+
*
|
|
53469
|
+
* @param content
|
|
53470
|
+
* @param context
|
|
53471
|
+
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
53472
|
+
* @example
|
|
53473
|
+
* ```typescript
|
|
53474
|
+
* const context = { baseUrl: 'https://example.com' };
|
|
53475
|
+
* const input = '<a href={baseUrl}>Link</a>';
|
|
53476
|
+
* evaluateAttributeExpressions(input, context)
|
|
53477
|
+
* // Returns: '<a href="https://example.com">Link</a>'
|
|
53478
|
+
* ```
|
|
53479
|
+
*/
|
|
53480
|
+
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
53481
|
+
const attrStartRegex = /(\w+)=\{/g;
|
|
53482
|
+
let result = '';
|
|
53483
|
+
let lastEnd = 0;
|
|
53484
|
+
let match = attrStartRegex.exec(content);
|
|
53485
|
+
while (match !== null) {
|
|
53486
|
+
const attributeName = match[1];
|
|
53487
|
+
const braceStart = match.index + match[0].length;
|
|
53488
|
+
const extracted = extractBalancedBraces(content, braceStart);
|
|
53489
|
+
if (extracted) {
|
|
53490
|
+
// The expression might contain template literals in MDX component tag props
|
|
53491
|
+
// E.g. <Component greeting={`Hello World!`} />
|
|
53492
|
+
// that is marked as inline code. So we need to restore the inline codes
|
|
53493
|
+
// in the expression to evaluate it
|
|
53494
|
+
let expression = extracted.content;
|
|
53495
|
+
if (protectedCode) {
|
|
53496
|
+
expression = restoreInlineCode(expression, protectedCode);
|
|
53497
|
+
}
|
|
53498
|
+
const fullMatchEnd = extracted.end;
|
|
53499
|
+
result += content.slice(lastEnd, match.index);
|
|
53500
|
+
try {
|
|
53501
|
+
const evalResult = evaluateExpression(expression, context);
|
|
53502
|
+
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
53503
|
+
if (attributeName === 'style') {
|
|
53504
|
+
const cssString = Object.entries(evalResult)
|
|
53505
|
+
.map(([key, value]) => {
|
|
53506
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
53507
|
+
return `${cssKey}: ${value}`;
|
|
53508
|
+
})
|
|
53509
|
+
.join('; ');
|
|
53510
|
+
result += `style="${cssString}"`;
|
|
53511
|
+
}
|
|
53512
|
+
else {
|
|
53513
|
+
// These are arrays / objects attribute values
|
|
53514
|
+
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
53515
|
+
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
53516
|
+
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
53517
|
+
result += `${attributeName}="${jsonValue}"`;
|
|
53518
|
+
}
|
|
53519
|
+
}
|
|
53520
|
+
else if (attributeName === 'className') {
|
|
53521
|
+
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
53522
|
+
// This will be restored later in the pipeline
|
|
53523
|
+
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
53524
|
+
}
|
|
53525
|
+
else {
|
|
53526
|
+
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
53527
|
+
}
|
|
53528
|
+
}
|
|
53529
|
+
catch (_error) {
|
|
53530
|
+
result += content.slice(match.index, fullMatchEnd);
|
|
53531
|
+
}
|
|
53532
|
+
lastEnd = fullMatchEnd;
|
|
53533
|
+
attrStartRegex.lastIndex = fullMatchEnd;
|
|
53534
|
+
}
|
|
53535
|
+
match = attrStartRegex.exec(content);
|
|
53536
|
+
}
|
|
53537
|
+
result += content.slice(lastEnd);
|
|
53538
|
+
return result;
|
|
53539
|
+
}
|
|
53540
|
+
/**
|
|
53541
|
+
* Preprocesses JSX-like expressions in markdown before parsing.
|
|
53542
|
+
* Inline expressions are handled separately; attribute expressions are processed here.
|
|
53543
|
+
*
|
|
53544
|
+
* @param content
|
|
53545
|
+
* @param context
|
|
53546
|
+
* @returns Preprocessed content ready for markdown parsing
|
|
53547
|
+
*/
|
|
53548
|
+
function preprocessJSXExpressions(content, context = {}) {
|
|
53549
|
+
// Step 0: Base64 encode HTMLBlock content
|
|
53550
|
+
let processed = protectHTMLBlockContent(content);
|
|
53551
|
+
// Step 1: Protect code blocks and inline code
|
|
53552
|
+
const { protectedCode, protectedContent } = protectCodeBlocks(processed);
|
|
53553
|
+
// Step 2: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
|
|
53554
|
+
// For inline expressions, we use a library to parse the expression & evaluate it later
|
|
53555
|
+
// For attribute expressions, it was difficult to use a library to parse them, so do it manually
|
|
53556
|
+
processed = evaluateAttributeExpressions(protectedContent, context, protectedCode);
|
|
53557
|
+
// Step 3: Escape problematic braces to prevent MDX expression parsing errors
|
|
53558
|
+
// This handles both unbalanced braces and paragraph-spanning expressions in one pass
|
|
53559
|
+
processed = escapeProblematicBraces(processed);
|
|
53560
|
+
// Step 4: Restore protected code blocks
|
|
53561
|
+
processed = restoreCodeBlocks(processed, protectedCode);
|
|
53562
|
+
return processed;
|
|
53563
|
+
}
|
|
53564
|
+
|
|
53565
|
+
;// ./processor/utils.ts
|
|
53566
|
+
|
|
53567
|
+
|
|
53568
|
+
|
|
53569
|
+
/**
|
|
53570
|
+
* Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
|
|
53571
|
+
* This currently sets all the values to a string since we process/compile the MDX on the fly
|
|
53572
|
+
* through the editor, and it'll throw errors over malformed JSX. TODO: fix this.
|
|
53573
|
+
*
|
|
53574
|
+
* @template T
|
|
53575
|
+
* @param {Node} node
|
|
53576
|
+
* @returns {string} formatted hProperties as JSX attributes
|
|
53577
|
+
*/
|
|
53578
|
+
const formatHProps = (node) => {
|
|
53579
|
+
const hProps = getHProps(node);
|
|
53580
|
+
return formatProps(hProps);
|
|
53581
|
+
};
|
|
53582
|
+
/**
|
|
53583
|
+
* Formats an object of props as a string.
|
|
53584
|
+
*
|
|
53585
|
+
* @param {Object} props
|
|
53586
|
+
* @returns {string}
|
|
53587
|
+
*/
|
|
53588
|
+
const formatProps = (props) => {
|
|
53589
|
+
const keys = Object.keys(props);
|
|
53590
|
+
return keys.map(key => `${key}="${props[key]}"`).join(' ');
|
|
53591
|
+
};
|
|
53592
|
+
/**
|
|
53593
|
+
* Returns the hProperties of a node.
|
|
53594
|
+
*
|
|
53595
|
+
* @template T
|
|
53596
|
+
* @param {Node} node
|
|
53597
|
+
* @returns {T} hProperties
|
|
53598
|
+
*/
|
|
53599
|
+
const getHProps = (node) => {
|
|
53600
|
+
const hProps = node.data?.hProperties || {};
|
|
53601
|
+
return hProps;
|
|
53602
|
+
};
|
|
53603
|
+
/**
|
|
53604
|
+
* Returns array of hProperty keys.
|
|
53605
|
+
*
|
|
53606
|
+
* @template T
|
|
53607
|
+
* @param {Node} node
|
|
53608
|
+
* @returns {Array} array of hProperty keys
|
|
53609
|
+
*/
|
|
53610
|
+
const getHPropKeys = (node) => {
|
|
53611
|
+
const hProps = getHProps(node);
|
|
53612
|
+
return Object.keys(hProps) || [];
|
|
53613
|
+
};
|
|
53614
|
+
/**
|
|
53615
|
+
* Gets the attributes of an MDX element and returns them as an object of hProperties.
|
|
53616
|
+
*
|
|
53617
|
+
* @template T
|
|
53618
|
+
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
53619
|
+
* @returns {T} object of hProperties
|
|
53620
|
+
*/
|
|
53621
|
+
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
53622
|
+
if ('name' in attr) {
|
|
53623
|
+
if (typeof attr.value === 'string') {
|
|
53624
|
+
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
53625
|
+
try {
|
|
53626
|
+
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
53627
|
+
}
|
|
53628
|
+
catch {
|
|
53629
|
+
memo[attr.name] = attr.value;
|
|
53630
|
+
}
|
|
53631
|
+
}
|
|
53632
|
+
else {
|
|
53633
|
+
memo[attr.name] = attr.value;
|
|
53634
|
+
}
|
|
53635
|
+
}
|
|
53636
|
+
else if (attr.value === null) {
|
|
53637
|
+
memo[attr.name] = true;
|
|
53638
|
+
}
|
|
53639
|
+
else if (attr.value.value !== 'undefined') {
|
|
53640
|
+
memo[attr.name] = JSON.parse(attr.value.value);
|
|
53641
|
+
}
|
|
53642
|
+
}
|
|
53643
|
+
return memo;
|
|
53644
|
+
}, {});
|
|
53645
|
+
/**
|
|
53646
|
+
* Gets the children of an MDX element and returns them as an array of Text nodes.
|
|
53647
|
+
* Currently only being used by the HTML Block component, which only expects a single text node.
|
|
53648
|
+
*
|
|
53649
|
+
* @template T
|
|
53650
|
+
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
53651
|
+
* @returns {Array} array of child text nodes
|
|
53652
|
+
*/
|
|
53653
|
+
const getChildren = (jsx) => jsx.children.reduce((memo, child, i) => {
|
|
53654
|
+
memo[i] = {
|
|
53655
|
+
type: 'text',
|
|
53656
|
+
value: child.value,
|
|
53657
|
+
position: child.position,
|
|
53658
|
+
};
|
|
53659
|
+
return memo;
|
|
53660
|
+
}, []);
|
|
53661
|
+
/**
|
|
53662
|
+
* Tests if a node is an MDX element.
|
|
53663
|
+
* TODO: Make this more extensible to all types of nodes. isElement(node, 'type' or ['type1', 'type2']), say
|
|
53664
|
+
*
|
|
53665
|
+
* @param {Node} node
|
|
53666
|
+
* @returns {(node is MdxJsxFlowElement | MdxJsxTextElement | MdxjsEsm)}
|
|
53667
|
+
*/
|
|
53668
|
+
const isMDXElement = (node) => {
|
|
53669
|
+
return ['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type);
|
|
53670
|
+
};
|
|
53671
|
+
/**
|
|
53672
|
+
* Tests if a node is an MDX ESM element (i.e. import or export).
|
|
53673
|
+
*
|
|
53674
|
+
* @param {Node} node
|
|
53675
|
+
* @returns {boolean}
|
|
53676
|
+
*/
|
|
53677
|
+
const isMDXEsm = (node) => {
|
|
53678
|
+
return node.type === 'mdxjsEsm';
|
|
53679
|
+
};
|
|
53680
|
+
/**
|
|
53681
|
+
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
53682
|
+
* and unindents the HTML.
|
|
53683
|
+
*
|
|
53684
|
+
* @param {string} html - HTML content from template literal
|
|
53685
|
+
* @returns {string} processed HTML
|
|
53686
|
+
*/
|
|
53687
|
+
function formatHtmlForMdxish(html) {
|
|
53688
|
+
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
53689
|
+
// from being parsed prematurely
|
|
53690
|
+
let processed = html;
|
|
53691
|
+
if (processed.startsWith('`') && processed.endsWith('`')) {
|
|
53692
|
+
processed = processed.slice(1, -1);
|
|
53693
|
+
}
|
|
53694
|
+
// Removes the leading/trailing newlines
|
|
53695
|
+
let cleaned = processed.replace(/^\s*\n|\n\s*$/g, '');
|
|
53696
|
+
// Convert literal \n sequences to actual newlines BEFORE processing backticks
|
|
53697
|
+
// This prevents the backtick unescaping regex from incorrectly matching \n sequences
|
|
53698
|
+
cleaned = cleaned.replace(/\\n/g, '\n');
|
|
53699
|
+
// Unescape backticks: \` -> ` (users escape backticks in template literals)
|
|
53700
|
+
// Handle both cases: \` (adjacent) and \ followed by ` (split by markdown parser)
|
|
53701
|
+
cleaned = cleaned.replace(/\\`/g, '`');
|
|
53702
|
+
// Also handle case where backslash and backtick got separated by markdown parsing
|
|
53703
|
+
// Pattern: backslash followed by any characters (but not \n which we already handled), then a backtick
|
|
53704
|
+
// This handles cases like: \example` -> `example` (replacing \ with ` at start)
|
|
53705
|
+
// Exclude \n sequences to avoid matching them incorrectly
|
|
53706
|
+
cleaned = cleaned.replace(/\\([^`\\n]*?)`/g, '`$1`');
|
|
53707
|
+
// Fix case where markdown parser consumed one backtick from triple backticks
|
|
53708
|
+
// Pattern: `` followed by a word (like ``javascript) should be ```javascript
|
|
53709
|
+
// This handles cases where code fences were parsed and one backtick was lost
|
|
53710
|
+
cleaned = cleaned.replace(/<(\w+[^>]*)>``(\w+)/g, '<$1>```$2');
|
|
53711
|
+
// Unescape dollar signs: \$ -> $ (users escape $ in template literals to prevent interpolation)
|
|
53712
|
+
cleaned = cleaned.replace(/\\\$/g, '$');
|
|
53713
|
+
return cleaned;
|
|
53714
|
+
}
|
|
53715
|
+
/**
|
|
53716
|
+
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
53717
|
+
* and unindents the HTML.
|
|
53718
|
+
*
|
|
53719
|
+
* @param {string} html
|
|
53720
|
+
* @returns {string} formatted HTML
|
|
53721
|
+
*/
|
|
53722
|
+
const formatHTML = (html) => {
|
|
53723
|
+
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
53724
|
+
// from being parsed prematurely
|
|
53725
|
+
if (html.startsWith('`') && html.endsWith('`')) {
|
|
53726
|
+
// eslint-disable-next-line no-param-reassign
|
|
53727
|
+
html = html.slice(1, -1);
|
|
53728
|
+
}
|
|
53729
|
+
// Removes the leading/trailing newlines
|
|
53730
|
+
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');
|
|
53731
|
+
// // Get the number of spaces in the first line to determine the tab size
|
|
53732
|
+
// const tab = cleaned.match(/^\s*/)[0].length;
|
|
53733
|
+
// // Remove the first indentation level from each line
|
|
53734
|
+
// const tabRegex = new RegExp(`^\\s{${tab}}`, 'gm');
|
|
53735
|
+
// const unindented = cleaned.replace(tabRegex, '');
|
|
53736
|
+
return cleaned;
|
|
53737
|
+
};
|
|
53738
|
+
/**
|
|
53739
|
+
* Reformat HTML for the markdown/mdx by adding an indentation to each line. This assures that the
|
|
53740
|
+
* HTML is indentend properly within the HTMLBlock component when rendered in the markdown/mdx.
|
|
53741
|
+
*
|
|
53742
|
+
* @param {string} html
|
|
53743
|
+
* @param {number} [indent=2]
|
|
53744
|
+
* @returns {string} re-formatted HTML
|
|
53745
|
+
*/
|
|
53746
|
+
const reformatHTML = (html) => {
|
|
53747
|
+
// Remove leading/trailing newlines
|
|
53748
|
+
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '').replaceAll(/(?<!\\(\\\\)*)`/g, '\\`');
|
|
53749
|
+
// // Create a tab/indent with the specified number of spaces
|
|
53750
|
+
// const tab = ' '.repeat(indent);
|
|
53751
|
+
// // Indent each line of the HTML (converts to an array, indents each line, then joins back)
|
|
53752
|
+
// const indented = cleaned.split('\n').map((line: string) => `${tab}${line}`).join('\n');
|
|
53753
|
+
return cleaned;
|
|
53754
|
+
};
|
|
53755
|
+
const toAttributes = (object, keys = []) => {
|
|
53756
|
+
const attributes = [];
|
|
53757
|
+
Object.entries(object).forEach(([name, v]) => {
|
|
53758
|
+
if (keys.length > 0 && !keys.includes(name))
|
|
53759
|
+
return;
|
|
53760
|
+
let value;
|
|
53761
|
+
if (typeof v === 'undefined' || v === null || v === '' || v === false) {
|
|
53762
|
+
return;
|
|
53763
|
+
}
|
|
53764
|
+
else if (typeof v === 'string') {
|
|
53765
|
+
value = v;
|
|
53766
|
+
}
|
|
53767
|
+
else {
|
|
53768
|
+
/* values can be null, undefined, string, or a expression, eg:
|
|
53769
|
+
*
|
|
53770
|
+
* ```
|
|
53771
|
+
* <Image src="..." border={false} size={width - 20} />
|
|
53772
|
+
* ```
|
|
53773
|
+
*
|
|
53774
|
+
* Parsing the expression seems to only be done by the library
|
|
53775
|
+
* `mdast-util-mdx-jsx`, and so the most straight forward way to parse
|
|
53776
|
+
* the expression and get the appropriate AST is with our `mdast`
|
|
53777
|
+
* function.
|
|
53778
|
+
*/
|
|
53779
|
+
const proxy = lib_mdast(`{${v}}`);
|
|
53780
|
+
const data = proxy.children[0].data;
|
|
53781
|
+
value = {
|
|
53782
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
53783
|
+
value: v.toString(),
|
|
53784
|
+
data,
|
|
53785
|
+
};
|
|
53786
|
+
}
|
|
53787
|
+
attributes.push({
|
|
53788
|
+
type: 'mdxJsxAttribute',
|
|
53789
|
+
name,
|
|
53790
|
+
value,
|
|
53791
|
+
});
|
|
53792
|
+
});
|
|
53793
|
+
return attributes;
|
|
53794
|
+
};
|
|
53795
|
+
/**
|
|
53796
|
+
* Checks if a named export exists in the MDX tree. Accepts either an mdast or
|
|
53797
|
+
* a hast tree.
|
|
53798
|
+
*
|
|
53799
|
+
* example:
|
|
53800
|
+
* ```
|
|
53801
|
+
* const mdx = `export const Foo = 'bar';`
|
|
53802
|
+
*
|
|
53803
|
+
* hasNamedExport(mdast(mdx), 'Foo') => true
|
|
53804
|
+
* ```
|
|
53805
|
+
*
|
|
53806
|
+
*/
|
|
53807
|
+
const hasNamedExport = (tree, name) => {
|
|
53808
|
+
let hasExport = false;
|
|
53809
|
+
// eslint-disable-next-line consistent-return
|
|
53810
|
+
visit(tree, 'mdxjsEsm', node => {
|
|
53811
|
+
if ('declaration' in node.data.estree.body[0] &&
|
|
53812
|
+
node.data.estree.body[0].declaration.type === 'VariableDeclaration') {
|
|
53813
|
+
const { declarations } = node.data.estree.body[0].declaration;
|
|
53814
|
+
hasExport = !!declarations.find(declaration => 'name' in declaration.id && declaration.id.name === name);
|
|
53815
|
+
return hasExport ? EXIT : CONTINUE;
|
|
53816
|
+
}
|
|
53817
|
+
});
|
|
53818
|
+
return hasExport;
|
|
53819
|
+
};
|
|
53820
|
+
/* Example mdast structures to find first export name in a mdxjsEsm node:
|
|
53821
|
+
There are three types of export declarations that we need to consider:
|
|
53822
|
+
1. VARIABLE DECLARATION
|
|
53823
|
+
"type": "mdxjsEsm",
|
|
53824
|
+
"value": "export const Foo = () => <div>Hello world</div>\nexport const Bar = () => <div>hello darkness my old friend</div>",
|
|
53825
|
+
"data": {
|
|
53826
|
+
"estree": {
|
|
53827
|
+
"type": "Program",
|
|
53828
|
+
"body": [
|
|
53829
|
+
{
|
|
53830
|
+
"type": "ExportNamedDeclaration",
|
|
53831
|
+
"declaration": {
|
|
53832
|
+
"type": "VariableDeclaration",
|
|
53833
|
+
"declarations": [
|
|
53834
|
+
{
|
|
53835
|
+
"type": "VariableDeclarator",
|
|
53836
|
+
"id": {
|
|
53837
|
+
"type": "Identifier",
|
|
53838
|
+
"name": "Foo" // --------> This is the export name
|
|
53839
|
+
},
|
|
53840
|
+
...
|
|
53841
|
+
|
|
53842
|
+
2/3. FUNCTION DECLARATION & CLASS DECLARATION
|
|
53843
|
+
"estree": {
|
|
53844
|
+
"type": "Program",
|
|
53845
|
+
"body": [
|
|
53846
|
+
{
|
|
53847
|
+
"type": "ExportNamedDeclaration",
|
|
53848
|
+
"declaration": {
|
|
53849
|
+
"type": "ClassDeclaration" | "FunctionDeclaration",
|
|
53850
|
+
"id": {
|
|
53851
|
+
"type": "Identifier",
|
|
53852
|
+
"name": "Foo" // --------> This is the export name
|
|
53853
|
+
},
|
|
53854
|
+
*/
|
|
53855
|
+
const getExports = (tree) => {
|
|
53856
|
+
const set = new Set();
|
|
53857
|
+
visit(tree, isMDXEsm, (node) => {
|
|
53858
|
+
// Once inside an mdxjsEsm node, we need to check for one or more declared exports within data.estree.body
|
|
53859
|
+
// 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
|
|
53860
|
+
const body = node.data?.estree.body;
|
|
53861
|
+
if (!body)
|
|
53862
|
+
return;
|
|
53863
|
+
body.forEach(child => {
|
|
53864
|
+
if (child.type === 'ExportNamedDeclaration') {
|
|
53865
|
+
// There are three types of ExportNamedDeclaration that we need to consider: VariableDeclaration, FunctionDeclaration, ClassDeclaration
|
|
53866
|
+
const declaration = child.declaration;
|
|
53867
|
+
// FunctionDeclaration and ClassDeclaration have the same structure
|
|
53868
|
+
if (declaration.type !== 'VariableDeclaration') {
|
|
53869
|
+
// Note: declaration.id.type is always 'Identifier' for FunctionDeclarations and ClassDeclarations
|
|
53870
|
+
set.add(declaration.id.name);
|
|
53871
|
+
}
|
|
53872
|
+
else {
|
|
53873
|
+
declaration.declarations.forEach(dec => {
|
|
53874
|
+
const id = dec.id;
|
|
53875
|
+
if (id.type === 'Identifier') {
|
|
53876
|
+
set.add(id.name);
|
|
53877
|
+
}
|
|
53878
|
+
});
|
|
53879
|
+
}
|
|
53880
|
+
}
|
|
53881
|
+
});
|
|
53882
|
+
});
|
|
53883
|
+
return Array.from(set);
|
|
53884
|
+
};
|
|
53885
|
+
|
|
53886
|
+
;// ./processor/compile/compatibility.ts
|
|
53887
|
+
|
|
53888
|
+
|
|
53889
|
+
|
|
53890
|
+
/*
|
|
53891
|
+
* Converts a (remark < v9) html node to a JSX string.
|
|
53892
|
+
*
|
|
53893
|
+
* First we replace html comments with the JSX equivalent. Then, we parse that
|
|
53894
|
+
* as html, and serialize it back as xml!
|
|
53895
|
+
*
|
|
53896
|
+
*/
|
|
53897
|
+
const compileHtml = (node) => {
|
|
53898
|
+
const string = node.value.replaceAll(/<!--(.*)-->/gms, '{/*$1*/}');
|
|
53899
|
+
return string;
|
|
53900
|
+
};
|
|
53901
|
+
const figureToImageBlock = (node) => {
|
|
53902
|
+
const { align, border, width, src, url, alt, title, ...image } = node.children.find((child) => child.type === 'image');
|
|
53903
|
+
const { className } = image.data.hProperties;
|
|
53904
|
+
const figcaption = node.children.find((child) => child.type === 'figcaption');
|
|
53905
|
+
const caption = figcaption ? toMarkdown(figcaption.children).trim() : null;
|
|
53906
|
+
const attributes = {
|
|
53907
|
+
...(align && { align }),
|
|
53908
|
+
...(alt && { alt }),
|
|
53909
|
+
...(className && { border: className === 'border' }),
|
|
53910
|
+
...(border && { border }),
|
|
53911
|
+
...(caption && { caption }),
|
|
53912
|
+
...(title && { title }),
|
|
53913
|
+
...(width && { width }),
|
|
53914
|
+
src: src || url,
|
|
53915
|
+
};
|
|
53916
|
+
return `<Image ${formatProps(attributes)} />`;
|
|
53917
|
+
};
|
|
53918
|
+
const embedToEmbedBlock = (node) => {
|
|
53919
|
+
const { html, ...embed } = node.data.hProperties;
|
|
53920
|
+
const attributes = {
|
|
53921
|
+
...embed,
|
|
53922
|
+
...(html && { html: encodeURIComponent(html) }),
|
|
53923
|
+
};
|
|
53924
|
+
return `<Embed ${formatProps(attributes)} />`;
|
|
53925
|
+
};
|
|
53926
|
+
const compatibility = (node) => {
|
|
53927
|
+
switch (node.type) {
|
|
53928
|
+
case NodeTypes.glossary: {
|
|
53929
|
+
// Glossary terms will no longer be serialized as special node types in the Editor but we want to ensure that we compile historical
|
|
53930
|
+
// data correctly
|
|
53931
|
+
const term = node.data?.hProperties?.term || node.children[0].value;
|
|
53932
|
+
return `<Glossary>${term}</Glossary>`;
|
|
53933
|
+
}
|
|
53934
|
+
case NodeTypes.reusableContent:
|
|
53935
|
+
return `<${node.tag} />`;
|
|
53936
|
+
case 'html':
|
|
53937
|
+
return compileHtml(node);
|
|
53938
|
+
case 'escape':
|
|
53939
|
+
return `\\${node.value}`;
|
|
53940
|
+
case 'figure':
|
|
53941
|
+
return figureToImageBlock(node);
|
|
53942
|
+
case 'embed':
|
|
53943
|
+
return embedToEmbedBlock(node);
|
|
53944
|
+
case 'i':
|
|
53945
|
+
return `:${node.data.hProperties.className[1]}:`;
|
|
53946
|
+
case 'yaml':
|
|
53947
|
+
return `---\n${node.value}\n---`;
|
|
53948
|
+
default:
|
|
53949
|
+
throw new Error('Unhandled node type!');
|
|
53950
|
+
}
|
|
53951
|
+
};
|
|
53952
|
+
/* harmony default export */ const compile_compatibility = (compatibility);
|
|
53953
|
+
|
|
53954
|
+
;// ./processor/compile/gemoji.ts
|
|
53955
|
+
const gemoji = (node) => `:${node.name}:`;
|
|
53956
|
+
/* harmony default export */ const compile_gemoji = (gemoji);
|
|
53957
|
+
|
|
53958
|
+
;// ./processor/compile/variable.ts
|
|
53959
|
+
const variable = (node) => `{user.${node.data?.hProperties?.name || ''}}`;
|
|
53960
|
+
/* harmony default export */ const compile_variable = (variable);
|
|
53961
|
+
|
|
53962
|
+
;// ./processor/transform/extract-text.ts
|
|
53963
|
+
/**
|
|
53964
|
+
* Extracts text content from a single AST node recursively.
|
|
53965
|
+
* Works with both MDAST and HAST-like node structures.
|
|
53966
|
+
*
|
|
53967
|
+
* Placed this outside of the utils.ts file to avoid circular dependencies.
|
|
53968
|
+
*
|
|
53969
|
+
* @param node - The node to extract text from (can be MDAST Node or HAST-like structure)
|
|
53970
|
+
* @returns The concatenated text content
|
|
53971
|
+
*/
|
|
53972
|
+
const extractText = (node) => {
|
|
53973
|
+
if (typeof node.value === 'string') {
|
|
53974
|
+
return node.value;
|
|
53975
|
+
}
|
|
53976
|
+
// When a blockquote contains only an image (no text), treat it as having content
|
|
53977
|
+
// so the blockquote is no longer treated as empty and preserved correctly.
|
|
53978
|
+
if (node.type === 'image') {
|
|
53979
|
+
return typeof node.alt === 'string' && node.alt ? node.alt : '[image]';
|
|
53980
|
+
}
|
|
53981
|
+
if (node.children && Array.isArray(node.children)) {
|
|
53982
|
+
return node.children
|
|
53983
|
+
.map(child => {
|
|
53984
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
53985
|
+
return extractText(child);
|
|
53986
|
+
}
|
|
53987
|
+
return '';
|
|
53988
|
+
})
|
|
53989
|
+
.join('');
|
|
53990
|
+
}
|
|
53991
|
+
return '';
|
|
53992
|
+
};
|
|
53993
|
+
|
|
53994
|
+
;// ./processor/transform/callouts.ts
|
|
53995
|
+
|
|
53996
|
+
|
|
53997
|
+
|
|
53998
|
+
|
|
53999
|
+
|
|
54000
|
+
|
|
54001
|
+
|
|
54002
|
+
|
|
54003
|
+
|
|
54004
|
+
|
|
54005
|
+
|
|
54006
|
+
|
|
54007
|
+
|
|
54008
|
+
|
|
54009
|
+
|
|
54010
|
+
const titleParser = unified().use(remarkParse).use(remarkGfm);
|
|
54011
|
+
// The title paragraph may contain custom AST nodes that `toMarkdown` doesn't
|
|
54012
|
+
// natively understand
|
|
54013
|
+
const toMarkdownExtensions = [
|
|
54014
|
+
gfmToMarkdown(),
|
|
54015
|
+
// For mdx variable syntaxes (e.g., {user.name})
|
|
54016
|
+
mdxExpressionToMarkdown(),
|
|
54017
|
+
// Important: This is required and would crash the parser if there's no variable, gemoji, or fa-icon node handler
|
|
54018
|
+
{
|
|
54019
|
+
handlers: {
|
|
54020
|
+
[NodeTypes.variable]: compile_variable,
|
|
54021
|
+
[NodeTypes.emoji]: compile_gemoji,
|
|
54022
|
+
[NodeTypes.i]: compile_compatibility,
|
|
54023
|
+
},
|
|
54024
|
+
},
|
|
54025
|
+
];
|
|
54026
|
+
const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
|
|
54027
|
+
const findFirst = (node) => {
|
|
54028
|
+
if ('children' in node)
|
|
54029
|
+
return findFirst(node.children[0]);
|
|
54030
|
+
if (node.type === 'text')
|
|
54031
|
+
return node;
|
|
54032
|
+
return null;
|
|
54033
|
+
};
|
|
54034
|
+
const wrapHeading = (node) => {
|
|
54035
|
+
const firstChild = node.children[0];
|
|
54036
|
+
return {
|
|
54037
|
+
type: 'heading',
|
|
54038
|
+
depth: 3,
|
|
54039
|
+
children: ('children' in firstChild ? firstChild.children : []),
|
|
54040
|
+
position: {
|
|
54041
|
+
start: firstChild.position.start,
|
|
54042
|
+
end: firstChild.position.end,
|
|
54043
|
+
},
|
|
54044
|
+
};
|
|
54045
|
+
};
|
|
54046
|
+
/**
|
|
54047
|
+
* Checks if a blockquote matches the expected callout structure:
|
|
54048
|
+
* blockquote > paragraph > text node
|
|
54049
|
+
*/
|
|
54050
|
+
const isCalloutStructure = (node) => {
|
|
54051
|
+
const firstChild = node.children?.[0];
|
|
54052
|
+
if (!firstChild || firstChild.type !== 'paragraph')
|
|
54053
|
+
return false;
|
|
54054
|
+
if (!('children' in firstChild))
|
|
54055
|
+
return false;
|
|
54056
|
+
const firstTextChild = firstChild.children?.[0];
|
|
54057
|
+
return firstTextChild?.type === 'text';
|
|
54058
|
+
};
|
|
54059
|
+
/**
|
|
54060
|
+
* Finds the first text node containing a newline in a paragraph's children.
|
|
54061
|
+
* Returns the index and the newline position within that text node.
|
|
54062
|
+
*/
|
|
54063
|
+
const findNewlineInParagraph = (paragraph) => {
|
|
54064
|
+
for (let i = 0; i < paragraph.children.length; i += 1) {
|
|
54065
|
+
const child = paragraph.children[i];
|
|
54066
|
+
if (child.type === 'text' && typeof child.value === 'string') {
|
|
54067
|
+
const newlineIndex = child.value.indexOf('\n');
|
|
54068
|
+
if (newlineIndex !== -1) {
|
|
54069
|
+
return { index: i, newlineIndex };
|
|
54070
|
+
}
|
|
54071
|
+
}
|
|
54072
|
+
}
|
|
54073
|
+
return null;
|
|
54074
|
+
};
|
|
54075
|
+
/**
|
|
54076
|
+
* Splits a paragraph at the first newline, separating heading content (before \n)
|
|
54077
|
+
* from body content (after \n). Mutates the paragraph to contain only heading children.
|
|
54078
|
+
*/
|
|
54079
|
+
const splitParagraphAtNewline = (paragraph) => {
|
|
54080
|
+
const splitPoint = findNewlineInParagraph(paragraph);
|
|
54081
|
+
if (!splitPoint)
|
|
54082
|
+
return null;
|
|
54083
|
+
const { index, newlineIndex } = splitPoint;
|
|
54084
|
+
const originalChildren = paragraph.children;
|
|
54085
|
+
const textNode = originalChildren[index];
|
|
54086
|
+
const beforeNewline = textNode.value.slice(0, newlineIndex);
|
|
54087
|
+
const afterNewline = textNode.value.slice(newlineIndex + 1);
|
|
54088
|
+
// Split paragraph: heading = children[0..index-1] + text before newline
|
|
54089
|
+
const headingChildren = originalChildren.slice(0, index);
|
|
54090
|
+
if (beforeNewline.length > 0 || headingChildren.length === 0) {
|
|
54091
|
+
headingChildren.push({ type: 'text', value: beforeNewline });
|
|
54092
|
+
}
|
|
54093
|
+
paragraph.children = headingChildren;
|
|
54094
|
+
// Body = text after newline + remaining children from original array
|
|
54095
|
+
const bodyChildren = [];
|
|
54096
|
+
if (afterNewline.length > 0) {
|
|
54097
|
+
bodyChildren.push({ type: 'text', value: afterNewline });
|
|
54098
|
+
}
|
|
54099
|
+
bodyChildren.push(...originalChildren.slice(index + 1));
|
|
54100
|
+
return bodyChildren.length > 0 ? bodyChildren : null;
|
|
54101
|
+
};
|
|
54102
|
+
/**
|
|
54103
|
+
* Removes the icon/match prefix from the first text node in a paragraph.
|
|
54104
|
+
* This is needed to clean up the raw AST after we've extracted the icon.
|
|
54105
|
+
*/
|
|
54106
|
+
const removeIconPrefix = (paragraph, prefixLength) => {
|
|
54107
|
+
const firstTextNode = findFirst(paragraph);
|
|
54108
|
+
if (firstTextNode && 'value' in firstTextNode && typeof firstTextNode.value === 'string') {
|
|
54109
|
+
firstTextNode.value = firstTextNode.value.slice(prefixLength);
|
|
54110
|
+
}
|
|
54111
|
+
};
|
|
54112
|
+
const processBlockquote = (node, index, parent, isMdxish = false) => {
|
|
54113
|
+
if (!isCalloutStructure(node)) {
|
|
54114
|
+
// Only stringify empty blockquotes (no extractable text content)
|
|
54115
|
+
// Preserve blockquotes with actual content (e.g., headings, lists, etc.)
|
|
54116
|
+
const content = extractText(node);
|
|
54117
|
+
const isEmpty = !content || content.trim() === '';
|
|
54118
|
+
if (isEmpty && index !== undefined && parent) {
|
|
54119
|
+
const textNode = {
|
|
54120
|
+
type: 'text',
|
|
54121
|
+
value: '>',
|
|
54122
|
+
};
|
|
54123
|
+
const paragraphNode = {
|
|
54124
|
+
type: 'paragraph',
|
|
54125
|
+
children: [textNode],
|
|
54126
|
+
position: node.position,
|
|
54127
|
+
};
|
|
54128
|
+
parent.children.splice(index, 1, paragraphNode);
|
|
54129
|
+
}
|
|
54130
|
+
return;
|
|
54131
|
+
}
|
|
54132
|
+
// isCalloutStructure ensures node.children[0] is a Paragraph with children
|
|
54133
|
+
const firstParagraph = node.children[0];
|
|
54134
|
+
const startText = lib_plain(firstParagraph).toString();
|
|
54135
|
+
const [match, icon] = startText.match(callouts_regex) || [];
|
|
54136
|
+
const firstParagraphOriginalEnd = firstParagraph.position.end;
|
|
54137
|
+
if (icon && match) {
|
|
54138
|
+
// Handle cases where heading and body are on the same line separated by a newline.
|
|
54139
|
+
// Example: "> ⚠️ **Bold heading**\nBody text here"
|
|
54140
|
+
const bodyChildren = splitParagraphAtNewline(firstParagraph);
|
|
54141
|
+
const didSplit = bodyChildren !== null;
|
|
54142
|
+
removeIconPrefix(firstParagraph, match.length);
|
|
54143
|
+
const firstText = findFirst(firstParagraph);
|
|
54144
|
+
const rawValue = firstText?.value ?? '';
|
|
54145
|
+
const hasContent = rawValue.trim().length > 0 || firstParagraph.children.length > 1;
|
|
54146
|
+
const empty = !hasContent;
|
|
54147
|
+
const theme = themes[icon] || 'default';
|
|
54148
|
+
if (hasContent || didSplit) {
|
|
54149
|
+
const headingMatch = rawValue.match(/^(#{1,6})\s*/);
|
|
54150
|
+
// # heading syntax is handled via direct AST manipulation so we can
|
|
54151
|
+
// set the depth while preserving the original inline children (bold, etc.)
|
|
54152
|
+
if (headingMatch) {
|
|
54153
|
+
firstText.value = rawValue.slice(headingMatch[0].length);
|
|
54154
|
+
const heading = wrapHeading(node);
|
|
54155
|
+
heading.depth = headingMatch[1].length;
|
|
54156
|
+
node.children[0] = heading;
|
|
54157
|
+
node.children[0].position.start.offset += match.length;
|
|
54158
|
+
node.children[0].position.start.column += match.length;
|
|
54159
|
+
}
|
|
54160
|
+
else if (isMdxish) {
|
|
54161
|
+
// Block-level title re-parsing is only needed for MDXish where HTML stays
|
|
54162
|
+
// as raw nodes. In MDX, remarkMdx has already converted HTML to JSX AST
|
|
54163
|
+
// nodes which toMarkdown can't serialize — and MDX doesn't need this
|
|
54164
|
+
// block-level title handling anyway.
|
|
54165
|
+
const headingText = toMarkdown({ type: 'root', children: [firstParagraph] }, {
|
|
54166
|
+
extensions: toMarkdownExtensions,
|
|
54167
|
+
})
|
|
54168
|
+
.trim()
|
|
54169
|
+
.replace(/^\\(?=[>#+\-*])/, '');
|
|
54170
|
+
const parsedTitle = titleParser.parse(headingText);
|
|
54171
|
+
const parsedFirstChild = parsedTitle.children[0];
|
|
54172
|
+
// Block-level syntax ("> quote", "- list") produces non-paragraph nodes;
|
|
54173
|
+
// inline text parses as a paragraph and falls through to wrapHeading().
|
|
54174
|
+
if (parsedFirstChild && parsedFirstChild.type !== 'paragraph') {
|
|
54175
|
+
// Strip positions from re-parsed nodes since they're relative to the heading text, not the original source
|
|
54176
|
+
visit(parsedTitle, (n) => {
|
|
54177
|
+
delete n.position;
|
|
54178
|
+
});
|
|
54179
|
+
const heading = wrapHeading(node);
|
|
54180
|
+
heading.children = parsedTitle.children;
|
|
54181
|
+
delete heading.position;
|
|
54182
|
+
node.children[0] = heading;
|
|
54183
|
+
}
|
|
54184
|
+
else {
|
|
54185
|
+
node.children[0] = wrapHeading(node);
|
|
54186
|
+
node.children[0].position.start.offset += match.length;
|
|
54187
|
+
node.children[0].position.start.column += match.length;
|
|
54188
|
+
}
|
|
54189
|
+
}
|
|
54190
|
+
else {
|
|
54191
|
+
node.children[0] = wrapHeading(node);
|
|
54192
|
+
node.children[0].position.start.offset += match.length;
|
|
54193
|
+
node.children[0].position.start.column += match.length;
|
|
54194
|
+
}
|
|
54195
|
+
}
|
|
54196
|
+
// Insert body content as a separate paragraph after the heading
|
|
54197
|
+
if (bodyChildren) {
|
|
54198
|
+
const headingPosition = node.children[0].position;
|
|
54199
|
+
node.children.splice(1, 0, {
|
|
54200
|
+
type: 'paragraph',
|
|
54201
|
+
children: bodyChildren,
|
|
54202
|
+
...(headingPosition && firstParagraphOriginalEnd
|
|
54203
|
+
? {
|
|
54204
|
+
position: {
|
|
54205
|
+
start: headingPosition.end,
|
|
54206
|
+
end: firstParagraphOriginalEnd,
|
|
54207
|
+
},
|
|
54208
|
+
}
|
|
54209
|
+
: {}),
|
|
54210
|
+
});
|
|
54211
|
+
}
|
|
54212
|
+
Object.assign(node, {
|
|
54213
|
+
type: NodeTypes.callout,
|
|
54214
|
+
data: {
|
|
54215
|
+
hName: 'Callout',
|
|
54216
|
+
hProperties: {
|
|
54217
|
+
icon,
|
|
54218
|
+
...(empty && { empty }),
|
|
54219
|
+
theme,
|
|
54220
|
+
},
|
|
54221
|
+
},
|
|
53410
54222
|
});
|
|
53411
54223
|
}
|
|
53412
54224
|
};
|
|
@@ -70489,857 +71301,121 @@ const emojiToName = {
|
|
|
70489
71301
|
'🇾🇪': 'yemen',
|
|
70490
71302
|
'🇾🇹': 'mayotte',
|
|
70491
71303
|
'🇿🇦': 'south_africa',
|
|
70492
|
-
'🇿🇲': 'zambia',
|
|
70493
|
-
'🇿🇼': 'zimbabwe',
|
|
70494
|
-
'🏴': 'england',
|
|
70495
|
-
'🏴': 'scotland',
|
|
70496
|
-
'🏴': 'wales'
|
|
70497
|
-
}
|
|
70498
|
-
|
|
70499
|
-
;// ./lib/owlmoji.ts
|
|
70500
|
-
|
|
70501
|
-
const owlmoji = [
|
|
70502
|
-
{
|
|
70503
|
-
emoji: '', // This `emoji` property doesn't get consumed, but is required for type consistency
|
|
70504
|
-
names: ['owlbert'],
|
|
70505
|
-
tags: ['owlbert'],
|
|
70506
|
-
description: 'an owlbert for any occasion',
|
|
70507
|
-
category: 'ReadMe',
|
|
70508
|
-
},
|
|
70509
|
-
{
|
|
70510
|
-
emoji: '',
|
|
70511
|
-
names: ['owlbert-books'],
|
|
70512
|
-
tags: ['owlbert'],
|
|
70513
|
-
description: 'owlbert carrying books',
|
|
70514
|
-
category: 'ReadMe',
|
|
70515
|
-
},
|
|
70516
|
-
{
|
|
70517
|
-
emoji: '',
|
|
70518
|
-
names: ['owlbert-mask'],
|
|
70519
|
-
tags: ['owlbert'],
|
|
70520
|
-
description: 'owlbert with a respirator',
|
|
70521
|
-
category: 'ReadMe',
|
|
70522
|
-
},
|
|
70523
|
-
{
|
|
70524
|
-
emoji: '',
|
|
70525
|
-
names: ['owlbert-reading'],
|
|
70526
|
-
tags: ['owlbert'],
|
|
70527
|
-
description: 'owlbert reading',
|
|
70528
|
-
category: 'ReadMe',
|
|
70529
|
-
},
|
|
70530
|
-
{
|
|
70531
|
-
emoji: '',
|
|
70532
|
-
names: ['owlbert-thinking'],
|
|
70533
|
-
tags: ['owlbert'],
|
|
70534
|
-
description: 'owlbert thinking',
|
|
70535
|
-
category: 'ReadMe',
|
|
70536
|
-
},
|
|
70537
|
-
];
|
|
70538
|
-
const owlmojiNames = owlmoji.flatMap(emoji => emoji.names);
|
|
70539
|
-
class Owlmoji {
|
|
70540
|
-
static kind = (name) => {
|
|
70541
|
-
if (name in nameToEmoji)
|
|
70542
|
-
return 'gemoji';
|
|
70543
|
-
else if (name.match(/^fa-/))
|
|
70544
|
-
return 'fontawesome';
|
|
70545
|
-
else if (owlmojiNames.includes(name))
|
|
70546
|
-
return 'owlmoji';
|
|
70547
|
-
return null;
|
|
70548
|
-
};
|
|
70549
|
-
static nameToEmoji = nameToEmoji;
|
|
70550
|
-
static owlmoji = gemoji_gemoji.concat(owlmoji);
|
|
70551
|
-
}
|
|
70552
|
-
|
|
70553
|
-
;// ./processor/transform/gemoji+.ts
|
|
70554
|
-
|
|
70555
|
-
|
|
70556
|
-
|
|
70557
|
-
const gemoji_regex = /(?<=^|\s):(?<name>\+1|[-\w]+):/g;
|
|
70558
|
-
const gemojiReplacer = (_, name) => {
|
|
70559
|
-
switch (Owlmoji.kind(name)) {
|
|
70560
|
-
case 'gemoji': {
|
|
70561
|
-
const node = {
|
|
70562
|
-
type: NodeTypes.emoji,
|
|
70563
|
-
value: Owlmoji.nameToEmoji[name],
|
|
70564
|
-
name,
|
|
70565
|
-
};
|
|
70566
|
-
return node;
|
|
70567
|
-
}
|
|
70568
|
-
case 'fontawesome': {
|
|
70569
|
-
const node = {
|
|
70570
|
-
type: NodeTypes.i,
|
|
70571
|
-
value: name,
|
|
70572
|
-
data: {
|
|
70573
|
-
hName: 'i',
|
|
70574
|
-
hProperties: {
|
|
70575
|
-
className: ['fa-regular', name],
|
|
70576
|
-
},
|
|
70577
|
-
},
|
|
70578
|
-
};
|
|
70579
|
-
return node;
|
|
70580
|
-
}
|
|
70581
|
-
case 'owlmoji': {
|
|
70582
|
-
const node = {
|
|
70583
|
-
type: 'image',
|
|
70584
|
-
title: `:${name}:`,
|
|
70585
|
-
alt: `:${name}:`,
|
|
70586
|
-
url: `/public/img/emojis/${name}.png`,
|
|
70587
|
-
data: {
|
|
70588
|
-
hProperties: {
|
|
70589
|
-
className: 'emoji',
|
|
70590
|
-
align: 'absmiddle',
|
|
70591
|
-
height: '20',
|
|
70592
|
-
width: '20',
|
|
70593
|
-
},
|
|
70594
|
-
},
|
|
70595
|
-
};
|
|
70596
|
-
return node;
|
|
70597
|
-
}
|
|
70598
|
-
default:
|
|
70599
|
-
return false;
|
|
70600
|
-
}
|
|
70601
|
-
};
|
|
70602
|
-
const gemojiTransformer = () => (tree) => {
|
|
70603
|
-
findAndReplace(tree, [gemoji_regex, gemojiReplacer]);
|
|
70604
|
-
return tree;
|
|
70605
|
-
};
|
|
70606
|
-
/* harmony default export */ const gemoji_ = (gemojiTransformer);
|
|
70607
|
-
|
|
70608
|
-
;// ./lib/mdast.ts
|
|
70609
|
-
|
|
70610
|
-
const mdast = (text, opts = {}) => {
|
|
70611
|
-
const processor = ast_processor(opts);
|
|
70612
|
-
const tree = processor.parse(text);
|
|
70613
|
-
return processor.runSync(tree);
|
|
70614
|
-
};
|
|
70615
|
-
/* harmony default export */ const lib_mdast = (mdast);
|
|
70616
|
-
|
|
70617
|
-
;// ./lib/utils/mdxish/protect-code-blocks.ts
|
|
70618
|
-
/**
|
|
70619
|
-
* Replaces code blocks and inline code with placeholders to protect them from preprocessing.
|
|
70620
|
-
*
|
|
70621
|
-
* @param content - The markdown content to process
|
|
70622
|
-
* @returns Object containing protected content and arrays of original code blocks
|
|
70623
|
-
* @example
|
|
70624
|
-
* ```typescript
|
|
70625
|
-
* const input = 'Text with `inline code` and ```fenced block```';
|
|
70626
|
-
* protectCodeBlocks(input)
|
|
70627
|
-
* // Returns: {
|
|
70628
|
-
* // protectedCode: {
|
|
70629
|
-
* // codeBlocks: ['```fenced block```'],
|
|
70630
|
-
* // inlineCode: ['`inline code`']
|
|
70631
|
-
* // },
|
|
70632
|
-
* // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
|
|
70633
|
-
* // }
|
|
70634
|
-
* ```
|
|
70635
|
-
*/
|
|
70636
|
-
function protectCodeBlocks(content) {
|
|
70637
|
-
const codeBlocks = [];
|
|
70638
|
-
const inlineCode = [];
|
|
70639
|
-
let protectedContent = '';
|
|
70640
|
-
let remaining = content;
|
|
70641
|
-
let codeBlockStart = remaining.indexOf('```');
|
|
70642
|
-
while (codeBlockStart !== -1) {
|
|
70643
|
-
protectedContent += remaining.slice(0, codeBlockStart);
|
|
70644
|
-
remaining = remaining.slice(codeBlockStart);
|
|
70645
|
-
const codeBlockEnd = remaining.indexOf('```', 3);
|
|
70646
|
-
if (codeBlockEnd === -1) {
|
|
70647
|
-
break;
|
|
70648
|
-
}
|
|
70649
|
-
const match = remaining.slice(0, codeBlockEnd + 3);
|
|
70650
|
-
const index = codeBlocks.length;
|
|
70651
|
-
codeBlocks.push(match);
|
|
70652
|
-
protectedContent += `___CODE_BLOCK_${index}___`;
|
|
70653
|
-
remaining = remaining.slice(codeBlockEnd + 3);
|
|
70654
|
-
codeBlockStart = remaining.indexOf('```');
|
|
70655
|
-
}
|
|
70656
|
-
protectedContent += remaining;
|
|
70657
|
-
protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
|
|
70658
|
-
const index = inlineCode.length;
|
|
70659
|
-
inlineCode.push(match);
|
|
70660
|
-
return `___INLINE_CODE_${index}___`;
|
|
70661
|
-
});
|
|
70662
|
-
return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
|
|
70663
|
-
}
|
|
70664
|
-
/**
|
|
70665
|
-
* Restores inline code by replacing placeholders with original content.
|
|
70666
|
-
*
|
|
70667
|
-
* @param content - Content with inline code placeholders
|
|
70668
|
-
* @param protectedCode - The protected code arrays
|
|
70669
|
-
* @returns Content with inline code restored
|
|
70670
|
-
*/
|
|
70671
|
-
function restoreInlineCode(content, protectedCode) {
|
|
70672
|
-
return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
|
|
70673
|
-
return protectedCode.inlineCode[parseInt(idx, 10)];
|
|
70674
|
-
});
|
|
70675
|
-
}
|
|
70676
|
-
/**
|
|
70677
|
-
* Restores fenced code blocks by replacing placeholders with original content.
|
|
70678
|
-
*
|
|
70679
|
-
* @param content - Content with code block placeholders
|
|
70680
|
-
* @param protectedCode - The protected code arrays
|
|
70681
|
-
* @returns Content with code blocks restored
|
|
70682
|
-
*/
|
|
70683
|
-
function restoreFencedCodeBlocks(content, protectedCode) {
|
|
70684
|
-
return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
|
|
70685
|
-
return protectedCode.codeBlocks[parseInt(idx, 10)];
|
|
70686
|
-
});
|
|
70687
|
-
}
|
|
70688
|
-
/**
|
|
70689
|
-
* Restores all code blocks and inline code by replacing placeholders with original content.
|
|
70690
|
-
*
|
|
70691
|
-
* @param content - Content with code placeholders
|
|
70692
|
-
* @param protectedCode - The protected code arrays
|
|
70693
|
-
* @returns Content with all code blocks and inline code restored
|
|
70694
|
-
* @example
|
|
70695
|
-
* ```typescript
|
|
70696
|
-
* const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
|
|
70697
|
-
* const protectedCode = {
|
|
70698
|
-
* codeBlocks: ['```js\ncode\n```'],
|
|
70699
|
-
* inlineCode: ['`inline`']
|
|
70700
|
-
* };
|
|
70701
|
-
* restoreCodeBlocks(content, protectedCode)
|
|
70702
|
-
* // Returns: 'Text with `inline` and ```js\ncode\n```'
|
|
70703
|
-
* ```
|
|
70704
|
-
*/
|
|
70705
|
-
function restoreCodeBlocks(content, protectedCode) {
|
|
70706
|
-
let restored = restoreFencedCodeBlocks(content, protectedCode);
|
|
70707
|
-
restored = restoreInlineCode(restored, protectedCode);
|
|
70708
|
-
return restored;
|
|
70709
|
-
}
|
|
70710
|
-
|
|
70711
|
-
;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
|
|
70712
|
-
|
|
70713
|
-
// Base64 encode (Node.js + browser compatible)
|
|
70714
|
-
function base64Encode(str) {
|
|
70715
|
-
if (typeof Buffer !== 'undefined') {
|
|
70716
|
-
return Buffer.from(str, 'utf-8').toString('base64');
|
|
70717
|
-
}
|
|
70718
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
70719
|
-
}
|
|
70720
|
-
// Base64 decode (Node.js + browser compatible)
|
|
70721
|
-
function base64Decode(str) {
|
|
70722
|
-
if (typeof Buffer !== 'undefined') {
|
|
70723
|
-
return Buffer.from(str, 'base64').toString('utf-8');
|
|
70724
|
-
}
|
|
70725
|
-
return decodeURIComponent(escape(atob(str)));
|
|
70726
|
-
}
|
|
70727
|
-
function escapeHtmlAttribute(value) {
|
|
70728
|
-
return value
|
|
70729
|
-
.replace(/&/g, '&')
|
|
70730
|
-
.replace(/"/g, '"')
|
|
70731
|
-
.replace(/</g, '<')
|
|
70732
|
-
.replace(/>/g, '>')
|
|
70733
|
-
.replace(/\n/g, ' ');
|
|
70734
|
-
}
|
|
70735
|
-
// Marker prefix for JSON-serialized complex values (arrays/objects)
|
|
70736
|
-
// Using a prefix that won't conflict with regular string values
|
|
70737
|
-
const JSON_VALUE_MARKER = '__MDXISH_JSON__';
|
|
70738
|
-
// Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
|
|
70739
|
-
const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
|
|
70740
|
-
const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
|
|
70741
|
-
/**
|
|
70742
|
-
* Evaluates a JavaScript expression using context variables.
|
|
70743
|
-
*
|
|
70744
|
-
* @param expression
|
|
70745
|
-
* @param context
|
|
70746
|
-
* @returns The evaluated result
|
|
70747
|
-
* @example
|
|
70748
|
-
* ```typescript
|
|
70749
|
-
* const context = { baseUrl: 'https://example.com', path: '/api' };
|
|
70750
|
-
* evaluateExpression('baseUrl + path', context)
|
|
70751
|
-
* // Returns: 'https://example.com/api'
|
|
70752
|
-
* ```
|
|
70753
|
-
*/
|
|
70754
|
-
function evaluateExpression(expression, context) {
|
|
70755
|
-
const contextKeys = Object.keys(context);
|
|
70756
|
-
const contextValues = Object.values(context);
|
|
70757
|
-
// eslint-disable-next-line no-new-func
|
|
70758
|
-
const func = new Function(...contextKeys, `return ${expression}`);
|
|
70759
|
-
return func(...contextValues);
|
|
70760
|
-
}
|
|
70761
|
-
/**
|
|
70762
|
-
* Base64 encodes HTMLBlock template literal content to prevent markdown parser from consuming <script>/<style> tags.
|
|
70763
|
-
*
|
|
70764
|
-
* @param content
|
|
70765
|
-
* @returns Content with HTMLBlock template literals base64 encoded in HTML comments
|
|
70766
|
-
* @example
|
|
70767
|
-
* ```typescript
|
|
70768
|
-
* const input = '<HTMLBlock>{`<script>alert("xss")</script>`}</HTMLBlock>';
|
|
70769
|
-
* protectHTMLBlockContent(input)
|
|
70770
|
-
* // Returns: '<HTMLBlock><!--RDMX_HTMLBLOCK:PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=:RDMX_HTMLBLOCK--></HTMLBlock>'
|
|
70771
|
-
* ```
|
|
70772
|
-
*/
|
|
70773
|
-
function protectHTMLBlockContent(content) {
|
|
70774
|
-
return content.replace(/(<HTMLBlock[^>]*>)\{\s*`((?:[^`\\]|\\.)*)`\s*\}(<\/HTMLBlock>)/g, (_match, openTag, templateContent, closeTag) => {
|
|
70775
|
-
const encoded = base64Encode(templateContent);
|
|
70776
|
-
return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
|
|
70777
|
-
});
|
|
70778
|
-
}
|
|
70779
|
-
/**
|
|
70780
|
-
* Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
|
|
70781
|
-
*
|
|
70782
|
-
* @param content
|
|
70783
|
-
* @returns Content with JSX comments removed
|
|
70784
|
-
* @example
|
|
70785
|
-
* ```typescript
|
|
70786
|
-
* removeJSXComments('Text { /* comment *\/ } more text')
|
|
70787
|
-
* // Returns: 'Text more text'
|
|
70788
|
-
* ```
|
|
70789
|
-
*/
|
|
70790
|
-
function removeJSXComments(content) {
|
|
70791
|
-
return content.replace(/\{\s*\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\s*\}/g, '');
|
|
70792
|
-
}
|
|
70793
|
-
/**
|
|
70794
|
-
* Extracts content between balanced braces, handling nested braces.
|
|
70795
|
-
*
|
|
70796
|
-
* @param content
|
|
70797
|
-
* @param start
|
|
70798
|
-
* @returns Object with extracted content and end position, or null if braces are unbalanced
|
|
70799
|
-
* @example
|
|
70800
|
-
* ```typescript
|
|
70801
|
-
* const input = 'foo{bar{baz}qux}end';
|
|
70802
|
-
* extractBalancedBraces(input, 3) // start at position 3 (after '{')
|
|
70803
|
-
* // Returns: { content: 'bar{baz}qux', end: 16 }
|
|
70804
|
-
* ```
|
|
70805
|
-
*/
|
|
70806
|
-
function extractBalancedBraces(content, start) {
|
|
70807
|
-
let depth = 1;
|
|
70808
|
-
let pos = start;
|
|
70809
|
-
while (pos < content.length && depth > 0) {
|
|
70810
|
-
const char = content[pos];
|
|
70811
|
-
if (char === '{')
|
|
70812
|
-
depth += 1;
|
|
70813
|
-
else if (char === '}')
|
|
70814
|
-
depth -= 1;
|
|
70815
|
-
pos += 1;
|
|
70816
|
-
}
|
|
70817
|
-
if (depth !== 0)
|
|
70818
|
-
return null;
|
|
70819
|
-
return { content: content.slice(start, pos - 1), end: pos };
|
|
70820
|
-
}
|
|
70821
|
-
/**
|
|
70822
|
-
* Escapes problematic braces in content to prevent MDX expression parsing errors.
|
|
70823
|
-
* Handles three cases:
|
|
70824
|
-
* 1. Unbalanced braces (e.g., `{foo` without closing `}`)
|
|
70825
|
-
* 2. Paragraph-spanning expressions (e.g., `{\n\n}` where blank line splits paragraphs)
|
|
70826
|
-
* 3. Skips HTML elements to prevent backslashes appearing in output
|
|
70827
|
-
*
|
|
70828
|
-
*/
|
|
70829
|
-
function escapeProblematicBraces(content) {
|
|
70830
|
-
// Skip HTML elements — their content should never be escaped because
|
|
70831
|
-
// rehypeRaw parses them into hast elements, making `\` literal text in output
|
|
70832
|
-
const htmlElements = [];
|
|
70833
|
-
const safe = content.replace(/<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g, match => {
|
|
70834
|
-
const idx = htmlElements.length;
|
|
70835
|
-
htmlElements.push(match);
|
|
70836
|
-
return `___HTML_ELEM_${idx}___`;
|
|
70837
|
-
});
|
|
70838
|
-
const toEscape = new Set();
|
|
70839
|
-
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
70840
|
-
const chars = Array.from(safe);
|
|
70841
|
-
let strDelim = null;
|
|
70842
|
-
let strEscaped = false;
|
|
70843
|
-
// Stack of open braces with their state
|
|
70844
|
-
const openStack = [];
|
|
70845
|
-
// Track position of last newline (outside strings) to detect blank lines
|
|
70846
|
-
let lastNewlinePos = -2; // -2 means no recent newline
|
|
70847
|
-
for (let i = 0; i < chars.length; i += 1) {
|
|
70848
|
-
const ch = chars[i];
|
|
70849
|
-
// Track string delimiters inside expressions to ignore braces within them
|
|
70850
|
-
if (openStack.length > 0) {
|
|
70851
|
-
if (strDelim) {
|
|
70852
|
-
if (strEscaped)
|
|
70853
|
-
strEscaped = false;
|
|
70854
|
-
else if (ch === '\\')
|
|
70855
|
-
strEscaped = true;
|
|
70856
|
-
else if (ch === strDelim)
|
|
70857
|
-
strDelim = null;
|
|
70858
|
-
// eslint-disable-next-line no-continue
|
|
70859
|
-
continue;
|
|
70860
|
-
}
|
|
70861
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
70862
|
-
strDelim = ch;
|
|
70863
|
-
// eslint-disable-next-line no-continue
|
|
70864
|
-
continue;
|
|
70865
|
-
}
|
|
70866
|
-
// Track newlines to detect blank lines (paragraph boundaries)
|
|
70867
|
-
if (ch === '\n') {
|
|
70868
|
-
// Check if this newline creates a blank line (only whitespace since last newline)
|
|
70869
|
-
if (lastNewlinePos >= 0) {
|
|
70870
|
-
const between = chars.slice(lastNewlinePos + 1, i).join('');
|
|
70871
|
-
if (/^[ \t]*$/.test(between)) {
|
|
70872
|
-
// This is a blank line - mark all open expressions as paragraph-spanning
|
|
70873
|
-
openStack.forEach(entry => {
|
|
70874
|
-
entry.hasBlankLine = true;
|
|
70875
|
-
});
|
|
70876
|
-
}
|
|
70877
|
-
}
|
|
70878
|
-
lastNewlinePos = i;
|
|
70879
|
-
}
|
|
70880
|
-
}
|
|
70881
|
-
// Skip already-escaped braces (count preceding backslashes)
|
|
70882
|
-
if (ch === '{' || ch === '}') {
|
|
70883
|
-
let bs = 0;
|
|
70884
|
-
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
70885
|
-
bs += 1;
|
|
70886
|
-
if (bs % 2 === 1) {
|
|
70887
|
-
// eslint-disable-next-line no-continue
|
|
70888
|
-
continue;
|
|
70889
|
-
}
|
|
70890
|
-
}
|
|
70891
|
-
if (ch === '{') {
|
|
70892
|
-
openStack.push({ pos: i, hasBlankLine: false });
|
|
70893
|
-
lastNewlinePos = -2; // Reset newline tracking for new expression
|
|
70894
|
-
}
|
|
70895
|
-
else if (ch === '}') {
|
|
70896
|
-
if (openStack.length > 0) {
|
|
70897
|
-
const entry = openStack.pop();
|
|
70898
|
-
// If expression spans paragraph boundary, escape both braces
|
|
70899
|
-
if (entry.hasBlankLine) {
|
|
70900
|
-
toEscape.add(entry.pos);
|
|
70901
|
-
toEscape.add(i);
|
|
70902
|
-
}
|
|
70903
|
-
}
|
|
70904
|
-
else {
|
|
70905
|
-
// Unbalanced closing brace (no matching open)
|
|
70906
|
-
toEscape.add(i);
|
|
70907
|
-
}
|
|
70908
|
-
}
|
|
70909
|
-
}
|
|
70910
|
-
// Any remaining open braces are unbalanced
|
|
70911
|
-
openStack.forEach(entry => toEscape.add(entry.pos));
|
|
70912
|
-
// If there are no problematic braces, return safe content as-is;
|
|
70913
|
-
// otherwise, escape each problematic `{` or `}` so MDX doesn't treat them as expressions.
|
|
70914
|
-
let result = toEscape.size === 0
|
|
70915
|
-
? safe
|
|
70916
|
-
: chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
|
|
70917
|
-
// Restore HTML elements
|
|
70918
|
-
if (htmlElements.length > 0) {
|
|
70919
|
-
result = result.replace(/___HTML_ELEM_(\d+)___/g, (_m, idx) => htmlElements[parseInt(idx, 10)]);
|
|
70920
|
-
}
|
|
70921
|
-
return result;
|
|
70922
|
-
}
|
|
70923
|
-
/**
|
|
70924
|
-
* Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
|
|
70925
|
-
* Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
|
|
70926
|
-
*
|
|
70927
|
-
* @param content
|
|
70928
|
-
* @param context
|
|
70929
|
-
* @returns Content with attribute expressions evaluated and converted to HTML attributes
|
|
70930
|
-
* @example
|
|
70931
|
-
* ```typescript
|
|
70932
|
-
* const context = { baseUrl: 'https://example.com' };
|
|
70933
|
-
* const input = '<a href={baseUrl}>Link</a>';
|
|
70934
|
-
* evaluateAttributeExpressions(input, context)
|
|
70935
|
-
* // Returns: '<a href="https://example.com">Link</a>'
|
|
70936
|
-
* ```
|
|
70937
|
-
*/
|
|
70938
|
-
function evaluateAttributeExpressions(content, context, protectedCode) {
|
|
70939
|
-
const attrStartRegex = /(\w+)=\{/g;
|
|
70940
|
-
let result = '';
|
|
70941
|
-
let lastEnd = 0;
|
|
70942
|
-
let match = attrStartRegex.exec(content);
|
|
70943
|
-
while (match !== null) {
|
|
70944
|
-
const attributeName = match[1];
|
|
70945
|
-
const braceStart = match.index + match[0].length;
|
|
70946
|
-
const extracted = extractBalancedBraces(content, braceStart);
|
|
70947
|
-
if (extracted) {
|
|
70948
|
-
// The expression might contain template literals in MDX component tag props
|
|
70949
|
-
// E.g. <Component greeting={`Hello World!`} />
|
|
70950
|
-
// that is marked as inline code. So we need to restore the inline codes
|
|
70951
|
-
// in the expression to evaluate it
|
|
70952
|
-
let expression = extracted.content;
|
|
70953
|
-
if (protectedCode) {
|
|
70954
|
-
expression = restoreInlineCode(expression, protectedCode);
|
|
70955
|
-
}
|
|
70956
|
-
const fullMatchEnd = extracted.end;
|
|
70957
|
-
result += content.slice(lastEnd, match.index);
|
|
70958
|
-
try {
|
|
70959
|
-
const evalResult = evaluateExpression(expression, context);
|
|
70960
|
-
if (typeof evalResult === 'object' && evalResult !== null) {
|
|
70961
|
-
if (attributeName === 'style') {
|
|
70962
|
-
const cssString = Object.entries(evalResult)
|
|
70963
|
-
.map(([key, value]) => {
|
|
70964
|
-
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
70965
|
-
return `${cssKey}: ${value}`;
|
|
70966
|
-
})
|
|
70967
|
-
.join('; ');
|
|
70968
|
-
result += `style="${cssString}"`;
|
|
70969
|
-
}
|
|
70970
|
-
else {
|
|
70971
|
-
// These are arrays / objects attribute values
|
|
70972
|
-
// Mark JSON-serialized values with a prefix so they can be parsed back correctly
|
|
70973
|
-
const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
|
|
70974
|
-
// Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
|
|
70975
|
-
result += `${attributeName}="${jsonValue}"`;
|
|
70976
|
-
}
|
|
70977
|
-
}
|
|
70978
|
-
else if (attributeName === 'className') {
|
|
70979
|
-
// Escape special characters so that it doesn't break and split the attribute value to nodes
|
|
70980
|
-
// This will be restored later in the pipeline
|
|
70981
|
-
result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
70982
|
-
}
|
|
70983
|
-
else {
|
|
70984
|
-
result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
|
|
70985
|
-
}
|
|
70986
|
-
}
|
|
70987
|
-
catch (_error) {
|
|
70988
|
-
result += content.slice(match.index, fullMatchEnd);
|
|
70989
|
-
}
|
|
70990
|
-
lastEnd = fullMatchEnd;
|
|
70991
|
-
attrStartRegex.lastIndex = fullMatchEnd;
|
|
70992
|
-
}
|
|
70993
|
-
match = attrStartRegex.exec(content);
|
|
70994
|
-
}
|
|
70995
|
-
result += content.slice(lastEnd);
|
|
70996
|
-
return result;
|
|
71304
|
+
'🇿🇲': 'zambia',
|
|
71305
|
+
'🇿🇼': 'zimbabwe',
|
|
71306
|
+
'🏴': 'england',
|
|
71307
|
+
'🏴': 'scotland',
|
|
71308
|
+
'🏴': 'wales'
|
|
70997
71309
|
}
|
|
70998
|
-
|
|
70999
|
-
|
|
71000
|
-
|
|
71001
|
-
|
|
71002
|
-
|
|
71003
|
-
|
|
71004
|
-
|
|
71005
|
-
|
|
71006
|
-
|
|
71007
|
-
|
|
71008
|
-
|
|
71009
|
-
|
|
71010
|
-
|
|
71011
|
-
|
|
71012
|
-
|
|
71013
|
-
|
|
71014
|
-
|
|
71015
|
-
|
|
71016
|
-
|
|
71017
|
-
|
|
71018
|
-
|
|
71019
|
-
|
|
71020
|
-
|
|
71310
|
+
|
|
71311
|
+
;// ./lib/owlmoji.ts
|
|
71312
|
+
|
|
71313
|
+
const owlmoji = [
|
|
71314
|
+
{
|
|
71315
|
+
emoji: '', // This `emoji` property doesn't get consumed, but is required for type consistency
|
|
71316
|
+
names: ['owlbert'],
|
|
71317
|
+
tags: ['owlbert'],
|
|
71318
|
+
description: 'an owlbert for any occasion',
|
|
71319
|
+
category: 'ReadMe',
|
|
71320
|
+
},
|
|
71321
|
+
{
|
|
71322
|
+
emoji: '',
|
|
71323
|
+
names: ['owlbert-books'],
|
|
71324
|
+
tags: ['owlbert'],
|
|
71325
|
+
description: 'owlbert carrying books',
|
|
71326
|
+
category: 'ReadMe',
|
|
71327
|
+
},
|
|
71328
|
+
{
|
|
71329
|
+
emoji: '',
|
|
71330
|
+
names: ['owlbert-mask'],
|
|
71331
|
+
tags: ['owlbert'],
|
|
71332
|
+
description: 'owlbert with a respirator',
|
|
71333
|
+
category: 'ReadMe',
|
|
71334
|
+
},
|
|
71335
|
+
{
|
|
71336
|
+
emoji: '',
|
|
71337
|
+
names: ['owlbert-reading'],
|
|
71338
|
+
tags: ['owlbert'],
|
|
71339
|
+
description: 'owlbert reading',
|
|
71340
|
+
category: 'ReadMe',
|
|
71341
|
+
},
|
|
71342
|
+
{
|
|
71343
|
+
emoji: '',
|
|
71344
|
+
names: ['owlbert-thinking'],
|
|
71345
|
+
tags: ['owlbert'],
|
|
71346
|
+
description: 'owlbert thinking',
|
|
71347
|
+
category: 'ReadMe',
|
|
71348
|
+
},
|
|
71349
|
+
];
|
|
71350
|
+
const owlmojiNames = owlmoji.flatMap(emoji => emoji.names);
|
|
71351
|
+
class Owlmoji {
|
|
71352
|
+
static kind = (name) => {
|
|
71353
|
+
if (name in nameToEmoji)
|
|
71354
|
+
return 'gemoji';
|
|
71355
|
+
else if (name.match(/^fa-/))
|
|
71356
|
+
return 'fontawesome';
|
|
71357
|
+
else if (owlmojiNames.includes(name))
|
|
71358
|
+
return 'owlmoji';
|
|
71359
|
+
return null;
|
|
71360
|
+
};
|
|
71361
|
+
static nameToEmoji = nameToEmoji;
|
|
71362
|
+
static owlmoji = gemoji_gemoji.concat(owlmoji);
|
|
71021
71363
|
}
|
|
71022
71364
|
|
|
71023
|
-
;// ./processor/
|
|
71365
|
+
;// ./processor/transform/gemoji+.ts
|
|
71024
71366
|
|
|
71025
71367
|
|
|
71026
71368
|
|
|
71027
|
-
|
|
71028
|
-
|
|
71029
|
-
|
|
71030
|
-
|
|
71031
|
-
|
|
71032
|
-
|
|
71033
|
-
|
|
71034
|
-
|
|
71035
|
-
*/
|
|
71036
|
-
const formatHProps = (node) => {
|
|
71037
|
-
const hProps = getHProps(node);
|
|
71038
|
-
return formatProps(hProps);
|
|
71039
|
-
};
|
|
71040
|
-
/**
|
|
71041
|
-
* Formats an object of props as a string.
|
|
71042
|
-
*
|
|
71043
|
-
* @param {Object} props
|
|
71044
|
-
* @returns {string}
|
|
71045
|
-
*/
|
|
71046
|
-
const formatProps = (props) => {
|
|
71047
|
-
const keys = Object.keys(props);
|
|
71048
|
-
return keys.map(key => `${key}="${props[key]}"`).join(' ');
|
|
71049
|
-
};
|
|
71050
|
-
/**
|
|
71051
|
-
* Returns the hProperties of a node.
|
|
71052
|
-
*
|
|
71053
|
-
* @template T
|
|
71054
|
-
* @param {Node} node
|
|
71055
|
-
* @returns {T} hProperties
|
|
71056
|
-
*/
|
|
71057
|
-
const getHProps = (node) => {
|
|
71058
|
-
const hProps = node.data?.hProperties || {};
|
|
71059
|
-
return hProps;
|
|
71060
|
-
};
|
|
71061
|
-
/**
|
|
71062
|
-
* Returns array of hProperty keys.
|
|
71063
|
-
*
|
|
71064
|
-
* @template T
|
|
71065
|
-
* @param {Node} node
|
|
71066
|
-
* @returns {Array} array of hProperty keys
|
|
71067
|
-
*/
|
|
71068
|
-
const getHPropKeys = (node) => {
|
|
71069
|
-
const hProps = getHProps(node);
|
|
71070
|
-
return Object.keys(hProps) || [];
|
|
71071
|
-
};
|
|
71072
|
-
/**
|
|
71073
|
-
* Gets the attributes of an MDX element and returns them as an object of hProperties.
|
|
71074
|
-
*
|
|
71075
|
-
* @template T
|
|
71076
|
-
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
71077
|
-
* @returns {T} object of hProperties
|
|
71078
|
-
*/
|
|
71079
|
-
const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
|
|
71080
|
-
if ('name' in attr) {
|
|
71081
|
-
if (typeof attr.value === 'string') {
|
|
71082
|
-
if (attr.value.startsWith(JSON_VALUE_MARKER)) {
|
|
71083
|
-
try {
|
|
71084
|
-
memo[attr.name] = JSON.parse(attr.value.slice(JSON_VALUE_MARKER.length));
|
|
71085
|
-
}
|
|
71086
|
-
catch {
|
|
71087
|
-
memo[attr.name] = attr.value;
|
|
71088
|
-
}
|
|
71089
|
-
}
|
|
71090
|
-
else {
|
|
71091
|
-
memo[attr.name] = attr.value;
|
|
71092
|
-
}
|
|
71093
|
-
}
|
|
71094
|
-
else if (attr.value === null) {
|
|
71095
|
-
memo[attr.name] = true;
|
|
71096
|
-
}
|
|
71097
|
-
else if (attr.value.value !== 'undefined') {
|
|
71098
|
-
memo[attr.name] = JSON.parse(attr.value.value);
|
|
71099
|
-
}
|
|
71100
|
-
}
|
|
71101
|
-
return memo;
|
|
71102
|
-
}, {});
|
|
71103
|
-
/**
|
|
71104
|
-
* Gets the children of an MDX element and returns them as an array of Text nodes.
|
|
71105
|
-
* Currently only being used by the HTML Block component, which only expects a single text node.
|
|
71106
|
-
*
|
|
71107
|
-
* @template T
|
|
71108
|
-
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
|
|
71109
|
-
* @returns {Array} array of child text nodes
|
|
71110
|
-
*/
|
|
71111
|
-
const getChildren = (jsx) => jsx.children.reduce((memo, child, i) => {
|
|
71112
|
-
memo[i] = {
|
|
71113
|
-
type: 'text',
|
|
71114
|
-
value: child.value,
|
|
71115
|
-
position: child.position,
|
|
71116
|
-
};
|
|
71117
|
-
return memo;
|
|
71118
|
-
}, []);
|
|
71119
|
-
/**
|
|
71120
|
-
* Tests if a node is an MDX element.
|
|
71121
|
-
* TODO: Make this more extensible to all types of nodes. isElement(node, 'type' or ['type1', 'type2']), say
|
|
71122
|
-
*
|
|
71123
|
-
* @param {Node} node
|
|
71124
|
-
* @returns {(node is MdxJsxFlowElement | MdxJsxTextElement | MdxjsEsm)}
|
|
71125
|
-
*/
|
|
71126
|
-
const isMDXElement = (node) => {
|
|
71127
|
-
return ['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type);
|
|
71128
|
-
};
|
|
71129
|
-
/**
|
|
71130
|
-
* Tests if a node is an MDX ESM element (i.e. import or export).
|
|
71131
|
-
*
|
|
71132
|
-
* @param {Node} node
|
|
71133
|
-
* @returns {boolean}
|
|
71134
|
-
*/
|
|
71135
|
-
const isMDXEsm = (node) => {
|
|
71136
|
-
return node.type === 'mdxjsEsm';
|
|
71137
|
-
};
|
|
71138
|
-
/**
|
|
71139
|
-
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
71140
|
-
* and unindents the HTML.
|
|
71141
|
-
*
|
|
71142
|
-
* @param {string} html - HTML content from template literal
|
|
71143
|
-
* @returns {string} processed HTML
|
|
71144
|
-
*/
|
|
71145
|
-
function formatHtmlForMdxish(html) {
|
|
71146
|
-
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
71147
|
-
// from being parsed prematurely
|
|
71148
|
-
let processed = html;
|
|
71149
|
-
if (processed.startsWith('`') && processed.endsWith('`')) {
|
|
71150
|
-
processed = processed.slice(1, -1);
|
|
71151
|
-
}
|
|
71152
|
-
// Removes the leading/trailing newlines
|
|
71153
|
-
let cleaned = processed.replace(/^\s*\n|\n\s*$/g, '');
|
|
71154
|
-
// Convert literal \n sequences to actual newlines BEFORE processing backticks
|
|
71155
|
-
// This prevents the backtick unescaping regex from incorrectly matching \n sequences
|
|
71156
|
-
cleaned = cleaned.replace(/\\n/g, '\n');
|
|
71157
|
-
// Unescape backticks: \` -> ` (users escape backticks in template literals)
|
|
71158
|
-
// Handle both cases: \` (adjacent) and \ followed by ` (split by markdown parser)
|
|
71159
|
-
cleaned = cleaned.replace(/\\`/g, '`');
|
|
71160
|
-
// Also handle case where backslash and backtick got separated by markdown parsing
|
|
71161
|
-
// Pattern: backslash followed by any characters (but not \n which we already handled), then a backtick
|
|
71162
|
-
// This handles cases like: \example` -> `example` (replacing \ with ` at start)
|
|
71163
|
-
// Exclude \n sequences to avoid matching them incorrectly
|
|
71164
|
-
cleaned = cleaned.replace(/\\([^`\\n]*?)`/g, '`$1`');
|
|
71165
|
-
// Fix case where markdown parser consumed one backtick from triple backticks
|
|
71166
|
-
// Pattern: `` followed by a word (like ``javascript) should be ```javascript
|
|
71167
|
-
// This handles cases where code fences were parsed and one backtick was lost
|
|
71168
|
-
cleaned = cleaned.replace(/<(\w+[^>]*)>``(\w+)/g, '<$1>```$2');
|
|
71169
|
-
// Unescape dollar signs: \$ -> $ (users escape $ in template literals to prevent interpolation)
|
|
71170
|
-
cleaned = cleaned.replace(/\\\$/g, '$');
|
|
71171
|
-
return cleaned;
|
|
71172
|
-
}
|
|
71173
|
-
/**
|
|
71174
|
-
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
|
|
71175
|
-
* and unindents the HTML.
|
|
71176
|
-
*
|
|
71177
|
-
* @param {string} html
|
|
71178
|
-
* @returns {string} formatted HTML
|
|
71179
|
-
*/
|
|
71180
|
-
const formatHTML = (html) => {
|
|
71181
|
-
// Remove leading/trailing backticks if present, since they're used to keep the HTML
|
|
71182
|
-
// from being parsed prematurely
|
|
71183
|
-
if (html.startsWith('`') && html.endsWith('`')) {
|
|
71184
|
-
// eslint-disable-next-line no-param-reassign
|
|
71185
|
-
html = html.slice(1, -1);
|
|
71186
|
-
}
|
|
71187
|
-
// Removes the leading/trailing newlines
|
|
71188
|
-
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');
|
|
71189
|
-
// // Get the number of spaces in the first line to determine the tab size
|
|
71190
|
-
// const tab = cleaned.match(/^\s*/)[0].length;
|
|
71191
|
-
// // Remove the first indentation level from each line
|
|
71192
|
-
// const tabRegex = new RegExp(`^\\s{${tab}}`, 'gm');
|
|
71193
|
-
// const unindented = cleaned.replace(tabRegex, '');
|
|
71194
|
-
return cleaned;
|
|
71195
|
-
};
|
|
71196
|
-
/**
|
|
71197
|
-
* Reformat HTML for the markdown/mdx by adding an indentation to each line. This assures that the
|
|
71198
|
-
* HTML is indentend properly within the HTMLBlock component when rendered in the markdown/mdx.
|
|
71199
|
-
*
|
|
71200
|
-
* @param {string} html
|
|
71201
|
-
* @param {number} [indent=2]
|
|
71202
|
-
* @returns {string} re-formatted HTML
|
|
71203
|
-
*/
|
|
71204
|
-
const reformatHTML = (html) => {
|
|
71205
|
-
// Remove leading/trailing newlines
|
|
71206
|
-
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '').replaceAll(/(?<!\\(\\\\)*)`/g, '\\`');
|
|
71207
|
-
// // Create a tab/indent with the specified number of spaces
|
|
71208
|
-
// const tab = ' '.repeat(indent);
|
|
71209
|
-
// // Indent each line of the HTML (converts to an array, indents each line, then joins back)
|
|
71210
|
-
// const indented = cleaned.split('\n').map((line: string) => `${tab}${line}`).join('\n');
|
|
71211
|
-
return cleaned;
|
|
71212
|
-
};
|
|
71213
|
-
const toAttributes = (object, keys = []) => {
|
|
71214
|
-
const attributes = [];
|
|
71215
|
-
Object.entries(object).forEach(([name, v]) => {
|
|
71216
|
-
if (keys.length > 0 && !keys.includes(name))
|
|
71217
|
-
return;
|
|
71218
|
-
let value;
|
|
71219
|
-
if (typeof v === 'undefined' || v === null || v === '' || v === false) {
|
|
71220
|
-
return;
|
|
71221
|
-
}
|
|
71222
|
-
else if (typeof v === 'string') {
|
|
71223
|
-
value = v;
|
|
71224
|
-
}
|
|
71225
|
-
else {
|
|
71226
|
-
/* values can be null, undefined, string, or a expression, eg:
|
|
71227
|
-
*
|
|
71228
|
-
* ```
|
|
71229
|
-
* <Image src="..." border={false} size={width - 20} />
|
|
71230
|
-
* ```
|
|
71231
|
-
*
|
|
71232
|
-
* Parsing the expression seems to only be done by the library
|
|
71233
|
-
* `mdast-util-mdx-jsx`, and so the most straight forward way to parse
|
|
71234
|
-
* the expression and get the appropriate AST is with our `mdast`
|
|
71235
|
-
* function.
|
|
71236
|
-
*/
|
|
71237
|
-
const proxy = lib_mdast(`{${v}}`);
|
|
71238
|
-
const data = proxy.children[0].data;
|
|
71239
|
-
value = {
|
|
71240
|
-
type: 'mdxJsxAttributeValueExpression',
|
|
71241
|
-
value: v.toString(),
|
|
71242
|
-
data,
|
|
71369
|
+
const gemoji_regex = /(?<=^|\s):(?<name>\+1|[-\w]+):/g;
|
|
71370
|
+
const gemojiReplacer = (_, name) => {
|
|
71371
|
+
switch (Owlmoji.kind(name)) {
|
|
71372
|
+
case 'gemoji': {
|
|
71373
|
+
const node = {
|
|
71374
|
+
type: NodeTypes.emoji,
|
|
71375
|
+
value: Owlmoji.nameToEmoji[name],
|
|
71376
|
+
name,
|
|
71243
71377
|
};
|
|
71378
|
+
return node;
|
|
71244
71379
|
}
|
|
71245
|
-
|
|
71246
|
-
|
|
71247
|
-
|
|
71248
|
-
|
|
71249
|
-
|
|
71250
|
-
|
|
71251
|
-
|
|
71252
|
-
|
|
71253
|
-
|
|
71254
|
-
|
|
71255
|
-
|
|
71256
|
-
|
|
71257
|
-
* example:
|
|
71258
|
-
* ```
|
|
71259
|
-
* const mdx = `export const Foo = 'bar';`
|
|
71260
|
-
*
|
|
71261
|
-
* hasNamedExport(mdast(mdx), 'Foo') => true
|
|
71262
|
-
* ```
|
|
71263
|
-
*
|
|
71264
|
-
*/
|
|
71265
|
-
const hasNamedExport = (tree, name) => {
|
|
71266
|
-
let hasExport = false;
|
|
71267
|
-
// eslint-disable-next-line consistent-return
|
|
71268
|
-
visit(tree, 'mdxjsEsm', node => {
|
|
71269
|
-
if ('declaration' in node.data.estree.body[0] &&
|
|
71270
|
-
node.data.estree.body[0].declaration.type === 'VariableDeclaration') {
|
|
71271
|
-
const { declarations } = node.data.estree.body[0].declaration;
|
|
71272
|
-
hasExport = !!declarations.find(declaration => 'name' in declaration.id && declaration.id.name === name);
|
|
71273
|
-
return hasExport ? EXIT : CONTINUE;
|
|
71380
|
+
case 'fontawesome': {
|
|
71381
|
+
const node = {
|
|
71382
|
+
type: NodeTypes.i,
|
|
71383
|
+
value: name,
|
|
71384
|
+
data: {
|
|
71385
|
+
hName: 'i',
|
|
71386
|
+
hProperties: {
|
|
71387
|
+
className: ['fa-regular', name],
|
|
71388
|
+
},
|
|
71389
|
+
},
|
|
71390
|
+
};
|
|
71391
|
+
return node;
|
|
71274
71392
|
}
|
|
71275
|
-
|
|
71276
|
-
|
|
71277
|
-
|
|
71278
|
-
|
|
71279
|
-
|
|
71280
|
-
|
|
71281
|
-
|
|
71282
|
-
|
|
71283
|
-
|
|
71284
|
-
|
|
71285
|
-
|
|
71286
|
-
|
|
71287
|
-
{
|
|
71288
|
-
"type": "ExportNamedDeclaration",
|
|
71289
|
-
"declaration": {
|
|
71290
|
-
"type": "VariableDeclaration",
|
|
71291
|
-
"declarations": [
|
|
71292
|
-
{
|
|
71293
|
-
"type": "VariableDeclarator",
|
|
71294
|
-
"id": {
|
|
71295
|
-
"type": "Identifier",
|
|
71296
|
-
"name": "Foo" // --------> This is the export name
|
|
71393
|
+
case 'owlmoji': {
|
|
71394
|
+
const node = {
|
|
71395
|
+
type: 'image',
|
|
71396
|
+
title: `:${name}:`,
|
|
71397
|
+
alt: `:${name}:`,
|
|
71398
|
+
url: `/public/img/emojis/${name}.png`,
|
|
71399
|
+
data: {
|
|
71400
|
+
hProperties: {
|
|
71401
|
+
className: 'emoji',
|
|
71402
|
+
align: 'absmiddle',
|
|
71403
|
+
height: '20',
|
|
71404
|
+
width: '20',
|
|
71297
71405
|
},
|
|
71298
|
-
...
|
|
71299
|
-
|
|
71300
|
-
2/3. FUNCTION DECLARATION & CLASS DECLARATION
|
|
71301
|
-
"estree": {
|
|
71302
|
-
"type": "Program",
|
|
71303
|
-
"body": [
|
|
71304
|
-
{
|
|
71305
|
-
"type": "ExportNamedDeclaration",
|
|
71306
|
-
"declaration": {
|
|
71307
|
-
"type": "ClassDeclaration" | "FunctionDeclaration",
|
|
71308
|
-
"id": {
|
|
71309
|
-
"type": "Identifier",
|
|
71310
|
-
"name": "Foo" // --------> This is the export name
|
|
71311
71406
|
},
|
|
71312
|
-
|
|
71313
|
-
|
|
71314
|
-
|
|
71315
|
-
|
|
71316
|
-
|
|
71317
|
-
|
|
71318
|
-
const body = node.data?.estree.body;
|
|
71319
|
-
if (!body)
|
|
71320
|
-
return;
|
|
71321
|
-
body.forEach(child => {
|
|
71322
|
-
if (child.type === 'ExportNamedDeclaration') {
|
|
71323
|
-
// There are three types of ExportNamedDeclaration that we need to consider: VariableDeclaration, FunctionDeclaration, ClassDeclaration
|
|
71324
|
-
const declaration = child.declaration;
|
|
71325
|
-
// FunctionDeclaration and ClassDeclaration have the same structure
|
|
71326
|
-
if (declaration.type !== 'VariableDeclaration') {
|
|
71327
|
-
// Note: declaration.id.type is always 'Identifier' for FunctionDeclarations and ClassDeclarations
|
|
71328
|
-
set.add(declaration.id.name);
|
|
71329
|
-
}
|
|
71330
|
-
else {
|
|
71331
|
-
declaration.declarations.forEach(dec => {
|
|
71332
|
-
const id = dec.id;
|
|
71333
|
-
if (id.type === 'Identifier') {
|
|
71334
|
-
set.add(id.name);
|
|
71335
|
-
}
|
|
71336
|
-
});
|
|
71337
|
-
}
|
|
71338
|
-
}
|
|
71339
|
-
});
|
|
71340
|
-
});
|
|
71341
|
-
return Array.from(set);
|
|
71407
|
+
};
|
|
71408
|
+
return node;
|
|
71409
|
+
}
|
|
71410
|
+
default:
|
|
71411
|
+
return false;
|
|
71412
|
+
}
|
|
71342
71413
|
};
|
|
71414
|
+
const gemojiTransformer = () => (tree) => {
|
|
71415
|
+
findAndReplace(tree, [gemoji_regex, gemojiReplacer]);
|
|
71416
|
+
return tree;
|
|
71417
|
+
};
|
|
71418
|
+
/* harmony default export */ const gemoji_ = (gemojiTransformer);
|
|
71343
71419
|
|
|
71344
71420
|
;// ./processor/transform/handle-missing-components.ts
|
|
71345
71421
|
|
|
@@ -72300,6 +72376,23 @@ const mdxishTables = () => tree => {
|
|
|
72300
72376
|
return;
|
|
72301
72377
|
try {
|
|
72302
72378
|
const parsed = tableNodeProcessor.runSync(tableNodeProcessor.parse(node.value));
|
|
72379
|
+
// since we use a subparser in `tableNodeProcessor` to parse `node.value`,
|
|
72380
|
+
// positions are relative to that substring. shifting them by the base
|
|
72381
|
+
// offset and line number makes them valid in the outer source coordinate space.
|
|
72382
|
+
// otherwise, consumers who directly slice based on position would read and grab the
|
|
72383
|
+
// wrong content
|
|
72384
|
+
const baseOffset = node.position?.start?.offset ?? 0;
|
|
72385
|
+
const baseLine = (node.position?.start?.line ?? 1) - 1;
|
|
72386
|
+
visit(parsed, child => {
|
|
72387
|
+
if (child.position?.start) {
|
|
72388
|
+
child.position.start.offset = (child.position.start.offset ?? 0) + baseOffset;
|
|
72389
|
+
child.position.start.line += baseLine;
|
|
72390
|
+
}
|
|
72391
|
+
if (child.position?.end) {
|
|
72392
|
+
child.position.end.offset = (child.position.end.offset ?? 0) + baseOffset;
|
|
72393
|
+
child.position.end.line += baseLine;
|
|
72394
|
+
}
|
|
72395
|
+
});
|
|
72303
72396
|
visit(parsed, isMDXElement, (tableNode) => {
|
|
72304
72397
|
if (tableNode.name === 'Table') {
|
|
72305
72398
|
processTableNode(tableNode, index, parent, node.position);
|
|
@@ -92710,74 +92803,6 @@ const codeTabs = (node, _, state, info) => {
|
|
|
92710
92803
|
};
|
|
92711
92804
|
/* harmony default export */ const compile_code_tabs = (codeTabs);
|
|
92712
92805
|
|
|
92713
|
-
;// ./processor/compile/compatibility.ts
|
|
92714
|
-
|
|
92715
|
-
|
|
92716
|
-
|
|
92717
|
-
/*
|
|
92718
|
-
* Converts a (remark < v9) html node to a JSX string.
|
|
92719
|
-
*
|
|
92720
|
-
* First we replace html comments with the JSX equivalent. Then, we parse that
|
|
92721
|
-
* as html, and serialize it back as xml!
|
|
92722
|
-
*
|
|
92723
|
-
*/
|
|
92724
|
-
const compileHtml = (node) => {
|
|
92725
|
-
const string = node.value.replaceAll(/<!--(.*)-->/gms, '{/*$1*/}');
|
|
92726
|
-
return string;
|
|
92727
|
-
};
|
|
92728
|
-
const figureToImageBlock = (node) => {
|
|
92729
|
-
const { align, border, width, src, url, alt, title, ...image } = node.children.find((child) => child.type === 'image');
|
|
92730
|
-
const { className } = image.data.hProperties;
|
|
92731
|
-
const figcaption = node.children.find((child) => child.type === 'figcaption');
|
|
92732
|
-
const caption = figcaption ? toMarkdown(figcaption.children).trim() : null;
|
|
92733
|
-
const attributes = {
|
|
92734
|
-
...(align && { align }),
|
|
92735
|
-
...(alt && { alt }),
|
|
92736
|
-
...(className && { border: className === 'border' }),
|
|
92737
|
-
...(border && { border }),
|
|
92738
|
-
...(caption && { caption }),
|
|
92739
|
-
...(title && { title }),
|
|
92740
|
-
...(width && { width }),
|
|
92741
|
-
src: src || url,
|
|
92742
|
-
};
|
|
92743
|
-
return `<Image ${formatProps(attributes)} />`;
|
|
92744
|
-
};
|
|
92745
|
-
const embedToEmbedBlock = (node) => {
|
|
92746
|
-
const { html, ...embed } = node.data.hProperties;
|
|
92747
|
-
const attributes = {
|
|
92748
|
-
...embed,
|
|
92749
|
-
...(html && { html: encodeURIComponent(html) }),
|
|
92750
|
-
};
|
|
92751
|
-
return `<Embed ${formatProps(attributes)} />`;
|
|
92752
|
-
};
|
|
92753
|
-
const compatibility = (node) => {
|
|
92754
|
-
switch (node.type) {
|
|
92755
|
-
case NodeTypes.glossary: {
|
|
92756
|
-
// Glossary terms will no longer be serialized as special node types in the Editor but we want to ensure that we compile historical
|
|
92757
|
-
// data correctly
|
|
92758
|
-
const term = node.data?.hProperties?.term || node.children[0].value;
|
|
92759
|
-
return `<Glossary>${term}</Glossary>`;
|
|
92760
|
-
}
|
|
92761
|
-
case NodeTypes.reusableContent:
|
|
92762
|
-
return `<${node.tag} />`;
|
|
92763
|
-
case 'html':
|
|
92764
|
-
return compileHtml(node);
|
|
92765
|
-
case 'escape':
|
|
92766
|
-
return `\\${node.value}`;
|
|
92767
|
-
case 'figure':
|
|
92768
|
-
return figureToImageBlock(node);
|
|
92769
|
-
case 'embed':
|
|
92770
|
-
return embedToEmbedBlock(node);
|
|
92771
|
-
case 'i':
|
|
92772
|
-
return `:${node.data.hProperties.className[1]}:`;
|
|
92773
|
-
case 'yaml':
|
|
92774
|
-
return `---\n${node.value}\n---`;
|
|
92775
|
-
default:
|
|
92776
|
-
throw new Error('Unhandled node type!');
|
|
92777
|
-
}
|
|
92778
|
-
};
|
|
92779
|
-
/* harmony default export */ const compile_compatibility = (compatibility);
|
|
92780
|
-
|
|
92781
92806
|
;// ./processor/compile/embed.ts
|
|
92782
92807
|
|
|
92783
92808
|
const embed_embed = (node) => {
|
|
@@ -98924,7 +98949,7 @@ function findJsxTableToken() {
|
|
|
98924
98949
|
return undefined;
|
|
98925
98950
|
}
|
|
98926
98951
|
function enterJsxTable(token) {
|
|
98927
|
-
jsx_table_contextMap.set(token, { chunks: [] });
|
|
98952
|
+
jsx_table_contextMap.set(token, { chunks: [], lastEndLine: token.start.line });
|
|
98928
98953
|
this.enter({ type: 'html', value: '' }, token);
|
|
98929
98954
|
}
|
|
98930
98955
|
function exitJsxTableData(token) {
|
|
@@ -98932,14 +98957,20 @@ function exitJsxTableData(token) {
|
|
|
98932
98957
|
if (!tableToken)
|
|
98933
98958
|
return;
|
|
98934
98959
|
const ctx = jsx_table_contextMap.get(tableToken);
|
|
98935
|
-
if (ctx)
|
|
98960
|
+
if (ctx) {
|
|
98961
|
+
const gap = token.start.line - ctx.lastEndLine;
|
|
98962
|
+
if (ctx.chunks.length > 0 && gap > 0) {
|
|
98963
|
+
ctx.chunks.push('\n'.repeat(gap));
|
|
98964
|
+
}
|
|
98936
98965
|
ctx.chunks.push(this.sliceSerialize(token));
|
|
98966
|
+
ctx.lastEndLine = token.end.line;
|
|
98967
|
+
}
|
|
98937
98968
|
}
|
|
98938
98969
|
function exitJsxTable(token) {
|
|
98939
98970
|
const ctx = jsx_table_contextMap.get(token);
|
|
98940
98971
|
const node = this.stack[this.stack.length - 1];
|
|
98941
98972
|
if (ctx) {
|
|
98942
|
-
node.value = ctx.chunks.join('
|
|
98973
|
+
node.value = ctx.chunks.join('');
|
|
98943
98974
|
jsx_table_contextMap.delete(token);
|
|
98944
98975
|
}
|
|
98945
98976
|
this.exit(token);
|