@readme/markdown 13.1.1 → 13.1.3

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/main.node.js CHANGED
@@ -25560,8 +25560,7 @@ function color(d) {
25560
25560
 
25561
25561
  ;// ./node_modules/unist-util-visit-parents/lib/index.js
25562
25562
  /**
25563
- * @typedef {import('unist').Node} UnistNode
25564
- * @typedef {import('unist').Parent} UnistParent
25563
+ * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
25565
25564
  */
25566
25565
 
25567
25566
  /**
@@ -25609,8 +25608,10 @@ function color(d) {
25609
25608
 
25610
25609
  /**
25611
25610
  * @typedef {(
25612
- * Check extends Array<any>
25613
- * ? MatchesOne<Value, Check[keyof Check]>
25611
+ * Check extends ReadonlyArray<infer T>
25612
+ * ? MatchesOne<Value, T>
25613
+ * : Check extends Array<infer T>
25614
+ * ? MatchesOne<Value, T>
25614
25615
  * : MatchesOne<Value, Check>
25615
25616
  * )} Matches
25616
25617
  * Check whether a node matches a check in the type system.
@@ -25880,9 +25881,9 @@ function visitParents(tree, test, visitor, reverse) {
25880
25881
  typeof value.tagName === 'string'
25881
25882
  ? value.tagName
25882
25883
  : // `xast`
25883
- typeof value.name === 'string'
25884
- ? value.name
25885
- : undefined
25884
+ typeof value.name === 'string'
25885
+ ? value.name
25886
+ : undefined
25886
25887
 
25887
25888
  Object.defineProperty(visit, 'name', {
25888
25889
  value:
@@ -91563,6 +91564,10 @@ const readme_components_types = {
91563
91564
  Recipe: NodeTypes.recipe,
91564
91565
  TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
91565
91566
  };
91567
+ // Node types that are phrasing (inline) content per the mdast spec. Phrasing
91568
+ // content at the document root violates the spec and causes mdx() to collapse
91569
+ // blank lines, so these must be wrapped in a paragraph when at root level.
91570
+ const phrasingTypes = new Set([NodeTypes.variable]);
91566
91571
  var TableNames;
91567
91572
  (function (TableNames) {
91568
91573
  TableNames["td"] = "td";
@@ -91718,6 +91723,28 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
91718
91723
  type: readme_components_types[node.name],
91719
91724
  position: node.position,
91720
91725
  };
91726
+ // Wrap in a paragraph if at root level. Links are phrasing content and
91727
+ // root children must all be the same category (per mdast spec). Mixing
91728
+ // phrasing with flow content (headings, paragraphs, etc.) causes mdx()
91729
+ // to collapse blank lines in the document.
91730
+ if (parent.type === 'root') {
91731
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
91732
+ }
91733
+ else {
91734
+ parent.children[index] = mdNode;
91735
+ }
91736
+ }
91737
+ else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
91738
+ const hProperties = getAttrs(node);
91739
+ const mdNode = {
91740
+ ...hProperties,
91741
+ type: readme_components_types[node.name],
91742
+ data: {
91743
+ hName: node.name,
91744
+ ...(Object.keys(hProperties).length && { hProperties }),
91745
+ },
91746
+ position: node.position,
91747
+ };
91721
91748
  parent.children[index] = mdNode;
91722
91749
  }
91723
91750
  else if (node.name in readme_components_types) {
@@ -91731,7 +91758,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
91731
91758
  },
91732
91759
  position: node.position,
91733
91760
  };
91734
- parent.children[index] = mdNode;
91761
+ if (parent.type === 'root' && phrasingTypes.has(readme_components_types[node.name])) {
91762
+ // @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
91763
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
91764
+ }
91765
+ else {
91766
+ parent.children[index] = mdNode;
91767
+ }
91735
91768
  }
91736
91769
  };
91737
91770
  const readmeComponents = (opts) => () => tree => {
@@ -113448,6 +113481,63 @@ function rehypeStringify(options) {
113448
113481
  }
113449
113482
  }
113450
113483
 
113484
+ ;// ./node_modules/mdast-util-newline-to-break/lib/index.js
113485
+ /**
113486
+ * @typedef {import('mdast').Nodes} Nodes
113487
+ * @typedef {import('mdast-util-find-and-replace').ReplaceFunction} ReplaceFunction
113488
+ */
113489
+
113490
+
113491
+
113492
+ /**
113493
+ * Turn normal line endings into hard breaks.
113494
+ *
113495
+ * @param {Nodes} tree
113496
+ * Tree to change.
113497
+ * @returns {undefined}
113498
+ * Nothing.
113499
+ */
113500
+ function newlineToBreak(tree) {
113501
+ findAndReplace(tree, [/\r?\n|\r/g, lib_replace])
113502
+ }
113503
+
113504
+ /**
113505
+ * Replace line endings.
113506
+ *
113507
+ * @type {ReplaceFunction}
113508
+ */
113509
+ function lib_replace() {
113510
+ return {type: 'break'}
113511
+ }
113512
+
113513
+ ;// ./node_modules/remark-breaks/lib/index.js
113514
+ /**
113515
+ * @typedef {import('mdast').Root} Root
113516
+ */
113517
+
113518
+
113519
+
113520
+ /**
113521
+ * Support hard breaks without needing spaces or escapes (turns enters into
113522
+ * `<br>`s).
113523
+ *
113524
+ * @returns
113525
+ * Transform.
113526
+ */
113527
+ function remarkBreaks() {
113528
+ /**
113529
+ * Transform.
113530
+ *
113531
+ * @param {Root} tree
113532
+ * Tree.
113533
+ * @returns {undefined}
113534
+ * Nothing.
113535
+ */
113536
+ return function (tree) {
113537
+ newlineToBreak(tree)
113538
+ }
113539
+ }
113540
+
113451
113541
  ;// ./lib/utils/mdxish/mdxish-get-component-name.ts
113452
113542
  /** Convert a string to PascalCase */
113453
113543
  function toPascalCase(str) {
@@ -114126,6 +114216,71 @@ const evaluateExpressions = ({ context = {} } = {}) => tree => {
114126
114216
  };
114127
114217
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
114128
114218
 
114219
+ ;// ./node_modules/rehype-parse/lib/index.js
114220
+ /**
114221
+ * @import {Root} from 'hast'
114222
+ * @import {Options as FromHtmlOptions} from 'hast-util-from-html'
114223
+ * @import {Parser, Processor} from 'unified'
114224
+ */
114225
+
114226
+ /**
114227
+ * @typedef {Omit<FromHtmlOptions, 'onerror'> & RehypeParseFields} Options
114228
+ * Configuration.
114229
+ *
114230
+ * @typedef RehypeParseFields
114231
+ * Extra fields.
114232
+ * @property {boolean | null | undefined} [emitParseErrors=false]
114233
+ * Whether to emit parse errors while parsing (default: `false`).
114234
+ *
114235
+ * > 👉 **Note**: parse errors are currently being added to HTML.
114236
+ * > Not all errors emitted by parse5 (or us) are specced yet.
114237
+ * > Some documentation may still be missing.
114238
+ */
114239
+
114240
+
114241
+
114242
+ /**
114243
+ * Plugin to add support for parsing from HTML.
114244
+ *
114245
+ * > 👉 **Note**: this is not an XML parser.
114246
+ * > It supports SVG as embedded in HTML.
114247
+ * > It does not support the features available in XML.
114248
+ * > Passing SVG files might break but fragments of modern SVG should be fine.
114249
+ * > Use [`xast-util-from-xml`][xast-util-from-xml] to parse XML.
114250
+ *
114251
+ * @param {Options | null | undefined} [options]
114252
+ * Configuration (optional).
114253
+ * @returns {undefined}
114254
+ * Nothing.
114255
+ */
114256
+ function rehypeParse(options) {
114257
+ /** @type {Processor<Root>} */
114258
+ // @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly.
114259
+ const self = this
114260
+ const {emitParseErrors, ...settings} = {...self.data('settings'), ...options}
114261
+
114262
+ self.parser = parser
114263
+
114264
+ /**
114265
+ * @type {Parser<Root>}
114266
+ */
114267
+ function parser(document, file) {
114268
+ return fromHtml(document, {
114269
+ ...settings,
114270
+ onerror: emitParseErrors
114271
+ ? function (message) {
114272
+ if (file.path) {
114273
+ message.name = file.path + ':' + message.name
114274
+ message.file = file.path
114275
+ }
114276
+
114277
+ file.messages.push(message)
114278
+ }
114279
+ : undefined
114280
+ })
114281
+ }
114282
+ }
114283
+
114129
114284
  ;// ./processor/transform/mdxish/normalize-malformed-md-syntax.ts
114130
114285
 
114131
114286
  // Marker patterns for multi-node emphasis detection
@@ -114469,6 +114624,18 @@ const normalizeEmphasisAST = () => (tree) => {
114469
114624
  };
114470
114625
  /* harmony default export */ const normalize_malformed_md_syntax = (normalizeEmphasisAST);
114471
114626
 
114627
+ ;// ./processor/transform/mdxish/magic-blocks/patterns.ts
114628
+ /** Matches HTML tags (open, close, self-closing) with optional attributes. */
114629
+ const HTML_TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9-]*)((?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)>/g;
114630
+ /** Matches an HTML element from its opening tag to the matching closing tag. */
114631
+ const HTML_ELEMENT_BLOCK_RE = /<([a-zA-Z][a-zA-Z0-9-]*)[\s>][\s\S]*?<\/\1>/g;
114632
+ /** Matches a newline with surrounding horizontal whitespace. */
114633
+ const NEWLINE_WITH_WHITESPACE_RE = /[^\S\n]*\n[^\S\n]*/g;
114634
+ /** Matches a closing block-level tag followed by non-tag text or by a newline then non-blank content. */
114635
+ const CLOSE_BLOCK_TAG_BOUNDARY_RE = /<\/([a-zA-Z][a-zA-Z0-9-]*)>\s*(?:(?!<)(\S)|\n([^\n]))/g;
114636
+ /** Tests whether a string contains a complete HTML element (open + close tag). */
114637
+ const COMPLETE_HTML_ELEMENT_RE = /<[a-zA-Z][^>]*>[\s\S]*<\/[a-zA-Z]/;
114638
+
114472
114639
  ;// ./processor/transform/mdxish/magic-blocks/placeholder.ts
114473
114640
  const EMPTY_IMAGE_PLACEHOLDER = {
114474
114641
  type: 'image',
@@ -114522,6 +114689,14 @@ const EMPTY_CODE_PLACEHOLDER = {
114522
114689
 
114523
114690
 
114524
114691
 
114692
+
114693
+
114694
+
114695
+
114696
+
114697
+
114698
+
114699
+
114525
114700
  /**
114526
114701
  * Wraps a node in a "pinned" container if sidebar: true is set.
114527
114702
  */
@@ -114549,12 +114724,125 @@ const imgWidthBySize = new Proxy(imgSizeValues, {
114549
114724
  });
114550
114725
  const textToInline = (text) => [{ type: 'text', value: text }];
114551
114726
  const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
114552
- /** Parses markdown and html to markdown nodes */
114553
- const contentParser = unified().use(remarkParse).use(remarkGfm).use(normalize_malformed_md_syntax);
114727
+ /**
114728
+ * Converts leading newlines in magic block content to `<br>` tags.
114729
+ * Leading newlines are stripped by remark-parse before they become soft break nodes,
114730
+ * so remark-breaks cannot handle them. We convert them to HTML `<br>` tags instead.
114731
+ */
114732
+ const ensureLeadingBreaks = (text) => text.replace(/^\n+/, match => '<br>'.repeat(match.length));
114733
+ /** Preprocesses magic block body content before parsing. */
114734
+ const preprocessBody = (text) => {
114735
+ return ensureLeadingBreaks(text);
114736
+ };
114737
+ /** Markdown parser */
114738
+ const contentParser = unified().use(remarkParse).use(remarkBreaks).use(remarkGfm).use(normalize_malformed_md_syntax);
114739
+ /** Markdown to HTML processor (mdast → hast → HTML string) */
114740
+ const markdownToHtml = unified()
114741
+ .use(remarkParse)
114742
+ .use(remarkGfm)
114743
+ .use(normalize_malformed_md_syntax)
114744
+ .use(remarkRehype)
114745
+ .use(rehypeStringify);
114746
+ /** HTML parser (HTML string → hast) */
114747
+ const htmlParser = unified().use(rehypeParse, { fragment: true });
114748
+ /** HTML stringifier (hast → HTML string) */
114749
+ const htmlStringifier = unified().use(rehypeStringify);
114750
+ /** Process \|, \<, \> backslash escapes. Only < is entity-escaped; > is left literal to avoid double-encoding by rehype. */
114751
+ const processBackslashEscapes = (text) => text.replace(/\\<([^>]*)>/g, '&lt;$1>').replace(/\\([<>|])/g, (_, c) => (c === '<' ? '&lt;' : c === '>' ? '>' : c));
114752
+ /** Block-level HTML tags that trigger CommonMark type 6 HTML blocks (condition 6). */
114753
+ const BLOCK_LEVEL_TAGS = new Set(htmlBlockNames);
114754
+ const escapeInvalidTags = (str) => str.replace(HTML_TAG_RE, (match, tag, rest) => {
114755
+ const tagName = tag.replace(/^\//, '');
114756
+ if (STANDARD_HTML_TAGS.has(tagName.toLowerCase()))
114757
+ return match;
114758
+ // Preserve PascalCase tags (custom components like <Glossary>) for the main pipeline
114759
+ if (/^[A-Z]/.test(tagName))
114760
+ return match;
114761
+ return `&lt;${tag}${rest}&gt;`;
114762
+ });
114763
+ /**
114764
+ * Process markdown within HTML string.
114765
+ * 1. Parse HTML to HAST
114766
+ * 2. Find text nodes, parse as markdown, convert to HAST
114767
+ * 3. Stringify back to HTML
114768
+ *
114769
+ * PascalCase component tags (e.g. `<Glossary>`) are temporarily replaced with
114770
+ * placeholders before HTML parsing so `rehype-parse` doesn't mangle them
114771
+ * (it treats unknown tags as void elements, stripping their children).
114772
+ */
114773
+ const processMarkdownInHtmlString = (html) => {
114774
+ const placeholders = [];
114775
+ let counter = 0;
114776
+ const safened = escapeInvalidTags(html).replace(HTML_TAG_RE, match => {
114777
+ if (!/^<\/?[A-Z]/.test(match))
114778
+ return match;
114779
+ const id = `<!--PC${(counter += 1)}-->`;
114780
+ placeholders.push([id, match]);
114781
+ return id;
114782
+ });
114783
+ const hast = htmlParser.parse(safened);
114784
+ const textToHast = (text) => {
114785
+ if (!text.trim())
114786
+ return [{ type: 'text', value: text }];
114787
+ const parsed = markdownToHtml.runSync(markdownToHtml.parse(escapeInvalidTags(text)));
114788
+ const nodes = parsed.children.flatMap(n => n.type === 'element' && n.tagName === 'p' ? n.children : [n]);
114789
+ const leading = text.match(/^\s+/)?.[0];
114790
+ const trailing = text.match(/\s+$/)?.[0];
114791
+ if (leading)
114792
+ nodes.unshift({ type: 'text', value: leading });
114793
+ if (trailing)
114794
+ nodes.push({ type: 'text', value: trailing });
114795
+ return nodes;
114796
+ };
114797
+ const processChildren = (children) => children.flatMap(child => (child.type === 'text' ? textToHast(child.value) : [child]));
114798
+ hast.children = processChildren(hast.children);
114799
+ visit(hast, 'element', (node) => {
114800
+ node.children = processChildren(node.children);
114801
+ });
114802
+ return placeholders.reduce((res, [id, original]) => res.replace(id, original), htmlStringifier.stringify(hast));
114803
+ };
114804
+ /**
114805
+ * Separate a closing block-level tag from the content that follows it.
114806
+ *
114807
+ * Each \n in the original text becomes a <br> tag to preserve spacing, then a
114808
+ * blank line (\n\n) is appended so CommonMark ends the HTML block and parses
114809
+ * the following content as markdown.
114810
+ */
114811
+ const separateBlockTagFromContent = (match, tag, inlineChar, nextLineChar) => {
114812
+ if (!BLOCK_LEVEL_TAGS.has(tag.toLowerCase()))
114813
+ return match;
114814
+ const newlineCount = (match.match(/\n/g) ?? []).length;
114815
+ const breaks = '<br>'.repeat(newlineCount);
114816
+ return `</${tag}>${breaks}\n\n${inlineChar || nextLineChar}`;
114817
+ };
114818
+ /**
114819
+ * CommonMark doesn't process markdown inside HTML blocks -
114820
+ * so `<ul><li>_text_</li></ul>` won't convert underscores to emphasis.
114821
+ * We parse first, then visit html nodes and process their text content.
114822
+ */
114554
114823
  const parseTableCell = (text) => {
114555
114824
  if (!text.trim())
114556
114825
  return [{ type: 'text', value: '' }];
114557
- const tree = contentParser.runSync(contentParser.parse(text));
114826
+ // Convert \n (and surrounding whitespace) to <br> inside HTML blocks so
114827
+ // CommonMark doesn't split them on blank lines.
114828
+ // Then strip leading whitespace to prevent indented code blocks.
114829
+ const escaped = processBackslashEscapes(text);
114830
+ const normalized = escaped
114831
+ .replace(HTML_ELEMENT_BLOCK_RE, match => match.replace(NEWLINE_WITH_WHITESPACE_RE, '<br>'))
114832
+ .replace(CLOSE_BLOCK_TAG_BOUNDARY_RE, separateBlockTagFromContent);
114833
+ const trimmedLines = normalized.split('\n').map(line => line.trimStart());
114834
+ const processed = trimmedLines.join('\n');
114835
+ const tree = contentParser.runSync(contentParser.parse(processed));
114836
+ // Process markdown inside complete HTML elements (e.g. _emphasis_ within <li>).
114837
+ // Bare tags like "<i>" are left for rehypeRaw since rehype-parse would mangle them.
114838
+ visit(tree, 'html', (node) => {
114839
+ if (COMPLETE_HTML_ELEMENT_RE.test(node.value)) {
114840
+ node.value = processMarkdownInHtmlString(node.value);
114841
+ }
114842
+ else {
114843
+ node.value = escapeInvalidTags(node.value);
114844
+ }
114845
+ });
114558
114846
  if (tree.children.length > 1) {
114559
114847
  return tree.children;
114560
114848
  }
@@ -114709,7 +114997,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
114709
114997
  });
114710
114998
  }
114711
114999
  if (hasBody) {
114712
- const bodyBlocks = parseBlock(calloutJson.body || '');
115000
+ const bodyBlocks = parseBlock(preprocessBody(calloutJson.body || ''));
114713
115001
  children.push(...bodyBlocks);
114714
115002
  }
114715
115003
  const calloutElement = {
@@ -114744,7 +115032,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
114744
115032
  const tokenizeCell = compatibilityMode ? textToBlock : parseTableCell;
114745
115033
  const tableChildren = Array.from({ length: rows + 1 }, (_, y) => ({
114746
115034
  children: Array.from({ length: cols }, (__, x) => ({
114747
- children: sparseData[y]?.[x] ? tokenizeCell(sparseData[y][x]) : [{ type: 'text', value: '' }],
115035
+ children: sparseData[y]?.[x] ? tokenizeCell(preprocessBody(sparseData[y][x])) : [{ type: 'text', value: '' }],
114748
115036
  type: y === 0 ? 'tableHead' : 'tableCell',
114749
115037
  })),
114750
115038
  type: 'tableRow',
@@ -114845,79 +115133,63 @@ const isBlockNode = (node) => blockTypes.includes(node.type);
114845
115133
  */
114846
115134
  const magicBlockTransformer = (options = {}) => tree => {
114847
115135
  const replacements = [];
114848
- visit(tree, 'magicBlock', (node, index, parent) => {
114849
- if (!parent || index === undefined)
114850
- return undefined;
115136
+ visitParents(tree, 'magicBlock', (node, ancestors) => {
115137
+ const parent = ancestors[ancestors.length - 1]; // direct parent of the current node
115138
+ const index = parent.children.indexOf(node);
115139
+ if (index === -1)
115140
+ return;
114851
115141
  const children = transformMagicBlock(node.blockType, node.data, node.value, options);
114852
115142
  if (!children.length) {
114853
- // Remove the node if transformation returns nothing
115143
+ // `visitParents` doesn't support [Action, Index] returns like `visit` does;
115144
+ // a bare return after splicing is sufficient since `visitParents` walks by
115145
+ // tree structure rather than index.
114854
115146
  parent.children.splice(index, 1);
114855
- return [SKIP, index];
115147
+ return;
114856
115148
  }
114857
115149
  // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
114858
115150
  if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
114859
115151
  const blockNodes = [];
114860
115152
  const inlineNodes = [];
114861
- // Separate block and inline nodes
114862
115153
  children.forEach(child => {
114863
- if (isBlockNode(child)) {
114864
- blockNodes.push(child);
114865
- }
114866
- else {
114867
- inlineNodes.push(child);
114868
- }
115154
+ (isBlockNode(child) ? blockNodes : inlineNodes).push(child);
114869
115155
  });
114870
- const before = parent.children.slice(0, index);
114871
- const after = parent.children.slice(index + 1);
114872
115156
  replacements.push({
115157
+ container: ancestors[ancestors.length - 2] || tree, // grandparent of the current node
114873
115158
  parent,
114874
115159
  blockNodes,
114875
115160
  inlineNodes,
114876
- before,
114877
- after,
115161
+ before: parent.children.slice(0, index),
115162
+ after: parent.children.slice(index + 1),
114878
115163
  });
114879
115164
  }
114880
115165
  else {
114881
- // Normal case: just replace the inlineCode with the children
114882
115166
  parent.children.splice(index, 1, ...children);
114883
115167
  }
114884
- return undefined;
114885
115168
  });
114886
115169
  // Second pass: apply replacements that require lifting block nodes out of paragraphs
114887
115170
  // Process in reverse order to maintain correct indices
114888
115171
  for (let i = replacements.length - 1; i >= 0; i -= 1) {
114889
- const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
114890
- // Find the paragraph's position in the root
114891
- const rootChildren = tree.children;
114892
- const paraIndex = rootChildren.findIndex(child => child === parent);
115172
+ const { after, before, blockNodes, container, inlineNodes, parent } = replacements[i];
115173
+ const containerChildren = container.children;
115174
+ const paraIndex = containerChildren.indexOf(parent);
114893
115175
  if (paraIndex === -1) {
114894
- // Paragraph not found in root - fall back to normal replacement
114895
- // This shouldn't happen normally, but handle it gracefully
114896
- // Reconstruct the original index from before.length
114897
- const originalIndex = before.length;
114898
- parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
115176
+ parent.children.splice(before.length, 1, ...blockNodes, ...inlineNodes);
114899
115177
  // eslint-disable-next-line no-continue
114900
115178
  continue;
114901
115179
  }
114902
- // Update or remove the paragraph
114903
115180
  if (inlineNodes.length > 0) {
114904
- // Keep paragraph with inline nodes
114905
115181
  parent.children = [...before, ...inlineNodes, ...after];
114906
- // Insert block nodes after the paragraph
114907
115182
  if (blockNodes.length > 0) {
114908
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
115183
+ containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
114909
115184
  }
114910
115185
  }
114911
115186
  else if (before.length === 0 && after.length === 0) {
114912
- // Remove empty paragraph and replace with block nodes
114913
- rootChildren.splice(paraIndex, 1, ...blockNodes);
115187
+ containerChildren.splice(paraIndex, 1, ...blockNodes);
114914
115188
  }
114915
115189
  else {
114916
- // Keep paragraph with remaining content
114917
115190
  parent.children = [...before, ...after];
114918
- // Insert block nodes after the paragraph
114919
115191
  if (blockNodes.length > 0) {
114920
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
115192
+ containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
114921
115193
  }
114922
115194
  }
114923
115195
  }
@@ -115752,6 +116024,45 @@ const restoreBooleanProperties = () => tree => {
115752
116024
  };
115753
116025
 
115754
116026
 
116027
+ ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
116028
+
116029
+ const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
116030
+ const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
116031
+ /**
116032
+ * Preprocessor to terminate HTML flow blocks.
116033
+ *
116034
+ * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
116035
+ * Without one, any content on the next line is consumed as part of the HTML block
116036
+ * and never parsed as its own construct. For example, a `[block:callout]` immediately
116037
+ * following `<div><p></p></div>` gets swallowed into the HTML flow token.
116038
+ *
116039
+ * @link https://spec.commonmark.org/0.29/#html-blocks
116040
+ *
116041
+ * This preprocessor inserts a blank line after standalone HTML lines when the
116042
+ * next line is non-blank, ensuring micromark's HTML flow tokenizer terminates
116043
+ * and subsequent content is parsed independently.
116044
+ *
116045
+ * Only targets non-indented lines with lowercase tag names. Uppercase tags
116046
+ * (e.g., `<Table>`, `<MyComponent>`) are JSX custom components and don't
116047
+ * trigger CommonMark HTML blocks, so they are left untouched.
116048
+ *
116049
+ * Lines inside fenced code blocks are skipped entirely.
116050
+ */
116051
+ function terminateHtmlFlowBlocks(content) {
116052
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
116053
+ const lines = protectedContent.split('\n');
116054
+ const result = [];
116055
+ for (let i = 0; i < lines.length; i += 1) {
116056
+ result.push(lines[i]);
116057
+ if (i < lines.length - 1 &&
116058
+ (STANDALONE_HTML_LINE_REGEX.test(lines[i]) || HTML_LINE_WITH_CONTENT_REGEX.test(lines[i])) &&
116059
+ lines[i + 1].trim().length > 0) {
116060
+ result.push('');
116061
+ }
116062
+ }
116063
+ return restoreCodeBlocks(result.join('\n'), protectedCode);
116064
+ }
116065
+
115755
116066
  ;// ./processor/transform/mdxish/variables-text.ts
115756
116067
 
115757
116068
 
@@ -117054,10 +117365,29 @@ function loadComponents() {
117054
117365
 
117055
117366
 
117056
117367
 
117368
+
117369
+
117057
117370
 
117058
117371
 
117059
117372
 
117060
117373
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
117374
+ /**
117375
+ * Preprocessing pipeline: applies string-level transformations to work around
117376
+ * CommonMark/remark limitations and reach parity with legacy (rdmd) rendering.
117377
+ *
117378
+ * Runs a series of string-level transformations before micromark/remark parsing:
117379
+ * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
117380
+ * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
117381
+ * 3. Evaluate JSX expressions in attributes (unless safeMode)
117382
+ * 4. Replace snake_case component names with parser-safe placeholders
117383
+ */
117384
+ function preprocessContent(content, opts) {
117385
+ const { safeMode, jsxContext, knownComponents } = opts;
117386
+ let result = normalizeTableSeparator(content);
117387
+ result = terminateHtmlFlowBlocks(result);
117388
+ result = safeMode ? result : preprocessJSXExpressions(result, jsxContext);
117389
+ return processSnakeCaseComponent(result, { knownComponents });
117390
+ }
117061
117391
  function mdxishAstProcessor(mdContent, opts = {}) {
117062
117392
  const { components: userComponents = {}, jsxContext = {}, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
117063
117393
  const components = {
@@ -117066,15 +117396,11 @@ function mdxishAstProcessor(mdContent, opts = {}) {
117066
117396
  };
117067
117397
  // Build set of known component names for snake_case filtering
117068
117398
  const knownComponents = new Set(Object.keys(components));
117069
- // Preprocessing pipeline: Transform content to be parser-ready
117070
- // Step 1: Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
117071
- const contentAfterTableNormalization = normalizeTableSeparator(mdContent);
117072
- // Step 2: Evaluate JSX expressions in attributes
117073
- const contentAfterJSXEvaluation = safeMode
117074
- ? contentAfterTableNormalization
117075
- : preprocessJSXExpressions(contentAfterTableNormalization, jsxContext);
117076
- // Step 3: Replace snake_case component names with parser-safe placeholders
117077
- const { content: parserReadyContent, mapping: snakeCaseMapping } = processSnakeCaseComponent(contentAfterJSXEvaluation, { knownComponents });
117399
+ const { content: parserReadyContent, mapping: snakeCaseMapping } = preprocessContent(mdContent, {
117400
+ safeMode,
117401
+ jsxContext,
117402
+ knownComponents,
117403
+ });
117078
117404
  // Create string map for tailwind transformer
117079
117405
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
117080
117406
  acc[key] = String(value);
@@ -117142,6 +117468,7 @@ function mdxish(mdContent, opts = {}) {
117142
117468
  };
117143
117469
  const { processor, parserReadyContent } = mdxishAstProcessor(mdContent, opts);
117144
117470
  processor
117471
+ .use(remarkBreaks)
117145
117472
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
117146
117473
  .use(preserveBooleanProperties) // RehypeRaw converts boolean properties to empty strings
117147
117474
  .use(rehypeRaw, { passThrough: ['html-block'] })
@@ -117638,8 +117965,59 @@ const mdxishTags_tags = (doc) => {
117638
117965
  };
117639
117966
  /* harmony default export */ const mdxishTags = (mdxishTags_tags);
117640
117967
 
117641
- ;// ./lib/stripComments.ts
117968
+ ;// ./lib/utils/extractMagicBlocks.ts
117969
+ /**
117970
+ * The content matching in this regex captures everything between `[block:TYPE]`
117971
+ * and `[/block]`, including new lines. Negative lookahead for the closing
117972
+ * `[/block]` tag is required to prevent greedy matching to ensure it stops at
117973
+ * the first closing tag it encounters preventing vulnerability to polynomial
117974
+ * backtracking issues.
117975
+ */
117976
+ const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
117977
+ /**
117978
+ * Extract legacy magic block syntax from a markdown string.
117979
+ * Returns the modified markdown and an array of extracted blocks.
117980
+ */
117981
+ function extractMagicBlocks(markdown) {
117982
+ const blocks = [];
117983
+ let index = 0;
117984
+ const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
117985
+ /**
117986
+ * Key is the unique identifier for the magic block
117987
+ */
117988
+ const key = `__MAGIC_BLOCK_${index}__`;
117989
+ /**
117990
+ * Token is a wrapper around the `key` to serialize & influence how the
117991
+ * magic block is parsed in the remark pipeline.
117992
+ * - Use backticks so it becomes a code span, preventing `remarkParse` from
117993
+ * parsing special characters in the token as markdown syntax
117994
+ * - Prepend a newline to ensure it is parsed as a block level node
117995
+ * - Append a newline to ensure it is separated from following content
117996
+ */
117997
+ const token = `\n\`${key}\`\n`;
117998
+ blocks.push({ key, raw: match, token });
117999
+ index += 1;
118000
+ return token;
118001
+ });
118002
+ return { replaced, blocks };
118003
+ }
118004
+ /**
118005
+ * Restore extracted magic blocks back into a markdown string.
118006
+ */
118007
+ function restoreMagicBlocks(replaced, blocks) {
118008
+ // If a magic block is at the start or end of the document, the extraction
118009
+ // token's newlines will have been trimmed during processing. We need to
118010
+ // account for that here to ensure the token is found and replaced correctly.
118011
+ // These extra newlines will be removed again when the final string is trimmed.
118012
+ const content = `\n${replaced}\n`;
118013
+ const restoredContent = blocks.reduce((acc, { token, raw }) => {
118014
+ // Ensure each magic block is separated by newlines when restored.
118015
+ return acc.split(token).join(`\n${raw}\n`);
118016
+ }, content);
118017
+ return restoredContent.trim();
118018
+ }
117642
118019
 
118020
+ ;// ./lib/stripComments.ts
117643
118021
 
117644
118022
 
117645
118023
 
@@ -117654,10 +118032,8 @@ const mdxishTags_tags = (doc) => {
117654
118032
  * Removes Markdown and MDX comments.
117655
118033
  */
117656
118034
  async function stripComments(doc, { mdx, mdxish } = {}) {
117657
- const processor = unified()
117658
- .data('micromarkExtensions', [magicBlock()])
117659
- .data('fromMarkdownExtensions', [magicBlockFromMarkdown()])
117660
- .data('toMarkdownExtensions', [magicBlockToMarkdown()]);
118035
+ const { replaced, blocks } = extractMagicBlocks(doc);
118036
+ const processor = unified();
117661
118037
  // we still require these two extensions because:
117662
118038
  // 1. we can rely on remarkMdx to parse MDXish
117663
118039
  // 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
@@ -117699,8 +118075,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
117699
118075
  },
117700
118076
  ],
117701
118077
  });
117702
- const file = await processor.process(doc);
117703
- return String(file).trim();
118078
+ const file = await processor.process(replaced);
118079
+ const stringified = String(file).trim();
118080
+ const restored = restoreMagicBlocks(stringified, blocks);
118081
+ return restored;
117704
118082
  }
117705
118083
  /* harmony default export */ const lib_stripComments = (stripComments);
117706
118084