@readme/markdown 14.11.4 → 14.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ import type { Parents, Text } from 'mdast';
2
+ import type { Info, State } from 'mdast-util-to-markdown';
3
+ declare const text: (node: Text, parent: Parents | undefined, state: State, info: Info) => string;
4
+ export default text;
@@ -1,22 +1,8 @@
1
1
  /**
2
- * Preprocessor to terminate HTML flow blocks.
3
- *
4
- * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
5
- * Without one, any content on the next line is consumed as part of the HTML block
6
- * and never parsed as its own construct. For example, a `[block:callout]` immediately
7
- * following `<div><p></p></div>` gets swallowed into the HTML flow token.
2
+ * CommonMark HTML blocks (types 6/7) end only at a blank line, so markdown on the
3
+ * next line is swallowed as HTML (`## heading` after `<div>` renders as literal text).
4
+ * Inserts the missing blank line; the gating rules live in `shouldTerminateBetween`.
8
5
  *
9
6
  * @link https://spec.commonmark.org/0.29/#html-blocks
10
- *
11
- * This preprocessor inserts a blank line after standalone HTML lines when the next
12
- * line is non-blank and not an HTML construct, ensuring micromark's HTML flow
13
- * tokenizer terminates and subsequent content is parsed independently.
14
- *
15
- * Conditions:
16
- * 1. Only non-indented lines with lowercase tag names are considered. Uppercase tags
17
- * (e.g. `<Table>`, `<MyComponent>`) are JSX custom components and don't trigger
18
- * CommonMark HTML blocks.
19
- * 2. Lines inside protected blocks (e.g. fenced code) are left untouched.
20
- * 3. Lines with an unclosed RAW_CONTENT_TAGS opener are exempted.
21
7
  */
22
8
  export declare function terminateHtmlFlowBlocks(content: string): string;
@@ -117497,6 +117497,19 @@ const compile_list_item_listItem = (node, parent, state, info) => {
117497
117497
  const plain_plain = (node) => node.value;
117498
117498
  /* harmony default export */ const compile_plain = (plain_plain);
117499
117499
 
117500
+ ;// ./processor/compile/text.ts
117501
+
117502
+ // A `_` flanked by word characters can never open or close emphasis under
117503
+ // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
117504
+ // intraword underscores is unnecessary and only produces noisy `\_` diffs.
117505
+ // Uses Unicode letter/number classes so non-ASCII words (e.g. `é_pay`) match.
117506
+ const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
117507
+ const compile_text_text = (node, parent, state, info) => {
117508
+ const serialized = handle.text(node, parent, state, info);
117509
+ return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
117510
+ };
117511
+ /* harmony default export */ const compile_text = (compile_text_text);
117512
+
117500
117513
  ;// ./processor/compile/index.ts
117501
117514
 
117502
117515
 
@@ -117509,11 +117522,11 @@ const plain_plain = (node) => node.value;
117509
117522
 
117510
117523
 
117511
117524
 
117525
+
117512
117526
  function compilers(mdxish = false) {
117513
117527
  const data = this.data();
117514
117528
  const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
117515
117529
  const handlers = {
117516
- ...(mdxish && { [enums_NodeTypes.anchor]: compile_anchor }),
117517
117530
  [enums_NodeTypes.callout]: compile_callout,
117518
117531
  [enums_NodeTypes.codeTabs]: compile_code_tabs,
117519
117532
  [enums_NodeTypes.embedBlock]: compile_embed,
@@ -117521,15 +117534,18 @@ function compilers(mdxish = false) {
117521
117534
  [enums_NodeTypes.glossary]: compile_compatibility,
117522
117535
  [enums_NodeTypes.htmlBlock]: html_block,
117523
117536
  [enums_NodeTypes.reusableContent]: compile_compatibility,
117524
- ...(mdxish && { [enums_NodeTypes.variable]: compile_variable }),
117525
117537
  embed: compile_compatibility,
117526
117538
  escape: compile_compatibility,
117527
117539
  figure: compile_compatibility,
117528
117540
  html: compile_compatibility,
117529
117541
  i: compile_compatibility,
117530
- ...(mdxish && { listItem: list_item }),
117531
117542
  plain: compile_plain,
117532
117543
  yaml: compile_compatibility,
117544
+ // needed only for mdxish
117545
+ ...(mdxish && { [enums_NodeTypes.anchor]: compile_anchor }),
117546
+ ...(mdxish && { listItem: list_item }),
117547
+ ...(mdxish && { text: compile_text }),
117548
+ ...(mdxish && { [enums_NodeTypes.variable]: compile_variable }),
117533
117549
  };
117534
117550
  toMarkdownExtensions.push({ extensions: [{ handlers }] });
117535
117551
  }
@@ -120971,70 +120987,168 @@ const mdxishInlineMdxComponents = () => tree => {
120971
120987
  };
120972
120988
  /* harmony default export */ const inline_mdx_blocks = (mdxishInlineMdxComponents);
120973
120989
 
120990
+ ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
120991
+
120992
+
120993
+
120994
+ const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
120995
+ const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
120996
+ // A line that is exactly one lowercase opening (or self-closing) tag — the shape
120997
+ // that opens a CommonMark type-6/7 HTML block and swallows the lines below it.
120998
+ const SINGLE_OPENING_TAG_REGEX = /^<[a-z][^<>]*>$/;
120999
+ // Tags whose contents must be preserved as is, inserting a blank line after the
121000
+ // opener corrupts the payload.
121001
+ // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
121002
+ const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
121003
+ // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
121004
+ const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
121005
+ open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
121006
+ close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
121007
+ }));
121008
+ function isLineHtml(line) {
121009
+ return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
121010
+ }
121011
+ // Per-tag {opens, closes} counts of RAW_CONTENT_TAGS on one line — feeds both the
121012
+ // per-line net-open check and the cumulative still-open depth tracking.
121013
+ function countRawContentTags(line) {
121014
+ return RAW_CONTENT_TAG_MATCHERS.map(({ open, close }) => ({
121015
+ opens: (line.match(open) ?? []).length,
121016
+ closes: (line.match(close) ?? []).length,
121017
+ }));
121018
+ }
121019
+ // Indentation width in columns, counting a tab as 4 per CommonMark.
121020
+ function indentWidth(line) {
121021
+ const leading = line.match(/^[ \t]*/)?.[0] ?? '';
121022
+ return leading.replace(/\t/g, ' ').length;
121023
+ }
121024
+ /**
121025
+ * Decides whether a blank line belongs between `line` and `next` so the HTML
121026
+ * flow block opened on `line` terminates and `next` parses as markdown.
121027
+ */
121028
+ function shouldTerminateBetween(line, next, { insideRawContent, lineLeavesRawOpen }) {
121029
+ if (next.trim().length === 0)
121030
+ return false;
121031
+ const currentIndent = indentWidth(line);
121032
+ const nextIndent = indentWidth(next);
121033
+ // 4+ columns is CommonMark indented-code territory: an inserted blank line
121034
+ // would turn the next line into a code block instead of freeing it (#1344).
121035
+ if (currentIndent > 3 || nextIndent > 3)
121036
+ return false;
121037
+ const currentTrimmed = line.trim();
121038
+ const nextTrimmed = next.trim();
121039
+ if (!isLineHtml(currentTrimmed) || isLineHtml(nextTrimmed) || lineLeavesRawOpen) {
121040
+ return false;
121041
+ }
121042
+ // Column-0 pairs keep the original (pre-relaxation) rule, deliberately ignoring
121043
+ // `insideRawContent`: one unclosed <pre>/<table> typo would otherwise suppress
121044
+ // termination for the whole rest of the document.
121045
+ if (currentIndent === 0 && nextIndent === 0)
121046
+ return true;
121047
+ // Indented shapes (either line at 1–3 columns) are stricter: only a lone opening
121048
+ // tag followed by markdown. A next line opening a tag or expression must stay
121049
+ // glued for the mdxComponent tokenizer; raw-content payloads stay byte-exact.
121050
+ return (!insideRawContent &&
121051
+ SINGLE_OPENING_TAG_REGEX.test(currentTrimmed) &&
121052
+ !nextTrimmed.startsWith('<') &&
121053
+ !nextTrimmed.startsWith('{'));
121054
+ }
121055
+ /**
121056
+ * CommonMark HTML blocks (types 6/7) end only at a blank line, so markdown on the
121057
+ * next line is swallowed as HTML (`## heading` after `<div>` renders as literal text).
121058
+ * Inserts the missing blank line; the gating rules live in `shouldTerminateBetween`.
121059
+ *
121060
+ * @link https://spec.commonmark.org/0.29/#html-blocks
121061
+ */
121062
+ function terminateHtmlFlowBlocks(content) {
121063
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
121064
+ const lines = protectedContent.split('\n');
121065
+ const result = [];
121066
+ // Per-tag count of still-open raw-content elements at the current boundary.
121067
+ const rawContentDepths = RAW_CONTENT_TAG_MATCHERS.map(() => 0);
121068
+ for (let i = 0; i < lines.length; i += 1) {
121069
+ const line = lines[i];
121070
+ result.push(line);
121071
+ const tagCounts = countRawContentTags(line);
121072
+ tagCounts.forEach(({ opens, closes }, tagIndex) => {
121073
+ rawContentDepths[tagIndex] = Math.max(0, rawContentDepths[tagIndex] + opens - closes);
121074
+ });
121075
+ const next = lines[i + 1];
121076
+ const rawContentFacts = {
121077
+ insideRawContent: rawContentDepths.some(depth => depth > 0),
121078
+ lineLeavesRawOpen: tagCounts.some(({ opens, closes }) => opens > closes),
121079
+ };
121080
+ if (next !== undefined && shouldTerminateBetween(line, next, rawContentFacts)) {
121081
+ result.push('');
121082
+ }
121083
+ }
121084
+ return restoreCodeBlocks(result.join('\n'), protectedCode);
121085
+ }
121086
+
120974
121087
  ;// ./processor/transform/mdxish/components/mdx-blocks.ts
120975
121088
 
120976
121089
 
120977
121090
 
120978
121091
 
120979
121092
 
120980
- /** Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
121093
+
121094
+
121095
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
120981
121096
  const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
120982
- /**
120983
- * Reduce leading whitespace on all lines just enough to prevent
120984
- * remark from treating indented content as code blocks (4+ spaces).
120985
- * Preserves relative indentation so whitespace text nodes are
120986
- * maintained in the HAST output.
121097
+ // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
121098
+ // of a legacy `<<VARIABLE>>`.
121099
+ const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
121100
+ // Excludes tags with dedicated transformers (`Table`, `HTMLBlock`, inline
121101
+ // components), which expect their wrapper to stay raw.
121102
+ const hasNestedGenericComponentTag = (content) => [...content.matchAll(NESTED_COMPONENT_TAG_RE)].some(match => !GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(match[1]));
121103
+ /**
121104
+ * Strip the shared leading indentation from a component body so readability indentation
121105
+ * isn't parsed as indented code (4+ spaces), e.g. ` <p>` / ` text` -> `<p>` / ` text`.
121106
+ * Relative indentation is kept, so content genuinely 4+ columns deeper stays code. We
121107
+ * only strip when a line actually reaches 4 columns; otherwise the body is left as-is so
121108
+ * its leading whitespace survives as text nodes (mixed component + HTML content needs it).
120987
121109
  */
120988
121110
  function safeDeindent(text) {
120989
121111
  const lines = text.split('\n');
120990
121112
  const nonEmptyLines = lines.filter(line => line.trim().length > 0);
120991
121113
  if (nonEmptyLines.length === 0)
120992
121114
  return text;
120993
- const minIndent = Math.min(...nonEmptyLines.map(line => {
120994
- const match = line.match(/^(\s*)/);
120995
- return match ? match[1].length : 0;
120996
- }));
120997
- // Only strip enough indent to keep all lines below the 4-space code threshold
120998
- const stripAmount = Math.max(0, minIndent - 3);
121115
+ // Indent counts characters (tab = 1), unlike indentWidth's CommonMark columns
121116
+ // (tab = 4) in terminate-html-flow-blocks.
121117
+ const indents = nonEmptyLines.map(line => line.match(/^(\s*)/)?.[1].length ?? 0);
121118
+ const minIndent = Math.min(...indents);
121119
+ const maxIndent = Math.max(...indents);
121120
+ const stripAmount = maxIndent > 3 ? minIndent : 0;
120999
121121
  if (stripAmount === 0)
121000
121122
  return text;
121001
121123
  return lines.map(line => line.slice(stripAmount)).join('\n');
121002
121124
  }
121003
121125
  /**
121004
- * Parse markdown content into mdast children nodes.
121005
- * Dedents the content first to prevent indented component content
121006
- * (from nested components) from being treated as code blocks.
121126
+ * Parse component-body markdown into mdast children. Dedenting shifts columns and
121127
+ * stales the top-level `terminateHtmlFlowBlocks` decisions, so that one preprocessor
121128
+ * re-runs here; other column-anchored fixups (compact headings, tables) do not.
121007
121129
  */
121008
121130
  const parseMdChildren = (value, safeMode) => {
121009
- const parsed = getInlineMdProcessor({ safeMode }).parse(safeDeindent(value).trim());
121131
+ const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
121010
121132
  return parsed.children || [];
121011
121133
  };
121012
- /**
121013
- * Parse substring content of a node and update the parent's children to include the new nodes.
121014
- */
121134
+ // Parses trailing content into sibling nodes and re-queues the parent so any
121135
+ // components among them get processed.
121015
121136
  const parseSibling = (stack, parent, index, sibling, safeMode) => {
121016
121137
  const siblingNodes = parseMdChildren(sibling, safeMode);
121017
- // The new sibling nodes might contain new components to be processed
121018
121138
  if (siblingNodes.length > 0) {
121019
121139
  parent.children.splice(index + 1, 0, ...siblingNodes);
121020
121140
  stack.push(parent);
121021
121141
  }
121022
121142
  };
121023
- /**
121024
- * Build a position ending at `consumedLength` into the html node's value, so the
121025
- * component doesn't claim trailing content the tokenizer swallowed into one node.
121026
- */
121143
+ // Ends the position at `consumedLength` so the component doesn't claim trailing
121144
+ // content the tokenizer swallowed into the same html node.
121027
121145
  const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
121028
121146
  if (!nodePosition?.start)
121029
121147
  return nodePosition;
121030
121148
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
121031
121149
  };
121032
- /**
121033
- * Build a position ending right after the last occurrence of `closingTag` within
121034
- * this node's span in the original source. Used in the trailing-content path so
121035
- * the offset is computed against the real source bytes (including blockquote/list
121036
- * prefixes that were stripped from the html node's value).
121037
- */
121150
+ // Like `positionEndingAtConsumed`, but measures against the original source so
121151
+ // blockquote/list prefixes stripped from the html node's value are counted.
121038
121152
  const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
121039
121153
  if (!nodePosition?.start || !nodePosition.end)
121040
121154
  return nodePosition;
@@ -121045,9 +121159,6 @@ const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) =>
121045
121159
  const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
121046
121160
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
121047
121161
  };
121048
- /**
121049
- * Create an MdxJsxFlowElement node from component data.
121050
- */
121051
121162
  const createComponentNode = ({ tag, attributes, children, startPosition, endPosition }) => ({
121052
121163
  type: 'mdxJsxFlowElement',
121053
121164
  name: tag,
@@ -121100,8 +121211,7 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121100
121211
  if ('children' in node && Array.isArray(node.children)) {
121101
121212
  stack.push(node);
121102
121213
  }
121103
- // Only visit HTML nodes with an actual html tag,
121104
- // which means a potential unparsed MDX component
121214
+ // Only html nodes can be an unparsed MDX component.
121105
121215
  const value = node.value;
121106
121216
  if (node.type !== 'html' || typeof value !== 'string')
121107
121217
  return;
@@ -121110,32 +121220,25 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121110
121220
  if (!parsed)
121111
121221
  return;
121112
121222
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
121113
- // Offset of `trimmed` within the (possibly whitespace-padded) html node value,
121114
- // so consumed-length math maps back onto the node's real source offsets.
121223
+ // Offsets so consumed-length math maps back onto the node's real source.
121115
121224
  const leadingWhitespace = value.length - value.trimStart().length;
121116
- // Index right after the opening tag's `>` within `trimmed`.
121117
121225
  const openingTagEnd = trimmed.length - contentAfterTag.length;
121118
- // Skip tags that have dedicated transformers
121119
121226
  if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
121120
- return;
121227
+ return; // owned by dedicated transformers
121121
121228
  const isPascal = isPascalCase(tag);
121122
- // Lowercase inline tags (inside a paragraph) with `{…}` attributes are
121123
- // promoted to `mdxJsxTextElement` by mdxishInlineComponentBlocks. Skip
121124
- // them here so they stay as html for that pass; PascalCase components
121125
- // keep going through this transformer (they stay flow-level even when
121126
- // inline, which is how ReadMe's custom components are modeled).
121229
+ // Lowercase inline tags with `{…}` attributes belong to
121230
+ // mdxishInlineComponentBlocks; leave them as html for that pass. PascalCase
121231
+ // components stay flow-level even when inline (ReadMe's component model).
121127
121232
  if (!isPascal && parent.type === 'paragraph')
121128
121233
  return;
121129
- // Lowercase HTML tags are eligible when they (or a descendant tag in their
121130
- // content, e.g. a `.map()` body returning JSX with `key={i}`) carry a
121131
- // JSX-expression attribute. Without this, a wrapper `<div>` with only plain
121132
- // attributes (or none) swallows its whole nested block — including any
121133
- // `style={{...}}`/`.map()` JSX inside it — as literal HTML text, which
121134
- // rehype-raw's parse5 pass then garbles (it has no notion of JS expressions).
121135
- // Plain HTML with no expressions anywhere stays as an html node so
121136
- // rehype-raw handles it as normal.
121234
+ // A lowercase wrapper is only promoted when it (or a descendant) carries a
121235
+ // JSX expression or nests a component; otherwise it would swallow that inner
121236
+ // JSX/component as literal text that rehype-raw's parse5 pass can't handle.
121237
+ // Table-structural wrappers are excluded `mdxishTables` re-parses those.
121137
121238
  const hasNestedExpressionAttr = !selfClosing && NESTED_ATTR_EXPRESSION_RE.test(contentAfterTag);
121138
- if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr)
121239
+ const isTableStructuralTag = tag === 'table' || tableTags.has(tag);
121240
+ const hasNestedComponentTag = !selfClosing && !isTableStructuralTag && hasNestedGenericComponentTag(contentAfterTag);
121241
+ if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr && !hasNestedComponentTag)
121139
121242
  return;
121140
121243
  const closingTagStr = `</${tag}>`;
121141
121244
  // Case 1: Self-closing tag
@@ -121149,7 +121252,6 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121149
121252
  endPosition: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd),
121150
121253
  });
121151
121254
  substituteNodeWithMdxNode(parent, index, componentNode);
121152
- // Check and parse if there's relevant content after the current closing tag
121153
121255
  const remainingContent = contentAfterTag.trim();
121154
121256
  if (remainingContent) {
121155
121257
  parseSibling(stack, parent, index, remainingContent, safeMode);
@@ -121158,17 +121260,14 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121158
121260
  }
121159
121261
  // Case 2: Self-contained block (closing tag in content)
121160
121262
  if (contentAfterTag.includes(closingTagStr)) {
121161
- // Find the first closing tag
121162
121263
  const closingTagIndex = contentAfterTag.lastIndexOf(closingTagStr);
121163
- // Pass raw (untrimmed) content so dedent in parseMdChildren can
121164
- // normalize indentation before trimming
121264
+ // Untrimmed so parseMdChildren can dedent before trimming.
121165
121265
  const componentInnerContent = contentAfterTag.substring(0, closingTagIndex);
121166
121266
  const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
121167
121267
  let parsedChildren = componentInnerContent.trim()
121168
121268
  ? parseMdChildren(componentInnerContent, safeMode)
121169
121269
  : [];
121170
- // Lowercase HTML tags are usually inline (e.g. <a>, <span>). Remark wraps
121171
- // bare text in a paragraph; unwrap when there's exactly one paragraph so
121270
+ // Lowercase tags are usually inline; unwrap a sole paragraph so their
121172
121271
  // phrasing content isn't spuriously block-wrapped.
121173
121272
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
121174
121273
  parsedChildren = parsedChildren[0].children;
@@ -121178,12 +121277,9 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121178
121277
  attributes,
121179
121278
  children: parsedChildren,
121180
121279
  startPosition: node.position,
121181
- // When trailing content follows the closing tag, compute the end position precisely
121182
- // within the html node's value so the component doesn't claim that content.
121183
- // Prefer source-based positioning when the original source is available: the html
121184
- // node's value has '> '/space prefixes stripped for blockquotes/list items, so
121185
- // positionEndingAtConsumed would undercount source offsets. When the entire node
121186
- // is consumed, use the original node position directly.
121280
+ // With trailing content, end precisely at the closing tag. Prefer source
121281
+ // offsets when available (the node's value strips blockquote/list
121282
+ // prefixes); otherwise fall back to the whole node position.
121187
121283
  endPosition: contentAfterClose
121188
121284
  ? source
121189
121285
  ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
@@ -121191,17 +121287,16 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121191
121287
  : node.position,
121192
121288
  });
121193
121289
  substituteNodeWithMdxNode(parent, index, componentNode);
121194
- // After the closing tag, there might be more content to be processed
121290
+ // Re-queue whichever side may hold further components.
121195
121291
  if (contentAfterClose) {
121196
121292
  parseSibling(stack, parent, index, contentAfterClose, safeMode);
121197
121293
  }
121198
121294
  else if (componentNode.children.length > 0) {
121199
- // The content inside the component block might contain new components to be processed
121200
121295
  stack.push(componentNode);
121201
121296
  }
121202
121297
  }
121203
121298
  };
121204
- // Process the nodes with the components depth-first to maintain the correct order of the nodes
121299
+ // Depth-first so nodes keep their source order.
121205
121300
  while (stack.length) {
121206
121301
  const parent = stack.pop();
121207
121302
  if (parent?.children) {
@@ -125452,73 +125547,6 @@ function normalizeTableSeparator(content) {
125452
125547
  }
125453
125548
  /* harmony default export */ const normalize_table_separator = ((/* unused pure expression or super */ null && (normalizeTableSeparator)));
125454
125549
 
125455
- ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
125456
-
125457
-
125458
-
125459
- const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
125460
- const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
125461
- // Tags whose contents must be preserved as is, inserting a blank line after the
125462
- // opener corrupts the payload.
125463
- // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
125464
- const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
125465
- // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
125466
- const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
125467
- open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
125468
- close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
125469
- }));
125470
- function isLineHtml(line) {
125471
- return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
125472
- }
125473
- // True if any RAW_CONTENT_TAGS opener on this line is not closed on the same line.
125474
- function hasUnclosedRawContentOpener(line) {
125475
- return RAW_CONTENT_TAG_MATCHERS.some(({ open, close }) => {
125476
- const opens = (line.match(open) ?? []).length;
125477
- const closes = (line.match(close) ?? []).length;
125478
- return opens > closes;
125479
- });
125480
- }
125481
- /**
125482
- * Preprocessor to terminate HTML flow blocks.
125483
- *
125484
- * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
125485
- * Without one, any content on the next line is consumed as part of the HTML block
125486
- * and never parsed as its own construct. For example, a `[block:callout]` immediately
125487
- * following `<div><p></p></div>` gets swallowed into the HTML flow token.
125488
- *
125489
- * @link https://spec.commonmark.org/0.29/#html-blocks
125490
- *
125491
- * This preprocessor inserts a blank line after standalone HTML lines when the next
125492
- * line is non-blank and not an HTML construct, ensuring micromark's HTML flow
125493
- * tokenizer terminates and subsequent content is parsed independently.
125494
- *
125495
- * Conditions:
125496
- * 1. Only non-indented lines with lowercase tag names are considered. Uppercase tags
125497
- * (e.g. `<Table>`, `<MyComponent>`) are JSX custom components and don't trigger
125498
- * CommonMark HTML blocks.
125499
- * 2. Lines inside protected blocks (e.g. fenced code) are left untouched.
125500
- * 3. Lines with an unclosed RAW_CONTENT_TAGS opener are exempted.
125501
- */
125502
- function terminateHtmlFlowBlocks(content) {
125503
- const { protectedContent, protectedCode } = protectCodeBlocks(content);
125504
- const lines = protectedContent.split('\n');
125505
- const result = [];
125506
- for (let i = 0; i < lines.length; i += 1) {
125507
- result.push(lines[i]);
125508
- // Skip blank & indented lines
125509
- if (i >= lines.length - 1 || lines[i + 1].trim().length === 0 || lines[i + 1].startsWith(' ') || lines[i + 1].startsWith('\t')) {
125510
- // eslint-disable-next-line no-continue
125511
- continue;
125512
- }
125513
- const isCurrentLineHtml = isLineHtml(lines[i]);
125514
- const isNextLineHtml = isLineHtml(lines[i + 1]);
125515
- if (isCurrentLineHtml && !isNextLineHtml && !hasUnclosedRawContentOpener(lines[i])) {
125516
- result.push('');
125517
- }
125518
- }
125519
- return restoreCodeBlocks(result.join('\n'), protectedCode);
125520
- }
125521
-
125522
125550
  ;// ./processor/transform/mdxish/variables-code.ts
125523
125551
 
125524
125552
 
@@ -126386,6 +126414,12 @@ function mdxishMdastToMd(mdast) {
126386
126414
  .use(remarkStringify, {
126387
126415
  bullet: '-',
126388
126416
  emphasis: '_',
126417
+ // Escape literal braces in text so they don't parse as (often
126418
+ // unterminated) MDX expressions on the next round trip.
126419
+ unsafe: [
126420
+ { character: '{', inConstruct: 'phrasing' },
126421
+ { character: '}', inConstruct: 'phrasing' },
126422
+ ],
126389
126423
  });
126390
126424
  return processor.stringify(processor.runSync(mdast));
126391
126425
  }