@adeu/core 1.17.2 → 1.18.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/dist/index.cjs +250 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +250 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.bugs.test.ts +12 -14
- package/src/engine.feedback.test.ts +34 -2
- package/src/engine.issue23.test.ts +23 -18
- package/src/engine.ts +359 -98
- package/src/reject_and_nested_redline.test.ts +137 -0
- package/src/repro.report.test.ts +11 -9
- package/src/repro_qa_report_v3.test.ts +19 -27
- package/src/repro_report_boundary_failures.test.ts +108 -0
package/dist/index.cjs
CHANGED
|
@@ -3063,8 +3063,19 @@ var RedlineEngine = class {
|
|
|
3063
3063
|
this.mapper = new DocumentMapper(this.doc);
|
|
3064
3064
|
this.comments_manager = new CommentsManager(this.doc);
|
|
3065
3065
|
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Return a hint when a short, single-token anchor contains punctuation that
|
|
3068
|
+
* can split awkwardly, else null.
|
|
3069
|
+
*
|
|
3070
|
+
* Surface this ONLY for edits that actually failed to match/apply. On a
|
|
3071
|
+
* successful edit the batch report already carries the redline preview, so
|
|
3072
|
+
* emitting this would be a false positive: the punctuation (dates,
|
|
3073
|
+
* `[_name_]` placeholders, `____` blanks) is frequently the literal target
|
|
3074
|
+
* and the edit succeeds despite it. Mirrors the Python engine.
|
|
3075
|
+
*/
|
|
3066
3076
|
_check_punctuation_warning(target_text) {
|
|
3067
3077
|
if (!target_text) return null;
|
|
3078
|
+
if (target_text.length > 20 || target_text.includes(" ")) return null;
|
|
3068
3079
|
if (target_text.includes("_") || target_text.includes("-")) {
|
|
3069
3080
|
return `Warning: target_text '${target_text}' contains tokenization-splitting punctuation ('_' or '-'). This can trigger mid-word splits in the diff engine. Consider using a longer plain-prose anchor.`;
|
|
3070
3081
|
}
|
|
@@ -3346,6 +3357,93 @@ var RedlineEngine = class {
|
|
|
3346
3357
|
}
|
|
3347
3358
|
}
|
|
3348
3359
|
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Revert every tracked change, returning the document to the state it had
|
|
3362
|
+
* before any revision was proposed. The exact inverse of
|
|
3363
|
+
* accept_all_revisions:
|
|
3364
|
+
*
|
|
3365
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
3366
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
3367
|
+
* <w:trPr>) drops the whole row.
|
|
3368
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
3369
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
3370
|
+
* the row survives.
|
|
3371
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
3372
|
+
*
|
|
3373
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
3374
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
3375
|
+
*
|
|
3376
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
3377
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
3378
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
3379
|
+
* body text.
|
|
3380
|
+
*
|
|
3381
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
3382
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
3383
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
3384
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
3385
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
3386
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
3387
|
+
*/
|
|
3388
|
+
reject_all_revisions() {
|
|
3389
|
+
const parts_to_process = [this.doc.element];
|
|
3390
|
+
for (const part of this.doc.pkg.parts) {
|
|
3391
|
+
if (part === this.doc.part) continue;
|
|
3392
|
+
if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
|
|
3393
|
+
parts_to_process.push(part._element);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
for (const root_element of parts_to_process) {
|
|
3397
|
+
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
3398
|
+
for (const ins of insNodes) {
|
|
3399
|
+
const parent = ins.parentNode;
|
|
3400
|
+
if (!parent) continue;
|
|
3401
|
+
this._clean_wrapping_comments(ins);
|
|
3402
|
+
this._delete_comments_in_element(ins);
|
|
3403
|
+
if (parent.tagName === "w:trPr") {
|
|
3404
|
+
const row = parent.parentNode;
|
|
3405
|
+
if (row && row.parentNode) {
|
|
3406
|
+
row.parentNode.removeChild(row);
|
|
3407
|
+
}
|
|
3408
|
+
} else {
|
|
3409
|
+
parent.removeChild(ins);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
const pNodes = findAllDescendants(root_element, "w:p");
|
|
3413
|
+
for (const p of pNodes) {
|
|
3414
|
+
const pPr = findChild(p, "w:pPr");
|
|
3415
|
+
if (pPr) {
|
|
3416
|
+
const rPr = findChild(pPr, "w:rPr");
|
|
3417
|
+
const delMark = rPr ? findChild(rPr, "w:del") : null;
|
|
3418
|
+
if (rPr && delMark) {
|
|
3419
|
+
rPr.removeChild(delMark);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
const delNodes = findAllDescendants(root_element, "w:del");
|
|
3424
|
+
for (const d of delNodes) {
|
|
3425
|
+
const parent = d.parentNode;
|
|
3426
|
+
if (!parent) continue;
|
|
3427
|
+
this._clean_wrapping_comments(d);
|
|
3428
|
+
if (parent.tagName === "w:trPr") {
|
|
3429
|
+
parent.removeChild(d);
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
const delTexts = Array.from(d.getElementsByTagName("w:delText"));
|
|
3433
|
+
for (const dt of delTexts) {
|
|
3434
|
+
const t = d.ownerDocument.createElement("w:t");
|
|
3435
|
+
t.textContent = dt.textContent;
|
|
3436
|
+
if (dt.hasAttribute("xml:space"))
|
|
3437
|
+
t.setAttribute("xml:space", "preserve");
|
|
3438
|
+
dt.parentNode?.replaceChild(t, dt);
|
|
3439
|
+
}
|
|
3440
|
+
while (d.firstChild) {
|
|
3441
|
+
parent.insertBefore(d.firstChild, d);
|
|
3442
|
+
}
|
|
3443
|
+
parent.removeChild(d);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3349
3447
|
_getNextId() {
|
|
3350
3448
|
this.current_id++;
|
|
3351
3449
|
return this.current_id.toString();
|
|
@@ -3993,29 +4091,36 @@ var RedlineEngine = class {
|
|
|
3993
4091
|
(edit.new_text || "").length - sfx
|
|
3994
4092
|
);
|
|
3995
4093
|
if (final_target.includes("\n\n")) {
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
if (
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4094
|
+
const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
|
|
4095
|
+
if (!balanced) {
|
|
4096
|
+
if (final_new.includes("\n\n")) {
|
|
4097
|
+
const parts = matched.split("\n\n");
|
|
4098
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4099
|
+
errors.push(
|
|
4100
|
+
`- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
4101
|
+
);
|
|
4102
|
+
}
|
|
4103
|
+
} else {
|
|
4104
|
+
const parts = final_target.split("\n\n");
|
|
4105
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4106
|
+
errors.push(
|
|
4107
|
+
`- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4009
4110
|
}
|
|
4010
4111
|
}
|
|
4011
4112
|
}
|
|
4012
4113
|
}
|
|
4013
4114
|
for (const [start, length] of matches) {
|
|
4014
|
-
const spans =
|
|
4115
|
+
const spans = target_mapper.spans.filter(
|
|
4015
4116
|
(s) => s.end > start && s.start < start + length
|
|
4016
4117
|
);
|
|
4017
|
-
const
|
|
4118
|
+
const insAuthors = /* @__PURE__ */ new Set();
|
|
4119
|
+
const commentAuthors = /* @__PURE__ */ new Set();
|
|
4120
|
+
let hasNonForeignRealText = false;
|
|
4018
4121
|
for (const s of spans) {
|
|
4122
|
+
if (s.run === null) continue;
|
|
4123
|
+
let isForeignIns = false;
|
|
4019
4124
|
if (s.ins_id) {
|
|
4020
4125
|
const insNodes = findAllDescendants(
|
|
4021
4126
|
this.doc.element,
|
|
@@ -4024,24 +4129,32 @@ var RedlineEngine = class {
|
|
|
4024
4129
|
if (insNodes.length > 0) {
|
|
4025
4130
|
const auth = insNodes[0].getAttribute("w:author");
|
|
4026
4131
|
if (auth && auth !== this.author) {
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
if (is_lockout) {
|
|
4030
|
-
nestedAuthors.add(auth);
|
|
4031
|
-
}
|
|
4132
|
+
insAuthors.add(auth);
|
|
4133
|
+
isForeignIns = true;
|
|
4032
4134
|
}
|
|
4033
4135
|
}
|
|
4034
4136
|
}
|
|
4137
|
+
if (!isForeignIns) hasNonForeignRealText = true;
|
|
4138
|
+
}
|
|
4139
|
+
for (const s of spans) {
|
|
4035
4140
|
if (s.comment_ids) {
|
|
4036
4141
|
for (const cid of s.comment_ids) {
|
|
4037
4142
|
const c_data = this.mapper.comments_map[cid];
|
|
4038
4143
|
if (c_data && c_data.author && c_data.author !== this.author) {
|
|
4039
|
-
|
|
4144
|
+
commentAuthors.add(c_data.author);
|
|
4040
4145
|
}
|
|
4041
4146
|
}
|
|
4042
4147
|
}
|
|
4043
4148
|
}
|
|
4044
|
-
if (
|
|
4149
|
+
if (insAuthors.size > 0 || commentAuthors.size > 0) {
|
|
4150
|
+
const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
|
|
4151
|
+
if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
|
|
4152
|
+
continue;
|
|
4153
|
+
}
|
|
4154
|
+
const nestedAuthors = /* @__PURE__ */ new Set([
|
|
4155
|
+
...insAuthors,
|
|
4156
|
+
...commentAuthors
|
|
4157
|
+
]);
|
|
4045
4158
|
errors.push(
|
|
4046
4159
|
`- Edit ${i + 1 + index_offset} Failed: Modification targets an active insertion from another author (${Array.from(nestedAuthors).join(", ")}). Accept that change first or scope your edit outside of it.`
|
|
4047
4160
|
);
|
|
@@ -4135,59 +4248,6 @@ var RedlineEngine = class {
|
|
|
4135
4248
|
const edits = changes.filter(
|
|
4136
4249
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
4137
4250
|
);
|
|
4138
|
-
let mapper_dirty = false;
|
|
4139
|
-
for (const edit of edits) {
|
|
4140
|
-
if (typeof edit !== "object" || edit === null || !edit.target_text) continue;
|
|
4141
|
-
const is_regex = edit.regex || false;
|
|
4142
|
-
let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4143
|
-
let target_mapper = this.mapper;
|
|
4144
|
-
if (matches.length === 0) {
|
|
4145
|
-
if (!this.clean_mapper) {
|
|
4146
|
-
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4147
|
-
}
|
|
4148
|
-
matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4149
|
-
target_mapper = this.clean_mapper;
|
|
4150
|
-
}
|
|
4151
|
-
for (const [start, length] of matches) {
|
|
4152
|
-
const spans = target_mapper.spans.filter(
|
|
4153
|
-
(s) => s.end > start && s.start < start + length
|
|
4154
|
-
);
|
|
4155
|
-
for (const s of spans) {
|
|
4156
|
-
if (s.ins_id) {
|
|
4157
|
-
const insNodes = findAllDescendants(this.doc.element, "w:ins").filter(
|
|
4158
|
-
(n) => n.getAttribute("w:id") === s.ins_id
|
|
4159
|
-
);
|
|
4160
|
-
if (insNodes.length > 0) {
|
|
4161
|
-
const auth = insNodes[0].getAttribute("w:author");
|
|
4162
|
-
if (auth && auth !== this.author) {
|
|
4163
|
-
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
4164
|
-
const match_mode = edit.match_mode || "strict";
|
|
4165
|
-
if (!is_fully_contained_in_ins && match_mode !== "all") {
|
|
4166
|
-
const node = insNodes[0];
|
|
4167
|
-
this._clean_wrapping_comments(node);
|
|
4168
|
-
const parent = node.parentNode;
|
|
4169
|
-
if (parent) {
|
|
4170
|
-
if (parent.tagName === "w:trPr") {
|
|
4171
|
-
parent.removeChild(node);
|
|
4172
|
-
} else {
|
|
4173
|
-
while (node.firstChild) {
|
|
4174
|
-
parent.insertBefore(node.firstChild, node);
|
|
4175
|
-
}
|
|
4176
|
-
parent.removeChild(node);
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
|
-
mapper_dirty = true;
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
}
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
if (mapper_dirty) {
|
|
4188
|
-
this.mapper = new DocumentMapper(this.doc);
|
|
4189
|
-
this.clean_mapper = null;
|
|
4190
|
-
}
|
|
4191
4251
|
if (!dry_run_mode) {
|
|
4192
4252
|
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4193
4253
|
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
@@ -4232,11 +4292,11 @@ var RedlineEngine = class {
|
|
|
4232
4292
|
for (let i = 0; i < edits.length; i++) {
|
|
4233
4293
|
const edit = edits[i];
|
|
4234
4294
|
const single_errors = this.validate_edits([edit], i);
|
|
4235
|
-
const warning = this._check_punctuation_warning(
|
|
4236
|
-
edit.target_text || ""
|
|
4237
|
-
);
|
|
4238
4295
|
if (single_errors.length > 0) {
|
|
4239
4296
|
skipped_edits++;
|
|
4297
|
+
const warning = this._check_punctuation_warning(
|
|
4298
|
+
edit.target_text || ""
|
|
4299
|
+
);
|
|
4240
4300
|
edits_reports.push({
|
|
4241
4301
|
status: "failed",
|
|
4242
4302
|
target_text: edit.target_text || "",
|
|
@@ -4256,7 +4316,7 @@ var RedlineEngine = class {
|
|
|
4256
4316
|
status: "applied",
|
|
4257
4317
|
target_text: edit.target_text || "",
|
|
4258
4318
|
new_text: edit.new_text || "",
|
|
4259
|
-
warning,
|
|
4319
|
+
warning: null,
|
|
4260
4320
|
error: null,
|
|
4261
4321
|
critic_markup: previews[0],
|
|
4262
4322
|
clean_text: previews[1],
|
|
@@ -4270,6 +4330,9 @@ var RedlineEngine = class {
|
|
|
4270
4330
|
} else {
|
|
4271
4331
|
skipped_edits++;
|
|
4272
4332
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
4333
|
+
const warning = this._check_punctuation_warning(
|
|
4334
|
+
edit.target_text || ""
|
|
4335
|
+
);
|
|
4273
4336
|
edits_reports.push({
|
|
4274
4337
|
status: "failed",
|
|
4275
4338
|
target_text: edit.target_text || "",
|
|
@@ -4315,15 +4378,17 @@ var RedlineEngine = class {
|
|
|
4315
4378
|
for (const edit of edits) {
|
|
4316
4379
|
const success = edit._applied_status || false;
|
|
4317
4380
|
const error_msg = edit._error_msg || null;
|
|
4318
|
-
const warning = this._check_punctuation_warning(
|
|
4319
|
-
edit.target_text || ""
|
|
4320
|
-
);
|
|
4321
4381
|
let critic_markup = null;
|
|
4322
4382
|
let clean_text = null;
|
|
4383
|
+
let warning = null;
|
|
4323
4384
|
if (success) {
|
|
4324
4385
|
const previews = this._build_edit_context_previews(edit);
|
|
4325
4386
|
critic_markup = previews[0];
|
|
4326
4387
|
clean_text = previews[1];
|
|
4388
|
+
} else {
|
|
4389
|
+
warning = this._check_punctuation_warning(
|
|
4390
|
+
edit.target_text || ""
|
|
4391
|
+
);
|
|
4327
4392
|
}
|
|
4328
4393
|
edits_reports.push({
|
|
4329
4394
|
status: success ? "applied" : "failed",
|
|
@@ -4431,6 +4496,7 @@ var RedlineEngine = class {
|
|
|
4431
4496
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4432
4497
|
);
|
|
4433
4498
|
const occupied_ranges = [];
|
|
4499
|
+
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4434
4500
|
for (const [edit, orig_new] of resolved_edits) {
|
|
4435
4501
|
const start = edit._resolved_start_idx || 0;
|
|
4436
4502
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
@@ -4459,13 +4525,20 @@ var RedlineEngine = class {
|
|
|
4459
4525
|
success = this._apply_table_edit(edit, false);
|
|
4460
4526
|
}
|
|
4461
4527
|
if (success) {
|
|
4462
|
-
|
|
4528
|
+
const group_id = edit._split_group_id;
|
|
4529
|
+
const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
|
|
4530
|
+
if (first_in_group && group_id !== void 0 && group_id !== null) {
|
|
4531
|
+
counted_split_groups.add(group_id);
|
|
4532
|
+
}
|
|
4533
|
+
if (first_in_group) applied++;
|
|
4463
4534
|
occupied_ranges.push([start, end]);
|
|
4464
4535
|
edit._applied_status = true;
|
|
4465
4536
|
const parent = edit._parent_edit_ref;
|
|
4466
4537
|
if (parent) {
|
|
4467
4538
|
parent._applied_status = true;
|
|
4468
|
-
|
|
4539
|
+
if (first_in_group) {
|
|
4540
|
+
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4541
|
+
}
|
|
4469
4542
|
const [path, page] = this._get_heading_path_and_page(
|
|
4470
4543
|
start,
|
|
4471
4544
|
this.mapper.full_text,
|
|
@@ -4476,7 +4549,9 @@ var RedlineEngine = class {
|
|
|
4476
4549
|
parent._pages = pages;
|
|
4477
4550
|
parent._heading_path = path;
|
|
4478
4551
|
} else {
|
|
4479
|
-
|
|
4552
|
+
if (first_in_group) {
|
|
4553
|
+
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4554
|
+
}
|
|
4480
4555
|
const [path, page] = this._get_heading_path_and_page(
|
|
4481
4556
|
start,
|
|
4482
4557
|
this.mapper.full_text,
|
|
@@ -4773,6 +4848,51 @@ var RedlineEngine = class {
|
|
|
4773
4848
|
final_target = actual_doc_text.substring(prefix_len, t_end);
|
|
4774
4849
|
final_new = current_effective_new_text.substring(prefix_len, n_end);
|
|
4775
4850
|
effective_start_idx = start_idx + prefix_len;
|
|
4851
|
+
const target_segs = actual_doc_text.split("\n\n");
|
|
4852
|
+
const new_segs = current_effective_new_text.split("\n\n");
|
|
4853
|
+
if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
|
|
4854
|
+
const split_sub_edits = [];
|
|
4855
|
+
let seg_offset = start_idx;
|
|
4856
|
+
let comment_assigned = false;
|
|
4857
|
+
for (let k = 0; k < target_segs.length; k++) {
|
|
4858
|
+
const t_seg = target_segs[k];
|
|
4859
|
+
const n_seg = new_segs[k];
|
|
4860
|
+
if (t_seg !== n_seg) {
|
|
4861
|
+
const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
|
|
4862
|
+
const seg_target = t_seg.substring(
|
|
4863
|
+
seg_prefix,
|
|
4864
|
+
t_seg.length - seg_suffix
|
|
4865
|
+
);
|
|
4866
|
+
const seg_new = n_seg.substring(
|
|
4867
|
+
seg_prefix,
|
|
4868
|
+
n_seg.length - seg_suffix
|
|
4869
|
+
);
|
|
4870
|
+
const seg_start = seg_offset + seg_prefix;
|
|
4871
|
+
let seg_op;
|
|
4872
|
+
if (!seg_target && seg_new) seg_op = "INSERTION";
|
|
4873
|
+
else if (seg_target && !seg_new) seg_op = "DELETION";
|
|
4874
|
+
else if (seg_target && seg_new) seg_op = "MODIFICATION";
|
|
4875
|
+
else seg_op = "COMMENT_ONLY";
|
|
4876
|
+
const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
|
|
4877
|
+
if (seg_comment) comment_assigned = true;
|
|
4878
|
+
split_sub_edits.push({
|
|
4879
|
+
type: "modify",
|
|
4880
|
+
target_text: seg_target,
|
|
4881
|
+
new_text: seg_new,
|
|
4882
|
+
comment: seg_comment,
|
|
4883
|
+
_match_start_index: seg_start,
|
|
4884
|
+
_internal_op: seg_op,
|
|
4885
|
+
_active_mapper_ref: active_mapper,
|
|
4886
|
+
_split_group_id: start_idx
|
|
4887
|
+
});
|
|
4888
|
+
}
|
|
4889
|
+
seg_offset += t_seg.length + 2;
|
|
4890
|
+
}
|
|
4891
|
+
if (split_sub_edits.length > 0) {
|
|
4892
|
+
for (const sub of split_sub_edits) all_sub_edits.push(sub);
|
|
4893
|
+
continue;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4776
4896
|
if (!final_target && final_new) effective_op = "INSERTION";
|
|
4777
4897
|
else if (final_target && !final_new) effective_op = "DELETION";
|
|
4778
4898
|
else if (final_target && final_new) effective_op = "MODIFICATION";
|
|
@@ -4792,6 +4912,35 @@ var RedlineEngine = class {
|
|
|
4792
4912
|
if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
|
|
4793
4913
|
return all_sub_edits[0];
|
|
4794
4914
|
}
|
|
4915
|
+
/**
|
|
4916
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
4917
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
4918
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
4919
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
4920
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
4921
|
+
* <w:ins>.
|
|
4922
|
+
*/
|
|
4923
|
+
_insert_and_split_ins(parent_ins, split_after, new_elem) {
|
|
4924
|
+
const grandparent = parent_ins.parentNode;
|
|
4925
|
+
if (!grandparent) return;
|
|
4926
|
+
const left = parent_ins.cloneNode(false);
|
|
4927
|
+
const right = parent_ins.cloneNode(false);
|
|
4928
|
+
let toRight = false;
|
|
4929
|
+
for (const kid of Array.from(parent_ins.childNodes)) {
|
|
4930
|
+
parent_ins.removeChild(kid);
|
|
4931
|
+
if (!toRight) {
|
|
4932
|
+
left.appendChild(kid);
|
|
4933
|
+
if (kid === split_after) toRight = true;
|
|
4934
|
+
} else {
|
|
4935
|
+
right.appendChild(kid);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
|
|
4939
|
+
grandparent.insertBefore(new_elem, parent_ins);
|
|
4940
|
+
if (right.childNodes.length > 0)
|
|
4941
|
+
grandparent.insertBefore(right, parent_ins);
|
|
4942
|
+
grandparent.removeChild(parent_ins);
|
|
4943
|
+
}
|
|
4795
4944
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
4796
4945
|
let op = edit._internal_op;
|
|
4797
4946
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
@@ -4999,7 +5148,16 @@ var RedlineEngine = class {
|
|
|
4999
5148
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
5000
5149
|
if (is_inline_first) {
|
|
5001
5150
|
if (anchor_run) {
|
|
5002
|
-
|
|
5151
|
+
const anchor_parent = anchor_run._element.parentNode;
|
|
5152
|
+
if (anchor_parent && anchor_parent.tagName === "w:ins") {
|
|
5153
|
+
this._insert_and_split_ins(
|
|
5154
|
+
anchor_parent,
|
|
5155
|
+
anchor_run._element,
|
|
5156
|
+
result.first_node
|
|
5157
|
+
);
|
|
5158
|
+
} else {
|
|
5159
|
+
insertAfter(result.first_node, anchor_run._element);
|
|
5160
|
+
}
|
|
5003
5161
|
} else if (anchor_para) {
|
|
5004
5162
|
anchor_para._element.appendChild(result.first_node);
|
|
5005
5163
|
}
|
|
@@ -5109,7 +5267,12 @@ var RedlineEngine = class {
|
|
|
5109
5267
|
if (result.first_node) {
|
|
5110
5268
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
5111
5269
|
if (is_inline_first) {
|
|
5112
|
-
|
|
5270
|
+
const del_parent = last_del.parentNode;
|
|
5271
|
+
if (del_parent && del_parent.tagName === "w:ins") {
|
|
5272
|
+
this._insert_and_split_ins(del_parent, last_del, result.first_node);
|
|
5273
|
+
} else {
|
|
5274
|
+
insertAfter(result.first_node, last_del);
|
|
5275
|
+
}
|
|
5113
5276
|
ins_elem = result.first_node;
|
|
5114
5277
|
} else {
|
|
5115
5278
|
ins_elem = result.last_ins;
|