@readme/markdown 11.12.0 → 11.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.node.js CHANGED
@@ -90788,77 +90788,197 @@ const mdxToHast = () => tree => {
90788
90788
  ;// ./processor/transform/mdxish/mdxish-component-blocks.ts
90789
90789
 
90790
90790
 
90791
- const tagPattern = /^<([A-Z][A-Za-z0-9_]*)([^>]*?)(\/?)>([\s\S]*)?$/;
90792
- const attributePattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
90791
+ const pascalCaseTagPattern = /^<([A-Z][A-Za-z0-9_]*)([^>]*?)(\/?)>([\s\S]*)?$/;
90792
+ const tagAttributePattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
90793
+ /**
90794
+ * Maximum number of siblings to scan forward when looking for a closing tag
90795
+ * to avoid scanning too far and degrading performance
90796
+ */
90797
+ const MAX_LOOKAHEAD = 30;
90798
+ /**
90799
+ * Tags that have dedicated transformers and should NOT be handled by this plugin.
90800
+ * These components have special parsing requirements that the generic component
90801
+ * block transformer cannot handle correctly.
90802
+ */
90803
+ const EXCLUDED_TAGS = new Set(['HTMLBlock', 'Table']);
90793
90804
  const inlineMdProcessor = unified().use(remarkParse);
90794
90805
  const isClosingTag = (value, tag) => value.trim() === `</${tag}>`;
90795
- // Remove matching closing tag from paragraph children; returns updated paragraph and removal status
90796
- const stripClosingFromParagraph = (node, tag) => {
90797
- if (!Array.isArray(node.children))
90798
- return { paragraph: node, found: false };
90799
- const children = [...node.children];
90800
- const closingIndex = children.findIndex(child => child.type === 'html' && isClosingTag(child.value || '', tag));
90801
- if (closingIndex === -1)
90802
- return { paragraph: node, found: false };
90803
- children.splice(closingIndex, 1);
90804
- return {
90805
- paragraph: { ...node, children },
90806
- found: true,
90807
- };
90808
- };
90809
- // Replace two child nodes (opening HTML tag + paragraph) with a single replacement node
90810
- const replaceChild = (parent, index, replacement) => {
90811
- parent.children.splice(index, 2, replacement);
90812
- };
90813
- // Parse markdown inside a component's inline content into mdast children.
90806
+ /**
90807
+ * Parse markdown content into mdast children nodes.
90808
+ */
90814
90809
  const parseMdChildren = (value) => {
90815
90810
  const parsed = inlineMdProcessor.parse(value);
90816
90811
  return parsed.children || [];
90817
90812
  };
90818
- // Convert raw attribute string into mdxJsxAttribute entries (strings only; no expressions).
90819
- // Handles both key-value attributes (theme="info") and boolean attributes (empty).
90813
+ /**
90814
+ * Convert raw attribute string into mdxJsxAttribute entries.
90815
+ * Handles both key-value attributes (theme="info") and boolean attributes (empty).
90816
+ */
90820
90817
  const parseAttributes = (raw) => {
90821
90818
  const attributes = [];
90822
90819
  const attrString = raw.trim();
90823
90820
  if (!attrString)
90824
90821
  return attributes;
90825
- // Reset regex lastIndex since it's a global regex that maintains state
90826
- attributePattern.lastIndex = 0;
90827
- let match = attributePattern.exec(attrString);
90822
+ tagAttributePattern.lastIndex = 0;
90823
+ let match = tagAttributePattern.exec(attrString);
90828
90824
  while (match !== null) {
90829
90825
  const [, attrName, attrValue] = match;
90830
- // Boolean attribute (no value) -> set to null
90831
- // Attribute with value -> clean and set string value
90832
- // Note: Attribute value types can't directly be numbers & booleans. String, nulls, undefined are supported.
90833
90826
  const value = attrValue ? attrValue.replace(/^['"]|['"]$/g, '') : null;
90834
- attributes.push({
90835
- type: 'mdxJsxAttribute',
90836
- name: attrName,
90837
- value,
90838
- });
90839
- match = attributePattern.exec(attrString);
90827
+ attributes.push({ type: 'mdxJsxAttribute', name: attrName, value });
90828
+ match = tagAttributePattern.exec(attrString);
90840
90829
  }
90841
90830
  return attributes;
90842
90831
  };
90843
- // Parse a single HTML-ish tag string into tag name, attributes, self-closing flag, and inline content.
90832
+ /**
90833
+ * Parse an HTML tag string into structured data.
90834
+ */
90844
90835
  const parseTag = (value) => {
90845
- const match = value.match(tagPattern);
90836
+ const match = value.match(pascalCaseTagPattern);
90846
90837
  if (!match)
90847
90838
  return null;
90848
- const [, tag, attrString = '', selfClosing = '', content = ''] = match;
90849
- const attributes = parseAttributes(attrString);
90839
+ const [, tag, attrString = '', selfClosing = '', contentAfterTag = ''] = match;
90850
90840
  return {
90851
90841
  tag,
90852
- attributes,
90842
+ attributes: parseAttributes(attrString),
90853
90843
  selfClosing: !!selfClosing,
90854
- content,
90844
+ contentAfterTag,
90855
90845
  };
90856
90846
  };
90857
- // Transform PascalCase HTML blocks into mdxJsxFlowElement nodes.
90858
- // Remark parses unknown tags as raw HTML; we rewrite them so MDX/rehype treats them as components.
90847
+ /**
90848
+ * Create an MdxJsxFlowElement node from component data.
90849
+ */
90850
+ const createComponentNode = ({ tag, attributes, children, startPosition, endPosition }) => ({
90851
+ type: 'mdxJsxFlowElement',
90852
+ name: tag,
90853
+ attributes,
90854
+ children,
90855
+ position: {
90856
+ start: startPosition?.start,
90857
+ end: endPosition?.end ?? startPosition?.end,
90858
+ },
90859
+ });
90860
+ /**
90861
+ * Remove a closing tag from a paragraph's children and return the updated paragraph.
90862
+ */
90863
+ const stripClosingTagFromParagraph = (node, tag) => {
90864
+ if (!Array.isArray(node.children))
90865
+ return { paragraph: node, found: false };
90866
+ const children = [...node.children];
90867
+ const closingIndex = children.findIndex(child => child.type === 'html' && isClosingTag(child.value || '', tag));
90868
+ if (closingIndex === -1)
90869
+ return { paragraph: node, found: false };
90870
+ children.splice(closingIndex, 1);
90871
+ return { paragraph: { ...node, children }, found: true };
90872
+ };
90873
+ /**
90874
+ * Scan forward through siblings to find a closing tag.
90875
+ * Handles:
90876
+ * - Exact match HTML siblings (e.g., `</Tag>`)
90877
+ * - HTML siblings with embedded closing tag (e.g., `...\n</Tag>`)
90878
+ * - Paragraph siblings containing the closing tag as a child
90879
+ *
90880
+ * Returns null if not found within MAX_LOOKAHEAD siblings
90881
+ */
90882
+ const scanForClosingTag = (parent, startIndex, tag) => {
90883
+ const closingTagStr = `</${tag}>`;
90884
+ const maxIndex = Math.min(startIndex + MAX_LOOKAHEAD, parent.children.length);
90885
+ let i = startIndex + 1;
90886
+ for (; i < maxIndex; i += 1) {
90887
+ const sibling = parent.children[i];
90888
+ // Check HTML siblings
90889
+ if (sibling.type === 'html') {
90890
+ const siblingValue = sibling.value || '';
90891
+ // Exact match (standalone closing tag)
90892
+ if (isClosingTag(siblingValue, tag)) {
90893
+ return { closingIndex: i, extraClosingChildren: [] };
90894
+ }
90895
+ // Embedded closing tag (closing tag at end of HTML block content)
90896
+ if (siblingValue.includes(closingTagStr)) {
90897
+ const contentBeforeClose = siblingValue.substring(0, siblingValue.lastIndexOf(closingTagStr)).trim();
90898
+ const extraChildren = contentBeforeClose
90899
+ ? parseMdChildren(contentBeforeClose)
90900
+ : [];
90901
+ return { closingIndex: i, extraClosingChildren: extraChildren };
90902
+ }
90903
+ }
90904
+ // Check paragraph siblings
90905
+ if (sibling.type === 'paragraph') {
90906
+ const { paragraph, found } = stripClosingTagFromParagraph(sibling, tag);
90907
+ if (found) {
90908
+ return { closingIndex: i, extraClosingChildren: [], strippedParagraph: paragraph };
90909
+ }
90910
+ }
90911
+ }
90912
+ if (i < parent.children.length) {
90913
+ // eslint-disable-next-line no-console
90914
+ console.warn(`Closing tag </${tag}> not found within ${MAX_LOOKAHEAD} siblings, stopping scan`);
90915
+ }
90916
+ return null;
90917
+ };
90918
+ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
90919
+ parent.children.splice(index, 1, mdxNode);
90920
+ };
90921
+ /**
90922
+ * Transform PascalCase HTML nodes into mdxJsxFlowElement nodes.
90923
+ *
90924
+ * Remark parses unknown/custom component tags as raw HTML nodes.
90925
+ * These are the custom readme MDX syntax for components.
90926
+ * This transformer identifies these patterns and converts them to proper MDX JSX elements so they
90927
+ * can be accurately recognized and rendered later with their component definition code.
90928
+ * Though for some tags, we need to handle them specially
90929
+ *
90930
+ * ## Supported HTML Structures
90931
+ *
90932
+ * ### 1. Self-closing tags
90933
+ * ```
90934
+ * <Component />
90935
+ * ```
90936
+ * Parsed as: `html: "<Component />"`
90937
+ *
90938
+ * ### 2. Self-contained blocks (entire component in single HTML node)
90939
+ * ```
90940
+ * <Button>Click me</Button>
90941
+ * ```
90942
+ * ```
90943
+ * <Component>
90944
+ * <h2>Title</h2>
90945
+ * <p>Content</p>
90946
+ * </Component>
90947
+ * ```
90948
+ * Parsed as: `html: "<Component>\n <h2>Title</h2>\n <p>Content</p>\n</Component>"`
90949
+ * The opening tag, content, and closing tag are all captured in one HTML node.
90950
+ *
90951
+ * ### 3. Multi-sibling components (closing tag in a following sibling)
90952
+ * Handles various structures where the closing tag is in a later sibling, such as:
90953
+ *
90954
+ * #### 3a. Block components (closing tag in sibling paragraph)
90955
+ * ```
90956
+ * <Callout>
90957
+ * Some **markdown** content
90958
+ * </Callout>
90959
+ * ```
90960
+ *
90961
+ * #### 3b. Multi-paragraph components (closing tag several siblings away)
90962
+ * ```
90963
+ * <Callout>
90964
+ *
90965
+ * First paragraph
90966
+ *
90967
+ * Second paragraph
90968
+ * </Callout>
90969
+ * ```
90970
+ *
90971
+ * #### 3c. Nested components split by blank lines (closing tag embedded in HTML sibling)
90972
+ * ```
90973
+ * <Outer>
90974
+ * <Inner>content</Inner>
90975
+ *
90976
+ * <Inner>content</Inner>
90977
+ * </Outer>
90978
+ * ```
90979
+ */
90859
90980
  const mdxishComponentBlocks = () => tree => {
90860
90981
  const stack = [tree];
90861
- // Process children depth-first, rewriting opening/closing component HTML pairs
90862
90982
  const processChildNode = (parent, index) => {
90863
90983
  const node = parent.children[index];
90864
90984
  if (!node)
@@ -90866,46 +90986,66 @@ const mdxishComponentBlocks = () => tree => {
90866
90986
  if ('children' in node && Array.isArray(node.children)) {
90867
90987
  stack.push(node);
90868
90988
  }
90989
+ // Only visit HTML nodes with an actual html tag
90869
90990
  const value = node.value;
90870
90991
  if (node.type !== 'html' || typeof value !== 'string')
90871
90992
  return;
90872
90993
  const parsed = parseTag(value);
90873
90994
  if (!parsed)
90874
90995
  return;
90875
- const { tag, attributes, selfClosing, content = '' } = parsed;
90876
- const extraChildren = content ? parseMdChildren(content.trimStart()) : [];
90996
+ const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
90997
+ // Skip tags that have dedicated transformers
90998
+ if (EXCLUDED_TAGS.has(tag))
90999
+ return;
91000
+ const closingTagStr = `</${tag}>`;
91001
+ // Case 1: Self-closing tag
90877
91002
  if (selfClosing) {
90878
- const componentNode = {
90879
- type: 'mdxJsxFlowElement',
90880
- name: tag,
91003
+ const componentNode = createComponentNode({
91004
+ tag,
90881
91005
  attributes,
90882
91006
  children: [],
90883
- position: node.position,
90884
- };
90885
- parent.children.splice(index, 1, componentNode);
91007
+ startPosition: node.position,
91008
+ });
91009
+ substituteNodeWithMdxNode(parent, index, componentNode);
90886
91010
  return;
90887
91011
  }
90888
- const next = parent.children[index + 1];
90889
- if (!next || next.type !== 'paragraph')
91012
+ // Case 2: Self-contained block (closing tag in content)
91013
+ if (contentAfterTag.includes(closingTagStr)) {
91014
+ const componentInnerContent = contentAfterTag.substring(0, contentAfterTag.lastIndexOf(closingTagStr)).trim();
91015
+ const componentNode = createComponentNode({
91016
+ tag,
91017
+ attributes,
91018
+ children: componentInnerContent ? parseMdChildren(componentInnerContent) : [],
91019
+ startPosition: node.position,
91020
+ });
91021
+ substituteNodeWithMdxNode(parent, index, componentNode);
90890
91022
  return;
90891
- const { paragraph, found } = stripClosingFromParagraph(next, tag);
90892
- if (!found)
91023
+ }
91024
+ // Case 3: Multi-sibling component (closing tag in a following sibling)
91025
+ // Scans forward through siblings to find closing tag in HTML or paragraph nodes
91026
+ const scanResult = scanForClosingTag(parent, index, tag);
91027
+ if (!scanResult)
90893
91028
  return;
90894
- const componentNode = {
90895
- type: 'mdxJsxFlowElement',
90896
- name: tag,
91029
+ const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
91030
+ const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
91031
+ // Collect all intermediate siblings between opening tag and closing tag
91032
+ const intermediateChildren = parent.children.slice(index + 1, closingIndex);
91033
+ // For paragraph siblings, include the paragraph's children (with closing tag stripped)
91034
+ // For HTML siblings, include any content parsed from before the closing tag
91035
+ const closingChildren = strippedParagraph
91036
+ ? strippedParagraph.children
91037
+ : extraClosingChildren;
91038
+ const componentNode = createComponentNode({
91039
+ tag,
90897
91040
  attributes,
90898
- children: [
90899
- ...extraChildren,
90900
- ...paragraph.children,
90901
- ],
90902
- position: {
90903
- start: node.position?.start,
90904
- end: next.position?.end,
90905
- },
90906
- };
90907
- replaceChild(parent, index, componentNode);
91041
+ children: [...extraChildren, ...intermediateChildren, ...closingChildren],
91042
+ startPosition: node.position,
91043
+ endPosition: parent.children[closingIndex]?.position,
91044
+ });
91045
+ // Remove all nodes from opening tag to closing tag (inclusive) and replace with component node
91046
+ parent.children.splice(index, closingIndex - index + 1, componentNode);
90908
91047
  };
91048
+ // Travel the tree depth-first
90909
91049
  while (stack.length) {
90910
91050
  const parent = stack.pop();
90911
91051
  if (parent?.children) {
@@ -113049,8 +113189,11 @@ function smartCamelCase(str) {
113049
113189
  }
113050
113190
  // Sort by length (longest first) to prevent shorter matches (e.g., "column" in "columns")
113051
113191
  const sortedBoundaries = [...allBoundaries].sort((a, b) => b.length - a.length);
113192
+ // Use case-sensitive matching ('g' not 'gi') so that once a letter is
113193
+ // capitalized by a longer boundary, shorter boundaries won't re-match it.
113194
+ // This prevents issues like 'iconcolor' becoming 'iconColOr' instead of 'iconColor'.
113052
113195
  return sortedBoundaries.reduce((res, word) => {
113053
- const regex = new RegExp(`(${word})([a-z])`, 'gi');
113196
+ const regex = new RegExp(`(${word})([a-z])`, 'g');
113054
113197
  return res.replace(regex, (_, prefix, nextChar) => prefix.toLowerCase() + nextChar.toUpperCase());
113055
113198
  }, str);
113056
113199
  }
@@ -113176,7 +113319,21 @@ const htmlBlockHandler = (_state, node) => {
113176
113319
  children: [],
113177
113320
  };
113178
113321
  };
113322
+ // Convert embed magic blocks to Embed components
113323
+ const embedHandler = (state, node) => {
113324
+ // Assert to get the minimum properties we need
113325
+ const { data } = node;
113326
+ return {
113327
+ type: 'element',
113328
+ // To differentiate between regular embeds and magic block embeds,
113329
+ // magic block embeds have a certain hName
113330
+ tagName: data?.hName === NodeTypes.embedBlock ? 'Embed' : 'embed',
113331
+ properties: data?.hProperties,
113332
+ children: state.all(node),
113333
+ };
113334
+ };
113179
113335
  const mdxComponentHandlers = {
113336
+ embed: embedHandler,
113180
113337
  mdxFlowExpression: mdxExpressionHandler,
113181
113338
  mdxJsxFlowElement: mdxJsxElementHandler,
113182
113339
  mdxJsxTextElement: mdxJsxElementHandler,
@@ -114021,14 +114178,17 @@ function parseMagicBlock(raw, options = {}) {
114021
114178
  else {
114022
114179
  children.push(...titleBlocks, ...bodyBlocks);
114023
114180
  }
114181
+ // If there is no title or title is empty
114182
+ const empty = !titleBlocks.length || !titleBlocks[0].children[0]?.value;
114024
114183
  // Create mdxJsxFlowElement directly for mdxish
114025
114184
  const calloutElement = {
114026
114185
  type: 'mdxJsxFlowElement',
114027
114186
  name: 'Callout',
114028
- attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default' }, [
114187
+ attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default', empty }, [
114029
114188
  'icon',
114030
114189
  'theme',
114031
114190
  'type',
114191
+ 'empty',
114032
114192
  ]),
114033
114193
  children: children,
114034
114194
  };
@@ -114086,9 +114246,9 @@ function parseMagicBlock(raw, options = {}) {
114086
114246
  return [
114087
114247
  wrapPinnedBlocks({
114088
114248
  children: [
114089
- { children: [{ type: 'text', value: title || null }], title: embedJson.provider, type: 'link', url },
114249
+ { children: [{ type: 'text', value: title || '' }], title: embedJson.provider, type: 'link', url },
114090
114250
  ],
114091
- data: { hName: 'rdme-embed', hProperties: { ...embedJson, href: url, html, title, url } },
114251
+ data: { hName: 'embed-block', hProperties: { ...embedJson, href: url, html, title, url } },
114092
114252
  type: 'embed',
114093
114253
  }, json),
114094
114254
  ];
@@ -114418,6 +114578,45 @@ const restoreSnakeCaseComponentNames = (options) => {
114418
114578
  };
114419
114579
  /* harmony default export */ const restore_snake_case_component_name = (restoreSnakeCaseComponentNames);
114420
114580
 
114581
+ ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
114582
+
114583
+ // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
114584
+ const TEMP_TRUE_BOOLEAN_VALUE = 'readme-this-is-a-temporary-boolean-attribute-\uE000';
114585
+ const TEMP_FALSE_BOOLEAN_VALUE = 'readme-this-is-a-temporary-boolean-attribute-\uE001';
114586
+ /**
114587
+ * Preserves boolean properties when passed to rehypeRaw because
114588
+ * rehypeRaw converts boolean properties in nodes to strings (e.g. true -> ""),
114589
+ * which can change the truthiness of the property. Hence we need to preserve the boolean properties.
114590
+ */
114591
+ const preserveBooleanProperties = () => tree => {
114592
+ visit(tree, 'element', (node) => {
114593
+ if (!node.properties)
114594
+ return;
114595
+ Object.entries(node.properties).forEach(([key, value]) => {
114596
+ if (typeof value === 'boolean') {
114597
+ node.properties[key] = value ? TEMP_TRUE_BOOLEAN_VALUE : TEMP_FALSE_BOOLEAN_VALUE;
114598
+ }
114599
+ });
114600
+ });
114601
+ return tree;
114602
+ };
114603
+ const restoreBooleanProperties = () => tree => {
114604
+ visit(tree, 'element', (node) => {
114605
+ if (!node.properties)
114606
+ return;
114607
+ Object.entries(node.properties).forEach(([key, value]) => {
114608
+ if (value === TEMP_TRUE_BOOLEAN_VALUE) {
114609
+ node.properties[key] = true;
114610
+ }
114611
+ else if (value === TEMP_FALSE_BOOLEAN_VALUE) {
114612
+ node.properties[key] = false;
114613
+ }
114614
+ });
114615
+ });
114616
+ return tree;
114617
+ };
114618
+
114619
+
114421
114620
  ;// ./processor/transform/mdxish/variables-text.ts
114422
114621
 
114423
114622
 
@@ -114445,6 +114644,8 @@ const variablesTextTransformer = () => tree => {
114445
114644
  if (parent.type === 'inlineCode')
114446
114645
  return;
114447
114646
  const text = node.value;
114647
+ if (typeof text !== 'string' || !text.trim())
114648
+ return;
114448
114649
  if (!text.includes('{user.') && !text.includes('{user['))
114449
114650
  return;
114450
114651
  const matches = [...text.matchAll(USER_VAR_REGEX)];
@@ -114597,6 +114798,7 @@ function loadComponents() {
114597
114798
 
114598
114799
 
114599
114800
 
114801
+
114600
114802
 
114601
114803
 
114602
114804
  const defaultTransformers = [callouts, code_tabs, gemoji_, transform_embeds];
@@ -114643,7 +114845,9 @@ function mdxish(mdContent, opts = {}) {
114643
114845
  .use(useTailwind ? transform_tailwind : undefined, { components: tempComponentsMap })
114644
114846
  .use(remarkGfm)
114645
114847
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
114848
+ .use(preserveBooleanProperties) // RehypeRaw converts boolean properties to empty strings
114646
114849
  .use(rehypeRaw, { passThrough: ['html-block'] })
114850
+ .use(restoreBooleanProperties)
114647
114851
  .use(rehypeSlug)
114648
114852
  .use(rehypeMdxishComponents, {
114649
114853
  components,