@ai-react-markdown/engine 2.4.2 → 2.4.5
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/README.md +95 -0
- package/dist/index.cjs +142 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -41
- package/dist/index.d.ts +77 -41
- package/dist/index.dev.cjs +142 -54
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +142 -54
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +142 -54
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @ai-react-markdown/engine
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@ai-react-markdown/engine)
|
|
4
|
+
[](https://www.npmjs.com/package/@ai-react-markdown/engine)
|
|
5
|
+
[](https://bundlephobia.com/package/@ai-react-markdown/engine)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](#installation)
|
|
10
|
+
[](https://github.com/AIEPhoenix/ai-react-markdown/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
[](https://github.com/AIEPhoenix/ai-react-markdown/actions/workflows/ci.yml)
|
|
13
|
+
[](https://github.com/AIEPhoenix/ai-react-markdown/actions/workflows/release.yml)
|
|
14
|
+
[](https://github.com/AIEPhoenix/ai-react-markdown)
|
|
15
|
+
|
|
3
16
|
Framework-agnostic Markdown engine for [ai-react-markdown](https://github.com/AIEPhoenix/ai-react-markdown) — incremental parsing, LaTeX preprocessing, definition/footnote machinery, and the unified plugin pipeline. Takes Markdown text in, produces a [hast](https://github.com/syntax-tree/hast) tree plus incremental-parse state out; rendering that tree is the job of a framework adapter such as [`@ai-react-markdown/core`](https://www.npmjs.com/package/@ai-react-markdown/core) (React).
|
|
4
17
|
|
|
5
18
|
> **Status: internal supplier.** This package exists to serve
|
|
@@ -10,12 +23,94 @@ Framework-agnostic Markdown engine for [ai-react-markdown](https://github.com/AI
|
|
|
10
23
|
> instead; this package is interesting to you only if you are building a
|
|
11
24
|
> framework adapter of your own.
|
|
12
25
|
|
|
26
|
+
## What's inside
|
|
27
|
+
|
|
28
|
+
Everything is exported from the package root (`import { … } from '@ai-react-markdown/engine'`); the barrel is grouped by layer:
|
|
29
|
+
|
|
30
|
+
| Layer | Modules | Highlights |
|
|
31
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| Preprocessors | `preprocessors/latex`, `preprocessors/remend`, `preprocessAIMDContent` | `preprocessLaTeX(text)` (currency `$`, `\[…\]` / `\(…\)` normalization, code-fence and inline-code protection), `createIncrementalLatexPreprocessor()` for append-only streams, `remend` for unterminated-markup mending |
|
|
33
|
+
| Incremental parsing | `incrementalParse/*` | `advanceIncrementalParse(state, content, options)` — the prefix-freeze engine: a line scanner decides a verified-safe freeze boundary, only the tail re-parses, and the two trees are spliced; every frame is deep-equal to a full parse (enforced by the arbiter suites) or falls back to one |
|
|
34
|
+
| Pipeline assembly | `markdown/*`, `pluginChain`, `plugins/catalog`, `customMdastHandlers`, `remarkInjectPhantomDefs`, `rehypeRebaseHashLinks`, `rehypeFooterAdorn` | `buildCoreRemarkPlugins` / `buildCoreRehypePlugins` / `buildCoreRemarkRehypeOptions` — the exact chains the React renderer uses; the sealed engine-plugin catalog (`highlight`, `definitionList`, `removeComments`, `smartypants`, `pangu`, `defaultEnginePlugins`) |
|
|
35
|
+
| Cross-chunk coordination | `documentRegistry`, `collectDefLabels`, `extractContributions`, `extractDefBodiesFromHast`, `crossChunkUrlSanitize` | `createRegistry()` — the per-document store that numbers footnotes and resolves link definitions across chunks; `sanitizeCrossChunkUrl()` mirrors the standalone two-gate URL policy |
|
|
36
|
+
| Sanitization | `sanitizeSchema`, `extendSanitizeSchema`, `markdown/urlTransform` | The library default `rehype-sanitize` schema (read-only singleton — clone with `extendSanitizeSchema`), `defaultUrlTransform` |
|
|
37
|
+
| Streaming | `smoothStream/controller` | `createSmoothStreamController()` — the framework-agnostic typewriter pacing state machine behind `<AIMarkdownSmoothStream>`, with `SMOOTH_STREAM_PACING_PRESETS` |
|
|
38
|
+
| Leaves | `hastPredicates`, `normalizeId`, `shortenDocumentId`, `devStageTimings`, `fixtures/scenarios` | Small pure helpers and the shared test corpus |
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @ai-react-markdown/engine
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Dual ESM/CJS build with types for both. No React dependency. The only peer is `katex` (`^0.16 || ^0.17`, **optional** — needed only if you render math). It ships transitively via `rehype-katex`, so hoisted installers resolve it automatically; strict-isolation installers (yarn PnP, `pnpm --node-linker=isolated`) must install it explicitly in your app.
|
|
47
|
+
|
|
48
|
+
## Example: the LaTeX preprocessor on its own
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { preprocessLaTeX } from '@ai-react-markdown/engine';
|
|
52
|
+
|
|
53
|
+
preprocessLaTeX('Price is $100, and \\(x^2\\) is inline math.');
|
|
54
|
+
// → 'Price is \\$100, and $$x^2$$ is inline math.'
|
|
55
|
+
// (currency `$` escaped; `\\(…\\)` normalized to the `$$…$$` form remark-math's inline rule accepts)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The same function runs inside `@ai-react-markdown/core` before every parse; the incremental variant (`createIncrementalLatexPreprocessor`) reuses work across append-only frames.
|
|
59
|
+
|
|
60
|
+
## Example: driving the incremental parser
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import {
|
|
64
|
+
advanceIncrementalParse,
|
|
65
|
+
buildCoreRemarkPlugins,
|
|
66
|
+
buildCoreRehypePlugins,
|
|
67
|
+
buildCoreRemarkRehypeOptions,
|
|
68
|
+
defaultEnginePlugins,
|
|
69
|
+
sanitizeSchema,
|
|
70
|
+
} from '@ai-react-markdown/engine';
|
|
71
|
+
|
|
72
|
+
const options = {
|
|
73
|
+
remarkPlugins: buildCoreRemarkPlugins(defaultEnginePlugins),
|
|
74
|
+
rehypePlugins: buildCoreRehypePlugins(sanitizeSchema, ''),
|
|
75
|
+
remarkRehypeOptions: buildCoreRemarkRehypeOptions(false),
|
|
76
|
+
depsKey: [],
|
|
77
|
+
defListEnabled: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let state = null;
|
|
81
|
+
for (const frame of ['# Hello', '# Hello\n\nworld', '# Hello\n\nworld and more']) {
|
|
82
|
+
const result = advanceIncrementalParse(state, frame, options);
|
|
83
|
+
state = result.nextState;
|
|
84
|
+
// result.hast — the full-document hast for this frame
|
|
85
|
+
// result.usedIncremental / result.boundary — whether the frame spliced, and where
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`AdvanceOptions` is documented in `incrementalParse/advanceIncrementalParse.ts`; the React renderer's `MarkdownContent` is the reference consumer.
|
|
90
|
+
|
|
91
|
+
## Verification
|
|
92
|
+
|
|
93
|
+
The incremental engine ships with a five-layer equivalence stack (fixture pins, fuzz arbiter, direction battery, exhaustive census, arbiter-sensitivity meta-suite) plus a release-gate soak (`scripts/run-soak.sh`); the full record lives in `src/experiments/prefixFreeze/README.md`. Every reachable divergence found so far is pinned as a deterministic test.
|
|
94
|
+
|
|
13
95
|
## Runtime support
|
|
14
96
|
|
|
15
97
|
Pure computation over strings and syntax trees: no DOM access, no
|
|
16
98
|
Node-only APIs, and no unguarded environment reads. Runs in browsers,
|
|
17
99
|
Node, workers, and embedded JS runtimes (e.g. Hermes/JavaScriptCore).
|
|
18
100
|
|
|
101
|
+
## Versioning
|
|
102
|
+
|
|
103
|
+
Lockstep with `@ai-react-markdown/core`, which pins this package **exactly** — the export surface follows what core consumes and may change in any release before 3.0.0 (see the status note above). Release notes: [release highlights](https://github.com/AIEPhoenix/ai-react-markdown/blob/main/docs/release-highlights.md).
|
|
104
|
+
|
|
105
|
+
## Package family
|
|
106
|
+
|
|
107
|
+
| Package | Role | Version policy |
|
|
108
|
+
| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
109
|
+
| [`@ai-react-markdown/core`](https://www.npmjs.com/package/@ai-react-markdown/core) | The React renderer — `<AIMarkdown>`, `<AIMarkdownSmoothStream>`, `<AIMarkdownDocuments>`, hooks, providers | Release train |
|
|
110
|
+
| [`@ai-react-markdown/mantine`](https://www.npmjs.com/package/@ai-react-markdown/mantine) | Mantine UI bindings — themed typography, code-highlight tabs, Mermaid, color-scheme wiring | Release train (lockstep with core) |
|
|
111
|
+
| [`@ai-react-markdown/engine`](https://www.npmjs.com/package/@ai-react-markdown/engine) | Framework-agnostic engine — incremental parsing, LaTeX preprocessing, plugin pipeline, cross-chunk registry | Release train (lockstep, pinned exactly by core; internal supplier) |
|
|
112
|
+
| [`@ai-react-markdown/remark-mark-highlight`](https://www.npmjs.com/package/@ai-react-markdown/remark-mark-highlight) | remark plugin for `==mark==` highlight syntax | Independent semver |
|
|
113
|
+
|
|
19
114
|
## License
|
|
20
115
|
|
|
21
116
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -269,6 +269,11 @@ var defaultUrlTransform = (value) => {
|
|
|
269
269
|
var import_micromark_util_html_tag_name = require("micromark-util-html-tag-name");
|
|
270
270
|
var import_micromark_util_normalize_identifier = require("micromark-util-normalize-identifier");
|
|
271
271
|
var TYPE6_NAMES = new Set(import_micromark_util_html_tag_name.htmlBlockNames);
|
|
272
|
+
var TABLE_PART_NAMES = /* @__PURE__ */ new Set(["td", "th", "tr", "tbody", "thead", "tfoot", "caption", "col", "colgroup"]);
|
|
273
|
+
var TYPE1_NAMES = /* @__PURE__ */ new Set(["script", "pre", "style", "textarea"]);
|
|
274
|
+
var TYPE6_START_RE = /^<\/?([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r]|\/?>|$)/;
|
|
275
|
+
var TYPE1_START_RE = /^<(script|pre|style|textarea)(?:[ \t\r]|>|$)/i;
|
|
276
|
+
var TYPE7_LINE_RE = /^<(\/?)([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r][^>]*|\/)?>[ \t\r]*$/;
|
|
272
277
|
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
273
278
|
"area",
|
|
274
279
|
"base",
|
|
@@ -407,7 +412,11 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
|
|
|
407
412
|
htmlFlowSinceBlank: false,
|
|
408
413
|
htmlSeamPending: false,
|
|
409
414
|
phasePoisonedAt: Infinity,
|
|
410
|
-
pendingTruncatedTags: []
|
|
415
|
+
pendingTruncatedTags: [],
|
|
416
|
+
pendingTruncatedCloses: [],
|
|
417
|
+
tagAcrossLines: false,
|
|
418
|
+
tagAcrossLinesIndent: 0,
|
|
419
|
+
htmlFlowReal: false
|
|
411
420
|
};
|
|
412
421
|
}
|
|
413
422
|
function isPlausibleLinkDefRest(rest) {
|
|
@@ -704,6 +713,8 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
704
713
|
for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
|
|
705
714
|
cp.pendingTruncatedTags = [];
|
|
706
715
|
}
|
|
716
|
+
cp.pendingTruncatedCloses = [];
|
|
717
|
+
cp.tagAcrossLines = false;
|
|
707
718
|
cp.blankRun += 1;
|
|
708
719
|
cp.lastBlankStart = ln.start;
|
|
709
720
|
cp.candidates.push({
|
|
@@ -717,6 +728,7 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
717
728
|
cp.paragraphHasUnpairedRun = false;
|
|
718
729
|
cp.openBracket = null;
|
|
719
730
|
cp.htmlFlowSinceBlank = false;
|
|
731
|
+
cp.htmlFlowReal = false;
|
|
720
732
|
cp.prevLineBlank = true;
|
|
721
733
|
cp.prevLineWasText = false;
|
|
722
734
|
cp.prevLineWasValidDef = false;
|
|
@@ -732,6 +744,16 @@ function processConfirmedLine(cp, ln, text) {
|
|
|
732
744
|
if (tagStart) {
|
|
733
745
|
cp.htmlFlowSinceBlank = true;
|
|
734
746
|
if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
|
|
747
|
+
if (!cp.htmlFlowReal) {
|
|
748
|
+
const t = mdTrimStart(ln.text);
|
|
749
|
+
const t6 = TYPE6_START_RE.exec(t);
|
|
750
|
+
const t7 = TYPE7_LINE_RE.exec(t);
|
|
751
|
+
if (t6 !== null && TYPE6_NAMES.has(t6[1].toLowerCase()) || TYPE1_START_RE.test(t) || // Type 7 cannot interrupt a paragraph, and excludes the raw-text
|
|
752
|
+
// names (those are type 1 as start tags, paragraph as end tags).
|
|
753
|
+
t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7[2].toLowerCase())) {
|
|
754
|
+
cp.htmlFlowReal = true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
735
757
|
}
|
|
736
758
|
const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
|
|
737
759
|
const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(mdTrimStart(ln.text));
|
|
@@ -872,7 +894,21 @@ ${cont(scanText)}` };
|
|
|
872
894
|
for (const [from, to] of rawSpans) {
|
|
873
895
|
tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
|
|
874
896
|
}
|
|
875
|
-
|
|
897
|
+
let skipTagScan = false;
|
|
898
|
+
if (cp.tagAcrossLines) {
|
|
899
|
+
if (ln.indent < cp.tagAcrossLinesIndent) poisonRawDivergence();
|
|
900
|
+
const gt = ln.text.indexOf(">");
|
|
901
|
+
if (gt === -1) {
|
|
902
|
+
skipTagScan = true;
|
|
903
|
+
} else {
|
|
904
|
+
if (/["']/.test(ln.text.slice(0, gt))) poisonRawDivergence();
|
|
905
|
+
for (const tag of cp.pendingTruncatedCloses) applyTag(tag, true);
|
|
906
|
+
cp.pendingTruncatedCloses = [];
|
|
907
|
+
cp.tagAcrossLines = false;
|
|
908
|
+
tagText = " ".repeat(gt + 1) + tagText.slice(gt + 1);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (!skipTagScan) {
|
|
876
912
|
TAG_OR_COMMENT_RE.lastIndex = 0;
|
|
877
913
|
let m;
|
|
878
914
|
let lastCommentOpenerIdx = -1;
|
|
@@ -902,6 +938,7 @@ ${cont(scanText)}` };
|
|
|
902
938
|
if (cp.commentOpen) continue;
|
|
903
939
|
const closing = m[1] === "/";
|
|
904
940
|
const tag = m[2].toLowerCase();
|
|
941
|
+
if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + m.index);
|
|
905
942
|
const selfClosing = m[3] !== void 0 && /\/\s*$/.test(m[3]);
|
|
906
943
|
if (VOID_TAGS.has(tag) || selfClosing) continue;
|
|
907
944
|
applyTag(tag, closing);
|
|
@@ -922,6 +959,7 @@ ${cont(scanText)}` };
|
|
|
922
959
|
if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
|
|
923
960
|
const closing = mr[1] === "/";
|
|
924
961
|
const tag = mr[2].toLowerCase();
|
|
962
|
+
if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
|
|
925
963
|
const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
|
|
926
964
|
if (VOID_TAGS.has(tag) || selfClosing) continue;
|
|
927
965
|
applyTag(tag, closing);
|
|
@@ -937,7 +975,14 @@ ${cont(scanText)}` };
|
|
|
937
975
|
if (m2) {
|
|
938
976
|
const closing = m2[1] === "/";
|
|
939
977
|
const tag = m2[2].toLowerCase();
|
|
940
|
-
if (
|
|
978
|
+
if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
|
|
979
|
+
if (cp.htmlFlowReal) {
|
|
980
|
+
cp.tagAcrossLines = true;
|
|
981
|
+
cp.tagAcrossLinesIndent = ln.indent;
|
|
982
|
+
}
|
|
983
|
+
if (closing) {
|
|
984
|
+
if (!VOID_TAGS.has(tag) && cp.htmlFlowReal) cp.pendingTruncatedCloses.push(tag);
|
|
985
|
+
} else if (!VOID_TAGS.has(tag)) {
|
|
941
986
|
applyTag(tag, closing);
|
|
942
987
|
const rawLastLt = ln.text.lastIndexOf("<");
|
|
943
988
|
const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
|
|
@@ -1149,6 +1194,8 @@ function rebaseDualWalk(node, segments, maxEnd, offsetDelta, lineDelta) {
|
|
|
1149
1194
|
for (const child of children) rebaseDualWalk(child, segments, maxEnd, offsetDelta, lineDelta);
|
|
1150
1195
|
}
|
|
1151
1196
|
}
|
|
1197
|
+
var TABLE_PART_TAG_RE = /<(?:td|th|tr|tbody|thead|tfoot|caption|col|colgroup)\b/i;
|
|
1198
|
+
var STRAY_SYNTHESIZED_END_TAG_RE = /<\/(?:br|p)\b/i;
|
|
1152
1199
|
function spliceTrees(input) {
|
|
1153
1200
|
const { prevMdast, prevHast, tailMdast, tailHast, content, boundary, injectionPrefix, injectedSegments } = input;
|
|
1154
1201
|
const injectedLen = injectionPrefix.length;
|
|
@@ -1205,6 +1252,12 @@ function spliceTrees(input) {
|
|
|
1205
1252
|
return !(start !== void 0 && start < injectedLen);
|
|
1206
1253
|
});
|
|
1207
1254
|
const tailWrapVisible = tailMdastChildren.some((child) => !isWrapInvisible(child));
|
|
1255
|
+
if (prefixMdast.some((c) => c.type === "html" && TABLE_PART_TAG_RE.test(c.value))) return null;
|
|
1256
|
+
for (const child of tailMdastChildren) {
|
|
1257
|
+
if (isWrapInvisible(child)) continue;
|
|
1258
|
+
if (child.type !== "html") break;
|
|
1259
|
+
if (STRAY_SYNTHESIZED_END_TAG_RE.test(child.value) || TABLE_PART_TAG_RE.test(child.value)) return null;
|
|
1260
|
+
}
|
|
1208
1261
|
const aligned = alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible);
|
|
1209
1262
|
if (aligned === null) return null;
|
|
1210
1263
|
const hastChildren = aligned.children;
|
|
@@ -1330,6 +1383,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
|
|
|
1330
1383
|
return null;
|
|
1331
1384
|
}
|
|
1332
1385
|
const lastIsLiteral = last !== void 0 && last.type === "text" && last.value.trim() !== "";
|
|
1386
|
+
if (lastIsLiteral && pairIdx >= 0 && visibles[pairIdx].type !== "html") {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1333
1389
|
const litOwnerEnd = pairIdx >= 0 ? visibles[pairIdx].position?.end?.offset : void 0;
|
|
1334
1390
|
const litEnd = lastIsLiteral ? last.position?.end?.offset : void 0;
|
|
1335
1391
|
if (lastIsLiteral && last.position !== void 0 && (litEnd === void 0 || litOwnerEnd === void 0)) {
|
|
@@ -1418,8 +1474,9 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
|
|
|
1418
1474
|
}
|
|
1419
1475
|
function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
|
|
1420
1476
|
const firstText = tailHastChildren[0];
|
|
1421
|
-
if (!firstText) return false;
|
|
1422
1477
|
const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
|
|
1478
|
+
if (firstVisible?.type === "html" && STRAY_SYNTHESIZED_END_TAG_RE.test(firstVisible.value)) return null;
|
|
1479
|
+
if (!firstText) return false;
|
|
1423
1480
|
if (!isSeparatorText(firstText)) {
|
|
1424
1481
|
if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
|
|
1425
1482
|
if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
|
|
@@ -1468,6 +1525,9 @@ function countTrailingNewlines(value) {
|
|
|
1468
1525
|
function countNewlines(text, end = text.length) {
|
|
1469
1526
|
let count = 0;
|
|
1470
1527
|
for (let i = text.indexOf("\n"); i !== -1 && i < end; i = text.indexOf("\n", i + 1)) count += 1;
|
|
1528
|
+
for (let i = text.indexOf("\r"); i !== -1 && i < end; i = text.indexOf("\r", i + 1)) {
|
|
1529
|
+
if (text.charCodeAt(i + 1) !== 10) count += 1;
|
|
1530
|
+
}
|
|
1471
1531
|
return count;
|
|
1472
1532
|
}
|
|
1473
1533
|
|
|
@@ -1587,7 +1647,7 @@ function normalizeId(s) {
|
|
|
1587
1647
|
return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
|
|
1588
1648
|
}
|
|
1589
1649
|
function normalizeForMatch(s) {
|
|
1590
|
-
return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\(
|
|
1650
|
+
return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\([!-/:-@[-`{-~])/g, "$1"));
|
|
1591
1651
|
}
|
|
1592
1652
|
|
|
1593
1653
|
// src/components/collectDefLabels.ts
|
|
@@ -1860,17 +1920,8 @@ function phantomSuffixCloser(content) {
|
|
|
1860
1920
|
}
|
|
1861
1921
|
|
|
1862
1922
|
// src/components/extractContributions.ts
|
|
1863
|
-
function fakeAnchorElement(url) {
|
|
1864
|
-
return { type: "element", tagName: "a", properties: { href: url }, children: [] };
|
|
1865
|
-
}
|
|
1866
|
-
function sanitizeDefUrl(url, urlTransform) {
|
|
1867
|
-
if (!urlTransform) return url;
|
|
1868
|
-
const result = urlTransform(url, "href", fakeAnchorElement(url));
|
|
1869
|
-
return result == null ? "" : String(result);
|
|
1870
|
-
}
|
|
1871
1923
|
function* extractContributions(mdast, options = {}) {
|
|
1872
1924
|
const phantomFn = options.phantomFootnoteLabels;
|
|
1873
|
-
const urlTransform = options.urlTransform;
|
|
1874
1925
|
const out = [];
|
|
1875
1926
|
(0, import_unist_util_visit3.visit)(mdast, (n) => {
|
|
1876
1927
|
if (n.type === "footnoteReference") {
|
|
@@ -1898,7 +1949,7 @@ function* extractContributions(mdast, options = {}) {
|
|
|
1898
1949
|
out.push({
|
|
1899
1950
|
kind: "linkDef",
|
|
1900
1951
|
label: normalizeId(d.identifier),
|
|
1901
|
-
url:
|
|
1952
|
+
url: d.url,
|
|
1902
1953
|
title: d.title
|
|
1903
1954
|
});
|
|
1904
1955
|
}
|
|
@@ -3612,8 +3663,7 @@ function isProtocolAllowed(url, allowed) {
|
|
|
3612
3663
|
}
|
|
3613
3664
|
function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
|
|
3614
3665
|
rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
|
|
3615
|
-
const
|
|
3616
|
-
const allowed = callerProtocols === void 0 || callerProtocols === null ? sanitizeSchema.protocols?.[key] : callerProtocols[key];
|
|
3666
|
+
const allowed = Object.hasOwn(schema, "protocols") ? schema.protocols?.[key] : sanitizeSchema.protocols?.[key];
|
|
3617
3667
|
if (allowed && allowed.length > 0 && !isProtocolAllowed(rawUrl, allowed)) return null;
|
|
3618
3668
|
const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
|
|
3619
3669
|
if (transformed == null) return null;
|
|
@@ -4011,23 +4061,28 @@ function lineHasBacktick(content, pos) {
|
|
|
4011
4061
|
}
|
|
4012
4062
|
return false;
|
|
4013
4063
|
}
|
|
4014
|
-
function
|
|
4064
|
+
function lineIndentBefore(content, pos) {
|
|
4015
4065
|
let i = pos - 1;
|
|
4016
|
-
let
|
|
4017
|
-
while (i >= 0 && content[i] === " ") {
|
|
4018
|
-
|
|
4019
|
-
if (spaces > 3) return false;
|
|
4066
|
+
let indent = 0;
|
|
4067
|
+
while (i >= 0 && (content[i] === " " || content[i] === " ")) {
|
|
4068
|
+
indent += content[i] === " " ? 4 : 1;
|
|
4020
4069
|
i--;
|
|
4021
4070
|
}
|
|
4022
|
-
return i < 0 || content[i] === "\n" || content[i] === "\r";
|
|
4071
|
+
return i < 0 || content[i] === "\n" || content[i] === "\r" ? indent : -1;
|
|
4023
4072
|
}
|
|
4024
4073
|
function findClosingBacktickRun(content, start, n) {
|
|
4025
4074
|
let i = start;
|
|
4026
4075
|
while (i < content.length) {
|
|
4027
|
-
|
|
4076
|
+
const ch = content[i];
|
|
4077
|
+
if (ch === "`") {
|
|
4028
4078
|
const runLen = getRepeatedMarkerLength(content, i, "`");
|
|
4029
4079
|
if (runLen === n) return i;
|
|
4030
4080
|
i += runLen;
|
|
4081
|
+
} else if (ch === "\n") {
|
|
4082
|
+
let j = i + 1;
|
|
4083
|
+
while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
|
|
4084
|
+
if (j >= content.length || content[j] === "\n") return -1;
|
|
4085
|
+
i += 1;
|
|
4031
4086
|
} else {
|
|
4032
4087
|
i += 1;
|
|
4033
4088
|
}
|
|
@@ -4040,6 +4095,7 @@ function splitByProtectedRegions(content) {
|
|
|
4040
4095
|
let multilineStart = -1;
|
|
4041
4096
|
let multilineFenceMarker = null;
|
|
4042
4097
|
let multilineFenceLength = 0;
|
|
4098
|
+
let multilineFenceIndent = 0;
|
|
4043
4099
|
function pushProtected(start, end) {
|
|
4044
4100
|
if (start > lastIndex) {
|
|
4045
4101
|
segments.push({ text: content.substring(lastIndex, start), isCode: false });
|
|
@@ -4053,7 +4109,8 @@ function splitByProtectedRegions(content) {
|
|
|
4053
4109
|
if (multilineStart !== -1) {
|
|
4054
4110
|
if (char === multilineFenceMarker) {
|
|
4055
4111
|
const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
|
|
4056
|
-
|
|
4112
|
+
const closerIndent = lineIndentBefore(content, i);
|
|
4113
|
+
if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
|
|
4057
4114
|
pushProtected(multilineStart, i + runLen);
|
|
4058
4115
|
multilineStart = -1;
|
|
4059
4116
|
multilineFenceMarker = null;
|
|
@@ -4069,10 +4126,12 @@ function splitByProtectedRegions(content) {
|
|
|
4069
4126
|
}
|
|
4070
4127
|
if (char === "`" || char === "~") {
|
|
4071
4128
|
const runLen = getRepeatedMarkerLength(content, i, char);
|
|
4072
|
-
|
|
4129
|
+
const openerIndent = lineIndentBefore(content, i);
|
|
4130
|
+
if (runLen >= 3 && openerIndent !== -1 && !(char === "`" && lineHasBacktick(content, i + runLen))) {
|
|
4073
4131
|
multilineStart = i;
|
|
4074
4132
|
multilineFenceMarker = char;
|
|
4075
4133
|
multilineFenceLength = runLen;
|
|
4134
|
+
multilineFenceIndent = openerIndent;
|
|
4076
4135
|
i += runLen;
|
|
4077
4136
|
continue;
|
|
4078
4137
|
}
|
|
@@ -4145,16 +4204,20 @@ function escapeCurrencyDollarSigns(text) {
|
|
|
4145
4204
|
currentLineProcessed += segment;
|
|
4146
4205
|
}
|
|
4147
4206
|
let needEscape = true;
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4207
|
+
const restStart = match.index + 1;
|
|
4208
|
+
const restEnd = i < currencyMatches.length - 1 ? currencyMatches[i + 1].index : text.length;
|
|
4209
|
+
let firstLineBeforeNextMatch = "";
|
|
4210
|
+
if (restEnd - restStart > 0) {
|
|
4211
|
+
let eol = restEnd;
|
|
4212
|
+
for (let k = restStart; k < restEnd; k++) {
|
|
4213
|
+
const c = text.charCodeAt(k);
|
|
4214
|
+
if (c === 10 || c === 13) {
|
|
4215
|
+
eol = k;
|
|
4216
|
+
break;
|
|
4217
|
+
}
|
|
4153
4218
|
}
|
|
4154
|
-
|
|
4155
|
-
restBeforeNextMatchOrEnd = text.substring(match.index + 1);
|
|
4219
|
+
firstLineBeforeNextMatch = text.substring(restStart, eol);
|
|
4156
4220
|
}
|
|
4157
|
-
const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
|
|
4158
4221
|
if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
|
|
4159
4222
|
const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
|
|
4160
4223
|
if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
|
|
@@ -4320,39 +4383,49 @@ function hasUnclosedTextCommand(text) {
|
|
|
4320
4383
|
return false;
|
|
4321
4384
|
}
|
|
4322
4385
|
var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
|
|
4323
|
-
|
|
4386
|
+
var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
|
|
4387
|
+
function processSliceInstrumented(slice, probe = true) {
|
|
4324
4388
|
const segments = splitByProtectedRegions(slice);
|
|
4325
|
-
|
|
4389
|
+
const parts = [];
|
|
4326
4390
|
let quiescent = true;
|
|
4327
4391
|
let truncatedAtSeamStart = false;
|
|
4328
4392
|
for (let index = 0; index < segments.length; index++) {
|
|
4329
4393
|
const segment = segments[index];
|
|
4330
4394
|
if (segment.isCode) {
|
|
4331
|
-
|
|
4395
|
+
parts.push(segment.text);
|
|
4332
4396
|
continue;
|
|
4333
4397
|
}
|
|
4334
4398
|
let text = segment.text;
|
|
4335
4399
|
text = escapeMhchemCommands(text);
|
|
4336
4400
|
text = escapeCurrencyDollarSigns(text);
|
|
4337
4401
|
text = convertLatexDelimiters(text);
|
|
4338
|
-
if (RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
|
|
4402
|
+
if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
|
|
4339
4403
|
text = escapeLatexPipes(text);
|
|
4340
|
-
if (findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
|
|
4404
|
+
if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
|
|
4341
4405
|
text = escapeLatexPipesInUnclosed(text);
|
|
4342
|
-
if (hasUnclosedTextCommand(text)) quiescent = false;
|
|
4406
|
+
if (probe && hasUnclosedTextCommand(text)) quiescent = false;
|
|
4343
4407
|
text = escapeTextUnderscores(text);
|
|
4344
4408
|
text = convertSingleToDoubleDollar(text);
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4409
|
+
if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
|
|
4410
|
+
const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
|
|
4411
|
+
if (unclosedDouble !== -1) {
|
|
4412
|
+
quiescent = false;
|
|
4413
|
+
if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
|
|
4414
|
+
truncatedAtSeamStart = true;
|
|
4415
|
+
}
|
|
4350
4416
|
}
|
|
4351
4417
|
}
|
|
4352
4418
|
text = truncateUnclosedLatexBlock(text);
|
|
4353
|
-
|
|
4419
|
+
parts.push(text);
|
|
4354
4420
|
}
|
|
4355
|
-
return { out, quiescent, truncatedAtSeamStart };
|
|
4421
|
+
return { out: parts.join(""), quiescent, truncatedAtSeamStart };
|
|
4422
|
+
}
|
|
4423
|
+
function isBlankRawLine(text, from, to) {
|
|
4424
|
+
for (let i = from; i < to; i++) {
|
|
4425
|
+
const c = text.charCodeAt(i);
|
|
4426
|
+
if (c !== 32 && c !== 9 && c !== 13) return false;
|
|
4427
|
+
}
|
|
4428
|
+
return true;
|
|
4356
4429
|
}
|
|
4357
4430
|
function findRawSafeCut(active) {
|
|
4358
4431
|
const segments = splitByProtectedRegions(active);
|
|
@@ -4367,10 +4440,12 @@ function findRawSafeCut(active) {
|
|
|
4367
4440
|
continue;
|
|
4368
4441
|
}
|
|
4369
4442
|
const text = segment.text;
|
|
4443
|
+
let atLineStart = offset === 0 || active.charCodeAt(offset - 1) === 10;
|
|
4370
4444
|
let lineStart = 0;
|
|
4371
4445
|
while (lineStart <= text.length) {
|
|
4372
4446
|
const nl = text.indexOf("\n", lineStart);
|
|
4373
4447
|
const lineEnd = nl === -1 ? text.length : nl;
|
|
4448
|
+
if (atLineStart && nl !== -1 && isBlankRawLine(text, lineStart, lineEnd)) backtickHazard = false;
|
|
4374
4449
|
for (let i = lineStart; i < lineEnd; i++) {
|
|
4375
4450
|
const ch = text[i];
|
|
4376
4451
|
if (ch === "`") backtickHazard = true;
|
|
@@ -4383,6 +4458,7 @@ function findRawSafeCut(active) {
|
|
|
4383
4458
|
if (nl === -1) break;
|
|
4384
4459
|
if (!backtickHazard && !latentLt) lastCut = offset + nl + 1;
|
|
4385
4460
|
lineStart = nl + 1;
|
|
4461
|
+
atLineStart = true;
|
|
4386
4462
|
}
|
|
4387
4463
|
offset += text.length;
|
|
4388
4464
|
}
|
|
@@ -4391,11 +4467,14 @@ function findRawSafeCut(active) {
|
|
|
4391
4467
|
var DEFAULT_FREEZE_ATTEMPT_THRESHOLD = 512;
|
|
4392
4468
|
function createIncrementalLatexPreprocessor(options) {
|
|
4393
4469
|
const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
|
|
4470
|
+
const onAttempt = options?.onAttempt;
|
|
4471
|
+
const backoff = options?.backoff ?? true;
|
|
4394
4472
|
let prevSource = "";
|
|
4395
4473
|
let prevOutput = "";
|
|
4396
4474
|
let frozenSrcEnd = 0;
|
|
4397
4475
|
let frozenOut = "";
|
|
4398
4476
|
let triggered = false;
|
|
4477
|
+
let nextAttemptLen = 0;
|
|
4399
4478
|
return function incrementalPreprocessLaTeX(source) {
|
|
4400
4479
|
if (source === prevSource) return prevOutput;
|
|
4401
4480
|
const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
|
|
@@ -4403,6 +4482,7 @@ function createIncrementalLatexPreprocessor(options) {
|
|
|
4403
4482
|
frozenSrcEnd = 0;
|
|
4404
4483
|
frozenOut = "";
|
|
4405
4484
|
triggered = false;
|
|
4485
|
+
nextAttemptLen = 0;
|
|
4406
4486
|
}
|
|
4407
4487
|
if (!triggered) {
|
|
4408
4488
|
const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
|
|
@@ -4414,18 +4494,26 @@ function createIncrementalLatexPreprocessor(options) {
|
|
|
4414
4494
|
triggered = true;
|
|
4415
4495
|
}
|
|
4416
4496
|
let active = source.slice(frozenSrcEnd);
|
|
4417
|
-
if (active.length > freezeThreshold) {
|
|
4497
|
+
if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
|
|
4498
|
+
const activeLength = active.length;
|
|
4499
|
+
let advanced = false;
|
|
4500
|
+
let frozenBytes = 0;
|
|
4501
|
+
const freeze = (cut2, slice) => {
|
|
4502
|
+
frozenOut += slice.out;
|
|
4503
|
+
frozenSrcEnd += cut2;
|
|
4504
|
+
active = source.slice(frozenSrcEnd);
|
|
4505
|
+
advanced = true;
|
|
4506
|
+
frozenBytes = cut2;
|
|
4507
|
+
};
|
|
4418
4508
|
const cut = findRawSafeCut(active);
|
|
4419
4509
|
if (cut > 0) {
|
|
4420
4510
|
const candidate = processSliceInstrumented(active.slice(0, cut));
|
|
4421
|
-
if (candidate.quiescent)
|
|
4422
|
-
frozenOut += candidate.out;
|
|
4423
|
-
frozenSrcEnd += cut;
|
|
4424
|
-
active = source.slice(frozenSrcEnd);
|
|
4425
|
-
}
|
|
4511
|
+
if (candidate.quiescent) freeze(cut, candidate);
|
|
4426
4512
|
}
|
|
4513
|
+
nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
|
|
4514
|
+
onAttempt?.({ activeLength, frozenBytes });
|
|
4427
4515
|
}
|
|
4428
|
-
const tail = processSliceInstrumented(active);
|
|
4516
|
+
const tail = processSliceInstrumented(active, false);
|
|
4429
4517
|
const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
|
|
4430
4518
|
const out = head + tail.out;
|
|
4431
4519
|
prevSource = source;
|