@readme/markdown 13.7.2 → 13.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -11704,10 +11704,47 @@ var dist_utils_default = /*#__PURE__*/__webpack_require__.n(dist_utils);
11704
11704
 
11705
11705
  let mermaid;
11706
11706
  const { uppercase } = (dist_utils_default());
11707
+ // Module-level queue that batches mermaid nodes across all CodeTabs instances into a
11708
+ // single mermaid.run() call. This is necessary because mermaid generates SVG element IDs
11709
+ // using Date.now(), which collides when multiple diagrams render in the same millisecond.
11710
+ // Colliding IDs cause diagrams to overlap or break layout.
11711
+ //
11712
+ // Why not use `deterministicIDSeed` with a unique ID per diagram? Mermaid's implementation
11713
+ // only uses seed.length (not the seed value) to compute the starting ID, so every UUID
11714
+ // (36 chars) produces the same `mermaid-36` prefix — the collision remains.
11715
+ // See: https://github.com/mermaid-js/mermaid/blob/mermaid%4011.12.0/packages/mermaid/src/utils.ts#L755-L761
11716
+ //
11717
+ // These vars must be module-scoped (not per-instance refs) because the batching requires
11718
+ // cross-instance coordination. They are short-lived: the queue drains on the next macrotask
11719
+ // and cleanup clears everything on unmount.
11720
+ let mermaidQueue = [];
11721
+ let mermaidFlushTimer = null;
11722
+ let currentTheme;
11723
+ function queueMermaidNode(node, theme) {
11724
+ mermaidQueue.push(node);
11725
+ currentTheme = theme;
11726
+ if (!mermaidFlushTimer) {
11727
+ // setTimeout(0) defers to a macrotask, after all useEffects have queued their nodes
11728
+ mermaidFlushTimer = setTimeout(async () => {
11729
+ const nodes = [...mermaidQueue];
11730
+ mermaidQueue = [];
11731
+ mermaidFlushTimer = null;
11732
+ const module = await Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 1387, 23));
11733
+ mermaid = module.default;
11734
+ mermaid.initialize({
11735
+ startOnLoad: false,
11736
+ theme: currentTheme === 'dark' ? 'dark' : 'default',
11737
+ deterministicIds: true,
11738
+ });
11739
+ await mermaid.run({ nodes });
11740
+ }, 0);
11741
+ }
11742
+ }
11707
11743
  const CodeTabs = (props) => {
11708
11744
  const { children } = props;
11709
11745
  const theme = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useContext)(Theme);
11710
11746
  const isHydrated = useHydrated();
11747
+ const mermaidRef = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)(null);
11711
11748
  // Handle both array (from rehype-react in rendering mdxish) and single element (MDX/JSX runtime) cases
11712
11749
  // The children here is the individual code block objects
11713
11750
  const childrenArray = Array.isArray(children) ? children : [children];
@@ -11718,22 +11755,20 @@ const CodeTabs = (props) => {
11718
11755
  return Array.isArray(pre?.props?.children) ? pre.props.children[0] : pre?.props?.children;
11719
11756
  };
11720
11757
  const containAtLeastOneMermaid = childrenArray.some(pre => getCodeComponent(pre)?.props?.lang === 'mermaid');
11721
- // Render Mermaid diagram
11722
11758
  (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useEffect)(() => {
11723
- // Ensure we only render mermaids when frontend is hydrated to avoid hydration errors
11724
- // because mermaid mutates the DOM before react hydrates
11725
- if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated) {
11726
- Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 1387, 23)).then(module => {
11727
- mermaid = module.default;
11728
- mermaid.initialize({
11729
- startOnLoad: false,
11730
- theme: theme === 'dark' ? 'dark' : 'default',
11731
- });
11732
- mermaid.run({
11733
- nodes: document.querySelectorAll('.mermaid-render'),
11734
- });
11735
- });
11736
- }
11759
+ // Wait for hydration so mermaid's DOM mutations don't cause mismatches
11760
+ if (typeof window !== 'undefined' && containAtLeastOneMermaid && isHydrated && mermaidRef.current) {
11761
+ queueMermaidNode(mermaidRef.current, theme);
11762
+ }
11763
+ return () => {
11764
+ // Clear the batch timer on unmount to prevent mermaid from running
11765
+ // after the DOM is torn down
11766
+ if (mermaidFlushTimer) {
11767
+ clearTimeout(mermaidFlushTimer);
11768
+ mermaidFlushTimer = null;
11769
+ mermaidQueue = [];
11770
+ }
11771
+ };
11737
11772
  }, [containAtLeastOneMermaid, theme, isHydrated]);
11738
11773
  function handleClick({ target }, index) {
11739
11774
  const $wrap = target.parentElement.parentElement;
@@ -11749,15 +11784,13 @@ const CodeTabs = (props) => {
11749
11784
  const codeComponent = getCodeComponent(childrenArray[0]);
11750
11785
  if (codeComponent?.props?.lang === 'mermaid') {
11751
11786
  const value = codeComponent?.props?.value;
11752
- return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { className: "mermaid-render mermaid_single" }, value);
11787
+ return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("pre", { ref: mermaidRef, className: "mermaid-render mermaid_single" }, value));
11753
11788
  }
11754
11789
  }
11755
11790
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: `CodeTabs CodeTabs_initial theme-${theme}` },
11756
11791
  external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "CodeTabs-toolbar" }, childrenArray.map((pre, i) => {
11757
11792
  // the first or only child should be our Code component
11758
- const tabCodeComponent = Array.isArray(pre.props?.children)
11759
- ? pre.props.children[0]
11760
- : pre.props?.children;
11793
+ const tabCodeComponent = Array.isArray(pre.props?.children) ? pre.props.children[0] : pre.props?.children;
11761
11794
  const lang = tabCodeComponent?.props?.lang;
11762
11795
  const meta = tabCodeComponent?.props?.meta;
11763
11796
  /* istanbul ignore next */
@@ -11840,6 +11873,8 @@ const GlossaryContext = (0,external_amd_react_commonjs_react_commonjs2_react_roo
11840
11873
 
11841
11874
  const Glossary = ({ children, term: termProp, terms }) => {
11842
11875
  const term = (Array.isArray(children) ? children[0] : children) || termProp;
11876
+ if (!term)
11877
+ return null;
11843
11878
  const foundTerm = terms.find(i => term.toLowerCase() === i?.term?.toLowerCase());
11844
11879
  if (!foundTerm)
11845
11880
  return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("span", null, term);
@@ -12309,19 +12344,10 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
12309
12344
  records.forEach(record => {
12310
12345
  if (record.type === 'childList') {
12311
12346
  record.addedNodes.forEach(node => {
12312
- if (!(node instanceof HTMLElement))
12347
+ if (!(node instanceof HTMLElement) || !node.classList.contains(tailwindPrefix))
12313
12348
  return;
12314
- // Check the added node itself
12315
- if (node.classList.contains(tailwindPrefix)) {
12316
- traverse(node, addClasses);
12317
- shouldUpdate = true;
12318
- }
12319
- // Also check descendants — React may insert a parent node
12320
- // whose children contain TailwindRoot elements
12321
- node.querySelectorAll(`.${tailwindPrefix}`).forEach(child => {
12322
- traverse(child, addClasses);
12323
- shouldUpdate = true;
12324
- });
12349
+ traverse(node, addClasses);
12350
+ shouldUpdate = true;
12325
12351
  });
12326
12352
  }
12327
12353
  else if (record.type === 'attributes') {
@@ -12330,10 +12356,10 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
12330
12356
  addClasses(record.target);
12331
12357
  shouldUpdate = true;
12332
12358
  }
12333
- if (shouldUpdate) {
12334
- setClasses(Array.from(classesSet.current));
12335
- }
12336
12359
  });
12360
+ if (shouldUpdate) {
12361
+ setClasses(Array.from(classesSet.current));
12362
+ }
12337
12363
  });
12338
12364
  observer.observe(ref.current.parentElement, {
12339
12365
  subtree: true,
@@ -52956,6 +52982,10 @@ const plain = (node, opts = {}) => {
52956
52982
  };
52957
52983
  /* harmony default export */ const lib_plain = (plain);
52958
52984
 
52985
+ ;// ./processor/compile/gemoji.ts
52986
+ const gemoji = (node) => `:${node.name}:`;
52987
+ /* harmony default export */ const compile_gemoji = (gemoji);
52988
+
52959
52989
  ;// ./processor/compile/variable.ts
52960
52990
  const variable = (node) => `{user.${node.data?.hProperties?.name || ''}}`;
52961
52991
  /* harmony default export */ const compile_variable = (variable);
@@ -53006,6 +53036,7 @@ const extractText = (node) => {
53006
53036
 
53007
53037
 
53008
53038
 
53039
+
53009
53040
  const titleParser = unified().use(remarkParse).use(remarkGfm);
53010
53041
  // The title paragraph may contain custom AST nodes that `toMarkdown` doesn't
53011
53042
  // natively understand
@@ -53013,8 +53044,8 @@ const toMarkdownExtensions = [
53013
53044
  gfmToMarkdown(),
53014
53045
  // For mdx variable syntaxes (e.g., {user.name})
53015
53046
  mdxExpressionToMarkdown(),
53016
- // Important: This is required and would crash the parser if there's no variable node handler
53017
- { handlers: { [NodeTypes.variable]: compile_variable } },
53047
+ // Important: This is required and would crash the parser if there's no variable and gemoji node handler
53048
+ { handlers: { [NodeTypes.variable]: compile_variable, [NodeTypes.emoji]: compile_gemoji } },
53018
53049
  ];
53019
53050
  const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
53020
53051
  const findFirst = (node) => {
@@ -53409,7 +53440,7 @@ const embedTransformer = () => {
53409
53440
  *
53410
53441
  * @type {Array<Gemoji>}
53411
53442
  */
53412
- const gemoji = [
53443
+ const gemoji_gemoji = [
53413
53444
  {
53414
53445
  emoji: '😀',
53415
53446
  names: ['grinning'],
@@ -70352,7 +70383,7 @@ class Owlmoji {
70352
70383
  return null;
70353
70384
  };
70354
70385
  static nameToEmoji = nameToEmoji;
70355
- static owlmoji = gemoji.concat(owlmoji);
70386
+ static owlmoji = gemoji_gemoji.concat(owlmoji);
70356
70387
  }
70357
70388
 
70358
70389
  ;// ./processor/transform/gemoji+.ts
@@ -71262,6 +71293,316 @@ const mdxToHast = () => tree => {
71262
71293
  };
71263
71294
  /* harmony default export */ const mdx_to_hast = (mdxToHast);
71264
71295
 
71296
+ ;// ./lib/mdast-util/gemoji/index.ts
71297
+
71298
+
71299
+ function exitGemoji(token) {
71300
+ const name = this.sliceSerialize(token).slice(1, -1); // strip surrounding colons
71301
+ switch (Owlmoji.kind(name)) {
71302
+ case 'gemoji': {
71303
+ this.enter({ type: NodeTypes.emoji, value: Owlmoji.nameToEmoji[name], name }, token);
71304
+ this.exit(token);
71305
+ break;
71306
+ }
71307
+ case 'fontawesome': {
71308
+ this.enter({
71309
+ type: NodeTypes.i,
71310
+ value: name,
71311
+ data: { hName: 'i', hProperties: { className: ['fa-regular', name] } },
71312
+ }, token);
71313
+ this.exit(token);
71314
+ break;
71315
+ }
71316
+ case 'owlmoji': {
71317
+ this.enter({
71318
+ type: NodeTypes.emoji,
71319
+ value: `:${name}:`,
71320
+ name,
71321
+ data: {
71322
+ hName: 'img',
71323
+ hProperties: {
71324
+ src: `/public/img/emojis/${name}.png`,
71325
+ alt: `:${name}:`,
71326
+ title: `:${name}:`,
71327
+ className: 'emoji',
71328
+ align: 'absmiddle',
71329
+ height: '20',
71330
+ width: '20',
71331
+ },
71332
+ },
71333
+ }, token);
71334
+ this.exit(token);
71335
+ break;
71336
+ }
71337
+ default: {
71338
+ // Unknown shortcode, emit as literal text `:name:`
71339
+ this.enter({ type: 'text', value: `:${name}:` }, token);
71340
+ this.exit(token);
71341
+ break;
71342
+ }
71343
+ }
71344
+ }
71345
+ function gemojiFromMarkdown() {
71346
+ return {
71347
+ exit: {
71348
+ gemoji: exitGemoji,
71349
+ },
71350
+ };
71351
+ }
71352
+
71353
+ ;// ./node_modules/micromark-util-symbol/lib/codes.js
71354
+ /**
71355
+ * Character codes.
71356
+ *
71357
+ * This module is compiled away!
71358
+ *
71359
+ * micromark works based on character codes.
71360
+ * This module contains constants for the ASCII block and the replacement
71361
+ * character.
71362
+ * A couple of them are handled in a special way, such as the line endings
71363
+ * (CR, LF, and CR+LF, commonly known as end-of-line: EOLs), the tab (horizontal
71364
+ * tab) and its expansion based on what column it’s at (virtual space),
71365
+ * and the end-of-file (eof) character.
71366
+ * As values are preprocessed before handling them, the actual characters LF,
71367
+ * CR, HT, and NUL (which is present as the replacement character), are
71368
+ * guaranteed to not exist.
71369
+ *
71370
+ * Unicode basic latin block.
71371
+ */
71372
+ const codes = /** @type {const} */ ({
71373
+ carriageReturn: -5,
71374
+ lineFeed: -4,
71375
+ carriageReturnLineFeed: -3,
71376
+ horizontalTab: -2,
71377
+ virtualSpace: -1,
71378
+ eof: null,
71379
+ nul: 0,
71380
+ soh: 1,
71381
+ stx: 2,
71382
+ etx: 3,
71383
+ eot: 4,
71384
+ enq: 5,
71385
+ ack: 6,
71386
+ bel: 7,
71387
+ bs: 8,
71388
+ ht: 9, // `\t`
71389
+ lf: 10, // `\n`
71390
+ vt: 11, // `\v`
71391
+ ff: 12, // `\f`
71392
+ cr: 13, // `\r`
71393
+ so: 14,
71394
+ si: 15,
71395
+ dle: 16,
71396
+ dc1: 17,
71397
+ dc2: 18,
71398
+ dc3: 19,
71399
+ dc4: 20,
71400
+ nak: 21,
71401
+ syn: 22,
71402
+ etb: 23,
71403
+ can: 24,
71404
+ em: 25,
71405
+ sub: 26,
71406
+ esc: 27,
71407
+ fs: 28,
71408
+ gs: 29,
71409
+ rs: 30,
71410
+ us: 31,
71411
+ space: 32,
71412
+ exclamationMark: 33, // `!`
71413
+ quotationMark: 34, // `"`
71414
+ numberSign: 35, // `#`
71415
+ dollarSign: 36, // `$`
71416
+ percentSign: 37, // `%`
71417
+ ampersand: 38, // `&`
71418
+ apostrophe: 39, // `'`
71419
+ leftParenthesis: 40, // `(`
71420
+ rightParenthesis: 41, // `)`
71421
+ asterisk: 42, // `*`
71422
+ plusSign: 43, // `+`
71423
+ comma: 44, // `,`
71424
+ dash: 45, // `-`
71425
+ dot: 46, // `.`
71426
+ slash: 47, // `/`
71427
+ digit0: 48, // `0`
71428
+ digit1: 49, // `1`
71429
+ digit2: 50, // `2`
71430
+ digit3: 51, // `3`
71431
+ digit4: 52, // `4`
71432
+ digit5: 53, // `5`
71433
+ digit6: 54, // `6`
71434
+ digit7: 55, // `7`
71435
+ digit8: 56, // `8`
71436
+ digit9: 57, // `9`
71437
+ colon: 58, // `:`
71438
+ semicolon: 59, // `;`
71439
+ lessThan: 60, // `<`
71440
+ equalsTo: 61, // `=`
71441
+ greaterThan: 62, // `>`
71442
+ questionMark: 63, // `?`
71443
+ atSign: 64, // `@`
71444
+ uppercaseA: 65, // `A`
71445
+ uppercaseB: 66, // `B`
71446
+ uppercaseC: 67, // `C`
71447
+ uppercaseD: 68, // `D`
71448
+ uppercaseE: 69, // `E`
71449
+ uppercaseF: 70, // `F`
71450
+ uppercaseG: 71, // `G`
71451
+ uppercaseH: 72, // `H`
71452
+ uppercaseI: 73, // `I`
71453
+ uppercaseJ: 74, // `J`
71454
+ uppercaseK: 75, // `K`
71455
+ uppercaseL: 76, // `L`
71456
+ uppercaseM: 77, // `M`
71457
+ uppercaseN: 78, // `N`
71458
+ uppercaseO: 79, // `O`
71459
+ uppercaseP: 80, // `P`
71460
+ uppercaseQ: 81, // `Q`
71461
+ uppercaseR: 82, // `R`
71462
+ uppercaseS: 83, // `S`
71463
+ uppercaseT: 84, // `T`
71464
+ uppercaseU: 85, // `U`
71465
+ uppercaseV: 86, // `V`
71466
+ uppercaseW: 87, // `W`
71467
+ uppercaseX: 88, // `X`
71468
+ uppercaseY: 89, // `Y`
71469
+ uppercaseZ: 90, // `Z`
71470
+ leftSquareBracket: 91, // `[`
71471
+ backslash: 92, // `\`
71472
+ rightSquareBracket: 93, // `]`
71473
+ caret: 94, // `^`
71474
+ underscore: 95, // `_`
71475
+ graveAccent: 96, // `` ` ``
71476
+ lowercaseA: 97, // `a`
71477
+ lowercaseB: 98, // `b`
71478
+ lowercaseC: 99, // `c`
71479
+ lowercaseD: 100, // `d`
71480
+ lowercaseE: 101, // `e`
71481
+ lowercaseF: 102, // `f`
71482
+ lowercaseG: 103, // `g`
71483
+ lowercaseH: 104, // `h`
71484
+ lowercaseI: 105, // `i`
71485
+ lowercaseJ: 106, // `j`
71486
+ lowercaseK: 107, // `k`
71487
+ lowercaseL: 108, // `l`
71488
+ lowercaseM: 109, // `m`
71489
+ lowercaseN: 110, // `n`
71490
+ lowercaseO: 111, // `o`
71491
+ lowercaseP: 112, // `p`
71492
+ lowercaseQ: 113, // `q`
71493
+ lowercaseR: 114, // `r`
71494
+ lowercaseS: 115, // `s`
71495
+ lowercaseT: 116, // `t`
71496
+ lowercaseU: 117, // `u`
71497
+ lowercaseV: 118, // `v`
71498
+ lowercaseW: 119, // `w`
71499
+ lowercaseX: 120, // `x`
71500
+ lowercaseY: 121, // `y`
71501
+ lowercaseZ: 122, // `z`
71502
+ leftCurlyBrace: 123, // `{`
71503
+ verticalBar: 124, // `|`
71504
+ rightCurlyBrace: 125, // `}`
71505
+ tilde: 126, // `~`
71506
+ del: 127,
71507
+ // Unicode Specials block.
71508
+ byteOrderMarker: 65_279,
71509
+ // Unicode Specials block.
71510
+ replacementCharacter: 65_533 // `�`
71511
+ })
71512
+
71513
+ ;// ./lib/micromark/gemoji/syntax.ts
71514
+
71515
+ // Matches the name pattern from the original regex: \+1 or [-\w]+
71516
+ // see https://github.com/readmeio/markdown/blob/489a71e19b34f640595ce81e988dad631045186f/processor/transform/gemoji%2B.ts#L9
71517
+ function isNameChar(code) {
71518
+ if (code === null)
71519
+ return false;
71520
+ return ((code >= codes.lowercaseA && code <= codes.lowercaseZ) ||
71521
+ (code >= codes.uppercaseA && code <= codes.uppercaseZ) ||
71522
+ (code >= codes.digit0 && code <= codes.digit9) ||
71523
+ code === codes.dash ||
71524
+ code === codes.underscore);
71525
+ }
71526
+ const gemojiConstruct = {
71527
+ name: 'gemoji',
71528
+ tokenize,
71529
+ };
71530
+ function tokenize(effects, ok, nok) {
71531
+ let hasName = false;
71532
+ // Entry point — expect opening `:`
71533
+ const start = (code) => {
71534
+ if (code !== codes.colon)
71535
+ return nok(code);
71536
+ effects.enter('gemoji');
71537
+ effects.enter('gemojiMarker');
71538
+ effects.consume(code); // :
71539
+ effects.exit('gemojiMarker');
71540
+ effects.enter('gemojiName');
71541
+ return nameStart;
71542
+ };
71543
+ // First char after `:`, branch on `+` for :+1:, otherwise start normal name
71544
+ const nameStart = (code) => {
71545
+ if (code === codes.plusSign) {
71546
+ effects.consume(code); // +
71547
+ return plusOne;
71548
+ }
71549
+ if (isNameChar(code)) {
71550
+ hasName = true;
71551
+ effects.consume(code);
71552
+ return name;
71553
+ }
71554
+ // Not a valid shortcode start (e.g. `::`, `: `, `:<`)
71555
+ return nok(code);
71556
+ };
71557
+ // After `+`, only `1` is valid (for :+1:), anything else rejects
71558
+ // this is a special case for :+1: 👍 since + isnt a normal name character
71559
+ const plusOne = (code) => {
71560
+ if (code === codes.digit1) {
71561
+ hasName = true;
71562
+ effects.consume(code); // 1
71563
+ return nameEnd;
71564
+ }
71565
+ return nok(code);
71566
+ };
71567
+ // Consume name characters until we hit closing `:` or an invalid char
71568
+ const name = (code) => {
71569
+ if (code === codes.colon) {
71570
+ if (!hasName)
71571
+ return nok(code);
71572
+ return nameEnd(code);
71573
+ }
71574
+ if (isNameChar(code)) {
71575
+ effects.consume(code);
71576
+ return name;
71577
+ }
71578
+ // Invalid character in name (space, newline, special char) — reject
71579
+ return nok(code);
71580
+ };
71581
+ // Expect closing `:`
71582
+ const nameEnd = (code) => {
71583
+ if (code !== codes.colon)
71584
+ return nok(code);
71585
+ effects.exit('gemojiName');
71586
+ effects.enter('gemojiMarker');
71587
+ effects.consume(code); // :
71588
+ effects.exit('gemojiMarker');
71589
+ effects.exit('gemoji');
71590
+ return ok;
71591
+ };
71592
+ return start;
71593
+ }
71594
+ function syntax_gemoji() {
71595
+ return {
71596
+ text: { [codes.colon]: gemojiConstruct },
71597
+ };
71598
+ }
71599
+
71600
+ ;// ./lib/micromark/gemoji/index.ts
71601
+ /**
71602
+ * Micromark extension for :emoji: inline syntax.
71603
+ */
71604
+
71605
+
71265
71606
  ;// ./processor/transform/mdxish/normalize-malformed-md-syntax.ts
71266
71607
 
71267
71608
  // Marker patterns for multi-node emphasis detection
@@ -71618,6 +71959,7 @@ const normalizeEmphasisAST = () => (tree) => {
71618
71959
 
71619
71960
 
71620
71961
 
71962
+
71621
71963
  const isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
71622
71964
  const tableTypes = {
71623
71965
  tr: 'tableRow',
@@ -71625,10 +71967,12 @@ const tableTypes = {
71625
71967
  td: 'tableCell',
71626
71968
  };
71627
71969
  const tableNodeProcessor = unified()
71970
+ .data('micromarkExtensions', [syntax_gemoji()])
71971
+ .data('fromMarkdownExtensions', [gemojiFromMarkdown()])
71628
71972
  .use(remarkParse)
71629
71973
  .use(remarkMdx)
71630
71974
  .use(normalize_malformed_md_syntax)
71631
- .use([[callouts, { isMdxish: true }], gemoji_, code_tabs])
71975
+ .use([[callouts, { isMdxish: true }], code_tabs])
71632
71976
  .use(remarkGfm);
71633
71977
  /**
71634
71978
  * Check if children are only text nodes that might contain markdown
@@ -72501,7 +72845,6 @@ const mdxishTransformers = [
72501
72845
  [callouts, { isMdxish: true }],
72502
72846
  code_tabs,
72503
72847
  transform_images,
72504
- gemoji_,
72505
72848
  ];
72506
72849
  /* harmony default export */ const transform = (Object.values(defaultTransforms));
72507
72850
 
@@ -92283,10 +92626,6 @@ const embed_embed = (node) => {
92283
92626
  };
92284
92627
  /* harmony default export */ const compile_embed = (embed_embed);
92285
92628
 
92286
- ;// ./processor/compile/gemoji.ts
92287
- const gemoji_gemoji = (node) => `:${node.name}:`;
92288
- /* harmony default export */ const compile_gemoji = (gemoji_gemoji);
92289
-
92290
92629
  ;// ./processor/compile/html-block.ts
92291
92630
 
92292
92631
  const htmlBlock = (node) => {
@@ -94269,6 +94608,86 @@ const mdxComponentHandlers = {
94269
94608
  [NodeTypes.htmlBlock]: htmlBlockHandler,
94270
94609
  };
94271
94610
 
94611
+ ;// ./processor/transform/mdxish/close-self-closing-html-tags.ts
94612
+
94613
+ /**
94614
+ * HTML void elements that are legitimately self-closing per the HTML spec.
94615
+ * These should NOT be transformed.
94616
+ *
94617
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#void-elements
94618
+ */
94619
+ const HTML_VOID_ELEMENTS = new Set([
94620
+ 'area',
94621
+ 'base',
94622
+ 'br',
94623
+ 'col',
94624
+ 'embed',
94625
+ 'hr',
94626
+ 'img',
94627
+ 'input',
94628
+ 'link',
94629
+ 'meta',
94630
+ 'param',
94631
+ 'source',
94632
+ 'track',
94633
+ 'wbr',
94634
+ ]);
94635
+ /**
94636
+ * Matches self-closing HTML tags: `<tagname/>` or `<tagname attr="val" />`
94637
+ *
94638
+ * Captures:
94639
+ * 1 - tag name (lowercase letters, digits, hyphens)
94640
+ * 2 - attributes (everything between tag name and `/>`)
94641
+ *
94642
+ * The attribute portion skips over quoted strings (`"..."` and `'...'`) so that
94643
+ * a `/>` inside an attribute value (e.g. `title="use /> here"`) does not cause
94644
+ * a premature match.
94645
+ *
94646
+ * Only matches lowercase tag names to avoid interfering with PascalCase
94647
+ * JSX custom components (e.g. `<MyComponent />`), which are handled
94648
+ * separately by mdxish-component-blocks.
94649
+ */
94650
+ const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)?)?\s*\/>/g;
94651
+ /**
94652
+ * String-level preprocessor that converts self-closing non-void HTML tags
94653
+ * into explicitly closed tags.
94654
+ *
94655
+ * Problem: `rehype-raw` (via parse5) follows the HTML spec where the `/` in
94656
+ * `<i />` is ignored for non-void elements. This causes `<i />` to be parsed
94657
+ * as an opening `<i>` tag, which then wraps all subsequent content.
94658
+ *
94659
+ * Solution: Transform `<i />` → `<i></i>` before parsing, so rehype-raw
94660
+ * sees a properly closed element.
94661
+ *
94662
+ * This preprocessor:
94663
+ * - Skips void elements (`<br />`, `<hr />`, `<img />`, etc.)
94664
+ * - Skips PascalCase tags (custom components handled elsewhere)
94665
+ * - Protects code blocks from transformation
94666
+ *
94667
+ * @example
94668
+ * closeSelfClosingHtmlTags('<i/> text') // '<i></i> text'
94669
+ * closeSelfClosingHtmlTags('<br />') // '<br />' (void, untouched)
94670
+ * closeSelfClosingHtmlTags('<MyComp />') // '<MyComp />' (PascalCase, untouched)
94671
+ * closeSelfClosingHtmlTags('<i class="icon" />') // '<i class="icon"></i>'
94672
+ */
94673
+ function closeSelfClosingHtmlTags(content) {
94674
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
94675
+ const result = protectedContent.replace(SELF_CLOSING_TAG_RE, (match, tagName, attrs) => {
94676
+ // Skip void elements (legitimately self-closing)
94677
+ if (HTML_VOID_ELEMENTS.has(tagName)) {
94678
+ return match;
94679
+ }
94680
+ // Skip tags with hyphens — these are custom elements (web components)
94681
+ // and should not be rewritten (e.g. <my-component />)
94682
+ if (tagName.includes('-')) {
94683
+ return match;
94684
+ }
94685
+ const attributes = attrs?.trim() ? ` ${attrs.trim()}` : '';
94686
+ return `<${tagName}${attributes}></${tagName}>`;
94687
+ });
94688
+ return restoreCodeBlocks(result, protectedCode);
94689
+ }
94690
+
94272
94691
  ;// ./processor/transform/mdxish/evaluate-expressions.ts
94273
94692
 
94274
94693
 
@@ -94558,166 +94977,6 @@ function legacyVariableFromMarkdown() {
94558
94977
  };
94559
94978
  }
94560
94979
 
94561
- ;// ./node_modules/micromark-util-symbol/lib/codes.js
94562
- /**
94563
- * Character codes.
94564
- *
94565
- * This module is compiled away!
94566
- *
94567
- * micromark works based on character codes.
94568
- * This module contains constants for the ASCII block and the replacement
94569
- * character.
94570
- * A couple of them are handled in a special way, such as the line endings
94571
- * (CR, LF, and CR+LF, commonly known as end-of-line: EOLs), the tab (horizontal
94572
- * tab) and its expansion based on what column it’s at (virtual space),
94573
- * and the end-of-file (eof) character.
94574
- * As values are preprocessed before handling them, the actual characters LF,
94575
- * CR, HT, and NUL (which is present as the replacement character), are
94576
- * guaranteed to not exist.
94577
- *
94578
- * Unicode basic latin block.
94579
- */
94580
- const codes = /** @type {const} */ ({
94581
- carriageReturn: -5,
94582
- lineFeed: -4,
94583
- carriageReturnLineFeed: -3,
94584
- horizontalTab: -2,
94585
- virtualSpace: -1,
94586
- eof: null,
94587
- nul: 0,
94588
- soh: 1,
94589
- stx: 2,
94590
- etx: 3,
94591
- eot: 4,
94592
- enq: 5,
94593
- ack: 6,
94594
- bel: 7,
94595
- bs: 8,
94596
- ht: 9, // `\t`
94597
- lf: 10, // `\n`
94598
- vt: 11, // `\v`
94599
- ff: 12, // `\f`
94600
- cr: 13, // `\r`
94601
- so: 14,
94602
- si: 15,
94603
- dle: 16,
94604
- dc1: 17,
94605
- dc2: 18,
94606
- dc3: 19,
94607
- dc4: 20,
94608
- nak: 21,
94609
- syn: 22,
94610
- etb: 23,
94611
- can: 24,
94612
- em: 25,
94613
- sub: 26,
94614
- esc: 27,
94615
- fs: 28,
94616
- gs: 29,
94617
- rs: 30,
94618
- us: 31,
94619
- space: 32,
94620
- exclamationMark: 33, // `!`
94621
- quotationMark: 34, // `"`
94622
- numberSign: 35, // `#`
94623
- dollarSign: 36, // `$`
94624
- percentSign: 37, // `%`
94625
- ampersand: 38, // `&`
94626
- apostrophe: 39, // `'`
94627
- leftParenthesis: 40, // `(`
94628
- rightParenthesis: 41, // `)`
94629
- asterisk: 42, // `*`
94630
- plusSign: 43, // `+`
94631
- comma: 44, // `,`
94632
- dash: 45, // `-`
94633
- dot: 46, // `.`
94634
- slash: 47, // `/`
94635
- digit0: 48, // `0`
94636
- digit1: 49, // `1`
94637
- digit2: 50, // `2`
94638
- digit3: 51, // `3`
94639
- digit4: 52, // `4`
94640
- digit5: 53, // `5`
94641
- digit6: 54, // `6`
94642
- digit7: 55, // `7`
94643
- digit8: 56, // `8`
94644
- digit9: 57, // `9`
94645
- colon: 58, // `:`
94646
- semicolon: 59, // `;`
94647
- lessThan: 60, // `<`
94648
- equalsTo: 61, // `=`
94649
- greaterThan: 62, // `>`
94650
- questionMark: 63, // `?`
94651
- atSign: 64, // `@`
94652
- uppercaseA: 65, // `A`
94653
- uppercaseB: 66, // `B`
94654
- uppercaseC: 67, // `C`
94655
- uppercaseD: 68, // `D`
94656
- uppercaseE: 69, // `E`
94657
- uppercaseF: 70, // `F`
94658
- uppercaseG: 71, // `G`
94659
- uppercaseH: 72, // `H`
94660
- uppercaseI: 73, // `I`
94661
- uppercaseJ: 74, // `J`
94662
- uppercaseK: 75, // `K`
94663
- uppercaseL: 76, // `L`
94664
- uppercaseM: 77, // `M`
94665
- uppercaseN: 78, // `N`
94666
- uppercaseO: 79, // `O`
94667
- uppercaseP: 80, // `P`
94668
- uppercaseQ: 81, // `Q`
94669
- uppercaseR: 82, // `R`
94670
- uppercaseS: 83, // `S`
94671
- uppercaseT: 84, // `T`
94672
- uppercaseU: 85, // `U`
94673
- uppercaseV: 86, // `V`
94674
- uppercaseW: 87, // `W`
94675
- uppercaseX: 88, // `X`
94676
- uppercaseY: 89, // `Y`
94677
- uppercaseZ: 90, // `Z`
94678
- leftSquareBracket: 91, // `[`
94679
- backslash: 92, // `\`
94680
- rightSquareBracket: 93, // `]`
94681
- caret: 94, // `^`
94682
- underscore: 95, // `_`
94683
- graveAccent: 96, // `` ` ``
94684
- lowercaseA: 97, // `a`
94685
- lowercaseB: 98, // `b`
94686
- lowercaseC: 99, // `c`
94687
- lowercaseD: 100, // `d`
94688
- lowercaseE: 101, // `e`
94689
- lowercaseF: 102, // `f`
94690
- lowercaseG: 103, // `g`
94691
- lowercaseH: 104, // `h`
94692
- lowercaseI: 105, // `i`
94693
- lowercaseJ: 106, // `j`
94694
- lowercaseK: 107, // `k`
94695
- lowercaseL: 108, // `l`
94696
- lowercaseM: 109, // `m`
94697
- lowercaseN: 110, // `n`
94698
- lowercaseO: 111, // `o`
94699
- lowercaseP: 112, // `p`
94700
- lowercaseQ: 113, // `q`
94701
- lowercaseR: 114, // `r`
94702
- lowercaseS: 115, // `s`
94703
- lowercaseT: 116, // `t`
94704
- lowercaseU: 117, // `u`
94705
- lowercaseV: 118, // `v`
94706
- lowercaseW: 119, // `w`
94707
- lowercaseX: 120, // `x`
94708
- lowercaseY: 121, // `y`
94709
- lowercaseZ: 122, // `z`
94710
- leftCurlyBrace: 123, // `{`
94711
- verticalBar: 124, // `|`
94712
- rightCurlyBrace: 125, // `}`
94713
- tilde: 126, // `~`
94714
- del: 127,
94715
- // Unicode Specials block.
94716
- byteOrderMarker: 65_279,
94717
- // Unicode Specials block.
94718
- replacementCharacter: 65_533 // `�`
94719
- })
94720
-
94721
94980
  ;// ./lib/micromark/legacy-variable/syntax.ts
94722
94981
 
94723
94982
 
@@ -94729,9 +94988,9 @@ function isAllowedValueChar(code) {
94729
94988
  }
94730
94989
  const legacyVariableConstruct = {
94731
94990
  name: 'legacyVariable',
94732
- tokenize,
94991
+ tokenize: syntax_tokenize,
94733
94992
  };
94734
- function tokenize(effects, ok, nok) {
94993
+ function syntax_tokenize(effects, ok, nok) {
94735
94994
  let hasValue = false;
94736
94995
  const start = (code) => {
94737
94996
  if (code !== codes.lessThan)
@@ -95870,6 +96129,8 @@ const EMPTY_CODE_PLACEHOLDER = {
95870
96129
 
95871
96130
 
95872
96131
 
96132
+
96133
+
95873
96134
 
95874
96135
 
95875
96136
 
@@ -95912,8 +96173,8 @@ const preprocessBody = (text) => {
95912
96173
  };
95913
96174
  /** Markdown parser */
95914
96175
  const contentParser = unified()
95915
- .data('micromarkExtensions', [legacyVariable(), looseHtmlEntity()])
95916
- .data('fromMarkdownExtensions', [legacyVariableFromMarkdown(), emptyTaskListItemFromMarkdown(), looseHtmlEntityFromMarkdown()])
96176
+ .data('micromarkExtensions', [syntax_gemoji(), legacyVariable(), looseHtmlEntity()])
96177
+ .data('fromMarkdownExtensions', [gemojiFromMarkdown(), legacyVariableFromMarkdown(), emptyTaskListItemFromMarkdown(), looseHtmlEntityFromMarkdown()])
95917
96178
  .use(remarkParse)
95918
96179
  .use(remarkBreaks)
95919
96180
  .use(remarkGfm)
@@ -95926,8 +96187,8 @@ const contentParser = unified()
95926
96187
  * such as `<ul><li>https://a</li>\n</ul>` due to subtokenizing recursion for URLs
95927
96188
  */
95928
96189
  const markdownToHtml = unified()
95929
- .data('micromarkExtensions', [gfmStrikethrough(), legacyVariable(), looseHtmlEntity()])
95930
- .data('fromMarkdownExtensions', [gfmStrikethroughFromMarkdown(), legacyVariableFromMarkdown(), emptyTaskListItemFromMarkdown(), looseHtmlEntityFromMarkdown()])
96190
+ .data('micromarkExtensions', [gfmStrikethrough(), syntax_gemoji(), legacyVariable(), looseHtmlEntity()])
96191
+ .data('fromMarkdownExtensions', [gfmStrikethroughFromMarkdown(), gemojiFromMarkdown(), legacyVariableFromMarkdown(), emptyTaskListItemFromMarkdown(), looseHtmlEntityFromMarkdown()])
95931
96192
  .use(remarkParse)
95932
96193
  .use(normalize_malformed_md_syntax)
95933
96194
  .use(remarkRehype)
@@ -96062,11 +96323,34 @@ const parseBlock = (text) => {
96062
96323
  const tree = contentParser.runSync(contentParser.parse(text));
96063
96324
  return tree.children;
96064
96325
  };
96065
- const parseInline = (text) => {
96326
+ /**
96327
+ * Minimal parser for api-header titles.
96328
+ * Disables markdown constructs that are not parsed in legacy (headings, lists)
96329
+ */
96330
+ const apiHeaderTitleParser = unified()
96331
+ .data('micromarkExtensions', [
96332
+ syntax_gemoji(),
96333
+ legacyVariable(),
96334
+ looseHtmlEntity(),
96335
+ {
96336
+ disable: {
96337
+ null: [
96338
+ 'blockQuote',
96339
+ 'headingAtx',
96340
+ 'list',
96341
+ 'thematicBreak',
96342
+ ],
96343
+ },
96344
+ },
96345
+ ])
96346
+ .data('fromMarkdownExtensions', [gemojiFromMarkdown(), legacyVariableFromMarkdown(), looseHtmlEntityFromMarkdown()])
96347
+ .use(remarkParse)
96348
+ .use(remarkGfm);
96349
+ const parseApiHeaderTitle = (text) => {
96066
96350
  if (!text.trim())
96067
96351
  return textToInline(text);
96068
- const tree = contentParser.runSync(contentParser.parse(text));
96069
- return tree.children;
96352
+ const tree = apiHeaderTitleParser.runSync(apiHeaderTitleParser.parse(text));
96353
+ return tree.children.flatMap(n => n.type === 'paragraph' && 'children' in n ? n.children : [n]);
96070
96354
  };
96071
96355
  /**
96072
96356
  * Transform a magicBlock node into final MDAST nodes.
@@ -96125,7 +96409,7 @@ function transformMagicBlock(blockType, data, rawValue, options = {}) {
96125
96409
  const depth = headerJson.level || (compatibilityMode ? 1 : 2);
96126
96410
  return [
96127
96411
  wrapPinnedBlocks({
96128
- children: 'title' in headerJson ? parseInline(headerJson.title || '') : [],
96412
+ children: 'title' in headerJson ? parseApiHeaderTitle(headerJson.title || '') : [],
96129
96413
  depth,
96130
96414
  type: 'heading',
96131
96415
  }, data),
@@ -97173,6 +97457,179 @@ function mdxish_jsx_to_mdast_extractText(nodes) {
97173
97457
  })
97174
97458
  .join('');
97175
97459
  }
97460
+ const FIGURE_OPEN_REGEX = /^<figure(\s[^>]*)?>$/;
97461
+ const FIGURE_CLOSE_REGEX = /^\s*<\/figure>\s*$/;
97462
+ const FIGURE_COMPLETE_REGEX = /^<figure(\s[^>]*)?>[\s\S]*<\/figure>\s*$/;
97463
+ const FIGCAPTION_REGEX = /<figcaption>(.*?)<\/figcaption>/s;
97464
+ const FIGCAPTION_OPEN_REGEX = /^<figcaption>$/;
97465
+ const FIGCAPTION_CLOSE_REGEX = /^<\/figcaption>$/;
97466
+ /**
97467
+ * Extracts an image or image-block from a node. If the node itself is an image/image-block,
97468
+ * it is returned directly. If the node is a paragraph, its children are searched for one.
97469
+ * This is needed because standalone markdown images (`![](url)`) may be wrapped in a
97470
+ * paragraph node by the parser, or already transformed to image-block by imageTransformer.
97471
+ */
97472
+ function findImageInNode(node) {
97473
+ if (node.type === 'image' || node.type === NodeTypes.imageBlock)
97474
+ return node;
97475
+ if (node.type === 'paragraph') {
97476
+ return node.children.find(child => child.type === 'image' || child.type === NodeTypes.imageBlock);
97477
+ }
97478
+ return undefined;
97479
+ }
97480
+ /**
97481
+ * Parses a complete `<figure>` HTML string into image URL, alt text, and optional caption.
97482
+ * Returns undefined if the HTML doesn't contain a recognizable image.
97483
+ */
97484
+ function parseCompleteFigure(html) {
97485
+ const imageMatch = /!\[([^\]]*)\]\(([^)]+)\)/.exec(html);
97486
+ if (!imageMatch)
97487
+ return undefined;
97488
+ const captionMatch = FIGCAPTION_REGEX.exec(html);
97489
+ return {
97490
+ alt: imageMatch[1],
97491
+ caption: captionMatch?.[1],
97492
+ url: imageMatch[2],
97493
+ };
97494
+ }
97495
+ /**
97496
+ * Builds a FigureNode containing an image and optional figcaption from parsed figure data.
97497
+ */
97498
+ function buildFigureNode(imageNode, captionText, position) {
97499
+ const figureChildren = [imageNode];
97500
+ if (captionText) {
97501
+ figureChildren.push({
97502
+ children: [{ type: 'text', value: captionText }],
97503
+ data: { hName: 'figcaption' },
97504
+ type: 'figcaption',
97505
+ });
97506
+ }
97507
+ return {
97508
+ children: figureChildren,
97509
+ data: { hName: 'figure' },
97510
+ position,
97511
+ type: 'figure',
97512
+ };
97513
+ }
97514
+ /**
97515
+ * Scans siblings starting at `startIndex` within a parent's children looking for the
97516
+ * figcaption text and </figure> closing tag. Handles three fragmentation patterns:
97517
+ *
97518
+ * 1. Combined: `<figcaption>Hello</figcaption>\n</figure>` in one html node
97519
+ * 2. Separate: `<figcaption>Hello</figcaption>` then `</figure>` as sibling html nodes
97520
+ * 3. Split (table cells): `<figcaption>` + text(Hello) + `</figcaption>` + `</figure>` as siblings
97521
+ */
97522
+ function scanForFigcaptionAndClose(children, startIndex) {
97523
+ let captionText;
97524
+ let endIndex = startIndex - 1;
97525
+ let foundClose = false;
97526
+ for (let j = startIndex; j < children.length; j += 1) {
97527
+ const sibling = children[j];
97528
+ const htmlValue = sibling.type === 'html' ? sibling.value : undefined;
97529
+ if (htmlValue === undefined && sibling.type !== 'text')
97530
+ break;
97531
+ // Standalone </figure>
97532
+ if (htmlValue && FIGURE_CLOSE_REGEX.test(htmlValue)) {
97533
+ endIndex = j;
97534
+ foundClose = true;
97535
+ break;
97536
+ }
97537
+ // Combined <figcaption>...</figcaption> in one node (possibly with </figure>)
97538
+ if (htmlValue) {
97539
+ const figcaptionMatch = FIGCAPTION_REGEX.exec(htmlValue);
97540
+ if (figcaptionMatch) {
97541
+ captionText = figcaptionMatch[1];
97542
+ if (FIGURE_CLOSE_REGEX.test(htmlValue.replace(FIGCAPTION_REGEX, ''))) {
97543
+ endIndex = j;
97544
+ foundClose = true;
97545
+ break;
97546
+ }
97547
+ const nextSibling = children[j + 1];
97548
+ if (nextSibling?.type === 'html' && FIGURE_CLOSE_REGEX.test(nextSibling.value)) {
97549
+ endIndex = j + 1;
97550
+ foundClose = true;
97551
+ break;
97552
+ }
97553
+ }
97554
+ }
97555
+ // Split figcaption: <figcaption> + text + </figcaption> as separate nodes (table cells)
97556
+ if (htmlValue && FIGCAPTION_OPEN_REGEX.test(htmlValue)) {
97557
+ const textNode = children[j + 1];
97558
+ const closeCaption = children[j + 2];
97559
+ if (textNode?.type === 'text' &&
97560
+ closeCaption?.type === 'html' &&
97561
+ FIGCAPTION_CLOSE_REGEX.test(closeCaption.value)) {
97562
+ captionText = textNode.value;
97563
+ const closeFigure = children[j + 3];
97564
+ if (closeFigure?.type === 'html' && FIGURE_CLOSE_REGEX.test(closeFigure.value)) {
97565
+ endIndex = j + 3;
97566
+ foundClose = true;
97567
+ break;
97568
+ }
97569
+ }
97570
+ }
97571
+ }
97572
+ return { captionText, endIndex, foundClose };
97573
+ }
97574
+ /**
97575
+ * Reconstruct fragmented or complete HTML <figure> elements into figure MDAST nodes.
97576
+ *
97577
+ * Handles three html-based cases (the JSX case is handled by transformFigure in COMPONENT_MAP):
97578
+ * 1. Complete: A single html node containing the full `<figure>...<figcaption>...</figure>` block
97579
+ * (e.g. inside callouts where blockquote parsing keeps it together).
97580
+ * 2. Fragmented siblings: `terminateHtmlFlowBlocks` splits `<figure>` into separate sibling
97581
+ * nodes (open tag, image, figcaption, close tag).
97582
+ * 3. Split tags (GFM table cells): Each tag becomes its own html node with text nodes between them.
97583
+ *
97584
+ * Runs on all parent nodes so it works inside callouts, tables, and at root level.
97585
+ */
97586
+ function reassembleHtmlFigures(tree) {
97587
+ // Case 1: Handle complete <figure> blocks in a single html node (e.g. inside callouts)
97588
+ visit(tree, 'html', (node, index, parent) => {
97589
+ if (!parent || index === undefined)
97590
+ return;
97591
+ if (!FIGURE_COMPLETE_REGEX.test(node.value.trim()))
97592
+ return;
97593
+ const parsed = parseCompleteFigure(node.value);
97594
+ if (!parsed)
97595
+ return;
97596
+ const imageNode = {
97597
+ type: 'image',
97598
+ url: parsed.url,
97599
+ alt: parsed.alt,
97600
+ };
97601
+ const figureNode = buildFigureNode(imageNode, parsed.caption, node.position);
97602
+ parent.children.splice(index, 1, figureNode);
97603
+ });
97604
+ // Case 2 & 3: Handle fragmented <figure> blocks split across sibling nodes
97605
+ const processChildren = (parent) => {
97606
+ const children = parent.children;
97607
+ let i = 0;
97608
+ while (i < children.length) {
97609
+ const node = children[i];
97610
+ const nextNode = children[i + 1];
97611
+ const imageNode = nextNode ? findImageInNode(nextNode) : undefined;
97612
+ if (node.type === 'html' &&
97613
+ FIGURE_OPEN_REGEX.test(node.value.trim()) &&
97614
+ imageNode) {
97615
+ const { captionText, endIndex, foundClose } = scanForFigcaptionAndClose(children, i + 2);
97616
+ if (foundClose) {
97617
+ const figureNode = buildFigureNode(imageNode, captionText, node.position);
97618
+ parent.children.splice(i, endIndex - i + 1, figureNode);
97619
+ }
97620
+ }
97621
+ i += 1;
97622
+ }
97623
+ };
97624
+ // Process all parent nodes (root, callouts, blockquotes, table cells, list items)
97625
+ visit(tree, (node) => {
97626
+ if ('children' in node)
97627
+ processChildren(node);
97628
+ });
97629
+ }
97630
+ /**
97631
+ * Transforms an inline `<Anchor>` JSX element into a readme-anchor MDAST node.
97632
+ */
97176
97633
  const transformAnchor = (jsx) => {
97177
97634
  const attrs = getAttrs(jsx);
97178
97635
  const { href = '', label, target, title } = attrs;
@@ -97191,6 +97648,10 @@ const transformAnchor = (jsx) => {
97191
97648
  position: jsx.position,
97192
97649
  };
97193
97650
  };
97651
+ /**
97652
+ * Transforms an `<Image />` JSX element into an image-block MDAST node.
97653
+ * Normalizes attributes (align, border, width→sizing) and parses caption markdown into children.
97654
+ */
97194
97655
  const transformImage = (jsx) => {
97195
97656
  const attrs = getAttrs(jsx);
97196
97657
  const { align, alt = '', border, caption, className, height, lazy, src = '', title = '', width } = attrs;
@@ -97230,6 +97691,9 @@ const transformImage = (jsx) => {
97230
97691
  position: jsx.position,
97231
97692
  };
97232
97693
  };
97694
+ /**
97695
+ * Transforms a `<Callout>` JSX element into an rdme-callout MDAST node.
97696
+ */
97233
97697
  const transformCallout = (jsx) => {
97234
97698
  const attrs = getAttrs(jsx);
97235
97699
  const { empty = false, icon = '', theme = '' } = attrs;
@@ -97247,6 +97711,9 @@ const transformCallout = (jsx) => {
97247
97711
  position: jsx.position,
97248
97712
  };
97249
97713
  };
97714
+ /**
97715
+ * Transforms an `<Embed />` JSX element into an embed-block MDAST node.
97716
+ */
97250
97717
  const transformEmbed = (jsx) => {
97251
97718
  const attrs = getAttrs(jsx);
97252
97719
  const { favicon, height, href, html, iframe, image, providerName, providerUrl, provider, title = '', typeOfEmbed, url = '', width, } = attrs;
@@ -97275,6 +97742,9 @@ const transformEmbed = (jsx) => {
97275
97742
  position: jsx.position,
97276
97743
  };
97277
97744
  };
97745
+ /**
97746
+ * Transforms a `<Recipe />` JSX element into a recipe MDAST node.
97747
+ */
97278
97748
  const transformRecipe = (jsx) => {
97279
97749
  const attrs = getAttrs(jsx);
97280
97750
  const { backgroundColor = '', emoji = '', id = '', link = '', slug = '', title = '' } = attrs;
@@ -97411,21 +97881,44 @@ const transformTable = (jsx) => {
97411
97881
  children: rows,
97412
97882
  };
97413
97883
  };
97884
+ /**
97885
+ * Transforms a `<figure>` JSX element into a FigureNode.
97886
+ * Inside JSX tables with blank lines, the parser treats `<figure>` as an mdxJsxFlowElement
97887
+ * containing a paragraph with the image and a `<figcaption>` mdxJsxTextElement as children.
97888
+ */
97889
+ const transformFigure = (jsx) => {
97890
+ let imageNode;
97891
+ let captionText;
97892
+ visit(jsx, (child) => {
97893
+ if (!imageNode && (child.type === 'image' || child.type === NodeTypes.imageBlock)) {
97894
+ imageNode = child;
97895
+ }
97896
+ if (!captionText && child.type === 'mdxJsxTextElement' && child.name === 'figcaption') {
97897
+ captionText = mdxish_jsx_to_mdast_extractText(child.children);
97898
+ }
97899
+ });
97900
+ if (!imageNode)
97901
+ return null;
97902
+ return buildFigureNode(imageNode, captionText, jsx.position);
97903
+ };
97414
97904
  const COMPONENT_MAP = {
97415
97905
  Callout: transformCallout,
97416
97906
  Embed: transformEmbed,
97417
97907
  Image: transformImage,
97908
+ figure: transformFigure,
97418
97909
  Recipe: transformRecipe,
97419
97910
  Table: transformTable,
97420
97911
  };
97421
97912
  /**
97422
- * Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
97913
+ * Transform mdxJsxFlowElement nodes, magic block nodes, and HTML figure elements
97914
+ * into proper MDAST node types.
97423
97915
  *
97424
97916
  * This transformer runs after mdxishComponentBlocks and converts:
97425
- * - JSX component elements (Image, Callout, Embed, Recipe) into their corresponding MDAST types
97917
+ * - JSX component elements (Image, Callout, Embed, Recipe, figure) into their corresponding MDAST types
97426
97918
  * - Magic block image nodes (type: 'image') into image-block
97427
97919
  * - Magic block embed nodes (type: 'embed') into embed-block
97428
- * - Figure nodes (magic blocks with captions) into flat image-block with caption string
97920
+ * - Fragmented HTML <figure> blocks (from terminateHtmlFlowBlocks) back into figure nodes
97921
+ * - Figure nodes (from magic blocks, HTML, or JSX) into flat image-block with caption string
97429
97922
  * - Normalizes all image-block attrs (border, align, sizing, caption) to a consistent shape
97430
97923
  *
97431
97924
  * This is controlled by the `newEditorTypes` flag to maintain backwards compatibility.
@@ -97472,6 +97965,9 @@ const mdxishJsxToMdast = () => tree => {
97472
97965
  parent.children[index] = newNode;
97473
97966
  return SKIP;
97474
97967
  });
97968
+ // Reassembling fragmented HTML <figure> blocks into proper figure/figcaption nodes
97969
+ // this will then later be transformed into image-block nodes by imageTransformer
97970
+ reassembleHtmlFigures(tree);
97475
97971
  // Flatten figure nodes (magic blocks with captions) into image-block nodes
97476
97972
  const isFigure = (node) => node.type === 'figure';
97477
97973
  visit(tree, isFigure, (node, index, parent) => {
@@ -97724,6 +98220,7 @@ function restoreSnakeCase(placeholderName, mapping) {
97724
98220
  ;// ./processor/transform/mdxish/mdxish-tables-to-jsx.ts
97725
98221
 
97726
98222
 
98223
+ const SELF_CLOSING_JSX_REGEX = /^\s*<[A-Z][^>]*\/>\s*$/;
97727
98224
  const mdxish_tables_to_jsx_alignToStyle = (align) => {
97728
98225
  if (!align || align === 'left')
97729
98226
  return null;
@@ -97750,7 +98247,7 @@ const mdxishTablesToJsx = () => tree => {
97750
98247
  visit(tree, (node) => ['table', 'tableau'].includes(node.type), (table, index, parent) => {
97751
98248
  let hasFlowContent = false;
97752
98249
  visit(table, mdxish_tables_to_jsx_isTableCell, (cell) => {
97753
- if (cell.children.length === 0)
98250
+ if (hasFlowContent || cell.children.length === 0)
97754
98251
  return;
97755
98252
  const content = cell.children.length === 1 && cell.children[0].type === 'paragraph'
97756
98253
  ? cell.children[0].children[0]
@@ -97761,17 +98258,21 @@ const mdxishTablesToJsx = () => tree => {
97761
98258
  breakParent.children.splice(breakIndex, 1, { type: 'text', value: '\n' });
97762
98259
  });
97763
98260
  if (!(phrasing(content) || content.type === 'plain') && content.type !== 'escape') {
97764
- if (content.type === 'html')
97765
- return;
97766
- hasFlowContent = true;
97767
- return EXIT;
97768
- }
97769
- visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
97770
- if (node.value.match(/\n/)) {
98261
+ // Plain HTML (e.g. <div>Hello</div>) is skipped here — it stays in GFM cells fine.
98262
+ // But self-closing JSX components (e.g. <Image src="..." caption="..." />) serialize
98263
+ // with newlines that break GFM cells, so they must trigger JSX <Table> serialization.
98264
+ const isPlainHtml = content.type === 'html' && !SELF_CLOSING_JSX_REGEX.test(content.value);
98265
+ if (!isPlainHtml) {
97771
98266
  hasFlowContent = true;
97772
- return EXIT;
97773
98267
  }
97774
- });
98268
+ }
98269
+ if (!hasFlowContent) {
98270
+ visit(cell, mdxish_tables_to_jsx_isLiteral, (node) => {
98271
+ if (node.value.match(/\n/)) {
98272
+ hasFlowContent = true;
98273
+ }
98274
+ });
98275
+ }
97775
98276
  });
97776
98277
  if (!hasFlowContent) {
97777
98278
  table.type = 'table';
@@ -100011,13 +100512,14 @@ function loadComponents() {
100011
100512
 
100012
100513
 
100013
100514
 
100515
+
100516
+
100014
100517
 
100015
100518
 
100016
100519
 
100017
100520
  const defaultTransformers = [
100018
100521
  [callouts, { isMdxish: true }],
100019
100522
  code_tabs,
100020
- gemoji_,
100021
100523
  transform_embeds,
100022
100524
  ];
100023
100525
  /**
@@ -100027,13 +100529,15 @@ const defaultTransformers = [
100027
100529
  * Runs a series of string-level transformations before micromark/remark parsing:
100028
100530
  * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
100029
100531
  * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
100030
- * 3. Evaluate JSX expressions in attributes (unless safeMode)
100031
- * 4. Replace snake_case component names with parser-safe placeholders
100532
+ * 3. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
100533
+ * 4. Evaluate JSX expressions in attributes (unless safeMode)
100534
+ * 5. Replace snake_case component names with parser-safe placeholders
100032
100535
  */
100033
100536
  function preprocessContent(content, opts) {
100034
100537
  const { safeMode, jsxContext, knownComponents } = opts;
100035
100538
  let result = normalizeTableSeparator(content);
100036
100539
  result = terminateHtmlFlowBlocks(result);
100540
+ result = closeSelfClosingHtmlTags(result);
100037
100541
  result = safeMode ? result : preprocessJSXExpressions(result, jsxContext);
100038
100542
  return processSnakeCaseComponent(result, { knownComponents });
100039
100543
  }
@@ -100063,12 +100567,13 @@ function mdxishAstProcessor(mdContent, opts = {}) {
100063
100567
  };
100064
100568
  const processor = unified()
100065
100569
  .data('micromarkExtensions', safeMode
100066
- ? [jsxTable(), magicBlock(), legacyVariable(), looseHtmlEntity()]
100067
- : [jsxTable(), magicBlock(), mdxExprTextOnly, legacyVariable(), looseHtmlEntity()])
100570
+ ? [jsxTable(), magicBlock(), syntax_gemoji(), legacyVariable(), looseHtmlEntity()]
100571
+ : [jsxTable(), magicBlock(), syntax_gemoji(), mdxExprTextOnly, legacyVariable(), looseHtmlEntity()])
100068
100572
  .data('fromMarkdownExtensions', safeMode
100069
100573
  ? [
100070
100574
  jsxTableFromMarkdown(),
100071
100575
  magicBlockFromMarkdown(),
100576
+ gemojiFromMarkdown(),
100072
100577
  legacyVariableFromMarkdown(),
100073
100578
  emptyTaskListItemFromMarkdown(),
100074
100579
  looseHtmlEntityFromMarkdown(),
@@ -100076,6 +100581,7 @@ function mdxishAstProcessor(mdContent, opts = {}) {
100076
100581
  : [
100077
100582
  jsxTableFromMarkdown(),
100078
100583
  magicBlockFromMarkdown(),
100584
+ gemojiFromMarkdown(),
100079
100585
  mdxExpressionFromMarkdown(),
100080
100586
  legacyVariableFromMarkdown(),
100081
100587
  emptyTaskListItemFromMarkdown(),