@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/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/lib/render-diff/differ.d.ts +49 -0
- package/dist/lib/render-diff/index.d.ts +2 -0
- package/dist/lib/render-diff/types.d.ts +119 -0
- package/dist/lib/render-fixture/index.d.ts +19 -0
- package/dist/lib/render-fixture/loadFixture.d.ts +21 -0
- package/dist/lib/render-fixture/renderFixture.d.ts +17 -0
- package/dist/main.js +162 -43
- package/dist/main.node.js +162 -43
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/mdxish/components/inline-html.d.ts +6 -5
- package/dist/render-diff.node.js +3568 -0
- package/dist/render-diff.node.js.map +1 -0
- package/dist/render-fixture.css +6 -0
- package/dist/render-fixture.css.map +1 -0
- package/dist/render-fixture.node.js +126511 -0
- package/dist/render-fixture.node.js.map +1 -0
- package/package.json +30 -2
package/dist/main.js
CHANGED
|
@@ -12060,10 +12060,55 @@ const Columns = ({ children, layout = 'auto' }) => {
|
|
|
12060
12060
|
};
|
|
12061
12061
|
/* harmony default export */ const components_Columns = (Columns);
|
|
12062
12062
|
|
|
12063
|
+
;// ./components/Embed/normalizeYouTubeUrl.ts
|
|
12064
|
+
// YouTube watch/share URLs (youtube.com/watch?v=ID, youtu.be/ID) set X-Frame-Options
|
|
12065
|
+
// and refuse to be framed. The player only allows embedding via youtube.com/embed/ID.
|
|
12066
|
+
const YOUTUBE_HOSTS = new Set(['youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtu.be']);
|
|
12067
|
+
// /embed/ accepts `start` (integer seconds), not the watch-page `t` value which may carry units (e.g. 596s, 1h2m3s).
|
|
12068
|
+
const toStartSeconds = (timestamp) => {
|
|
12069
|
+
const [, hours, minutes, seconds] = timestamp.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/) ?? [];
|
|
12070
|
+
const total = (Number(hours) || 0) * 3600 + (Number(minutes) || 0) * 60 + (Number(seconds) || 0);
|
|
12071
|
+
return total > 0 ? String(total) : null;
|
|
12072
|
+
};
|
|
12073
|
+
const extractVideoId = (parsed) => {
|
|
12074
|
+
if (parsed.hostname.endsWith('youtu.be'))
|
|
12075
|
+
return parsed.pathname.slice(1).split('/')[0] || null;
|
|
12076
|
+
if (parsed.pathname.startsWith('/watch'))
|
|
12077
|
+
return parsed.searchParams.get('v');
|
|
12078
|
+
if (parsed.pathname.startsWith('/shorts/'))
|
|
12079
|
+
return parsed.pathname.slice('/shorts/'.length).split('/')[0] || null;
|
|
12080
|
+
return null;
|
|
12081
|
+
};
|
|
12082
|
+
// Most youtube links we get from the browser are watch URLs, like https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
12083
|
+
// These /watch URLs won't work in an iframe, so we need to convert them to the embed URL if iframe is used
|
|
12084
|
+
const normalizeYouTubeUrlToEmbedUrl = (url) => {
|
|
12085
|
+
let parsed;
|
|
12086
|
+
try {
|
|
12087
|
+
parsed = new URL(url);
|
|
12088
|
+
}
|
|
12089
|
+
catch {
|
|
12090
|
+
return url;
|
|
12091
|
+
}
|
|
12092
|
+
if (!YOUTUBE_HOSTS.has(parsed.hostname))
|
|
12093
|
+
return url;
|
|
12094
|
+
if (parsed.pathname.startsWith('/embed/'))
|
|
12095
|
+
return url;
|
|
12096
|
+
const videoId = extractVideoId(parsed);
|
|
12097
|
+
if (!videoId)
|
|
12098
|
+
return url;
|
|
12099
|
+
const embed = new URL(`https://www.youtube.com/embed/${videoId}`);
|
|
12100
|
+
const timestamp = parsed.searchParams.get('start') || parsed.searchParams.get('t');
|
|
12101
|
+
const start = timestamp ? toStartSeconds(timestamp) : null;
|
|
12102
|
+
if (start)
|
|
12103
|
+
embed.searchParams.set('start', start);
|
|
12104
|
+
return embed.toString();
|
|
12105
|
+
};
|
|
12106
|
+
|
|
12063
12107
|
;// ./components/Embed/index.tsx
|
|
12064
12108
|
/* eslint-disable no-param-reassign */
|
|
12065
12109
|
/* eslint-disable react/jsx-props-no-spreading */
|
|
12066
12110
|
|
|
12111
|
+
|
|
12067
12112
|
const Favicon = ({ src, alt = 'favicon', ...attr }) => (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("img", { ...attr, alt: alt, height: "14", src: src, width: "14" }));
|
|
12068
12113
|
const IFRAME_DERIVABLE_TYPES = new Set(['youtube', 'jsfiddle', 'pdf']);
|
|
12069
12114
|
// HTML width/height attrs accept bare numbers (interpreted as px), but CSS does not, convert.
|
|
@@ -12097,7 +12142,8 @@ const Embed = ({ lazy = true, url, html, providerName, providerUrl, title, ifram
|
|
|
12097
12142
|
// Fall back to a direct iframe for URL-derivable embed types when html is missing.
|
|
12098
12143
|
const renderTypeAsIframe = !html && !explicitOptOut && url && typeOfEmbed && IFRAME_DERIVABLE_TYPES.has(typeOfEmbed);
|
|
12099
12144
|
if (iframe || renderTypeAsIframe) {
|
|
12100
|
-
|
|
12145
|
+
const src = normalizeYouTubeUrlToEmbedUrl(url);
|
|
12146
|
+
return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("iframe", { ...spreadAttrs, src: src, style: iframeStyle, title: title });
|
|
12101
12147
|
}
|
|
12102
12148
|
if (!providerUrl && url)
|
|
12103
12149
|
providerUrl = new URL(url).hostname
|
|
@@ -100750,6 +100796,12 @@ const types_types = /** @type {const} */ ({
|
|
|
100750
100796
|
|
|
100751
100797
|
|
|
100752
100798
|
|
|
100799
|
+
|
|
100800
|
+
// Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
|
|
100801
|
+
// section, …) always start a block, so they stay flow even with trailing
|
|
100802
|
+
// content. Other lowercase tags (i, span, …) follow the type-7 rule and only
|
|
100803
|
+
// stay flow when nothing trails the close tag.
|
|
100804
|
+
const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
|
|
100753
100805
|
const syntax_nonLazyContinuationStart = {
|
|
100754
100806
|
tokenize: syntax_tokenizeNonLazyContinuationStart,
|
|
100755
100807
|
partial: true,
|
|
@@ -100786,11 +100838,12 @@ const mdxComponentTextConstruct = {
|
|
|
100786
100838
|
* lowercase HTML tags that carry at least one `{…}` attribute expression.
|
|
100787
100839
|
* Multi-line, concrete, `afterClose` consumes the rest of the line.
|
|
100788
100840
|
*
|
|
100789
|
-
* **Text** — runs inside paragraphs / inline context. Claims
|
|
100790
|
-
*
|
|
100791
|
-
*
|
|
100792
|
-
*
|
|
100793
|
-
*
|
|
100841
|
+
* **Text** — runs inside paragraphs / inline context. Claims lowercase tags and
|
|
100842
|
+
* inline PascalCase components (`INLINE_COMPONENT_TAGS` — Anchor, Glossary), both
|
|
100843
|
+
* gated on at least one `{…}` brace attribute. All other PascalCase stays
|
|
100844
|
+
* flow-only, matching how ReadMe's custom components are authored. Aborts on line
|
|
100845
|
+
* endings (inline constructs don't span lines) and exits immediately after
|
|
100846
|
+
* `</tag>` so the paragraph's inline parser picks up the trailing text.
|
|
100794
100847
|
*/
|
|
100795
100848
|
function createTokenize(mode) {
|
|
100796
100849
|
const isFlow = mode === 'flow';
|
|
@@ -100802,8 +100855,9 @@ function createTokenize(mode) {
|
|
|
100802
100855
|
let closingTagName = '';
|
|
100803
100856
|
// For lowercase tags we only want to claim the block if it uses JSX
|
|
100804
100857
|
// attribute expression syntax (`attr={...}`). Plain HTML should fall
|
|
100805
|
-
// through to CommonMark html-flow.
|
|
100806
|
-
//
|
|
100858
|
+
// through to CommonMark html-flow. Flow mode claims any PascalCase block
|
|
100859
|
+
// component; text mode claims only inline PascalCase components
|
|
100860
|
+
// (INLINE_COMPONENT_TAGS — Anchor, Glossary), also brace-gated.
|
|
100807
100861
|
let isLowercaseTag = false;
|
|
100808
100862
|
let sawBraceAttr = false;
|
|
100809
100863
|
// Code span tracking
|
|
@@ -100814,6 +100868,9 @@ function createTokenize(mode) {
|
|
|
100814
100868
|
let fenceLength = 0;
|
|
100815
100869
|
let fenceCloseLength = 0;
|
|
100816
100870
|
let atLineStart = false;
|
|
100871
|
+
// True once this construct consumes any line ending; lets `afterClose`
|
|
100872
|
+
// treat only single-line lowercase tags as inline candidates.
|
|
100873
|
+
let sawLineEnding = false;
|
|
100817
100874
|
// Bail when the opener line has unmatched tag-like tokens in its body.
|
|
100818
100875
|
// `<Foo>_<Bar>.csv` leaves opens > closes; matched shapes like
|
|
100819
100876
|
// `<Callout>x <strong>y</strong>` balance to 0. Without this,
|
|
@@ -100964,13 +101021,13 @@ function createTokenize(mode) {
|
|
|
100964
101021
|
}
|
|
100965
101022
|
// ── Tag name parsing ───────────────────────────────────────────────────
|
|
100966
101023
|
function tagNameFirst(code) {
|
|
100967
|
-
// Uppercase A-Z → PascalCase MDX component. Flow
|
|
100968
|
-
// components
|
|
101024
|
+
// Uppercase A-Z → PascalCase MDX component. Flow mode claims block
|
|
101025
|
+
// components; text mode only claims inline components (Anchor, Glossary),
|
|
101026
|
+
// which is enforced once the full name is known in `tagNameRest`.
|
|
100969
101027
|
if (code !== null && code >= codes.uppercaseA && code <= codes.uppercaseZ) {
|
|
100970
|
-
if (!isFlow)
|
|
100971
|
-
return nok(code);
|
|
100972
101028
|
tagName = String.fromCharCode(code);
|
|
100973
101029
|
isLowercaseTag = false;
|
|
101030
|
+
sawBraceAttr = false;
|
|
100974
101031
|
effects.consume(code);
|
|
100975
101032
|
return tagNameRest;
|
|
100976
101033
|
}
|
|
@@ -100998,10 +101055,18 @@ function createTokenize(mode) {
|
|
|
100998
101055
|
effects.consume(code);
|
|
100999
101056
|
return tagNameRest;
|
|
101000
101057
|
}
|
|
101001
|
-
// Tag name complete —
|
|
101002
|
-
|
|
101058
|
+
// Tag name complete — decide whether this tokenizer claims the tag.
|
|
101059
|
+
// Three cases: lowercase tags are always candidates (brace-gated later in
|
|
101060
|
+
// `afterOpenTagName`); flow-mode PascalCase claims any block component
|
|
101061
|
+
// except those with a dedicated tokenizer; text-mode PascalCase claims
|
|
101062
|
+
// only inline components (Anchor, Glossary).
|
|
101063
|
+
const claimable = isLowercaseTag
|
|
101064
|
+
? true
|
|
101065
|
+
: isFlow
|
|
101066
|
+
? !TOKENIZER_MDX_COMPONENT_EXCLUDED_TAGS.has(tagName)
|
|
101067
|
+
: INLINE_COMPONENT_TAGS.has(tagName);
|
|
101068
|
+
if (!claimable)
|
|
101003
101069
|
return nok(code);
|
|
101004
|
-
}
|
|
101005
101070
|
depth = 1;
|
|
101006
101071
|
return afterOpenTagName(code);
|
|
101007
101072
|
}
|
|
@@ -101009,6 +101074,10 @@ function createTokenize(mode) {
|
|
|
101009
101074
|
function afterOpenTagName(code) {
|
|
101010
101075
|
if (code === null)
|
|
101011
101076
|
return nok(code);
|
|
101077
|
+
// Everything except a flow-mode PascalCase block component must carry a
|
|
101078
|
+
// `{…}` brace attribute to be claimed; plain HTML falls through to
|
|
101079
|
+
// CommonMark.
|
|
101080
|
+
const requiresBraceAttr = isLowercaseTag || !isFlow;
|
|
101012
101081
|
if (markdownLineEnding(code)) {
|
|
101013
101082
|
if (!isFlow)
|
|
101014
101083
|
return nok(code);
|
|
@@ -101017,14 +101086,14 @@ function createTokenize(mode) {
|
|
|
101017
101086
|
}
|
|
101018
101087
|
// Self-closing />
|
|
101019
101088
|
if (code === codes.slash) {
|
|
101020
|
-
if (
|
|
101089
|
+
if (requiresBraceAttr && !sawBraceAttr)
|
|
101021
101090
|
return nok(code);
|
|
101022
101091
|
effects.consume(code);
|
|
101023
101092
|
return selfCloseGt;
|
|
101024
101093
|
}
|
|
101025
101094
|
// End of opening tag
|
|
101026
101095
|
if (code === codes.greaterThan) {
|
|
101027
|
-
if (
|
|
101096
|
+
if (requiresBraceAttr && !sawBraceAttr)
|
|
101028
101097
|
return nok(code);
|
|
101029
101098
|
effects.consume(code);
|
|
101030
101099
|
onOpenerLine = isFlow;
|
|
@@ -101088,6 +101157,7 @@ function createTokenize(mode) {
|
|
|
101088
101157
|
return effects.check(syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
|
|
101089
101158
|
}
|
|
101090
101159
|
function openTagContinuationNonLazy(code) {
|
|
101160
|
+
sawLineEnding = true;
|
|
101091
101161
|
effects.enter(types_types.lineEnding);
|
|
101092
101162
|
effects.consume(code);
|
|
101093
101163
|
effects.exit(types_types.lineEnding);
|
|
@@ -101211,6 +101281,7 @@ function createTokenize(mode) {
|
|
|
101211
101281
|
return effects.check(syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
|
|
101212
101282
|
}
|
|
101213
101283
|
function fencedCodeContinuationNonLazy(code) {
|
|
101284
|
+
sawLineEnding = true;
|
|
101214
101285
|
effects.enter(types_types.lineEnding);
|
|
101215
101286
|
effects.consume(code);
|
|
101216
101287
|
effects.exit(types_types.lineEnding);
|
|
@@ -101367,6 +101438,12 @@ function createTokenize(mode) {
|
|
|
101367
101438
|
effects.exit('mdxComponent');
|
|
101368
101439
|
return ok(code);
|
|
101369
101440
|
}
|
|
101441
|
+
// A single-line type-7 lowercase tag with content trailing its close is
|
|
101442
|
+
// inline, so `<i …></i> <a>…</a>` stays on one line instead of splitting
|
|
101443
|
+
// into separate blocks. Raw/block tags (pre, div, …) stay flow.
|
|
101444
|
+
if (isLowercaseTag && !sawLineEnding && !htmlFlowTagNames.has(tagName)) {
|
|
101445
|
+
return afterCloseInlineCandidate(code);
|
|
101446
|
+
}
|
|
101370
101447
|
if (code === null || markdownLineEnding(code)) {
|
|
101371
101448
|
effects.exit('mdxComponentData');
|
|
101372
101449
|
effects.exit('mdxComponent');
|
|
@@ -101377,11 +101454,26 @@ function createTokenize(mode) {
|
|
|
101377
101454
|
effects.consume(code);
|
|
101378
101455
|
return afterClose;
|
|
101379
101456
|
}
|
|
101457
|
+
// Whitespace-only to the line ending keeps the flow block; any other
|
|
101458
|
+
// trailing char means inline content, so refuse and let inline parsing run.
|
|
101459
|
+
function afterCloseInlineCandidate(code) {
|
|
101460
|
+
if (code === null || markdownLineEnding(code)) {
|
|
101461
|
+
effects.exit('mdxComponentData');
|
|
101462
|
+
effects.exit('mdxComponent');
|
|
101463
|
+
return ok(code);
|
|
101464
|
+
}
|
|
101465
|
+
if (markdownSpace(code)) {
|
|
101466
|
+
effects.consume(code);
|
|
101467
|
+
return afterCloseInlineCandidate;
|
|
101468
|
+
}
|
|
101469
|
+
return nok(code);
|
|
101470
|
+
}
|
|
101380
101471
|
// ── Body continuation (line endings) ───────────────────────────────────
|
|
101381
101472
|
function bodyContinuationStart(code) {
|
|
101382
101473
|
return effects.check(syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
|
|
101383
101474
|
}
|
|
101384
101475
|
function bodyContinuationNonLazy(code) {
|
|
101476
|
+
sawLineEnding = true;
|
|
101385
101477
|
effects.enter(types_types.lineEnding);
|
|
101386
101478
|
effects.consume(code);
|
|
101387
101479
|
effects.exit(types_types.lineEnding);
|
|
@@ -101451,12 +101543,13 @@ function syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
|
|
|
101451
101543
|
* self-closing `<Component />`). Prevents CommonMark from fragmenting them
|
|
101452
101544
|
* across multiple HTML / paragraph nodes.
|
|
101453
101545
|
*
|
|
101454
|
-
* **Text (inline)** — registers
|
|
101455
|
-
* (
|
|
101456
|
-
*
|
|
101457
|
-
*
|
|
101458
|
-
*
|
|
101459
|
-
*
|
|
101546
|
+
* **Text (inline)** — registers for lowercase tags and inline PascalCase
|
|
101547
|
+
* components (Anchor, Glossary) that carry brace attrs (e.g.
|
|
101548
|
+
* `Start <a href={url}>here</a> end`, `<Anchor href={url}>x</Anchor>`). Picks
|
|
101549
|
+
* them up during inline parsing so they render inline inside their paragraph,
|
|
101550
|
+
* then are rewritten to `mdxJsxTextElement` by the `components/inline-html`
|
|
101551
|
+
* transformer. All other PascalCase is flow-only; ReadMe's custom components
|
|
101552
|
+
* are authored as block-level elements.
|
|
101460
101553
|
*
|
|
101461
101554
|
* Excludes tags handled by dedicated tokenizers: Table, HTMLBlock, Glossary,
|
|
101462
101555
|
* Anchor.
|
|
@@ -101575,12 +101668,12 @@ const parsePhrasingChildren = (value, safeMode) => {
|
|
|
101575
101668
|
return first?.type === 'paragraph' ? first.children : [];
|
|
101576
101669
|
};
|
|
101577
101670
|
/**
|
|
101578
|
-
* Attempt to promote
|
|
101579
|
-
* `
|
|
101580
|
-
*
|
|
101581
|
-
*
|
|
101582
|
-
* `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`,
|
|
101583
|
-
* ReadMe's flow-level component authoring model.
|
|
101671
|
+
* Attempt to promote an inline `html` node to an `mdxJsxTextElement`. Scope
|
|
101672
|
+
* mirrors what the `mdxComponent` text tokenizer claims: lowercase tags and
|
|
101673
|
+
* inline PascalCase components (`INLINE_COMPONENT_TAGS` — Anchor, Glossary),
|
|
101674
|
+
* both carrying `{…}` attribute expressions. All other PascalCase components
|
|
101675
|
+
* stay with `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`,
|
|
101676
|
+
* matching ReadMe's flow-level component authoring model.
|
|
101584
101677
|
*
|
|
101585
101678
|
* Returns the original node unchanged when it doesn't qualify.
|
|
101586
101679
|
*/
|
|
@@ -101589,12 +101682,11 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
|
|
|
101589
101682
|
if (!parsed)
|
|
101590
101683
|
return node;
|
|
101591
101684
|
const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
|
|
101592
|
-
// PascalCase stays flow-level
|
|
101593
|
-
|
|
101594
|
-
|
|
101595
|
-
//
|
|
101596
|
-
|
|
101597
|
-
if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
|
|
101685
|
+
// Non-inline PascalCase stays flow-level (handled by mdxishComponentBlocks)
|
|
101686
|
+
// and dedicated tokenizers (Table, HTMLBlock) own their tags. Inline
|
|
101687
|
+
// components (Anchor, Glossary) with expression attrs are promoted here —
|
|
101688
|
+
// the text tokenizer captures them as a single html node.
|
|
101689
|
+
if (isPascalCase(tag) && !INLINE_COMPONENT_TAGS.has(tag))
|
|
101598
101690
|
return node;
|
|
101599
101691
|
// Plain inline HTML (no expressions) stays as an html node so rehype-raw
|
|
101600
101692
|
// parses it with parse5 as normal.
|
|
@@ -101618,14 +101710,15 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
|
|
|
101618
101710
|
* Runs after `mdxishComponentBlocks`, which skips paragraph-parented html
|
|
101619
101711
|
* nodes and leaves them for this pass. Two producers put html nodes inside
|
|
101620
101712
|
* paragraphs:
|
|
101621
|
-
* 1. The `mdxComponent` text tokenizer, for lowercase tags
|
|
101622
|
-
*
|
|
101713
|
+
* 1. The `mdxComponent` text tokenizer, for lowercase tags and inline
|
|
101714
|
+
* PascalCase components (Anchor, Glossary) with `{…}` attribute
|
|
101715
|
+
* expressions (e.g. `<a href={url}>here</a>`, `<Anchor href={url}>x</Anchor>`).
|
|
101623
101716
|
* 2. CommonMark's built-in html-text tokenizer, for PascalCase components
|
|
101624
101717
|
* (e.g. a single html node for self-closing `<C />`).
|
|
101625
101718
|
*
|
|
101626
|
-
* Eligibility mirrors `mdxishComponentBlocks`:
|
|
101627
|
-
*
|
|
101628
|
-
* `<a href="x">` stays as an html node for rehype-raw.
|
|
101719
|
+
* Eligibility mirrors `mdxishComponentBlocks`: a tag only promotes when it
|
|
101720
|
+
* carries an expression attribute and isn't a non-inline PascalCase component;
|
|
101721
|
+
* plain inline HTML like `<a href="x">` stays as an html node for rehype-raw.
|
|
101629
101722
|
*/
|
|
101630
101723
|
const mdxishInlineMdxHtmlBlocks = (opts = {}) => tree => {
|
|
101631
101724
|
const safeMode = !!opts.safeMode;
|
|
@@ -101750,6 +101843,22 @@ const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
|
|
|
101750
101843
|
return nodePosition;
|
|
101751
101844
|
return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
|
|
101752
101845
|
};
|
|
101846
|
+
/**
|
|
101847
|
+
* Build a position ending right after the last occurrence of `closingTag` within
|
|
101848
|
+
* this node's span in the original source. Used in the trailing-content path so
|
|
101849
|
+
* the offset is computed against the real source bytes (including blockquote/list
|
|
101850
|
+
* prefixes that were stripped from the html node's value).
|
|
101851
|
+
*/
|
|
101852
|
+
const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
|
|
101853
|
+
if (!nodePosition?.start || !nodePosition.end)
|
|
101854
|
+
return nodePosition;
|
|
101855
|
+
const nodeSource = source.slice(nodePosition.start.offset, nodePosition.end.offset);
|
|
101856
|
+
const closingTagOffset = nodeSource.lastIndexOf(closingTag);
|
|
101857
|
+
if (closingTagOffset === -1)
|
|
101858
|
+
return nodePosition;
|
|
101859
|
+
const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
|
|
101860
|
+
return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
|
|
101861
|
+
};
|
|
101753
101862
|
/**
|
|
101754
101863
|
* Create an MdxJsxFlowElement node from component data.
|
|
101755
101864
|
*/
|
|
@@ -101793,9 +101902,10 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
|
|
|
101793
101902
|
* The opening tag, content, and closing tag are all captured in one HTML node
|
|
101794
101903
|
* (guaranteed by the mdx-component tokenizer).
|
|
101795
101904
|
*/
|
|
101796
|
-
const mdxishMdxComponentBlocks = (opts = {}) => tree => {
|
|
101905
|
+
const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
|
|
101797
101906
|
const stack = [tree];
|
|
101798
101907
|
const safeMode = !!opts.safeMode;
|
|
101908
|
+
const source = file?.value ? String(file.value) : null;
|
|
101799
101909
|
const parseOpts = { preserveExpressionsAsText: safeMode };
|
|
101800
101910
|
const processChildNode = (parent, index) => {
|
|
101801
101911
|
const node = parent.children[index];
|
|
@@ -101876,8 +101986,17 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
|
|
|
101876
101986
|
attributes,
|
|
101877
101987
|
children: parsedChildren,
|
|
101878
101988
|
startPosition: node.position,
|
|
101879
|
-
//
|
|
101880
|
-
|
|
101989
|
+
// When trailing content follows the closing tag, compute the end position precisely
|
|
101990
|
+
// within the html node's value so the component doesn't claim that content.
|
|
101991
|
+
// Prefer source-based positioning when the original source is available: the html
|
|
101992
|
+
// node's value has '> '/space prefixes stripped for blockquotes/list items, so
|
|
101993
|
+
// positionEndingAtConsumed would undercount source offsets. When the entire node
|
|
101994
|
+
// is consumed, use the original node position directly.
|
|
101995
|
+
endPosition: contentAfterClose
|
|
101996
|
+
? source
|
|
101997
|
+
? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
|
|
101998
|
+
: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
|
|
101999
|
+
: node.position,
|
|
101881
102000
|
});
|
|
101882
102001
|
substituteNodeWithMdxNode(parent, index, componentNode);
|
|
101883
102002
|
// After the closing tag, there might be more content to be processed
|