@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.js CHANGED
@@ -13007,8 +13007,7 @@ function color(d) {
13007
13007
 
13008
13008
  ;// ./node_modules/unist-util-visit-parents/lib/index.js
13009
13009
  /**
13010
- * @typedef {import('unist').Node} UnistNode
13011
- * @typedef {import('unist').Parent} UnistParent
13010
+ * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
13012
13011
  */
13013
13012
 
13014
13013
  /**
@@ -13056,8 +13055,10 @@ function color(d) {
13056
13055
 
13057
13056
  /**
13058
13057
  * @typedef {(
13059
- * Check extends Array<any>
13060
- * ? MatchesOne<Value, Check[keyof Check]>
13058
+ * Check extends ReadonlyArray<infer T>
13059
+ * ? MatchesOne<Value, T>
13060
+ * : Check extends Array<infer T>
13061
+ * ? MatchesOne<Value, T>
13061
13062
  * : MatchesOne<Value, Check>
13062
13063
  * )} Matches
13063
13064
  * Check whether a node matches a check in the type system.
@@ -13327,9 +13328,9 @@ function visitParents(tree, test, visitor, reverse) {
13327
13328
  typeof value.tagName === 'string'
13328
13329
  ? value.tagName
13329
13330
  : // `xast`
13330
- typeof value.name === 'string'
13331
- ? value.name
13332
- : undefined
13331
+ typeof value.name === 'string'
13332
+ ? value.name
13333
+ : undefined
13333
13334
 
13334
13335
  Object.defineProperty(visit, 'name', {
13335
13336
  value:
@@ -71359,6 +71360,10 @@ const types = {
71359
71360
  Recipe: NodeTypes.recipe,
71360
71361
  TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
71361
71362
  };
71363
+ // Node types that are phrasing (inline) content per the mdast spec. Phrasing
71364
+ // content at the document root violates the spec and causes mdx() to collapse
71365
+ // blank lines, so these must be wrapped in a paragraph when at root level.
71366
+ const phrasingTypes = new Set([NodeTypes.variable]);
71362
71367
  var TableNames;
71363
71368
  (function (TableNames) {
71364
71369
  TableNames["td"] = "td";
@@ -71514,6 +71519,28 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
71514
71519
  type: types[node.name],
71515
71520
  position: node.position,
71516
71521
  };
71522
+ // Wrap in a paragraph if at root level. Links are phrasing content and
71523
+ // root children must all be the same category (per mdast spec). Mixing
71524
+ // phrasing with flow content (headings, paragraphs, etc.) causes mdx()
71525
+ // to collapse blank lines in the document.
71526
+ if (parent.type === 'root') {
71527
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
71528
+ }
71529
+ else {
71530
+ parent.children[index] = mdNode;
71531
+ }
71532
+ }
71533
+ else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
71534
+ const hProperties = getAttrs(node);
71535
+ const mdNode = {
71536
+ ...hProperties,
71537
+ type: types[node.name],
71538
+ data: {
71539
+ hName: node.name,
71540
+ ...(Object.keys(hProperties).length && { hProperties }),
71541
+ },
71542
+ position: node.position,
71543
+ };
71517
71544
  parent.children[index] = mdNode;
71518
71545
  }
71519
71546
  else if (node.name in types) {
@@ -71527,7 +71554,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
71527
71554
  },
71528
71555
  position: node.position,
71529
71556
  };
71530
- parent.children[index] = mdNode;
71557
+ if (parent.type === 'root' && phrasingTypes.has(types[node.name])) {
71558
+ // @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
71559
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
71560
+ }
71561
+ else {
71562
+ parent.children[index] = mdNode;
71563
+ }
71531
71564
  }
71532
71565
  };
71533
71566
  const readmeComponents = (opts) => () => tree => {
@@ -93244,6 +93277,63 @@ function rehypeStringify(options) {
93244
93277
  }
93245
93278
  }
93246
93279
 
93280
+ ;// ./node_modules/mdast-util-newline-to-break/lib/index.js
93281
+ /**
93282
+ * @typedef {import('mdast').Nodes} Nodes
93283
+ * @typedef {import('mdast-util-find-and-replace').ReplaceFunction} ReplaceFunction
93284
+ */
93285
+
93286
+
93287
+
93288
+ /**
93289
+ * Turn normal line endings into hard breaks.
93290
+ *
93291
+ * @param {Nodes} tree
93292
+ * Tree to change.
93293
+ * @returns {undefined}
93294
+ * Nothing.
93295
+ */
93296
+ function newlineToBreak(tree) {
93297
+ findAndReplace(tree, [/\r?\n|\r/g, lib_replace])
93298
+ }
93299
+
93300
+ /**
93301
+ * Replace line endings.
93302
+ *
93303
+ * @type {ReplaceFunction}
93304
+ */
93305
+ function lib_replace() {
93306
+ return {type: 'break'}
93307
+ }
93308
+
93309
+ ;// ./node_modules/remark-breaks/lib/index.js
93310
+ /**
93311
+ * @typedef {import('mdast').Root} Root
93312
+ */
93313
+
93314
+
93315
+
93316
+ /**
93317
+ * Support hard breaks without needing spaces or escapes (turns enters into
93318
+ * `<br>`s).
93319
+ *
93320
+ * @returns
93321
+ * Transform.
93322
+ */
93323
+ function remarkBreaks() {
93324
+ /**
93325
+ * Transform.
93326
+ *
93327
+ * @param {Root} tree
93328
+ * Tree.
93329
+ * @returns {undefined}
93330
+ * Nothing.
93331
+ */
93332
+ return function (tree) {
93333
+ newlineToBreak(tree)
93334
+ }
93335
+ }
93336
+
93247
93337
  ;// ./lib/utils/mdxish/mdxish-get-component-name.ts
93248
93338
  /** Convert a string to PascalCase */
93249
93339
  function toPascalCase(str) {
@@ -93922,6 +94012,71 @@ const evaluateExpressions = ({ context = {} } = {}) => tree => {
93922
94012
  };
93923
94013
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
93924
94014
 
94015
+ ;// ./node_modules/rehype-parse/lib/index.js
94016
+ /**
94017
+ * @import {Root} from 'hast'
94018
+ * @import {Options as FromHtmlOptions} from 'hast-util-from-html'
94019
+ * @import {Parser, Processor} from 'unified'
94020
+ */
94021
+
94022
+ /**
94023
+ * @typedef {Omit<FromHtmlOptions, 'onerror'> & RehypeParseFields} Options
94024
+ * Configuration.
94025
+ *
94026
+ * @typedef RehypeParseFields
94027
+ * Extra fields.
94028
+ * @property {boolean | null | undefined} [emitParseErrors=false]
94029
+ * Whether to emit parse errors while parsing (default: `false`).
94030
+ *
94031
+ * > 👉 **Note**: parse errors are currently being added to HTML.
94032
+ * > Not all errors emitted by parse5 (or us) are specced yet.
94033
+ * > Some documentation may still be missing.
94034
+ */
94035
+
94036
+
94037
+
94038
+ /**
94039
+ * Plugin to add support for parsing from HTML.
94040
+ *
94041
+ * > 👉 **Note**: this is not an XML parser.
94042
+ * > It supports SVG as embedded in HTML.
94043
+ * > It does not support the features available in XML.
94044
+ * > Passing SVG files might break but fragments of modern SVG should be fine.
94045
+ * > Use [`xast-util-from-xml`][xast-util-from-xml] to parse XML.
94046
+ *
94047
+ * @param {Options | null | undefined} [options]
94048
+ * Configuration (optional).
94049
+ * @returns {undefined}
94050
+ * Nothing.
94051
+ */
94052
+ function rehypeParse(options) {
94053
+ /** @type {Processor<Root>} */
94054
+ // @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly.
94055
+ const self = this
94056
+ const {emitParseErrors, ...settings} = {...self.data('settings'), ...options}
94057
+
94058
+ self.parser = parser
94059
+
94060
+ /**
94061
+ * @type {Parser<Root>}
94062
+ */
94063
+ function parser(document, file) {
94064
+ return fromHtml(document, {
94065
+ ...settings,
94066
+ onerror: emitParseErrors
94067
+ ? function (message) {
94068
+ if (file.path) {
94069
+ message.name = file.path + ':' + message.name
94070
+ message.file = file.path
94071
+ }
94072
+
94073
+ file.messages.push(message)
94074
+ }
94075
+ : undefined
94076
+ })
94077
+ }
94078
+ }
94079
+
93925
94080
  ;// ./processor/transform/mdxish/normalize-malformed-md-syntax.ts
93926
94081
 
93927
94082
  // Marker patterns for multi-node emphasis detection
@@ -94265,6 +94420,18 @@ const normalizeEmphasisAST = () => (tree) => {
94265
94420
  };
94266
94421
  /* harmony default export */ const normalize_malformed_md_syntax = (normalizeEmphasisAST);
94267
94422
 
94423
+ ;// ./processor/transform/mdxish/magic-blocks/patterns.ts
94424
+ /** Matches HTML tags (open, close, self-closing) with optional attributes. */
94425
+ const HTML_TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9-]*)((?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)>/g;
94426
+ /** Matches an HTML element from its opening tag to the matching closing tag. */
94427
+ const HTML_ELEMENT_BLOCK_RE = /<([a-zA-Z][a-zA-Z0-9-]*)[\s>][\s\S]*?<\/\1>/g;
94428
+ /** Matches a newline with surrounding horizontal whitespace. */
94429
+ const NEWLINE_WITH_WHITESPACE_RE = /[^\S\n]*\n[^\S\n]*/g;
94430
+ /** Matches a closing block-level tag followed by non-tag text or by a newline then non-blank content. */
94431
+ const CLOSE_BLOCK_TAG_BOUNDARY_RE = /<\/([a-zA-Z][a-zA-Z0-9-]*)>\s*(?:(?!<)(\S)|\n([^\n]))/g;
94432
+ /** Tests whether a string contains a complete HTML element (open + close tag). */
94433
+ const COMPLETE_HTML_ELEMENT_RE = /<[a-zA-Z][^>]*>[\s\S]*<\/[a-zA-Z]/;
94434
+
94268
94435
  ;// ./processor/transform/mdxish/magic-blocks/placeholder.ts
94269
94436
  const EMPTY_IMAGE_PLACEHOLDER = {
94270
94437
  type: 'image',
@@ -94318,6 +94485,14 @@ const EMPTY_CODE_PLACEHOLDER = {
94318
94485
 
94319
94486
 
94320
94487
 
94488
+
94489
+
94490
+
94491
+
94492
+
94493
+
94494
+
94495
+
94321
94496
  /**
94322
94497
  * Wraps a node in a "pinned" container if sidebar: true is set.
94323
94498
  */
@@ -94345,12 +94520,125 @@ const imgWidthBySize = new Proxy(imgSizeValues, {
94345
94520
  });
94346
94521
  const textToInline = (text) => [{ type: 'text', value: text }];
94347
94522
  const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
94348
- /** Parses markdown and html to markdown nodes */
94349
- const contentParser = unified().use(remarkParse).use(remarkGfm).use(normalize_malformed_md_syntax);
94523
+ /**
94524
+ * Converts leading newlines in magic block content to `<br>` tags.
94525
+ * Leading newlines are stripped by remark-parse before they become soft break nodes,
94526
+ * so remark-breaks cannot handle them. We convert them to HTML `<br>` tags instead.
94527
+ */
94528
+ const ensureLeadingBreaks = (text) => text.replace(/^\n+/, match => '<br>'.repeat(match.length));
94529
+ /** Preprocesses magic block body content before parsing. */
94530
+ const preprocessBody = (text) => {
94531
+ return ensureLeadingBreaks(text);
94532
+ };
94533
+ /** Markdown parser */
94534
+ const contentParser = unified().use(remarkParse).use(remarkBreaks).use(remarkGfm).use(normalize_malformed_md_syntax);
94535
+ /** Markdown to HTML processor (mdast → hast → HTML string) */
94536
+ const markdownToHtml = unified()
94537
+ .use(remarkParse)
94538
+ .use(remarkGfm)
94539
+ .use(normalize_malformed_md_syntax)
94540
+ .use(remarkRehype)
94541
+ .use(rehypeStringify);
94542
+ /** HTML parser (HTML string → hast) */
94543
+ const htmlParser = unified().use(rehypeParse, { fragment: true });
94544
+ /** HTML stringifier (hast → HTML string) */
94545
+ const htmlStringifier = unified().use(rehypeStringify);
94546
+ /** Process \|, \<, \> backslash escapes. Only < is entity-escaped; > is left literal to avoid double-encoding by rehype. */
94547
+ const processBackslashEscapes = (text) => text.replace(/\\<([^>]*)>/g, '&lt;$1>').replace(/\\([<>|])/g, (_, c) => (c === '<' ? '&lt;' : c === '>' ? '>' : c));
94548
+ /** Block-level HTML tags that trigger CommonMark type 6 HTML blocks (condition 6). */
94549
+ const BLOCK_LEVEL_TAGS = new Set(htmlBlockNames);
94550
+ const escapeInvalidTags = (str) => str.replace(HTML_TAG_RE, (match, tag, rest) => {
94551
+ const tagName = tag.replace(/^\//, '');
94552
+ if (STANDARD_HTML_TAGS.has(tagName.toLowerCase()))
94553
+ return match;
94554
+ // Preserve PascalCase tags (custom components like <Glossary>) for the main pipeline
94555
+ if (/^[A-Z]/.test(tagName))
94556
+ return match;
94557
+ return `&lt;${tag}${rest}&gt;`;
94558
+ });
94559
+ /**
94560
+ * Process markdown within HTML string.
94561
+ * 1. Parse HTML to HAST
94562
+ * 2. Find text nodes, parse as markdown, convert to HAST
94563
+ * 3. Stringify back to HTML
94564
+ *
94565
+ * PascalCase component tags (e.g. `<Glossary>`) are temporarily replaced with
94566
+ * placeholders before HTML parsing so `rehype-parse` doesn't mangle them
94567
+ * (it treats unknown tags as void elements, stripping their children).
94568
+ */
94569
+ const processMarkdownInHtmlString = (html) => {
94570
+ const placeholders = [];
94571
+ let counter = 0;
94572
+ const safened = escapeInvalidTags(html).replace(HTML_TAG_RE, match => {
94573
+ if (!/^<\/?[A-Z]/.test(match))
94574
+ return match;
94575
+ const id = `<!--PC${(counter += 1)}-->`;
94576
+ placeholders.push([id, match]);
94577
+ return id;
94578
+ });
94579
+ const hast = htmlParser.parse(safened);
94580
+ const textToHast = (text) => {
94581
+ if (!text.trim())
94582
+ return [{ type: 'text', value: text }];
94583
+ const parsed = markdownToHtml.runSync(markdownToHtml.parse(escapeInvalidTags(text)));
94584
+ const nodes = parsed.children.flatMap(n => n.type === 'element' && n.tagName === 'p' ? n.children : [n]);
94585
+ const leading = text.match(/^\s+/)?.[0];
94586
+ const trailing = text.match(/\s+$/)?.[0];
94587
+ if (leading)
94588
+ nodes.unshift({ type: 'text', value: leading });
94589
+ if (trailing)
94590
+ nodes.push({ type: 'text', value: trailing });
94591
+ return nodes;
94592
+ };
94593
+ const processChildren = (children) => children.flatMap(child => (child.type === 'text' ? textToHast(child.value) : [child]));
94594
+ hast.children = processChildren(hast.children);
94595
+ visit(hast, 'element', (node) => {
94596
+ node.children = processChildren(node.children);
94597
+ });
94598
+ return placeholders.reduce((res, [id, original]) => res.replace(id, original), htmlStringifier.stringify(hast));
94599
+ };
94600
+ /**
94601
+ * Separate a closing block-level tag from the content that follows it.
94602
+ *
94603
+ * Each \n in the original text becomes a <br> tag to preserve spacing, then a
94604
+ * blank line (\n\n) is appended so CommonMark ends the HTML block and parses
94605
+ * the following content as markdown.
94606
+ */
94607
+ const separateBlockTagFromContent = (match, tag, inlineChar, nextLineChar) => {
94608
+ if (!BLOCK_LEVEL_TAGS.has(tag.toLowerCase()))
94609
+ return match;
94610
+ const newlineCount = (match.match(/\n/g) ?? []).length;
94611
+ const breaks = '<br>'.repeat(newlineCount);
94612
+ return `</${tag}>${breaks}\n\n${inlineChar || nextLineChar}`;
94613
+ };
94614
+ /**
94615
+ * CommonMark doesn't process markdown inside HTML blocks -
94616
+ * so `<ul><li>_text_</li></ul>` won't convert underscores to emphasis.
94617
+ * We parse first, then visit html nodes and process their text content.
94618
+ */
94350
94619
  const parseTableCell = (text) => {
94351
94620
  if (!text.trim())
94352
94621
  return [{ type: 'text', value: '' }];
94353
- const tree = contentParser.runSync(contentParser.parse(text));
94622
+ // Convert \n (and surrounding whitespace) to <br> inside HTML blocks so
94623
+ // CommonMark doesn't split them on blank lines.
94624
+ // Then strip leading whitespace to prevent indented code blocks.
94625
+ const escaped = processBackslashEscapes(text);
94626
+ const normalized = escaped
94627
+ .replace(HTML_ELEMENT_BLOCK_RE, match => match.replace(NEWLINE_WITH_WHITESPACE_RE, '<br>'))
94628
+ .replace(CLOSE_BLOCK_TAG_BOUNDARY_RE, separateBlockTagFromContent);
94629
+ const trimmedLines = normalized.split('\n').map(line => line.trimStart());
94630
+ const processed = trimmedLines.join('\n');
94631
+ const tree = contentParser.runSync(contentParser.parse(processed));
94632
+ // Process markdown inside complete HTML elements (e.g. _emphasis_ within <li>).
94633
+ // Bare tags like "<i>" are left for rehypeRaw since rehype-parse would mangle them.
94634
+ visit(tree, 'html', (node) => {
94635
+ if (COMPLETE_HTML_ELEMENT_RE.test(node.value)) {
94636
+ node.value = processMarkdownInHtmlString(node.value);
94637
+ }
94638
+ else {
94639
+ node.value = escapeInvalidTags(node.value);
94640
+ }
94641
+ });
94354
94642
  if (tree.children.length > 1) {
94355
94643
  return tree.children;
94356
94644
  }
@@ -94505,7 +94793,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
94505
94793
  });
94506
94794
  }
94507
94795
  if (hasBody) {
94508
- const bodyBlocks = parseBlock(calloutJson.body || '');
94796
+ const bodyBlocks = parseBlock(preprocessBody(calloutJson.body || ''));
94509
94797
  children.push(...bodyBlocks);
94510
94798
  }
94511
94799
  const calloutElement = {
@@ -94540,7 +94828,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
94540
94828
  const tokenizeCell = compatibilityMode ? textToBlock : parseTableCell;
94541
94829
  const tableChildren = Array.from({ length: rows + 1 }, (_, y) => ({
94542
94830
  children: Array.from({ length: cols }, (__, x) => ({
94543
- children: sparseData[y]?.[x] ? tokenizeCell(sparseData[y][x]) : [{ type: 'text', value: '' }],
94831
+ children: sparseData[y]?.[x] ? tokenizeCell(preprocessBody(sparseData[y][x])) : [{ type: 'text', value: '' }],
94544
94832
  type: y === 0 ? 'tableHead' : 'tableCell',
94545
94833
  })),
94546
94834
  type: 'tableRow',
@@ -94641,79 +94929,63 @@ const isBlockNode = (node) => blockTypes.includes(node.type);
94641
94929
  */
94642
94930
  const magicBlockTransformer = (options = {}) => tree => {
94643
94931
  const replacements = [];
94644
- visit(tree, 'magicBlock', (node, index, parent) => {
94645
- if (!parent || index === undefined)
94646
- return undefined;
94932
+ visitParents(tree, 'magicBlock', (node, ancestors) => {
94933
+ const parent = ancestors[ancestors.length - 1]; // direct parent of the current node
94934
+ const index = parent.children.indexOf(node);
94935
+ if (index === -1)
94936
+ return;
94647
94937
  const children = transformMagicBlock(node.blockType, node.data, node.value, options);
94648
94938
  if (!children.length) {
94649
- // Remove the node if transformation returns nothing
94939
+ // `visitParents` doesn't support [Action, Index] returns like `visit` does;
94940
+ // a bare return after splicing is sufficient since `visitParents` walks by
94941
+ // tree structure rather than index.
94650
94942
  parent.children.splice(index, 1);
94651
- return [SKIP, index];
94943
+ return;
94652
94944
  }
94653
94945
  // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
94654
94946
  if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
94655
94947
  const blockNodes = [];
94656
94948
  const inlineNodes = [];
94657
- // Separate block and inline nodes
94658
94949
  children.forEach(child => {
94659
- if (isBlockNode(child)) {
94660
- blockNodes.push(child);
94661
- }
94662
- else {
94663
- inlineNodes.push(child);
94664
- }
94950
+ (isBlockNode(child) ? blockNodes : inlineNodes).push(child);
94665
94951
  });
94666
- const before = parent.children.slice(0, index);
94667
- const after = parent.children.slice(index + 1);
94668
94952
  replacements.push({
94953
+ container: ancestors[ancestors.length - 2] || tree, // grandparent of the current node
94669
94954
  parent,
94670
94955
  blockNodes,
94671
94956
  inlineNodes,
94672
- before,
94673
- after,
94957
+ before: parent.children.slice(0, index),
94958
+ after: parent.children.slice(index + 1),
94674
94959
  });
94675
94960
  }
94676
94961
  else {
94677
- // Normal case: just replace the inlineCode with the children
94678
94962
  parent.children.splice(index, 1, ...children);
94679
94963
  }
94680
- return undefined;
94681
94964
  });
94682
94965
  // Second pass: apply replacements that require lifting block nodes out of paragraphs
94683
94966
  // Process in reverse order to maintain correct indices
94684
94967
  for (let i = replacements.length - 1; i >= 0; i -= 1) {
94685
- const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
94686
- // Find the paragraph's position in the root
94687
- const rootChildren = tree.children;
94688
- const paraIndex = rootChildren.findIndex(child => child === parent);
94968
+ const { after, before, blockNodes, container, inlineNodes, parent } = replacements[i];
94969
+ const containerChildren = container.children;
94970
+ const paraIndex = containerChildren.indexOf(parent);
94689
94971
  if (paraIndex === -1) {
94690
- // Paragraph not found in root - fall back to normal replacement
94691
- // This shouldn't happen normally, but handle it gracefully
94692
- // Reconstruct the original index from before.length
94693
- const originalIndex = before.length;
94694
- parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
94972
+ parent.children.splice(before.length, 1, ...blockNodes, ...inlineNodes);
94695
94973
  // eslint-disable-next-line no-continue
94696
94974
  continue;
94697
94975
  }
94698
- // Update or remove the paragraph
94699
94976
  if (inlineNodes.length > 0) {
94700
- // Keep paragraph with inline nodes
94701
94977
  parent.children = [...before, ...inlineNodes, ...after];
94702
- // Insert block nodes after the paragraph
94703
94978
  if (blockNodes.length > 0) {
94704
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94979
+ containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
94705
94980
  }
94706
94981
  }
94707
94982
  else if (before.length === 0 && after.length === 0) {
94708
- // Remove empty paragraph and replace with block nodes
94709
- rootChildren.splice(paraIndex, 1, ...blockNodes);
94983
+ containerChildren.splice(paraIndex, 1, ...blockNodes);
94710
94984
  }
94711
94985
  else {
94712
- // Keep paragraph with remaining content
94713
94986
  parent.children = [...before, ...after];
94714
- // Insert block nodes after the paragraph
94715
94987
  if (blockNodes.length > 0) {
94716
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94988
+ containerChildren.splice(paraIndex + 1, 0, ...blockNodes);
94717
94989
  }
94718
94990
  }
94719
94991
  }
@@ -95548,6 +95820,45 @@ const restoreBooleanProperties = () => tree => {
95548
95820
  };
95549
95821
 
95550
95822
 
95823
+ ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
95824
+
95825
+ const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
95826
+ const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
95827
+ /**
95828
+ * Preprocessor to terminate HTML flow blocks.
95829
+ *
95830
+ * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
95831
+ * Without one, any content on the next line is consumed as part of the HTML block
95832
+ * and never parsed as its own construct. For example, a `[block:callout]` immediately
95833
+ * following `<div><p></p></div>` gets swallowed into the HTML flow token.
95834
+ *
95835
+ * @link https://spec.commonmark.org/0.29/#html-blocks
95836
+ *
95837
+ * This preprocessor inserts a blank line after standalone HTML lines when the
95838
+ * next line is non-blank, ensuring micromark's HTML flow tokenizer terminates
95839
+ * and subsequent content is parsed independently.
95840
+ *
95841
+ * Only targets non-indented lines with lowercase tag names. Uppercase tags
95842
+ * (e.g., `<Table>`, `<MyComponent>`) are JSX custom components and don't
95843
+ * trigger CommonMark HTML blocks, so they are left untouched.
95844
+ *
95845
+ * Lines inside fenced code blocks are skipped entirely.
95846
+ */
95847
+ function terminateHtmlFlowBlocks(content) {
95848
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
95849
+ const lines = protectedContent.split('\n');
95850
+ const result = [];
95851
+ for (let i = 0; i < lines.length; i += 1) {
95852
+ result.push(lines[i]);
95853
+ if (i < lines.length - 1 &&
95854
+ (STANDALONE_HTML_LINE_REGEX.test(lines[i]) || HTML_LINE_WITH_CONTENT_REGEX.test(lines[i])) &&
95855
+ lines[i + 1].trim().length > 0) {
95856
+ result.push('');
95857
+ }
95858
+ }
95859
+ return restoreCodeBlocks(result.join('\n'), protectedCode);
95860
+ }
95861
+
95551
95862
  ;// ./processor/transform/mdxish/variables-text.ts
95552
95863
 
95553
95864
 
@@ -96850,10 +97161,29 @@ function loadComponents() {
96850
97161
 
96851
97162
 
96852
97163
 
97164
+
97165
+
96853
97166
 
96854
97167
 
96855
97168
 
96856
97169
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
97170
+ /**
97171
+ * Preprocessing pipeline: applies string-level transformations to work around
97172
+ * CommonMark/remark limitations and reach parity with legacy (rdmd) rendering.
97173
+ *
97174
+ * Runs a series of string-level transformations before micromark/remark parsing:
97175
+ * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
97176
+ * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
97177
+ * 3. Evaluate JSX expressions in attributes (unless safeMode)
97178
+ * 4. Replace snake_case component names with parser-safe placeholders
97179
+ */
97180
+ function preprocessContent(content, opts) {
97181
+ const { safeMode, jsxContext, knownComponents } = opts;
97182
+ let result = normalizeTableSeparator(content);
97183
+ result = terminateHtmlFlowBlocks(result);
97184
+ result = safeMode ? result : preprocessJSXExpressions(result, jsxContext);
97185
+ return processSnakeCaseComponent(result, { knownComponents });
97186
+ }
96857
97187
  function mdxishAstProcessor(mdContent, opts = {}) {
96858
97188
  const { components: userComponents = {}, jsxContext = {}, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
96859
97189
  const components = {
@@ -96862,15 +97192,11 @@ function mdxishAstProcessor(mdContent, opts = {}) {
96862
97192
  };
96863
97193
  // Build set of known component names for snake_case filtering
96864
97194
  const knownComponents = new Set(Object.keys(components));
96865
- // Preprocessing pipeline: Transform content to be parser-ready
96866
- // Step 1: Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
96867
- const contentAfterTableNormalization = normalizeTableSeparator(mdContent);
96868
- // Step 2: Evaluate JSX expressions in attributes
96869
- const contentAfterJSXEvaluation = safeMode
96870
- ? contentAfterTableNormalization
96871
- : preprocessJSXExpressions(contentAfterTableNormalization, jsxContext);
96872
- // Step 3: Replace snake_case component names with parser-safe placeholders
96873
- const { content: parserReadyContent, mapping: snakeCaseMapping } = processSnakeCaseComponent(contentAfterJSXEvaluation, { knownComponents });
97195
+ const { content: parserReadyContent, mapping: snakeCaseMapping } = preprocessContent(mdContent, {
97196
+ safeMode,
97197
+ jsxContext,
97198
+ knownComponents,
97199
+ });
96874
97200
  // Create string map for tailwind transformer
96875
97201
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
96876
97202
  acc[key] = String(value);
@@ -96938,6 +97264,7 @@ function mdxish(mdContent, opts = {}) {
96938
97264
  };
96939
97265
  const { processor, parserReadyContent } = mdxishAstProcessor(mdContent, opts);
96940
97266
  processor
97267
+ .use(remarkBreaks)
96941
97268
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
96942
97269
  .use(preserveBooleanProperties) // RehypeRaw converts boolean properties to empty strings
96943
97270
  .use(rehypeRaw, { passThrough: ['html-block'] })
@@ -97434,8 +97761,59 @@ const mdxishTags_tags = (doc) => {
97434
97761
  };
97435
97762
  /* harmony default export */ const mdxishTags = (mdxishTags_tags);
97436
97763
 
97437
- ;// ./lib/stripComments.ts
97764
+ ;// ./lib/utils/extractMagicBlocks.ts
97765
+ /**
97766
+ * The content matching in this regex captures everything between `[block:TYPE]`
97767
+ * and `[/block]`, including new lines. Negative lookahead for the closing
97768
+ * `[/block]` tag is required to prevent greedy matching to ensure it stops at
97769
+ * the first closing tag it encounters preventing vulnerability to polynomial
97770
+ * backtracking issues.
97771
+ */
97772
+ const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
97773
+ /**
97774
+ * Extract legacy magic block syntax from a markdown string.
97775
+ * Returns the modified markdown and an array of extracted blocks.
97776
+ */
97777
+ function extractMagicBlocks(markdown) {
97778
+ const blocks = [];
97779
+ let index = 0;
97780
+ const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
97781
+ /**
97782
+ * Key is the unique identifier for the magic block
97783
+ */
97784
+ const key = `__MAGIC_BLOCK_${index}__`;
97785
+ /**
97786
+ * Token is a wrapper around the `key` to serialize & influence how the
97787
+ * magic block is parsed in the remark pipeline.
97788
+ * - Use backticks so it becomes a code span, preventing `remarkParse` from
97789
+ * parsing special characters in the token as markdown syntax
97790
+ * - Prepend a newline to ensure it is parsed as a block level node
97791
+ * - Append a newline to ensure it is separated from following content
97792
+ */
97793
+ const token = `\n\`${key}\`\n`;
97794
+ blocks.push({ key, raw: match, token });
97795
+ index += 1;
97796
+ return token;
97797
+ });
97798
+ return { replaced, blocks };
97799
+ }
97800
+ /**
97801
+ * Restore extracted magic blocks back into a markdown string.
97802
+ */
97803
+ function restoreMagicBlocks(replaced, blocks) {
97804
+ // If a magic block is at the start or end of the document, the extraction
97805
+ // token's newlines will have been trimmed during processing. We need to
97806
+ // account for that here to ensure the token is found and replaced correctly.
97807
+ // These extra newlines will be removed again when the final string is trimmed.
97808
+ const content = `\n${replaced}\n`;
97809
+ const restoredContent = blocks.reduce((acc, { token, raw }) => {
97810
+ // Ensure each magic block is separated by newlines when restored.
97811
+ return acc.split(token).join(`\n${raw}\n`);
97812
+ }, content);
97813
+ return restoredContent.trim();
97814
+ }
97438
97815
 
97816
+ ;// ./lib/stripComments.ts
97439
97817
 
97440
97818
 
97441
97819
 
@@ -97450,10 +97828,8 @@ const mdxishTags_tags = (doc) => {
97450
97828
  * Removes Markdown and MDX comments.
97451
97829
  */
97452
97830
  async function stripComments(doc, { mdx, mdxish } = {}) {
97453
- const processor = unified()
97454
- .data('micromarkExtensions', [magicBlock()])
97455
- .data('fromMarkdownExtensions', [magicBlockFromMarkdown()])
97456
- .data('toMarkdownExtensions', [magicBlockToMarkdown()]);
97831
+ const { replaced, blocks } = extractMagicBlocks(doc);
97832
+ const processor = unified();
97457
97833
  // we still require these two extensions because:
97458
97834
  // 1. we can rely on remarkMdx to parse MDXish
97459
97835
  // 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
@@ -97495,8 +97871,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
97495
97871
  },
97496
97872
  ],
97497
97873
  });
97498
- const file = await processor.process(doc);
97499
- return String(file).trim();
97874
+ const file = await processor.process(replaced);
97875
+ const stringified = String(file).trim();
97876
+ const restored = restoreMagicBlocks(stringified, blocks);
97877
+ return restored;
97500
97878
  }
97501
97879
  /* harmony default export */ const lib_stripComments = (stripComments);
97502
97880