@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.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
- if (final_new.includes("\n\n")) {
3946
- const parts = matched.split("\n\n");
3947
- if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
3948
- errors.push(
3949
- `- 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.`
3950
- );
3951
- }
3952
- } else {
3953
- const parts = final_target.split("\n\n");
3954
- if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
3955
- errors.push(
3956
- `- 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.`
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 = this.mapper.spans.filter(
4053
+ const spans = target_mapper.spans.filter(
3964
4054
  (s) => s.end > start && s.start < start + length
3965
4055
  );
3966
- const nestedAuthors = /* @__PURE__ */ new Set();
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
- const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
3977
- const is_lockout = is_fully_contained_in_ins || match_mode === "all";
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
- nestedAuthors.add(c_data.author);
4082
+ commentAuthors.add(c_data.author);
3989
4083
  }
3990
4084
  }
3991
4085
  }
3992
4086
  }
3993
- if (nestedAuthors.size > 0) {
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
- applied++;
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
- parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
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
- edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
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
- insertAfter(result.first_node, anchor_run._element);
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
- insertAfter(result.first_node, last_del);
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;