@readme/markdown 13.1.2 → 13.1.4
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/dist/lib/utils/extractMagicBlocks.d.ts +25 -0
- package/dist/main.js +123 -24
- package/dist/main.node.js +123 -24
- package/dist/main.node.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface BlockHit {
|
|
2
|
+
key: string;
|
|
3
|
+
raw: string;
|
|
4
|
+
token: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The content matching in this regex captures everything between `[block:TYPE]`
|
|
8
|
+
* and `[/block]`, including new lines. Negative lookahead for the closing
|
|
9
|
+
* `[/block]` tag is required to prevent greedy matching to ensure it stops at
|
|
10
|
+
* the first closing tag it encounters preventing vulnerability to polynomial
|
|
11
|
+
* backtracking issues.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MAGIC_BLOCK_REGEX: RegExp;
|
|
14
|
+
/**
|
|
15
|
+
* Extract legacy magic block syntax from a markdown string.
|
|
16
|
+
* Returns the modified markdown and an array of extracted blocks.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractMagicBlocks(markdown: string): {
|
|
19
|
+
replaced: string;
|
|
20
|
+
blocks: BlockHit[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Restore extracted magic blocks back into a markdown string.
|
|
24
|
+
*/
|
|
25
|
+
export declare function restoreMagicBlocks(replaced: string, blocks: BlockHit[]): string;
|
package/dist/main.js
CHANGED
|
@@ -70954,13 +70954,15 @@ const scanForClosingTag = (parent, startIndex, tag) => {
|
|
|
70954
70954
|
if (isClosingTag(siblingValue, tag)) {
|
|
70955
70955
|
return { closingIndex: i, extraClosingChildren: [] };
|
|
70956
70956
|
}
|
|
70957
|
-
// Embedded closing tag (closing tag
|
|
70957
|
+
// Embedded closing tag (closing tag within HTML block content)
|
|
70958
70958
|
if (siblingValue.includes(closingTagStr)) {
|
|
70959
|
-
const
|
|
70959
|
+
const closeTagPos = siblingValue.indexOf(closingTagStr);
|
|
70960
|
+
const contentBeforeClose = siblingValue.substring(0, closeTagPos).trim();
|
|
70961
|
+
const contentAfterClose = siblingValue.substring(closeTagPos + closingTagStr.length).trim();
|
|
70960
70962
|
const extraChildren = contentBeforeClose
|
|
70961
70963
|
? parseMdChildren(contentBeforeClose)
|
|
70962
70964
|
: [];
|
|
70963
|
-
return { closingIndex: i, extraClosingChildren: extraChildren };
|
|
70965
|
+
return { closingIndex: i, extraClosingChildren: extraChildren, contentAfterClose: contentAfterClose || undefined };
|
|
70964
70966
|
}
|
|
70965
70967
|
}
|
|
70966
70968
|
// Check paragraph siblings
|
|
@@ -71105,7 +71107,7 @@ const mdxishComponentBlocks = () => tree => {
|
|
|
71105
71107
|
const scanResult = scanForClosingTag(parent, index, tag);
|
|
71106
71108
|
if (!scanResult)
|
|
71107
71109
|
return;
|
|
71108
|
-
const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
|
|
71110
|
+
const { closingIndex, extraClosingChildren, strippedParagraph, contentAfterClose: remainingAfterClose } = scanResult;
|
|
71109
71111
|
const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
|
|
71110
71112
|
// Collect all intermediate siblings between opening tag and closing tag
|
|
71111
71113
|
const intermediateChildren = parent.children.slice(index + 1, closingIndex);
|
|
@@ -71128,6 +71130,11 @@ const mdxishComponentBlocks = () => tree => {
|
|
|
71128
71130
|
if (componentNode.children.length > 0) {
|
|
71129
71131
|
stack.push(componentNode);
|
|
71130
71132
|
}
|
|
71133
|
+
// If the closing tag sibling had content after it (e.g., another component opening tag),
|
|
71134
|
+
// re-insert it as a sibling so it can be processed in subsequent iterations
|
|
71135
|
+
if (remainingAfterClose) {
|
|
71136
|
+
parseSibling(stack, parent, index, remainingAfterClose);
|
|
71137
|
+
}
|
|
71131
71138
|
};
|
|
71132
71139
|
// Process the nodes with the components depth-first to maintain the correct order of the nodes
|
|
71133
71140
|
while (stack.length) {
|
|
@@ -71360,6 +71367,10 @@ const types = {
|
|
|
71360
71367
|
Recipe: NodeTypes.recipe,
|
|
71361
71368
|
TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
|
|
71362
71369
|
};
|
|
71370
|
+
// Node types that are phrasing (inline) content per the mdast spec. Phrasing
|
|
71371
|
+
// content at the document root violates the spec and causes mdx() to collapse
|
|
71372
|
+
// blank lines, so these must be wrapped in a paragraph when at root level.
|
|
71373
|
+
const phrasingTypes = new Set([NodeTypes.variable]);
|
|
71363
71374
|
var TableNames;
|
|
71364
71375
|
(function (TableNames) {
|
|
71365
71376
|
TableNames["td"] = "td";
|
|
@@ -71515,7 +71526,16 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
|
|
|
71515
71526
|
type: types[node.name],
|
|
71516
71527
|
position: node.position,
|
|
71517
71528
|
};
|
|
71518
|
-
|
|
71529
|
+
// Wrap in a paragraph if at root level. Links are phrasing content and
|
|
71530
|
+
// root children must all be the same category (per mdast spec). Mixing
|
|
71531
|
+
// phrasing with flow content (headings, paragraphs, etc.) causes mdx()
|
|
71532
|
+
// to collapse blank lines in the document.
|
|
71533
|
+
if (parent.type === 'root') {
|
|
71534
|
+
parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
|
|
71535
|
+
}
|
|
71536
|
+
else {
|
|
71537
|
+
parent.children[index] = mdNode;
|
|
71538
|
+
}
|
|
71519
71539
|
}
|
|
71520
71540
|
else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
|
|
71521
71541
|
const hProperties = getAttrs(node);
|
|
@@ -71541,7 +71561,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
|
|
|
71541
71561
|
},
|
|
71542
71562
|
position: node.position,
|
|
71543
71563
|
};
|
|
71544
|
-
parent.
|
|
71564
|
+
if (parent.type === 'root' && phrasingTypes.has(types[node.name])) {
|
|
71565
|
+
// @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
|
|
71566
|
+
parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
|
|
71567
|
+
}
|
|
71568
|
+
else {
|
|
71569
|
+
parent.children[index] = mdNode;
|
|
71570
|
+
}
|
|
71545
71571
|
}
|
|
71546
71572
|
};
|
|
71547
71573
|
const readmeComponents = (opts) => () => tree => {
|
|
@@ -93425,7 +93451,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
|
|
|
93425
93451
|
return false;
|
|
93426
93452
|
}
|
|
93427
93453
|
/** Parse and replace text children with processed markdown */
|
|
93428
|
-
function parseTextChildren(node, processMarkdown) {
|
|
93454
|
+
function parseTextChildren(node, processMarkdown, components) {
|
|
93429
93455
|
if (!node.children?.length)
|
|
93430
93456
|
return;
|
|
93431
93457
|
// First pass: Recursively process text children as they may contain stringified markdown / mdx content
|
|
@@ -93440,6 +93466,27 @@ function parseTextChildren(node, processMarkdown) {
|
|
|
93440
93466
|
}
|
|
93441
93467
|
return children;
|
|
93442
93468
|
});
|
|
93469
|
+
// Unwrap <p> elements whose meaningful children are ALL components.
|
|
93470
|
+
// The markdown parser wraps inline content in <p> tags, but when that content
|
|
93471
|
+
// is actually component children (e.g., <Tab> inside <Tabs>), the wrapper
|
|
93472
|
+
// should be removed so components appear as direct children.
|
|
93473
|
+
// Only unwrap when every non-whitespace, non-br child is a known component
|
|
93474
|
+
// to avoid breaking paragraphs with mixed content (text + inline HTML like <code>).
|
|
93475
|
+
node.children = node.children.flatMap(child => {
|
|
93476
|
+
if (child.type !== 'element' || child.tagName !== 'p')
|
|
93477
|
+
return [child];
|
|
93478
|
+
const meaningfulChildren = child.children.filter(gc => {
|
|
93479
|
+
if (gc.type === 'text' && !gc.value.trim())
|
|
93480
|
+
return false;
|
|
93481
|
+
if (gc.type === 'element' && gc.tagName === 'br')
|
|
93482
|
+
return false;
|
|
93483
|
+
return true;
|
|
93484
|
+
});
|
|
93485
|
+
const allComponents = meaningfulChildren.length > 0 && meaningfulChildren.every(gc => gc.type === 'element' && getComponentName(gc.tagName, components));
|
|
93486
|
+
if (!allComponents)
|
|
93487
|
+
return [child];
|
|
93488
|
+
return meaningfulChildren;
|
|
93489
|
+
});
|
|
93443
93490
|
// Post-processing: remove whitespace-only text nodes if all siblings are components
|
|
93444
93491
|
// This prevents whitespace between component children from being counted as extra children
|
|
93445
93492
|
if (areAllChildrenComponents(node.children)) {
|
|
@@ -93494,7 +93541,7 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
|
|
|
93494
93541
|
}
|
|
93495
93542
|
node.tagName = componentName;
|
|
93496
93543
|
normalizeProperties(node);
|
|
93497
|
-
parseTextChildren(node, processMarkdown);
|
|
93544
|
+
parseTextChildren(node, processMarkdown, components);
|
|
93498
93545
|
});
|
|
93499
93546
|
// Remove unknown components in reverse order to preserve indices
|
|
93500
93547
|
for (let i = nodesToRemove.length - 1; i >= 0; i -= 1) {
|
|
@@ -93795,8 +93842,10 @@ function escapeUnbalancedBraces(content) {
|
|
|
93795
93842
|
const unbalanced = new Set();
|
|
93796
93843
|
let strDelim = null;
|
|
93797
93844
|
let strEscaped = false;
|
|
93798
|
-
|
|
93799
|
-
|
|
93845
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
93846
|
+
const chars = Array.from(content);
|
|
93847
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
93848
|
+
const ch = chars[i];
|
|
93800
93849
|
// Track strings inside expressions to ignore braces within them
|
|
93801
93850
|
if (opens.length > 0) {
|
|
93802
93851
|
if (strDelim) {
|
|
@@ -93818,7 +93867,7 @@ function escapeUnbalancedBraces(content) {
|
|
|
93818
93867
|
// Skip already-escaped braces (count preceding backslashes)
|
|
93819
93868
|
if (ch === '{' || ch === '}') {
|
|
93820
93869
|
let bs = 0;
|
|
93821
|
-
for (let j = i - 1; j >= 0 &&
|
|
93870
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
93822
93871
|
bs += 1;
|
|
93823
93872
|
if (bs % 2 === 1) {
|
|
93824
93873
|
// eslint-disable-next-line no-continue
|
|
@@ -93837,7 +93886,7 @@ function escapeUnbalancedBraces(content) {
|
|
|
93837
93886
|
opens.forEach(pos => unbalanced.add(pos));
|
|
93838
93887
|
if (unbalanced.size === 0)
|
|
93839
93888
|
return content;
|
|
93840
|
-
return
|
|
93889
|
+
return chars
|
|
93841
93890
|
.map((ch, i) => (unbalanced.has(i) ? `\\${ch}` : ch))
|
|
93842
93891
|
.join('');
|
|
93843
93892
|
}
|
|
@@ -97742,8 +97791,59 @@ const mdxishTags_tags = (doc) => {
|
|
|
97742
97791
|
};
|
|
97743
97792
|
/* harmony default export */ const mdxishTags = (mdxishTags_tags);
|
|
97744
97793
|
|
|
97745
|
-
;// ./lib/
|
|
97794
|
+
;// ./lib/utils/extractMagicBlocks.ts
|
|
97795
|
+
/**
|
|
97796
|
+
* The content matching in this regex captures everything between `[block:TYPE]`
|
|
97797
|
+
* and `[/block]`, including new lines. Negative lookahead for the closing
|
|
97798
|
+
* `[/block]` tag is required to prevent greedy matching to ensure it stops at
|
|
97799
|
+
* the first closing tag it encounters preventing vulnerability to polynomial
|
|
97800
|
+
* backtracking issues.
|
|
97801
|
+
*/
|
|
97802
|
+
const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
|
|
97803
|
+
/**
|
|
97804
|
+
* Extract legacy magic block syntax from a markdown string.
|
|
97805
|
+
* Returns the modified markdown and an array of extracted blocks.
|
|
97806
|
+
*/
|
|
97807
|
+
function extractMagicBlocks(markdown) {
|
|
97808
|
+
const blocks = [];
|
|
97809
|
+
let index = 0;
|
|
97810
|
+
const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
|
|
97811
|
+
/**
|
|
97812
|
+
* Key is the unique identifier for the magic block
|
|
97813
|
+
*/
|
|
97814
|
+
const key = `__MAGIC_BLOCK_${index}__`;
|
|
97815
|
+
/**
|
|
97816
|
+
* Token is a wrapper around the `key` to serialize & influence how the
|
|
97817
|
+
* magic block is parsed in the remark pipeline.
|
|
97818
|
+
* - Use backticks so it becomes a code span, preventing `remarkParse` from
|
|
97819
|
+
* parsing special characters in the token as markdown syntax
|
|
97820
|
+
* - Prepend a newline to ensure it is parsed as a block level node
|
|
97821
|
+
* - Append a newline to ensure it is separated from following content
|
|
97822
|
+
*/
|
|
97823
|
+
const token = `\n\`${key}\`\n`;
|
|
97824
|
+
blocks.push({ key, raw: match, token });
|
|
97825
|
+
index += 1;
|
|
97826
|
+
return token;
|
|
97827
|
+
});
|
|
97828
|
+
return { replaced, blocks };
|
|
97829
|
+
}
|
|
97830
|
+
/**
|
|
97831
|
+
* Restore extracted magic blocks back into a markdown string.
|
|
97832
|
+
*/
|
|
97833
|
+
function restoreMagicBlocks(replaced, blocks) {
|
|
97834
|
+
// If a magic block is at the start or end of the document, the extraction
|
|
97835
|
+
// token's newlines will have been trimmed during processing. We need to
|
|
97836
|
+
// account for that here to ensure the token is found and replaced correctly.
|
|
97837
|
+
// These extra newlines will be removed again when the final string is trimmed.
|
|
97838
|
+
const content = `\n${replaced}\n`;
|
|
97839
|
+
const restoredContent = blocks.reduce((acc, { token, raw }) => {
|
|
97840
|
+
// Ensure each magic block is separated by newlines when restored.
|
|
97841
|
+
return acc.split(token).join(`\n${raw}\n`);
|
|
97842
|
+
}, content);
|
|
97843
|
+
return restoredContent.trim();
|
|
97844
|
+
}
|
|
97746
97845
|
|
|
97846
|
+
;// ./lib/stripComments.ts
|
|
97747
97847
|
|
|
97748
97848
|
|
|
97749
97849
|
|
|
@@ -97758,19 +97858,16 @@ const mdxishTags_tags = (doc) => {
|
|
|
97758
97858
|
* Removes Markdown and MDX comments.
|
|
97759
97859
|
*/
|
|
97760
97860
|
async function stripComments(doc, { mdx, mdxish } = {}) {
|
|
97761
|
-
const
|
|
97762
|
-
const
|
|
97861
|
+
const { replaced, blocks } = extractMagicBlocks(doc);
|
|
97862
|
+
const processor = unified();
|
|
97763
97863
|
// we still require these two extensions because:
|
|
97764
|
-
// 1. we
|
|
97864
|
+
// 1. we can rely on remarkMdx to parse MDXish
|
|
97765
97865
|
// 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
|
|
97766
97866
|
if (mdxish) {
|
|
97767
|
-
|
|
97768
|
-
|
|
97867
|
+
processor
|
|
97868
|
+
.data('micromarkExtensions', [mdxExpression({ allowEmpty: true })])
|
|
97869
|
+
.data('fromMarkdownExtensions', [mdxExpressionFromMarkdown()]);
|
|
97769
97870
|
}
|
|
97770
|
-
const processor = unified()
|
|
97771
|
-
.data('micromarkExtensions', micromarkExtensions)
|
|
97772
|
-
.data('fromMarkdownExtensions', fromMarkdownExtensions)
|
|
97773
|
-
.data('toMarkdownExtensions', [magicBlockToMarkdown()]);
|
|
97774
97871
|
processor
|
|
97775
97872
|
.use(remarkParse)
|
|
97776
97873
|
.use(normalize_malformed_md_syntax)
|
|
@@ -97804,8 +97901,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
|
|
|
97804
97901
|
},
|
|
97805
97902
|
],
|
|
97806
97903
|
});
|
|
97807
|
-
const file = await processor.process(
|
|
97808
|
-
|
|
97904
|
+
const file = await processor.process(replaced);
|
|
97905
|
+
const stringified = String(file).trim();
|
|
97906
|
+
const restored = restoreMagicBlocks(stringified, blocks);
|
|
97907
|
+
return restored;
|
|
97809
97908
|
}
|
|
97810
97909
|
/* harmony default export */ const lib_stripComments = (stripComments);
|
|
97811
97910
|
|
package/dist/main.node.js
CHANGED
|
@@ -91158,13 +91158,15 @@ const scanForClosingTag = (parent, startIndex, tag) => {
|
|
|
91158
91158
|
if (isClosingTag(siblingValue, tag)) {
|
|
91159
91159
|
return { closingIndex: i, extraClosingChildren: [] };
|
|
91160
91160
|
}
|
|
91161
|
-
// Embedded closing tag (closing tag
|
|
91161
|
+
// Embedded closing tag (closing tag within HTML block content)
|
|
91162
91162
|
if (siblingValue.includes(closingTagStr)) {
|
|
91163
|
-
const
|
|
91163
|
+
const closeTagPos = siblingValue.indexOf(closingTagStr);
|
|
91164
|
+
const contentBeforeClose = siblingValue.substring(0, closeTagPos).trim();
|
|
91165
|
+
const contentAfterClose = siblingValue.substring(closeTagPos + closingTagStr.length).trim();
|
|
91164
91166
|
const extraChildren = contentBeforeClose
|
|
91165
91167
|
? parseMdChildren(contentBeforeClose)
|
|
91166
91168
|
: [];
|
|
91167
|
-
return { closingIndex: i, extraClosingChildren: extraChildren };
|
|
91169
|
+
return { closingIndex: i, extraClosingChildren: extraChildren, contentAfterClose: contentAfterClose || undefined };
|
|
91168
91170
|
}
|
|
91169
91171
|
}
|
|
91170
91172
|
// Check paragraph siblings
|
|
@@ -91309,7 +91311,7 @@ const mdxishComponentBlocks = () => tree => {
|
|
|
91309
91311
|
const scanResult = scanForClosingTag(parent, index, tag);
|
|
91310
91312
|
if (!scanResult)
|
|
91311
91313
|
return;
|
|
91312
|
-
const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
|
|
91314
|
+
const { closingIndex, extraClosingChildren, strippedParagraph, contentAfterClose: remainingAfterClose } = scanResult;
|
|
91313
91315
|
const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
|
|
91314
91316
|
// Collect all intermediate siblings between opening tag and closing tag
|
|
91315
91317
|
const intermediateChildren = parent.children.slice(index + 1, closingIndex);
|
|
@@ -91332,6 +91334,11 @@ const mdxishComponentBlocks = () => tree => {
|
|
|
91332
91334
|
if (componentNode.children.length > 0) {
|
|
91333
91335
|
stack.push(componentNode);
|
|
91334
91336
|
}
|
|
91337
|
+
// If the closing tag sibling had content after it (e.g., another component opening tag),
|
|
91338
|
+
// re-insert it as a sibling so it can be processed in subsequent iterations
|
|
91339
|
+
if (remainingAfterClose) {
|
|
91340
|
+
parseSibling(stack, parent, index, remainingAfterClose);
|
|
91341
|
+
}
|
|
91335
91342
|
};
|
|
91336
91343
|
// Process the nodes with the components depth-first to maintain the correct order of the nodes
|
|
91337
91344
|
while (stack.length) {
|
|
@@ -91564,6 +91571,10 @@ const readme_components_types = {
|
|
|
91564
91571
|
Recipe: NodeTypes.recipe,
|
|
91565
91572
|
TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
|
|
91566
91573
|
};
|
|
91574
|
+
// Node types that are phrasing (inline) content per the mdast spec. Phrasing
|
|
91575
|
+
// content at the document root violates the spec and causes mdx() to collapse
|
|
91576
|
+
// blank lines, so these must be wrapped in a paragraph when at root level.
|
|
91577
|
+
const phrasingTypes = new Set([NodeTypes.variable]);
|
|
91567
91578
|
var TableNames;
|
|
91568
91579
|
(function (TableNames) {
|
|
91569
91580
|
TableNames["td"] = "td";
|
|
@@ -91719,7 +91730,16 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
|
|
|
91719
91730
|
type: readme_components_types[node.name],
|
|
91720
91731
|
position: node.position,
|
|
91721
91732
|
};
|
|
91722
|
-
|
|
91733
|
+
// Wrap in a paragraph if at root level. Links are phrasing content and
|
|
91734
|
+
// root children must all be the same category (per mdast spec). Mixing
|
|
91735
|
+
// phrasing with flow content (headings, paragraphs, etc.) causes mdx()
|
|
91736
|
+
// to collapse blank lines in the document.
|
|
91737
|
+
if (parent.type === 'root') {
|
|
91738
|
+
parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
|
|
91739
|
+
}
|
|
91740
|
+
else {
|
|
91741
|
+
parent.children[index] = mdNode;
|
|
91742
|
+
}
|
|
91723
91743
|
}
|
|
91724
91744
|
else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
|
|
91725
91745
|
const hProperties = getAttrs(node);
|
|
@@ -91745,7 +91765,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
|
|
|
91745
91765
|
},
|
|
91746
91766
|
position: node.position,
|
|
91747
91767
|
};
|
|
91748
|
-
parent.
|
|
91768
|
+
if (parent.type === 'root' && phrasingTypes.has(readme_components_types[node.name])) {
|
|
91769
|
+
// @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
|
|
91770
|
+
parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
|
|
91771
|
+
}
|
|
91772
|
+
else {
|
|
91773
|
+
parent.children[index] = mdNode;
|
|
91774
|
+
}
|
|
91749
91775
|
}
|
|
91750
91776
|
};
|
|
91751
91777
|
const readmeComponents = (opts) => () => tree => {
|
|
@@ -113629,7 +113655,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
|
|
|
113629
113655
|
return false;
|
|
113630
113656
|
}
|
|
113631
113657
|
/** Parse and replace text children with processed markdown */
|
|
113632
|
-
function parseTextChildren(node, processMarkdown) {
|
|
113658
|
+
function parseTextChildren(node, processMarkdown, components) {
|
|
113633
113659
|
if (!node.children?.length)
|
|
113634
113660
|
return;
|
|
113635
113661
|
// First pass: Recursively process text children as they may contain stringified markdown / mdx content
|
|
@@ -113644,6 +113670,27 @@ function parseTextChildren(node, processMarkdown) {
|
|
|
113644
113670
|
}
|
|
113645
113671
|
return children;
|
|
113646
113672
|
});
|
|
113673
|
+
// Unwrap <p> elements whose meaningful children are ALL components.
|
|
113674
|
+
// The markdown parser wraps inline content in <p> tags, but when that content
|
|
113675
|
+
// is actually component children (e.g., <Tab> inside <Tabs>), the wrapper
|
|
113676
|
+
// should be removed so components appear as direct children.
|
|
113677
|
+
// Only unwrap when every non-whitespace, non-br child is a known component
|
|
113678
|
+
// to avoid breaking paragraphs with mixed content (text + inline HTML like <code>).
|
|
113679
|
+
node.children = node.children.flatMap(child => {
|
|
113680
|
+
if (child.type !== 'element' || child.tagName !== 'p')
|
|
113681
|
+
return [child];
|
|
113682
|
+
const meaningfulChildren = child.children.filter(gc => {
|
|
113683
|
+
if (gc.type === 'text' && !gc.value.trim())
|
|
113684
|
+
return false;
|
|
113685
|
+
if (gc.type === 'element' && gc.tagName === 'br')
|
|
113686
|
+
return false;
|
|
113687
|
+
return true;
|
|
113688
|
+
});
|
|
113689
|
+
const allComponents = meaningfulChildren.length > 0 && meaningfulChildren.every(gc => gc.type === 'element' && getComponentName(gc.tagName, components));
|
|
113690
|
+
if (!allComponents)
|
|
113691
|
+
return [child];
|
|
113692
|
+
return meaningfulChildren;
|
|
113693
|
+
});
|
|
113647
113694
|
// Post-processing: remove whitespace-only text nodes if all siblings are components
|
|
113648
113695
|
// This prevents whitespace between component children from being counted as extra children
|
|
113649
113696
|
if (areAllChildrenComponents(node.children)) {
|
|
@@ -113698,7 +113745,7 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
|
|
|
113698
113745
|
}
|
|
113699
113746
|
node.tagName = componentName;
|
|
113700
113747
|
normalizeProperties(node);
|
|
113701
|
-
parseTextChildren(node, processMarkdown);
|
|
113748
|
+
parseTextChildren(node, processMarkdown, components);
|
|
113702
113749
|
});
|
|
113703
113750
|
// Remove unknown components in reverse order to preserve indices
|
|
113704
113751
|
for (let i = nodesToRemove.length - 1; i >= 0; i -= 1) {
|
|
@@ -113999,8 +114046,10 @@ function escapeUnbalancedBraces(content) {
|
|
|
113999
114046
|
const unbalanced = new Set();
|
|
114000
114047
|
let strDelim = null;
|
|
114001
114048
|
let strEscaped = false;
|
|
114002
|
-
|
|
114003
|
-
|
|
114049
|
+
// Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
|
|
114050
|
+
const chars = Array.from(content);
|
|
114051
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
114052
|
+
const ch = chars[i];
|
|
114004
114053
|
// Track strings inside expressions to ignore braces within them
|
|
114005
114054
|
if (opens.length > 0) {
|
|
114006
114055
|
if (strDelim) {
|
|
@@ -114022,7 +114071,7 @@ function escapeUnbalancedBraces(content) {
|
|
|
114022
114071
|
// Skip already-escaped braces (count preceding backslashes)
|
|
114023
114072
|
if (ch === '{' || ch === '}') {
|
|
114024
114073
|
let bs = 0;
|
|
114025
|
-
for (let j = i - 1; j >= 0 &&
|
|
114074
|
+
for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
|
|
114026
114075
|
bs += 1;
|
|
114027
114076
|
if (bs % 2 === 1) {
|
|
114028
114077
|
// eslint-disable-next-line no-continue
|
|
@@ -114041,7 +114090,7 @@ function escapeUnbalancedBraces(content) {
|
|
|
114041
114090
|
opens.forEach(pos => unbalanced.add(pos));
|
|
114042
114091
|
if (unbalanced.size === 0)
|
|
114043
114092
|
return content;
|
|
114044
|
-
return
|
|
114093
|
+
return chars
|
|
114045
114094
|
.map((ch, i) => (unbalanced.has(i) ? `\\${ch}` : ch))
|
|
114046
114095
|
.join('');
|
|
114047
114096
|
}
|
|
@@ -117946,8 +117995,59 @@ const mdxishTags_tags = (doc) => {
|
|
|
117946
117995
|
};
|
|
117947
117996
|
/* harmony default export */ const mdxishTags = (mdxishTags_tags);
|
|
117948
117997
|
|
|
117949
|
-
;// ./lib/
|
|
117998
|
+
;// ./lib/utils/extractMagicBlocks.ts
|
|
117999
|
+
/**
|
|
118000
|
+
* The content matching in this regex captures everything between `[block:TYPE]`
|
|
118001
|
+
* and `[/block]`, including new lines. Negative lookahead for the closing
|
|
118002
|
+
* `[/block]` tag is required to prevent greedy matching to ensure it stops at
|
|
118003
|
+
* the first closing tag it encounters preventing vulnerability to polynomial
|
|
118004
|
+
* backtracking issues.
|
|
118005
|
+
*/
|
|
118006
|
+
const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
|
|
118007
|
+
/**
|
|
118008
|
+
* Extract legacy magic block syntax from a markdown string.
|
|
118009
|
+
* Returns the modified markdown and an array of extracted blocks.
|
|
118010
|
+
*/
|
|
118011
|
+
function extractMagicBlocks(markdown) {
|
|
118012
|
+
const blocks = [];
|
|
118013
|
+
let index = 0;
|
|
118014
|
+
const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
|
|
118015
|
+
/**
|
|
118016
|
+
* Key is the unique identifier for the magic block
|
|
118017
|
+
*/
|
|
118018
|
+
const key = `__MAGIC_BLOCK_${index}__`;
|
|
118019
|
+
/**
|
|
118020
|
+
* Token is a wrapper around the `key` to serialize & influence how the
|
|
118021
|
+
* magic block is parsed in the remark pipeline.
|
|
118022
|
+
* - Use backticks so it becomes a code span, preventing `remarkParse` from
|
|
118023
|
+
* parsing special characters in the token as markdown syntax
|
|
118024
|
+
* - Prepend a newline to ensure it is parsed as a block level node
|
|
118025
|
+
* - Append a newline to ensure it is separated from following content
|
|
118026
|
+
*/
|
|
118027
|
+
const token = `\n\`${key}\`\n`;
|
|
118028
|
+
blocks.push({ key, raw: match, token });
|
|
118029
|
+
index += 1;
|
|
118030
|
+
return token;
|
|
118031
|
+
});
|
|
118032
|
+
return { replaced, blocks };
|
|
118033
|
+
}
|
|
118034
|
+
/**
|
|
118035
|
+
* Restore extracted magic blocks back into a markdown string.
|
|
118036
|
+
*/
|
|
118037
|
+
function restoreMagicBlocks(replaced, blocks) {
|
|
118038
|
+
// If a magic block is at the start or end of the document, the extraction
|
|
118039
|
+
// token's newlines will have been trimmed during processing. We need to
|
|
118040
|
+
// account for that here to ensure the token is found and replaced correctly.
|
|
118041
|
+
// These extra newlines will be removed again when the final string is trimmed.
|
|
118042
|
+
const content = `\n${replaced}\n`;
|
|
118043
|
+
const restoredContent = blocks.reduce((acc, { token, raw }) => {
|
|
118044
|
+
// Ensure each magic block is separated by newlines when restored.
|
|
118045
|
+
return acc.split(token).join(`\n${raw}\n`);
|
|
118046
|
+
}, content);
|
|
118047
|
+
return restoredContent.trim();
|
|
118048
|
+
}
|
|
117950
118049
|
|
|
118050
|
+
;// ./lib/stripComments.ts
|
|
117951
118051
|
|
|
117952
118052
|
|
|
117953
118053
|
|
|
@@ -117962,19 +118062,16 @@ const mdxishTags_tags = (doc) => {
|
|
|
117962
118062
|
* Removes Markdown and MDX comments.
|
|
117963
118063
|
*/
|
|
117964
118064
|
async function stripComments(doc, { mdx, mdxish } = {}) {
|
|
117965
|
-
const
|
|
117966
|
-
const
|
|
118065
|
+
const { replaced, blocks } = extractMagicBlocks(doc);
|
|
118066
|
+
const processor = unified();
|
|
117967
118067
|
// we still require these two extensions because:
|
|
117968
|
-
// 1. we
|
|
118068
|
+
// 1. we can rely on remarkMdx to parse MDXish
|
|
117969
118069
|
// 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
|
|
117970
118070
|
if (mdxish) {
|
|
117971
|
-
|
|
117972
|
-
|
|
118071
|
+
processor
|
|
118072
|
+
.data('micromarkExtensions', [mdxExpression({ allowEmpty: true })])
|
|
118073
|
+
.data('fromMarkdownExtensions', [mdxExpressionFromMarkdown()]);
|
|
117973
118074
|
}
|
|
117974
|
-
const processor = unified()
|
|
117975
|
-
.data('micromarkExtensions', micromarkExtensions)
|
|
117976
|
-
.data('fromMarkdownExtensions', fromMarkdownExtensions)
|
|
117977
|
-
.data('toMarkdownExtensions', [magicBlockToMarkdown()]);
|
|
117978
118075
|
processor
|
|
117979
118076
|
.use(remarkParse)
|
|
117980
118077
|
.use(normalize_malformed_md_syntax)
|
|
@@ -118008,8 +118105,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
|
|
|
118008
118105
|
},
|
|
118009
118106
|
],
|
|
118010
118107
|
});
|
|
118011
|
-
const file = await processor.process(
|
|
118012
|
-
|
|
118108
|
+
const file = await processor.process(replaced);
|
|
118109
|
+
const stringified = String(file).trim();
|
|
118110
|
+
const restored = restoreMagicBlocks(stringified, blocks);
|
|
118111
|
+
return restored;
|
|
118013
118112
|
}
|
|
118014
118113
|
/* harmony default export */ const lib_stripComments = (stripComments);
|
|
118015
118114
|
|