@readme/markdown 12.2.0 → 13.0.0

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
@@ -11908,7 +11908,9 @@ const CreateHeading = (depth) => {
11908
11908
  ;// ./components/Image/index.tsx
11909
11909
 
11910
11910
  const Image = (Props) => {
11911
- const { align = '', alt = '', border = false, caption, className = '', height = 'auto', src, title = '', width = 'auto', lazy = true, children, } = Props;
11911
+ const { align = '', alt = '', border: borderProp = false, caption, className = '', height = 'auto', src, title = '', width = 'auto', lazy = true, children, } = Props;
11912
+ // Normalize border: MDXish passes {false} as the string "false", not a boolean
11913
+ const border = borderProp === true || borderProp === 'true';
11912
11914
  const [lightbox, setLightbox] = external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useState(false);
11913
11915
  if (className === 'emoji') {
11914
11916
  return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.createElement("img", { alt: alt, height: height, loading: lazy ? 'lazy' : 'eager', src: src, title: title, width: width });
@@ -14345,7 +14347,7 @@ function htmlExtension(all, extension) {
14345
14347
 
14346
14348
  ;// ./node_modules/micromark-util-character/index.js
14347
14349
  /**
14348
- * @typedef {import('micromark-util-types').Code} Code
14350
+ * @import {Code} from 'micromark-util-types'
14349
14351
  */
14350
14352
 
14351
14353
  /**
@@ -14571,7 +14573,9 @@ const unicodeWhitespace = regexCheck(/\s/);
14571
14573
  * Create a code check from a regex.
14572
14574
  *
14573
14575
  * @param {RegExp} regex
14576
+ * Expression.
14574
14577
  * @returns {(code: Code) => boolean}
14578
+ * Check.
14575
14579
  */
14576
14580
  function regexCheck(regex) {
14577
14581
  return check;
@@ -70517,7 +70521,7 @@ const toAttributes = (object, keys = []) => {
70517
70521
  if (keys.length > 0 && !keys.includes(name))
70518
70522
  return;
70519
70523
  let value;
70520
- if (typeof v === 'undefined' || v === null || v === '') {
70524
+ if (typeof v === 'undefined' || v === null || v === '' || v === false) {
70521
70525
  return;
70522
70526
  }
70523
70527
  else if (typeof v === 'string') {
@@ -91665,6 +91669,10 @@ ${reformatHTML(html)}
91665
91669
  const plain_plain = (node) => node.value;
91666
91670
  /* harmony default export */ const compile_plain = (plain_plain);
91667
91671
 
91672
+ ;// ./processor/compile/variable.ts
91673
+ const variable = (node) => `{user.${node.data?.hProperties?.name || ''}}`;
91674
+ /* harmony default export */ const compile_variable = (variable);
91675
+
91668
91676
  ;// ./processor/compile/index.ts
91669
91677
 
91670
91678
 
@@ -91674,7 +91682,8 @@ const plain_plain = (node) => node.value;
91674
91682
 
91675
91683
 
91676
91684
 
91677
- function compilers() {
91685
+
91686
+ function compilers(mdxish = false) {
91678
91687
  const data = this.data();
91679
91688
  const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
91680
91689
  const handlers = {
@@ -91685,6 +91694,7 @@ function compilers() {
91685
91694
  [NodeTypes.glossary]: compile_compatibility,
91686
91695
  [NodeTypes.htmlBlock]: html_block,
91687
91696
  [NodeTypes.reusableContent]: compile_compatibility,
91697
+ ...(mdxish && { [NodeTypes.variable]: compile_variable }),
91688
91698
  embed: compile_compatibility,
91689
91699
  escape: compile_compatibility,
91690
91700
  figure: compile_compatibility,
@@ -91695,6 +91705,9 @@ function compilers() {
91695
91705
  };
91696
91706
  toMarkdownExtensions.push({ extensions: [{ handlers }] });
91697
91707
  }
91708
+ function mdxishCompilers() {
91709
+ return compilers.call(this, true);
91710
+ }
91698
91711
  /* harmony default export */ const processor_compile = (compilers);
91699
91712
 
91700
91713
  ;// ./processor/transform/escape-pipes-in-tables.ts
@@ -93405,13 +93418,17 @@ const htmlBlockHandler = (_state, node) => {
93405
93418
  const embedHandler = (state, node) => {
93406
93419
  // Assert to get the minimum properties we need
93407
93420
  const { data } = node;
93421
+ // Magic block embeds (hName === 'embed-block') render as Embed component
93422
+ // which doesn't use children - it renders based on props only
93423
+ const isMagicBlockEmbed = data?.hName === NodeTypes.embedBlock;
93408
93424
  return {
93409
93425
  type: 'element',
93410
93426
  // To differentiate between regular embeds and magic block embeds,
93411
93427
  // magic block embeds have a certain hName
93412
- tagName: data?.hName === NodeTypes.embedBlock ? 'Embed' : 'embed',
93428
+ tagName: isMagicBlockEmbed ? 'Embed' : 'embed',
93413
93429
  properties: data?.hProperties,
93414
- children: state.all(node),
93430
+ // Don't include children for magic block embeds - Embed component renders based on props
93431
+ children: isMagicBlockEmbed ? [] : state.all(node),
93415
93432
  };
93416
93433
  };
93417
93434
  const mdxComponentHandlers = {
@@ -93424,7 +93441,102 @@ const mdxComponentHandlers = {
93424
93441
  [NodeTypes.htmlBlock]: htmlBlockHandler,
93425
93442
  };
93426
93443
 
93444
+ ;// ./lib/utils/mdxish/protect-code-blocks.ts
93445
+ /**
93446
+ * Replaces code blocks and inline code with placeholders to protect them from preprocessing.
93447
+ *
93448
+ * @param content - The markdown content to process
93449
+ * @returns Object containing protected content and arrays of original code blocks
93450
+ * @example
93451
+ * ```typescript
93452
+ * const input = 'Text with `inline code` and ```fenced block```';
93453
+ * protectCodeBlocks(input)
93454
+ * // Returns: {
93455
+ * // protectedCode: {
93456
+ * // codeBlocks: ['```fenced block```'],
93457
+ * // inlineCode: ['`inline code`']
93458
+ * // },
93459
+ * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
93460
+ * // }
93461
+ * ```
93462
+ */
93463
+ function protectCodeBlocks(content) {
93464
+ const codeBlocks = [];
93465
+ const inlineCode = [];
93466
+ let protectedContent = '';
93467
+ let remaining = content;
93468
+ let codeBlockStart = remaining.indexOf('```');
93469
+ while (codeBlockStart !== -1) {
93470
+ protectedContent += remaining.slice(0, codeBlockStart);
93471
+ remaining = remaining.slice(codeBlockStart);
93472
+ const codeBlockEnd = remaining.indexOf('```', 3);
93473
+ if (codeBlockEnd === -1) {
93474
+ break;
93475
+ }
93476
+ const match = remaining.slice(0, codeBlockEnd + 3);
93477
+ const index = codeBlocks.length;
93478
+ codeBlocks.push(match);
93479
+ protectedContent += `___CODE_BLOCK_${index}___`;
93480
+ remaining = remaining.slice(codeBlockEnd + 3);
93481
+ codeBlockStart = remaining.indexOf('```');
93482
+ }
93483
+ protectedContent += remaining;
93484
+ protectedContent = protectedContent.replace(/`([^`\n]+)`/g, match => {
93485
+ const index = inlineCode.length;
93486
+ inlineCode.push(match);
93487
+ return `___INLINE_CODE_${index}___`;
93488
+ });
93489
+ return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
93490
+ }
93491
+ /**
93492
+ * Restores inline code by replacing placeholders with original content.
93493
+ *
93494
+ * @param content - Content with inline code placeholders
93495
+ * @param protectedCode - The protected code arrays
93496
+ * @returns Content with inline code restored
93497
+ */
93498
+ function restoreInlineCode(content, protectedCode) {
93499
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
93500
+ return protectedCode.inlineCode[parseInt(idx, 10)];
93501
+ });
93502
+ }
93503
+ /**
93504
+ * Restores fenced code blocks by replacing placeholders with original content.
93505
+ *
93506
+ * @param content - Content with code block placeholders
93507
+ * @param protectedCode - The protected code arrays
93508
+ * @returns Content with code blocks restored
93509
+ */
93510
+ function restoreFencedCodeBlocks(content, protectedCode) {
93511
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
93512
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
93513
+ });
93514
+ }
93515
+ /**
93516
+ * Restores all code blocks and inline code by replacing placeholders with original content.
93517
+ *
93518
+ * @param content - Content with code placeholders
93519
+ * @param protectedCode - The protected code arrays
93520
+ * @returns Content with all code blocks and inline code restored
93521
+ * @example
93522
+ * ```typescript
93523
+ * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
93524
+ * const protectedCode = {
93525
+ * codeBlocks: ['```js\ncode\n```'],
93526
+ * inlineCode: ['`inline`']
93527
+ * };
93528
+ * restoreCodeBlocks(content, protectedCode)
93529
+ * // Returns: 'Text with `inline` and ```js\ncode\n```'
93530
+ * ```
93531
+ */
93532
+ function restoreCodeBlocks(content, protectedCode) {
93533
+ let restored = restoreFencedCodeBlocks(content, protectedCode);
93534
+ restored = restoreInlineCode(restored, protectedCode);
93535
+ return restored;
93536
+ }
93537
+
93427
93538
  ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
93539
+
93428
93540
  // Base64 encode (Node.js + browser compatible)
93429
93541
  function base64Encode(str) {
93430
93542
  if (typeof Buffer !== 'undefined') {
@@ -93491,52 +93603,6 @@ function protectHTMLBlockContent(content) {
93491
93603
  return `${openTag}${HTML_BLOCK_CONTENT_START}${encoded}${HTML_BLOCK_CONTENT_END}${closeTag}`;
93492
93604
  });
93493
93605
  }
93494
- /**
93495
- * Replaces code blocks and inline code with placeholders to protect them from JSX processing.
93496
- *
93497
- * @param content
93498
- * @returns Object containing protected content and arrays of original code blocks
93499
- * @example
93500
- * ```typescript
93501
- * const input = 'Text with `inline code` and ```fenced block```';
93502
- * protectCodeBlocks(input)
93503
- * // Returns: {
93504
- * // protectedCode: {
93505
- * // codeBlocks: ['```fenced block```'],
93506
- * // inlineCode: ['`inline code`']
93507
- * // },
93508
- * // protectedContent: 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___'
93509
- * // }
93510
- * ```
93511
- */
93512
- function protectCodeBlocks(content) {
93513
- const codeBlocks = [];
93514
- const inlineCode = [];
93515
- let protectedContent = '';
93516
- let remaining = content;
93517
- let codeBlockStart = remaining.indexOf('```');
93518
- while (codeBlockStart !== -1) {
93519
- protectedContent += remaining.slice(0, codeBlockStart);
93520
- remaining = remaining.slice(codeBlockStart);
93521
- const codeBlockEnd = remaining.indexOf('```', 3);
93522
- if (codeBlockEnd === -1) {
93523
- break;
93524
- }
93525
- const match = remaining.slice(0, codeBlockEnd + 3);
93526
- const index = codeBlocks.length;
93527
- codeBlocks.push(match);
93528
- protectedContent += `___CODE_BLOCK_${index}___`;
93529
- remaining = remaining.slice(codeBlockEnd + 3);
93530
- codeBlockStart = remaining.indexOf('```');
93531
- }
93532
- protectedContent += remaining;
93533
- protectedContent = protectedContent.replace(/`[^`]+`/g, match => {
93534
- const index = inlineCode.length;
93535
- inlineCode.push(match);
93536
- return `___INLINE_CODE_${index}___`;
93537
- });
93538
- return { protectedCode: { codeBlocks, inlineCode }, protectedContent };
93539
- }
93540
93606
  /**
93541
93607
  * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
93542
93608
  *
@@ -93579,16 +93645,6 @@ function extractBalancedBraces(content, start) {
93579
93645
  return null;
93580
93646
  return { content: content.slice(start, pos - 1), end: pos };
93581
93647
  }
93582
- function restoreInlineCode(content, protectedCode) {
93583
- return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
93584
- return protectedCode.inlineCode[parseInt(idx, 10)];
93585
- });
93586
- }
93587
- function restoreCodeBlocks(content, protectedCode) {
93588
- return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
93589
- return protectedCode.codeBlocks[parseInt(idx, 10)];
93590
- });
93591
- }
93592
93648
  /**
93593
93649
  * Escapes unbalanced braces in content to prevent MDX expression parsing errors.
93594
93650
  * Handles: already-escaped braces, string literals inside expressions, nested balanced braces.
@@ -93719,28 +93775,6 @@ function evaluateAttributeExpressions(content, context, protectedCode) {
93719
93775
  result += content.slice(lastEnd);
93720
93776
  return result;
93721
93777
  }
93722
- /**
93723
- * Restores code blocks and inline code by replacing placeholders with original content.
93724
- *
93725
- * @param content
93726
- * @param protectedCode
93727
- * @returns Content with all code blocks and inline code restored
93728
- * @example
93729
- * ```typescript
93730
- * const content = 'Text with ___INLINE_CODE_0___ and ___CODE_BLOCK_0___';
93731
- * const protectedCode = {
93732
- * codeBlocks: ['```js\ncode\n```'],
93733
- * inlineCode: ['`inline`']
93734
- * };
93735
- * restoreCodeBlocks(content, protectedCode)
93736
- * // Returns: 'Text with `inline` and ```js\ncode\n```'
93737
- * ```
93738
- */
93739
- function restoreProtectedCodes(content, protectedCode) {
93740
- let restored = restoreCodeBlocks(content, protectedCode);
93741
- restored = restoreInlineCode(restored, protectedCode);
93742
- return restored;
93743
- }
93744
93778
  /**
93745
93779
  * Preprocesses JSX-like expressions in markdown before parsing.
93746
93780
  * Inline expressions are handled separately; attribute expressions are processed here.
@@ -93763,7 +93797,7 @@ function preprocessJSXExpressions(content, context = {}) {
93763
93797
  // Step 4: Escape unbalanced braces to prevent MDX expression parsing errors
93764
93798
  processed = escapeUnbalancedBraces(processed);
93765
93799
  // Step 5: Restore protected code blocks
93766
- processed = restoreProtectedCodes(processed, protectedCode);
93800
+ processed = restoreCodeBlocks(processed, protectedCode);
93767
93801
  return processed;
93768
93802
  }
93769
93803
 
@@ -93818,6 +93852,459 @@ const evaluateExpressions = ({ context = {} } = {}) => tree => {
93818
93852
  };
93819
93853
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
93820
93854
 
93855
+ ;// ./processor/transform/mdxish/magic-blocks/placeholder.ts
93856
+ const EMPTY_IMAGE_PLACEHOLDER = {
93857
+ type: 'image',
93858
+ url: '',
93859
+ alt: '',
93860
+ title: '',
93861
+ data: { hProperties: {} },
93862
+ };
93863
+ const EMPTY_EMBED_PLACEHOLDER = {
93864
+ type: 'embed',
93865
+ children: [{ type: 'link', url: '', title: '', children: [{ type: 'text', value: '' }] }],
93866
+ data: { hName: 'embed-block', hProperties: { url: '', href: '', title: '' } },
93867
+ };
93868
+ const EMPTY_RECIPE_PLACEHOLDER = {
93869
+ type: 'mdxJsxFlowElement',
93870
+ name: 'Recipe',
93871
+ attributes: [],
93872
+ children: [],
93873
+ };
93874
+ const EMPTY_CALLOUT_PLACEHOLDER = {
93875
+ type: 'mdxJsxFlowElement',
93876
+ name: 'Callout',
93877
+ attributes: [
93878
+ { type: 'mdxJsxAttribute', name: 'icon', value: '📘' },
93879
+ { type: 'mdxJsxAttribute', name: 'theme', value: 'info' },
93880
+ { type: 'mdxJsxAttribute', name: 'type', value: 'info' },
93881
+ { type: 'mdxJsxAttribute', name: 'empty', value: 'true' },
93882
+ ],
93883
+ children: [{ type: 'heading', depth: 3, children: [{ type: 'text', value: '' }] }],
93884
+ };
93885
+ const EMPTY_TABLE_PLACEHOLDER = {
93886
+ type: 'table',
93887
+ align: ['left', 'left'],
93888
+ children: [
93889
+ { type: 'tableRow', children: [{ type: 'tableCell', children: [{ type: 'text', value: '' }] }] },
93890
+ { type: 'tableRow', children: [{ type: 'tableCell', children: [{ type: 'text', value: '' }] }] },
93891
+ ],
93892
+ };
93893
+ const EMPTY_CODE_PLACEHOLDER = {
93894
+ type: 'code',
93895
+ value: '',
93896
+ lang: null,
93897
+ meta: null,
93898
+ };
93899
+
93900
+ ;// ./processor/transform/mdxish/magic-blocks/magic-block-transformer.ts
93901
+
93902
+
93903
+
93904
+
93905
+
93906
+
93907
+ /**
93908
+ * Wraps a node in a "pinned" container if sidebar: true is set.
93909
+ */
93910
+ const wrapPinnedBlocks = (node, data) => {
93911
+ if (!data.sidebar)
93912
+ return node;
93913
+ return {
93914
+ children: [node],
93915
+ data: { hName: 'rdme-pin', hProperties: { className: 'pin' } },
93916
+ type: 'rdme-pin',
93917
+ };
93918
+ };
93919
+ /**
93920
+ * Named size presets for image widths.
93921
+ */
93922
+ const imgSizeValues = {
93923
+ full: '100%',
93924
+ original: 'auto',
93925
+ };
93926
+ /**
93927
+ * Proxy that resolves image sizing values.
93928
+ */
93929
+ const imgWidthBySize = new Proxy(imgSizeValues, {
93930
+ get: (widths, size) => (size?.match(/^\d+$/) ? `${size}%` : size in widths ? widths[size] : size),
93931
+ });
93932
+ const textToInline = (text) => [{ type: 'text', value: text }];
93933
+ const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
93934
+ const contentParser = unified().use(remarkParse).use(remarkGfm);
93935
+ const parseTableCell = (text) => {
93936
+ if (!text.trim())
93937
+ return [{ type: 'text', value: '' }];
93938
+ const tree = contentParser.runSync(contentParser.parse(text));
93939
+ if (tree.children.length > 1) {
93940
+ return tree.children;
93941
+ }
93942
+ return tree.children.flatMap(n => n.type === 'paragraph' && 'children' in n ? n.children : [n]);
93943
+ };
93944
+ const parseBlock = (text) => {
93945
+ if (!text.trim())
93946
+ return [{ type: 'paragraph', children: [{ type: 'text', value: '' }] }];
93947
+ const tree = contentParser.runSync(contentParser.parse(text));
93948
+ return tree.children;
93949
+ };
93950
+ /**
93951
+ * Transform a magicBlock node into final MDAST nodes.
93952
+ */
93953
+ function transformMagicBlock(blockType, data, rawValue, options = {}) {
93954
+ const { compatibilityMode = false, safeMode = false } = options;
93955
+ // Handle empty data by returning placeholder nodes for known block types
93956
+ // This allows the editor to show appropriate placeholder UI instead of nothing
93957
+ if (Object.keys(data).length < 1) {
93958
+ switch (blockType) {
93959
+ case 'image':
93960
+ return [EMPTY_IMAGE_PLACEHOLDER];
93961
+ case 'embed':
93962
+ return [EMPTY_EMBED_PLACEHOLDER];
93963
+ case 'code':
93964
+ return [EMPTY_CODE_PLACEHOLDER];
93965
+ case 'callout':
93966
+ return [EMPTY_CALLOUT_PLACEHOLDER];
93967
+ case 'parameters':
93968
+ case 'table':
93969
+ return [EMPTY_TABLE_PLACEHOLDER];
93970
+ case 'recipe':
93971
+ case 'tutorial-tile':
93972
+ return [EMPTY_RECIPE_PLACEHOLDER];
93973
+ default:
93974
+ return [{ type: 'paragraph', children: [{ type: 'text', value: rawValue }] }];
93975
+ }
93976
+ }
93977
+ switch (blockType) {
93978
+ case 'code': {
93979
+ const codeJson = data;
93980
+ if (!codeJson.codes || !Array.isArray(codeJson.codes)) {
93981
+ return [wrapPinnedBlocks(EMPTY_CODE_PLACEHOLDER, data)];
93982
+ }
93983
+ const children = codeJson.codes.map(obj => ({
93984
+ className: 'tab-panel',
93985
+ data: { hName: 'code', hProperties: { lang: obj.language, meta: obj.name || null } },
93986
+ lang: obj.language,
93987
+ meta: obj.name || null,
93988
+ type: 'code',
93989
+ value: obj.code.trim(),
93990
+ }));
93991
+ // Single code block without a tab name (meta or language) renders as a plain code block
93992
+ // Otherwise, we want to render it as a code tabs block
93993
+ if (children.length === 1) {
93994
+ if (!children[0].value)
93995
+ return [];
93996
+ if (!(children[0].meta || children[0].lang))
93997
+ return [wrapPinnedBlocks(children[0], data)];
93998
+ }
93999
+ // Multiple code blocks or a single code block with a tab name (meta or language) renders as a code tabs block
94000
+ return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'CodeTabs' }, type: 'code-tabs' }, data)];
94001
+ }
94002
+ case 'api-header': {
94003
+ const headerJson = data;
94004
+ const depth = headerJson.level || (compatibilityMode ? 1 : 2);
94005
+ return [
94006
+ wrapPinnedBlocks({
94007
+ children: 'title' in headerJson ? textToInline(headerJson.title || '') : [],
94008
+ depth,
94009
+ type: 'heading',
94010
+ }, data),
94011
+ ];
94012
+ }
94013
+ case 'image': {
94014
+ const imageJson = data;
94015
+ if (!imageJson.images || !Array.isArray(imageJson.images)) {
94016
+ return [wrapPinnedBlocks(EMPTY_IMAGE_PLACEHOLDER, data)];
94017
+ }
94018
+ const imgData = imageJson.images.find(i => i.image);
94019
+ if (!imgData?.image) {
94020
+ return [wrapPinnedBlocks(EMPTY_IMAGE_PLACEHOLDER, data)];
94021
+ }
94022
+ const [url, title, alt] = imgData.image;
94023
+ const block = {
94024
+ alt: alt || imgData.caption || '',
94025
+ data: {
94026
+ hProperties: {
94027
+ ...(imgData.align && { align: imgData.align }),
94028
+ ...(imgData.border && { border: imgData.border.toString() }),
94029
+ ...(imgData.sizing && { width: imgWidthBySize[imgData.sizing] }),
94030
+ },
94031
+ },
94032
+ title,
94033
+ type: 'image',
94034
+ url,
94035
+ };
94036
+ const img = imgData.caption
94037
+ ? {
94038
+ children: [
94039
+ block,
94040
+ { children: parseBlock(imgData.caption), data: { hName: 'figcaption' }, type: 'figcaption' },
94041
+ ],
94042
+ data: { hName: 'figure' },
94043
+ type: 'figure',
94044
+ url,
94045
+ }
94046
+ : block;
94047
+ return [wrapPinnedBlocks(img, data)];
94048
+ }
94049
+ case 'callout': {
94050
+ const calloutJson = data;
94051
+ const types = {
94052
+ danger: ['❗️', 'error'],
94053
+ info: ['📘', 'info'],
94054
+ success: ['👍', 'okay'],
94055
+ warning: ['🚧', 'warn'],
94056
+ };
94057
+ const resolvedType = typeof calloutJson.type === 'string' && calloutJson.type in types
94058
+ ? types[calloutJson.type]
94059
+ : [calloutJson.icon || '👍', typeof calloutJson.type === 'string' ? calloutJson.type : 'default'];
94060
+ const [icon, theme] = Array.isArray(resolvedType) ? resolvedType : ['👍', 'default'];
94061
+ if (!(calloutJson.title || calloutJson.body))
94062
+ return [];
94063
+ const hasTitle = !!calloutJson.title?.trim();
94064
+ const hasBody = !!calloutJson.body?.trim();
94065
+ const empty = !hasTitle;
94066
+ const children = [];
94067
+ if (hasTitle) {
94068
+ const titleBlocks = parseBlock(calloutJson.title || '');
94069
+ if (titleBlocks.length > 0 && titleBlocks[0].type === 'paragraph') {
94070
+ const firstTitle = titleBlocks[0];
94071
+ const heading = {
94072
+ type: 'heading',
94073
+ depth: 3,
94074
+ children: (firstTitle.children || []),
94075
+ };
94076
+ children.push(heading);
94077
+ children.push(...titleBlocks.slice(1));
94078
+ }
94079
+ else {
94080
+ children.push(...titleBlocks);
94081
+ }
94082
+ }
94083
+ else {
94084
+ // Add empty heading placeholder so body goes to children.slice(1)
94085
+ // The Callout component expects children[0] to be the heading
94086
+ children.push({
94087
+ type: 'heading',
94088
+ depth: 3,
94089
+ children: [{ type: 'text', value: '' }],
94090
+ });
94091
+ }
94092
+ if (hasBody) {
94093
+ const bodyBlocks = parseBlock(calloutJson.body || '');
94094
+ children.push(...bodyBlocks);
94095
+ }
94096
+ const calloutElement = {
94097
+ type: 'mdxJsxFlowElement',
94098
+ name: 'Callout',
94099
+ attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default', empty }, [
94100
+ 'icon',
94101
+ 'theme',
94102
+ 'type',
94103
+ 'empty',
94104
+ ]),
94105
+ children: children,
94106
+ };
94107
+ return [wrapPinnedBlocks(calloutElement, data)];
94108
+ }
94109
+ case 'parameters': {
94110
+ const paramsJson = data;
94111
+ const { cols, data: tableData, rows } = paramsJson;
94112
+ if (!tableData || !Object.keys(tableData).length)
94113
+ return [];
94114
+ if (typeof cols !== 'number' || typeof rows !== 'number' || cols < 1 || rows < 0)
94115
+ return [];
94116
+ const sparseData = Object.entries(tableData).reduce((mapped, [key, v]) => {
94117
+ const [row, col] = key.split('-');
94118
+ const rowIndex = row === 'h' ? 0 : parseInt(row, 10) + 1;
94119
+ const colIndex = parseInt(col, 10);
94120
+ if (!mapped[rowIndex])
94121
+ mapped[rowIndex] = [];
94122
+ mapped[rowIndex][colIndex] = v;
94123
+ return mapped;
94124
+ }, []);
94125
+ const tokenizeCell = compatibilityMode ? textToBlock : parseTableCell;
94126
+ const tableChildren = Array.from({ length: rows + 1 }, (_, y) => ({
94127
+ children: Array.from({ length: cols }, (__, x) => ({
94128
+ children: sparseData[y]?.[x] ? tokenizeCell(sparseData[y][x]) : [{ type: 'text', value: '' }],
94129
+ type: y === 0 ? 'tableHead' : 'tableCell',
94130
+ })),
94131
+ type: 'tableRow',
94132
+ }));
94133
+ return [
94134
+ wrapPinnedBlocks({ align: paramsJson.align ?? new Array(cols).fill('left'), children: tableChildren, type: 'table' }, data),
94135
+ ];
94136
+ }
94137
+ case 'embed': {
94138
+ const embedJson = data;
94139
+ if (!embedJson.url) {
94140
+ return [wrapPinnedBlocks(EMPTY_EMBED_PLACEHOLDER, data)];
94141
+ }
94142
+ const { html, title, url } = embedJson;
94143
+ try {
94144
+ embedJson.provider = new URL(url).hostname
94145
+ .split(/(?:www)?\./)
94146
+ .filter(i => i)
94147
+ .join('.');
94148
+ }
94149
+ catch {
94150
+ embedJson.provider = url;
94151
+ }
94152
+ return [
94153
+ wrapPinnedBlocks({
94154
+ children: [
94155
+ { children: [{ type: 'text', value: title || '' }], title: embedJson.provider, type: 'link', url },
94156
+ ],
94157
+ data: { hName: 'embed-block', hProperties: { ...embedJson, href: url, html, title, url } },
94158
+ type: 'embed',
94159
+ }, data),
94160
+ ];
94161
+ }
94162
+ case 'html': {
94163
+ const htmlJson = data;
94164
+ if (typeof htmlJson.html !== 'string')
94165
+ return [];
94166
+ return [
94167
+ wrapPinnedBlocks({
94168
+ data: {
94169
+ hName: 'html-block',
94170
+ hProperties: { html: htmlJson.html, runScripts: compatibilityMode, safeMode },
94171
+ },
94172
+ type: 'html-block',
94173
+ }, data),
94174
+ ];
94175
+ }
94176
+ case 'recipe':
94177
+ case 'tutorial-tile': {
94178
+ const recipeJson = data;
94179
+ if (!recipeJson.slug || !recipeJson.title)
94180
+ return [];
94181
+ const recipeNode = {
94182
+ type: 'mdxJsxFlowElement',
94183
+ name: 'Recipe',
94184
+ attributes: toAttributes(recipeJson, ['slug', 'title']),
94185
+ children: [],
94186
+ position: undefined,
94187
+ };
94188
+ return [recipeNode];
94189
+ }
94190
+ default: {
94191
+ const text = data.text || data.html || '';
94192
+ return [
94193
+ wrapPinnedBlocks({ children: textToBlock(text), data: { hName: blockType || 'div', hProperties: data, ...data }, type: 'div' }, data),
94194
+ ];
94195
+ }
94196
+ }
94197
+ }
94198
+ /**
94199
+ * Check if a child node is a flow element that needs unwrapping.
94200
+ */
94201
+ const blockTypes = [
94202
+ 'heading',
94203
+ 'code',
94204
+ 'code-tabs',
94205
+ 'paragraph',
94206
+ 'blockquote',
94207
+ 'list',
94208
+ 'table',
94209
+ 'thematicBreak',
94210
+ 'html',
94211
+ 'yaml',
94212
+ 'toml',
94213
+ 'rdme-pin',
94214
+ 'rdme-callout',
94215
+ 'html-block',
94216
+ 'embed',
94217
+ 'figure',
94218
+ 'mdxJsxFlowElement',
94219
+ ];
94220
+ /**
94221
+ * Check if a node is a block-level node (cannot be inside a paragraph)
94222
+ */
94223
+ const isBlockNode = (node) => blockTypes.includes(node.type);
94224
+ /**
94225
+ * Unified plugin that transforms magicBlock nodes into final MDAST nodes.
94226
+ */
94227
+ const magicBlockTransformer = (options = {}) => tree => {
94228
+ const replacements = [];
94229
+ visit(tree, 'magicBlock', (node, index, parent) => {
94230
+ if (!parent || index === undefined)
94231
+ return undefined;
94232
+ const children = transformMagicBlock(node.blockType, node.data, node.value, options);
94233
+ if (!children.length) {
94234
+ // Remove the node if transformation returns nothing
94235
+ parent.children.splice(index, 1);
94236
+ return [SKIP, index];
94237
+ }
94238
+ // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
94239
+ if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
94240
+ const blockNodes = [];
94241
+ const inlineNodes = [];
94242
+ // Separate block and inline nodes
94243
+ children.forEach(child => {
94244
+ if (isBlockNode(child)) {
94245
+ blockNodes.push(child);
94246
+ }
94247
+ else {
94248
+ inlineNodes.push(child);
94249
+ }
94250
+ });
94251
+ const before = parent.children.slice(0, index);
94252
+ const after = parent.children.slice(index + 1);
94253
+ replacements.push({
94254
+ parent,
94255
+ blockNodes,
94256
+ inlineNodes,
94257
+ before,
94258
+ after,
94259
+ });
94260
+ }
94261
+ else {
94262
+ // Normal case: just replace the inlineCode with the children
94263
+ parent.children.splice(index, 1, ...children);
94264
+ }
94265
+ return undefined;
94266
+ });
94267
+ // Second pass: apply replacements that require lifting block nodes out of paragraphs
94268
+ // Process in reverse order to maintain correct indices
94269
+ for (let i = replacements.length - 1; i >= 0; i -= 1) {
94270
+ const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
94271
+ // Find the paragraph's position in the root
94272
+ const rootChildren = tree.children;
94273
+ const paraIndex = rootChildren.findIndex(child => child === parent);
94274
+ if (paraIndex === -1) {
94275
+ // Paragraph not found in root - fall back to normal replacement
94276
+ // This shouldn't happen normally, but handle it gracefully
94277
+ // Reconstruct the original index from before.length
94278
+ const originalIndex = before.length;
94279
+ parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
94280
+ // eslint-disable-next-line no-continue
94281
+ continue;
94282
+ }
94283
+ // Update or remove the paragraph
94284
+ if (inlineNodes.length > 0) {
94285
+ // Keep paragraph with inline nodes
94286
+ parent.children = [...before, ...inlineNodes, ...after];
94287
+ // Insert block nodes after the paragraph
94288
+ if (blockNodes.length > 0) {
94289
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94290
+ }
94291
+ }
94292
+ else if (before.length === 0 && after.length === 0) {
94293
+ // Remove empty paragraph and replace with block nodes
94294
+ rootChildren.splice(paraIndex, 1, ...blockNodes);
94295
+ }
94296
+ else {
94297
+ // Keep paragraph with remaining content
94298
+ parent.children = [...before, ...after];
94299
+ // Insert block nodes after the paragraph
94300
+ if (blockNodes.length > 0) {
94301
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94302
+ }
94303
+ }
94304
+ }
94305
+ };
94306
+ /* harmony default export */ const magic_block_transformer = (magicBlockTransformer);
94307
+
93821
94308
  ;// ./processor/transform/mdxish/mdxish-html-blocks.ts
93822
94309
 
93823
94310
 
@@ -94153,444 +94640,220 @@ const mdxishHtmlBlocks = () => tree => {
94153
94640
  };
94154
94641
  /* harmony default export */ const mdxish_html_blocks = (mdxishHtmlBlocks);
94155
94642
 
94156
- ;// ./processor/transform/mdxish/mdxish-magic-blocks.ts
94643
+ ;// ./processor/transform/mdxish/mdxish-jsx-to-mdast.ts
94157
94644
 
94158
94645
 
94159
94646
 
94160
-
94161
-
94162
- /**
94163
- * Matches legacy magic block syntax: [block:TYPE]...JSON...[/block]
94164
- * Group 1: block type (e.g., "image", "code", "callout")
94165
- * Group 2: JSON content between the tags
94166
- * Taken from the v6 branch
94167
- */
94168
- const RGXP = /^\s*\[block:([^\]]*)\]([^]+?)\[\/block\]/;
94169
- /**
94170
- * Wraps a node in a "pinned" container if sidebar: true is set in the JSON.
94171
- * Pinned blocks are displayed in a sidebar/floating position in the UI.
94172
- */
94173
- const wrapPinnedBlocks = (node, json) => {
94174
- if (!json.sidebar)
94175
- return node;
94647
+ const transformImage = (jsx) => {
94648
+ const attrs = getAttrs(jsx);
94649
+ const { align, alt = '', border, caption, className, height, lazy, src = '', title = '', width } = attrs;
94650
+ const hProperties = {
94651
+ alt,
94652
+ src,
94653
+ title,
94654
+ ...(align && { align }),
94655
+ ...(border !== undefined && { border: String(border) }),
94656
+ ...(caption && { caption }),
94657
+ ...(className && { className }),
94658
+ ...(height !== undefined && { height: String(height) }),
94659
+ ...(lazy !== undefined && { lazy }),
94660
+ ...(width !== undefined && { width: String(width) }),
94661
+ };
94176
94662
  return {
94177
- children: [node],
94178
- data: { hName: 'rdme-pin', hProperties: { className: 'pin' } },
94179
- type: 'rdme-pin',
94663
+ type: NodeTypes.imageBlock,
94664
+ align,
94665
+ alt,
94666
+ border: border !== undefined ? String(border) : undefined,
94667
+ caption,
94668
+ className,
94669
+ height: height !== undefined ? String(height) : undefined,
94670
+ lazy,
94671
+ src,
94672
+ title,
94673
+ width: width !== undefined ? String(width) : undefined,
94674
+ data: {
94675
+ hName: 'img',
94676
+ hProperties,
94677
+ },
94678
+ position: jsx.position,
94180
94679
  };
94181
94680
  };
94182
- /**
94183
- * Named size presets for image widths.
94184
- */
94185
- const imgSizeValues = {
94186
- full: '100%',
94187
- original: 'auto',
94188
- };
94189
- /**
94190
- * Proxy that resolves image sizing values:
94191
- * - "full" → "100%", "original" → "auto" (from imgSizeValues)
94192
- * - Pure numbers like "50" → "50%" (percentage)
94193
- * - Anything else passes through as-is (e.g., "200px")
94194
- */
94195
- const imgWidthBySize = new Proxy(imgSizeValues, {
94196
- get: (widths, size) => (size?.match(/^\d+$/) ? `${size}%` : size in widths ? widths[size] : size),
94197
- });
94198
- // Simple text to inline nodes (just returns text node - no markdown parsing)
94199
- const textToInline = (text) => [{ type: 'text', value: text }];
94200
- // Simple text to block nodes (wraps in paragraph)
94201
- const textToBlock = (text) => [{ children: textToInline(text), type: 'paragraph' }];
94202
- /** Parses markdown and html to markdown nodes */
94203
- const contentParser = unified().use(remarkParse).use(remarkGfm);
94204
- // Table cells may contain html or markdown content, so we need to parse it accordingly instead of keeping it as raw text
94205
- const parseTableCell = (text) => {
94206
- if (!text.trim())
94207
- return [{ type: 'text', value: '' }];
94208
- const tree = contentParser.runSync(contentParser.parse(text));
94209
- // If there are multiple block-level nodes, keep them as-is to preserve the document structure and spacing
94210
- if (tree.children.length > 1) {
94211
- return tree.children;
94212
- }
94213
- return tree.children.flatMap(n =>
94214
- // This unwraps the extra p node that might appear & wrapping the content
94215
- n.type === 'paragraph' && 'children' in n ? n.children : [n]);
94216
- };
94217
- // Parse markdown/HTML into block-level nodes (preserves paragraphs, headings, lists, etc.)
94218
- const parseBlock = (text) => {
94219
- if (!text.trim())
94220
- return [{ type: 'paragraph', children: [{ type: 'text', value: '' }] }];
94221
- const tree = contentParser.runSync(contentParser.parse(text));
94222
- return tree.children;
94681
+ const transformCallout = (jsx) => {
94682
+ const attrs = getAttrs(jsx);
94683
+ const { empty = false, icon = '', theme = '' } = attrs;
94684
+ return {
94685
+ type: NodeTypes.callout,
94686
+ children: jsx.children,
94687
+ data: {
94688
+ hName: 'Callout',
94689
+ hProperties: {
94690
+ empty,
94691
+ icon,
94692
+ theme,
94693
+ },
94694
+ },
94695
+ position: jsx.position,
94696
+ };
94223
94697
  };
94224
- /**
94225
- * Parse a magic block string and return MDAST nodes.
94226
- *
94227
- * @param raw - The raw magic block string including [block:TYPE] and [/block] tags
94228
- * @param options - Parsing options for compatibility and error handling
94229
- * @returns Array of MDAST nodes representing the parsed block
94230
- */
94231
- function parseMagicBlock(raw, options = {}) {
94232
- const { alwaysThrow = false, compatibilityMode = false, safeMode = false } = options;
94233
- const matchResult = RGXP.exec(raw);
94234
- if (!matchResult)
94235
- return [];
94236
- const [, rawType, jsonStr] = matchResult;
94237
- const type = rawType?.trim();
94238
- if (!type)
94239
- return [];
94240
- let json;
94241
- try {
94242
- json = JSON.parse(jsonStr);
94243
- }
94244
- catch (err) {
94245
- // eslint-disable-next-line no-console
94246
- console.error('Invalid Magic Block JSON:', err);
94247
- if (alwaysThrow)
94248
- throw new Error('Invalid Magic Block JSON');
94249
- return [];
94250
- }
94251
- if (Object.keys(json).length < 1)
94252
- return [];
94253
- // Each case handles a different magic block type and returns appropriate MDAST nodes
94254
- switch (type) {
94255
- // Code blocks: single code block or tabbed code blocks (multiple languages)
94256
- case 'code': {
94257
- const codeJson = json;
94258
- const children = codeJson.codes.map(obj => ({
94259
- className: 'tab-panel',
94260
- data: { hName: 'code', hProperties: { lang: obj.language, meta: obj.name || null } },
94261
- lang: obj.language,
94262
- meta: obj.name || null, // Tab name shown in the UI
94263
- type: 'code',
94264
- value: obj.code.trim(),
94265
- }));
94266
- // Single code block without a tab name (meta or language) renders as a plain code block
94267
- // Otherwise, we want to render it as a code tabs block
94268
- if (children.length === 1) {
94269
- if (!children[0].value)
94270
- return [];
94271
- if (!(children[0].meta || children[0].lang))
94272
- return [wrapPinnedBlocks(children[0], json)];
94273
- }
94274
- // Multiple code blocks or a single code block with a tab name (meta or language) renders as a code tabs block
94275
- return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'CodeTabs' }, type: 'code-tabs' }, json)];
94276
- }
94277
- // API header: renders as a heading element (h1-h6)
94278
- case 'api-header': {
94279
- const headerJson = json;
94280
- // In compatibility mode, default to h1; otherwise h2
94281
- const depth = headerJson.level || (compatibilityMode ? 1 : 2);
94282
- return [
94283
- wrapPinnedBlocks({
94284
- children: 'title' in headerJson ? textToInline(headerJson.title || '') : [],
94285
- depth,
94286
- type: 'heading',
94287
- }, json),
94288
- ];
94289
- }
94290
- // Image block: renders as <img> or <figure> with caption
94291
- case 'image': {
94292
- const imageJson = json;
94293
- const imgData = imageJson.images.find(i => i.image);
94294
- if (!imgData?.image)
94295
- return [];
94296
- // Image array format: [url, title?, alt?]
94297
- const [url, title, alt] = imgData.image;
94298
- const block = {
94299
- alt: alt || imgData.caption || '',
94300
- data: {
94301
- hProperties: {
94302
- ...(imgData.align && { align: imgData.align }),
94303
- ...(imgData.border && { border: imgData.border.toString() }),
94304
- ...(imgData.sizing && { width: imgWidthBySize[imgData.sizing] }),
94305
- },
94306
- },
94698
+ const transformEmbed = (jsx) => {
94699
+ const attrs = getAttrs(jsx);
94700
+ const { favicon, html, iframe, image, providerName, providerUrl, title = '', url = '' } = attrs;
94701
+ return {
94702
+ type: NodeTypes.embedBlock,
94703
+ title,
94704
+ url,
94705
+ data: {
94706
+ hName: 'embed',
94707
+ hProperties: {
94307
94708
  title,
94308
- type: 'image',
94309
94709
  url,
94310
- };
94311
- // Wrap in <figure> if caption is present
94312
- const img = imgData.caption
94313
- ? {
94314
- children: [
94315
- block,
94316
- { children: textToBlock(imgData.caption), data: { hName: 'figcaption' }, type: 'figcaption' },
94317
- ],
94318
- data: { hName: 'figure' },
94319
- type: 'figure',
94320
- url,
94321
- }
94322
- : block;
94323
- return [wrapPinnedBlocks(img, json)];
94324
- }
94325
- // Callout: info/warning/error boxes with icon and theme
94326
- case 'callout': {
94327
- const calloutJson = json;
94328
- // Preset callout types map to [icon, theme] tuples
94329
- const types = {
94330
- danger: ['❗️', 'error'],
94331
- info: ['📘', 'info'],
94332
- success: ['👍', 'okay'],
94333
- warning: ['🚧', 'warn'],
94334
- };
94335
- // Resolve type to [icon, theme] - use preset if available, otherwise custom
94336
- const resolvedType = typeof calloutJson.type === 'string' && calloutJson.type in types
94337
- ? types[calloutJson.type]
94338
- : [calloutJson.icon || '👍', typeof calloutJson.type === 'string' ? calloutJson.type : 'default'];
94339
- const [icon, theme] = Array.isArray(resolvedType) ? resolvedType : ['👍', 'default'];
94340
- if (!(calloutJson.title || calloutJson.body))
94341
- return [];
94342
- // Parses html & markdown content
94343
- const titleBlocks = parseBlock(calloutJson.title || '');
94344
- const bodyBlocks = parseBlock(calloutJson.body || '');
94345
- const children = [];
94346
- if (titleBlocks.length > 0 && titleBlocks[0].type === 'paragraph') {
94347
- const firstTitle = titleBlocks[0];
94348
- const heading = {
94349
- type: 'heading',
94350
- depth: 3,
94351
- children: (firstTitle.children || []),
94352
- };
94353
- children.push(heading);
94354
- children.push(...titleBlocks.slice(1), ...bodyBlocks);
94355
- }
94356
- else {
94357
- children.push(...titleBlocks, ...bodyBlocks);
94358
- }
94359
- // If there is no title or title is empty
94360
- const empty = !titleBlocks.length || !titleBlocks[0].children[0]?.value;
94361
- // Create mdxJsxFlowElement directly for mdxish
94362
- const calloutElement = {
94363
- type: 'mdxJsxFlowElement',
94364
- name: 'Callout',
94365
- attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default', empty }, [
94366
- 'icon',
94367
- 'theme',
94368
- 'type',
94369
- 'empty',
94370
- ]),
94371
- children: children,
94372
- };
94373
- return [wrapPinnedBlocks(calloutElement, json)];
94374
- }
94375
- // Parameters: renders as a table (used for API parameters, etc.)
94376
- case 'parameters': {
94377
- const paramsJson = json;
94378
- const { cols, data, rows } = paramsJson;
94379
- if (!Object.keys(data).length)
94380
- return [];
94381
- /**
94382
- * Convert sparse key-value data to 2D array.
94383
- * Keys are formatted as "ROW-COL" where ROW is "h" for header or a number.
94384
- * Example: { "h-0": "Name", "h-1": "Type", "0-0": "id", "0-1": "string" }
94385
- * Becomes: [["Name", "Type"], ["id", "string"]]
94386
- */
94387
- const sparseData = Object.entries(data).reduce((mapped, [key, v]) => {
94388
- const [row, col] = key.split('-');
94389
- // Header row ("h") becomes index 0, data rows are offset by 1
94390
- const rowIndex = row === 'h' ? 0 : parseInt(row, 10) + 1;
94391
- const colIndex = parseInt(col, 10);
94392
- if (!mapped[rowIndex])
94393
- mapped[rowIndex] = [];
94394
- mapped[rowIndex][colIndex] = v;
94395
- return mapped;
94396
- }, []);
94397
- // In compatibility mode, wrap cell content in paragraphs; otherwise inline text
94398
- const tokenizeCell = compatibilityMode ? textToBlock : parseTableCell;
94399
- const children = Array.from({ length: rows + 1 }, (_, y) => ({
94400
- children: Array.from({ length: cols }, (__, x) => ({
94401
- children: sparseData[y]?.[x] ? tokenizeCell(sparseData[y][x]) : [{ type: 'text', value: '' }],
94402
- type: y === 0 ? 'tableHead' : 'tableCell',
94403
- })),
94404
- type: 'tableRow',
94405
- }));
94406
- return [
94407
- wrapPinnedBlocks({ align: paramsJson.align ?? new Array(cols).fill('left'), children, type: 'table' }, json),
94408
- ];
94409
- }
94410
- // Embed: external content (YouTube, etc.) with provider detection
94411
- case 'embed': {
94412
- const embedJson = json;
94413
- const { html, title, url } = embedJson;
94414
- // Extract provider name from URL hostname (e.g., "youtube.com" → "youtube.com")
94415
- try {
94416
- embedJson.provider = new URL(url).hostname
94417
- .split(/(?:www)?\./)
94418
- .filter(i => i)
94419
- .join('.');
94420
- }
94421
- catch {
94422
- embedJson.provider = url;
94423
- }
94424
- return [
94425
- wrapPinnedBlocks({
94426
- children: [
94427
- { children: [{ type: 'text', value: title || '' }], title: embedJson.provider, type: 'link', url },
94428
- ],
94429
- data: { hName: 'embed-block', hProperties: { ...embedJson, href: url, html, title, url } },
94430
- type: 'embed',
94431
- }, json),
94432
- ];
94433
- }
94434
- // HTML block: raw HTML content (scripts enabled only in compatibility mode)
94435
- case 'html': {
94436
- const htmlJson = json;
94437
- return [
94438
- wrapPinnedBlocks({
94439
- data: {
94440
- hName: 'html-block',
94441
- hProperties: { html: htmlJson.html, runScripts: compatibilityMode, safeMode },
94442
- },
94443
- type: 'html-block',
94444
- }, json),
94445
- ];
94446
- }
94447
- // Recipe/TutorialTile: renders as Recipe component
94448
- case 'recipe':
94449
- case 'tutorial-tile': {
94450
- const recipeJson = json;
94451
- if (!recipeJson.slug || !recipeJson.title)
94452
- return [];
94453
- // Create mdxJsxFlowElement directly for mdxish flow
94454
- // Note: Don't wrap in pinned blocks for mdxish - rehypeMdxishComponents handles component resolution
94455
- // The node structure matches what mdxishComponentBlocks creates for JSX tags
94456
- const recipeNode = {
94457
- type: 'mdxJsxFlowElement',
94458
- name: 'Recipe',
94459
- attributes: toAttributes(recipeJson, ['slug', 'title']),
94460
- children: [],
94461
- // Position is optional but helps with debugging
94462
- position: undefined,
94463
- };
94464
- return [recipeNode];
94465
- }
94466
- // Unknown block types: render as generic div with JSON properties
94467
- default: {
94468
- const text = json.text || json.html || '';
94469
- return [
94470
- wrapPinnedBlocks({ children: textToBlock(text), data: { hName: type || 'div', hProperties: json, ...json }, type: 'div' }, json),
94471
- ];
94472
- }
94473
- }
94474
- }
94710
+ ...(favicon && { favicon }),
94711
+ ...(html && { html }),
94712
+ ...(iframe !== undefined && { iframe }),
94713
+ ...(image && { image }),
94714
+ ...(providerName && { providerName }),
94715
+ ...(providerUrl && { providerUrl }),
94716
+ },
94717
+ },
94718
+ position: jsx.position,
94719
+ };
94720
+ };
94721
+ const transformRecipe = (jsx) => {
94722
+ const attrs = getAttrs(jsx);
94723
+ const { backgroundColor = '', emoji = '', id = '', link = '', slug = '', title = '' } = attrs;
94724
+ return {
94725
+ type: NodeTypes.recipe,
94726
+ backgroundColor,
94727
+ emoji,
94728
+ id,
94729
+ link,
94730
+ slug,
94731
+ title,
94732
+ position: jsx.position,
94733
+ };
94734
+ };
94475
94735
  /**
94476
- * Block-level node types that cannot be nested inside paragraphs.
94477
- */
94478
- const blockTypes = [
94479
- 'heading',
94480
- 'code',
94481
- 'code-tabs',
94482
- 'paragraph',
94483
- 'blockquote',
94484
- 'list',
94485
- 'table',
94486
- 'thematicBreak',
94487
- 'html',
94488
- 'yaml',
94489
- 'toml',
94490
- 'rdme-pin',
94491
- 'rdme-callout',
94492
- 'html-block',
94493
- 'embed',
94494
- 'figure',
94495
- 'mdxJsxFlowElement',
94496
- ];
94736
+ * Transform a magic block image node into an ImageBlock.
94737
+ * Magic block images have structure: { type: 'image', url, title, alt, data.hProperties }
94738
+ */
94739
+ const transformMagicBlockImage = (node) => {
94740
+ const { alt = '', data, position, title = '', url = '' } = node;
94741
+ const hProps = data?.hProperties || {};
94742
+ const { align, border, width } = hProps;
94743
+ const hProperties = {
94744
+ alt,
94745
+ src: url,
94746
+ title,
94747
+ ...(align && { align }),
94748
+ ...(border && { border }),
94749
+ ...(width && { width }),
94750
+ };
94751
+ return {
94752
+ type: NodeTypes.imageBlock,
94753
+ align,
94754
+ alt,
94755
+ border,
94756
+ src: url,
94757
+ title,
94758
+ width,
94759
+ data: {
94760
+ hName: 'img',
94761
+ hProperties,
94762
+ },
94763
+ position,
94764
+ };
94765
+ };
94497
94766
  /**
94498
- * Check if a node is a block-level node (cannot be inside a paragraph)
94767
+ * Transform a magic block embed node into an EmbedBlock.
94768
+ * Magic block embeds have structure: { type: 'embed', children, data.hProperties }
94499
94769
  */
94500
- const isBlockNode = (node) => blockTypes.includes(node.type);
94770
+ const transformMagicBlockEmbed = (node) => {
94771
+ const { data, position } = node;
94772
+ const hProps = data?.hProperties || {};
94773
+ const { favicon, html, image, providerName, providerUrl, title = '', url = '' } = hProps;
94774
+ return {
94775
+ type: NodeTypes.embedBlock,
94776
+ title,
94777
+ url,
94778
+ data: {
94779
+ hName: 'embed',
94780
+ hProperties: {
94781
+ title,
94782
+ url,
94783
+ ...(favicon && { favicon }),
94784
+ ...(html && { html }),
94785
+ ...(image && { image }),
94786
+ ...(providerName && { providerName }),
94787
+ ...(providerUrl && { providerUrl }),
94788
+ },
94789
+ },
94790
+ position,
94791
+ };
94792
+ };
94793
+ const COMPONENT_MAP = {
94794
+ Callout: transformCallout,
94795
+ Embed: transformEmbed,
94796
+ Image: transformImage,
94797
+ Recipe: transformRecipe,
94798
+ };
94501
94799
  /**
94502
- * Unified plugin that restores magic blocks from placeholder tokens.
94800
+ * Transform mdxJsxFlowElement nodes and magic block nodes into proper MDAST node types.
94503
94801
  *
94504
- * During preprocessing, extractMagicBlocks replaces [block:TYPE]...[/block]
94505
- * with inline code tokens like `__MAGIC_BLOCK_0__`. This plugin finds those
94506
- * tokens in the parsed MDAST and replaces them with the parsed block content.
94802
+ * This transformer runs after mdxishComponentBlocks and converts:
94803
+ * - JSX component elements (Image, Callout, Embed, Recipe) into their corresponding MDAST types
94804
+ * - Magic block image nodes (type: 'image') into image-block
94805
+ * - Magic block embed nodes (type: 'embed') into embed-block
94806
+ * - Figure nodes containing images (from magic blocks with captions) - transforms the inner image
94807
+ *
94808
+ * This is controlled by the `newEditorTypes` flag to maintain backwards compatibility.
94507
94809
  */
94508
- const magicBlockRestorer = ({ blocks }) => tree => {
94509
- if (!blocks.length)
94510
- return;
94511
- // Map: key original raw magic block content
94512
- const magicBlockKeys = new Map(blocks.map(({ key, raw }) => [key, raw]));
94513
- // Collect replacements to apply (we need to visit in reverse to maintain indices)
94514
- const replacements = [];
94515
- // First pass: collect all replacements
94516
- visit(tree, 'inlineCode', (node, index, parent) => {
94517
- if (!parent || index == null)
94518
- return undefined;
94519
- const raw = magicBlockKeys.get(node.value);
94520
- if (!raw)
94521
- return undefined;
94522
- const children = parseMagicBlock(raw);
94523
- if (!children.length)
94524
- return undefined;
94525
- // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
94526
- if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
94527
- const blockNodes = [];
94528
- const inlineNodes = [];
94529
- // Separate block and inline nodes
94530
- children.forEach(child => {
94531
- if (isBlockNode(child)) {
94532
- blockNodes.push(child);
94533
- }
94534
- else {
94535
- inlineNodes.push(child);
94536
- }
94537
- });
94538
- const before = parent.children.slice(0, index);
94539
- const after = parent.children.slice(index + 1);
94540
- replacements.push({
94541
- parent,
94542
- blockNodes,
94543
- inlineNodes,
94544
- before,
94545
- after,
94546
- });
94547
- }
94548
- else {
94549
- // Normal case: just replace the inlineCode with the children
94550
- parent.children.splice(index, 1, ...children);
94551
- }
94552
- return undefined;
94810
+ const mdxishJsxToMdast = () => tree => {
94811
+ // Transform JSX components (Image, Callout, Embed, Recipe)
94812
+ visit(tree, 'mdxJsxFlowElement', (node, index, parent) => {
94813
+ if (!parent || index === undefined || !node.name)
94814
+ return;
94815
+ const transformer = COMPONENT_MAP[node.name];
94816
+ if (!transformer)
94817
+ return;
94818
+ const newNode = transformer(node);
94819
+ // Replace the JSX node with the MDAST node
94820
+ parent.children[index] = newNode;
94553
94821
  });
94554
- // Second pass: apply replacements that require lifting block nodes out of paragraphs
94555
- // Process in reverse order to maintain correct indices
94556
- for (let i = replacements.length - 1; i >= 0; i -= 1) {
94557
- const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
94558
- // Find the paragraph's position in the root
94559
- const rootChildren = tree.children;
94560
- const paraIndex = rootChildren.findIndex(child => child === parent);
94561
- if (paraIndex === -1) {
94562
- // Paragraph not found in root - fall back to normal replacement
94563
- // This shouldn't happen normally, but handle it gracefully
94564
- // Reconstruct the original index from before.length
94565
- const originalIndex = before.length;
94566
- parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
94567
- // eslint-disable-next-line no-continue
94568
- continue;
94569
- }
94570
- // Update or remove the paragraph
94571
- if (inlineNodes.length > 0) {
94572
- // Keep paragraph with inline nodes
94573
- parent.children = [...before, ...inlineNodes, ...after];
94574
- // Insert block nodes after the paragraph
94575
- if (blockNodes.length > 0) {
94576
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94577
- }
94578
- }
94579
- else if (before.length === 0 && after.length === 0) {
94580
- // Remove empty paragraph and replace with block nodes
94581
- rootChildren.splice(paraIndex, 1, ...blockNodes);
94582
- }
94583
- else {
94584
- // Keep paragraph with remaining content
94585
- parent.children = [...before, ...after];
94586
- // Insert block nodes after the paragraph
94587
- if (blockNodes.length > 0) {
94588
- rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94822
+ // Transform magic block images (type: 'image') to image-block
94823
+ // Note: Standard markdown images are wrapped in paragraphs and handled by imageTransformer
94824
+ // Magic block images are direct children of root, so we handle them here
94825
+ visit(tree, 'image', (node, index, parent) => {
94826
+ if (!parent || index === undefined)
94827
+ return SKIP;
94828
+ // Skip images inside paragraphs (those are standard markdown images handled by imageTransformer)
94829
+ if (parent.type === 'paragraph')
94830
+ return SKIP;
94831
+ const newNode = transformMagicBlockImage(node);
94832
+ parent.children[index] = newNode;
94833
+ return SKIP;
94834
+ });
94835
+ // Transform magic block embeds (type: 'embed') to embed-block
94836
+ visit(tree, 'embed', (node, index, parent) => {
94837
+ if (!parent || index === undefined)
94838
+ return SKIP;
94839
+ const newNode = transformMagicBlockEmbed(node);
94840
+ parent.children[index] = newNode;
94841
+ return SKIP;
94842
+ });
94843
+ // Transform images inside figure nodes (magic blocks with captions)
94844
+ const isFigure = (node) => node.type === 'figure';
94845
+ visit(tree, isFigure, node => {
94846
+ // Find and transform the image child
94847
+ node.children = node.children.map(child => {
94848
+ if (child.type === 'image') {
94849
+ return transformMagicBlockImage(child);
94589
94850
  }
94590
- }
94591
- }
94851
+ return child;
94852
+ });
94853
+ });
94854
+ return tree;
94592
94855
  };
94593
- /* harmony default export */ const mdxish_magic_blocks = (magicBlockRestorer);
94856
+ /* harmony default export */ const mdxish_jsx_to_mdast = (mdxishJsxToMdast);
94594
94857
 
94595
94858
  ;// ./processor/transform/mdxish/mdxish-mermaid.ts
94596
94859
 
@@ -94632,25 +94895,50 @@ const componentTagPattern = /<(\/?[A-Z][A-Za-z0-9_]*)([^>]*?)(\/?)>/g;
94632
94895
 
94633
94896
  ;// ./processor/transform/mdxish/mdxish-snake-case-components.ts
94634
94897
 
94898
+
94635
94899
  /**
94636
94900
  * Replaces snake_case component names with valid HTML placeholders.
94637
94901
  * Required because remark-parse rejects tags with underscores.
94638
94902
  * Example: `<Snake_case />` → `<MDXishSnakeCase0 />`
94903
+ *
94904
+ * Code blocks and inline code are protected and will not be transformed.
94905
+ *
94906
+ * @param content - The markdown content to process
94907
+ * @param options - Options including knownComponents to filter by
94639
94908
  */
94640
- function processSnakeCaseComponent(content) {
94909
+ function processSnakeCaseComponent(content, options = {}) {
94910
+ const { knownComponents } = options;
94641
94911
  // Early exit if no potential snake_case components
94642
94912
  if (!/[A-Z][A-Za-z0-9]*_[A-Za-z0-9_]*/.test(content)) {
94643
94913
  return { content, mapping: {} };
94644
94914
  }
94915
+ // Step 1: Extract code blocks to protect them from transformation
94916
+ const { protectedCode, protectedContent } = protectCodeBlocks(content);
94917
+ // Find the highest existing placeholder number to avoid collisions
94918
+ // e.g., if content has <MDXishSnakeCase0 />, start counter from 1
94919
+ const placeholderPattern = /MDXishSnakeCase(\d+)/g;
94920
+ let startCounter = 0;
94921
+ let placeholderMatch;
94922
+ while ((placeholderMatch = placeholderPattern.exec(content)) !== null) {
94923
+ const num = parseInt(placeholderMatch[1], 10);
94924
+ if (num >= startCounter) {
94925
+ startCounter = num + 1;
94926
+ }
94927
+ }
94645
94928
  const mapping = {};
94646
94929
  const reverseMap = new Map();
94647
- let counter = 0;
94648
- const processedContent = content.replace(componentTagPattern, (match, tagName, attrs, selfClosing) => {
94930
+ let counter = startCounter;
94931
+ // Step 2: Transform snake_case components in non-code content
94932
+ const processedContent = protectedContent.replace(componentTagPattern, (match, tagName, attrs, selfClosing) => {
94649
94933
  if (!tagName.includes('_')) {
94650
94934
  return match;
94651
94935
  }
94652
94936
  const isClosing = tagName.startsWith('/');
94653
94937
  const cleanTagName = isClosing ? tagName.slice(1) : tagName;
94938
+ // Only transform if it's a known component (or if no filter is provided)
94939
+ if (knownComponents && !knownComponents.has(cleanTagName)) {
94940
+ return match;
94941
+ }
94654
94942
  let placeholder = reverseMap.get(cleanTagName);
94655
94943
  if (!placeholder) {
94656
94944
  // eslint-disable-next-line no-plusplus
@@ -94661,8 +94949,10 @@ function processSnakeCaseComponent(content) {
94661
94949
  const processedTagName = isClosing ? `/${placeholder}` : placeholder;
94662
94950
  return `<${processedTagName}${attrs}${selfClosing}>`;
94663
94951
  });
94952
+ // Step 3: Restore code blocks (untouched)
94953
+ const finalContent = restoreCodeBlocks(processedContent, protectedCode);
94664
94954
  return {
94665
- content: processedContent,
94955
+ content: finalContent,
94666
94956
  mapping,
94667
94957
  };
94668
94958
  }
@@ -95044,57 +95334,1172 @@ const variablesTextTransformer = () => tree => {
95044
95334
  };
95045
95335
  /* harmony default export */ const variables_text = (variablesTextTransformer);
95046
95336
 
95047
- ;// ./lib/utils/extractMagicBlocks.ts
95337
+ ;// ./lib/mdast-util/magic-block/index.ts
95338
+ const contextMap = new WeakMap();
95048
95339
  /**
95049
- * The content matching in this regex captures everything between `[block:TYPE]`
95050
- * and `[/block]`, including new lines. Negative lookahead for the closing
95051
- * `[/block]` tag is required to prevent greedy matching to ensure it stops at
95052
- * the first closing tag it encounters preventing vulnerability to polynomial
95053
- * backtracking issues.
95340
+ * Find the magicBlock token in the token ancestry.
95054
95341
  */
95055
- const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
95342
+ function findMagicBlockToken() {
95343
+ // Walk up the token stack to find the magicBlock token
95344
+ const events = this.tokenStack;
95345
+ for (let i = events.length - 1; i >= 0; i -= 1) {
95346
+ const token = events[i][0];
95347
+ if (token.type === 'magicBlock') {
95348
+ return token;
95349
+ }
95350
+ }
95351
+ return undefined;
95352
+ }
95056
95353
  /**
95057
- * Extract legacy magic block syntax from a markdown string.
95058
- * Returns the modified markdown and an array of extracted blocks.
95354
+ * Enter handler: Create a new magicBlock node.
95059
95355
  */
95060
- function extractMagicBlocks(markdown) {
95061
- const blocks = [];
95062
- let index = 0;
95063
- const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
95064
- /**
95065
- * Key is the unique identifier for the magic block
95066
- */
95067
- const key = `__MAGIC_BLOCK_${index}__`;
95068
- /**
95069
- * Token is a wrapper around the `key` to serialize & influence how the
95070
- * magic block is parsed in the remark pipeline.
95071
- * - Use backticks so it becomes a code span, preventing `remarkParse` from
95072
- * parsing special characters in the token as markdown syntax
95073
- * - Prepend a newline to ensure it is parsed as a block level node
95074
- * - Append a newline to ensure it is separated from following content
95075
- */
95076
- const token = `\n\`${key}\`\n`;
95077
- blocks.push({ key, raw: match, token });
95078
- index += 1;
95079
- return token;
95080
- });
95081
- return { replaced, blocks };
95356
+ function enterMagicBlock(token) {
95357
+ // Initialize context for this magic block
95358
+ contextMap.set(token, { blockType: '', dataChunks: [] });
95359
+ this.enter({
95360
+ type: 'magicBlock',
95361
+ blockType: '',
95362
+ data: {},
95363
+ value: '',
95364
+ }, token);
95365
+ }
95366
+ /**
95367
+ * Exit handler for block type: Extract the block type from the token.
95368
+ */
95369
+ function exitMagicBlockType(token) {
95370
+ const blockToken = findMagicBlockToken.call(this);
95371
+ if (!blockToken)
95372
+ return;
95373
+ const context = contextMap.get(blockToken);
95374
+ if (context) {
95375
+ context.blockType = this.sliceSerialize(token);
95376
+ }
95377
+ }
95378
+ /**
95379
+ * Exit handler for block data: Accumulate JSON content chunks.
95380
+ */
95381
+ function exitMagicBlockData(token) {
95382
+ const blockToken = findMagicBlockToken.call(this);
95383
+ if (!blockToken)
95384
+ return;
95385
+ const context = contextMap.get(blockToken);
95386
+ if (context) {
95387
+ context.dataChunks.push(this.sliceSerialize(token));
95388
+ }
95389
+ }
95390
+ /**
95391
+ * Exit handler for line endings: Preserve newlines in multiline blocks.
95392
+ */
95393
+ function exitMagicBlockLineEnding(token) {
95394
+ const blockToken = findMagicBlockToken.call(this);
95395
+ if (!blockToken)
95396
+ return;
95397
+ const context = contextMap.get(blockToken);
95398
+ if (context) {
95399
+ context.dataChunks.push(this.sliceSerialize(token));
95400
+ }
95401
+ }
95402
+ /**
95403
+ * Exit handler for end marker: If this is a failed end marker check (not the final marker),
95404
+ * add its content to the data chunks so we don't lose characters like '['.
95405
+ */
95406
+ function exitMagicBlockMarkerEnd(token) {
95407
+ const blockToken = findMagicBlockToken.call(this);
95408
+ if (!blockToken)
95409
+ return;
95410
+ // Get the content of the marker
95411
+ const markerContent = this.sliceSerialize(token);
95412
+ // If this marker doesn't end with ']', it's a failed check and content belongs to data
95413
+ // The successful end marker would be "[/block]"
95414
+ if (!markerContent.endsWith(']') || markerContent !== '[/block]') {
95415
+ const context = contextMap.get(blockToken);
95416
+ if (context) {
95417
+ context.dataChunks.push(markerContent);
95418
+ }
95419
+ }
95420
+ }
95421
+ /**
95422
+ * Exit handler: Finalize the magicBlock node with parsed JSON data.
95423
+ */
95424
+ function exitMagicBlock(token) {
95425
+ const context = contextMap.get(token);
95426
+ const node = this.stack[this.stack.length - 1];
95427
+ if (context) {
95428
+ const rawJson = context.dataChunks.join('');
95429
+ node.blockType = context.blockType;
95430
+ node.value = `[block:${context.blockType}]${rawJson}[/block]`;
95431
+ // Parse JSON data
95432
+ try {
95433
+ node.data = JSON.parse(rawJson.trim());
95434
+ }
95435
+ catch {
95436
+ // Invalid JSON - store empty object but keep the raw value
95437
+ node.data = {};
95438
+ }
95439
+ // Clean up context
95440
+ contextMap.delete(token);
95441
+ }
95442
+ this.exit(token);
95443
+ }
95444
+ /**
95445
+ * Handler to serialize magicBlock nodes back to markdown.
95446
+ */
95447
+ const handleMagicBlock = (node) => {
95448
+ const magicNode = node;
95449
+ // If we have the original raw value, use it
95450
+ if (magicNode.value) {
95451
+ return magicNode.value;
95452
+ }
95453
+ // Otherwise reconstruct from parsed data
95454
+ const json = JSON.stringify(magicNode.data, null, 2);
95455
+ return `[block:${magicNode.blockType}]\n${json}\n[/block]`;
95456
+ };
95457
+ /**
95458
+ * Create an extension for `mdast-util-from-markdown` to enable magic blocks.
95459
+ *
95460
+ * Converts micromark magic block tokens into `magicBlock` MDAST nodes.
95461
+ *
95462
+ * @returns Extension for `mdast-util-from-markdown`
95463
+ */
95464
+ function magicBlockFromMarkdown() {
95465
+ return {
95466
+ enter: {
95467
+ magicBlock: enterMagicBlock,
95468
+ },
95469
+ exit: {
95470
+ magicBlockType: exitMagicBlockType,
95471
+ magicBlockData: exitMagicBlockData,
95472
+ magicBlockLineEnding: exitMagicBlockLineEnding,
95473
+ magicBlockMarkerEnd: exitMagicBlockMarkerEnd,
95474
+ magicBlock: exitMagicBlock,
95475
+ },
95476
+ };
95477
+ }
95478
+ /**
95479
+ * Create an extension for `mdast-util-to-markdown` to serialize magic blocks.
95480
+ *
95481
+ * Converts `magicBlock` MDAST nodes back to `[block:TYPE]JSON[/block]` syntax.
95482
+ *
95483
+ * @returns Extension for `mdast-util-to-markdown`
95484
+ */
95485
+ function magicBlockToMarkdown() {
95486
+ return {
95487
+ handlers: {
95488
+ magicBlock: handleMagicBlock,
95489
+ },
95490
+ };
95491
+ }
95492
+
95493
+ ;// ./node_modules/micromark-util-symbol/lib/codes.js
95494
+ /**
95495
+ * Character codes.
95496
+ *
95497
+ * This module is compiled away!
95498
+ *
95499
+ * micromark works based on character codes.
95500
+ * This module contains constants for the ASCII block and the replacement
95501
+ * character.
95502
+ * A couple of them are handled in a special way, such as the line endings
95503
+ * (CR, LF, and CR+LF, commonly known as end-of-line: EOLs), the tab (horizontal
95504
+ * tab) and its expansion based on what column it’s at (virtual space),
95505
+ * and the end-of-file (eof) character.
95506
+ * As values are preprocessed before handling them, the actual characters LF,
95507
+ * CR, HT, and NUL (which is present as the replacement character), are
95508
+ * guaranteed to not exist.
95509
+ *
95510
+ * Unicode basic latin block.
95511
+ */
95512
+ const codes = /** @type {const} */ ({
95513
+ carriageReturn: -5,
95514
+ lineFeed: -4,
95515
+ carriageReturnLineFeed: -3,
95516
+ horizontalTab: -2,
95517
+ virtualSpace: -1,
95518
+ eof: null,
95519
+ nul: 0,
95520
+ soh: 1,
95521
+ stx: 2,
95522
+ etx: 3,
95523
+ eot: 4,
95524
+ enq: 5,
95525
+ ack: 6,
95526
+ bel: 7,
95527
+ bs: 8,
95528
+ ht: 9, // `\t`
95529
+ lf: 10, // `\n`
95530
+ vt: 11, // `\v`
95531
+ ff: 12, // `\f`
95532
+ cr: 13, // `\r`
95533
+ so: 14,
95534
+ si: 15,
95535
+ dle: 16,
95536
+ dc1: 17,
95537
+ dc2: 18,
95538
+ dc3: 19,
95539
+ dc4: 20,
95540
+ nak: 21,
95541
+ syn: 22,
95542
+ etb: 23,
95543
+ can: 24,
95544
+ em: 25,
95545
+ sub: 26,
95546
+ esc: 27,
95547
+ fs: 28,
95548
+ gs: 29,
95549
+ rs: 30,
95550
+ us: 31,
95551
+ space: 32,
95552
+ exclamationMark: 33, // `!`
95553
+ quotationMark: 34, // `"`
95554
+ numberSign: 35, // `#`
95555
+ dollarSign: 36, // `$`
95556
+ percentSign: 37, // `%`
95557
+ ampersand: 38, // `&`
95558
+ apostrophe: 39, // `'`
95559
+ leftParenthesis: 40, // `(`
95560
+ rightParenthesis: 41, // `)`
95561
+ asterisk: 42, // `*`
95562
+ plusSign: 43, // `+`
95563
+ comma: 44, // `,`
95564
+ dash: 45, // `-`
95565
+ dot: 46, // `.`
95566
+ slash: 47, // `/`
95567
+ digit0: 48, // `0`
95568
+ digit1: 49, // `1`
95569
+ digit2: 50, // `2`
95570
+ digit3: 51, // `3`
95571
+ digit4: 52, // `4`
95572
+ digit5: 53, // `5`
95573
+ digit6: 54, // `6`
95574
+ digit7: 55, // `7`
95575
+ digit8: 56, // `8`
95576
+ digit9: 57, // `9`
95577
+ colon: 58, // `:`
95578
+ semicolon: 59, // `;`
95579
+ lessThan: 60, // `<`
95580
+ equalsTo: 61, // `=`
95581
+ greaterThan: 62, // `>`
95582
+ questionMark: 63, // `?`
95583
+ atSign: 64, // `@`
95584
+ uppercaseA: 65, // `A`
95585
+ uppercaseB: 66, // `B`
95586
+ uppercaseC: 67, // `C`
95587
+ uppercaseD: 68, // `D`
95588
+ uppercaseE: 69, // `E`
95589
+ uppercaseF: 70, // `F`
95590
+ uppercaseG: 71, // `G`
95591
+ uppercaseH: 72, // `H`
95592
+ uppercaseI: 73, // `I`
95593
+ uppercaseJ: 74, // `J`
95594
+ uppercaseK: 75, // `K`
95595
+ uppercaseL: 76, // `L`
95596
+ uppercaseM: 77, // `M`
95597
+ uppercaseN: 78, // `N`
95598
+ uppercaseO: 79, // `O`
95599
+ uppercaseP: 80, // `P`
95600
+ uppercaseQ: 81, // `Q`
95601
+ uppercaseR: 82, // `R`
95602
+ uppercaseS: 83, // `S`
95603
+ uppercaseT: 84, // `T`
95604
+ uppercaseU: 85, // `U`
95605
+ uppercaseV: 86, // `V`
95606
+ uppercaseW: 87, // `W`
95607
+ uppercaseX: 88, // `X`
95608
+ uppercaseY: 89, // `Y`
95609
+ uppercaseZ: 90, // `Z`
95610
+ leftSquareBracket: 91, // `[`
95611
+ backslash: 92, // `\`
95612
+ rightSquareBracket: 93, // `]`
95613
+ caret: 94, // `^`
95614
+ underscore: 95, // `_`
95615
+ graveAccent: 96, // `` ` ``
95616
+ lowercaseA: 97, // `a`
95617
+ lowercaseB: 98, // `b`
95618
+ lowercaseC: 99, // `c`
95619
+ lowercaseD: 100, // `d`
95620
+ lowercaseE: 101, // `e`
95621
+ lowercaseF: 102, // `f`
95622
+ lowercaseG: 103, // `g`
95623
+ lowercaseH: 104, // `h`
95624
+ lowercaseI: 105, // `i`
95625
+ lowercaseJ: 106, // `j`
95626
+ lowercaseK: 107, // `k`
95627
+ lowercaseL: 108, // `l`
95628
+ lowercaseM: 109, // `m`
95629
+ lowercaseN: 110, // `n`
95630
+ lowercaseO: 111, // `o`
95631
+ lowercaseP: 112, // `p`
95632
+ lowercaseQ: 113, // `q`
95633
+ lowercaseR: 114, // `r`
95634
+ lowercaseS: 115, // `s`
95635
+ lowercaseT: 116, // `t`
95636
+ lowercaseU: 117, // `u`
95637
+ lowercaseV: 118, // `v`
95638
+ lowercaseW: 119, // `w`
95639
+ lowercaseX: 120, // `x`
95640
+ lowercaseY: 121, // `y`
95641
+ lowercaseZ: 122, // `z`
95642
+ leftCurlyBrace: 123, // `{`
95643
+ verticalBar: 124, // `|`
95644
+ rightCurlyBrace: 125, // `}`
95645
+ tilde: 126, // `~`
95646
+ del: 127,
95647
+ // Unicode Specials block.
95648
+ byteOrderMarker: 65_279,
95649
+ // Unicode Specials block.
95650
+ replacementCharacter: 65_533 // `�`
95651
+ })
95652
+
95653
+ ;// ./lib/micromark/magic-block/syntax.ts
95654
+
95655
+
95656
+ /**
95657
+ * Known magic block types that the tokenizer will recognize.
95658
+ * Unknown types will not be tokenized as magic blocks.
95659
+ */
95660
+ const KNOWN_BLOCK_TYPES = new Set([
95661
+ 'code',
95662
+ 'api-header',
95663
+ 'image',
95664
+ 'callout',
95665
+ 'parameters',
95666
+ 'table',
95667
+ 'embed',
95668
+ 'html',
95669
+ 'recipe',
95670
+ 'tutorial-tile',
95671
+ ]);
95672
+ /**
95673
+ * Check if a character is valid for a magic block type identifier.
95674
+ * Types can contain alphanumeric characters and hyphens (e.g., "api-header", "tutorial-tile")
95675
+ */
95676
+ function isTypeChar(code) {
95677
+ return asciiAlphanumeric(code) || code === codes.dash;
95678
+ }
95679
+ /**
95680
+ * Creates the opening marker state machine: [block:
95681
+ * Returns the first state function to start parsing.
95682
+ */
95683
+ function createOpeningMarkerParser(effects, nok, onComplete) {
95684
+ const expectB = (code) => {
95685
+ if (code !== codes.lowercaseB)
95686
+ return nok(code);
95687
+ effects.consume(code);
95688
+ return expectL;
95689
+ };
95690
+ const expectL = (code) => {
95691
+ if (code !== codes.lowercaseL)
95692
+ return nok(code);
95693
+ effects.consume(code);
95694
+ return expectO;
95695
+ };
95696
+ const expectO = (code) => {
95697
+ if (code !== codes.lowercaseO)
95698
+ return nok(code);
95699
+ effects.consume(code);
95700
+ return expectC;
95701
+ };
95702
+ const expectC = (code) => {
95703
+ if (code !== codes.lowercaseC)
95704
+ return nok(code);
95705
+ effects.consume(code);
95706
+ return expectK;
95707
+ };
95708
+ const expectK = (code) => {
95709
+ if (code !== codes.lowercaseK)
95710
+ return nok(code);
95711
+ effects.consume(code);
95712
+ return expectColon;
95713
+ };
95714
+ const expectColon = (code) => {
95715
+ if (code !== codes.colon)
95716
+ return nok(code);
95717
+ effects.consume(code);
95718
+ effects.exit('magicBlockMarkerStart');
95719
+ effects.enter('magicBlockType');
95720
+ return onComplete;
95721
+ };
95722
+ return expectB;
95723
+ }
95724
+ /**
95725
+ * Creates the type capture state machine.
95726
+ * Captures type characters until ] and validates against known types.
95727
+ */
95728
+ function createTypeCaptureParser(effects, nok, onComplete, blockTypeRef) {
95729
+ const captureTypeFirst = (code) => {
95730
+ // Reject empty type name [block:]
95731
+ if (code === codes.rightSquareBracket) {
95732
+ return nok(code);
95733
+ }
95734
+ if (isTypeChar(code)) {
95735
+ blockTypeRef.value += String.fromCharCode(code);
95736
+ effects.consume(code);
95737
+ return captureType;
95738
+ }
95739
+ return nok(code);
95740
+ };
95741
+ const captureType = (code) => {
95742
+ if (code === codes.rightSquareBracket) {
95743
+ if (!KNOWN_BLOCK_TYPES.has(blockTypeRef.value)) {
95744
+ return nok(code);
95745
+ }
95746
+ effects.exit('magicBlockType');
95747
+ effects.enter('magicBlockMarkerTypeEnd');
95748
+ effects.consume(code);
95749
+ effects.exit('magicBlockMarkerTypeEnd');
95750
+ return onComplete;
95751
+ }
95752
+ if (isTypeChar(code)) {
95753
+ blockTypeRef.value += String.fromCharCode(code);
95754
+ effects.consume(code);
95755
+ return captureType;
95756
+ }
95757
+ return nok(code);
95758
+ };
95759
+ return { first: captureTypeFirst, remaining: captureType };
95760
+ }
95761
+ /**
95762
+ * Creates the closing marker state machine: /block]
95763
+ * Handles partial matches by calling onMismatch to fall back to data capture.
95764
+ */
95765
+ function createClosingMarkerParser(effects, onSuccess, onMismatch, onEof, jsonState) {
95766
+ const handleMismatch = (code) => {
95767
+ if (jsonState && code === codes.quotationMark) {
95768
+ jsonState.inString = true;
95769
+ }
95770
+ return onMismatch(code);
95771
+ };
95772
+ const expectSlash = (code) => {
95773
+ if (code === null)
95774
+ return onEof(code);
95775
+ if (code !== codes.slash)
95776
+ return handleMismatch(code);
95777
+ effects.consume(code);
95778
+ return expectB;
95779
+ };
95780
+ const expectB = (code) => {
95781
+ if (code === null)
95782
+ return onEof(code);
95783
+ if (code !== codes.lowercaseB)
95784
+ return handleMismatch(code);
95785
+ effects.consume(code);
95786
+ return expectL;
95787
+ };
95788
+ const expectL = (code) => {
95789
+ if (code === null)
95790
+ return onEof(code);
95791
+ if (code !== codes.lowercaseL)
95792
+ return handleMismatch(code);
95793
+ effects.consume(code);
95794
+ return expectO;
95795
+ };
95796
+ const expectO = (code) => {
95797
+ if (code === null)
95798
+ return onEof(code);
95799
+ if (code !== codes.lowercaseO)
95800
+ return handleMismatch(code);
95801
+ effects.consume(code);
95802
+ return expectC;
95803
+ };
95804
+ const expectC = (code) => {
95805
+ if (code === null)
95806
+ return onEof(code);
95807
+ if (code !== codes.lowercaseC)
95808
+ return handleMismatch(code);
95809
+ effects.consume(code);
95810
+ return expectK;
95811
+ };
95812
+ const expectK = (code) => {
95813
+ if (code === null)
95814
+ return onEof(code);
95815
+ if (code !== codes.lowercaseK)
95816
+ return handleMismatch(code);
95817
+ effects.consume(code);
95818
+ return expectBracket;
95819
+ };
95820
+ const expectBracket = (code) => {
95821
+ if (code === null)
95822
+ return onEof(code);
95823
+ if (code !== codes.rightSquareBracket)
95824
+ return handleMismatch(code);
95825
+ effects.consume(code);
95826
+ return onSuccess;
95827
+ };
95828
+ return { expectSlash };
95082
95829
  }
95083
95830
  /**
95084
- * Restore extracted magic blocks back into a markdown string.
95831
+ * Partial construct for checking non-lazy continuation.
95832
+ * This is used by the flow tokenizer to check if we can continue
95833
+ * parsing on the next line.
95834
+ */
95835
+ const syntax_nonLazyContinuation = {
95836
+ partial: true,
95837
+ tokenize: syntax_tokenizeNonLazyContinuation,
95838
+ };
95839
+ /**
95840
+ * Tokenizer for non-lazy continuation checking.
95841
+ * Returns ok if the next line is non-lazy (can continue), nok if lazy.
95842
+ */
95843
+ function syntax_tokenizeNonLazyContinuation(effects, ok, nok) {
95844
+ const lineStart = (code) => {
95845
+ // `this` here refers to the micromark parser
95846
+ // since we are just passing functions as references and not actually calling them
95847
+ // micromarks's internal parser will call this automatically with appropriate arguments
95848
+ return this.parser.lazy[this.now().line] ? nok(code) : ok(code);
95849
+ };
95850
+ const start = (code) => {
95851
+ if (code === null) {
95852
+ return nok(code);
95853
+ }
95854
+ if (!markdownLineEnding(code)) {
95855
+ return nok(code);
95856
+ }
95857
+ effects.enter('magicBlockLineEnding');
95858
+ effects.consume(code);
95859
+ effects.exit('magicBlockLineEnding');
95860
+ return lineStart;
95861
+ };
95862
+ return start;
95863
+ }
95864
+ /**
95865
+ * Create a micromark extension for magic block syntax.
95866
+ *
95867
+ * This extension handles both single-line and multiline magic blocks:
95868
+ * - Flow construct (concrete): Handles block-level multiline magic blocks at document level
95869
+ * - Text construct: Handles inline magic blocks in lists, paragraphs, etc.
95870
+ *
95871
+ * The flow construct is marked as "concrete" which prevents it from being
95872
+ * interrupted by container markers (like `>` for blockquotes or `-` for lists).
95085
95873
  */
95086
- function restoreMagicBlocks(replaced, blocks) {
95087
- // If a magic block is at the start or end of the document, the extraction
95088
- // token's newlines will have been trimmed during processing. We need to
95089
- // account for that here to ensure the token is found and replaced correctly.
95090
- // These extra newlines will be removed again when the final string is trimmed.
95091
- const content = `\n${replaced}\n`;
95092
- const restoredContent = blocks.reduce((acc, { token, raw }) => {
95093
- // Ensure each magic block is separated by newlines when restored.
95094
- return acc.split(token).join(`\n${raw}\n`);
95095
- }, content);
95096
- return restoredContent.trim();
95874
+ function magicBlock() {
95875
+ return {
95876
+ // Flow construct - handles block-level magic blocks at document root
95877
+ // Marked as concrete to prevent interruption by containers
95878
+ flow: {
95879
+ [codes.leftSquareBracket]: {
95880
+ name: 'magicBlock',
95881
+ concrete: true,
95882
+ tokenize: tokenizeMagicBlockFlow,
95883
+ },
95884
+ },
95885
+ // Text construct - handles magic blocks in inline contexts (lists, paragraphs)
95886
+ text: {
95887
+ [codes.leftSquareBracket]: {
95888
+ name: 'magicBlock',
95889
+ tokenize: tokenizeMagicBlockText,
95890
+ },
95891
+ },
95892
+ };
95893
+ }
95894
+ /**
95895
+ * Flow tokenizer for block-level magic blocks (multiline).
95896
+ * Uses the continuation checking pattern from code fences.
95897
+ */
95898
+ function tokenizeMagicBlockFlow(effects, ok, nok) {
95899
+ // State for tracking JSON content
95900
+ const jsonState = { escapeNext: false, inString: false };
95901
+ const blockTypeRef = { value: '' };
95902
+ let seenOpenBrace = false;
95903
+ // Create shared parsers for opening marker and type capture
95904
+ const typeParser = createTypeCaptureParser(effects, nok, beforeData, blockTypeRef);
95905
+ const openingMarkerParser = createOpeningMarkerParser(effects, nok, typeParser.first);
95906
+ return start;
95907
+ function start(code) {
95908
+ if (code !== codes.leftSquareBracket)
95909
+ return nok(code);
95910
+ effects.enter('magicBlock');
95911
+ effects.enter('magicBlockMarkerStart');
95912
+ effects.consume(code);
95913
+ return openingMarkerParser;
95914
+ }
95915
+ /**
95916
+ * State before data content - handles line endings or starts data capture.
95917
+ */
95918
+ function beforeData(code) {
95919
+ // EOF - magic block must be closed
95920
+ if (code === null) {
95921
+ effects.exit('magicBlock');
95922
+ return nok(code);
95923
+ }
95924
+ // Line ending before any data - check continuation
95925
+ if (markdownLineEnding(code)) {
95926
+ return effects.check(syntax_nonLazyContinuation, continuationOkBeforeData, after)(code);
95927
+ }
95928
+ // Check for closing marker directly (without entering data)
95929
+ // This handles cases like [block:type]\n{}\n\n[/block] where there are
95930
+ // newlines after the data object
95931
+ if (code === codes.leftSquareBracket) {
95932
+ effects.enter('magicBlockMarkerEnd');
95933
+ effects.consume(code);
95934
+ return expectSlashFromContinuation;
95935
+ }
95936
+ // If we've already seen the opening brace, just continue capturing data
95937
+ if (seenOpenBrace) {
95938
+ effects.enter('magicBlockData');
95939
+ return captureData(code);
95940
+ }
95941
+ // Skip whitespace (spaces/tabs) before the data - stay in beforeData
95942
+ // Don't enter magicBlockData token yet until we confirm there's a '{'
95943
+ if (code === codes.space || code === codes.horizontalTab) {
95944
+ return beforeDataWhitespace(code);
95945
+ }
95946
+ // Data must start with '{' for valid JSON
95947
+ if (code !== codes.leftCurlyBrace) {
95948
+ effects.exit('magicBlock');
95949
+ return nok(code);
95950
+ }
95951
+ // We have '{' - enter data token and start capturing
95952
+ seenOpenBrace = true;
95953
+ effects.enter('magicBlockData');
95954
+ return captureData(code);
95955
+ }
95956
+ /**
95957
+ * Consume whitespace before the data without creating a token.
95958
+ * Uses a temporary token to satisfy micromark's requirement.
95959
+ */
95960
+ function beforeDataWhitespace(code) {
95961
+ if (code === null) {
95962
+ effects.exit('magicBlock');
95963
+ return nok(code);
95964
+ }
95965
+ if (markdownLineEnding(code)) {
95966
+ return effects.check(syntax_nonLazyContinuation, continuationOkBeforeData, after)(code);
95967
+ }
95968
+ if (code === codes.space || code === codes.horizontalTab) {
95969
+ // We need to consume this but can't without a token - use magicBlockData
95970
+ // and track that we haven't seen '{' yet
95971
+ effects.enter('magicBlockData');
95972
+ effects.consume(code);
95973
+ return beforeDataWhitespaceContinue;
95974
+ }
95975
+ if (code === codes.leftCurlyBrace) {
95976
+ seenOpenBrace = true;
95977
+ effects.enter('magicBlockData');
95978
+ return captureData(code);
95979
+ }
95980
+ effects.exit('magicBlock');
95981
+ return nok(code);
95982
+ }
95983
+ /**
95984
+ * Continue consuming whitespace or validate the opening brace.
95985
+ */
95986
+ function beforeDataWhitespaceContinue(code) {
95987
+ if (code === null) {
95988
+ effects.exit('magicBlockData');
95989
+ effects.exit('magicBlock');
95990
+ return nok(code);
95991
+ }
95992
+ if (markdownLineEnding(code)) {
95993
+ effects.exit('magicBlockData');
95994
+ return effects.check(syntax_nonLazyContinuation, continuationOk, after)(code);
95995
+ }
95996
+ if (code === codes.space || code === codes.horizontalTab) {
95997
+ effects.consume(code);
95998
+ return beforeDataWhitespaceContinue;
95999
+ }
96000
+ if (code === codes.leftCurlyBrace) {
96001
+ seenOpenBrace = true;
96002
+ return captureData(code);
96003
+ }
96004
+ effects.exit('magicBlockData');
96005
+ effects.exit('magicBlock');
96006
+ return nok(code);
96007
+ }
96008
+ /**
96009
+ * Continuation OK before we've entered data token.
96010
+ */
96011
+ function continuationOkBeforeData(code) {
96012
+ effects.enter('magicBlockLineEnding');
96013
+ effects.consume(code);
96014
+ effects.exit('magicBlockLineEnding');
96015
+ return beforeData; // Stay in beforeData state - don't enter magicBlockData yet
96016
+ }
96017
+ function captureData(code) {
96018
+ // EOF - magic block must be closed
96019
+ if (code === null) {
96020
+ effects.exit('magicBlockData');
96021
+ effects.exit('magicBlock');
96022
+ return nok(code);
96023
+ }
96024
+ // At line ending, check if we can continue on the next line
96025
+ if (markdownLineEnding(code)) {
96026
+ effects.exit('magicBlockData');
96027
+ return effects.check(syntax_nonLazyContinuation, continuationOk, after)(code);
96028
+ }
96029
+ if (jsonState.escapeNext) {
96030
+ jsonState.escapeNext = false;
96031
+ effects.consume(code);
96032
+ return captureData;
96033
+ }
96034
+ if (jsonState.inString) {
96035
+ if (code === codes.backslash) {
96036
+ jsonState.escapeNext = true;
96037
+ effects.consume(code);
96038
+ return captureData;
96039
+ }
96040
+ if (code === codes.quotationMark) {
96041
+ jsonState.inString = false;
96042
+ }
96043
+ effects.consume(code);
96044
+ return captureData;
96045
+ }
96046
+ if (code === codes.quotationMark) {
96047
+ jsonState.inString = true;
96048
+ effects.consume(code);
96049
+ return captureData;
96050
+ }
96051
+ if (code === codes.leftSquareBracket) {
96052
+ effects.exit('magicBlockData');
96053
+ effects.enter('magicBlockMarkerEnd');
96054
+ effects.consume(code);
96055
+ return expectSlash;
96056
+ }
96057
+ effects.consume(code);
96058
+ return captureData;
96059
+ }
96060
+ /**
96061
+ * Called when non-lazy continuation check passes - we can continue parsing.
96062
+ */
96063
+ function continuationOk(code) {
96064
+ // Consume the line ending
96065
+ effects.enter('magicBlockLineEnding');
96066
+ effects.consume(code);
96067
+ effects.exit('magicBlockLineEnding');
96068
+ return continuationStart;
96069
+ }
96070
+ /**
96071
+ * Start of continuation line - check for more line endings, closing marker, or start capturing data.
96072
+ */
96073
+ function continuationStart(code) {
96074
+ // Handle consecutive line endings
96075
+ if (code === null) {
96076
+ effects.exit('magicBlock');
96077
+ return nok(code);
96078
+ }
96079
+ if (markdownLineEnding(code)) {
96080
+ return effects.check(syntax_nonLazyContinuation, continuationOkBeforeData, after)(code);
96081
+ }
96082
+ // Check if this is the start of the closing marker [/block]
96083
+ // If so, handle it directly without entering magicBlockData
96084
+ if (code === codes.leftSquareBracket) {
96085
+ effects.enter('magicBlockMarkerEnd');
96086
+ effects.consume(code);
96087
+ return expectSlashFromContinuation;
96088
+ }
96089
+ effects.enter('magicBlockData');
96090
+ return captureData(code);
96091
+ }
96092
+ /**
96093
+ * Check for closing marker slash when coming from continuation.
96094
+ * If not a closing marker, create an empty data token and continue.
96095
+ */
96096
+ function expectSlashFromContinuation(code) {
96097
+ if (code === null) {
96098
+ effects.exit('magicBlockMarkerEnd');
96099
+ effects.exit('magicBlock');
96100
+ return nok(code);
96101
+ }
96102
+ if (markdownLineEnding(code)) {
96103
+ effects.exit('magicBlockMarkerEnd');
96104
+ return effects.check(syntax_nonLazyContinuation, continuationOkBeforeData, after)(code);
96105
+ }
96106
+ if (code === codes.slash) {
96107
+ effects.consume(code);
96108
+ return expectClosingB;
96109
+ }
96110
+ // Not a closing marker - this is data content
96111
+ // Exit marker and enter data
96112
+ effects.exit('magicBlockMarkerEnd');
96113
+ effects.enter('magicBlockData');
96114
+ // The [ was consumed by the marker, so we need to conceptually "have it" in our data
96115
+ // But since we already consumed it into the marker, we need a different approach
96116
+ // Re-classify: consume this character as data and continue
96117
+ if (code === codes.quotationMark)
96118
+ jsonState.inString = true;
96119
+ effects.consume(code);
96120
+ return captureData;
96121
+ }
96122
+ function expectSlash(code) {
96123
+ if (code === null) {
96124
+ effects.exit('magicBlockMarkerEnd');
96125
+ effects.exit('magicBlock');
96126
+ return nok(code);
96127
+ }
96128
+ // At line ending during marker parsing
96129
+ if (markdownLineEnding(code)) {
96130
+ effects.exit('magicBlockMarkerEnd');
96131
+ return effects.check(syntax_nonLazyContinuation, continuationOk, after)(code);
96132
+ }
96133
+ if (code !== codes.slash) {
96134
+ effects.exit('magicBlockMarkerEnd');
96135
+ effects.enter('magicBlockData');
96136
+ if (code === codes.quotationMark)
96137
+ jsonState.inString = true;
96138
+ effects.consume(code);
96139
+ return captureData;
96140
+ }
96141
+ effects.consume(code);
96142
+ return expectClosingB;
96143
+ }
96144
+ function expectClosingB(code) {
96145
+ if (code === null) {
96146
+ effects.exit('magicBlockMarkerEnd');
96147
+ effects.exit('magicBlock');
96148
+ return nok(code);
96149
+ }
96150
+ if (code !== codes.lowercaseB) {
96151
+ effects.exit('magicBlockMarkerEnd');
96152
+ effects.enter('magicBlockData');
96153
+ if (code === codes.quotationMark)
96154
+ jsonState.inString = true;
96155
+ effects.consume(code);
96156
+ return captureData;
96157
+ }
96158
+ effects.consume(code);
96159
+ return expectClosingL;
96160
+ }
96161
+ function expectClosingL(code) {
96162
+ if (code === null) {
96163
+ effects.exit('magicBlockMarkerEnd');
96164
+ effects.exit('magicBlock');
96165
+ return nok(code);
96166
+ }
96167
+ if (code !== codes.lowercaseL) {
96168
+ effects.exit('magicBlockMarkerEnd');
96169
+ effects.enter('magicBlockData');
96170
+ if (code === codes.quotationMark)
96171
+ jsonState.inString = true;
96172
+ effects.consume(code);
96173
+ return captureData;
96174
+ }
96175
+ effects.consume(code);
96176
+ return expectClosingO;
96177
+ }
96178
+ function expectClosingO(code) {
96179
+ if (code === null) {
96180
+ effects.exit('magicBlockMarkerEnd');
96181
+ effects.exit('magicBlock');
96182
+ return nok(code);
96183
+ }
96184
+ if (code !== codes.lowercaseO) {
96185
+ effects.exit('magicBlockMarkerEnd');
96186
+ effects.enter('magicBlockData');
96187
+ if (code === codes.quotationMark)
96188
+ jsonState.inString = true;
96189
+ effects.consume(code);
96190
+ return captureData;
96191
+ }
96192
+ effects.consume(code);
96193
+ return expectClosingC;
96194
+ }
96195
+ function expectClosingC(code) {
96196
+ if (code === null) {
96197
+ effects.exit('magicBlockMarkerEnd');
96198
+ effects.exit('magicBlock');
96199
+ return nok(code);
96200
+ }
96201
+ if (code !== codes.lowercaseC) {
96202
+ effects.exit('magicBlockMarkerEnd');
96203
+ effects.enter('magicBlockData');
96204
+ if (code === codes.quotationMark)
96205
+ jsonState.inString = true;
96206
+ effects.consume(code);
96207
+ return captureData;
96208
+ }
96209
+ effects.consume(code);
96210
+ return expectClosingK;
96211
+ }
96212
+ function expectClosingK(code) {
96213
+ if (code === null) {
96214
+ effects.exit('magicBlockMarkerEnd');
96215
+ effects.exit('magicBlock');
96216
+ return nok(code);
96217
+ }
96218
+ if (code !== codes.lowercaseK) {
96219
+ effects.exit('magicBlockMarkerEnd');
96220
+ effects.enter('magicBlockData');
96221
+ if (code === codes.quotationMark)
96222
+ jsonState.inString = true;
96223
+ effects.consume(code);
96224
+ return captureData;
96225
+ }
96226
+ effects.consume(code);
96227
+ return expectClosingBracket;
96228
+ }
96229
+ function expectClosingBracket(code) {
96230
+ if (code === null) {
96231
+ effects.exit('magicBlockMarkerEnd');
96232
+ effects.exit('magicBlock');
96233
+ return nok(code);
96234
+ }
96235
+ if (code !== codes.rightSquareBracket) {
96236
+ effects.exit('magicBlockMarkerEnd');
96237
+ effects.enter('magicBlockData');
96238
+ if (code === codes.quotationMark)
96239
+ jsonState.inString = true;
96240
+ effects.consume(code);
96241
+ return captureData;
96242
+ }
96243
+ effects.consume(code);
96244
+ effects.exit('magicBlockMarkerEnd');
96245
+ // Check for trailing whitespace on the same line
96246
+ return consumeTrailing;
96247
+ }
96248
+ /**
96249
+ * Consume trailing whitespace (spaces/tabs) on the same line after [/block].
96250
+ * For concrete flow constructs, we must end at eol/eof or fail.
96251
+ * Trailing whitespace is consumed; other content causes nok (text tokenizer handles it).
96252
+ */
96253
+ function consumeTrailing(code) {
96254
+ // End of file - done
96255
+ if (code === null) {
96256
+ effects.exit('magicBlock');
96257
+ return ok(code);
96258
+ }
96259
+ // Line ending - done
96260
+ if (markdownLineEnding(code)) {
96261
+ effects.exit('magicBlock');
96262
+ return ok(code);
96263
+ }
96264
+ // Space or tab - consume as trailing whitespace
96265
+ if (code === codes.space || code === codes.horizontalTab) {
96266
+ effects.enter('magicBlockTrailing');
96267
+ effects.consume(code);
96268
+ return consumeTrailingContinue;
96269
+ }
96270
+ // Any other character - fail flow tokenizer, let text tokenizer handle it
96271
+ effects.exit('magicBlock');
96272
+ return nok(code);
96273
+ }
96274
+ /**
96275
+ * Continue consuming trailing whitespace.
96276
+ */
96277
+ function consumeTrailingContinue(code) {
96278
+ // End of file - done
96279
+ if (code === null) {
96280
+ effects.exit('magicBlockTrailing');
96281
+ effects.exit('magicBlock');
96282
+ return ok(code);
96283
+ }
96284
+ // Line ending - done
96285
+ if (markdownLineEnding(code)) {
96286
+ effects.exit('magicBlockTrailing');
96287
+ effects.exit('magicBlock');
96288
+ return ok(code);
96289
+ }
96290
+ // More space or tab - keep consuming
96291
+ if (code === codes.space || code === codes.horizontalTab) {
96292
+ effects.consume(code);
96293
+ return consumeTrailingContinue;
96294
+ }
96295
+ // Non-whitespace after whitespace - fail flow tokenizer
96296
+ effects.exit('magicBlockTrailing');
96297
+ effects.exit('magicBlock');
96298
+ return nok(code);
96299
+ }
96300
+ /**
96301
+ * Called when we can't continue (lazy line or EOF) - exit the construct.
96302
+ */
96303
+ function after(code) {
96304
+ effects.exit('magicBlock');
96305
+ return nok(code);
96306
+ }
96307
+ }
96308
+ /**
96309
+ * Text tokenizer for single-line magic blocks only.
96310
+ * Used in inline contexts like list items and paragraphs.
96311
+ * Multiline blocks are handled by the flow tokenizer.
96312
+ */
96313
+ function tokenizeMagicBlockText(effects, ok, nok) {
96314
+ // State for tracking JSON content
96315
+ const jsonState = { escapeNext: false, inString: false };
96316
+ const blockTypeRef = { value: '' };
96317
+ let seenOpenBrace = false;
96318
+ // Create shared parsers for opening marker and type capture
96319
+ const typeParser = createTypeCaptureParser(effects, nok, beforeData, blockTypeRef);
96320
+ const openingMarkerParser = createOpeningMarkerParser(effects, nok, typeParser.first);
96321
+ // Success handler for closing marker - exits tokens and returns ok
96322
+ const closingSuccess = (code) => {
96323
+ effects.exit('magicBlockMarkerEnd');
96324
+ effects.exit('magicBlock');
96325
+ return ok(code);
96326
+ };
96327
+ // Mismatch handler - falls back to data capture
96328
+ const closingMismatch = (code) => {
96329
+ effects.exit('magicBlockMarkerEnd');
96330
+ effects.enter('magicBlockData');
96331
+ if (code === codes.quotationMark)
96332
+ jsonState.inString = true;
96333
+ effects.consume(code);
96334
+ return captureData;
96335
+ };
96336
+ // EOF handler
96337
+ const closingEof = (_code) => nok(_code);
96338
+ // Create closing marker parsers
96339
+ const closingFromBeforeData = createClosingMarkerParser(effects, closingSuccess, closingMismatch, closingEof, jsonState);
96340
+ const closingFromData = createClosingMarkerParser(effects, closingSuccess, closingMismatch, closingEof, jsonState);
96341
+ return start;
96342
+ function start(code) {
96343
+ if (code !== codes.leftSquareBracket)
96344
+ return nok(code);
96345
+ effects.enter('magicBlock');
96346
+ effects.enter('magicBlockMarkerStart');
96347
+ effects.consume(code);
96348
+ return openingMarkerParser;
96349
+ }
96350
+ /**
96351
+ * State before data content - handles line endings before entering data token.
96352
+ * Whitespace before '{' is allowed.
96353
+ */
96354
+ function beforeData(code) {
96355
+ // Fail on EOF - magic block must be closed
96356
+ if (code === null) {
96357
+ return nok(code);
96358
+ }
96359
+ // Handle line endings before any data - consume them without entering data token
96360
+ if (markdownLineEnding(code)) {
96361
+ effects.enter('magicBlockLineEnding');
96362
+ effects.consume(code);
96363
+ effects.exit('magicBlockLineEnding');
96364
+ return beforeData;
96365
+ }
96366
+ // Check for closing marker directly (without entering data)
96367
+ if (code === codes.leftSquareBracket) {
96368
+ effects.enter('magicBlockMarkerEnd');
96369
+ effects.consume(code);
96370
+ return closingFromBeforeData.expectSlash;
96371
+ }
96372
+ // If we've already seen the opening brace, just continue capturing data
96373
+ if (seenOpenBrace) {
96374
+ effects.enter('magicBlockData');
96375
+ return captureData(code);
96376
+ }
96377
+ // Skip whitespace (spaces/tabs) before the data
96378
+ if (code === codes.space || code === codes.horizontalTab) {
96379
+ return beforeDataWhitespace(code);
96380
+ }
96381
+ // Data must start with '{' for valid JSON
96382
+ if (code !== codes.leftCurlyBrace) {
96383
+ return nok(code);
96384
+ }
96385
+ // We have '{' - enter data token and start capturing
96386
+ seenOpenBrace = true;
96387
+ effects.enter('magicBlockData');
96388
+ return captureData(code);
96389
+ }
96390
+ /**
96391
+ * Consume whitespace before the data.
96392
+ */
96393
+ function beforeDataWhitespace(code) {
96394
+ if (code === null) {
96395
+ return nok(code);
96396
+ }
96397
+ if (markdownLineEnding(code)) {
96398
+ effects.enter('magicBlockLineEnding');
96399
+ effects.consume(code);
96400
+ effects.exit('magicBlockLineEnding');
96401
+ return beforeData;
96402
+ }
96403
+ if (code === codes.space || code === codes.horizontalTab) {
96404
+ effects.enter('magicBlockData');
96405
+ effects.consume(code);
96406
+ return beforeDataWhitespaceContinue;
96407
+ }
96408
+ if (code === codes.leftCurlyBrace) {
96409
+ seenOpenBrace = true;
96410
+ effects.enter('magicBlockData');
96411
+ return captureData(code);
96412
+ }
96413
+ return nok(code);
96414
+ }
96415
+ /**
96416
+ * Continue consuming whitespace or validate the opening brace.
96417
+ */
96418
+ function beforeDataWhitespaceContinue(code) {
96419
+ if (code === null) {
96420
+ effects.exit('magicBlockData');
96421
+ return nok(code);
96422
+ }
96423
+ if (markdownLineEnding(code)) {
96424
+ effects.exit('magicBlockData');
96425
+ effects.enter('magicBlockLineEnding');
96426
+ effects.consume(code);
96427
+ effects.exit('magicBlockLineEnding');
96428
+ return beforeData;
96429
+ }
96430
+ if (code === codes.space || code === codes.horizontalTab) {
96431
+ effects.consume(code);
96432
+ return beforeDataWhitespaceContinue;
96433
+ }
96434
+ if (code === codes.leftCurlyBrace) {
96435
+ seenOpenBrace = true;
96436
+ return captureData(code);
96437
+ }
96438
+ effects.exit('magicBlockData');
96439
+ return nok(code);
96440
+ }
96441
+ function captureData(code) {
96442
+ // Fail on EOF - magic block must be closed
96443
+ if (code === null) {
96444
+ effects.exit('magicBlockData');
96445
+ return nok(code);
96446
+ }
96447
+ // Handle multiline magic blocks within text/paragraphs
96448
+ // Exit data, consume line ending with proper token, then re-enter data
96449
+ if (markdownLineEnding(code)) {
96450
+ effects.exit('magicBlockData');
96451
+ effects.enter('magicBlockLineEnding');
96452
+ effects.consume(code);
96453
+ effects.exit('magicBlockLineEnding');
96454
+ return beforeData; // Go back to beforeData to handle potential empty lines
96455
+ }
96456
+ if (jsonState.escapeNext) {
96457
+ jsonState.escapeNext = false;
96458
+ effects.consume(code);
96459
+ return captureData;
96460
+ }
96461
+ if (jsonState.inString) {
96462
+ if (code === codes.backslash) {
96463
+ jsonState.escapeNext = true;
96464
+ effects.consume(code);
96465
+ return captureData;
96466
+ }
96467
+ if (code === codes.quotationMark) {
96468
+ jsonState.inString = false;
96469
+ }
96470
+ effects.consume(code);
96471
+ return captureData;
96472
+ }
96473
+ if (code === codes.quotationMark) {
96474
+ jsonState.inString = true;
96475
+ effects.consume(code);
96476
+ return captureData;
96477
+ }
96478
+ if (code === codes.leftSquareBracket) {
96479
+ effects.exit('magicBlockData');
96480
+ effects.enter('magicBlockMarkerEnd');
96481
+ effects.consume(code);
96482
+ return closingFromData.expectSlash;
96483
+ }
96484
+ effects.consume(code);
96485
+ return captureData;
96486
+ }
95097
96487
  }
96488
+ /* harmony default export */ const syntax = ((/* unused pure expression or super */ null && (magicBlock)));
96489
+
96490
+ ;// ./lib/micromark/magic-block/index.ts
96491
+ /**
96492
+ * Micromark extension for magic block syntax.
96493
+ *
96494
+ * Usage:
96495
+ * ```ts
96496
+ * import { magicBlock } from './lib/micromark/magic-block';
96497
+ *
96498
+ * const processor = unified()
96499
+ * .data('micromarkExtensions', [magicBlock()])
96500
+ * ```
96501
+ */
96502
+
95098
96503
 
95099
96504
  ;// ./lib/utils/mdxish/mdxish-load-components.ts
95100
96505
 
@@ -95161,44 +96566,52 @@ function loadComponents() {
95161
96566
 
95162
96567
 
95163
96568
 
96569
+
96570
+
95164
96571
 
95165
96572
 
95166
96573
 
95167
96574
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
95168
96575
  function mdxishAstProcessor(mdContent, opts = {}) {
95169
- const { components: userComponents = {}, jsxContext = {}, useTailwind } = opts;
96576
+ const { components: userComponents = {}, jsxContext = {}, newEditorTypes = false, useTailwind } = opts;
95170
96577
  const components = {
95171
96578
  ...loadComponents(),
95172
96579
  ...userComponents,
95173
96580
  };
96581
+ // Build set of known component names for snake_case filtering
96582
+ const knownComponents = new Set(Object.keys(components));
95174
96583
  // Preprocessing pipeline: Transform content to be parser-ready
95175
- // Step 1: Extract legacy magic blocks
95176
- const { replaced: contentAfterMagicBlocks, blocks } = extractMagicBlocks(mdContent);
95177
- // Step 2: Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
95178
- const contentAfterTableNormalization = normalizeTableSeparator(contentAfterMagicBlocks);
95179
- // Step 3: Evaluate JSX expressions in attributes
96584
+ // Step 1: Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
96585
+ const contentAfterTableNormalization = normalizeTableSeparator(mdContent);
96586
+ // Step 2: Evaluate JSX expressions in attributes
95180
96587
  const contentAfterJSXEvaluation = preprocessJSXExpressions(contentAfterTableNormalization, jsxContext);
95181
- // Step 4: Replace snake_case component names with parser-safe placeholders
95182
- // (e.g., <Snake_case /> <MDXishSnakeCase0 /> which will be restored after parsing)
95183
- const { content: parserReadyContent, mapping: snakeCaseMapping } = processSnakeCaseComponent(contentAfterJSXEvaluation);
96588
+ // Step 3: Replace snake_case component names with parser-safe placeholders
96589
+ const { content: parserReadyContent, mapping: snakeCaseMapping } = processSnakeCaseComponent(contentAfterJSXEvaluation, { knownComponents });
95184
96590
  // Create string map for tailwind transformer
95185
96591
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
95186
96592
  acc[key] = String(value);
95187
96593
  return acc;
95188
96594
  }, {});
96595
+ // Get mdxExpression extension and remove its flow construct to prevent
96596
+ // `{...}` from interrupting paragraphs (which breaks multiline magic blocks)
96597
+ const mdxExprExt = mdxExpression({ allowEmpty: true });
96598
+ const mdxExprTextOnly = {
96599
+ text: mdxExprExt.text,
96600
+ };
95189
96601
  const processor = unified()
95190
- .data('micromarkExtensions', [mdxExpression({ allowEmpty: true })]) // Parse inline JSX expressions as AST nodes for later evaluation
95191
- .data('fromMarkdownExtensions', [mdxExpressionFromMarkdown()])
96602
+ .data('micromarkExtensions', [magicBlock(), mdxExprTextOnly])
96603
+ .data('fromMarkdownExtensions', [magicBlockFromMarkdown(), mdxExpressionFromMarkdown()])
95192
96604
  .use(remarkParse)
95193
96605
  .use(remarkFrontmatter)
95194
96606
  .use(normalize_malformed_md_syntax)
95195
- .use(mdxish_magic_blocks, { blocks })
96607
+ .use(magic_block_transformer)
95196
96608
  .use(transform_images, { isMdxish: true })
95197
96609
  .use(defaultTransformers)
95198
96610
  .use(mdxish_component_blocks)
95199
96611
  .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
95200
96612
  .use(mdxish_tables)
95201
96613
  .use(mdxish_html_blocks)
96614
+ .use(newEditorTypes ? mdxish_jsx_to_mdast : undefined) // Convert JSX elements to MDAST types
95202
96615
  .use(evaluate_expressions, { context: jsxContext }) // Evaluate MDX expressions using jsxContext
95203
96616
  .use(variables_text) // Parse {user.*} patterns from text (can't rely on remarkMdx)
95204
96617
  .use(useTailwind ? transform_tailwind : undefined, { components: tempComponentsMap })
@@ -95217,10 +96630,14 @@ function mdxishAstProcessor(mdContent, opts = {}) {
95217
96630
  * Converts an Mdast to a Markdown string.
95218
96631
  */
95219
96632
  function mdxishMdastToMd(mdast) {
95220
- const md = unified().use(remarkGfm).use(processor_compile).use(remarkStringify, {
96633
+ const md = unified()
96634
+ .use(remarkGfm)
96635
+ .use(mdxishCompilers)
96636
+ .use(remarkStringify, {
95221
96637
  bullet: '-',
95222
96638
  emphasis: '_',
95223
- }).stringify(mdast);
96639
+ })
96640
+ .stringify(mdast);
95224
96641
  return md;
95225
96642
  }
95226
96643
  /**
@@ -95716,11 +97133,14 @@ const tags = (doc) => {
95716
97133
 
95717
97134
 
95718
97135
 
97136
+
95719
97137
  const mdxishTags_tags = (doc) => {
95720
- const { replaced: sanitizedDoc } = extractMagicBlocks(doc);
95721
97138
  const set = new Set();
95722
- const processor = remark().use(mdxish_component_blocks);
95723
- const tree = processor.parse(sanitizedDoc);
97139
+ const processor = remark()
97140
+ .data('micromarkExtensions', [magicBlock()])
97141
+ .data('fromMarkdownExtensions', [magicBlockFromMarkdown()])
97142
+ .use(mdxish_component_blocks);
97143
+ const tree = processor.parse(doc);
95724
97144
  visit(processor.runSync(tree), isMDXElement, (node) => {
95725
97145
  if (node.name?.match(/^[A-Z]/)) {
95726
97146
  set.add(node.name);
@@ -95741,12 +97161,15 @@ const mdxishTags_tags = (doc) => {
95741
97161
 
95742
97162
 
95743
97163
 
97164
+
95744
97165
  /**
95745
97166
  * Removes Markdown and MDX comments.
95746
97167
  */
95747
97168
  async function stripComments(doc, { mdx, mdxish } = {}) {
95748
- const { replaced, blocks } = extractMagicBlocks(doc);
95749
- const processor = unified();
97169
+ const processor = unified()
97170
+ .data('micromarkExtensions', [magicBlock()])
97171
+ .data('fromMarkdownExtensions', [magicBlockFromMarkdown()])
97172
+ .data('toMarkdownExtensions', [magicBlockToMarkdown()]);
95750
97173
  // we still require these two extensions because:
95751
97174
  // 1. we can rely on remarkMdx to parse MDXish
95752
97175
  // 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
@@ -95788,10 +97211,8 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
95788
97211
  },
95789
97212
  ],
95790
97213
  });
95791
- const file = await processor.process(replaced);
95792
- const stringified = String(file).trim();
95793
- const restored = restoreMagicBlocks(stringified, blocks);
95794
- return restored;
97214
+ const file = await processor.process(doc);
97215
+ return String(file).trim();
95795
97216
  }
95796
97217
  /* harmony default export */ const lib_stripComments = (stripComments);
95797
97218