@stll/folio-core 0.53.0 → 0.54.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.
Files changed (148) hide show
  1. package/dist/ai-edits/apply.js +390 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +3 -1
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/fonts/embeddedFonts.js +1 -1
  41. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  42. package/dist/i18n/messages/catalogs.gen.js +34 -34
  43. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  44. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  45. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  46. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  47. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  48. package/dist/markdown/escape.d.ts +2 -2
  49. package/dist/markdown/escape.js +4 -5
  50. package/dist/markdown/renderBlock.js +91 -25
  51. package/dist/markdown/renderRuns.d.ts +10 -1
  52. package/dist/markdown/renderRuns.js +320 -59
  53. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  54. package/dist/prosemirror/clearRunColor.js +46 -0
  55. package/dist/prosemirror/commands/comments.js +56 -21
  56. package/dist/prosemirror/commands/formatPainter.js +1 -1
  57. package/dist/prosemirror/commands/hyperlink.js +2 -1
  58. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  59. package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
  60. package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
  61. package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
  62. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
  63. package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
  64. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  65. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  66. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  67. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  68. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
  69. package/dist/prosemirror/conversion/toProseDoc.js +31 -150
  70. package/dist/prosemirror/documentSchema.d.ts +8 -0
  71. package/dist/prosemirror/documentSchema.js +18 -0
  72. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  73. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  74. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  75. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  76. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  77. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  78. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  79. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  80. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  81. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  82. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  83. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  84. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  85. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  86. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  87. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  88. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  89. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  90. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  91. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  92. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  94. package/dist/prosemirror/extensions/types.d.ts +7 -0
  95. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  96. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  97. package/dist/prosemirror/index.d.ts +2 -1
  98. package/dist/prosemirror/index.js +2 -1
  99. package/dist/prosemirror/listNumbering.d.ts +3 -21
  100. package/dist/prosemirror/listNumbering.js +10 -25
  101. package/dist/prosemirror/listRendering.d.ts +27 -0
  102. package/dist/prosemirror/listRendering.js +27 -0
  103. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  104. package/dist/prosemirror/markupViewNotes.js +49 -0
  105. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  106. package/dist/prosemirror/markupViewProjection.js +7 -2
  107. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  108. package/dist/prosemirror/noteReferenceReview.js +332 -0
  109. package/dist/prosemirror/paragraphIndentation.js +5 -2
  110. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  111. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  112. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  113. package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
  114. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  115. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  116. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  117. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  118. package/dist/prosemirror/plugins/index.d.ts +2 -1
  119. package/dist/prosemirror/plugins/index.js +2 -1
  120. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  121. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  122. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  123. package/dist/prosemirror/plugins/suggestionMode.js +391 -28
  124. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  125. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  126. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  127. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  128. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  129. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  130. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  131. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  132. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  133. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  134. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  135. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  136. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  137. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  138. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  139. package/dist/prosemirror/tableCellPaste.js +525 -0
  140. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  141. package/dist/prosemirror/tableGridMutation.js +166 -18
  142. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  143. package/dist/prosemirror/tableRunIn.js +165 -0
  144. package/dist/prosemirror/textInput.js +11 -2
  145. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  146. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  147. package/dist/server.d.ts +2 -2
  148. package/package.json +5 -5
@@ -54,10 +54,6 @@ type BatchClaim = InlineClaim |
54
54
  * any other does — see `followedByInsertion`.
55
55
  */
56
56
  keepsParagraph: boolean;
57
- /** The node leaves the document while the batch is still applying. */
58
- removesNode: boolean;
59
- /** Applied directly rather than tracked. */
60
- direct: boolean;
61
57
  } |
62
58
  /**
63
59
  * Joins `block` with the block starting at `next`, where `block` ends.
@@ -88,11 +84,20 @@ type BatchClaim = InlineClaim |
88
84
  axis: "row" | "column" | "table";
89
85
  ranges: readonly PositionRange[];
90
86
  } |
91
- /** Any other change to a table's grid: rows or columns added, cells merged or split. */
87
+ /**
88
+ * Any other change to a table's grid: rows or columns added, cells merged or
89
+ * split. A merge also names the blank cells it folds into its first one
90
+ * (`folded`, empty for every other change). Tracked, a merge folds only
91
+ * blank cells, since the record moves no content; applied directly, it
92
+ * moves whatever they hold. Another operation of the batch writing into one
93
+ * would let the merge apply in one mode and not the other, so nothing else
94
+ * may claim them.
95
+ */
92
96
  {
93
97
  type: "tableStructure";
94
98
  table: number;
95
99
  axis: "row" | "column" | "cell";
100
+ folded: readonly PositionRange[];
96
101
  } | {
97
102
  type: "unclaimed";
98
103
  };
@@ -106,10 +111,12 @@ declare class BatchClaims {
106
111
  private readonly insertions;
107
112
  private readonly joins;
108
113
  private readonly removals;
114
+ /** Accepted merges' folded cells; see the `tableStructure` claim. */
115
+ private readonly folds;
109
116
  private readonly tables;
110
117
  private readonly rowInsertions;
111
118
  private readonly columnInsertions;
112
- /** Accepted deletions that keep their paragraph, by where the paragraph ends. */
119
+ /** Accepted deletions that keep their paragraph, by where the paragraph starts and ends. */
113
120
  private readonly keptParagraphs;
114
121
  /** `claim`'s roles, given the insertions accepted so far. */
115
122
  private rolesOf;
@@ -124,6 +131,14 @@ declare class BatchClaims {
124
131
  * conflicts with none.
125
132
  */
126
133
  conflictOf(claim: BatchClaim): string | null;
134
+ /** The accepted merge whose folded cells hold `position`. */
135
+ private foldAt;
136
+ /**
137
+ * An accepted operation that claims anything inside `ranges`, the blank
138
+ * cells a merge folds. A block insertion anchored in a cell lands beside its
139
+ * table, so only the cell's own paragraph can be claimed there.
140
+ */
141
+ private claimedInside;
127
142
  add(operationId: string, claim: BatchClaim): void;
128
143
  }
129
144
  //#endregion
@@ -19,9 +19,7 @@ const blockRolesOf = (claim) => {
19
19
  block: claim.block,
20
20
  role: {
21
21
  kind: "deleteBlock",
22
- keepsParagraph: claim.keepsParagraph,
23
- removesNode: claim.removesNode,
24
- direct: claim.direct
22
+ keepsParagraph: claim.keepsParagraph
25
23
  }
26
24
  }];
27
25
  case "merge": return [{
@@ -42,14 +40,16 @@ const blockRolesOf = (claim) => {
42
40
  };
43
41
  /**
44
42
  * A deletion of the paragraph that ends its container, once the batch inserts
45
- * a block after it: the insertion lands first, the paragraph then ends
46
- * nothing, and it goes as any other — tracked, its mark is deleted into the
47
- * inserted block and its properties with it; directly, the node goes.
43
+ * a block next to it. After it, the insertion lands first, the paragraph then
44
+ * ends nothing, and it goes as any other — tracked, its mark is deleted into
45
+ * the inserted block and its properties with it; directly, the node goes.
46
+ * Before it, the paragraph the deletion retires the mark of is the inserted
47
+ * one, a pending insertion, so retiring it joins the two and this paragraph
48
+ * does not stay either.
48
49
  */
49
50
  const followedByInsertion = (role) => ({
50
51
  ...role,
51
- keepsParagraph: false,
52
- removesNode: role.direct
52
+ keepsParagraph: false
53
53
  });
54
54
  const isPoint = ({ from, to }) => from === to;
55
55
  /**
@@ -80,7 +80,7 @@ const rolesConflict = (left, right) => {
80
80
  switch (left.kind) {
81
81
  case "deleteBlock": switch (right.kind) {
82
82
  case "paragraphProperties": return !left.keepsParagraph;
83
- case "mergeNext": return left.removesNode;
83
+ case "mergeNext": return false;
84
84
  default: return true;
85
85
  }
86
86
  case "rewriteBlock": switch (right.kind) {
@@ -122,14 +122,16 @@ var BatchClaims = class {
122
122
  insertions = /* @__PURE__ */ new Map();
123
123
  joins = /* @__PURE__ */ new Map();
124
124
  removals = [];
125
+ /** Accepted merges' folded cells; see the `tableStructure` claim. */
126
+ folds = [];
125
127
  tables = /* @__PURE__ */ new Map();
126
128
  rowInsertions = /* @__PURE__ */ new Map();
127
129
  columnInsertions = /* @__PURE__ */ new Map();
128
- /** Accepted deletions that keep their paragraph, by where the paragraph ends. */
130
+ /** Accepted deletions that keep their paragraph, by where the paragraph starts and ends. */
129
131
  keptParagraphs = /* @__PURE__ */ new Map();
130
132
  /** `claim`'s roles, given the insertions accepted so far. */
131
133
  rolesOf(claim) {
132
- return blockRolesOf(claim).map(({ block, role }) => role.kind === "deleteBlock" && role.keepsParagraph && claim.type === "deleteBlock" && this.insertions.has(claim.end) ? {
134
+ return blockRolesOf(claim).map(({ block, role }) => role.kind === "deleteBlock" && role.keepsParagraph && claim.type === "deleteBlock" && (this.insertions.has(claim.end) || this.insertions.has(claim.block)) ? {
133
135
  block,
134
136
  role: followedByInsertion(role)
135
137
  } : {
@@ -158,6 +160,8 @@ var BatchClaims = class {
158
160
  conflictOf(claim) {
159
161
  for (const { block, role } of this.rolesOf(claim)) {
160
162
  for (const entry of this.blocks.get(block) ?? []) if (rolesConflict(entry.role, role)) return entry.operationId;
163
+ const fold = this.foldAt(block);
164
+ if (fold !== null) return fold;
161
165
  if (role.kind === "deleteBlock") continue;
162
166
  for (const removal of this.removals) if (insideAny(removal.claim.ranges, block)) return removal.operationId;
163
167
  }
@@ -170,7 +174,7 @@ var BatchClaims = class {
170
174
  if (following !== null) return following;
171
175
  return this.removals.find(({ claim: removed }) => strictlyInsideAny(removed.ranges, claim.at))?.operationId ?? null;
172
176
  }
173
- case "tableStructure": return this.removals.find(({ claim: removed }) => insideAny(removed.ranges, claim.table) || removed.table === claim.table && removed.axis !== claim.axis && (removed.axis === "row" || removed.axis === "column"))?.operationId ?? this.removals.find(({ claim: removed }) => removed.wholeTable && removed.table === claim.table)?.operationId ?? null;
177
+ case "tableStructure": return this.claimedInside(claim.folded) ?? this.removals.find(({ claim: removed }) => insideAny(removed.ranges, claim.table) || removed.table === claim.table && removed.axis !== claim.axis && (removed.axis === "row" || removed.axis === "column"))?.operationId ?? this.removals.find(({ claim: removed }) => removed.wholeTable && removed.table === claim.table)?.operationId ?? null;
174
178
  case "tableRemoval":
175
179
  if (claim.axis === "row") {
176
180
  const columnInsertion = this.columnInsertions.get(claim.table);
@@ -195,6 +199,23 @@ var BatchClaims = class {
195
199
  default: return null;
196
200
  }
197
201
  }
202
+ /** The accepted merge whose folded cells hold `position`. */
203
+ foldAt(position) {
204
+ return this.folds.find(({ ranges }) => insideAny(ranges, position))?.operationId ?? null;
205
+ }
206
+ /**
207
+ * An accepted operation that claims anything inside `ranges`, the blank
208
+ * cells a merge folds. A block insertion anchored in a cell lands beside its
209
+ * table, so only the cell's own paragraph can be claimed there.
210
+ */
211
+ claimedInside(ranges) {
212
+ if (ranges.length === 0) return null;
213
+ for (const [block, entries] of this.blocks) {
214
+ const entry = entries[0];
215
+ if (entry !== void 0 && insideAny(ranges, block)) return entry.operationId;
216
+ }
217
+ return null;
218
+ }
198
219
  add(operationId, claim) {
199
220
  for (const { block, role } of this.rolesOf(claim)) {
200
221
  const entries = this.blocks.get(block);
@@ -206,10 +227,16 @@ var BatchClaims = class {
206
227
  operationId,
207
228
  role
208
229
  }]);
209
- if (claim.type === "deleteBlock" && role.kind === "deleteBlock" && role.keepsParagraph) this.keptParagraphs.set(claim.end, {
210
- block,
211
- operationId
212
- });
230
+ if (claim.type === "deleteBlock" && role.kind === "deleteBlock" && role.keepsParagraph) {
231
+ this.keptParagraphs.set(claim.end, {
232
+ block,
233
+ operationId
234
+ });
235
+ this.keptParagraphs.set(claim.block, {
236
+ block,
237
+ operationId
238
+ });
239
+ }
213
240
  }
214
241
  switch (claim.type) {
215
242
  case "merge":
@@ -225,6 +252,10 @@ var BatchClaims = class {
225
252
  break;
226
253
  }
227
254
  case "tableStructure":
255
+ if (claim.folded.length > 0) this.folds.push({
256
+ operationId,
257
+ ranges: claim.folded
258
+ });
228
259
  if (!this.tables.has(claim.table)) this.tables.set(claim.table, operationId);
229
260
  if (claim.axis === "row" && !this.rowInsertions.has(claim.table)) this.rowInsertions.set(claim.table, operationId);
230
261
  if (claim.axis === "column" && !this.columnInsertions.has(claim.table)) this.columnInsertions.set(claim.table, operationId);
@@ -341,6 +341,8 @@ declare class FolioDocxReviewer {
341
341
  private state;
342
342
  private readonly secondaryStoryStates;
343
343
  private readonly removedHeaderFooterStories;
344
+ /** Notes whose reference a resolution removed: a note exists only through its reference. */
345
+ private readonly removedNoteStories;
344
346
  private readonly sectionReferenceRemovals;
345
347
  private importedStyles;
346
348
  private readonly importedMedia;
@@ -445,7 +447,9 @@ declare class FolioDocxReviewer {
445
447
  applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
446
448
  /** Apply a versioned operation batch to one editable document story. */
447
449
  applyDocumentOperationsToStory({ story, batch, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground, undefinedStyles }: FolioApplyDocumentOperationsToStoryOptions): FolioDocumentOperationResult;
450
+ /** Apply a batch; the notes whose references it deletes or removes follow them. */
448
451
  private applyDocumentOperationsInternal;
452
+ private applyDocumentOperationsToStoryState;
449
453
  /** Undo the latest unchanged document-operation batch and its created comments. */
450
454
  undoDocumentOperations(undoHandle: FolioDocumentOperationUndoHandle): FolioDocumentOperationUndoResult;
451
455
  /**
@@ -569,6 +573,22 @@ declare class FolioDocxReviewer {
569
573
  private resolveWithSectionReferenceHistory;
570
574
  private resolveEveryFinalSectionChange;
571
575
  private resolveEveryStory;
576
+ /**
577
+ * Change the body the way a note follows its reference: a note whose
578
+ * reference goes, goes too, as a note no text refers to does; a note whose
579
+ * reference an edit deletes as a tracked change has its text deleted with
580
+ * it; a note whose reference's deletion is rejected gets back the text that
581
+ * deletion took.
582
+ */
583
+ private withNotesFollowingReferences;
584
+ /** Delete the text of each note whose reference went from live to a tracked deletion. */
585
+ private deleteNotesWithTheirReferences;
586
+ /**
587
+ * The notes a story other than the body refers to (a header, a footer, a
588
+ * comment, another note), leaving out references inside the `skipped` notes.
589
+ */
590
+ private noteKeysReferencedOutsideBody;
591
+ private resolveEveryStoryOnce;
572
592
  private acceptOrdinaryChangesWithSuggestions;
573
593
  /** The current document model with edits merged back in. */
574
594
  toDocument(): document_d_exports.Document;
@@ -17,7 +17,7 @@ import { attemptSelectiveSave } from "../docx/selectiveSave.js";
17
17
  import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
18
18
  import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
19
19
  import { captureSectionReferenceInventory, resolvedSectionReferenceLosses, withSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
20
- import { expectCharacterStyleMarkAttrs } from "../prosemirror/attrs/index.js";
20
+ import { expectCharacterStyleMarkAttrs, expectFootnoteRefMarkAttrs } from "../prosemirror/attrs/index.js";
21
21
  import { acceptAIEditRevision, acceptSuggestion, getSuggestions, rejectAIEditRevision, rejectAllSuggestions, rejectSuggestion, resolveAllChangesInHeadlessState, suggestionIdOfRevision } from "../prosemirror/commands/comments.js";
22
22
  import { sectionRejectProperties } from "../prosemirror/commands/propertyChangeScope.js";
23
23
  import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
@@ -26,8 +26,10 @@ import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/A
26
26
  import { getChangedParagraphIds, getTrackedSectionEndpointRemoval, hasStructuralChanges, hasUntrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
27
27
  import { completeNumberingForDoc } from "../prosemirror/listInstanceReferences.js";
28
28
  import "../prosemirror/listMarker.js";
29
+ import { deleteNoteWithReference, noteDeletionRevisions, noteKeysReferencedIn, noteReferenceStates, noteReferencesRestored, referenceDeletions, referencedNotes } from "../prosemirror/noteReferenceReview.js";
29
30
  import { createDocumentNumberingPlugin, withDocumentNumbering } from "../prosemirror/plugins/documentNumbering.js";
30
- import { createDocumentStylesPlugin, getDocumentStyleResolver, withDocumentStyles } from "../prosemirror/plugins/documentStyles.js";
31
+ import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
32
+ import { createDocumentStylesPlugin, withDocumentStyles } from "../prosemirror/plugins/documentStyles.js";
31
33
  import { schema, singletonManager } from "../prosemirror/schema/index.js";
32
34
  import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
33
35
  import { storyListNumbering } from "../prosemirror/storyListNumbering.js";
@@ -35,8 +37,9 @@ import { canonicalJson } from "../utils/canonicalJson.js";
35
37
  import { deterministicHexId } from "../utils/hexId.js";
36
38
  import { buildAnnotatedBlockTextWithNoteReferences } from "./clean-text.js";
37
39
  import { anchoredCommentIdsInBlocks, anchoredCommentIdsInProseDoc, withoutLostCommentThreads } from "./comment-lifecycle.js";
40
+ import { noteReferenceEditsAllowed } from "./note-references.js";
38
41
  import { FolioPendingSuggestionRegistry, createPendingSuggestionSourceSnapshot, withoutPendingSuggestionCommentMarks } from "./pending-suggestions.js";
39
- import { getCommentAnchorsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot } from "./read.js";
42
+ import { getCommentAnchorsFromDoc, getTrackedChangeGroupIdsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot } from "./read.js";
40
43
  import { collectNoteReferenceLabels, createFolioAIEditSnapshotWithStyleResolver, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText, sourceDocumentOf } from "./snapshot.js";
41
44
  import { matchTableGeometry } from "./table-geometry.js";
42
45
  import { tableTemplateCanCrossPackageLosslessly } from "./table-template.js";
@@ -254,6 +257,28 @@ const comparisonAccessByReviewer = /* @__PURE__ */ new WeakMap();
254
257
  const MAIN_STORY = Object.freeze({ type: "main" });
255
258
  const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
256
259
  const noteStoryKey = ({ type, noteId }) => `${type}:${noteId}`;
260
+ /** The note story a {@link noteStoryKey} key names. */
261
+ const noteStoryHandleOf = (key) => {
262
+ const [type, noteId] = key.split(":");
263
+ return (type === "footnote" || type === "endnote") && noteId !== void 0 ? {
264
+ type,
265
+ noteId: Number(noteId)
266
+ } : null;
267
+ };
268
+ /** The notes a story's text refers to, keyed as {@link noteStoryKey} keys them. */
269
+ const referencedNoteKeys = (doc) => {
270
+ const keys = /* @__PURE__ */ new Set();
271
+ doc.descendants((node) => {
272
+ if (!node.isText) return true;
273
+ for (const mark of node.marks) {
274
+ if (mark.type.name !== "footnoteRef") continue;
275
+ const { id, noteType } = expectFootnoteRefMarkAttrs(mark);
276
+ keys.add(`${noteType === "endnote" ? "endnote" : "footnote"}:${String(id)}`);
277
+ }
278
+ return false;
279
+ });
280
+ return keys;
281
+ };
257
282
  /**
258
283
  * A part's roles as a reader label: `first`, or, in a document with several
259
284
  * sections, `first (sections 1, 3), default (section 2)`.
@@ -365,6 +390,8 @@ var FolioDocxReviewer = class FolioDocxReviewer {
365
390
  state;
366
391
  secondaryStoryStates = /* @__PURE__ */ new Map();
367
392
  removedHeaderFooterStories = /* @__PURE__ */ new Map();
393
+ /** Notes whose reference a resolution removed: a note exists only through its reference. */
394
+ removedNoteStories = /* @__PURE__ */ new Map();
368
395
  sectionReferenceRemovals = [];
369
396
  importedStyles;
370
397
  importedMedia = /* @__PURE__ */ new Map();
@@ -905,7 +932,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
905
932
  createUndoEntry: true
906
933
  });
907
934
  }
908
- applyDocumentOperationsInternal({ story, batch, author, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground, undefinedStyles, createUndoEntry }) {
935
+ /** Apply a batch; the notes whose references it deletes or removes follow them. */
936
+ applyDocumentOperationsInternal(options) {
937
+ if (noteReferenceEditsAllowed()) return this.applyDocumentOperationsToStoryState(options);
938
+ return this.withNotesFollowingReferences(() => this.applyDocumentOperationsToStoryState(options));
939
+ }
940
+ applyDocumentOperationsToStoryState({ story, batch, author, snapshot, revisionStamp, wordDiff, tableTemplates, replacementBackground, undefinedStyles, createUndoEntry }) {
909
941
  const beforeState = this.requireEditableStoryState(story);
910
942
  const operationAuthor = author ?? this.author;
911
943
  const sourceSnapshot = snapshot ?? createStateSnapshot(beforeState);
@@ -993,7 +1025,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
993
1025
  undoHandle,
994
1026
  reason: "documentChanged"
995
1027
  };
996
- this.setEditableStoryState(entry.story, entry.beforeState);
1028
+ this.withNotesFollowingReferences(() => this.setEditableStoryState(entry.story, entry.beforeState));
997
1029
  this.createdComments.length = entry.createdCommentsLengthBefore;
998
1030
  this.documentOperationUndoEntries.pop();
999
1031
  return {
@@ -1100,7 +1132,10 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1100
1132
  type: "endnote",
1101
1133
  noteId: endnote.id
1102
1134
  });
1103
- return handles.filter((handle) => handle.type !== "header" && handle.type !== "footer" || !this.removedHeaderFooterStories.has(headerFooterStoryKey(handle)));
1135
+ return handles.filter((handle) => {
1136
+ if (handle.type === "header" || handle.type === "footer") return !this.removedHeaderFooterStories.has(headerFooterStoryKey(handle));
1137
+ return handle.type !== "footnote" && handle.type !== "endnote" || !this.removedNoteStories.has(noteStoryKey(handle));
1138
+ });
1104
1139
  }
1105
1140
  /** Discover every readable document story through a typed, serializable handle. */
1106
1141
  listStories() {
@@ -1373,13 +1408,15 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1373
1408
  * revision is no longer present (already resolved, or never existed).
1374
1409
  */
1375
1410
  acceptChange(target) {
1376
- return this.resolveWithSectionReferenceHistory(() => this.acceptChangeInternal(target));
1411
+ return this.resolveWithSectionReferenceHistory(() => this.withNotesFollowingReferences(() => this.acceptChangeInternal(target)));
1377
1412
  }
1378
1413
  acceptChangeInternal(target) {
1379
1414
  const id = revisionIdOf(target);
1415
+ const ids = getTrackedChangeGroupIdsFromDoc(this.state.doc, id);
1380
1416
  const suggestionId = suggestionIdOfRevision(this.state, id);
1381
1417
  if (suggestionId !== null) this.runCommand(acceptSuggestion(suggestionId, { author: this.author }));
1382
- const bodyChanged = this.runCommand(acceptAIEditRevision(id));
1418
+ let bodyChanged = false;
1419
+ for (const memberId of ids) bodyChanged = this.runCommand(acceptAIEditRevision(memberId)) || bodyChanged;
1383
1420
  const sectionChanged = this.resolveFinalSectionProperties("accept", id) > 0;
1384
1421
  return bodyChanged || sectionChanged;
1385
1422
  }
@@ -1388,13 +1425,15 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1388
1425
  * deletion's text is restored. See {@link acceptChange} for targeting.
1389
1426
  */
1390
1427
  rejectChange(target) {
1391
- return this.resolveWithSectionReferenceHistory(() => this.rejectChangeInternal(target));
1428
+ return this.resolveWithSectionReferenceHistory(() => this.withNotesFollowingReferences(() => this.rejectChangeInternal(target)));
1392
1429
  }
1393
1430
  rejectChangeInternal(target) {
1394
1431
  const id = revisionIdOf(target);
1432
+ const ids = getTrackedChangeGroupIdsFromDoc(this.state.doc, id);
1395
1433
  const suggestionId = suggestionIdOfRevision(this.state, id);
1396
1434
  if (suggestionId !== null && this.runCommand(rejectSuggestion(suggestionId))) return true;
1397
- const bodyChanged = this.runCommand(rejectAIEditRevision(id));
1435
+ let bodyChanged = false;
1436
+ for (const memberId of ids) bodyChanged = this.runCommand(rejectAIEditRevision(memberId)) || bodyChanged;
1398
1437
  const sectionChanged = this.resolveFinalSectionProperties("reject", id) > 0;
1399
1438
  return bodyChanged || sectionChanged;
1400
1439
  }
@@ -1473,6 +1512,77 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1473
1512
  return propertyChanges?.length ?? 0;
1474
1513
  }
1475
1514
  resolveEveryStory(mode) {
1515
+ return this.withNotesFollowingReferences(() => this.resolveEveryStoryOnce(mode));
1516
+ }
1517
+ /**
1518
+ * Change the body the way a note follows its reference: a note whose
1519
+ * reference goes, goes too, as a note no text refers to does; a note whose
1520
+ * reference an edit deletes as a tracked change has its text deleted with
1521
+ * it; a note whose reference's deletion is rejected gets back the text that
1522
+ * deletion took.
1523
+ */
1524
+ withNotesFollowingReferences(change) {
1525
+ const before = this.state.doc;
1526
+ const result = change();
1527
+ const after = this.state.doc;
1528
+ const referencedAfter = referencedNoteKeys(after);
1529
+ for (const key of referencedAfter) this.removedNoteStories.delete(key);
1530
+ const unreferenced = new Set([...referencedNoteKeys(before)].filter((key) => !referencedAfter.has(key)));
1531
+ const referencedElsewhere = unreferenced.size > 0 ? this.noteKeysReferencedOutsideBody(unreferenced) : /* @__PURE__ */ new Set();
1532
+ for (const key of unreferenced) {
1533
+ if (referencedElsewhere.has(key)) continue;
1534
+ const handle = noteStoryHandleOf(key);
1535
+ if (handle) this.removedNoteStories.set(noteStoryKey(handle), handle);
1536
+ }
1537
+ const rejectedDeletions = referenceDeletions(before);
1538
+ for (const key of noteReferencesRestored(before, after)) {
1539
+ const handle = noteStoryHandleOf(key);
1540
+ const state = handle ? this.getEditableStoryState(handle) : null;
1541
+ const reference = rejectedDeletions.get(key);
1542
+ const revisions = state && reference ? noteDeletionRevisions(state.doc, reference) : [];
1543
+ if (handle && revisions.length > 0) this.runStoryCommand(rejectAIEditRevision(revisions), handle);
1544
+ }
1545
+ this.deleteNotesWithTheirReferences(before, after);
1546
+ return result;
1547
+ }
1548
+ /** Delete the text of each note whose reference went from live to a tracked deletion. */
1549
+ deleteNotesWithTheirReferences(before, after) {
1550
+ const was = noteReferenceStates(before);
1551
+ const deletions = referenceDeletions(after);
1552
+ for (const [key, state] of noteReferenceStates(after)) {
1553
+ if (state !== "deleted" || was.get(key) !== "live") continue;
1554
+ const reference = deletions.get(key);
1555
+ if (!reference || reference.attrs["provenance"] === "suggested" || reference.attrs["moveKind"] != null) continue;
1556
+ const handle = noteStoryHandleOf(key);
1557
+ if (!handle || this.removedNoteStories.has(noteStoryKey(handle))) continue;
1558
+ this.runStoryCommand((storyState, dispatch) => {
1559
+ const transaction = deleteNoteWithReference(storyState.tr, reference);
1560
+ if (!transaction.docChanged) return false;
1561
+ dispatch?.(transaction);
1562
+ return true;
1563
+ }, handle);
1564
+ }
1565
+ }
1566
+ /**
1567
+ * The notes a story other than the body refers to (a header, a footer, a
1568
+ * comment, another note), leaving out references inside the `skipped` notes.
1569
+ */
1570
+ noteKeysReferencedOutsideBody(skipped) {
1571
+ const keys = new Set(noteKeysReferencedIn(this.baseDocument.package.document.comments ?? []));
1572
+ for (const handle of this.listStoryHandlesInternal()) {
1573
+ if (handle.type === "main") continue;
1574
+ let source;
1575
+ if (handle.type === "footnote" || handle.type === "endnote") {
1576
+ if (skipped.has(noteStoryKey(handle))) continue;
1577
+ source = this.getNoteStory(handle);
1578
+ } else source = this.getHeaderFooterStory(handle);
1579
+ const state = this.secondaryStoryStates.get(secondaryStoryKey(handle))?.state;
1580
+ const found = state ? referencedNoteKeys(state.doc) : noteKeysReferencedIn(source?.content ?? []);
1581
+ for (const key of found) keys.add(key);
1582
+ }
1583
+ return keys;
1584
+ }
1585
+ resolveEveryStoryOnce(mode) {
1476
1586
  let count = 0;
1477
1587
  const resolvedStories = [];
1478
1588
  const refreshes = [];
@@ -1571,6 +1681,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1571
1681
  secondaryStoryStates,
1572
1682
  sectionReferenceRemovals: [...this.sectionReferenceRemovals],
1573
1683
  removedHeaderFooterStories: [...this.removedHeaderFooterStories.values()],
1684
+ removedNoteStories: [...this.removedNoteStories.values()],
1574
1685
  importedStyles: this.importedStyles,
1575
1686
  importedMedia: new Map(this.importedMedia),
1576
1687
  importedHeaders: new Map(this.importedHeaders),
@@ -1601,6 +1712,20 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1601
1712
  document.package.footers.delete(handle.relationshipId);
1602
1713
  }
1603
1714
  this.mergeEditedSecondaryStories(document, snapshot.secondaryStoryStates);
1715
+ if (snapshot.removedNoteStories.length > 0) {
1716
+ const candidates = new Set(snapshot.removedNoteStories.map(noteStoryKey));
1717
+ const referenced = referencedNotes(document, candidates);
1718
+ const removed = new Set([...candidates].filter((key) => !referenced.has(key)));
1719
+ const { footnotes, endnotes } = document.package;
1720
+ if (footnotes) document.package.footnotes = footnotes.filter((note) => !removed.has(noteStoryKey({
1721
+ type: "footnote",
1722
+ noteId: note.id
1723
+ })));
1724
+ if (endnotes) document.package.endnotes = endnotes.filter((note) => !removed.has(noteStoryKey({
1725
+ type: "endnote",
1726
+ noteId: note.id
1727
+ })));
1728
+ }
1604
1729
  const definitions = [...document.package.document.comments ?? [], ...snapshot.createdComments];
1605
1730
  const kept = this.keptCommentThreads(definitions, snapshot.mainState, snapshot.secondaryStoryStates);
1606
1731
  if (kept.length !== definitions.length || snapshot.createdComments.length > 0 || snapshot.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides(kept, snapshot.resolvedOverrides);
@@ -1610,7 +1735,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1610
1735
  const snapshot = this.captureReviewerState();
1611
1736
  const pendingCommentIds = new Set(this.exportPendingSuggestions().flatMap(({ commentId }) => commentId === void 0 ? [] : [commentId]));
1612
1737
  const changedParaIds = new Set(getChangedParagraphIds(snapshot.mainState));
1613
- let structuralChange = hasStructuralChanges(snapshot.mainState) || snapshot.finalSectionPropertiesOverride !== void 0 || snapshot.importedStyles !== void 0 || snapshot.importedHeaders.size > 0 || snapshot.importedFooters.size > 0;
1738
+ let structuralChange = hasStructuralChanges(snapshot.mainState) || snapshot.finalSectionPropertiesOverride !== void 0 || snapshot.importedStyles !== void 0 || snapshot.importedHeaders.size > 0 || snapshot.importedFooters.size > 0 || snapshot.removedNoteStories.length > 0;
1614
1739
  let untrackedChanges = hasUntrackedChanges(snapshot.mainState);
1615
1740
  const changedNoteParaIds = /* @__PURE__ */ new Set();
1616
1741
  for (const entry of snapshot.secondaryStoryStates) {
@@ -2032,6 +2157,10 @@ var FolioDocxReviewer = class FolioDocxReviewer {
2032
2157
  }
2033
2158
  addAll(anchoredNow, anchoredCommentIdsInBlocks((handle.type === "header" || handle.type === "footer" ? this.getHeaderFooterStory(handle) : this.getNoteStory(handle))?.content ?? []));
2034
2159
  }
2160
+ for (const handle of this.removedNoteStories.values()) {
2161
+ const entry = loaded.get(secondaryStoryKey(handle));
2162
+ addAll(anchoredBefore, entry ? anchoredCommentIdsInProseDoc(entry.initialState.doc) : anchoredCommentIdsInBlocks(this.getNoteStory(handle)?.content ?? []));
2163
+ }
2035
2164
  return withoutLostCommentThreads(comments, {
2036
2165
  anchoredBefore,
2037
2166
  anchoredNow
@@ -1,6 +1,6 @@
1
1
  import { parseFolioDocumentOperationBatch } from "../document-operations.js";
2
2
  import { rejectAllSuggestions } from "../prosemirror/commands/comments.js";
3
- import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
3
+ import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
4
4
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
5
5
  import { canonicalJson } from "../utils/canonicalJson.js";
6
6
  import { createFolioAIEditSnapshotWithStyleResolver, hashFolioAIBlockText, sourceDocumentOf } from "./snapshot.js";
@@ -62,14 +62,16 @@ type FolioCommentAnchor = {
62
62
  declare const getTrackedChangesFromSnapshot: (snapshot: FolioAIEditSnapshot) => FolioReviewChange[];
63
63
  /**
64
64
  * The tracked changes present in the body, read from inline marks and
65
- * structural node attributes. Runs of one inline revision within a block fold
66
- * into a single entry.
65
+ * structural node attributes. Adjacent runs sharing a revision wrapper path
66
+ * fold into one entry; interrupted or differently nested wrappers stay separate.
67
67
  *
68
68
  * A tracked row insertion or deletion marks the row AND every run in its
69
69
  * cells, the way Word writes it. Both halves are one change, so the run marks
70
70
  * fold into the row's entry rather than reporting a second time.
71
71
  */
72
72
  declare const getTrackedChangesFromDoc: (doc: Node) => FolioReviewChange[];
73
+ /** Physical revision ids behind an inline change split by markup. */
74
+ declare const getTrackedChangeGroupIdsFromDoc: (doc: Node, id: number) => number[];
73
75
  /** @internal Read revision statistics without paying for an unrelated block projection. */
74
76
  declare const getTrackedChangeStatsFromDoc: (doc: Node) => {
75
77
  highestId: number;
@@ -82,4 +84,4 @@ declare const getTrackedChangeStatsFromDoc: (doc: Node) => {
82
84
  */
83
85
  declare const getCommentAnchorsFromDoc: (doc: Node) => FolioCommentAnchor[];
84
86
  //#endregion
85
- export { FOLIO_REVIEW_CHANGE_KINDS, FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot };
87
+ export { FOLIO_REVIEW_CHANGE_KINDS, FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangeGroupIdsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot };