@adeu/core 1.17.2 → 1.18.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 +227 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +227 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.bugs.test.ts +12 -14
- package/src/engine.issue23.test.ts +23 -18
- package/src/engine.ts +329 -91
- 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.d.cts
CHANGED
|
@@ -200,6 +200,35 @@ declare class RedlineEngine {
|
|
|
200
200
|
private _scan_existing_ids;
|
|
201
201
|
private _get_heading_path_and_page;
|
|
202
202
|
accept_all_revisions(): void;
|
|
203
|
+
/**
|
|
204
|
+
* Revert every tracked change, returning the document to the state it had
|
|
205
|
+
* before any revision was proposed. The exact inverse of
|
|
206
|
+
* accept_all_revisions:
|
|
207
|
+
*
|
|
208
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
209
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
210
|
+
* <w:trPr>) drops the whole row.
|
|
211
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
212
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
213
|
+
* the row survives.
|
|
214
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
215
|
+
*
|
|
216
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
217
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
218
|
+
*
|
|
219
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
220
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
221
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
222
|
+
* body text.
|
|
223
|
+
*
|
|
224
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
225
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
226
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
227
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
228
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
229
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
230
|
+
*/
|
|
231
|
+
reject_all_revisions(): void;
|
|
203
232
|
private _getNextId;
|
|
204
233
|
private _create_track_change_tag;
|
|
205
234
|
private _set_text_content;
|
|
@@ -286,6 +315,15 @@ declare class RedlineEngine {
|
|
|
286
315
|
apply_review_actions(actions: any[]): [number, number];
|
|
287
316
|
private _apply_table_edit;
|
|
288
317
|
private _pre_resolve_heuristic_edit;
|
|
318
|
+
/**
|
|
319
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
320
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
321
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
322
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
323
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
324
|
+
* <w:ins>.
|
|
325
|
+
*/
|
|
326
|
+
private _insert_and_split_ins;
|
|
289
327
|
private _apply_single_edit_indexed;
|
|
290
328
|
}
|
|
291
329
|
|
package/dist/index.d.ts
CHANGED
|
@@ -200,6 +200,35 @@ declare class RedlineEngine {
|
|
|
200
200
|
private _scan_existing_ids;
|
|
201
201
|
private _get_heading_path_and_page;
|
|
202
202
|
accept_all_revisions(): void;
|
|
203
|
+
/**
|
|
204
|
+
* Revert every tracked change, returning the document to the state it had
|
|
205
|
+
* before any revision was proposed. The exact inverse of
|
|
206
|
+
* accept_all_revisions:
|
|
207
|
+
*
|
|
208
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
209
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
210
|
+
* <w:trPr>) drops the whole row.
|
|
211
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
212
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
213
|
+
* the row survives.
|
|
214
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
215
|
+
*
|
|
216
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
217
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
218
|
+
*
|
|
219
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
220
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
221
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
222
|
+
* body text.
|
|
223
|
+
*
|
|
224
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
225
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
226
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
227
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
228
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
229
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
230
|
+
*/
|
|
231
|
+
reject_all_revisions(): void;
|
|
203
232
|
private _getNextId;
|
|
204
233
|
private _create_track_change_tag;
|
|
205
234
|
private _set_text_content;
|
|
@@ -286,6 +315,15 @@ declare class RedlineEngine {
|
|
|
286
315
|
apply_review_actions(actions: any[]): [number, number];
|
|
287
316
|
private _apply_table_edit;
|
|
288
317
|
private _pre_resolve_heuristic_edit;
|
|
318
|
+
/**
|
|
319
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
320
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
321
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
322
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
323
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
324
|
+
* <w:ins>.
|
|
325
|
+
*/
|
|
326
|
+
private _insert_and_split_ins;
|
|
289
327
|
private _apply_single_edit_indexed;
|
|
290
328
|
}
|
|
291
329
|
|
package/dist/index.js
CHANGED
|
@@ -3295,6 +3295,93 @@ var RedlineEngine = class {
|
|
|
3295
3295
|
}
|
|
3296
3296
|
}
|
|
3297
3297
|
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Revert every tracked change, returning the document to the state it had
|
|
3300
|
+
* before any revision was proposed. The exact inverse of
|
|
3301
|
+
* accept_all_revisions:
|
|
3302
|
+
*
|
|
3303
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
3304
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
3305
|
+
* <w:trPr>) drops the whole row.
|
|
3306
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
3307
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
3308
|
+
* the row survives.
|
|
3309
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
3310
|
+
*
|
|
3311
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
3312
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
3313
|
+
*
|
|
3314
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
3315
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
3316
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
3317
|
+
* body text.
|
|
3318
|
+
*
|
|
3319
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
3320
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
3321
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
3322
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
3323
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
3324
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
3325
|
+
*/
|
|
3326
|
+
reject_all_revisions() {
|
|
3327
|
+
const parts_to_process = [this.doc.element];
|
|
3328
|
+
for (const part of this.doc.pkg.parts) {
|
|
3329
|
+
if (part === this.doc.part) continue;
|
|
3330
|
+
if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
|
|
3331
|
+
parts_to_process.push(part._element);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
for (const root_element of parts_to_process) {
|
|
3335
|
+
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
3336
|
+
for (const ins of insNodes) {
|
|
3337
|
+
const parent = ins.parentNode;
|
|
3338
|
+
if (!parent) continue;
|
|
3339
|
+
this._clean_wrapping_comments(ins);
|
|
3340
|
+
this._delete_comments_in_element(ins);
|
|
3341
|
+
if (parent.tagName === "w:trPr") {
|
|
3342
|
+
const row = parent.parentNode;
|
|
3343
|
+
if (row && row.parentNode) {
|
|
3344
|
+
row.parentNode.removeChild(row);
|
|
3345
|
+
}
|
|
3346
|
+
} else {
|
|
3347
|
+
parent.removeChild(ins);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
const pNodes = findAllDescendants(root_element, "w:p");
|
|
3351
|
+
for (const p of pNodes) {
|
|
3352
|
+
const pPr = findChild(p, "w:pPr");
|
|
3353
|
+
if (pPr) {
|
|
3354
|
+
const rPr = findChild(pPr, "w:rPr");
|
|
3355
|
+
const delMark = rPr ? findChild(rPr, "w:del") : null;
|
|
3356
|
+
if (rPr && delMark) {
|
|
3357
|
+
rPr.removeChild(delMark);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
const delNodes = findAllDescendants(root_element, "w:del");
|
|
3362
|
+
for (const d of delNodes) {
|
|
3363
|
+
const parent = d.parentNode;
|
|
3364
|
+
if (!parent) continue;
|
|
3365
|
+
this._clean_wrapping_comments(d);
|
|
3366
|
+
if (parent.tagName === "w:trPr") {
|
|
3367
|
+
parent.removeChild(d);
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
const delTexts = Array.from(d.getElementsByTagName("w:delText"));
|
|
3371
|
+
for (const dt of delTexts) {
|
|
3372
|
+
const t = d.ownerDocument.createElement("w:t");
|
|
3373
|
+
t.textContent = dt.textContent;
|
|
3374
|
+
if (dt.hasAttribute("xml:space"))
|
|
3375
|
+
t.setAttribute("xml:space", "preserve");
|
|
3376
|
+
dt.parentNode?.replaceChild(t, dt);
|
|
3377
|
+
}
|
|
3378
|
+
while (d.firstChild) {
|
|
3379
|
+
parent.insertBefore(d.firstChild, d);
|
|
3380
|
+
}
|
|
3381
|
+
parent.removeChild(d);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3298
3385
|
_getNextId() {
|
|
3299
3386
|
this.current_id++;
|
|
3300
3387
|
return this.current_id.toString();
|
|
@@ -3942,29 +4029,36 @@ var RedlineEngine = class {
|
|
|
3942
4029
|
(edit.new_text || "").length - sfx
|
|
3943
4030
|
);
|
|
3944
4031
|
if (final_target.includes("\n\n")) {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
if (
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
4032
|
+
const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
|
|
4033
|
+
if (!balanced) {
|
|
4034
|
+
if (final_new.includes("\n\n")) {
|
|
4035
|
+
const parts = matched.split("\n\n");
|
|
4036
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4037
|
+
errors.push(
|
|
4038
|
+
`- 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.`
|
|
4039
|
+
);
|
|
4040
|
+
}
|
|
4041
|
+
} else {
|
|
4042
|
+
const parts = final_target.split("\n\n");
|
|
4043
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4044
|
+
errors.push(
|
|
4045
|
+
`- 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.`
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
3958
4048
|
}
|
|
3959
4049
|
}
|
|
3960
4050
|
}
|
|
3961
4051
|
}
|
|
3962
4052
|
for (const [start, length] of matches) {
|
|
3963
|
-
const spans =
|
|
4053
|
+
const spans = target_mapper.spans.filter(
|
|
3964
4054
|
(s) => s.end > start && s.start < start + length
|
|
3965
4055
|
);
|
|
3966
|
-
const
|
|
4056
|
+
const insAuthors = /* @__PURE__ */ new Set();
|
|
4057
|
+
const commentAuthors = /* @__PURE__ */ new Set();
|
|
4058
|
+
let hasNonForeignRealText = false;
|
|
3967
4059
|
for (const s of spans) {
|
|
4060
|
+
if (s.run === null) continue;
|
|
4061
|
+
let isForeignIns = false;
|
|
3968
4062
|
if (s.ins_id) {
|
|
3969
4063
|
const insNodes = findAllDescendants(
|
|
3970
4064
|
this.doc.element,
|
|
@@ -3973,24 +4067,32 @@ var RedlineEngine = class {
|
|
|
3973
4067
|
if (insNodes.length > 0) {
|
|
3974
4068
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3975
4069
|
if (auth && auth !== this.author) {
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
if (is_lockout) {
|
|
3979
|
-
nestedAuthors.add(auth);
|
|
3980
|
-
}
|
|
4070
|
+
insAuthors.add(auth);
|
|
4071
|
+
isForeignIns = true;
|
|
3981
4072
|
}
|
|
3982
4073
|
}
|
|
3983
4074
|
}
|
|
4075
|
+
if (!isForeignIns) hasNonForeignRealText = true;
|
|
4076
|
+
}
|
|
4077
|
+
for (const s of spans) {
|
|
3984
4078
|
if (s.comment_ids) {
|
|
3985
4079
|
for (const cid of s.comment_ids) {
|
|
3986
4080
|
const c_data = this.mapper.comments_map[cid];
|
|
3987
4081
|
if (c_data && c_data.author && c_data.author !== this.author) {
|
|
3988
|
-
|
|
4082
|
+
commentAuthors.add(c_data.author);
|
|
3989
4083
|
}
|
|
3990
4084
|
}
|
|
3991
4085
|
}
|
|
3992
4086
|
}
|
|
3993
|
-
if (
|
|
4087
|
+
if (insAuthors.size > 0 || commentAuthors.size > 0) {
|
|
4088
|
+
const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
|
|
4089
|
+
if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
const nestedAuthors = /* @__PURE__ */ new Set([
|
|
4093
|
+
...insAuthors,
|
|
4094
|
+
...commentAuthors
|
|
4095
|
+
]);
|
|
3994
4096
|
errors.push(
|
|
3995
4097
|
`- 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.`
|
|
3996
4098
|
);
|
|
@@ -4084,59 +4186,6 @@ var RedlineEngine = class {
|
|
|
4084
4186
|
const edits = changes.filter(
|
|
4085
4187
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
4086
4188
|
);
|
|
4087
|
-
let mapper_dirty = false;
|
|
4088
|
-
for (const edit of edits) {
|
|
4089
|
-
if (typeof edit !== "object" || edit === null || !edit.target_text) continue;
|
|
4090
|
-
const is_regex = edit.regex || false;
|
|
4091
|
-
let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4092
|
-
let target_mapper = this.mapper;
|
|
4093
|
-
if (matches.length === 0) {
|
|
4094
|
-
if (!this.clean_mapper) {
|
|
4095
|
-
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4096
|
-
}
|
|
4097
|
-
matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4098
|
-
target_mapper = this.clean_mapper;
|
|
4099
|
-
}
|
|
4100
|
-
for (const [start, length] of matches) {
|
|
4101
|
-
const spans = target_mapper.spans.filter(
|
|
4102
|
-
(s) => s.end > start && s.start < start + length
|
|
4103
|
-
);
|
|
4104
|
-
for (const s of spans) {
|
|
4105
|
-
if (s.ins_id) {
|
|
4106
|
-
const insNodes = findAllDescendants(this.doc.element, "w:ins").filter(
|
|
4107
|
-
(n) => n.getAttribute("w:id") === s.ins_id
|
|
4108
|
-
);
|
|
4109
|
-
if (insNodes.length > 0) {
|
|
4110
|
-
const auth = insNodes[0].getAttribute("w:author");
|
|
4111
|
-
if (auth && auth !== this.author) {
|
|
4112
|
-
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
4113
|
-
const match_mode = edit.match_mode || "strict";
|
|
4114
|
-
if (!is_fully_contained_in_ins && match_mode !== "all") {
|
|
4115
|
-
const node = insNodes[0];
|
|
4116
|
-
this._clean_wrapping_comments(node);
|
|
4117
|
-
const parent = node.parentNode;
|
|
4118
|
-
if (parent) {
|
|
4119
|
-
if (parent.tagName === "w:trPr") {
|
|
4120
|
-
parent.removeChild(node);
|
|
4121
|
-
} else {
|
|
4122
|
-
while (node.firstChild) {
|
|
4123
|
-
parent.insertBefore(node.firstChild, node);
|
|
4124
|
-
}
|
|
4125
|
-
parent.removeChild(node);
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
mapper_dirty = true;
|
|
4129
|
-
}
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
if (mapper_dirty) {
|
|
4137
|
-
this.mapper = new DocumentMapper(this.doc);
|
|
4138
|
-
this.clean_mapper = null;
|
|
4139
|
-
}
|
|
4140
4189
|
if (!dry_run_mode) {
|
|
4141
4190
|
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4142
4191
|
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
@@ -4380,6 +4429,7 @@ var RedlineEngine = class {
|
|
|
4380
4429
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4381
4430
|
);
|
|
4382
4431
|
const occupied_ranges = [];
|
|
4432
|
+
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4383
4433
|
for (const [edit, orig_new] of resolved_edits) {
|
|
4384
4434
|
const start = edit._resolved_start_idx || 0;
|
|
4385
4435
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
@@ -4408,13 +4458,20 @@ var RedlineEngine = class {
|
|
|
4408
4458
|
success = this._apply_table_edit(edit, false);
|
|
4409
4459
|
}
|
|
4410
4460
|
if (success) {
|
|
4411
|
-
|
|
4461
|
+
const group_id = edit._split_group_id;
|
|
4462
|
+
const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
|
|
4463
|
+
if (first_in_group && group_id !== void 0 && group_id !== null) {
|
|
4464
|
+
counted_split_groups.add(group_id);
|
|
4465
|
+
}
|
|
4466
|
+
if (first_in_group) applied++;
|
|
4412
4467
|
occupied_ranges.push([start, end]);
|
|
4413
4468
|
edit._applied_status = true;
|
|
4414
4469
|
const parent = edit._parent_edit_ref;
|
|
4415
4470
|
if (parent) {
|
|
4416
4471
|
parent._applied_status = true;
|
|
4417
|
-
|
|
4472
|
+
if (first_in_group) {
|
|
4473
|
+
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4474
|
+
}
|
|
4418
4475
|
const [path, page] = this._get_heading_path_and_page(
|
|
4419
4476
|
start,
|
|
4420
4477
|
this.mapper.full_text,
|
|
@@ -4425,7 +4482,9 @@ var RedlineEngine = class {
|
|
|
4425
4482
|
parent._pages = pages;
|
|
4426
4483
|
parent._heading_path = path;
|
|
4427
4484
|
} else {
|
|
4428
|
-
|
|
4485
|
+
if (first_in_group) {
|
|
4486
|
+
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4487
|
+
}
|
|
4429
4488
|
const [path, page] = this._get_heading_path_and_page(
|
|
4430
4489
|
start,
|
|
4431
4490
|
this.mapper.full_text,
|
|
@@ -4722,6 +4781,51 @@ var RedlineEngine = class {
|
|
|
4722
4781
|
final_target = actual_doc_text.substring(prefix_len, t_end);
|
|
4723
4782
|
final_new = current_effective_new_text.substring(prefix_len, n_end);
|
|
4724
4783
|
effective_start_idx = start_idx + prefix_len;
|
|
4784
|
+
const target_segs = actual_doc_text.split("\n\n");
|
|
4785
|
+
const new_segs = current_effective_new_text.split("\n\n");
|
|
4786
|
+
if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
|
|
4787
|
+
const split_sub_edits = [];
|
|
4788
|
+
let seg_offset = start_idx;
|
|
4789
|
+
let comment_assigned = false;
|
|
4790
|
+
for (let k = 0; k < target_segs.length; k++) {
|
|
4791
|
+
const t_seg = target_segs[k];
|
|
4792
|
+
const n_seg = new_segs[k];
|
|
4793
|
+
if (t_seg !== n_seg) {
|
|
4794
|
+
const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
|
|
4795
|
+
const seg_target = t_seg.substring(
|
|
4796
|
+
seg_prefix,
|
|
4797
|
+
t_seg.length - seg_suffix
|
|
4798
|
+
);
|
|
4799
|
+
const seg_new = n_seg.substring(
|
|
4800
|
+
seg_prefix,
|
|
4801
|
+
n_seg.length - seg_suffix
|
|
4802
|
+
);
|
|
4803
|
+
const seg_start = seg_offset + seg_prefix;
|
|
4804
|
+
let seg_op;
|
|
4805
|
+
if (!seg_target && seg_new) seg_op = "INSERTION";
|
|
4806
|
+
else if (seg_target && !seg_new) seg_op = "DELETION";
|
|
4807
|
+
else if (seg_target && seg_new) seg_op = "MODIFICATION";
|
|
4808
|
+
else seg_op = "COMMENT_ONLY";
|
|
4809
|
+
const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
|
|
4810
|
+
if (seg_comment) comment_assigned = true;
|
|
4811
|
+
split_sub_edits.push({
|
|
4812
|
+
type: "modify",
|
|
4813
|
+
target_text: seg_target,
|
|
4814
|
+
new_text: seg_new,
|
|
4815
|
+
comment: seg_comment,
|
|
4816
|
+
_match_start_index: seg_start,
|
|
4817
|
+
_internal_op: seg_op,
|
|
4818
|
+
_active_mapper_ref: active_mapper,
|
|
4819
|
+
_split_group_id: start_idx
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4822
|
+
seg_offset += t_seg.length + 2;
|
|
4823
|
+
}
|
|
4824
|
+
if (split_sub_edits.length > 0) {
|
|
4825
|
+
for (const sub of split_sub_edits) all_sub_edits.push(sub);
|
|
4826
|
+
continue;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4725
4829
|
if (!final_target && final_new) effective_op = "INSERTION";
|
|
4726
4830
|
else if (final_target && !final_new) effective_op = "DELETION";
|
|
4727
4831
|
else if (final_target && final_new) effective_op = "MODIFICATION";
|
|
@@ -4741,6 +4845,35 @@ var RedlineEngine = class {
|
|
|
4741
4845
|
if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
|
|
4742
4846
|
return all_sub_edits[0];
|
|
4743
4847
|
}
|
|
4848
|
+
/**
|
|
4849
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
4850
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
4851
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
4852
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
4853
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
4854
|
+
* <w:ins>.
|
|
4855
|
+
*/
|
|
4856
|
+
_insert_and_split_ins(parent_ins, split_after, new_elem) {
|
|
4857
|
+
const grandparent = parent_ins.parentNode;
|
|
4858
|
+
if (!grandparent) return;
|
|
4859
|
+
const left = parent_ins.cloneNode(false);
|
|
4860
|
+
const right = parent_ins.cloneNode(false);
|
|
4861
|
+
let toRight = false;
|
|
4862
|
+
for (const kid of Array.from(parent_ins.childNodes)) {
|
|
4863
|
+
parent_ins.removeChild(kid);
|
|
4864
|
+
if (!toRight) {
|
|
4865
|
+
left.appendChild(kid);
|
|
4866
|
+
if (kid === split_after) toRight = true;
|
|
4867
|
+
} else {
|
|
4868
|
+
right.appendChild(kid);
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
|
|
4872
|
+
grandparent.insertBefore(new_elem, parent_ins);
|
|
4873
|
+
if (right.childNodes.length > 0)
|
|
4874
|
+
grandparent.insertBefore(right, parent_ins);
|
|
4875
|
+
grandparent.removeChild(parent_ins);
|
|
4876
|
+
}
|
|
4744
4877
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
4745
4878
|
let op = edit._internal_op;
|
|
4746
4879
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
@@ -4948,7 +5081,16 @@ var RedlineEngine = class {
|
|
|
4948
5081
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4949
5082
|
if (is_inline_first) {
|
|
4950
5083
|
if (anchor_run) {
|
|
4951
|
-
|
|
5084
|
+
const anchor_parent = anchor_run._element.parentNode;
|
|
5085
|
+
if (anchor_parent && anchor_parent.tagName === "w:ins") {
|
|
5086
|
+
this._insert_and_split_ins(
|
|
5087
|
+
anchor_parent,
|
|
5088
|
+
anchor_run._element,
|
|
5089
|
+
result.first_node
|
|
5090
|
+
);
|
|
5091
|
+
} else {
|
|
5092
|
+
insertAfter(result.first_node, anchor_run._element);
|
|
5093
|
+
}
|
|
4952
5094
|
} else if (anchor_para) {
|
|
4953
5095
|
anchor_para._element.appendChild(result.first_node);
|
|
4954
5096
|
}
|
|
@@ -5058,7 +5200,12 @@ var RedlineEngine = class {
|
|
|
5058
5200
|
if (result.first_node) {
|
|
5059
5201
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
5060
5202
|
if (is_inline_first) {
|
|
5061
|
-
|
|
5203
|
+
const del_parent = last_del.parentNode;
|
|
5204
|
+
if (del_parent && del_parent.tagName === "w:ins") {
|
|
5205
|
+
this._insert_and_split_ins(del_parent, last_del, result.first_node);
|
|
5206
|
+
} else {
|
|
5207
|
+
insertAfter(result.first_node, last_del);
|
|
5208
|
+
}
|
|
5062
5209
|
ins_elem = result.first_node;
|
|
5063
5210
|
} else {
|
|
5064
5211
|
ins_elem = result.last_ins;
|