@ai-react-markdown/engine 2.4.1 → 2.4.2
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/index.cjs +151 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -16
- package/dist/index.d.ts +38 -16
- package/dist/index.dev.cjs +151 -40
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +151 -40
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +151 -40
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.d.cts
CHANGED
|
@@ -384,6 +384,16 @@ interface FreezeScanCheckpoint {
|
|
|
384
384
|
prevLineWasValidDef: boolean;
|
|
385
385
|
/** An earlier line of the current paragraph left an unpaired backtick
|
|
386
386
|
* run — masking is disabled until the paragraph ends (safety gate). */
|
|
387
|
+
/** A `[` left unclosed at the end of a paragraph line (code spans
|
|
388
|
+
* masked). micromark lets a reference label span soft line breaks, so
|
|
389
|
+
* the label may close on a LATER line where the per-line REF_RE never
|
|
390
|
+
* sees a `[…]` pair (v2.4.1 review P1: `see [foo\nbar] end` + a late
|
|
391
|
+
* `[foo bar]: /u` retargeted frozen output). Cleared wherever the
|
|
392
|
+
* paragraph ends. */
|
|
393
|
+
openBracket: {
|
|
394
|
+
offset: number;
|
|
395
|
+
text: string;
|
|
396
|
+
} | null;
|
|
387
397
|
paragraphHasUnpairedRun: boolean;
|
|
388
398
|
/** Blocker-6 pending flag: a confirmed html-flow line left balanced
|
|
389
399
|
* floating raw remnant, and no later content line has pinned the seam
|
|
@@ -850,10 +860,12 @@ interface ChunkData {
|
|
|
850
860
|
* interface. Driving the registry directly is reserved for internal
|
|
851
861
|
* coordinators (the package's own `MarkdownContent` renderer) and tests,
|
|
852
862
|
* which import the wider `RegistryInternal` type from this module.
|
|
853
|
-
* `RegistryInternal` is exported
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
863
|
+
* `RegistryInternal` is exported from this engine package (the core
|
|
864
|
+
* renderer consumes it) but NOT from `@ai-react-markdown/core`'s public
|
|
865
|
+
* barrel — keeping mutators off the consumer-facing surface prevents a
|
|
866
|
+
* misbehaving consumer-component from corrupting refcounts, skipping
|
|
867
|
+
* version bumps, or otherwise breaking the invariants the renderer relies
|
|
868
|
+
* on.
|
|
857
869
|
*/
|
|
858
870
|
interface Registry {
|
|
859
871
|
/** Chunk mount-order Symbol list. **Read-only.** Direct mutation
|
|
@@ -906,9 +918,10 @@ interface Registry {
|
|
|
906
918
|
* methods and implementation-private fields (reactId-keyed refcount table,
|
|
907
919
|
* subscriber set, microtask-coalesce flag, `_notify` itself).
|
|
908
920
|
*
|
|
909
|
-
* Exported from this module
|
|
910
|
-
* and tests can hold a strongly-typed
|
|
911
|
-
*
|
|
921
|
+
* Exported from this module (and the engine barrel) so internal
|
|
922
|
+
* coordinators (`MarkdownContent`) and tests can hold a strongly-typed
|
|
923
|
+
* reference, but **not** re-exported from `@ai-react-markdown/core`'s
|
|
924
|
+
* barrel — a consumer flipping `_notifyScheduled = true`
|
|
912
925
|
* or pushing into `chunkOrder` directly would silently break the
|
|
913
926
|
* coalesce / numbering invariants. The runtime value returned by
|
|
914
927
|
* {@link createRegistry} always satisfies this wider shape; public consumers
|
|
@@ -1319,11 +1332,13 @@ declare function buildCrossChunkHandlers(): Handlers;
|
|
|
1319
1332
|
* In the standalone path, every `<a href>` and `<img src>` element passes
|
|
1320
1333
|
* through TWO gates before render:
|
|
1321
1334
|
*
|
|
1322
|
-
* 1. `
|
|
1323
|
-
* `defaultUrlTransform`, which mirrors GitHub's protocol allowlist).
|
|
1324
|
-
* Runs in `markdown/transform.ts` during the hast visit pass.
|
|
1325
|
-
* 2. `rehype-sanitize` `protocols.<attr>` allowlist — drops the attribute
|
|
1335
|
+
* 1. `rehype-sanitize` `protocols.<attr>` allowlist — drops the attribute
|
|
1326
1336
|
* entirely if the URL's protocol isn't permitted for that attribute.
|
|
1337
|
+
* Runs in the rehype plugin chain.
|
|
1338
|
+
* 2. `urlTransform(url, key, node)` — the caller's allowlist (default:
|
|
1339
|
+
* `defaultUrlTransform`, which mirrors GitHub's protocol allowlist).
|
|
1340
|
+
* Runs in `markdown/transform.ts` during the hast visit pass; its
|
|
1341
|
+
* return value IS the attribute (`''` renders `href=""`).
|
|
1327
1342
|
*
|
|
1328
1343
|
* Cross-chunk references skip BOTH passes naturally: the placeholder hast
|
|
1329
1344
|
* tag (`<cross-chunk-link>` / `<cross-chunk-image>`) carries only a `label`
|
|
@@ -1353,13 +1368,20 @@ declare function buildCrossChunkHandlers(): Handlers;
|
|
|
1353
1368
|
type UrlAttrKey = 'href' | 'src';
|
|
1354
1369
|
type UrlAttrTag = 'a' | 'img';
|
|
1355
1370
|
/**
|
|
1356
|
-
* Run a cross-chunk-resolved URL through both standalone gates
|
|
1371
|
+
* Run a cross-chunk-resolved URL through both standalone gates, in the
|
|
1372
|
+
* standalone ORDER: `rehype-sanitize`'s protocol allowlist runs in the
|
|
1373
|
+
* plugin chain first (a blocked URL loses the attribute entirely), then
|
|
1374
|
+
* `urlTransform` rewrites whatever survived during the hast visit pass (its
|
|
1375
|
+
* return value is the attribute — `''` from `defaultUrlTransform` renders
|
|
1376
|
+
* `href=""`, exactly like a legal empty destination `[x]: <>`).
|
|
1357
1377
|
*
|
|
1358
|
-
* @returns
|
|
1359
|
-
* URL —
|
|
1360
|
-
*
|
|
1378
|
+
* @returns `null` when the protocol gate strips the attribute (render it
|
|
1379
|
+
* ABSENT); otherwise the transformed URL string — possibly `''`, which
|
|
1380
|
+
* the caller must render as `href=""` / `src=""` to stay byte-identical
|
|
1381
|
+
* with standalone (v2.4.1 review: the two used to collapse into `''` and
|
|
1382
|
+
* the placeholder omitted the attribute for `[x]: <>` too).
|
|
1361
1383
|
*/
|
|
1362
|
-
declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string;
|
|
1384
|
+
declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string | null;
|
|
1363
1385
|
|
|
1364
1386
|
/**
|
|
1365
1387
|
* The five shipped engine plugins and the default set.
|
package/dist/index.d.ts
CHANGED
|
@@ -384,6 +384,16 @@ interface FreezeScanCheckpoint {
|
|
|
384
384
|
prevLineWasValidDef: boolean;
|
|
385
385
|
/** An earlier line of the current paragraph left an unpaired backtick
|
|
386
386
|
* run — masking is disabled until the paragraph ends (safety gate). */
|
|
387
|
+
/** A `[` left unclosed at the end of a paragraph line (code spans
|
|
388
|
+
* masked). micromark lets a reference label span soft line breaks, so
|
|
389
|
+
* the label may close on a LATER line where the per-line REF_RE never
|
|
390
|
+
* sees a `[…]` pair (v2.4.1 review P1: `see [foo\nbar] end` + a late
|
|
391
|
+
* `[foo bar]: /u` retargeted frozen output). Cleared wherever the
|
|
392
|
+
* paragraph ends. */
|
|
393
|
+
openBracket: {
|
|
394
|
+
offset: number;
|
|
395
|
+
text: string;
|
|
396
|
+
} | null;
|
|
387
397
|
paragraphHasUnpairedRun: boolean;
|
|
388
398
|
/** Blocker-6 pending flag: a confirmed html-flow line left balanced
|
|
389
399
|
* floating raw remnant, and no later content line has pinned the seam
|
|
@@ -850,10 +860,12 @@ interface ChunkData {
|
|
|
850
860
|
* interface. Driving the registry directly is reserved for internal
|
|
851
861
|
* coordinators (the package's own `MarkdownContent` renderer) and tests,
|
|
852
862
|
* which import the wider `RegistryInternal` type from this module.
|
|
853
|
-
* `RegistryInternal` is exported
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
863
|
+
* `RegistryInternal` is exported from this engine package (the core
|
|
864
|
+
* renderer consumes it) but NOT from `@ai-react-markdown/core`'s public
|
|
865
|
+
* barrel — keeping mutators off the consumer-facing surface prevents a
|
|
866
|
+
* misbehaving consumer-component from corrupting refcounts, skipping
|
|
867
|
+
* version bumps, or otherwise breaking the invariants the renderer relies
|
|
868
|
+
* on.
|
|
857
869
|
*/
|
|
858
870
|
interface Registry {
|
|
859
871
|
/** Chunk mount-order Symbol list. **Read-only.** Direct mutation
|
|
@@ -906,9 +918,10 @@ interface Registry {
|
|
|
906
918
|
* methods and implementation-private fields (reactId-keyed refcount table,
|
|
907
919
|
* subscriber set, microtask-coalesce flag, `_notify` itself).
|
|
908
920
|
*
|
|
909
|
-
* Exported from this module
|
|
910
|
-
* and tests can hold a strongly-typed
|
|
911
|
-
*
|
|
921
|
+
* Exported from this module (and the engine barrel) so internal
|
|
922
|
+
* coordinators (`MarkdownContent`) and tests can hold a strongly-typed
|
|
923
|
+
* reference, but **not** re-exported from `@ai-react-markdown/core`'s
|
|
924
|
+
* barrel — a consumer flipping `_notifyScheduled = true`
|
|
912
925
|
* or pushing into `chunkOrder` directly would silently break the
|
|
913
926
|
* coalesce / numbering invariants. The runtime value returned by
|
|
914
927
|
* {@link createRegistry} always satisfies this wider shape; public consumers
|
|
@@ -1319,11 +1332,13 @@ declare function buildCrossChunkHandlers(): Handlers;
|
|
|
1319
1332
|
* In the standalone path, every `<a href>` and `<img src>` element passes
|
|
1320
1333
|
* through TWO gates before render:
|
|
1321
1334
|
*
|
|
1322
|
-
* 1. `
|
|
1323
|
-
* `defaultUrlTransform`, which mirrors GitHub's protocol allowlist).
|
|
1324
|
-
* Runs in `markdown/transform.ts` during the hast visit pass.
|
|
1325
|
-
* 2. `rehype-sanitize` `protocols.<attr>` allowlist — drops the attribute
|
|
1335
|
+
* 1. `rehype-sanitize` `protocols.<attr>` allowlist — drops the attribute
|
|
1326
1336
|
* entirely if the URL's protocol isn't permitted for that attribute.
|
|
1337
|
+
* Runs in the rehype plugin chain.
|
|
1338
|
+
* 2. `urlTransform(url, key, node)` — the caller's allowlist (default:
|
|
1339
|
+
* `defaultUrlTransform`, which mirrors GitHub's protocol allowlist).
|
|
1340
|
+
* Runs in `markdown/transform.ts` during the hast visit pass; its
|
|
1341
|
+
* return value IS the attribute (`''` renders `href=""`).
|
|
1327
1342
|
*
|
|
1328
1343
|
* Cross-chunk references skip BOTH passes naturally: the placeholder hast
|
|
1329
1344
|
* tag (`<cross-chunk-link>` / `<cross-chunk-image>`) carries only a `label`
|
|
@@ -1353,13 +1368,20 @@ declare function buildCrossChunkHandlers(): Handlers;
|
|
|
1353
1368
|
type UrlAttrKey = 'href' | 'src';
|
|
1354
1369
|
type UrlAttrTag = 'a' | 'img';
|
|
1355
1370
|
/**
|
|
1356
|
-
* Run a cross-chunk-resolved URL through both standalone gates
|
|
1371
|
+
* Run a cross-chunk-resolved URL through both standalone gates, in the
|
|
1372
|
+
* standalone ORDER: `rehype-sanitize`'s protocol allowlist runs in the
|
|
1373
|
+
* plugin chain first (a blocked URL loses the attribute entirely), then
|
|
1374
|
+
* `urlTransform` rewrites whatever survived during the hast visit pass (its
|
|
1375
|
+
* return value is the attribute — `''` from `defaultUrlTransform` renders
|
|
1376
|
+
* `href=""`, exactly like a legal empty destination `[x]: <>`).
|
|
1357
1377
|
*
|
|
1358
|
-
* @returns
|
|
1359
|
-
* URL —
|
|
1360
|
-
*
|
|
1378
|
+
* @returns `null` when the protocol gate strips the attribute (render it
|
|
1379
|
+
* ABSENT); otherwise the transformed URL string — possibly `''`, which
|
|
1380
|
+
* the caller must render as `href=""` / `src=""` to stay byte-identical
|
|
1381
|
+
* with standalone (v2.4.1 review: the two used to collapse into `''` and
|
|
1382
|
+
* the placeholder omitted the attribute for `[x]: <>` too).
|
|
1361
1383
|
*/
|
|
1362
|
-
declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string;
|
|
1384
|
+
declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string | null;
|
|
1363
1385
|
|
|
1364
1386
|
/**
|
|
1365
1387
|
* The five shipped engine plugins and the default set.
|
package/dist/index.dev.cjs
CHANGED
|
@@ -295,6 +295,10 @@ var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|--
|
|
|
295
295
|
var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
|
|
296
296
|
var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
|
|
297
297
|
var BACKTICK_RUN_RE = /`+/g;
|
|
298
|
+
var MD_BLANK_RE = /^[ \t\r]*$/;
|
|
299
|
+
var isMdBlank = (text) => MD_BLANK_RE.test(text);
|
|
300
|
+
var mdTrim = (text) => text.replace(/^[ \t\r]+|[ \t\r]+$/g, "");
|
|
301
|
+
var mdTrimStart = (text) => text.replace(/^[ \t\r]+/, "");
|
|
298
302
|
function computeIndent(text) {
|
|
299
303
|
let indent = 0;
|
|
300
304
|
for (const ch of text) {
|
|
@@ -304,8 +308,25 @@ function computeIndent(text) {
|
|
|
304
308
|
}
|
|
305
309
|
return indent;
|
|
306
310
|
}
|
|
311
|
+
function firstUnescaped(text, ch) {
|
|
312
|
+
for (let i = 0; i < text.length; i++) {
|
|
313
|
+
if (text[i] === "\\") i += 1;
|
|
314
|
+
else if (text[i] === ch) return i;
|
|
315
|
+
}
|
|
316
|
+
return -1;
|
|
317
|
+
}
|
|
318
|
+
function lastUnclosedBracket(text) {
|
|
319
|
+
let open = -1;
|
|
320
|
+
for (let i = 0; i < text.length; i++) {
|
|
321
|
+
const c = text[i];
|
|
322
|
+
if (c === "\\") i += 1;
|
|
323
|
+
else if (c === "[") open = i;
|
|
324
|
+
else if (c === "]") open = -1;
|
|
325
|
+
}
|
|
326
|
+
return open;
|
|
327
|
+
}
|
|
307
328
|
function normalizeLabel(label) {
|
|
308
|
-
const collapsed = label.
|
|
329
|
+
const collapsed = label.replace(/[ \t\r\n]+/g, " ").replace(/^ | $/g, "");
|
|
309
330
|
return collapsed ? (0, import_micromark_util_normalize_identifier.normalizeIdentifier)(collapsed) : "";
|
|
310
331
|
}
|
|
311
332
|
function canBecomeDdLine(text, confirmed) {
|
|
@@ -382,6 +403,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
|
|
|
382
403
|
prevLineWasText: false,
|
|
383
404
|
prevLineWasValidDef: false,
|
|
384
405
|
paragraphHasUnpairedRun: false,
|
|
406
|
+
openBracket: null,
|
|
385
407
|
htmlFlowSinceBlank: false,
|
|
386
408
|
htmlSeamPending: false,
|
|
387
409
|
phasePoisonedAt: Infinity,
|
|
@@ -389,11 +411,11 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
|
|
|
389
411
|
};
|
|
390
412
|
}
|
|
391
413
|
function isPlausibleLinkDefRest(rest) {
|
|
392
|
-
const t = rest
|
|
414
|
+
const t = mdTrim(rest);
|
|
393
415
|
if (t === "") return false;
|
|
394
416
|
const destEnd = linkDestinationEnd(t);
|
|
395
417
|
if (destEnd === -1) return false;
|
|
396
|
-
const after = t.slice(destEnd)
|
|
418
|
+
const after = mdTrim(t.slice(destEnd));
|
|
397
419
|
if (after === "") return true;
|
|
398
420
|
const opener = after[0];
|
|
399
421
|
if (opener !== '"' && opener !== "'" && opener !== "(") return false;
|
|
@@ -403,7 +425,7 @@ function isPlausibleLinkDefRest(rest) {
|
|
|
403
425
|
i += 1;
|
|
404
426
|
continue;
|
|
405
427
|
}
|
|
406
|
-
if (after[i] === closer) return after.slice(i + 1)
|
|
428
|
+
if (after[i] === closer) return isMdBlank(after.slice(i + 1));
|
|
407
429
|
}
|
|
408
430
|
return false;
|
|
409
431
|
}
|
|
@@ -440,6 +462,36 @@ function linkDestinationEnd(t) {
|
|
|
440
462
|
if (balance !== 0 || i === 0) return -1;
|
|
441
463
|
return i;
|
|
442
464
|
}
|
|
465
|
+
function inlineResourceEnd(text, openIdx) {
|
|
466
|
+
let i = openIdx + 1;
|
|
467
|
+
const skipWs = () => {
|
|
468
|
+
while (i < text.length && (text[i] === " " || text[i] === " ")) i += 1;
|
|
469
|
+
};
|
|
470
|
+
skipWs();
|
|
471
|
+
if (text[i] === ")") return i + 1;
|
|
472
|
+
const destEnd = linkDestinationEnd(text.slice(i));
|
|
473
|
+
if (destEnd === -1) return -1;
|
|
474
|
+
i += destEnd;
|
|
475
|
+
const beforeWs = i;
|
|
476
|
+
skipWs();
|
|
477
|
+
if (text[i] === ")") return i + 1;
|
|
478
|
+
if (i === beforeWs) return -1;
|
|
479
|
+
const opener = text[i];
|
|
480
|
+
if (opener !== '"' && opener !== "'" && opener !== "(") return -1;
|
|
481
|
+
const closer = opener === "(" ? ")" : opener;
|
|
482
|
+
for (i += 1; i < text.length; i++) {
|
|
483
|
+
if (text[i] === "\\") {
|
|
484
|
+
i += 1;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (text[i] === closer) {
|
|
488
|
+
i += 1;
|
|
489
|
+
skipWs();
|
|
490
|
+
return text[i] === ")" ? i + 1 : -1;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return -1;
|
|
494
|
+
}
|
|
443
495
|
function classifyBlockStart(text, indent, defListEnabled) {
|
|
444
496
|
if (indent >= 4) return true;
|
|
445
497
|
if (LIST_MARKER_RE.test(text) || FOOTNOTE_DEF_RE.test(text)) return true;
|
|
@@ -457,12 +509,13 @@ function computeFreezeBoundary(text, options, resume) {
|
|
|
457
509
|
let end = text.indexOf("\n", start);
|
|
458
510
|
if (end === -1) end = text.length;
|
|
459
511
|
const confirmed = end < text.length;
|
|
460
|
-
const
|
|
512
|
+
const rawLine = text.slice(start, end);
|
|
513
|
+
const lineText = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
461
514
|
const ln = {
|
|
462
515
|
start,
|
|
463
516
|
end,
|
|
464
517
|
text: lineText,
|
|
465
|
-
blank: confirmed && lineText
|
|
518
|
+
blank: confirmed && isMdBlank(lineText),
|
|
466
519
|
indent: computeIndent(lineText)
|
|
467
520
|
};
|
|
468
521
|
if (!confirmed) {
|
|
@@ -551,7 +604,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
551
604
|
const isBlockStart = cp.prevLineBlank;
|
|
552
605
|
if (cp.htmlSeamPending && !ln.blank && !cp.htmlFlowSinceBlank && !(cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen)) {
|
|
553
606
|
const defShapedLine = DEF_RE.test(ln.text) || FOOTNOTE_DEF_RE.test(ln.text);
|
|
554
|
-
const commentOnly = ln.text.replace(/<!--[\s\S]*?-->/g, " ").replace(/<!--[\s\S]*$/, " ").
|
|
607
|
+
const commentOnly = ln.text.replace(/<!--[\s\S]*?-->/g, " ").replace(/<!--[\s\S]*$/, " ").replace(/[ \t\r]/g, "") === "";
|
|
555
608
|
if (!defShapedLine && !commentOnly) {
|
|
556
609
|
cp.htmlSeamPending = false;
|
|
557
610
|
}
|
|
@@ -572,13 +625,14 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
572
625
|
const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen;
|
|
573
626
|
if (cp.inFence) {
|
|
574
627
|
const close = FENCE_RE.exec(ln.text);
|
|
575
|
-
if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && ln.text.
|
|
628
|
+
if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && isMdBlank(ln.text.slice(close[0].length))) {
|
|
576
629
|
cp.inFence = false;
|
|
577
630
|
cp.fenceChar = "";
|
|
578
631
|
cp.fenceLen = 0;
|
|
579
632
|
}
|
|
580
633
|
cp.blankRun = 0;
|
|
581
634
|
cp.paragraphHasUnpairedRun = false;
|
|
635
|
+
cp.openBracket = null;
|
|
582
636
|
cp.prevLineBlank = false;
|
|
583
637
|
cp.prevLineWasText = false;
|
|
584
638
|
cp.prevLineWasValidDef = false;
|
|
@@ -600,6 +654,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
600
654
|
cp.openIndent = ln.indent;
|
|
601
655
|
cp.blankRun = 0;
|
|
602
656
|
cp.paragraphHasUnpairedRun = false;
|
|
657
|
+
cp.openBracket = null;
|
|
603
658
|
cp.prevLineBlank = false;
|
|
604
659
|
cp.prevLineWasText = false;
|
|
605
660
|
cp.prevLineWasValidDef = false;
|
|
@@ -608,12 +663,13 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
608
663
|
}
|
|
609
664
|
if (cp.inMath) {
|
|
610
665
|
const close = MATH_RUN_RE.exec(ln.text);
|
|
611
|
-
if (close && close[1].length >= cp.mathFenceLen && ln.text.
|
|
666
|
+
if (close && close[1].length >= cp.mathFenceLen && isMdBlank(ln.text.slice(close[0].length))) {
|
|
612
667
|
cp.inMath = false;
|
|
613
668
|
cp.mathFenceLen = 0;
|
|
614
669
|
}
|
|
615
670
|
cp.blankRun = 0;
|
|
616
671
|
cp.paragraphHasUnpairedRun = false;
|
|
672
|
+
cp.openBracket = null;
|
|
617
673
|
cp.prevLineBlank = false;
|
|
618
674
|
cp.prevLineWasText = false;
|
|
619
675
|
cp.prevLineWasValidDef = false;
|
|
@@ -635,6 +691,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
635
691
|
cp.openIndent = ln.indent;
|
|
636
692
|
cp.blankRun = 0;
|
|
637
693
|
cp.paragraphHasUnpairedRun = false;
|
|
694
|
+
cp.openBracket = null;
|
|
638
695
|
cp.prevLineBlank = false;
|
|
639
696
|
cp.prevLineWasText = false;
|
|
640
697
|
cp.prevLineWasValidDef = false;
|
|
@@ -658,6 +715,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
658
715
|
defListSettled: null
|
|
659
716
|
});
|
|
660
717
|
cp.paragraphHasUnpairedRun = false;
|
|
718
|
+
cp.openBracket = null;
|
|
661
719
|
cp.htmlFlowSinceBlank = false;
|
|
662
720
|
cp.prevLineBlank = true;
|
|
663
721
|
cp.prevLineWasText = false;
|
|
@@ -670,13 +728,13 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
670
728
|
} else if (LIST_MARKER_RE.test(ln.text) || FOOTNOTE_DEF_RE.test(ln.text) || cp.defListEnabled && DEF_LIST_DD_RE.test(ln.text) || ln.indent >= 4) {
|
|
671
729
|
cp.hazardVerdict = true;
|
|
672
730
|
}
|
|
673
|
-
const tagStart = ln.indent <= 3 ? /^<\/?([A-Za-z][A-Za-z0-9-]*)/.exec(ln.text
|
|
731
|
+
const tagStart = ln.indent <= 3 ? /^<\/?([A-Za-z][A-Za-z0-9-]*)/.exec(mdTrimStart(ln.text)) : null;
|
|
674
732
|
if (tagStart) {
|
|
675
733
|
cp.htmlFlowSinceBlank = true;
|
|
676
734
|
if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
|
|
677
735
|
}
|
|
678
736
|
const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
|
|
679
|
-
const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(ln.text
|
|
737
|
+
const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(mdTrimStart(ln.text));
|
|
680
738
|
const { masked, unpaired } = inRawText ? { masked: null, unpaired: false } : maskIntraLineCodeSpans(ln.text, cp.paragraphHasUnpairedRun);
|
|
681
739
|
if (unpaired) cp.paragraphHasUnpairedRun = true;
|
|
682
740
|
const scanText = masked ?? ln.text;
|
|
@@ -694,28 +752,50 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
694
752
|
if (key && !cp.defs.has(key)) cp.defs.set(key, ln.end);
|
|
695
753
|
}
|
|
696
754
|
}
|
|
697
|
-
if (cp.referenceTaint
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
while ((m = REF_RE.exec(scanText)) !== null) {
|
|
702
|
-
const follow = scanText[m.index + m[0].length];
|
|
703
|
-
if (follow === "(") continue;
|
|
704
|
-
if (follow === ":" && m.index === defBracket) continue;
|
|
705
|
-
const inner = m[1];
|
|
755
|
+
if (cp.referenceTaint) {
|
|
756
|
+
const pushRef = (offset, inner, followAt) => {
|
|
757
|
+
const follow = scanText[followAt];
|
|
758
|
+
if (follow === "(" && inlineResourceEnd(scanText, followAt) !== -1) return;
|
|
706
759
|
let label;
|
|
707
760
|
let footnote = false;
|
|
708
761
|
if (inner.startsWith("^")) {
|
|
709
762
|
footnote = true;
|
|
710
763
|
label = normalizeLabel(inner.slice(1));
|
|
711
764
|
} else if (follow === "[") {
|
|
712
|
-
const explicit = /^\[((?:[^[\]\\]|\\.)*)\]/.exec(scanText.slice(
|
|
765
|
+
const explicit = /^\[((?:[^[\]\\]|\\.)*)\]/.exec(scanText.slice(followAt));
|
|
713
766
|
label = normalizeLabel(explicit && explicit[1] ? explicit[1] : inner);
|
|
714
767
|
} else {
|
|
715
768
|
label = normalizeLabel(inner);
|
|
716
769
|
}
|
|
717
|
-
if (
|
|
718
|
-
|
|
770
|
+
if (label) cp.unresolvedRefs.push({ offset, label, footnote });
|
|
771
|
+
};
|
|
772
|
+
const pending = cp.openBracket;
|
|
773
|
+
cp.openBracket = null;
|
|
774
|
+
if (pending) {
|
|
775
|
+
const close = firstUnescaped(scanText, "]");
|
|
776
|
+
const open = firstUnescaped(scanText, "[");
|
|
777
|
+
const cont = (t) => t.replace(/^ {0,3}>[ \t]?/, "");
|
|
778
|
+
if (close !== -1 && (open === -1 || close < open)) {
|
|
779
|
+
pushRef(pending.offset, `${pending.text}
|
|
780
|
+
${cont(scanText.slice(0, close))}`, close + 1);
|
|
781
|
+
} else if (close === -1 && open === -1) {
|
|
782
|
+
cp.openBracket = { offset: pending.offset, text: `${pending.text}
|
|
783
|
+
${cont(scanText)}` };
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (scanText.includes("[")) {
|
|
787
|
+
const defBracket = validDef ? def.index + def[0].indexOf("[") : -1;
|
|
788
|
+
REF_RE.lastIndex = 0;
|
|
789
|
+
let m;
|
|
790
|
+
while ((m = REF_RE.exec(scanText)) !== null) {
|
|
791
|
+
const followAt = m.index + m[0].length;
|
|
792
|
+
if (scanText[followAt] === ":" && m.index === defBracket) continue;
|
|
793
|
+
pushRef(ln.start + m.index, m[1], followAt);
|
|
794
|
+
}
|
|
795
|
+
const trailingOpen = lastUnclosedBracket(scanText);
|
|
796
|
+
if (trailingOpen !== -1) {
|
|
797
|
+
cp.openBracket = { offset: ln.start + trailingOpen, text: scanText.slice(trailingOpen + 1) };
|
|
798
|
+
}
|
|
719
799
|
}
|
|
720
800
|
}
|
|
721
801
|
const rawSpans = [];
|
|
@@ -776,7 +856,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
776
856
|
if (scanText[pi + 2] === ">") {
|
|
777
857
|
rawSpans.push([pi, pi + 3]);
|
|
778
858
|
pos = pi + 3;
|
|
779
|
-
if (scanText.slice(0, pi)
|
|
859
|
+
if (!isMdBlank(scanText.slice(0, pi)) || ln.indent > 3) poisonRawDivergence();
|
|
780
860
|
continue;
|
|
781
861
|
}
|
|
782
862
|
rawSpans.push([pi, pi + 2]);
|
|
@@ -827,7 +907,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
827
907
|
applyTag(tag, closing);
|
|
828
908
|
}
|
|
829
909
|
if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
|
|
830
|
-
if (tagText.slice(0, lastCommentOpenerIdx)
|
|
910
|
+
if (!isMdBlank(tagText.slice(0, lastCommentOpenerIdx))) {
|
|
831
911
|
cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
|
|
832
912
|
}
|
|
833
913
|
}
|
|
@@ -1053,13 +1133,14 @@ function rebaseDualWalk(node, segments, maxEnd, offsetDelta, lineDelta) {
|
|
|
1053
1133
|
const position = node.position;
|
|
1054
1134
|
if (position) {
|
|
1055
1135
|
for (const point of [position.start, position.end]) {
|
|
1136
|
+
if (!point) continue;
|
|
1056
1137
|
const seg = point.offset !== void 0 && point.offset <= maxEnd ? segments.find((s) => point.offset >= s.injStart && point.offset <= s.injEnd) : void 0;
|
|
1057
1138
|
if (seg) {
|
|
1058
1139
|
point.offset += seg.offsetDelta;
|
|
1059
|
-
point.line += seg.lineDelta;
|
|
1140
|
+
if (typeof point.line === "number") point.line += seg.lineDelta;
|
|
1060
1141
|
} else {
|
|
1061
1142
|
if (point.offset !== void 0) point.offset += offsetDelta;
|
|
1062
|
-
point.line += lineDelta;
|
|
1143
|
+
if (typeof point.line === "number") point.line += lineDelta;
|
|
1063
1144
|
}
|
|
1064
1145
|
}
|
|
1065
1146
|
}
|
|
@@ -1226,6 +1307,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
|
|
|
1226
1307
|
nextIdx = pairIdx + 1 + stripped;
|
|
1227
1308
|
const candidate = visibles[nextIdx];
|
|
1228
1309
|
if (!candidate) return null;
|
|
1310
|
+
if (start === void 0 && sepBuffer.some((sep) => sep.type !== "text" || sep.value !== "\n")) {
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1229
1313
|
if (start !== void 0) {
|
|
1230
1314
|
const cStart = candidate.position?.start?.offset;
|
|
1231
1315
|
const cEnd = candidate.position?.end?.offset;
|
|
@@ -1242,6 +1326,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
|
|
|
1242
1326
|
const trailingGaps = sawContent ? trailingStripped : Math.max(0, visibles.length - 1);
|
|
1243
1327
|
const seam = visibles.length > 0 && tailWrapVisible ? 1 : 0;
|
|
1244
1328
|
const last = out[out.length - 1];
|
|
1329
|
+
if (last !== void 0 && last.type === "text" && last.position !== void 0 && last.value.trim() === "") {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1245
1332
|
const lastIsLiteral = last !== void 0 && last.type === "text" && last.value.trim() !== "";
|
|
1246
1333
|
const litOwnerEnd = pairIdx >= 0 ? visibles[pairIdx].position?.end?.offset : void 0;
|
|
1247
1334
|
const litEnd = lastIsLiteral ? last.position?.end?.offset : void 0;
|
|
@@ -1279,8 +1366,16 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
|
|
|
1279
1366
|
const v = lastPaired.value;
|
|
1280
1367
|
const lastLt = v.lastIndexOf("<");
|
|
1281
1368
|
if (lastLt !== -1 && /^<[!?]/.test(v.slice(lastLt))) return null;
|
|
1369
|
+
const lastOut = out[out.length - 1];
|
|
1370
|
+
const outEnd = lastOut?.type === "element" ? lastOut.position?.end?.offset : void 0;
|
|
1371
|
+
const blockEnd = lastPaired.position?.end?.offset;
|
|
1372
|
+
if (outEnd !== void 0 && blockEnd !== void 0 && outEnd < blockEnd) return null;
|
|
1282
1373
|
}
|
|
1283
1374
|
if (sepBuffer.length !== trailingGaps && sepBuffer.length !== trailingGaps + 1) return null;
|
|
1375
|
+
for (let j = pairIdx + 1; j < visibles.length; j++) {
|
|
1376
|
+
const v = visibles[j];
|
|
1377
|
+
if (v.type === "html" && !/^\s*<[!?]/.test(v.value)) return null;
|
|
1378
|
+
}
|
|
1284
1379
|
for (let i = 0; i < trailingGaps + seam; i++) {
|
|
1285
1380
|
out.push({ type: "text", value: "\n" });
|
|
1286
1381
|
}
|
|
@@ -1875,6 +1970,7 @@ function createRegistry(onEmpty) {
|
|
|
1875
1970
|
}
|
|
1876
1971
|
},
|
|
1877
1972
|
contributeLabels(symbol, footnotes, links) {
|
|
1973
|
+
if (!this.chunkOrder.includes(symbol)) return;
|
|
1878
1974
|
const data = this.chunkData.get(symbol);
|
|
1879
1975
|
if (data) {
|
|
1880
1976
|
data.ownFootnoteLabels = footnotes;
|
|
@@ -1899,6 +1995,7 @@ function createRegistry(onEmpty) {
|
|
|
1899
1995
|
this._notify();
|
|
1900
1996
|
},
|
|
1901
1997
|
contributeChunkData(symbol, data) {
|
|
1998
|
+
if (!this.chunkOrder.includes(symbol)) return;
|
|
1902
1999
|
this.chunkData.set(symbol, data);
|
|
1903
2000
|
this.labelSet.footnoteLabels = /* @__PURE__ */ new Set();
|
|
1904
2001
|
this.labelSet.linkLabels = /* @__PURE__ */ new Set();
|
|
@@ -2004,14 +2101,16 @@ var import_rehype_unwrap_images = __toESM(require("rehype-unwrap-images"), 1);
|
|
|
2004
2101
|
var import_unist_util_visit4 = require("unist-util-visit");
|
|
2005
2102
|
var IMAGE_TAGS = /* @__PURE__ */ new Set(["img", "cross-chunk-image"]);
|
|
2006
2103
|
var LINK_TAGS = /* @__PURE__ */ new Set(["a", "cross-chunk-link"]);
|
|
2007
|
-
function applicable(node, inLink) {
|
|
2104
|
+
function applicable(node, inLink, seen) {
|
|
2008
2105
|
let image = 0 /* Unknown */;
|
|
2009
2106
|
for (const child of node.children) {
|
|
2010
2107
|
if (child.type === "text" && /^\s*$/.test(child.value)) continue;
|
|
2011
2108
|
if (child.type === "element" && IMAGE_TAGS.has(child.tagName)) {
|
|
2109
|
+
if (child.tagName !== "img") seen.placeholder = true;
|
|
2012
2110
|
image = 1 /* ContainsImage */;
|
|
2013
2111
|
} else if (!inLink && child.type === "element" && LINK_TAGS.has(child.tagName)) {
|
|
2014
|
-
|
|
2112
|
+
if (child.tagName !== "a") seen.placeholder = true;
|
|
2113
|
+
const inner = applicable(child, true, seen);
|
|
2015
2114
|
if (inner === 2 /* ContainsOther */) return 2 /* ContainsOther */;
|
|
2016
2115
|
if (inner === 1 /* ContainsImage */) image = 1 /* ContainsImage */;
|
|
2017
2116
|
} else {
|
|
@@ -2023,9 +2122,9 @@ function applicable(node, inLink) {
|
|
|
2023
2122
|
function rehypeUnwrapCrossChunkImages() {
|
|
2024
2123
|
return function transform(tree) {
|
|
2025
2124
|
(0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => {
|
|
2026
|
-
if (node.tagName
|
|
2027
|
-
|
|
2028
|
-
|
|
2125
|
+
if (node.tagName !== "p" || !parent || typeof index !== "number") return;
|
|
2126
|
+
const seen = { placeholder: false };
|
|
2127
|
+
if (applicable(node, false, seen) === 1 /* ContainsImage */ && seen.placeholder) {
|
|
2029
2128
|
parent.children.splice(index, 1, ...node.children);
|
|
2030
2129
|
return [import_unist_util_visit4.SKIP, index];
|
|
2031
2130
|
}
|
|
@@ -3513,14 +3612,12 @@ function isProtocolAllowed(url, allowed) {
|
|
|
3513
3612
|
}
|
|
3514
3613
|
function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
|
|
3515
3614
|
rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
|
|
3516
|
-
const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
|
|
3517
|
-
if (transformed == null) return "";
|
|
3518
|
-
const stringUrl = String(transformed);
|
|
3519
|
-
if (stringUrl === "") return "";
|
|
3520
3615
|
const callerProtocols = schema.protocols;
|
|
3521
3616
|
const allowed = callerProtocols === void 0 || callerProtocols === null ? sanitizeSchema.protocols?.[key] : callerProtocols[key];
|
|
3522
|
-
if (
|
|
3523
|
-
|
|
3617
|
+
if (allowed && allowed.length > 0 && !isProtocolAllowed(rawUrl, allowed)) return null;
|
|
3618
|
+
const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
|
|
3619
|
+
if (transformed == null) return null;
|
|
3620
|
+
return String(transformed);
|
|
3524
3621
|
}
|
|
3525
3622
|
|
|
3526
3623
|
// src/plugins/defs.ts
|
|
@@ -3912,6 +4009,20 @@ var LITERAL_CONTENT_CLOSE_REGEX = {
|
|
|
3912
4009
|
math: /<\/math\s*>/gi,
|
|
3913
4010
|
svg: /<\/svg\s*>/gi
|
|
3914
4011
|
};
|
|
4012
|
+
function restOfLineIsBlank(content, pos) {
|
|
4013
|
+
for (let i = pos; i < content.length; i++) {
|
|
4014
|
+
const c = content[i];
|
|
4015
|
+
if (c === "\n") return true;
|
|
4016
|
+
if (c !== " " && c !== " " && c !== "\r") return false;
|
|
4017
|
+
}
|
|
4018
|
+
return true;
|
|
4019
|
+
}
|
|
4020
|
+
function lineHasBacktick(content, pos) {
|
|
4021
|
+
for (let i = pos; i < content.length && content[i] !== "\n"; i++) {
|
|
4022
|
+
if (content[i] === "`") return true;
|
|
4023
|
+
}
|
|
4024
|
+
return false;
|
|
4025
|
+
}
|
|
3915
4026
|
function isAtLineStart(content, pos) {
|
|
3916
4027
|
let i = pos - 1;
|
|
3917
4028
|
let spaces = 0;
|
|
@@ -3954,7 +4065,7 @@ function splitByProtectedRegions(content) {
|
|
|
3954
4065
|
if (multilineStart !== -1) {
|
|
3955
4066
|
if (char === multilineFenceMarker) {
|
|
3956
4067
|
const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
|
|
3957
|
-
if (runLen >= multilineFenceLength && isAtLineStart(content, i)) {
|
|
4068
|
+
if (runLen >= multilineFenceLength && isAtLineStart(content, i) && restOfLineIsBlank(content, i + runLen)) {
|
|
3958
4069
|
pushProtected(multilineStart, i + runLen);
|
|
3959
4070
|
multilineStart = -1;
|
|
3960
4071
|
multilineFenceMarker = null;
|
|
@@ -3970,7 +4081,7 @@ function splitByProtectedRegions(content) {
|
|
|
3970
4081
|
}
|
|
3971
4082
|
if (char === "`" || char === "~") {
|
|
3972
4083
|
const runLen = getRepeatedMarkerLength(content, i, char);
|
|
3973
|
-
if (runLen >= 3 && isAtLineStart(content, i)) {
|
|
4084
|
+
if (runLen >= 3 && isAtLineStart(content, i) && !(char === "`" && lineHasBacktick(content, i + runLen))) {
|
|
3974
4085
|
multilineStart = i;
|
|
3975
4086
|
multilineFenceMarker = char;
|
|
3976
4087
|
multilineFenceLength = runLen;
|