@stll/folio-core 0.52.0 → 0.53.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 (307) hide show
  1. package/README.md +23 -0
  2. package/dist/ai-edits/apply.d.ts +26 -1
  3. package/dist/ai-edits/apply.js +1519 -105
  4. package/dist/ai-edits/batch-claims.d.ts +130 -0
  5. package/dist/ai-edits/batch-claims.js +244 -0
  6. package/dist/ai-edits/blockRange.js +9 -3
  7. package/dist/ai-edits/character-boundaries.d.ts +43 -0
  8. package/dist/ai-edits/character-boundaries.js +95 -0
  9. package/dist/ai-edits/clean-text.d.ts +33 -3
  10. package/dist/ai-edits/clean-text.js +52 -6
  11. package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
  12. package/dist/ai-edits/comment-lifecycle.js +52 -0
  13. package/dist/ai-edits/headless.d.ts +71 -7
  14. package/dist/ai-edits/headless.js +381 -41
  15. package/dist/ai-edits/index.d.ts +2 -1
  16. package/dist/ai-edits/index.js +2 -1
  17. package/dist/ai-edits/minimal-replacement.d.ts +21 -6
  18. package/dist/ai-edits/minimal-replacement.js +134 -15
  19. package/dist/ai-edits/newListNumbering.d.ts +23 -0
  20. package/dist/ai-edits/newListNumbering.js +94 -0
  21. package/dist/ai-edits/note-references.d.ts +96 -0
  22. package/dist/ai-edits/note-references.js +139 -0
  23. package/dist/ai-edits/pending-suggestions.d.ts +79 -0
  24. package/dist/ai-edits/pending-suggestions.js +306 -0
  25. package/dist/ai-edits/read.d.ts +1 -1
  26. package/dist/ai-edits/read.js +56 -43
  27. package/dist/ai-edits/result-validation.d.ts +29 -0
  28. package/dist/ai-edits/result-validation.js +164 -0
  29. package/dist/ai-edits/snapshot.d.ts +36 -3
  30. package/dist/ai-edits/snapshot.js +177 -23
  31. package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
  32. package/dist/ai-edits/table-cell-mutations.js +4 -1
  33. package/dist/ai-edits/table-geometry.d.ts +1 -1
  34. package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
  35. package/dist/ai-edits/table-row-column-mutations.js +61 -26
  36. package/dist/ai-edits/types.d.ts +155 -16
  37. package/dist/compare/compare.d.ts +1 -1
  38. package/dist/compare/compare.js +65 -16
  39. package/dist/compare/content-alignment.js +13 -2
  40. package/dist/compare/content-types.d.ts +7 -0
  41. package/dist/compare/content.d.ts +4 -1
  42. package/dist/compare/content.js +6 -1
  43. package/dist/compare/formatting.js +2 -1
  44. package/dist/compare/inline-atoms.d.ts +21 -4
  45. package/dist/compare/inline-atoms.js +138 -16
  46. package/dist/compare/plan.js +10 -5
  47. package/dist/compare/scenario.js +14 -3
  48. package/dist/compare/section-boundary-properties.js +1 -1
  49. package/dist/compare/types.d.ts +4 -2
  50. package/dist/compare/types.js +2 -1
  51. package/dist/compare/verification.d.ts +1 -1
  52. package/dist/compare/verification.js +15 -3
  53. package/dist/controller/committedLayoutDiff.d.ts +25 -0
  54. package/dist/controller/committedLayoutDiff.js +71 -0
  55. package/dist/controller/contentControlWidgetController.d.ts +1 -1
  56. package/dist/controller/folioEditor.d.ts +1 -1
  57. package/dist/controller/fontReadiness.d.ts +70 -3
  58. package/dist/controller/fontReadiness.js +100 -10
  59. package/dist/controller/headerFooterEditorManager.js +6 -1
  60. package/dist/controller/layoutPipeline.d.ts +12 -10
  61. package/dist/controller/layoutPipeline.js +62 -9
  62. package/dist/controller/layoutRunOptions.d.ts +24 -0
  63. package/dist/controller/layoutRunOptions.js +17 -0
  64. package/dist/controller/layoutScheduler.d.ts +19 -14
  65. package/dist/controller/layoutScheduler.js +15 -12
  66. package/dist/controller/layoutSession.d.ts +26 -2
  67. package/dist/controller/layoutSession.js +1 -1
  68. package/dist/controller/noteEditorManager.js +8 -3
  69. package/dist/document-operations.d.ts +11 -2
  70. package/dist/document-operations.js +230 -21
  71. package/dist/docx/altChunk.d.ts +8 -0
  72. package/dist/docx/altChunk.js +17 -0
  73. package/dist/docx/attributeRemainder.d.ts +1 -1
  74. package/dist/docx/blockContentParser.d.ts +64 -1
  75. package/dist/docx/blockContentParser.js +17 -4
  76. package/dist/docx/blockCustomXmlShell.d.ts +7 -0
  77. package/dist/docx/blockCustomXmlShell.js +14 -0
  78. package/dist/docx/blockPlainText.js +7 -0
  79. package/dist/docx/commentAnchorIndex.js +1 -1
  80. package/dist/docx/commentRangeIntegrity.js +8 -3
  81. package/dist/docx/commentReferenceNormalization.js +1 -1
  82. package/dist/docx/commentReplyMarkers.js +20 -1
  83. package/dist/docx/compatibility.d.ts +1 -1
  84. package/dist/docx/compatibility.js +34 -13
  85. package/dist/docx/containerChildren.gen.d.ts +1 -1
  86. package/dist/docx/containerChildren.gen.js +1 -0
  87. package/dist/docx/documentParser.js +12 -12
  88. package/dist/docx/drawingIdNormalization.d.ts +3 -1
  89. package/dist/docx/drawingIdNormalization.js +6 -1
  90. package/dist/docx/ensureParaIds.js +119 -97
  91. package/dist/docx/footnoteParser.js +8 -4
  92. package/dist/docx/headerFooterParser.js +3 -3
  93. package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
  94. package/dist/docx/headerFooterReferenceNormalization.js +52 -27
  95. package/dist/docx/hyperlinkParser.d.ts +1 -1
  96. package/dist/docx/index.d.ts +1 -1
  97. package/dist/docx/listNumberingInstances.d.ts +59 -0
  98. package/dist/docx/listNumberingInstances.js +249 -0
  99. package/dist/docx/normalizeBaseDirection.js +2 -1
  100. package/dist/docx/opaqueCarrier.d.ts +7 -0
  101. package/dist/docx/opaqueCarrier.js +38 -0
  102. package/dist/docx/packageParts.js +4 -1
  103. package/dist/docx/paragraphParser.js +91 -24
  104. package/dist/docx/paragraphPropertySource.d.ts +4 -2
  105. package/dist/docx/paragraphPropertySource.js +7 -1
  106. package/dist/docx/paragraphTraversal.js +1 -0
  107. package/dist/docx/parseWarningMessage.js +4 -1
  108. package/dist/docx/parser.js +173 -20
  109. package/dist/docx/rezip.js +27 -5
  110. package/dist/docx/sdtProperties.js +1 -1
  111. package/dist/docx/sdtPropertiesPatch.js +3 -0
  112. package/dist/docx/selectiveSave.js +16 -9
  113. package/dist/docx/selectiveXmlPatch.js +106 -37
  114. package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
  115. package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
  116. package/dist/docx/serializer/documentSerializer.js +2 -0
  117. package/dist/docx/serializer/headerFooterSerializer.js +2 -0
  118. package/dist/docx/serializer/noteSerializer.js +3 -1
  119. package/dist/docx/serializer/paragraphSerializer.js +33 -9
  120. package/dist/docx/serializer/tableSerializer.js +25 -38
  121. package/dist/docx/server/createBilingualDocument.js +3 -3
  122. package/dist/docx/tableParser.d.ts +157 -1
  123. package/dist/docx/tableParser.js +89 -10
  124. package/dist/docx/textBoxParser.js +1 -1
  125. package/dist/docx/unzip.d.ts +2 -1
  126. package/dist/docx/unzip.js +1 -1
  127. package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
  128. package/dist/docx/wordprocessingPrefixes.js +286 -0
  129. package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
  130. package/dist/i18n/messages/catalogs.gen.js +153 -17
  131. package/dist/i18n/messages/messages.gen.d.ts +8 -0
  132. package/dist/internal/compare/inline-presentation.d.ts +2 -1
  133. package/dist/internal/compare/inline-presentation.js +11 -10
  134. package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
  135. package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
  136. package/dist/internal/indexedPositionMap.d.ts +11 -0
  137. package/dist/internal/indexedPositionMap.js +42 -0
  138. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  139. package/dist/internal/revisionResolutionEdits.d.ts +13 -0
  140. package/dist/internal/revisionResolutionEdits.js +19 -0
  141. package/dist/internal/revisionResolutionInline.d.ts +24 -0
  142. package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
  143. package/dist/internal/revisionResolutionStep.d.ts +34 -0
  144. package/dist/internal/revisionResolutionStep.js +282 -0
  145. package/dist/internal/revisionResolutionTracking.d.ts +11 -0
  146. package/dist/internal/revisionResolutionTracking.js +124 -0
  147. package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
  148. package/dist/internal/wholeStoryRevisionResolution.js +530 -0
  149. package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
  150. package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
  151. package/dist/layout-bridge/convert/tableConversion.js +1 -1
  152. package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
  153. package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
  154. package/dist/layout-engine/layoutInstrumentation.js +4 -1
  155. package/dist/layout-engine/measure/cache.d.ts +18 -1
  156. package/dist/layout-engine/measure/cache.js +31 -1
  157. package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
  158. package/dist/layout-engine/measure/measureContainer.js +7 -47
  159. package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
  160. package/dist/layout-engine/measure/measureHelpers.js +22 -2
  161. package/dist/layout-engine/measure/measureWorker.js +2 -3
  162. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
  163. package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
  164. package/dist/layout-engine/types.d.ts +6 -0
  165. package/dist/layout-painter/renderPage.js +1 -0
  166. package/dist/layout-painter/renderParagraph.js +1 -1
  167. package/dist/managers/ContextMenuManager.js +5 -2
  168. package/dist/managers/types.d.ts +3 -0
  169. package/dist/markdown/fromMarkdown.js +2 -1
  170. package/dist/markdown/internals.d.ts +11 -1
  171. package/dist/markdown/internals.js +24 -3
  172. package/dist/markdown/renderBlock.js +39 -9
  173. package/dist/markdown/renderParagraph.d.ts +13 -1
  174. package/dist/markdown/renderParagraph.js +85 -38
  175. package/dist/markdown/renderRuns.js +3 -10
  176. package/dist/markdown/renderTable.js +24 -12
  177. package/dist/markdown/trailers.js +2 -2
  178. package/dist/markdown/types.d.ts +29 -7
  179. package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
  180. package/dist/paged-layout/incrementalMeasure.js +1 -9
  181. package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
  182. package/dist/paged-layout/transactionDirtyRange.js +29 -41
  183. package/dist/panel-layout.d.ts +83 -0
  184. package/dist/panel-layout.js +96 -0
  185. package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
  186. package/dist/prosemirror/anchoredTextBoxes.js +156 -0
  187. package/dist/prosemirror/attrs/index.d.ts +6 -2
  188. package/dist/prosemirror/attrs/index.js +172 -1
  189. package/dist/prosemirror/commands/comments.d.ts +7 -8
  190. package/dist/prosemirror/commands/comments.js +241 -207
  191. package/dist/prosemirror/commands/contentControls.d.ts +1 -1
  192. package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
  193. package/dist/prosemirror/commands/formatting.d.ts +1 -1
  194. package/dist/prosemirror/commands/index.d.ts +2 -2
  195. package/dist/prosemirror/commands/index.js +2 -2
  196. package/dist/prosemirror/commands/paragraph.d.ts +7 -1
  197. package/dist/prosemirror/commands/paragraph.js +9 -1
  198. package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
  199. package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
  200. package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
  201. package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
  202. package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
  203. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
  204. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
  205. package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
  206. package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
  207. package/dist/prosemirror/commands/sectionBreak.js +2 -1
  208. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
  209. package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
  210. package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
  211. package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
  212. package/dist/prosemirror/conversion/toProseDoc.js +39 -4
  213. package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
  214. package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
  215. package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
  216. package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
  217. package/dist/prosemirror/extensions/StarterKit.js +3 -1
  218. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  219. package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
  220. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
  221. package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
  222. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
  223. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
  224. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
  225. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
  226. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
  227. package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
  228. package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
  229. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
  230. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
  231. package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
  232. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
  233. package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
  234. package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
  235. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
  236. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
  237. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
  238. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
  239. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
  240. package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
  241. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
  242. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  243. package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
  244. package/dist/prosemirror/extensions/types.d.ts +4 -1
  245. package/dist/prosemirror/findReplaceSelection.js +1 -1
  246. package/dist/prosemirror/index.d.ts +3 -2
  247. package/dist/prosemirror/index.js +3 -2
  248. package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
  249. package/dist/prosemirror/indexedNodeLookup.js +103 -0
  250. package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
  251. package/dist/prosemirror/listAutoformatMarkers.js +98 -0
  252. package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
  253. package/dist/prosemirror/listInstanceReferences.js +72 -0
  254. package/dist/prosemirror/listLabels.d.ts +24 -0
  255. package/dist/prosemirror/listLabels.js +111 -0
  256. package/dist/prosemirror/listNumbering.d.ts +110 -0
  257. package/dist/prosemirror/listNumbering.js +345 -0
  258. package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
  259. package/dist/prosemirror/listRenderingAttrs.js +8 -1
  260. package/dist/prosemirror/markupViewProjection.d.ts +95 -0
  261. package/dist/prosemirror/markupViewProjection.js +83 -0
  262. package/dist/prosemirror/numberingAttr.d.ts +9 -1
  263. package/dist/prosemirror/numberingAttr.js +9 -1
  264. package/dist/prosemirror/outlineLevelAttr.js +1 -1
  265. package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
  266. package/dist/prosemirror/paragraphIndentation.js +29 -7
  267. package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
  268. package/dist/prosemirror/paragraphMarkJoin.js +24 -0
  269. package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
  270. package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
  271. package/dist/prosemirror/plugins/documentNumbering.js +97 -11
  272. package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
  273. package/dist/prosemirror/plugins/documentStyles.js +14 -1
  274. package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
  275. package/dist/prosemirror/plugins/suggestionMode.js +116 -52
  276. package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
  277. package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
  278. package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
  279. package/dist/prosemirror/positionSweep.d.ts +24 -0
  280. package/dist/prosemirror/positionSweep.js +222 -0
  281. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
  282. package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
  283. package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
  284. package/dist/prosemirror/replacedAnnotations.js +170 -1
  285. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
  286. package/dist/prosemirror/schema/index.d.ts +2 -2
  287. package/dist/prosemirror/schema/index.js +3 -0
  288. package/dist/prosemirror/schema/nodes.d.ts +42 -11
  289. package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
  290. package/dist/prosemirror/sectionMarkEdits.js +59 -0
  291. package/dist/prosemirror/storyListNumbering.d.ts +17 -0
  292. package/dist/prosemirror/storyListNumbering.js +102 -0
  293. package/dist/prosemirror/tableGridMutation.d.ts +48 -2
  294. package/dist/prosemirror/tableGridMutation.js +106 -1
  295. package/dist/prosemirror/validation.js +5 -1
  296. package/dist/redline.js +93 -9
  297. package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
  298. package/dist/render-dom/BodySelectionOverlay.js +3 -2
  299. package/dist/utils/findReplace.js +2 -2
  300. package/dist/utils/mergeDocumentContent.js +1 -1
  301. package/dist/utils/noteReferenceLabels.d.ts +36 -0
  302. package/dist/utils/noteReferenceLabels.js +32 -0
  303. package/dist/utils/replaceText.js +1 -1
  304. package/dist/version-comparison.d.ts +1 -0
  305. package/dist/version-comparison.js +1 -0
  306. package/package.json +3 -2
  307. package/dist/internal/headlessRevisionResolution.d.ts +0 -23
@@ -5,9 +5,10 @@ import { FolioAIEditApplyOutcome, FolioAIEditView, FolioRevisionStamp, FolioWord
5
5
  import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange } from "./blockRange.js";
6
6
  import { buildAnnotatedBlockText } from "./clean-text.js";
7
7
  import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
8
+ import { FOLIO_PENDING_SUGGESTION_VERSION, FolioPendingSuggestionLoadResult, FolioPendingSuggestionRecord, FolioPendingSuggestionRegistry, FolioPendingSuggestionStaleReason, createPendingSuggestionSourceSnapshot } from "./pending-suggestions.js";
8
9
  import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
9
10
  import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
10
11
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
11
12
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
12
13
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
13
- export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
14
+ export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_PENDING_SUGGESTION_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioPendingSuggestionLoadResult, type FolioPendingSuggestionRecord, FolioPendingSuggestionRegistry, type FolioPendingSuggestionStaleReason, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, createPendingSuggestionSourceSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
@@ -4,8 +4,9 @@ import { applyFolioAIEditOperations } from "./apply.js";
4
4
  import { clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange } from "./blockRange.js";
5
5
  import { buildAnnotatedBlockText } from "./clean-text.js";
6
6
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
7
+ import { FOLIO_PENDING_SUGGESTION_VERSION, FolioPendingSuggestionRegistry, createPendingSuggestionSourceSnapshot } from "./pending-suggestions.js";
7
8
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
8
9
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
9
10
  import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
10
11
  import { WORD_DIFF_GRANULARITIES, diffWordSegments } from "./word-diff.js";
11
- export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
12
+ export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_PENDING_SUGGESTION_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioPendingSuggestionRegistry, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, createPendingSuggestionSourceSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
@@ -36,11 +36,13 @@ declare const shortestTokenDiff: (before: readonly string[], after: readonly str
36
36
  *
37
37
  * Words (with their leading whitespace, and edge punctuation on its own; see
38
38
  * `tokenizeWords`) are aligned by the shortest edit script, and each changed
39
- * stretch is then trimmed to the characters that actually differ, so
40
- * appending a letter to a word that spans two runs leaves both runs alone. A
41
- * rewrite past {@link MAX_TOKEN_EDITS}, or a diff that does not reconstruct
42
- * both strings exactly (it never should), is one change over the whole span,
43
- * trimmed the same way.
39
+ * stretch is then trimmed: to whole words it shares at either end, and once
40
+ * what is left lies within one word on both sides, to the letters that
41
+ * actually differ. Appending a letter to a word that spans two runs leaves
42
+ * both runs alone, and a rewrite of several words never keeps a letter two of
43
+ * them merely happen to share. A rewrite past {@link MAX_TOKEN_EDITS}, or a
44
+ * diff that does not reconstruct both strings exactly (it never should), is
45
+ * one change over the whole span, trimmed the same way.
44
46
  */
45
47
  declare const planTextChanges: (source: string, replacement: string) => TextChange[];
46
48
  /**
@@ -58,7 +60,20 @@ declare const changesFromSegments: (segments: readonly WordDiffSegment[]) => Tex
58
60
  * `spans` are in the same coordinates as the changes (the source span's).
59
61
  */
60
62
  declare const widenChangesToAtomicSpans: (source: string, changes: readonly TextChange[], spans: readonly AtomicTextSpan[]) => TextChange[];
63
+ /**
64
+ * `changes` cut around every atomic span (a field result) that `kept`, another
65
+ * plan of the same replacement, leaves untouched.
66
+ *
67
+ * A redline's changes are cut for a reader and may swallow a field whose
68
+ * displayed text the replacement repeats; rewriting it deletes the field and
69
+ * inserts its text as plain text, which the direct edit, keeping the field,
70
+ * never does. Cutting the change around the field keeps it in both modes,
71
+ * and a piece of the cut that no longer changes anything is dropped. Where
72
+ * the field's text does not stand in the change's new text exactly where
73
+ * `kept` puts it, the change is left whole.
74
+ */
75
+ declare const keepAtomicSpans: (source: string, replacement: string, changes: readonly TextChange[], kept: readonly TextChange[], spans: readonly AtomicTextSpan[]) => TextChange[];
61
76
  /** `source` with `changes` applied; the invariant the planner owes. */
62
77
  declare const applyTextChanges: (source: string, changes: readonly TextChange[]) => string;
63
78
  //#endregion
64
- export { AtomicTextSpan, MAX_TOKEN_EDITS, TextChange, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
79
+ export { AtomicTextSpan, MAX_TOKEN_EDITS, TextChange, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, keepAtomicSpans, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
@@ -36,14 +36,24 @@ import { tokenizeWords } from "./word-diff.js";
36
36
  * A replacement carrying inline emphasis markup (`**bold**`) states its own
37
37
  * formatting and still replaces the match whole in direct mode.
38
38
  *
39
+ * A change across several words is trimmed only to whole words it shares
40
+ * with the replacement at either end ({@link planTextChanges}): the letters
41
+ * two different words happen to share are not the same text, and keeping one
42
+ * would leave part of a new word in the formatting of the word it replaced.
43
+ *
39
44
  * Tracked-changes and suggested modes cut their changes from the redline diff
40
45
  * instead ({@link changesFromSegments}: word or character granularity, as the
41
46
  * caller asked), and map them onto the document by the same rules. A change
42
47
  * marks exactly the characters it removes as a deletion, which keeps their
43
- * runs, and writes its text as an insertion after them, formatted as the
44
- * first removed character that is not a note reference; a pure insertion is
45
- * placed and formatted as above. Nothing outside a change is wrapped in a
46
- * revision.
48
+ * runs, and writes its text as an insertion after them. The insertion's
49
+ * formatting is not decided by the redline's cut: every character of it takes
50
+ * the formatting the direct edit gives that character, so accepting the
51
+ * redline leaves exactly what the direct edit writes, although the redline
52
+ * rewrites whole words the direct edit only touches. Nothing outside a change
53
+ * is wrapped in a revision.
54
+ *
55
+ * Whichever mode applies it, a replacement that rewrites characters of more
56
+ * than one formatting reports `uniformReplacementFormatting` in its receipt.
47
57
  *
48
58
  * This module is the pure half: clean-text offsets in, changes out. The
49
59
  * applier maps each change onto the document.
@@ -64,15 +74,64 @@ const commonSuffixLength = (a, b) => {
64
74
  while (length < limit && a.charCodeAt(a.length - 1 - length) === b.charCodeAt(b.length - 1 - length)) length++;
65
75
  return length > 0 && isLowSurrogate(a.charCodeAt(a.length - length)) ? length - 1 : length;
66
76
  };
67
- /** `change` without the characters its deleted and inserted text share at either end. */
77
+ /** A letter, digit, combining mark or connector: what a word is made of. */
78
+ const WORD_CHARACTER = /^[\p{L}\p{N}\p{M}\p{Pc}]$/u;
79
+ const codePointBefore = (text, index) => {
80
+ const low = text.charCodeAt(index - 1);
81
+ return index >= 2 && isLowSurrogate(low) && isHighSurrogate(text.charCodeAt(index - 2)) ? text.slice(index - 2, index) : text.slice(index - 1, index);
82
+ };
83
+ const codePointAt = (text, index) => String.fromCodePoint(text.codePointAt(index) ?? 0);
84
+ /** Whether `index` separates two words of `text` rather than two letters of one. */
85
+ const isWordBoundary = (text, index) => index <= 0 || index >= text.length || !WORD_CHARACTER.test(codePointBefore(text, index)) || !WORD_CHARACTER.test(codePointAt(text, index));
86
+ /** At most one word, with whatever separators around it. */
87
+ const SINGLE_WORD = /^[^\p{L}\p{N}\p{M}\p{Pc}]*[\p{L}\p{N}\p{M}\p{Pc}]*[^\p{L}\p{N}\p{M}\p{Pc}]*$/u;
88
+ /**
89
+ * The longest length up to `length` that ends (`fromEnd` false) or starts
90
+ * (`fromEnd` true) on a word boundary of both strings.
91
+ */
92
+ const wordAlignedLength = (left, right, length, fromEnd) => {
93
+ for (let candidate = length; candidate > 0; candidate--) {
94
+ const leftIndex = fromEnd ? left.length - candidate : candidate;
95
+ const rightIndex = fromEnd ? right.length - candidate : candidate;
96
+ if (isWordBoundary(left, leftIndex) && isWordBoundary(right, rightIndex)) return candidate;
97
+ }
98
+ return 0;
99
+ };
100
+ /**
101
+ * `change` without the characters its deleted and inserted text share at either
102
+ * end.
103
+ *
104
+ * Within one word, letter by letter: `Supplier` to `Suppliers` inserts one `s`
105
+ * and leaves both runs of a word split across two alone. A change spanning
106
+ * several words keeps only whole words it shares at an end: the `s` that
107
+ * `agrees` and `performs` happen to end with is a coincidence, and keeping it
108
+ * would leave the last letter of `performs` in the formatting `agrees` had
109
+ * while the rest of the word takes the formatting of the text it replaces.
110
+ */
68
111
  const trimChange = (source, change) => {
69
- const removed = source.slice(change.start, change.end);
70
- const prefix = commonPrefixLength(removed, change.text);
71
- const suffix = commonSuffixLength(removed.slice(prefix), change.text.slice(prefix));
112
+ let removed = source.slice(change.start, change.end);
113
+ let inserted = change.text;
114
+ let prefix = 0;
115
+ let suffix = 0;
116
+ for (const granularity of ["word", "letter"]) {
117
+ const withinOneWord = SINGLE_WORD.test(removed) && SINGLE_WORD.test(inserted);
118
+ if (granularity === "letter" && !withinOneWord) break;
119
+ const sharedPrefix = commonPrefixLength(removed, inserted);
120
+ const nextPrefix = withinOneWord ? sharedPrefix : wordAlignedLength(removed, inserted, sharedPrefix, false);
121
+ removed = removed.slice(nextPrefix);
122
+ inserted = inserted.slice(nextPrefix);
123
+ const sharedSuffix = commonSuffixLength(removed, inserted);
124
+ const nextSuffix = withinOneWord ? sharedSuffix : wordAlignedLength(removed, inserted, sharedSuffix, true);
125
+ removed = removed.slice(0, removed.length - nextSuffix);
126
+ inserted = inserted.slice(0, inserted.length - nextSuffix);
127
+ prefix += nextPrefix;
128
+ suffix += nextSuffix;
129
+ if (withinOneWord) break;
130
+ }
72
131
  const trimmed = {
73
132
  start: change.start + prefix,
74
133
  end: change.end - suffix,
75
- text: change.text.slice(prefix, change.text.length - suffix)
134
+ text: inserted
76
135
  };
77
136
  return trimmed.start === trimmed.end && trimmed.text.length === 0 ? null : trimmed;
78
137
  };
@@ -169,11 +228,13 @@ const shortestTokenDiff = (before, after, maxEdits = MAX_TOKEN_EDITS) => {
169
228
  *
170
229
  * Words (with their leading whitespace, and edge punctuation on its own; see
171
230
  * `tokenizeWords`) are aligned by the shortest edit script, and each changed
172
- * stretch is then trimmed to the characters that actually differ, so
173
- * appending a letter to a word that spans two runs leaves both runs alone. A
174
- * rewrite past {@link MAX_TOKEN_EDITS}, or a diff that does not reconstruct
175
- * both strings exactly (it never should), is one change over the whole span,
176
- * trimmed the same way.
231
+ * stretch is then trimmed: to whole words it shares at either end, and once
232
+ * what is left lies within one word on both sides, to the letters that
233
+ * actually differ. Appending a letter to a word that spans two runs leaves
234
+ * both runs alone, and a rewrite of several words never keeps a letter two of
235
+ * them merely happen to share. A rewrite past {@link MAX_TOKEN_EDITS}, or a
236
+ * diff that does not reconstruct both strings exactly (it never should), is
237
+ * one change over the whole span, trimmed the same way.
177
238
  */
178
239
  const planTextChanges = (source, replacement) => {
179
240
  if (source === replacement) return [];
@@ -268,6 +329,64 @@ const widenChangesToAtomicSpans = (source, changes, spans) => {
268
329
  }
269
330
  return merged;
270
331
  };
332
+ /** Where each character `changes` keeps of `source` lands in the result. */
333
+ const keptPositions = (source, changes) => {
334
+ const kept = /* @__PURE__ */ new Map();
335
+ let shift = 0;
336
+ let cursor = 0;
337
+ for (const change of changes) {
338
+ for (; cursor < change.start; cursor++) kept.set(cursor, cursor + shift);
339
+ cursor = change.end;
340
+ shift += change.text.length - (change.end - change.start);
341
+ }
342
+ for (; cursor < source.length; cursor++) kept.set(cursor, cursor + shift);
343
+ return kept;
344
+ };
345
+ /**
346
+ * `changes` cut around every atomic span (a field result) that `kept`, another
347
+ * plan of the same replacement, leaves untouched.
348
+ *
349
+ * A redline's changes are cut for a reader and may swallow a field whose
350
+ * displayed text the replacement repeats; rewriting it deletes the field and
351
+ * inserts its text as plain text, which the direct edit, keeping the field,
352
+ * never does. Cutting the change around the field keeps it in both modes,
353
+ * and a piece of the cut that no longer changes anything is dropped. Where
354
+ * the field's text does not stand in the change's new text exactly where
355
+ * `kept` puts it, the change is left whole.
356
+ */
357
+ const keepAtomicSpans = (source, replacement, changes, kept, spans) => {
358
+ if (spans.length === 0) return [...changes];
359
+ const keptAt = keptPositions(source, kept);
360
+ const cut = [];
361
+ let shift = 0;
362
+ for (const change of changes) {
363
+ const replacementStart = change.start + shift;
364
+ shift += change.text.length - (change.end - change.start);
365
+ let sourceCursor = change.start;
366
+ let textCursor = 0;
367
+ for (const { offset, length } of spans) {
368
+ if (offset < sourceCursor || offset + length > change.end || length === 0) continue;
369
+ const lands = keptAt.get(offset);
370
+ const wholeAndInOrder = lands !== void 0 && Array.from({ length }, (_, index) => keptAt.get(offset + index)).every((position, index) => position === lands + index);
371
+ const local = lands === void 0 ? -1 : lands - replacementStart;
372
+ if (!wholeAndInOrder || local < textCursor || change.text.slice(local, local + length) !== source.slice(offset, offset + length)) continue;
373
+ cut.push({
374
+ start: sourceCursor,
375
+ end: offset,
376
+ text: change.text.slice(textCursor, local)
377
+ });
378
+ sourceCursor = offset + length;
379
+ textCursor = local + length;
380
+ }
381
+ cut.push({
382
+ start: sourceCursor,
383
+ end: change.end,
384
+ text: change.text.slice(textCursor)
385
+ });
386
+ }
387
+ const pieces = cut.filter(({ start, end, text }) => (start < end || text.length > 0) && source.slice(start, end) !== text);
388
+ return applyTextChanges(source, pieces) === replacement ? pieces : [...changes];
389
+ };
271
390
  /** `source` with `changes` applied; the invariant the planner owes. */
272
391
  const applyTextChanges = (source, changes) => {
273
392
  let result = "";
@@ -279,4 +398,4 @@ const applyTextChanges = (source, changes) => {
279
398
  return result + source.slice(cursor);
280
399
  };
281
400
  //#endregion
282
- export { MAX_TOKEN_EDITS, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
401
+ export { MAX_TOKEN_EDITS, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, keepAtomicSpans, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
@@ -0,0 +1,23 @@
1
+ import { FolioAIEditOperation, FolioAIListNumbering, FolioAIListReference, FolioAINewListReference } from "./types.js";
2
+ import { NumberingMap } from "../docx/numberingParser.js";
3
+ //#region src/ai-edits/newListNumbering.d.ts
4
+ /** Whether `numbering` asks for a new list rather than naming an instance. */
5
+ declare const isFolioAINewListReference: (numbering: FolioAIListNumbering) => numbering is FolioAINewListReference;
6
+ /**
7
+ * The concrete instance an operation's numbering names once new lists are
8
+ * resolved. A new-list request reaching this point skipped resolution, which
9
+ * is a bug in the apply path rather than a malformed operation.
10
+ */
11
+ declare const concreteListReference: (numbering: FolioAIListNumbering) => FolioAIListReference;
12
+ type ResolvedNewLists = {
13
+ operations: FolioAIEditOperation[];
14
+ numbering: NumberingMap | null;
15
+ };
16
+ /**
17
+ * Replace every new-list request with a reference to an instance minted for
18
+ * its operation: one instance per operation and kind. `numbering` is the map
19
+ * the batch applies against; the result's map adds the minted instances.
20
+ */
21
+ declare const resolveNewListOperations: (operations: readonly FolioAIEditOperation[], numbering: NumberingMap | null) => ResolvedNewLists;
22
+ //#endregion
23
+ export { concreteListReference, isFolioAINewListReference, resolveNewListOperations };
@@ -0,0 +1,94 @@
1
+ import { mintListInstance } from "../docx/listNumberingInstances.js";
2
+ import { createNumberingMap } from "../docx/numberingParser.js";
3
+ import { panic } from "better-result";
4
+ //#region src/ai-edits/newListNumbering.ts
5
+ /**
6
+ * Operations that start a new list (`numbering: { start: "new", kind }`)
7
+ * resolved to the numbering instance each one defines.
8
+ *
9
+ * The instance is minted against the document's numbering (the package's
10
+ * definitions plus every instance its paragraphs already define), so its id
11
+ * collides with nothing, and against the instances earlier operations in the
12
+ * batch minted, so two operations start two lists. The paragraphs the
13
+ * operation numbers then carry the instance's rendering, which is what
14
+ * defines it in the saved package (see `docx/listNumberingInstances.ts`).
15
+ */
16
+ /** Whether `numbering` asks for a new list rather than naming an instance. */
17
+ const isFolioAINewListReference = (numbering) => "start" in numbering;
18
+ /**
19
+ * The concrete instance an operation's numbering names once new lists are
20
+ * resolved. A new-list request reaching this point skipped resolution, which
21
+ * is a bug in the apply path rather than a malformed operation.
22
+ */
23
+ const concreteListReference = (numbering) => {
24
+ if (isFolioAINewListReference(numbering)) return panic("A new-list numbering request reached apply unresolved");
25
+ return numbering;
26
+ };
27
+ /**
28
+ * Replace every new-list request with a reference to an instance minted for
29
+ * its operation: one instance per operation and kind. `numbering` is the map
30
+ * the batch applies against; the result's map adds the minted instances.
31
+ */
32
+ const resolveNewListOperations = (operations, numbering) => {
33
+ let definitions = numbering?.definitions;
34
+ let minted = false;
35
+ const resolveOperation = (operation) => {
36
+ const instances = /* @__PURE__ */ new Map();
37
+ const resolve = (value) => {
38
+ if (value === null || value === void 0 || !isFolioAINewListReference(value)) return value;
39
+ let numId = instances.get(value.kind);
40
+ if (numId === void 0) {
41
+ const instance = mintListInstance(definitions, { kind: value.kind });
42
+ definitions = instance.definitions;
43
+ numId = instance.numId;
44
+ instances.set(value.kind, numId);
45
+ minted = true;
46
+ }
47
+ return {
48
+ numId,
49
+ level: value.level ?? 0
50
+ };
51
+ };
52
+ const resolveProperties = (properties) => properties?.numbering ? {
53
+ ...properties,
54
+ numbering: resolve(properties.numbering)
55
+ } : properties;
56
+ switch (operation.type) {
57
+ case "insertAfterBlock":
58
+ case "insertBeforeBlock": return operation.numbering ? {
59
+ ...operation,
60
+ numbering: resolve(operation.numbering)
61
+ } : operation;
62
+ case "setBlockParagraphProperties": return operation.properties.numbering ? {
63
+ ...operation,
64
+ properties: {
65
+ ...operation.properties,
66
+ numbering: resolve(operation.properties.numbering)
67
+ }
68
+ } : operation;
69
+ case "splitBlock": {
70
+ const firstParagraphProperties = resolveProperties(operation.firstParagraphProperties);
71
+ const secondParagraphProperties = resolveProperties(operation.secondParagraphProperties);
72
+ return {
73
+ ...operation,
74
+ ...firstParagraphProperties && { firstParagraphProperties },
75
+ ...secondParagraphProperties && { secondParagraphProperties }
76
+ };
77
+ }
78
+ case "mergeBlockWithNext": {
79
+ const mergedParagraphProperties = resolveProperties(operation.mergedParagraphProperties);
80
+ return {
81
+ ...operation,
82
+ ...mergedParagraphProperties && { mergedParagraphProperties }
83
+ };
84
+ }
85
+ default: return operation;
86
+ }
87
+ };
88
+ return {
89
+ operations: operations.map(resolveOperation),
90
+ numbering: minted && definitions ? createNumberingMap(definitions) : numbering
91
+ };
92
+ };
93
+ //#endregion
94
+ export { concreteListReference, isFolioAINewListReference, resolveNewListOperations };
@@ -0,0 +1,96 @@
1
+ import { NoteReferenceKind } from "../utils/noteReferenceLabels.js";
2
+ import { TextChange } from "./minimal-replacement.js";
3
+ import { CleanBlockText } from "./clean-text.js";
4
+ import { Node } from "prosemirror-model";
5
+ //#region src/ai-edits/note-references.d.ts
6
+ /** Which note a reference names: its kind and its package id. */
7
+ type NoteReferenceIdentity = {
8
+ noteType: NoteReferenceKind;
9
+ noteId: string;
10
+ };
11
+ /** The reference a text node carries, or `null` for ordinary text. */
12
+ declare const noteReferenceOf: (node: Node) => NoteReferenceIdentity | null;
13
+ /**
14
+ * The marker each reference of a story shows, keyed by note: numbered once
15
+ * for a whole story so every block reads the same number the Markdown export
16
+ * gives it, and so an edit elsewhere in one batch cannot renumber a block the
17
+ * batch resolved earlier.
18
+ */
19
+ type NoteReferenceLabels = {
20
+ labelOf: (reference: NoteReferenceIdentity) => string | undefined;
21
+ };
22
+ /** Labels that grow as the blocks of a story are numbered, in reading order. */
23
+ type NoteReferenceLabeler = NoteReferenceLabels & {
24
+ /**
25
+ * Number the references of one block. A reference the clean view omits
26
+ * (tracked-deleted, hidden) takes no number, exactly as its characters take
27
+ * no place in the clean text.
28
+ */
29
+ numberBlock: (block: Node) => void;
30
+ };
31
+ /**
32
+ * The one numbering every AI reader of a story shares. The snapshot numbers
33
+ * block by block inside its own walk; `collectNoteReferenceLabels` (with the
34
+ * snapshot, so both skip what the snapshot skips) numbers a whole story.
35
+ */
36
+ declare const createNoteReferenceLabeler: () => NoteReferenceLabeler;
37
+ /**
38
+ * The text a reader shows for an inline text node: a note reference's marker
39
+ * instead of its package id. A reference the clean view omits (a tracked
40
+ * deletion) has no number there, and reads as an unnumbered marker.
41
+ */
42
+ declare const readerTextOf: (node: Node, labels: NoteReferenceLabels) => string;
43
+ /** A note reference's marker inside a span of clean text, relative to the span. */
44
+ type NoteReferenceSpan = {
45
+ offset: number;
46
+ length: number;
47
+ };
48
+ type StructuralBoundaries = Pick<CleanBlockText, "structuralBoundaries">;
49
+ /** The markers lying wholly inside clean text `[start, end)`, relative to `start`. */
50
+ declare const noteReferenceSpansWithin: ({ structuralBoundaries }: StructuralBoundaries, start: number, end: number) => NoteReferenceSpan[];
51
+ /** Whether clean text `[start, end)` starts or ends inside a reference's marker. */
52
+ declare const cutsIntoNoteReference: ({ structuralBoundaries }: StructuralBoundaries, start: number, end: number) => boolean;
53
+ /**
54
+ * @internal Run `apply` with its text operations free to remove note
55
+ * references and to write marker text in place of new ones.
56
+ *
57
+ * Only a document comparison does this. Its operations come from a revised
58
+ * document that adds and removes references, so refusing them would drop the
59
+ * edit; each marker it writes is turned back into the reference the revised
60
+ * document holds once the operations land (`compare/inline-atoms.ts`), and a
61
+ * removed reference is a tracked deletion of the reference itself. Every other
62
+ * caller keeps the protection. The scope is synchronous, like the applier.
63
+ */
64
+ declare const withComparisonNoteReferenceEdits: <T>(apply: () => T) => T;
65
+ /** @internal Whether the running apply belongs to a comparison (see above). */
66
+ declare const noteReferenceEditsAllowed: () => boolean;
67
+ /** The pieces of a replacement between the markers it has to keep. */
68
+ type NoteReferenceSegments = {
69
+ /** The source text between markers, with its offset in the source. */
70
+ source: {
71
+ offset: number;
72
+ text: string;
73
+ }[];
74
+ /** What each source piece becomes. */
75
+ replacement: string[];
76
+ };
77
+ /**
78
+ * Pair a text replacement with the reference markers its source holds.
79
+ *
80
+ * A reference is not text, so a replacement may only rewrite the prose around
81
+ * it: every marker of `source` has to reappear in `replacement`, in order, and
82
+ * the prose between two markers becomes the prose between the same two. A
83
+ * replacement that drops, reorders or rewrites a marker — or writes a new
84
+ * marker-shaped string, which would read as a reference and be none — answers
85
+ * `null`: the edit is refused rather than deleting a reference, or leaving
86
+ * text behind that claims to be one.
87
+ */
88
+ declare const segmentsAroundNoteReferences: (source: string, replacement: string, spans: readonly NoteReferenceSpan[]) => NoteReferenceSegments | null;
89
+ /**
90
+ * Plan a replacement's changes piece by piece between the markers it keeps, so
91
+ * no change ever touches a reference. `null` when the replacement does not
92
+ * keep them (see {@link segmentsAroundNoteReferences}).
93
+ */
94
+ declare const planChangesAroundNoteReferences: (source: string, replacement: string, spans: readonly NoteReferenceSpan[], planChanges: (source: string, replacement: string) => readonly TextChange[]) => TextChange[] | null;
95
+ //#endregion
96
+ export { NoteReferenceIdentity, NoteReferenceLabeler, NoteReferenceLabels, NoteReferenceSpan, createNoteReferenceLabeler, cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, readerTextOf, segmentsAroundNoteReferences, withComparisonNoteReferenceEdits };
@@ -0,0 +1,139 @@
1
+ import { createNoteReferenceNumbering, noteReferenceMarker } from "../utils/noteReferenceLabels.js";
2
+ //#region src/ai-edits/note-references.ts
3
+ const NOTE_REFERENCE_MARK = "footnoteRef";
4
+ const DELETION_MARK = "deletion";
5
+ const HIDDEN_MARK = "hidden";
6
+ /** The reference a text node carries, or `null` for ordinary text. */
7
+ const noteReferenceOf = (node) => {
8
+ if (!node.isText) return null;
9
+ const mark = node.marks.find((candidate) => candidate.type.name === NOTE_REFERENCE_MARK);
10
+ if (!mark) return null;
11
+ return {
12
+ noteType: mark.attrs["noteType"] === "endnote" ? "endnote" : "footnote",
13
+ noteId: String(mark.attrs["id"])
14
+ };
15
+ };
16
+ const labelKey = ({ noteType, noteId }) => `${noteType}:${noteId}`;
17
+ const isOmittedFromCleanView = (node) => node.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK);
18
+ /**
19
+ * The one numbering every AI reader of a story shares. The snapshot numbers
20
+ * block by block inside its own walk; `collectNoteReferenceLabels` (with the
21
+ * snapshot, so both skip what the snapshot skips) numbers a whole story.
22
+ */
23
+ const createNoteReferenceLabeler = () => {
24
+ const labels = /* @__PURE__ */ new Map();
25
+ const numbering = createNoteReferenceNumbering();
26
+ return {
27
+ labelOf: (reference) => labels.get(labelKey(reference)),
28
+ numberBlock: (block) => {
29
+ block.descendants((node) => {
30
+ const reference = noteReferenceOf(node);
31
+ if (reference === null || isOmittedFromCleanView(node)) return true;
32
+ const { displayNumber, first } = numbering.next(reference.noteType, reference.noteId);
33
+ if (first) labels.set(labelKey(reference), noteReferenceMarker(reference.noteType, displayNumber));
34
+ return true;
35
+ });
36
+ }
37
+ };
38
+ };
39
+ /**
40
+ * The text a reader shows for an inline text node: a note reference's marker
41
+ * instead of its package id. A reference the clean view omits (a tracked
42
+ * deletion) has no number there, and reads as an unnumbered marker.
43
+ */
44
+ const readerTextOf = (node, labels) => {
45
+ const reference = noteReferenceOf(node);
46
+ if (reference === null) return node.text ?? "";
47
+ return labels.labelOf(reference) ?? (reference.noteType === "endnote" ? UNNUMBERED_ENDNOTE_MARKER : UNNUMBERED_FOOTNOTE_MARKER);
48
+ };
49
+ const UNNUMBERED_FOOTNOTE_MARKER = "[^?]";
50
+ const UNNUMBERED_ENDNOTE_MARKER = "[^e?]";
51
+ /** The markers lying wholly inside clean text `[start, end)`, relative to `start`. */
52
+ const noteReferenceSpansWithin = ({ structuralBoundaries }, start, end) => structuralBoundaries.flatMap((boundary) => boundary.type === "noteReference" && boundary.offset >= start && boundary.offset + boundary.length <= end ? [{
53
+ offset: boundary.offset - start,
54
+ length: boundary.length
55
+ }] : []);
56
+ /** Whether clean text `[start, end)` starts or ends inside a reference's marker. */
57
+ const cutsIntoNoteReference = ({ structuralBoundaries }, start, end) => structuralBoundaries.some((boundary) => boundary.type === "noteReference" && [start, end].some((offset) => offset > boundary.offset && offset < boundary.offset + boundary.length));
58
+ let comparisonEditsNoteReferences = false;
59
+ /**
60
+ * @internal Run `apply` with its text operations free to remove note
61
+ * references and to write marker text in place of new ones.
62
+ *
63
+ * Only a document comparison does this. Its operations come from a revised
64
+ * document that adds and removes references, so refusing them would drop the
65
+ * edit; each marker it writes is turned back into the reference the revised
66
+ * document holds once the operations land (`compare/inline-atoms.ts`), and a
67
+ * removed reference is a tracked deletion of the reference itself. Every other
68
+ * caller keeps the protection. The scope is synchronous, like the applier.
69
+ */
70
+ const withComparisonNoteReferenceEdits = (apply) => {
71
+ const previous = comparisonEditsNoteReferences;
72
+ comparisonEditsNoteReferences = true;
73
+ try {
74
+ return apply();
75
+ } finally {
76
+ comparisonEditsNoteReferences = previous;
77
+ }
78
+ };
79
+ /** @internal Whether the running apply belongs to a comparison (see above). */
80
+ const noteReferenceEditsAllowed = () => comparisonEditsNoteReferences;
81
+ const NOTE_MARKER_PATTERN = /\[\^e?[1-9]\d*\]/gu;
82
+ const markerCount = (text) => text.match(NOTE_MARKER_PATTERN)?.length ?? 0;
83
+ /**
84
+ * Pair a text replacement with the reference markers its source holds.
85
+ *
86
+ * A reference is not text, so a replacement may only rewrite the prose around
87
+ * it: every marker of `source` has to reappear in `replacement`, in order, and
88
+ * the prose between two markers becomes the prose between the same two. A
89
+ * replacement that drops, reorders or rewrites a marker — or writes a new
90
+ * marker-shaped string, which would read as a reference and be none — answers
91
+ * `null`: the edit is refused rather than deleting a reference, or leaving
92
+ * text behind that claims to be one.
93
+ */
94
+ const segmentsAroundNoteReferences = (source, replacement, spans) => {
95
+ const segments = {
96
+ source: [],
97
+ replacement: []
98
+ };
99
+ let sourceCursor = 0;
100
+ let replacementCursor = 0;
101
+ for (const { offset, length } of spans) {
102
+ const marker = source.slice(offset, offset + length);
103
+ const at = replacement.indexOf(marker, replacementCursor);
104
+ if (at === -1) return null;
105
+ segments.source.push({
106
+ offset: sourceCursor,
107
+ text: source.slice(sourceCursor, offset)
108
+ });
109
+ segments.replacement.push(replacement.slice(replacementCursor, at));
110
+ sourceCursor = offset + length;
111
+ replacementCursor = at + marker.length;
112
+ }
113
+ segments.source.push({
114
+ offset: sourceCursor,
115
+ text: source.slice(sourceCursor)
116
+ });
117
+ segments.replacement.push(replacement.slice(replacementCursor));
118
+ for (const [index, piece] of segments.replacement.entries()) if (markerCount(piece) > markerCount(segments.source[index]?.text ?? "")) return null;
119
+ const sourceProse = segments.source.map(({ text }) => text).join("");
120
+ const replacementProse = segments.replacement.join("");
121
+ if (markerCount(replacementProse) > markerCount(sourceProse)) return null;
122
+ return segments;
123
+ };
124
+ /**
125
+ * Plan a replacement's changes piece by piece between the markers it keeps, so
126
+ * no change ever touches a reference. `null` when the replacement does not
127
+ * keep them (see {@link segmentsAroundNoteReferences}).
128
+ */
129
+ const planChangesAroundNoteReferences = (source, replacement, spans, planChanges) => {
130
+ const segments = segmentsAroundNoteReferences(source, replacement, spans);
131
+ if (segments === null) return null;
132
+ return segments.source.flatMap(({ offset, text }, index) => planChanges(text, segments.replacement[index] ?? "").map((change) => ({
133
+ start: change.start + offset,
134
+ end: change.end + offset,
135
+ text: change.text
136
+ })));
137
+ };
138
+ //#endregion
139
+ export { createNoteReferenceLabeler, cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, readerTextOf, segmentsAroundNoteReferences, withComparisonNoteReferenceEdits };