@readme/markdown 14.10.1 → 14.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.node.js CHANGED
@@ -19744,10 +19744,55 @@ const Columns = ({ children, layout = 'auto' }) => {
19744
19744
  };
19745
19745
  /* harmony default export */ const components_Columns = (Columns);
19746
19746
 
19747
+ ;// ./components/Embed/normalizeYouTubeUrl.ts
19748
+ // YouTube watch/share URLs (youtube.com/watch?v=ID, youtu.be/ID) set X-Frame-Options
19749
+ // and refuse to be framed. The player only allows embedding via youtube.com/embed/ID.
19750
+ const YOUTUBE_HOSTS = new Set(['youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtu.be']);
19751
+ // /embed/ accepts `start` (integer seconds), not the watch-page `t` value which may carry units (e.g. 596s, 1h2m3s).
19752
+ const toStartSeconds = (timestamp) => {
19753
+ const [, hours, minutes, seconds] = timestamp.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/) ?? [];
19754
+ const total = (Number(hours) || 0) * 3600 + (Number(minutes) || 0) * 60 + (Number(seconds) || 0);
19755
+ return total > 0 ? String(total) : null;
19756
+ };
19757
+ const extractVideoId = (parsed) => {
19758
+ if (parsed.hostname.endsWith('youtu.be'))
19759
+ return parsed.pathname.slice(1).split('/')[0] || null;
19760
+ if (parsed.pathname.startsWith('/watch'))
19761
+ return parsed.searchParams.get('v');
19762
+ if (parsed.pathname.startsWith('/shorts/'))
19763
+ return parsed.pathname.slice('/shorts/'.length).split('/')[0] || null;
19764
+ return null;
19765
+ };
19766
+ // Most youtube links we get from the browser are watch URLs, like https://www.youtube.com/watch?v=dQw4w9WgXcQ
19767
+ // These /watch URLs won't work in an iframe, so we need to convert them to the embed URL if iframe is used
19768
+ const normalizeYouTubeUrlToEmbedUrl = (url) => {
19769
+ let parsed;
19770
+ try {
19771
+ parsed = new URL(url);
19772
+ }
19773
+ catch {
19774
+ return url;
19775
+ }
19776
+ if (!YOUTUBE_HOSTS.has(parsed.hostname))
19777
+ return url;
19778
+ if (parsed.pathname.startsWith('/embed/'))
19779
+ return url;
19780
+ const videoId = extractVideoId(parsed);
19781
+ if (!videoId)
19782
+ return url;
19783
+ const embed = new URL(`https://www.youtube.com/embed/${videoId}`);
19784
+ const timestamp = parsed.searchParams.get('start') || parsed.searchParams.get('t');
19785
+ const start = timestamp ? toStartSeconds(timestamp) : null;
19786
+ if (start)
19787
+ embed.searchParams.set('start', start);
19788
+ return embed.toString();
19789
+ };
19790
+
19747
19791
  ;// ./components/Embed/index.tsx
19748
19792
  /* eslint-disable no-param-reassign */
19749
19793
  /* eslint-disable react/jsx-props-no-spreading */
19750
19794
 
19795
+
19751
19796
  const Favicon = ({ src, alt = 'favicon', ...attr }) => (external_react_default().createElement("img", { ...attr, alt: alt, height: "14", src: src, width: "14" }));
19752
19797
  const IFRAME_DERIVABLE_TYPES = new Set(['youtube', 'jsfiddle', 'pdf']);
19753
19798
  // HTML width/height attrs accept bare numbers (interpreted as px), but CSS does not, convert.
@@ -19781,7 +19826,8 @@ const Embed = ({ lazy = true, url, html, providerName, providerUrl, title, ifram
19781
19826
  // Fall back to a direct iframe for URL-derivable embed types when html is missing.
19782
19827
  const renderTypeAsIframe = !html && !explicitOptOut && url && typeOfEmbed && IFRAME_DERIVABLE_TYPES.has(typeOfEmbed);
19783
19828
  if (iframe || renderTypeAsIframe) {
19784
- return external_react_default().createElement("iframe", { ...spreadAttrs, src: url, style: iframeStyle, title: title });
19829
+ const src = normalizeYouTubeUrlToEmbedUrl(url);
19830
+ return external_react_default().createElement("iframe", { ...spreadAttrs, src: src, style: iframeStyle, title: title });
19785
19831
  }
19786
19832
  if (!providerUrl && url)
19787
19833
  providerUrl = new URL(url).hostname
@@ -120974,6 +121020,12 @@ const types_types = /** @type {const} */ ({
120974
121020
 
120975
121021
 
120976
121022
 
121023
+
121024
+ // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
121025
+ // section, …) always start a block, so they stay flow even with trailing
121026
+ // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
121027
+ // stay flow when nothing trails the close tag.
121028
+ const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
120977
121029
  const syntax_nonLazyContinuationStart = {
120978
121030
  tokenize: syntax_tokenizeNonLazyContinuationStart,
120979
121031
  partial: true,
@@ -121010,11 +121062,12 @@ const mdxComponentTextConstruct = {
121010
121062
  * lowercase HTML tags that carry at least one `{…}` attribute expression.
121011
121063
  * Multi-line, concrete, `afterClose` consumes the rest of the line.
121012
121064
  *
121013
- * **Text** — runs inside paragraphs / inline context. Claims *only* lowercase
121014
- * tags with brace attributes (PascalCase is intentionally flow-only, matching
121015
- * how ReadMe's custom components are authored). Aborts on line endings (inline
121016
- * constructs don't span lines) and exits immediately after `</tag>` so the
121017
- * paragraph's inline parser picks up the trailing text.
121065
+ * **Text** — runs inside paragraphs / inline context. Claims lowercase tags and
121066
+ * inline PascalCase components (`INLINE_COMPONENT_TAGS` Anchor, Glossary), both
121067
+ * gated on at least one `{…}` brace attribute. All other PascalCase stays
121068
+ * flow-only, matching how ReadMe's custom components are authored. Aborts on line
121069
+ * endings (inline constructs don't span lines) and exits immediately after
121070
+ * `</tag>` so the paragraph's inline parser picks up the trailing text.
121018
121071
  */
121019
121072
  function createTokenize(mode) {
121020
121073
  const isFlow = mode === 'flow';
@@ -121026,8 +121079,9 @@ function createTokenize(mode) {
121026
121079
  let closingTagName = '';
121027
121080
  // For lowercase tags we only want to claim the block if it uses JSX
121028
121081
  // attribute expression syntax (`attr={...}`). Plain HTML should fall
121029
- // through to CommonMark html-flow. Uppercase tags are always claimed
121030
- // (flow only PascalCase is not accepted in text mode).
121082
+ // through to CommonMark html-flow. Flow mode claims any PascalCase block
121083
+ // component; text mode claims only inline PascalCase components
121084
+ // (INLINE_COMPONENT_TAGS — Anchor, Glossary), also brace-gated.
121031
121085
  let isLowercaseTag = false;
121032
121086
  let sawBraceAttr = false;
121033
121087
  // Code span tracking
@@ -121038,6 +121092,9 @@ function createTokenize(mode) {
121038
121092
  let fenceLength = 0;
121039
121093
  let fenceCloseLength = 0;
121040
121094
  let atLineStart = false;
121095
+ // True once this construct consumes any line ending; lets `afterClose`
121096
+ // treat only single-line lowercase tags as inline candidates.
121097
+ let sawLineEnding = false;
121041
121098
  // Bail when the opener line has unmatched tag-like tokens in its body.
121042
121099
  // `<Foo>_<Bar>.csv` leaves opens > closes; matched shapes like
121043
121100
  // `<Callout>x <strong>y</strong>` balance to 0. Without this,
@@ -121188,13 +121245,13 @@ function createTokenize(mode) {
121188
121245
  }
121189
121246
  // ── Tag name parsing ───────────────────────────────────────────────────
121190
121247
  function tagNameFirst(code) {
121191
- // Uppercase A-Z → PascalCase MDX component. Flow-only PascalCase
121192
- // components are block elements in ReadMe's authoring model.
121248
+ // Uppercase A-Z → PascalCase MDX component. Flow mode claims block
121249
+ // components; text mode only claims inline components (Anchor, Glossary),
121250
+ // which is enforced once the full name is known in `tagNameRest`.
121193
121251
  if (code !== null && code >= codes.uppercaseA && code <= codes.uppercaseZ) {
121194
- if (!isFlow)
121195
- return nok(code);
121196
121252
  tagName = String.fromCharCode(code);
121197
121253
  isLowercaseTag = false;
121254
+ sawBraceAttr = false;
121198
121255
  effects.consume(code);
121199
121256
  return tagNameRest;
121200
121257
  }
@@ -121222,10 +121279,18 @@ function createTokenize(mode) {
121222
121279
  effects.consume(code);
121223
121280
  return tagNameRest;
121224
121281
  }
121225
- // Tag name complete — check exclusions
121226
- if (TOKENIZER_MDX_COMPONENT_EXCLUDED_TAGS.has(tagName)) {
121282
+ // Tag name complete — decide whether this tokenizer claims the tag.
121283
+ // Three cases: lowercase tags are always candidates (brace-gated later in
121284
+ // `afterOpenTagName`); flow-mode PascalCase claims any block component
121285
+ // except those with a dedicated tokenizer; text-mode PascalCase claims
121286
+ // only inline components (Anchor, Glossary).
121287
+ const claimable = isLowercaseTag
121288
+ ? true
121289
+ : isFlow
121290
+ ? !TOKENIZER_MDX_COMPONENT_EXCLUDED_TAGS.has(tagName)
121291
+ : INLINE_COMPONENT_TAGS.has(tagName);
121292
+ if (!claimable)
121227
121293
  return nok(code);
121228
- }
121229
121294
  depth = 1;
121230
121295
  return afterOpenTagName(code);
121231
121296
  }
@@ -121233,6 +121298,10 @@ function createTokenize(mode) {
121233
121298
  function afterOpenTagName(code) {
121234
121299
  if (code === null)
121235
121300
  return nok(code);
121301
+ // Everything except a flow-mode PascalCase block component must carry a
121302
+ // `{…}` brace attribute to be claimed; plain HTML falls through to
121303
+ // CommonMark.
121304
+ const requiresBraceAttr = isLowercaseTag || !isFlow;
121236
121305
  if (markdownLineEnding(code)) {
121237
121306
  if (!isFlow)
121238
121307
  return nok(code);
@@ -121241,14 +121310,14 @@ function createTokenize(mode) {
121241
121310
  }
121242
121311
  // Self-closing />
121243
121312
  if (code === codes.slash) {
121244
- if (isLowercaseTag && !sawBraceAttr)
121313
+ if (requiresBraceAttr && !sawBraceAttr)
121245
121314
  return nok(code);
121246
121315
  effects.consume(code);
121247
121316
  return selfCloseGt;
121248
121317
  }
121249
121318
  // End of opening tag
121250
121319
  if (code === codes.greaterThan) {
121251
- if (isLowercaseTag && !sawBraceAttr)
121320
+ if (requiresBraceAttr && !sawBraceAttr)
121252
121321
  return nok(code);
121253
121322
  effects.consume(code);
121254
121323
  onOpenerLine = isFlow;
@@ -121312,6 +121381,7 @@ function createTokenize(mode) {
121312
121381
  return effects.check(syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
121313
121382
  }
121314
121383
  function openTagContinuationNonLazy(code) {
121384
+ sawLineEnding = true;
121315
121385
  effects.enter(types_types.lineEnding);
121316
121386
  effects.consume(code);
121317
121387
  effects.exit(types_types.lineEnding);
@@ -121435,6 +121505,7 @@ function createTokenize(mode) {
121435
121505
  return effects.check(syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
121436
121506
  }
121437
121507
  function fencedCodeContinuationNonLazy(code) {
121508
+ sawLineEnding = true;
121438
121509
  effects.enter(types_types.lineEnding);
121439
121510
  effects.consume(code);
121440
121511
  effects.exit(types_types.lineEnding);
@@ -121591,6 +121662,12 @@ function createTokenize(mode) {
121591
121662
  effects.exit('mdxComponent');
121592
121663
  return ok(code);
121593
121664
  }
121665
+ // A single-line type-7 lowercase tag with content trailing its close is
121666
+ // inline, so `<i …></i> <a>…</a>` stays on one line instead of splitting
121667
+ // into separate blocks. Raw/block tags (pre, div, …) stay flow.
121668
+ if (isLowercaseTag && !sawLineEnding && !htmlFlowTagNames.has(tagName)) {
121669
+ return afterCloseInlineCandidate(code);
121670
+ }
121594
121671
  if (code === null || markdownLineEnding(code)) {
121595
121672
  effects.exit('mdxComponentData');
121596
121673
  effects.exit('mdxComponent');
@@ -121601,11 +121678,26 @@ function createTokenize(mode) {
121601
121678
  effects.consume(code);
121602
121679
  return afterClose;
121603
121680
  }
121681
+ // Whitespace-only to the line ending keeps the flow block; any other
121682
+ // trailing char means inline content, so refuse and let inline parsing run.
121683
+ function afterCloseInlineCandidate(code) {
121684
+ if (code === null || markdownLineEnding(code)) {
121685
+ effects.exit('mdxComponentData');
121686
+ effects.exit('mdxComponent');
121687
+ return ok(code);
121688
+ }
121689
+ if (markdownSpace(code)) {
121690
+ effects.consume(code);
121691
+ return afterCloseInlineCandidate;
121692
+ }
121693
+ return nok(code);
121694
+ }
121604
121695
  // ── Body continuation (line endings) ───────────────────────────────────
121605
121696
  function bodyContinuationStart(code) {
121606
121697
  return effects.check(syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
121607
121698
  }
121608
121699
  function bodyContinuationNonLazy(code) {
121700
+ sawLineEnding = true;
121609
121701
  effects.enter(types_types.lineEnding);
121610
121702
  effects.consume(code);
121611
121703
  effects.exit(types_types.lineEnding);
@@ -121675,12 +121767,13 @@ function syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
121675
121767
  * self-closing `<Component />`). Prevents CommonMark from fragmenting them
121676
121768
  * across multiple HTML / paragraph nodes.
121677
121769
  *
121678
- * **Text (inline)** — registers only for lowercase tags with brace attrs
121679
- * (e.g. `Start <a href={url}>here</a> end`). Picks them up during inline
121680
- * parsing so they render inline inside their paragraph, then are rewritten
121681
- * to `mdxJsxTextElement` by the `components/inline-html` transformer.
121682
- * PascalCase is intentionally flow-only; ReadMe's custom components are
121683
- * authored as block-level elements.
121770
+ * **Text (inline)** — registers for lowercase tags and inline PascalCase
121771
+ * components (Anchor, Glossary) that carry brace attrs (e.g.
121772
+ * `Start <a href={url}>here</a> end`, `<Anchor href={url}>x</Anchor>`). Picks
121773
+ * them up during inline parsing so they render inline inside their paragraph,
121774
+ * then are rewritten to `mdxJsxTextElement` by the `components/inline-html`
121775
+ * transformer. All other PascalCase is flow-only; ReadMe's custom components
121776
+ * are authored as block-level elements.
121684
121777
  *
121685
121778
  * Excludes tags handled by dedicated tokenizers: Table, HTMLBlock, Glossary,
121686
121779
  * Anchor.
@@ -121799,12 +121892,12 @@ const parsePhrasingChildren = (value, safeMode) => {
121799
121892
  return first?.type === 'paragraph' ? first.children : [];
121800
121893
  };
121801
121894
  /**
121802
- * Attempt to promote a lowercase inline `html` node to an
121803
- * `mdxJsxTextElement`. Scope is deliberately narrow: only lowercase tags
121804
- * carrying `{…}` attribute expressions (what the `mdxComponent` text
121805
- * tokenizer claims). PascalCase components even when inline — stay with
121806
- * `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`, matching
121807
- * ReadMe's flow-level component authoring model.
121895
+ * Attempt to promote an inline `html` node to an `mdxJsxTextElement`. Scope
121896
+ * mirrors what the `mdxComponent` text tokenizer claims: lowercase tags and
121897
+ * inline PascalCase components (`INLINE_COMPONENT_TAGS` — Anchor, Glossary),
121898
+ * both carrying `{…}` attribute expressions. All other PascalCase components
121899
+ * stay with `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`,
121900
+ * matching ReadMe's flow-level component authoring model.
121808
121901
  *
121809
121902
  * Returns the original node unchanged when it doesn't qualify.
121810
121903
  */
@@ -121813,12 +121906,11 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
121813
121906
  if (!parsed)
121814
121907
  return node;
121815
121908
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
121816
- // PascalCase stays flow-level; handled by mdxishComponentBlocks.
121817
- if (isPascalCase(tag))
121818
- return node;
121819
- // Dedicated tokenizers (Table, HTMLBlock, Glossary, Anchor) handle their
121820
- // own tags — don't hijack them here.
121821
- if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
121909
+ // Non-inline PascalCase stays flow-level (handled by mdxishComponentBlocks)
121910
+ // and dedicated tokenizers (Table, HTMLBlock) own their tags. Inline
121911
+ // components (Anchor, Glossary) with expression attrs are promoted here —
121912
+ // the text tokenizer captures them as a single html node.
121913
+ if (isPascalCase(tag) && !INLINE_COMPONENT_TAGS.has(tag))
121822
121914
  return node;
121823
121915
  // Plain inline HTML (no expressions) stays as an html node so rehype-raw
121824
121916
  // parses it with parse5 as normal.
@@ -121842,14 +121934,15 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
121842
121934
  * Runs after `mdxishComponentBlocks`, which skips paragraph-parented html
121843
121935
  * nodes and leaves them for this pass. Two producers put html nodes inside
121844
121936
  * paragraphs:
121845
- * 1. The `mdxComponent` text tokenizer, for lowercase tags with `{…}`
121846
- * attribute expressions (e.g. `<a href={url}>here</a>`).
121937
+ * 1. The `mdxComponent` text tokenizer, for lowercase tags and inline
121938
+ * PascalCase components (Anchor, Glossary) with `{}` attribute
121939
+ * expressions (e.g. `<a href={url}>here</a>`, `<Anchor href={url}>x</Anchor>`).
121847
121940
  * 2. CommonMark's built-in html-text tokenizer, for PascalCase components
121848
121941
  * (e.g. a single html node for self-closing `<C />`).
121849
121942
  *
121850
- * Eligibility mirrors `mdxishComponentBlocks`: lowercase tags only promote
121851
- * when they carry an expression attribute; plain inline HTML like
121852
- * `<a href="x">` stays as an html node for rehype-raw.
121943
+ * Eligibility mirrors `mdxishComponentBlocks`: a tag only promotes when it
121944
+ * carries an expression attribute and isn't a non-inline PascalCase component;
121945
+ * plain inline HTML like `<a href="x">` stays as an html node for rehype-raw.
121853
121946
  */
121854
121947
  const mdxishInlineMdxHtmlBlocks = (opts = {}) => tree => {
121855
121948
  const safeMode = !!opts.safeMode;
@@ -121974,6 +122067,22 @@ const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
121974
122067
  return nodePosition;
121975
122068
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
121976
122069
  };
122070
+ /**
122071
+ * Build a position ending right after the last occurrence of `closingTag` within
122072
+ * this node's span in the original source. Used in the trailing-content path so
122073
+ * the offset is computed against the real source bytes (including blockquote/list
122074
+ * prefixes that were stripped from the html node's value).
122075
+ */
122076
+ const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
122077
+ if (!nodePosition?.start || !nodePosition.end)
122078
+ return nodePosition;
122079
+ const nodeSource = source.slice(nodePosition.start.offset, nodePosition.end.offset);
122080
+ const closingTagOffset = nodeSource.lastIndexOf(closingTag);
122081
+ if (closingTagOffset === -1)
122082
+ return nodePosition;
122083
+ const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
122084
+ return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
122085
+ };
121977
122086
  /**
121978
122087
  * Create an MdxJsxFlowElement node from component data.
121979
122088
  */
@@ -122017,9 +122126,10 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
122017
122126
  * The opening tag, content, and closing tag are all captured in one HTML node
122018
122127
  * (guaranteed by the mdx-component tokenizer).
122019
122128
  */
122020
- const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122129
+ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122021
122130
  const stack = [tree];
122022
122131
  const safeMode = !!opts.safeMode;
122132
+ const source = file?.value ? String(file.value) : null;
122023
122133
  const parseOpts = { preserveExpressionsAsText: safeMode };
122024
122134
  const processChildNode = (parent, index) => {
122025
122135
  const node = parent.children[index];
@@ -122100,8 +122210,17 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122100
122210
  attributes,
122101
122211
  children: parsedChildren,
122102
122212
  startPosition: node.position,
122103
- // End at the closing tag, not at trailing content re-parsed as siblings below.
122104
- endPosition: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length),
122213
+ // When trailing content follows the closing tag, compute the end position precisely
122214
+ // within the html node's value so the component doesn't claim that content.
122215
+ // Prefer source-based positioning when the original source is available: the html
122216
+ // node's value has '> '/space prefixes stripped for blockquotes/list items, so
122217
+ // positionEndingAtConsumed would undercount source offsets. When the entire node
122218
+ // is consumed, use the original node position directly.
122219
+ endPosition: contentAfterClose
122220
+ ? source
122221
+ ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
122222
+ : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
122223
+ : node.position,
122105
122224
  });
122106
122225
  substituteNodeWithMdxNode(parent, index, componentNode);
122107
122226
  // After the closing tag, there might be more content to be processed