@ai-markdown/core 3.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.cjs +942 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +497 -0
- package/dist/index.d.ts +497 -0
- package/dist/index.dev.cjs +942 -0
- package/dist/index.dev.cjs.map +1 -0
- package/dist/index.dev.js +905 -0
- package/dist/index.dev.js.map +1 -0
- package/dist/index.js +905 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
// src/blockPlan.ts
|
|
2
|
+
import { SKIP, visit } from "unist-util-visit";
|
|
3
|
+
import { isFootnoteSection, normalizeId } from "@ai-markdown/engine";
|
|
4
|
+
var TAINT_TYPES = /* @__PURE__ */ new Set([
|
|
5
|
+
"footnoteReference",
|
|
6
|
+
"footnoteDefinition",
|
|
7
|
+
"linkReference",
|
|
8
|
+
"imageReference",
|
|
9
|
+
"definition"
|
|
10
|
+
]);
|
|
11
|
+
var CTX_TYPES = TAINT_TYPES;
|
|
12
|
+
function extractRaw(node, source) {
|
|
13
|
+
if (!node.position) return "";
|
|
14
|
+
return source.slice(node.position.start.offset, node.position.end.offset);
|
|
15
|
+
}
|
|
16
|
+
function hasMdastSource(node) {
|
|
17
|
+
return node.position !== void 0;
|
|
18
|
+
}
|
|
19
|
+
function computeHtmlBlockDigest(el, source, ownEnd) {
|
|
20
|
+
return computeHtmlBlockDigestWithExtent(el, source, ownEnd).digest;
|
|
21
|
+
}
|
|
22
|
+
function computeHtmlBlockDigestWithExtent(el, source, ownEnd) {
|
|
23
|
+
let maxEnd = el.position?.end?.offset ?? 0;
|
|
24
|
+
let count = 0;
|
|
25
|
+
let hasFootnoteSection = false;
|
|
26
|
+
const stack = [...el.children];
|
|
27
|
+
while (stack.length > 0) {
|
|
28
|
+
const node = stack.pop();
|
|
29
|
+
count++;
|
|
30
|
+
const end = node.position?.end?.offset;
|
|
31
|
+
if (end !== void 0 && end > maxEnd) {
|
|
32
|
+
maxEnd = end;
|
|
33
|
+
}
|
|
34
|
+
if (node.type === "element") {
|
|
35
|
+
if (isFootnoteSection(node)) {
|
|
36
|
+
hasFootnoteSection = true;
|
|
37
|
+
}
|
|
38
|
+
for (const child of node.children) {
|
|
39
|
+
stack.push(child);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const swallowed = source !== void 0 && ownEnd !== void 0 && maxEnd > ownEnd ? source.slice(ownEnd, maxEnd) : "";
|
|
44
|
+
return { digest: `${maxEnd}:${count}:${hasFootnoteSection ? 1 : 0}:${fnv1a(swallowed)}`, maxEnd };
|
|
45
|
+
}
|
|
46
|
+
function fnv1a(text) {
|
|
47
|
+
let h = 2166136261;
|
|
48
|
+
for (let i = 0; i < text.length; i++) {
|
|
49
|
+
h ^= text.charCodeAt(i);
|
|
50
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
51
|
+
}
|
|
52
|
+
return h.toString(36);
|
|
53
|
+
}
|
|
54
|
+
var EMPTY_LABELS = /* @__PURE__ */ new Set();
|
|
55
|
+
var FOOTNOTE_SECTION_KEY = "__footnote_section__";
|
|
56
|
+
function buildBlocks(mdast, hast, source, options = {}) {
|
|
57
|
+
const phantomFootnotes = options.phantomFootnoteLabels ?? EMPTY_LABELS;
|
|
58
|
+
const mdastByOffset = /* @__PURE__ */ new Map();
|
|
59
|
+
const mdastRanges = [];
|
|
60
|
+
for (const child of mdast.children) {
|
|
61
|
+
const off = child.position?.start.offset;
|
|
62
|
+
const endOff = child.position?.end.offset;
|
|
63
|
+
if (off !== void 0) {
|
|
64
|
+
mdastByOffset.set(off, child);
|
|
65
|
+
}
|
|
66
|
+
if (off !== void 0 && endOff !== void 0) {
|
|
67
|
+
mdastRanges.push({ start: off, end: endOff, node: child });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (false) {
|
|
71
|
+
for (let i = 1; i < mdastRanges.length; i++) {
|
|
72
|
+
if (mdastRanges[i].start < mdastRanges[i - 1].start) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"block-memo: mdast.children not sorted by source offset \u2014 a remark plugin is reordering top-level children."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function findContainingMdast(offset) {
|
|
80
|
+
let lo = 0;
|
|
81
|
+
let hi = mdastRanges.length;
|
|
82
|
+
while (lo < hi) {
|
|
83
|
+
const mid = lo + hi >>> 1;
|
|
84
|
+
if (mdastRanges[mid].start <= offset) lo = mid + 1;
|
|
85
|
+
else hi = mid;
|
|
86
|
+
}
|
|
87
|
+
const idx = lo - 1;
|
|
88
|
+
if (idx < 0) return void 0;
|
|
89
|
+
const r = mdastRanges[idx];
|
|
90
|
+
return offset < r.end ? r.node : void 0;
|
|
91
|
+
}
|
|
92
|
+
const ctxParts = [];
|
|
93
|
+
const taintOffsets = [];
|
|
94
|
+
const firstTaintAtOrAfter = (offset) => {
|
|
95
|
+
let lo = 0;
|
|
96
|
+
let hi = taintOffsets.length;
|
|
97
|
+
while (lo < hi) {
|
|
98
|
+
const mid = lo + hi >>> 1;
|
|
99
|
+
if (taintOffsets[mid] < offset) lo = mid + 1;
|
|
100
|
+
else hi = mid;
|
|
101
|
+
}
|
|
102
|
+
return lo;
|
|
103
|
+
};
|
|
104
|
+
visit(options.contextMdast ?? mdast, (n) => {
|
|
105
|
+
if (!CTX_TYPES.has(n.type)) return;
|
|
106
|
+
const at = n.position?.start?.offset;
|
|
107
|
+
if (at !== void 0) taintOffsets.push(at);
|
|
108
|
+
if (n.type === "footnoteReference") ctxParts.push(["fr", n.identifier]);
|
|
109
|
+
else if (n.type === "footnoteDefinition") ctxParts.push(["fd", n.identifier, extractRaw(n, source)]);
|
|
110
|
+
else if (n.type === "linkReference") ctxParts.push(["lr", n.identifier]);
|
|
111
|
+
else if (n.type === "imageReference") ctxParts.push(["ir", n.identifier]);
|
|
112
|
+
else if (n.type === "definition") ctxParts.push(["d", n.identifier, n.url, n.title ?? null]);
|
|
113
|
+
});
|
|
114
|
+
if (false) {
|
|
115
|
+
for (let i = 1; i < taintOffsets.length; i++) {
|
|
116
|
+
if (taintOffsets[i] < taintOffsets[i - 1]) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"block-memo: taint node start offsets are not source-ordered \u2014 a remark plugin is reordering nodes or emitting bad positions."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const globalCtx = JSON.stringify(ctxParts);
|
|
124
|
+
const plan = [];
|
|
125
|
+
const blocks = [];
|
|
126
|
+
const blockHasts = [];
|
|
127
|
+
let synthetic;
|
|
128
|
+
const refLocalCtx = /* @__PURE__ */ new Map();
|
|
129
|
+
{
|
|
130
|
+
const counts = /* @__PURE__ */ new Map();
|
|
131
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
132
|
+
const rankOf = (id) => {
|
|
133
|
+
if (phantomFootnotes.has(id)) return -1;
|
|
134
|
+
let rank = ranks.get(id);
|
|
135
|
+
if (rank === void 0) {
|
|
136
|
+
rank = ranks.size;
|
|
137
|
+
ranks.set(id, rank);
|
|
138
|
+
}
|
|
139
|
+
return rank;
|
|
140
|
+
};
|
|
141
|
+
visit(options.contextMdast ?? mdast, (n) => {
|
|
142
|
+
if (n.type === "footnoteDefinition") {
|
|
143
|
+
rankOf(normalizeId(String(n.identifier)));
|
|
144
|
+
return SKIP;
|
|
145
|
+
}
|
|
146
|
+
if (n.type !== "footnoteReference") return;
|
|
147
|
+
const id = normalizeId(String(n.identifier));
|
|
148
|
+
const prior = counts.get(id) ?? 0;
|
|
149
|
+
refLocalCtx.set(n, [prior, rankOf(id)]);
|
|
150
|
+
counts.set(id, prior + 1);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
for (let i = 0; i < hast.children.length; i++) {
|
|
154
|
+
const hastChild = hast.children[i];
|
|
155
|
+
if (hastChild.type !== "element") {
|
|
156
|
+
const off = hastChild.position?.start.offset;
|
|
157
|
+
const key = off !== void 0 ? `inline-${off}` : `inline-i${i}`;
|
|
158
|
+
plan.push({ kind: "inline", el: hastChild, key });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const el = hastChild;
|
|
162
|
+
if (isFootnoteSection(el)) {
|
|
163
|
+
synthetic = el;
|
|
164
|
+
plan.push({ kind: "synthetic", el, key: FOOTNOTE_SECTION_KEY });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const hastOffset = el.position?.start.offset;
|
|
168
|
+
if (hastOffset === void 0) {
|
|
169
|
+
plan.push({ kind: "inline", el, key: `inline-i${i}` });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
let mdastNode = mdastByOffset.get(hastOffset);
|
|
173
|
+
let viaRangeFallback = false;
|
|
174
|
+
if (!mdastNode) {
|
|
175
|
+
mdastNode = findContainingMdast(hastOffset);
|
|
176
|
+
viaRangeFallback = mdastNode !== void 0;
|
|
177
|
+
}
|
|
178
|
+
if (!mdastNode) {
|
|
179
|
+
if (false) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`block-memo: hast block at offset ${hastOffset} has no mdast counterpart. A rehype plugin may have synthesized positions outside source. tagName=${el.tagName}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (mdastNode.type === "footnoteDefinition") {
|
|
188
|
+
plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const isRawHtmlBlock = mdastNode.type === "html" || viaRangeFallback;
|
|
192
|
+
let hasReference = false;
|
|
193
|
+
const swallowed = isRawHtmlBlock ? scanSwallowedSubtree(el) : null;
|
|
194
|
+
const footnoteRefLabels = [];
|
|
195
|
+
const linkRefLabels = [];
|
|
196
|
+
const imageRefLabels = [];
|
|
197
|
+
const footnoteDefLabels = [];
|
|
198
|
+
const local = [];
|
|
199
|
+
visit(mdastNode, (n) => {
|
|
200
|
+
if (!TAINT_TYPES.has(n.type)) return;
|
|
201
|
+
hasReference = true;
|
|
202
|
+
const id = "identifier" in n ? normalizeId(String(n.identifier)) : null;
|
|
203
|
+
if (id === null) return;
|
|
204
|
+
if (n.type === "footnoteReference") {
|
|
205
|
+
footnoteRefLabels.push(id);
|
|
206
|
+
const ctx = refLocalCtx.get(n);
|
|
207
|
+
if (ctx) local.push([id, ctx[0], ctx[1]]);
|
|
208
|
+
} else if (n.type === "linkReference") linkRefLabels.push(id);
|
|
209
|
+
else if (n.type === "imageReference") imageRefLabels.push(id);
|
|
210
|
+
else if (n.type === "footnoteDefinition") footnoteDefLabels.push(id);
|
|
211
|
+
});
|
|
212
|
+
if (swallowed !== null && swallowed.hasReference) {
|
|
213
|
+
hasReference = true;
|
|
214
|
+
footnoteRefLabels.push(...swallowed.refLabels);
|
|
215
|
+
linkRefLabels.push(...swallowed.linkLabels);
|
|
216
|
+
imageRefLabels.push(...swallowed.imageLabels);
|
|
217
|
+
local.push(...swallowed.local);
|
|
218
|
+
}
|
|
219
|
+
const footnoteRefLocalCtx = local.length > 0 ? JSON.stringify(local) : void 0;
|
|
220
|
+
const swallowedDefCtx = swallowed !== null && swallowed.bakedDefs.length > 0 ? JSON.stringify(swallowed.bakedDefs) : void 0;
|
|
221
|
+
const mdastPos = mdastNode.position;
|
|
222
|
+
if (!mdastPos || mdastPos.start?.offset === void 0 || mdastPos.end?.offset === void 0) {
|
|
223
|
+
plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let hastDigest;
|
|
227
|
+
let swallowedTaint = false;
|
|
228
|
+
if (isRawHtmlBlock) {
|
|
229
|
+
const d = computeHtmlBlockDigestWithExtent(el, source, mdastPos.end.offset);
|
|
230
|
+
hastDigest = d.digest;
|
|
231
|
+
const first = firstTaintAtOrAfter(mdastPos.end.offset);
|
|
232
|
+
swallowedTaint = first < taintOffsets.length && taintOffsets[first] < d.maxEnd;
|
|
233
|
+
}
|
|
234
|
+
if (swallowedTaint) hasReference = true;
|
|
235
|
+
const info = {
|
|
236
|
+
raw: extractRaw(mdastNode, source),
|
|
237
|
+
startOffset: mdastPos.start.offset,
|
|
238
|
+
endOffset: mdastPos.end.offset,
|
|
239
|
+
startLine: mdastPos.start.line,
|
|
240
|
+
startColumn: mdastPos.start.column,
|
|
241
|
+
hasReference,
|
|
242
|
+
...hastDigest !== void 0 ? { hastDigest } : {},
|
|
243
|
+
...swallowed?.hasFootnoteSection ? { containsFootnoteSection: true } : {},
|
|
244
|
+
...hasReference ? {
|
|
245
|
+
taintLabels: {
|
|
246
|
+
footnoteRefLabels,
|
|
247
|
+
linkRefLabels,
|
|
248
|
+
imageRefLabels,
|
|
249
|
+
footnoteDefLabels,
|
|
250
|
+
...footnoteRefLocalCtx !== void 0 ? { footnoteRefLocalCtx } : {},
|
|
251
|
+
...swallowedDefCtx !== void 0 ? { swallowedDefCtx } : {}
|
|
252
|
+
}
|
|
253
|
+
} : {}
|
|
254
|
+
};
|
|
255
|
+
blocks.push(info);
|
|
256
|
+
blockHasts.push(el);
|
|
257
|
+
plan.push({ kind: "block", el, info, key: `block-${hastOffset}` });
|
|
258
|
+
}
|
|
259
|
+
return { plan, globalCtx, blocks, blockHasts, synthetic };
|
|
260
|
+
}
|
|
261
|
+
function computeBlockFingerprint(taintLabels, registry, thisChunkSym, clobberPrefix) {
|
|
262
|
+
const parts = [clobberPrefix];
|
|
263
|
+
for (const label of taintLabels.footnoteRefLabels) {
|
|
264
|
+
parts.push(`fn:${label}=${registry.globalNumber(label) ?? "null"}`);
|
|
265
|
+
}
|
|
266
|
+
if (taintLabels.footnoteRefLocalCtx !== void 0) parts.push(`fl:${taintLabels.footnoteRefLocalCtx}`);
|
|
267
|
+
if (taintLabels.swallowedDefCtx !== void 0) parts.push(`sd:${taintLabels.swallowedDefCtx}`);
|
|
268
|
+
for (const label of taintLabels.linkRefLabels) {
|
|
269
|
+
const def = registry.resolveLinkDef(label);
|
|
270
|
+
parts.push(`lr:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
|
|
271
|
+
}
|
|
272
|
+
for (const label of taintLabels.imageRefLabels) {
|
|
273
|
+
const def = registry.resolveLinkDef(label);
|
|
274
|
+
parts.push(`ir:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
|
|
275
|
+
}
|
|
276
|
+
for (const label of taintLabels.footnoteDefLabels) {
|
|
277
|
+
const isCanonical = registry.canonicalFootnoteFor(label) === thisChunkSym ? 1 : 0;
|
|
278
|
+
parts.push(`fd:${label}=${isCanonical}/${registry.getRefsForLabel(label)}`);
|
|
279
|
+
}
|
|
280
|
+
return parts.join("|");
|
|
281
|
+
}
|
|
282
|
+
var PLACEHOLDER_TAGS = /* @__PURE__ */ new Set(["footnote-sup", "cross-chunk-link", "cross-chunk-image"]);
|
|
283
|
+
function scanSwallowedSubtree(el) {
|
|
284
|
+
const refLabels = [];
|
|
285
|
+
const linkLabels = [];
|
|
286
|
+
const imageLabels = [];
|
|
287
|
+
const local = [];
|
|
288
|
+
const bakedDefs = [];
|
|
289
|
+
let hasReference = false;
|
|
290
|
+
let hasFootnoteSection = false;
|
|
291
|
+
visit(el, "element", (node) => {
|
|
292
|
+
if (isFootnoteSection(node)) {
|
|
293
|
+
hasFootnoteSection = true;
|
|
294
|
+
hasReference = true;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const props = node.properties ?? {};
|
|
298
|
+
if (PLACEHOLDER_TAGS.has(node.tagName)) {
|
|
299
|
+
hasReference = true;
|
|
300
|
+
const identifier = props.identifier ?? props.label;
|
|
301
|
+
const label = identifier === void 0 ? null : normalizeId(String(identifier));
|
|
302
|
+
if (label === null) return;
|
|
303
|
+
if (node.tagName === "footnote-sup") {
|
|
304
|
+
refLabels.push(label);
|
|
305
|
+
const occ = Number(props.localOccurrence);
|
|
306
|
+
const num = Number(props.localNumber);
|
|
307
|
+
local.push([label, Number.isFinite(occ) ? occ : -1, Number.isFinite(num) ? num : -1]);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (node.tagName === "cross-chunk-link") linkLabels.push(label);
|
|
311
|
+
else imageLabels.push(label);
|
|
312
|
+
if (typeof props.localUrl === "string") {
|
|
313
|
+
bakedDefs.push([label, props.localUrl, typeof props.localTitle === "string" ? props.localTitle : null]);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (node.tagName === "a" && props.dataFootnoteRef !== void 0) hasReference = true;
|
|
318
|
+
});
|
|
319
|
+
return { hasReference, refLabels, linkLabels, imageLabels, local, bakedDefs, hasFootnoteSection };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/blockPlanner.ts
|
|
323
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
324
|
+
var UNPAIRED_TYPES = /* @__PURE__ */ new Set(["html", "definition", "footnoteDefinition"]);
|
|
325
|
+
function createBlockPlanner() {
|
|
326
|
+
let previous;
|
|
327
|
+
const eligible = /* @__PURE__ */ new WeakMap();
|
|
328
|
+
const isEligible = (node) => {
|
|
329
|
+
let result = eligible.get(node);
|
|
330
|
+
if (result === void 0) {
|
|
331
|
+
result = true;
|
|
332
|
+
visit2(node, (child) => {
|
|
333
|
+
if (UNPAIRED_TYPES.has(child.type)) result = false;
|
|
334
|
+
});
|
|
335
|
+
eligible.set(node, result);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
};
|
|
339
|
+
return (mdast, hast, source, options = {}) => {
|
|
340
|
+
let m = 0;
|
|
341
|
+
let h = 0;
|
|
342
|
+
let reusedContext = false;
|
|
343
|
+
if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {
|
|
344
|
+
while (m < mdast.children.length) {
|
|
345
|
+
const node = mdast.children[m];
|
|
346
|
+
const el = hast.children[h];
|
|
347
|
+
const item = previous.result.plan[h];
|
|
348
|
+
if (node !== previous.mdast.children[m] || el !== previous.hast.children[h] || !el || el.type !== "element" || !item || item.kind !== "block" || item.info.startOffset !== node.position?.start.offset || !isEligible(node))
|
|
349
|
+
break;
|
|
350
|
+
reusedContext ||= item.info.hasReference;
|
|
351
|
+
m++;
|
|
352
|
+
h++;
|
|
353
|
+
while (h < hast.children.length) {
|
|
354
|
+
const gap = hast.children[h];
|
|
355
|
+
if (gap !== previous.hast.children[h] || gap.type !== "text" || /\S/.test(gap.value)) break;
|
|
356
|
+
h++;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
let result;
|
|
361
|
+
if (m > 0 && previous) {
|
|
362
|
+
const tail = buildBlocks(
|
|
363
|
+
{ ...mdast, children: mdast.children.slice(m) },
|
|
364
|
+
{ ...hast, children: hast.children.slice(h) },
|
|
365
|
+
source,
|
|
366
|
+
reusedContext ? { ...options, contextMdast: options.contextMdast ?? mdast } : options
|
|
367
|
+
);
|
|
368
|
+
const prefix = previous.result.plan.slice(0, h);
|
|
369
|
+
const prefixBlocks = prefix.filter(
|
|
370
|
+
(item) => item.kind === "block"
|
|
371
|
+
);
|
|
372
|
+
result = {
|
|
373
|
+
...tail,
|
|
374
|
+
plan: [
|
|
375
|
+
...prefix,
|
|
376
|
+
...tail.plan.map(
|
|
377
|
+
(item) => /^inline-i\d+$/.test(item.key) ? { ...item, key: `inline-i${Number(item.key.slice(8)) + h}` } : item
|
|
378
|
+
)
|
|
379
|
+
],
|
|
380
|
+
blocks: [...prefixBlocks.map((item) => item.info), ...tail.blocks],
|
|
381
|
+
blockHasts: [...prefixBlocks.map((item) => item.el), ...tail.blockHasts]
|
|
382
|
+
};
|
|
383
|
+
} else result = buildBlocks(mdast, hast, source, options);
|
|
384
|
+
previous = { mdast, hast, source, result, phantoms: options.phantomFootnoteLabels };
|
|
385
|
+
return result;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/contribution.ts
|
|
390
|
+
import { extractContributions, extractDefBodiesFromHast } from "@ai-markdown/engine";
|
|
391
|
+
function createContributionSession() {
|
|
392
|
+
let lastContribution = null;
|
|
393
|
+
return {
|
|
394
|
+
commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }) {
|
|
395
|
+
if (!registry || !sym) return;
|
|
396
|
+
const refs = [];
|
|
397
|
+
const defMeta = /* @__PURE__ */ new Map();
|
|
398
|
+
const linkDefs = /* @__PURE__ */ new Map();
|
|
399
|
+
for (const node of extractContributions(pipeline.mdast, {
|
|
400
|
+
phantomFootnoteLabels: targetPhantoms.missingFootnotes
|
|
401
|
+
})) {
|
|
402
|
+
if (node.kind === "ref") {
|
|
403
|
+
refs.push({ label: node.label, kind: node.refKind, referenceType: node.referenceType });
|
|
404
|
+
} else if (node.kind === "fnDef") {
|
|
405
|
+
defMeta.set(node.label, {
|
|
406
|
+
identifier: node.label,
|
|
407
|
+
sourceIdentifier: node.sourceIdentifier,
|
|
408
|
+
contentSource: node.content
|
|
409
|
+
});
|
|
410
|
+
} else if (node.kind === "linkDef") {
|
|
411
|
+
linkDefs.set(node.label, { identifier: node.label, url: node.url, title: node.title });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const fp = JSON.stringify({
|
|
415
|
+
r: refs,
|
|
416
|
+
d: Array.from(defMeta.entries()).map(([k, v]) => [k, v.sourceIdentifier, v.contentSource]),
|
|
417
|
+
l: Array.from(linkDefs.entries()).map(([k, v]) => [k, v.url, v.title ?? ""]),
|
|
418
|
+
ofn: Array.from(ownLabels.footnoteLabels).sort(),
|
|
419
|
+
ol: Array.from(ownLabels.linkLabels).sort(),
|
|
420
|
+
// Include targetPhantoms in the fingerprint: a phantom→resolved
|
|
421
|
+
// transition (another chunk publishes a def for a label this chunk
|
|
422
|
+
// references inside one of its OWN def bodies) changes the rendered
|
|
423
|
+
// hast — the `<cross-chunk-link>` / `<cross-chunk-image>` placeholder
|
|
424
|
+
// disappears and a real `<a>` / `<img>` takes its place — without
|
|
425
|
+
// touching this chunk's refs / defMeta / linkDefs / ownLabels. Without
|
|
426
|
+
// including the phantom snapshot in the fingerprint, the fp check
|
|
427
|
+
// would short-circuit and the registry would keep stale bodyHast
|
|
428
|
+
// forever, leaving the aggregate footer rendering the placeholder
|
|
429
|
+
// long after the label was resolved.
|
|
430
|
+
tpfn: Array.from(targetPhantoms.missingFootnotes).sort(),
|
|
431
|
+
tpl: Array.from(targetPhantoms.missingLinks).sort()
|
|
432
|
+
});
|
|
433
|
+
const last = lastContribution;
|
|
434
|
+
if (last?.registry === registry && last.symbol === sym && last.fp === fp && last.chain.length === chain.length && last.chain.every((dep, i) => dep === chain[i])) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const bodiesByLabel = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);
|
|
438
|
+
const defs = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const [label, meta] of defMeta) {
|
|
440
|
+
defs.set(label, {
|
|
441
|
+
identifier: meta.identifier,
|
|
442
|
+
sourceIdentifier: meta.sourceIdentifier,
|
|
443
|
+
contentSource: meta.contentSource,
|
|
444
|
+
bodyHast: bodiesByLabel.get(label) ?? []
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
lastContribution = { registry, symbol: sym, fp, chain };
|
|
448
|
+
registry.contributeChunkData(sym, {
|
|
449
|
+
refs,
|
|
450
|
+
defs,
|
|
451
|
+
linkDefs,
|
|
452
|
+
ownFootnoteLabels: ownLabels.footnoteLabels,
|
|
453
|
+
ownLinkLabels: ownLabels.linkLabels
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/smoothCoordinator.ts
|
|
460
|
+
function createSmoothCoordinator(onEmpty) {
|
|
461
|
+
const order = [];
|
|
462
|
+
const done = /* @__PURE__ */ new Set();
|
|
463
|
+
const lastProgressAt = /* @__PURE__ */ new Map();
|
|
464
|
+
const coord = {
|
|
465
|
+
order,
|
|
466
|
+
done,
|
|
467
|
+
lastProgressAt,
|
|
468
|
+
version: 0,
|
|
469
|
+
_refcounts: /* @__PURE__ */ new Map(),
|
|
470
|
+
_subscribers: /* @__PURE__ */ new Set(),
|
|
471
|
+
_notifyScheduled: false,
|
|
472
|
+
register(chunkId) {
|
|
473
|
+
const count = this._refcounts.get(chunkId) ?? 0;
|
|
474
|
+
this._refcounts.set(chunkId, count + 1);
|
|
475
|
+
if (count === 0 && !order.includes(chunkId)) {
|
|
476
|
+
order.push(chunkId);
|
|
477
|
+
this._notify();
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
release(chunkId) {
|
|
481
|
+
const count = this._refcounts.get(chunkId);
|
|
482
|
+
if (count === void 0) return;
|
|
483
|
+
if (count > 1) {
|
|
484
|
+
this._refcounts.set(chunkId, count - 1);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
this._refcounts.set(chunkId, 0);
|
|
488
|
+
queueMicrotask(() => {
|
|
489
|
+
if (this._refcounts.get(chunkId) !== 0) return;
|
|
490
|
+
this._refcounts.delete(chunkId);
|
|
491
|
+
const idx = order.indexOf(chunkId);
|
|
492
|
+
if (idx !== -1) order.splice(idx, 1);
|
|
493
|
+
done.delete(chunkId);
|
|
494
|
+
lastProgressAt.delete(chunkId);
|
|
495
|
+
this._notify();
|
|
496
|
+
if (order.length === 0 && onEmpty) onEmpty();
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
markDone(chunkId) {
|
|
500
|
+
if (done.has(chunkId)) return;
|
|
501
|
+
done.add(chunkId);
|
|
502
|
+
this._notify();
|
|
503
|
+
},
|
|
504
|
+
stampProgress(chunkId, at) {
|
|
505
|
+
lastProgressAt.set(chunkId, at);
|
|
506
|
+
},
|
|
507
|
+
isReleased(chunkId) {
|
|
508
|
+
for (const id of order) {
|
|
509
|
+
if (id === chunkId) return true;
|
|
510
|
+
if (!done.has(id)) return false;
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
},
|
|
514
|
+
earliestBlockerOf(chunkId) {
|
|
515
|
+
for (const id of order) {
|
|
516
|
+
if (id === chunkId) return null;
|
|
517
|
+
if (!done.has(id)) return id;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
},
|
|
521
|
+
subscribe(cb) {
|
|
522
|
+
this._subscribers.add(cb);
|
|
523
|
+
return () => {
|
|
524
|
+
this._subscribers.delete(cb);
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
_notify() {
|
|
528
|
+
this.version++;
|
|
529
|
+
if (this._notifyScheduled) return;
|
|
530
|
+
this._notifyScheduled = true;
|
|
531
|
+
queueMicrotask(() => {
|
|
532
|
+
this._notifyScheduled = false;
|
|
533
|
+
for (const cb of [...this._subscribers]) cb();
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
return coord;
|
|
538
|
+
}
|
|
539
|
+
function evaluateGateWarn(coordinator, chunkId, nowMs, thresholdMs) {
|
|
540
|
+
const blocker = coordinator.earliestBlockerOf(chunkId);
|
|
541
|
+
if (!blocker) return "clear";
|
|
542
|
+
const progressedAt = coordinator.lastProgressAt.get(blocker) ?? 0;
|
|
543
|
+
return nowMs - progressedAt < thresholdMs ? "rearm" : "warn";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/coordinator.ts
|
|
547
|
+
function createSmoothCoordinator2(onEmpty) {
|
|
548
|
+
return createSmoothCoordinator(onEmpty);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/tailSignal.ts
|
|
552
|
+
var DESCEND_THROUGH = /* @__PURE__ */ new Set(["blockquote", "list", "listItem", "footnoteDefinition"]);
|
|
553
|
+
function deriveTailSignal(mdast, contentLength) {
|
|
554
|
+
const children = mdast.children;
|
|
555
|
+
let index = children.length - 1;
|
|
556
|
+
while (index >= 0) {
|
|
557
|
+
const offset = children[index].position?.start?.offset;
|
|
558
|
+
if (offset !== void 0 && offset >= contentLength) {
|
|
559
|
+
index--;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
if (index < 0) return null;
|
|
565
|
+
let node = children[index];
|
|
566
|
+
let deepestFootnoteDef = null;
|
|
567
|
+
for (; ; ) {
|
|
568
|
+
if (node.type === "footnoteDefinition") deepestFootnoteDef = node;
|
|
569
|
+
if (node.type === "definition") {
|
|
570
|
+
return { kind: "invisible-def" };
|
|
571
|
+
}
|
|
572
|
+
if (!DESCEND_THROUGH.has(node.type)) break;
|
|
573
|
+
const kids = node.children;
|
|
574
|
+
if (!kids || kids.length === 0) break;
|
|
575
|
+
node = kids[kids.length - 1];
|
|
576
|
+
}
|
|
577
|
+
return deepestFootnoteDef ? { kind: "footnote-def", identifier: deepestFootnoteDef.identifier } : null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/aggregateFootnotes.ts
|
|
581
|
+
import { footnoteSafeId, lastMeaningfulIdx } from "@ai-markdown/engine";
|
|
582
|
+
|
|
583
|
+
// src/cloneHastForRender.ts
|
|
584
|
+
function cloneHastForRender(node) {
|
|
585
|
+
const copy = { ...node };
|
|
586
|
+
const withChildren = copy;
|
|
587
|
+
if (Array.isArray(withChildren.children)) {
|
|
588
|
+
withChildren.children = withChildren.children.map((child) => cloneHastForRender(child));
|
|
589
|
+
}
|
|
590
|
+
const withProperties = copy;
|
|
591
|
+
if (withProperties.properties) {
|
|
592
|
+
withProperties.properties = { ...withProperties.properties };
|
|
593
|
+
}
|
|
594
|
+
return copy;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/aggregateFootnotes.ts
|
|
598
|
+
function buildBackref(href, occurrence, globalNumber) {
|
|
599
|
+
const children = [{ type: "text", value: "\u21A9" }];
|
|
600
|
+
if (occurrence > 1) {
|
|
601
|
+
children.push({
|
|
602
|
+
type: "element",
|
|
603
|
+
tagName: "sup",
|
|
604
|
+
properties: {},
|
|
605
|
+
children: [{ type: "text", value: String(occurrence) }]
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
const ariaLabel = occurrence === 1 ? `Back to reference ${globalNumber}` : `Back to reference ${globalNumber}-${occurrence}`;
|
|
609
|
+
return {
|
|
610
|
+
type: "element",
|
|
611
|
+
tagName: "a",
|
|
612
|
+
properties: {
|
|
613
|
+
href,
|
|
614
|
+
dataFootnoteBackref: "",
|
|
615
|
+
className: ["data-footnote-backref"],
|
|
616
|
+
ariaLabel
|
|
617
|
+
},
|
|
618
|
+
children
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function buildAggregateTree(registry, clobberPrefix, preserveOrphanReferences = false) {
|
|
622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
623
|
+
const ordered = [];
|
|
624
|
+
for (const sym of registry.chunkOrder) {
|
|
625
|
+
const data = registry.chunkData.get(sym);
|
|
626
|
+
if (!data) continue;
|
|
627
|
+
for (const ref of data.refs) {
|
|
628
|
+
if (ref.kind !== "footnote") continue;
|
|
629
|
+
if (seen.has(ref.label)) continue;
|
|
630
|
+
seen.add(ref.label);
|
|
631
|
+
const canonicalSym = registry.canonicalFootnoteFor(ref.label);
|
|
632
|
+
if (!canonicalSym) continue;
|
|
633
|
+
const def = registry.chunkData.get(canonicalSym)?.defs.get(ref.label);
|
|
634
|
+
if (!def) continue;
|
|
635
|
+
const n = registry.globalNumber(ref.label);
|
|
636
|
+
if (n === null) continue;
|
|
637
|
+
const sourceIdentifier = def.sourceIdentifier ?? ref.label;
|
|
638
|
+
ordered.push({
|
|
639
|
+
normalizedLabel: ref.label,
|
|
640
|
+
sourceIdentifier,
|
|
641
|
+
bodyHast: def.bodyHast ?? [],
|
|
642
|
+
n,
|
|
643
|
+
withBackref: true
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (preserveOrphanReferences) {
|
|
648
|
+
for (const sym of registry.chunkOrder) {
|
|
649
|
+
const data = registry.chunkData.get(sym);
|
|
650
|
+
if (!data) continue;
|
|
651
|
+
for (const [label, def] of data.defs) {
|
|
652
|
+
if (seen.has(label)) continue;
|
|
653
|
+
if (registry.canonicalFootnoteFor(label) !== sym) continue;
|
|
654
|
+
seen.add(label);
|
|
655
|
+
ordered.push({
|
|
656
|
+
normalizedLabel: label,
|
|
657
|
+
sourceIdentifier: def.sourceIdentifier ?? label,
|
|
658
|
+
bodyHast: def.bodyHast ?? [],
|
|
659
|
+
n: null,
|
|
660
|
+
withBackref: false
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (ordered.length === 0) return null;
|
|
666
|
+
const liElements = ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {
|
|
667
|
+
const safeId = footnoteSafeId(sourceIdentifier);
|
|
668
|
+
const liChildren = bodyHast.map((c) => cloneHastForRender(c));
|
|
669
|
+
if (withBackref) {
|
|
670
|
+
const totalRefs = registry.getRefsForLabel(normalizedLabel);
|
|
671
|
+
const tailIdx = lastMeaningfulIdx(liChildren);
|
|
672
|
+
const tail = tailIdx !== -1 ? liChildren[tailIdx] : null;
|
|
673
|
+
const dest = tail && tail.type === "element" && tail.tagName === "p" ? tail : null;
|
|
674
|
+
const appended = [];
|
|
675
|
+
for (let i = 1; i <= Math.max(totalRefs, 1); i++) {
|
|
676
|
+
appended.push({ type: "text", value: " " });
|
|
677
|
+
const href = i === 1 ? `#${clobberPrefix}fnref-${safeId}` : `#${clobberPrefix}fnref-${safeId}-${i}`;
|
|
678
|
+
appended.push(buildBackref(href, i, n ?? 0));
|
|
679
|
+
}
|
|
680
|
+
if (dest) {
|
|
681
|
+
dest.children = [...dest.children, ...appended];
|
|
682
|
+
} else if (tailIdx !== -1) {
|
|
683
|
+
liChildren.splice(tailIdx + 1, 0, ...appended);
|
|
684
|
+
} else {
|
|
685
|
+
liChildren.push(...appended);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
type: "element",
|
|
690
|
+
tagName: "li",
|
|
691
|
+
properties: {
|
|
692
|
+
id: `${clobberPrefix}fn-${safeId}`,
|
|
693
|
+
// Stringified: @types/hast (≥3.0.5) types li's `value` as string;
|
|
694
|
+
// React serializes value={3} and value="3" to identical markup.
|
|
695
|
+
...n !== null ? { value: String(n) } : {}
|
|
696
|
+
},
|
|
697
|
+
children: liChildren
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
type: "element",
|
|
702
|
+
tagName: "section",
|
|
703
|
+
properties: {
|
|
704
|
+
className: ["footnotes"],
|
|
705
|
+
dataFootnotes: true,
|
|
706
|
+
// a11y: name the section landmark so screen readers still announce it
|
|
707
|
+
// even though the visible "Footnotes" h2 is omitted in coordinated mode.
|
|
708
|
+
ariaLabel: "Footnotes"
|
|
709
|
+
},
|
|
710
|
+
children: [
|
|
711
|
+
// Conventional separator above the footnote list. The default
|
|
712
|
+
// mdast-util-to-hast footer doesn't emit one (GitHub renders the
|
|
713
|
+
// separation via CSS `border-top` on `.footnotes`); we render a literal
|
|
714
|
+
// `<hr>` so the visual break is preserved without sanitize-friendly CSS.
|
|
715
|
+
{
|
|
716
|
+
type: "element",
|
|
717
|
+
tagName: "hr",
|
|
718
|
+
properties: {},
|
|
719
|
+
children: []
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
type: "element",
|
|
723
|
+
tagName: "ol",
|
|
724
|
+
properties: {},
|
|
725
|
+
children: liElements
|
|
726
|
+
}
|
|
727
|
+
]
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/pipelineSession.ts
|
|
732
|
+
import {
|
|
733
|
+
advanceIncrementalParse,
|
|
734
|
+
buildPhantomSuffix,
|
|
735
|
+
phantomSuffixCloser,
|
|
736
|
+
parseStage,
|
|
737
|
+
transformStage
|
|
738
|
+
} from "@ai-markdown/engine";
|
|
739
|
+
var unmeasured = (_stage, fn) => fn();
|
|
740
|
+
function createPipelineSession() {
|
|
741
|
+
let state = null;
|
|
742
|
+
return {
|
|
743
|
+
reset() {
|
|
744
|
+
state = null;
|
|
745
|
+
},
|
|
746
|
+
parse({
|
|
747
|
+
content,
|
|
748
|
+
targetPhantoms,
|
|
749
|
+
remarkPlugins,
|
|
750
|
+
rehypePlugins,
|
|
751
|
+
remarkRehypeOptions,
|
|
752
|
+
handlers,
|
|
753
|
+
preserveForBodyHarvest,
|
|
754
|
+
documentId,
|
|
755
|
+
provenance,
|
|
756
|
+
incrementalParse,
|
|
757
|
+
defListEnabled,
|
|
758
|
+
measure: measureHere = unmeasured
|
|
759
|
+
}) {
|
|
760
|
+
const phantomDefs = buildPhantomSuffix(targetPhantoms);
|
|
761
|
+
const phantomSuffix = phantomDefs === "" ? "" : phantomSuffixCloser(content ?? "") + phantomDefs;
|
|
762
|
+
const augmented = (content ?? "") + phantomSuffix;
|
|
763
|
+
const baseHandlers = remarkRehypeOptions?.handlers ?? {};
|
|
764
|
+
const mergedRemarkRehypeOptions = handlers ? {
|
|
765
|
+
...remarkRehypeOptions,
|
|
766
|
+
handlers: { ...baseHandlers, ...handlers },
|
|
767
|
+
// Phantom label sets are empty in standalone mode (no PASS 0.5
|
|
768
|
+
// injection happened); the footnoteDefinition handler still reads
|
|
769
|
+
// them via `state.options.phantomFootnoteLabels.has(id)`, which
|
|
770
|
+
// returns false for every id → orphan-protect path proceeds.
|
|
771
|
+
phantomFootnoteLabels: targetPhantoms.missingFootnotes,
|
|
772
|
+
phantomLinkLabels: targetPhantoms.missingLinks,
|
|
773
|
+
preserveOrphan: preserveForBodyHarvest,
|
|
774
|
+
documentId,
|
|
775
|
+
// The SAME value `buildCoreRehypePlugins` received — the
|
|
776
|
+
// verifier unwraps every placeholder stamped with anything else.
|
|
777
|
+
provenance
|
|
778
|
+
} : {
|
|
779
|
+
...remarkRehypeOptions
|
|
780
|
+
};
|
|
781
|
+
if (!incrementalParse) {
|
|
782
|
+
state = null;
|
|
783
|
+
const parsed = measureHere(
|
|
784
|
+
"parse",
|
|
785
|
+
() => parseStage({
|
|
786
|
+
children: augmented,
|
|
787
|
+
remarkPlugins,
|
|
788
|
+
rehypePlugins,
|
|
789
|
+
remarkRehypeOptions: mergedRemarkRehypeOptions
|
|
790
|
+
})
|
|
791
|
+
);
|
|
792
|
+
const hastRoot = measureHere("transform", () => transformStage(parsed));
|
|
793
|
+
return { mdast: parsed.mdast, hast: hastRoot };
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
const result = advanceIncrementalParse(state, content ?? "", {
|
|
797
|
+
remarkPlugins,
|
|
798
|
+
rehypePlugins,
|
|
799
|
+
remarkRehypeOptions: mergedRemarkRehypeOptions,
|
|
800
|
+
// Identity tuple over every parse input beyond the content itself.
|
|
801
|
+
// Deliberately covers MORE than the G3 flush's 12 fields (handlers /
|
|
802
|
+
// preserveForBodyHarvest / documentId can change without touching
|
|
803
|
+
// any G3 field — e.g. a `preserveOrphanReferences` flip). The
|
|
804
|
+
// phantom label sets are deliberately NOT here: their churn tracks
|
|
805
|
+
// the suffix (always re-parsed with the tail), never the prefix.
|
|
806
|
+
depsKey: [
|
|
807
|
+
remarkPlugins,
|
|
808
|
+
rehypePlugins,
|
|
809
|
+
remarkRehypeOptions,
|
|
810
|
+
handlers,
|
|
811
|
+
preserveForBodyHarvest,
|
|
812
|
+
documentId,
|
|
813
|
+
// Explicit even though `rehypePlugins` already carries it: a
|
|
814
|
+
// credential change must never reuse trees stamped under the
|
|
815
|
+
// old one.
|
|
816
|
+
provenance
|
|
817
|
+
],
|
|
818
|
+
defListEnabled,
|
|
819
|
+
phantomSuffix,
|
|
820
|
+
measure: measureHere
|
|
821
|
+
});
|
|
822
|
+
state = result.nextState;
|
|
823
|
+
return { mdast: result.mdast, hast: result.hast };
|
|
824
|
+
} catch (error) {
|
|
825
|
+
state = null;
|
|
826
|
+
if (false) {
|
|
827
|
+
console.error("[ai-react-markdown] incremental parse failed \u2014 full parse fallback for this frame:", error);
|
|
828
|
+
}
|
|
829
|
+
const parsed = measureHere(
|
|
830
|
+
"parse",
|
|
831
|
+
() => parseStage({
|
|
832
|
+
children: augmented,
|
|
833
|
+
remarkPlugins,
|
|
834
|
+
rehypePlugins,
|
|
835
|
+
remarkRehypeOptions: mergedRemarkRehypeOptions
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
const hastRoot = measureHere("transform", () => transformStage(parsed));
|
|
839
|
+
return { mdast: parsed.mdast, hast: hastRoot };
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/coordinationPreparation.ts
|
|
846
|
+
import { buildCrossChunkHandlers, normalizeForMatch } from "@ai-markdown/engine";
|
|
847
|
+
function derivePhantomTargets({ content, ownLabels, labels }, previous) {
|
|
848
|
+
const missingFootnotes = /* @__PURE__ */ new Set();
|
|
849
|
+
const missingLinks = /* @__PURE__ */ new Set();
|
|
850
|
+
if (labels) {
|
|
851
|
+
const footnotes = [...labels.footnoteLabels].filter((label) => !ownLabels.footnoteLabels.has(label));
|
|
852
|
+
const links = [...labels.linkLabels].filter((label) => !ownLabels.linkLabels.has(label));
|
|
853
|
+
if (footnotes.length || links.length) {
|
|
854
|
+
const normalized = normalizeForMatch(content);
|
|
855
|
+
for (const label of footnotes) if (normalized.includes(label)) missingFootnotes.add(label);
|
|
856
|
+
for (const label of links) if (normalized.includes(label)) missingLinks.add(label);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (previous && missingFootnotes.size === previous.missingFootnotes.size && missingLinks.size === previous.missingLinks.size && [...missingFootnotes].every((label) => previous.missingFootnotes.has(label)) && [...missingLinks].every((label) => previous.missingLinks.has(label)))
|
|
860
|
+
return previous;
|
|
861
|
+
return { missingFootnotes, missingLinks };
|
|
862
|
+
}
|
|
863
|
+
function deriveCoordinationPolicy({
|
|
864
|
+
coordinated,
|
|
865
|
+
registered,
|
|
866
|
+
preserveOrphanReferences
|
|
867
|
+
}, previous) {
|
|
868
|
+
const mode = coordinated ? "coordinated" : preserveOrphanReferences ? "orphan" : "standalone";
|
|
869
|
+
const preserveForBodyHarvest = preserveOrphanReferences || coordinated && registered;
|
|
870
|
+
if (previous?.mode === mode && previous.preserveForBodyHarvest === preserveForBodyHarvest) return previous;
|
|
871
|
+
const handlers = previous?.mode === mode ? previous.handlers : mode === "coordinated" ? buildCrossChunkHandlers() : mode === "orphan" ? { footnoteDefinition: buildCrossChunkHandlers().footnoteDefinition } : void 0;
|
|
872
|
+
return { mode, handlers, preserveForBodyHarvest };
|
|
873
|
+
}
|
|
874
|
+
function buildContributionChain(inputs) {
|
|
875
|
+
return [
|
|
876
|
+
inputs.remarkPlugins,
|
|
877
|
+
inputs.rehypePlugins,
|
|
878
|
+
inputs.remarkRehypeOptions,
|
|
879
|
+
inputs.handlers,
|
|
880
|
+
inputs.preserveForBodyHarvest,
|
|
881
|
+
inputs.clobberPrefix,
|
|
882
|
+
inputs.documentId,
|
|
883
|
+
inputs.provenance
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
export {
|
|
887
|
+
buildAggregateTree,
|
|
888
|
+
buildBlocks,
|
|
889
|
+
buildContributionChain,
|
|
890
|
+
cloneHastForRender,
|
|
891
|
+
computeBlockFingerprint,
|
|
892
|
+
computeHtmlBlockDigest,
|
|
893
|
+
computeHtmlBlockDigestWithExtent,
|
|
894
|
+
createBlockPlanner,
|
|
895
|
+
createContributionSession,
|
|
896
|
+
createPipelineSession,
|
|
897
|
+
createSmoothCoordinator2 as createSmoothCoordinator,
|
|
898
|
+
deriveCoordinationPolicy,
|
|
899
|
+
derivePhantomTargets,
|
|
900
|
+
deriveTailSignal,
|
|
901
|
+
evaluateGateWarn,
|
|
902
|
+
hasMdastSource,
|
|
903
|
+
isFootnoteSection
|
|
904
|
+
};
|
|
905
|
+
//# sourceMappingURL=index.js.map
|