@adeu/core 1.20.0 → 1.21.0
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 +414 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +414 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.bugs.test.ts +1 -1
- package/src/engine.qa_v4_port.test.ts +304 -0
- package/src/engine.sequential.test.ts +140 -0
- package/src/engine.ts +407 -25
- package/src/mapper.ts +112 -19
- package/src/markup.test.ts +1 -1
- package/src/markup.ts +11 -5
- package/src/repro_qa_report_v3.test.ts +9 -3
- package/src/utils/text.ts +26 -0
package/dist/index.cjs
CHANGED
|
@@ -1264,6 +1264,7 @@ function* iter_paragraph_content(paragraph) {
|
|
|
1264
1264
|
}
|
|
1265
1265
|
|
|
1266
1266
|
// src/mapper.ts
|
|
1267
|
+
var STYLE_MARKER_TEXTS = /* @__PURE__ */ new Set(["**", "__", "*", "_"]);
|
|
1267
1268
|
var DocumentMapper = class {
|
|
1268
1269
|
doc;
|
|
1269
1270
|
clean_view;
|
|
@@ -1273,6 +1274,7 @@ var DocumentMapper = class {
|
|
|
1273
1274
|
spans = [];
|
|
1274
1275
|
appendix_start_index = -1;
|
|
1275
1276
|
_text_chunks = [];
|
|
1277
|
+
_plain_projection = null;
|
|
1276
1278
|
constructor(doc, clean_view = false, original_view = false) {
|
|
1277
1279
|
this.doc = doc;
|
|
1278
1280
|
this.clean_view = clean_view;
|
|
@@ -1285,6 +1287,7 @@ var DocumentMapper = class {
|
|
|
1285
1287
|
this.spans = [];
|
|
1286
1288
|
this._text_chunks = [];
|
|
1287
1289
|
this.full_text = "";
|
|
1290
|
+
this._plain_projection = null;
|
|
1288
1291
|
for (const part of iter_document_parts(this.doc)) {
|
|
1289
1292
|
current_offset = this._map_blocks(part, current_offset);
|
|
1290
1293
|
if (this.spans.length > 0 && this.spans[this.spans.length - 1].text !== "\n\n") {
|
|
@@ -1804,6 +1807,64 @@ ${header}`;
|
|
|
1804
1807
|
if (remaining) parts.push(escapeRegExp(remaining));
|
|
1805
1808
|
return parts.join("");
|
|
1806
1809
|
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Returns [plain_text, offset_map] where plain_text is full_text with the
|
|
1812
|
+
* VIRTUAL markdown style delimiters (bold/italic markers emitted around
|
|
1813
|
+
* formatted runs) removed, and offset_map[i] is the full_text index of
|
|
1814
|
+
* plain_text[i].
|
|
1815
|
+
*
|
|
1816
|
+
* Formatting run boundaries can fall mid-word (e.g. a paragraph projected
|
|
1817
|
+
* as "**Al**pha"), where neither exact matching nor the whitespace-anchored
|
|
1818
|
+
* fuzzy regex can find the plain target "Alpha". Matching against this
|
|
1819
|
+
* projection and mapping the span back to full_text closes that gap (QA H2).
|
|
1820
|
+
*
|
|
1821
|
+
* Built lazily and invalidated by _build_map(): most batches never need it.
|
|
1822
|
+
*/
|
|
1823
|
+
_get_plain_projection() {
|
|
1824
|
+
if (this._plain_projection === null) {
|
|
1825
|
+
const chunks = [];
|
|
1826
|
+
const offsets = [];
|
|
1827
|
+
for (const s of this.spans) {
|
|
1828
|
+
if (s.run === null && s.paragraph !== null && STYLE_MARKER_TEXTS.has(s.text)) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
chunks.push(s.text);
|
|
1832
|
+
for (let k = s.start; k < s.end; k++) offsets.push(k);
|
|
1833
|
+
}
|
|
1834
|
+
this._plain_projection = [chunks.join(""), offsets];
|
|
1835
|
+
}
|
|
1836
|
+
return this._plain_projection;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Matches a markdown-stripped target against the plain projection and maps
|
|
1840
|
+
* each hit back to a [start, length] span in full_text. Interior style
|
|
1841
|
+
* markers end up inside the returned span (so "Alpha" over "**Al**pha"
|
|
1842
|
+
* resolves to the "Al**pha" range); markers just outside the matched
|
|
1843
|
+
* characters are excluded.
|
|
1844
|
+
*/
|
|
1845
|
+
_find_plain_projection_matches(target_text) {
|
|
1846
|
+
const [plain_text, offsets] = this._get_plain_projection();
|
|
1847
|
+
if (plain_text.length === this.full_text.length) {
|
|
1848
|
+
return [];
|
|
1849
|
+
}
|
|
1850
|
+
const norm_target = this._replace_smart_quotes(
|
|
1851
|
+
this._strip_markdown_formatting(target_text)
|
|
1852
|
+
);
|
|
1853
|
+
if (!norm_target) return [];
|
|
1854
|
+
const norm_plain = this._replace_smart_quotes(plain_text);
|
|
1855
|
+
const results = [];
|
|
1856
|
+
let from = 0;
|
|
1857
|
+
while (true) {
|
|
1858
|
+
const p_start = norm_plain.indexOf(norm_target, from);
|
|
1859
|
+
if (p_start === -1) break;
|
|
1860
|
+
const p_end = p_start + norm_target.length;
|
|
1861
|
+
const raw_start = offsets[p_start];
|
|
1862
|
+
const raw_end = offsets[p_end - 1] + 1;
|
|
1863
|
+
results.push([raw_start, raw_end - raw_start]);
|
|
1864
|
+
from = p_end;
|
|
1865
|
+
}
|
|
1866
|
+
return results;
|
|
1867
|
+
}
|
|
1807
1868
|
find_match_index(target_text, is_regex = false) {
|
|
1808
1869
|
if (is_regex) {
|
|
1809
1870
|
try {
|
|
@@ -1825,6 +1886,8 @@ ${header}`;
|
|
|
1825
1886
|
start_idx = this.full_text.indexOf(stripped_target);
|
|
1826
1887
|
return [start_idx, stripped_target.length];
|
|
1827
1888
|
}
|
|
1889
|
+
const plain_first = this._find_plain_projection_matches(target_text);
|
|
1890
|
+
if (plain_first.length > 0) return plain_first[0];
|
|
1828
1891
|
try {
|
|
1829
1892
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text));
|
|
1830
1893
|
const match = pattern.exec(this.full_text);
|
|
@@ -1845,29 +1908,33 @@ ${header}`;
|
|
|
1845
1908
|
}
|
|
1846
1909
|
return [];
|
|
1847
1910
|
}
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1911
|
+
const findAllLiteral = (haystack, needle) => {
|
|
1912
|
+
const out = [];
|
|
1913
|
+
if (!needle) return out;
|
|
1914
|
+
let from = 0;
|
|
1915
|
+
while (true) {
|
|
1916
|
+
const idx = haystack.indexOf(needle, from);
|
|
1917
|
+
if (idx === -1) break;
|
|
1918
|
+
out.push([idx, needle.length]);
|
|
1919
|
+
from = idx + needle.length;
|
|
1920
|
+
}
|
|
1921
|
+
return out;
|
|
1922
|
+
};
|
|
1923
|
+
let matches = findAllLiteral(this.full_text, target_text);
|
|
1924
|
+
if (matches.length > 0) return matches;
|
|
1853
1925
|
const norm_full = this._replace_smart_quotes(this.full_text);
|
|
1854
1926
|
const norm_target = this._replace_smart_quotes(target_text);
|
|
1855
|
-
matches =
|
|
1856
|
-
|
|
1857
|
-
];
|
|
1858
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1927
|
+
matches = findAllLiteral(norm_full, norm_target);
|
|
1928
|
+
if (matches.length > 0) return matches;
|
|
1859
1929
|
const stripped_target = this._strip_markdown_formatting(target_text);
|
|
1860
|
-
matches =
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
];
|
|
1865
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1930
|
+
matches = findAllLiteral(this.full_text, stripped_target);
|
|
1931
|
+
if (matches.length > 0) return matches;
|
|
1932
|
+
const plain_matches = this._find_plain_projection_matches(target_text);
|
|
1933
|
+
if (plain_matches.length > 0) return plain_matches;
|
|
1866
1934
|
try {
|
|
1867
1935
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text), "g");
|
|
1868
|
-
|
|
1869
|
-
if (
|
|
1870
|
-
return matches.map((m) => [m.index, m[0].length]);
|
|
1936
|
+
const fuzzy = [...this.full_text.matchAll(pattern)];
|
|
1937
|
+
if (fuzzy.length > 0) return fuzzy.map((m) => [m.index, m[0].length]);
|
|
1871
1938
|
} catch (e) {
|
|
1872
1939
|
}
|
|
1873
1940
|
return [];
|
|
@@ -2857,18 +2924,30 @@ function format_ambiguity_error(edit_index, target_text, haystack, match_positio
|
|
|
2857
2924
|
}
|
|
2858
2925
|
lines.push(" To resolve, re-send this edit using ONE of these strategies:");
|
|
2859
2926
|
lines.push(
|
|
2860
|
-
|
|
2927
|
+
' 1. RECOMMENDED: Provide more surrounding context in your target_text to uniquely identify a single location (keep the default "match_mode": "strict").'
|
|
2861
2928
|
);
|
|
2862
2929
|
lines.push(
|
|
2863
|
-
|
|
2930
|
+
` 2. Set "match_mode": "all" to modify ALL ${total} occurrences \u2014 only after verifying from the occurrence list above that EVERY occurrence should change.`
|
|
2864
2931
|
);
|
|
2865
2932
|
lines.push(
|
|
2866
|
-
' 3.
|
|
2933
|
+
' 3. Set "match_mode": "first" to modify only the FIRST occurrence \u2014 only after verifying the first occurrence above is the one you intend to change.'
|
|
2867
2934
|
);
|
|
2868
2935
|
return lines.join("\n");
|
|
2869
2936
|
}
|
|
2870
2937
|
|
|
2938
|
+
// src/utils/text.ts
|
|
2939
|
+
var REPORT_ECHO_CAP = 500;
|
|
2940
|
+
var PREVIEW_TEXT_CAP = 200;
|
|
2941
|
+
function truncate_middle(text, cap) {
|
|
2942
|
+
if (text === null || text === void 0 || text.length <= cap) return text;
|
|
2943
|
+
const head = Math.max(1, Math.floor(cap * 2 / 3));
|
|
2944
|
+
const tail = Math.max(1, cap - head);
|
|
2945
|
+
const omitted = text.length - head - tail;
|
|
2946
|
+
return `${text.slice(0, head)}\u2026 [${omitted.toLocaleString("en-US")} chars omitted] \u2026${text.slice(-tail)}`;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2871
2949
|
// src/engine.ts
|
|
2950
|
+
var PREVIEW_CONTEXT_CHARS = 30;
|
|
2872
2951
|
function getNextElement(el) {
|
|
2873
2952
|
let next = el.nextSibling;
|
|
2874
2953
|
while (next) {
|
|
@@ -2943,6 +3022,11 @@ var BatchValidationError = class extends Error {
|
|
|
2943
3022
|
this.errors = errors;
|
|
2944
3023
|
}
|
|
2945
3024
|
};
|
|
3025
|
+
function sequential_context_hint(applied_so_far) {
|
|
3026
|
+
return `
|
|
3027
|
+
Note: ${applied_so_far} earlier edit(s) in this batch were already applied. Batches apply sequentially \u2014 each edit must target the document text as it reads AFTER the preceding edits (e.g. target the replacement text an earlier edit introduced, not the original wording).`;
|
|
3028
|
+
}
|
|
3029
|
+
var TRANSACTIONAL_NOT_APPLIED_ERROR = "Not applied: the batch is transactional and other edits failed validation (see their errors). Fix or remove those edits and re-run.";
|
|
2946
3030
|
function validate_edit_strings(edits, index_offset = 0) {
|
|
2947
3031
|
const errors = [];
|
|
2948
3032
|
for (let i = 0; i < edits.length; i++) {
|
|
@@ -3046,7 +3130,7 @@ function validate_edit_strings(edits, index_offset = 0) {
|
|
|
3046
3130
|
}
|
|
3047
3131
|
return errors;
|
|
3048
3132
|
}
|
|
3049
|
-
var RedlineEngine = class {
|
|
3133
|
+
var RedlineEngine = class _RedlineEngine {
|
|
3050
3134
|
doc;
|
|
3051
3135
|
author;
|
|
3052
3136
|
timestamp;
|
|
@@ -3193,8 +3277,184 @@ var RedlineEngine = class {
|
|
|
3193
3277
|
}
|
|
3194
3278
|
return "";
|
|
3195
3279
|
}
|
|
3280
|
+
// CriticMarkup wrapper pairs used when tidying preview context windows.
|
|
3281
|
+
static _PREVIEW_WRAPPER_PAIRS = [
|
|
3282
|
+
["{--", "--}"],
|
|
3283
|
+
["{++", "++}"],
|
|
3284
|
+
["{==", "==}"],
|
|
3285
|
+
["{>>", "<<}"]
|
|
3286
|
+
];
|
|
3287
|
+
/**
|
|
3288
|
+
* Makes a fixed-width slice of the raw-view projection presentable: drops
|
|
3289
|
+
* complete {>>...<<} meta blocks (annotations of pre-existing changes, not
|
|
3290
|
+
* part of this edit) and any wrapper fragments the window boundary chopped
|
|
3291
|
+
* in half. Without this, previews leak internal scaffolding like
|
|
3292
|
+
* "[Chg:5 delete]" (QA H1). Mirrors the Python engine.
|
|
3293
|
+
*/
|
|
3294
|
+
static _tidy_preview_context(snippet, side) {
|
|
3295
|
+
snippet = snippet.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3296
|
+
for (const [open_tok, close_tok] of _RedlineEngine._PREVIEW_WRAPPER_PAIRS) {
|
|
3297
|
+
if (side === "before") {
|
|
3298
|
+
let depth = 0;
|
|
3299
|
+
let cut = 0;
|
|
3300
|
+
let i = 0;
|
|
3301
|
+
while (i < snippet.length) {
|
|
3302
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3303
|
+
depth += 1;
|
|
3304
|
+
i += open_tok.length;
|
|
3305
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3306
|
+
if (depth === 0) cut = i + close_tok.length;
|
|
3307
|
+
else depth -= 1;
|
|
3308
|
+
i += close_tok.length;
|
|
3309
|
+
} else {
|
|
3310
|
+
i += 1;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
snippet = snippet.substring(cut);
|
|
3314
|
+
} else {
|
|
3315
|
+
const opens = [];
|
|
3316
|
+
let i = 0;
|
|
3317
|
+
while (i < snippet.length) {
|
|
3318
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3319
|
+
opens.push(i);
|
|
3320
|
+
i += open_tok.length;
|
|
3321
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3322
|
+
if (opens.length > 0) opens.pop();
|
|
3323
|
+
i += close_tok.length;
|
|
3324
|
+
} else {
|
|
3325
|
+
i += 1;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
if (opens.length > 0) snippet = snippet.substring(0, opens[0]);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
if (side === "before") {
|
|
3332
|
+
snippet = snippet.replace(/^[-+=<>]{0,2}\}/, "");
|
|
3333
|
+
} else {
|
|
3334
|
+
snippet = snippet.replace(/\{[-+=<>]{0,2}$/, "");
|
|
3335
|
+
}
|
|
3336
|
+
return snippet;
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
3340
|
+
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
3341
|
+
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
3342
|
+
* tracked-change markup, which produced garbled previews mixing unrelated
|
|
3343
|
+
* edits and internal scaffolding (QA H1).
|
|
3344
|
+
*/
|
|
3345
|
+
_capture_preview_context(edit) {
|
|
3346
|
+
if (edit.type !== "modify") return;
|
|
3347
|
+
const start_idx = edit._resolved_start_idx;
|
|
3348
|
+
if (start_idx === void 0 || start_idx === null) return;
|
|
3349
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3350
|
+
const full_text = active_mapper.full_text;
|
|
3351
|
+
if (!full_text) return;
|
|
3352
|
+
const length = (edit.target_text || "").length;
|
|
3353
|
+
const before = full_text.substring(
|
|
3354
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3355
|
+
start_idx
|
|
3356
|
+
);
|
|
3357
|
+
const after = full_text.substring(
|
|
3358
|
+
start_idx + length,
|
|
3359
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3360
|
+
);
|
|
3361
|
+
edit._preview_context = [
|
|
3362
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3363
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3364
|
+
];
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Like _capture_preview_context, but snapshots the context around the
|
|
3368
|
+
* ORIGINAL edit's full matched span (stashed by _pre_resolve_heuristic_edit),
|
|
3369
|
+
* so the report preview can present the complete logical change of a
|
|
3370
|
+
* compound modification instead of its first sub-edit.
|
|
3371
|
+
*/
|
|
3372
|
+
_capture_parent_preview_context(parent) {
|
|
3373
|
+
if (!parent || parent.type !== "modify") return;
|
|
3374
|
+
if (parent._preview_context || !parent._preview_span) return;
|
|
3375
|
+
const [start_idx, match_len] = parent._preview_span;
|
|
3376
|
+
const active_mapper = parent._preview_mapper_ref || this.mapper;
|
|
3377
|
+
const full_text = active_mapper.full_text;
|
|
3378
|
+
if (!full_text) return;
|
|
3379
|
+
const before = full_text.substring(
|
|
3380
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3381
|
+
start_idx
|
|
3382
|
+
);
|
|
3383
|
+
const after = full_text.substring(
|
|
3384
|
+
start_idx + match_len,
|
|
3385
|
+
start_idx + match_len + PREVIEW_CONTEXT_CHARS
|
|
3386
|
+
);
|
|
3387
|
+
parent._preview_context = [
|
|
3388
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3389
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3390
|
+
];
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Renders the preview from the edit's full matched span. The common
|
|
3394
|
+
* prefix/suffix between matched and replacement text is moved into the
|
|
3395
|
+
* surrounding context so the {--...--}{++...++} block shows the minimal
|
|
3396
|
+
* complete change.
|
|
3397
|
+
*/
|
|
3398
|
+
_build_full_match_preview(edit) {
|
|
3399
|
+
let [context_before, context_after] = edit._preview_context;
|
|
3400
|
+
let matched = edit._preview_matched_text || "";
|
|
3401
|
+
let new_text = edit._preview_new_text !== void 0 && edit._preview_new_text !== null ? edit._preview_new_text : edit.new_text || "";
|
|
3402
|
+
const [matched_clean, matched_style] = this._parse_markdown_style(matched);
|
|
3403
|
+
const [new_clean, new_style] = this._parse_markdown_style(new_text);
|
|
3404
|
+
if (matched_style && matched_style.startsWith("Heading")) {
|
|
3405
|
+
context_before = context_before + matched.substring(0, matched.length - matched_clean.length);
|
|
3406
|
+
matched = matched_clean;
|
|
3407
|
+
}
|
|
3408
|
+
if (new_style && new_style.startsWith("Heading")) {
|
|
3409
|
+
new_text = new_clean;
|
|
3410
|
+
}
|
|
3411
|
+
const [prefix_len, suffix_len] = trim_common_context(matched, new_text);
|
|
3412
|
+
let display_target = matched.substring(
|
|
3413
|
+
prefix_len,
|
|
3414
|
+
matched.length - suffix_len
|
|
3415
|
+
);
|
|
3416
|
+
let display_new = new_text.substring(
|
|
3417
|
+
prefix_len,
|
|
3418
|
+
new_text.length - suffix_len
|
|
3419
|
+
);
|
|
3420
|
+
context_before = context_before + matched.substring(0, prefix_len);
|
|
3421
|
+
if (suffix_len) {
|
|
3422
|
+
context_after = matched.substring(matched.length - suffix_len) + context_after;
|
|
3423
|
+
}
|
|
3424
|
+
display_target = truncate_middle(display_target, PREVIEW_TEXT_CAP);
|
|
3425
|
+
display_new = truncate_middle(display_new, PREVIEW_TEXT_CAP);
|
|
3426
|
+
let critic_markup;
|
|
3427
|
+
if (!display_target && !display_new) {
|
|
3428
|
+
const anchor = truncate_middle(matched, PREVIEW_TEXT_CAP);
|
|
3429
|
+
const body = anchor ? `{==${anchor}==}` : "";
|
|
3430
|
+
critic_markup = `${context_before.substring(0, context_before.length - matched.length)}${body}${context_after}`;
|
|
3431
|
+
} else {
|
|
3432
|
+
const deletion = display_target ? `{--${display_target}--}` : "";
|
|
3433
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3434
|
+
critic_markup = `${context_before}${deletion}${insertion}${context_after}`;
|
|
3435
|
+
}
|
|
3436
|
+
let clean_text = critic_markup;
|
|
3437
|
+
clean_text = clean_text.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3438
|
+
clean_text = clean_text.replace(/\{--[\s\S]*?--\}/g, "");
|
|
3439
|
+
clean_text = clean_text.replace(/\{\+\+([\s\S]*?)\+\+\}/g, "$1");
|
|
3440
|
+
return [critic_markup, clean_text];
|
|
3441
|
+
}
|
|
3442
|
+
/**
|
|
3443
|
+
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
3444
|
+
* no new_text field — surface its cell contents instead of the misleading
|
|
3445
|
+
* empty string the report used to print (QA M4).
|
|
3446
|
+
*/
|
|
3447
|
+
static _report_new_text(edit) {
|
|
3448
|
+
if (edit && edit.type === "insert_row" && Array.isArray(edit.cells)) {
|
|
3449
|
+
return edit.cells.join(" | ");
|
|
3450
|
+
}
|
|
3451
|
+
return edit && edit.new_text || "";
|
|
3452
|
+
}
|
|
3196
3453
|
_build_edit_context_previews(edit) {
|
|
3197
3454
|
if (edit.type !== "modify") return [null, null];
|
|
3455
|
+
if (edit._preview_span && edit._preview_context) {
|
|
3456
|
+
return this._build_full_match_preview(edit);
|
|
3457
|
+
}
|
|
3198
3458
|
if (edit._resolved_proxy_edit) {
|
|
3199
3459
|
edit = edit._resolved_proxy_edit;
|
|
3200
3460
|
}
|
|
@@ -3213,17 +3473,33 @@ var RedlineEngine = class {
|
|
|
3213
3473
|
new_text = clean_new;
|
|
3214
3474
|
}
|
|
3215
3475
|
const length = target_text.length;
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3476
|
+
let context_before;
|
|
3477
|
+
let context_after;
|
|
3478
|
+
if (edit._preview_context) {
|
|
3479
|
+
[context_before, context_after] = edit._preview_context;
|
|
3480
|
+
} else {
|
|
3481
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3482
|
+
const full_text = active_mapper.full_text;
|
|
3483
|
+
if (!full_text) return [null, null];
|
|
3484
|
+
context_before = _RedlineEngine._tidy_preview_context(
|
|
3485
|
+
full_text.substring(
|
|
3486
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3487
|
+
start_idx
|
|
3488
|
+
),
|
|
3489
|
+
"before"
|
|
3490
|
+
);
|
|
3491
|
+
context_after = _RedlineEngine._tidy_preview_context(
|
|
3492
|
+
full_text.substring(
|
|
3493
|
+
start_idx + length,
|
|
3494
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3495
|
+
),
|
|
3496
|
+
"after"
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3499
|
+
const display_target = truncate_middle(target_text, PREVIEW_TEXT_CAP);
|
|
3500
|
+
const display_new = truncate_middle(new_text, PREVIEW_TEXT_CAP);
|
|
3501
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3502
|
+
const critic_markup = `${context_before}{--${display_target}--}${insertion}${context_after}`;
|
|
3227
3503
|
let clean_text = critic_markup;
|
|
3228
3504
|
clean_text = clean_text.replace(/\{>>.*?<<\}/gs, "");
|
|
3229
3505
|
clean_text = clean_text.replace(/\{--.*?--\}/gs, "");
|
|
@@ -4164,7 +4440,7 @@ var RedlineEngine = class {
|
|
|
4164
4440
|
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
4165
4441
|
errors.push(
|
|
4166
4442
|
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
4167
|
-
"${edit.target_text}"${hint}`
|
|
4443
|
+
"${truncate_middle(edit.target_text, REPORT_ECHO_CAP)}"${hint}`
|
|
4168
4444
|
);
|
|
4169
4445
|
}
|
|
4170
4446
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
@@ -4265,9 +4541,48 @@ var RedlineEngine = class {
|
|
|
4265
4541
|
);
|
|
4266
4542
|
}
|
|
4267
4543
|
}
|
|
4544
|
+
if ((edit.type === "insert_row" || edit.type === "delete_row") && matches.length > 0) {
|
|
4545
|
+
const [start, length] = matches[0];
|
|
4546
|
+
const n_cols = _RedlineEngine._column_count_at(
|
|
4547
|
+
target_mapper,
|
|
4548
|
+
start,
|
|
4549
|
+
length
|
|
4550
|
+
);
|
|
4551
|
+
if (n_cols === null) {
|
|
4552
|
+
errors.push(
|
|
4553
|
+
`- Edit ${i + 1 + index_offset} Failed: ${edit.type} target text was found, but it is not inside a table row. Anchor the operation on text that appears within the table.`
|
|
4554
|
+
);
|
|
4555
|
+
} else if (edit.type === "insert_row" && Array.isArray(edit.cells) && edit.cells.length > n_cols) {
|
|
4556
|
+
errors.push(
|
|
4557
|
+
`- Edit ${i + 1 + index_offset} Failed: insert_row provides ${edit.cells.length} cells but the target table has ${n_cols} column(s). The extra cell(s) would be dropped. Provide at most ${n_cols} cells \u2014 rows given fewer cells are padded with empty ones.`
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4268
4561
|
}
|
|
4269
4562
|
return errors;
|
|
4270
4563
|
}
|
|
4564
|
+
/**
|
|
4565
|
+
* Number of columns (w:tc elements) in the table row containing the text at
|
|
4566
|
+
* [start, start+length) in `mapper`, or null if that text is not inside a
|
|
4567
|
+
* table row.
|
|
4568
|
+
*/
|
|
4569
|
+
static _column_count_at(mapper, start, length) {
|
|
4570
|
+
for (const s of mapper.spans) {
|
|
4571
|
+
if (s.run === null || s.end <= start || s.start >= start + length) {
|
|
4572
|
+
continue;
|
|
4573
|
+
}
|
|
4574
|
+
let curr = s.run._element;
|
|
4575
|
+
while (curr) {
|
|
4576
|
+
if (curr.nodeType === 1 && curr.tagName === "w:tr") {
|
|
4577
|
+
return findAllDescendants(curr, "w:tc").filter(
|
|
4578
|
+
(tc) => tc.parentNode === curr
|
|
4579
|
+
).length;
|
|
4580
|
+
}
|
|
4581
|
+
curr = curr.parentNode;
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
return null;
|
|
4585
|
+
}
|
|
4271
4586
|
validate_review_actions(actions) {
|
|
4272
4587
|
const errors = [];
|
|
4273
4588
|
for (let i = 0; i < actions.length; i++) {
|
|
@@ -4410,19 +4725,25 @@ var RedlineEngine = class {
|
|
|
4410
4725
|
});
|
|
4411
4726
|
if (dry_run_mode) {
|
|
4412
4727
|
const reports_by_input = new Array(edits.length);
|
|
4728
|
+
const validation_failed_idx = /* @__PURE__ */ new Set();
|
|
4413
4729
|
for (const { edit, i } of ordered_edits) {
|
|
4414
|
-
|
|
4730
|
+
let single_errors = this.validate_edits([edit], i);
|
|
4415
4731
|
if (single_errors.length > 0) {
|
|
4732
|
+
if (applied_edits > 0) {
|
|
4733
|
+
const hint = sequential_context_hint(applied_edits);
|
|
4734
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4735
|
+
}
|
|
4736
|
+
validation_failed_idx.add(i);
|
|
4416
4737
|
skipped_edits++;
|
|
4417
4738
|
const warning = this._check_punctuation_warning(
|
|
4418
4739
|
edit.target_text || ""
|
|
4419
4740
|
);
|
|
4420
4741
|
reports_by_input[i] = {
|
|
4421
4742
|
status: "failed",
|
|
4422
|
-
target_text: edit.target_text || "",
|
|
4423
|
-
new_text: edit
|
|
4743
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4744
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4424
4745
|
warning,
|
|
4425
|
-
error: single_errors
|
|
4746
|
+
error: single_errors.join("\n"),
|
|
4426
4747
|
critic_markup: null,
|
|
4427
4748
|
clean_text: null
|
|
4428
4749
|
};
|
|
@@ -4434,8 +4755,8 @@ var RedlineEngine = class {
|
|
|
4434
4755
|
const previews = this._build_edit_context_previews(edit);
|
|
4435
4756
|
reports_by_input[i] = {
|
|
4436
4757
|
status: "applied",
|
|
4437
|
-
target_text: edit.target_text || "",
|
|
4438
|
-
new_text: edit
|
|
4758
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4759
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4439
4760
|
warning: null,
|
|
4440
4761
|
error: null,
|
|
4441
4762
|
critic_markup: previews[0],
|
|
@@ -4455,8 +4776,8 @@ var RedlineEngine = class {
|
|
|
4455
4776
|
);
|
|
4456
4777
|
reports_by_input[i] = {
|
|
4457
4778
|
status: "failed",
|
|
4458
|
-
target_text: edit.target_text || "",
|
|
4459
|
-
new_text: edit
|
|
4779
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4780
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4460
4781
|
warning,
|
|
4461
4782
|
error: error_msg,
|
|
4462
4783
|
critic_markup: null,
|
|
@@ -4464,18 +4785,45 @@ var RedlineEngine = class {
|
|
|
4464
4785
|
};
|
|
4465
4786
|
}
|
|
4466
4787
|
}
|
|
4788
|
+
if (validation_failed_idx.size > 0) {
|
|
4789
|
+
applied_edits = 0;
|
|
4790
|
+
skipped_edits = edits.length;
|
|
4791
|
+
for (let i = 0; i < reports_by_input.length; i++) {
|
|
4792
|
+
const report = reports_by_input[i];
|
|
4793
|
+
if (!report || validation_failed_idx.has(i)) continue;
|
|
4794
|
+
if (report.status === "applied") {
|
|
4795
|
+
reports_by_input[i] = {
|
|
4796
|
+
status: "failed",
|
|
4797
|
+
target_text: report.target_text,
|
|
4798
|
+
new_text: report.new_text,
|
|
4799
|
+
warning: null,
|
|
4800
|
+
error: TRANSACTIONAL_NOT_APPLIED_ERROR,
|
|
4801
|
+
critic_markup: null,
|
|
4802
|
+
clean_text: null
|
|
4803
|
+
};
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4467
4807
|
edits_reports.push(...reports_by_input);
|
|
4468
4808
|
} else {
|
|
4469
4809
|
const snapshot = takeSnapshot(this.doc);
|
|
4470
4810
|
const originalCurrentId = this.current_id;
|
|
4471
4811
|
try {
|
|
4472
4812
|
const sequential_errors = [];
|
|
4813
|
+
let applied_so_far = 0;
|
|
4473
4814
|
for (const { edit, i } of ordered_edits) {
|
|
4474
|
-
|
|
4815
|
+
let single_errors = this.validate_edits([edit], i);
|
|
4475
4816
|
if (single_errors.length > 0) {
|
|
4817
|
+
if (applied_so_far > 0) {
|
|
4818
|
+
const hint = sequential_context_hint(applied_so_far);
|
|
4819
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4820
|
+
}
|
|
4476
4821
|
sequential_errors.push(...single_errors);
|
|
4477
4822
|
} else {
|
|
4478
4823
|
this.apply_edits([edit], page_offsets);
|
|
4824
|
+
if (edit._applied_status) {
|
|
4825
|
+
applied_so_far++;
|
|
4826
|
+
}
|
|
4479
4827
|
this.mapper = new DocumentMapper(this.doc);
|
|
4480
4828
|
this.clean_mapper = null;
|
|
4481
4829
|
}
|
|
@@ -4510,8 +4858,8 @@ var RedlineEngine = class {
|
|
|
4510
4858
|
}
|
|
4511
4859
|
edits_reports.push({
|
|
4512
4860
|
status: success ? "applied" : "failed",
|
|
4513
|
-
target_text: edit.target_text || "",
|
|
4514
|
-
new_text: edit
|
|
4861
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4862
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4515
4863
|
warning,
|
|
4516
4864
|
error: error_msg,
|
|
4517
4865
|
critic_markup,
|
|
@@ -4613,6 +4961,12 @@ var RedlineEngine = class {
|
|
|
4613
4961
|
resolved_edits.sort(
|
|
4614
4962
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4615
4963
|
);
|
|
4964
|
+
for (const [res_edit] of resolved_edits) {
|
|
4965
|
+
this._capture_preview_context(res_edit);
|
|
4966
|
+
if (res_edit._parent_edit_ref) {
|
|
4967
|
+
this._capture_parent_preview_context(res_edit._parent_edit_ref);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4616
4970
|
const occupied_ranges = [];
|
|
4617
4971
|
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4618
4972
|
for (const [edit, orig_new] of resolved_edits) {
|
|
@@ -4813,7 +5167,15 @@ var RedlineEngine = class {
|
|
|
4813
5167
|
const trPr = tr.ownerDocument.createElement("w:trPr");
|
|
4814
5168
|
new_tr.appendChild(trPr);
|
|
4815
5169
|
trPr.appendChild(this._create_track_change_tag("w:ins"));
|
|
4816
|
-
|
|
5170
|
+
const anchor_cols = findAllDescendants(tr, "w:tc").filter(
|
|
5171
|
+
(tc) => tc.parentNode === tr
|
|
5172
|
+
).length;
|
|
5173
|
+
let cell_values = Array.isArray(edit.cells) ? [...edit.cells] : [];
|
|
5174
|
+
if (anchor_cols > 0) {
|
|
5175
|
+
while (cell_values.length < anchor_cols) cell_values.push("");
|
|
5176
|
+
cell_values = cell_values.slice(0, anchor_cols);
|
|
5177
|
+
}
|
|
5178
|
+
for (const cellText of cell_values) {
|
|
4817
5179
|
const tc = tr.ownerDocument.createElement("w:tc");
|
|
4818
5180
|
const p = tr.ownerDocument.createElement("w:p");
|
|
4819
5181
|
const r = tr.ownerDocument.createElement("w:r");
|
|
@@ -4909,6 +5271,12 @@ var RedlineEngine = class {
|
|
|
4909
5271
|
} catch (e) {
|
|
4910
5272
|
}
|
|
4911
5273
|
}
|
|
5274
|
+
if (!edit._preview_span) {
|
|
5275
|
+
edit._preview_span = [start_idx, match_len];
|
|
5276
|
+
edit._preview_matched_text = actual_doc_text;
|
|
5277
|
+
edit._preview_new_text = current_effective_new_text;
|
|
5278
|
+
edit._preview_mapper_ref = active_mapper;
|
|
5279
|
+
}
|
|
4912
5280
|
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4913
5281
|
edit.target_text
|
|
4914
5282
|
);
|