@readme/markdown 14.10.2 → 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/components/Embed/index.tsx +4 -1
- package/components/Embed/normalizeYouTubeUrl.ts +41 -0
- package/dist/components/Embed/normalizeYouTubeUrl.d.ts +1 -0
- package/dist/lib/micromark/mdx-component/syntax.d.ts +7 -6
- package/dist/main.js +133 -40
- package/dist/main.node.js +133 -40
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/mdxish/components/inline-html.d.ts +6 -5
- package/dist/render-fixture.node.js +133 -40
- package/dist/render-fixture.node.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
121014
|
-
*
|
|
121015
|
-
*
|
|
121016
|
-
*
|
|
121017
|
-
*
|
|
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.
|
|
121030
|
-
//
|
|
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
|
|
121192
|
-
// components
|
|
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 —
|
|
121226
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
121679
|
-
* (
|
|
121680
|
-
*
|
|
121681
|
-
*
|
|
121682
|
-
*
|
|
121683
|
-
*
|
|
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
|
|
121803
|
-
* `
|
|
121804
|
-
*
|
|
121805
|
-
*
|
|
121806
|
-
* `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`,
|
|
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
|
|
121817
|
-
|
|
121818
|
-
|
|
121819
|
-
//
|
|
121820
|
-
|
|
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
|
|
121846
|
-
*
|
|
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`:
|
|
121851
|
-
*
|
|
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;
|