@stll/folio-core 0.45.0 → 0.47.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 (451) hide show
  1. package/dist/ai-edits/apply.js +85 -43
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/read.d.ts +1 -0
  4. package/dist/ai-edits/read.js +1 -0
  5. package/dist/ai-edits/snapshot.js +32 -27
  6. package/dist/ai-edits/table-cell-mutations.js +15 -7
  7. package/dist/ai-edits/table-geometry.js +21 -18
  8. package/dist/ai-edits/table-template.js +2 -2
  9. package/dist/compare/compare.js +2 -5
  10. package/dist/compare/content-alignment.js +108 -12
  11. package/dist/compare/content-types.d.ts +8 -2
  12. package/dist/compare/content.js +2 -11
  13. package/dist/compare/plan.js +7 -4
  14. package/dist/compare/scenario.js +2 -5
  15. package/dist/compare/section-boundary-properties.js +1 -2
  16. package/dist/compare/types.d.ts +9 -1
  17. package/dist/compare/types.js +12 -1
  18. package/dist/compat/eigenpal.d.ts +2 -1
  19. package/dist/compat/eigenpal.js +2 -1
  20. package/dist/content-controls/checkboxDisplay.d.ts +17 -0
  21. package/dist/content-controls/checkboxDisplay.js +46 -0
  22. package/dist/content-controls/findContentControls.js +3 -1
  23. package/dist/content-controls/mutateContentControls.js +57 -43
  24. package/dist/controller/contentControlWidgetController.d.ts +1 -0
  25. package/dist/controller/contentControlWidgetController.js +3 -0
  26. package/dist/controller/hiddenEditorApi.js +3 -1
  27. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  28. package/dist/controller/hiddenEditorManager.js +6 -3
  29. package/dist/controller/layoutPipeline.js +8 -3
  30. package/dist/display-list/build/buildDisplayList.js +20 -1
  31. package/dist/display-list/build/imagePrimitives.d.ts +4 -4
  32. package/dist/display-list/build/imagePrimitives.js +38 -18
  33. package/dist/display-list/build/pageBorderPrimitives.js +2 -1
  34. package/dist/display-list/build/paragraphPrimitives.js +113 -14
  35. package/dist/display-list/build/previewPrimitives.d.ts +10 -0
  36. package/dist/display-list/build/previewPrimitives.js +89 -0
  37. package/dist/display-list/build/strokes.d.ts +177 -8
  38. package/dist/display-list/build/strokes.js +167 -47
  39. package/dist/display-list/build/tablePrimitives.js +4 -2
  40. package/dist/display-list/build/textBoxPrimitives.js +6 -4
  41. package/dist/display-list/build/textDecorations.d.ts +15 -1
  42. package/dist/display-list/build/textDecorations.js +15 -1
  43. package/dist/display-list/dom/renderDisplayListToDom.js +11 -21
  44. package/dist/display-list/primitives.d.ts +13 -2
  45. package/dist/display-list/primitives.js +36 -1
  46. package/dist/display-list/types.d.ts +17 -0
  47. package/dist/document-operations.js +2 -1
  48. package/dist/docx/attributeRemainder.d.ts +76 -0
  49. package/dist/docx/attributeRemainder.js +111 -0
  50. package/dist/docx/blockContentParser.d.ts +3 -1
  51. package/dist/docx/blockContentParser.js +102 -106
  52. package/dist/docx/blockPlainText.d.ts +3 -4
  53. package/dist/docx/blockPlainText.js +3 -0
  54. package/dist/docx/borderParser.js +26 -11
  55. package/dist/docx/builtInStyles.d.ts +8 -16
  56. package/dist/docx/builtInStyles.js +54 -17
  57. package/dist/docx/commentAnchorIndex.d.ts +30 -0
  58. package/dist/docx/commentAnchorIndex.js +50 -0
  59. package/dist/docx/commentParser.d.ts +1 -1
  60. package/dist/docx/commentParser.js +56 -39
  61. package/dist/docx/commentRangeIntegrity.d.ts +16 -1
  62. package/dist/docx/commentRangeIntegrity.js +42 -1
  63. package/dist/docx/commentRangeJoin.d.ts +9 -0
  64. package/dist/docx/commentRangeJoin.js +33 -0
  65. package/dist/docx/commentReferenceCompletion.d.ts +9 -0
  66. package/dist/docx/commentReferenceCompletion.js +47 -0
  67. package/dist/docx/commentReferenceNormalization.js +3 -1
  68. package/dist/docx/commentReplyMarkers.js +11 -19
  69. package/dist/docx/compatibility.js +56 -41
  70. package/dist/docx/containerChildren.d.ts +208 -0
  71. package/dist/docx/containerChildren.gen.d.ts +43 -0
  72. package/dist/docx/containerChildren.gen.js +263 -0
  73. package/dist/docx/containerChildren.js +153 -0
  74. package/dist/docx/diagramPreview.d.ts +14 -2
  75. package/dist/docx/diagramPreview.js +81 -169
  76. package/dist/docx/documentParser.d.ts +1 -1
  77. package/dist/docx/documentParser.js +7 -4
  78. package/dist/docx/drawingAnchor.d.ts +87 -0
  79. package/dist/docx/drawingAnchor.js +201 -0
  80. package/dist/docx/drawingUtils.d.ts +43 -38
  81. package/dist/docx/drawingUtils.js +205 -118
  82. package/dist/docx/ensureParaIds.js +17 -7
  83. package/dist/docx/fieldParser.d.ts +1 -1
  84. package/dist/docx/fieldParser.js +4 -4
  85. package/dist/docx/fieldState.d.ts +26 -0
  86. package/dist/docx/fieldState.js +53 -0
  87. package/dist/docx/fontTableParser.js +102 -41
  88. package/dist/docx/footnoteParser.d.ts +1 -1
  89. package/dist/docx/footnoteParser.js +10 -6
  90. package/dist/docx/graphicFrameLocks.d.ts +16 -4
  91. package/dist/docx/graphicFrameLocks.js +19 -6
  92. package/dist/docx/groupDrawingParser.js +4 -3
  93. package/dist/docx/headerFooterParser.js +1 -1
  94. package/dist/docx/headerFooterReferenceNormalization.js +1 -0
  95. package/dist/docx/hyperlinkParser.d.ts +66 -9
  96. package/dist/docx/hyperlinkParser.js +153 -26
  97. package/dist/docx/imageParser.js +160 -115
  98. package/dist/docx/index.d.ts +2 -1
  99. package/dist/docx/index.js +2 -1
  100. package/dist/docx/inlineContentRemovals.d.ts +28 -0
  101. package/dist/docx/inlineContentRemovals.js +46 -0
  102. package/dist/docx/inlineWrapperContent.d.ts +166 -0
  103. package/dist/docx/inlineWrapperContent.js +165 -0
  104. package/dist/docx/inlineWrapperParser.d.ts +11 -0
  105. package/dist/docx/inlineWrapperParser.js +60 -0
  106. package/dist/docx/nonVisualDrawingProps.d.ts +5 -13
  107. package/dist/docx/normalizeBaseDirection.js +12 -1
  108. package/dist/docx/numberingParser.d.ts +25 -2
  109. package/dist/docx/numberingParser.js +264 -361
  110. package/dist/docx/numberingReference.d.ts +9 -16
  111. package/dist/docx/numberingReference.js +17 -16
  112. package/dist/docx/numberingReferenceNormalization.js +7 -7
  113. package/dist/docx/ooxmlCounterFormatter.d.ts +1 -1
  114. package/dist/docx/ooxmlCounterFormatter.js +4 -1
  115. package/dist/docx/paraIdAttribute.d.ts +111 -0
  116. package/dist/docx/paraIdAttribute.js +142 -0
  117. package/dist/docx/paragraphParser.d.ts +10 -25
  118. package/dist/docx/paragraphParser.js +421 -496
  119. package/dist/docx/paragraphProperties.d.ts +23 -0
  120. package/dist/docx/paragraphProperties.js +279 -0
  121. package/dist/docx/paragraphPropertySource.js +7 -1
  122. package/dist/docx/paragraphTextBoxEnrichment.d.ts +3 -2
  123. package/dist/docx/paragraphTextBoxEnrichment.js +20 -59
  124. package/dist/docx/paragraphTraversal.d.ts +14 -10
  125. package/dist/docx/paragraphTraversal.js +53 -52
  126. package/dist/docx/parseWarningMessage.js +5 -1
  127. package/dist/docx/parserEnums.d.ts +6 -8
  128. package/dist/docx/parserEnums.js +4 -6
  129. package/dist/docx/preservedRunContent.d.ts +32 -0
  130. package/dist/docx/preservedRunContent.js +96 -0
  131. package/dist/docx/previewBudget.d.ts +44 -23
  132. package/dist/docx/previewBudget.js +61 -30
  133. package/dist/docx/propertyElementAttributes.d.ts +178 -0
  134. package/dist/docx/propertyElementAttributes.js +114 -0
  135. package/dist/docx/renderedPageBreakNormalization.js +4 -6
  136. package/dist/docx/rezip.d.ts +28 -2
  137. package/dist/docx/rezip.js +152 -27
  138. package/dist/docx/runConsolidator.d.ts +42 -2
  139. package/dist/docx/runConsolidator.js +60 -6
  140. package/dist/docx/runParser.d.ts +55 -19
  141. package/dist/docx/runParser.js +394 -306
  142. package/dist/docx/runPayload.d.ts +5 -0
  143. package/dist/docx/runPayload.js +4 -0
  144. package/dist/docx/sdtProperties.d.ts +39 -5
  145. package/dist/docx/sdtProperties.js +205 -84
  146. package/dist/docx/sdtPropertiesPatch.d.ts +5 -9
  147. package/dist/docx/sdtPropertiesPatch.js +68 -112
  148. package/dist/docx/sectionParser.d.ts +1 -2
  149. package/dist/docx/sectionParser.js +241 -238
  150. package/dist/docx/selectiveSave.js +3 -2
  151. package/dist/docx/selectiveXmlPatch.d.ts +66 -3
  152. package/dist/docx/selectiveXmlPatch.js +276 -133
  153. package/dist/docx/serializer/blockSdtSerializer.js +2 -96
  154. package/dist/docx/serializer/borderSerializer.d.ts +5 -5
  155. package/dist/docx/serializer/borderSerializer.js +12 -10
  156. package/dist/docx/serializer/commentSerializer.js +5 -7
  157. package/dist/docx/serializer/documentSerializer.js +11 -11
  158. package/dist/docx/serializer/fontTableSerializer.js +31 -3
  159. package/dist/docx/serializer/headerFooterSerializer.js +10 -9
  160. package/dist/docx/serializer/markupRangeAttributes.d.ts +9 -1
  161. package/dist/docx/serializer/markupRangeAttributes.js +9 -1
  162. package/dist/docx/serializer/noteSerializer.js +11 -8
  163. package/dist/docx/serializer/numberingSerializer.d.ts +1 -6
  164. package/dist/docx/serializer/numberingSerializer.js +81 -79
  165. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -9
  166. package/dist/docx/serializer/paragraphSerializer.js +156 -236
  167. package/dist/docx/serializer/runSerializer.d.ts +1 -5
  168. package/dist/docx/serializer/runSerializer.js +134 -75
  169. package/dist/docx/serializer/sdtPropertiesSerializer.d.ts +26 -0
  170. package/dist/docx/serializer/sdtPropertiesSerializer.js +111 -0
  171. package/dist/docx/serializer/sectionPropertiesSerializer.js +42 -49
  172. package/dist/docx/serializer/settingsSerializer.js +6 -4
  173. package/dist/docx/serializer/stylesSerializer.js +8 -11
  174. package/dist/docx/serializer/tableSerializer.js +283 -105
  175. package/dist/docx/serializer/textFormattingSerializer.d.ts +28 -4
  176. package/dist/docx/serializer/textFormattingSerializer.js +111 -101
  177. package/dist/docx/serializer/xmlUtils.d.ts +1 -1
  178. package/dist/docx/serializer/xmlUtils.js +1 -1
  179. package/dist/docx/server/createBilingualDocument.js +20 -16
  180. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  181. package/dist/docx/server/materializeYjsDocx.js +9 -1
  182. package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
  183. package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
  184. package/dist/docx/settingsParser.js +7 -4
  185. package/dist/docx/shadingParser.d.ts +2 -1
  186. package/dist/docx/shadingParser.js +43 -7
  187. package/dist/docx/shapeParser.js +13 -8
  188. package/dist/docx/strictNames.d.ts +11 -0
  189. package/dist/docx/strictNames.gen.d.ts +45 -0
  190. package/dist/docx/strictNames.gen.js +50 -0
  191. package/dist/docx/strictNames.js +43 -0
  192. package/dist/docx/strictValueEncodings.gen.js +133 -131
  193. package/dist/docx/styleParser.js +17 -349
  194. package/dist/docx/tableParser.d.ts +11 -2
  195. package/dist/docx/tableParser.js +665 -220
  196. package/dist/docx/textBoxParser.d.ts +28 -3
  197. package/dist/docx/textBoxParser.js +69 -9
  198. package/dist/docx/themeColorAttribute.d.ts +13 -0
  199. package/dist/docx/themeColorAttribute.js +24 -0
  200. package/dist/docx/trackedMoveRangeNormalization.js +2 -1
  201. package/dist/docx/unzip.js +5 -4
  202. package/dist/docx/vmlImageParser.d.ts +30 -5
  203. package/dist/docx/vmlImageParser.js +91 -32
  204. package/dist/docx/vmlPreview.js +2 -2
  205. package/dist/docx/wrapPolygon.d.ts +30 -0
  206. package/dist/docx/wrapPolygon.js +70 -0
  207. package/dist/docx/xmlEncoding.d.ts +5 -0
  208. package/dist/docx/xmlEncoding.js +12 -0
  209. package/dist/docx/xmlParser.d.ts +23 -1
  210. package/dist/docx/xmlParser.js +31 -2
  211. package/dist/fonts/embeddedFonts.js +3 -3
  212. package/dist/headless-layout.js +12 -1
  213. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  214. package/dist/i18n/messages/catalogs.gen.js +102 -34
  215. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  216. package/dist/index.d.ts +2 -1
  217. package/dist/index.js +2 -1
  218. package/dist/internal/headlessRevisionResolution.js +9 -0
  219. package/dist/internal/pageBreakRunSourceDescendantIndex.js +7 -2
  220. package/dist/internal/paragraphFormattingSerialization.d.ts +20 -9
  221. package/dist/internal/paragraphFormattingSerialization.js +118 -83
  222. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  223. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  224. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  225. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  226. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +17 -12
  227. package/dist/layout-bridge/convert/toFlowBlocks.js +267 -101
  228. package/dist/layout-engine/index.d.ts +2 -2
  229. package/dist/layout-engine/index.js +53 -17
  230. package/dist/layout-engine/measure/measureBlocks.d.ts +3 -0
  231. package/dist/layout-engine/measure/measureBlocks.js +6 -10
  232. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  233. package/dist/layout-engine/measure/tableCellFloating.js +2 -0
  234. package/dist/layout-engine/measure/tableCellGrid.js +2 -1
  235. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  236. package/dist/layout-engine/measure/tableInlinePlacement.js +2 -5
  237. package/dist/layout-engine/paragraphSequence.js +3 -2
  238. package/dist/layout-engine/renderedBreakReconciliation.js +2 -1
  239. package/dist/layout-engine/section-breaks.d.ts +49 -2
  240. package/dist/layout-engine/section-breaks.js +55 -4
  241. package/dist/layout-engine/types.d.ts +69 -17
  242. package/dist/layout-engine/types.js +16 -1
  243. package/dist/layout-painter/borderStroke.d.ts +18 -4
  244. package/dist/layout-painter/renderImage.d.ts +5 -1
  245. package/dist/layout-painter/renderImage.js +10 -3
  246. package/dist/layout-painter/renderPage.d.ts +2 -0
  247. package/dist/layout-painter/renderPage.js +10 -4
  248. package/dist/layout-painter/renderParagraph.d.ts +14 -1
  249. package/dist/layout-painter/renderParagraph.js +34 -28
  250. package/dist/layout-painter/renderTable.d.ts +4 -1
  251. package/dist/layout-painter/renderTable.js +22 -22
  252. package/dist/layout-painter/renderTextBox.js +5 -3
  253. package/dist/managers/autoSaveCodec.d.ts +9 -1
  254. package/dist/managers/autoSaveCodec.js +12 -4
  255. package/dist/markdown/renderBlock.js +3 -0
  256. package/dist/markdown/renderRuns.d.ts +1 -1
  257. package/dist/markdown/renderRuns.js +34 -6
  258. package/dist/markdown/renderTable.js +24 -6
  259. package/dist/markdown/trailers.js +4 -1
  260. package/dist/model.d.ts +3 -3
  261. package/dist/model.js +2 -2
  262. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  263. package/dist/paged-layout/sectionBlockWidths.js +26 -4
  264. package/dist/pdf/images.js +142 -6
  265. package/dist/prosemirror/attrs/index.d.ts +34 -5
  266. package/dist/prosemirror/attrs/index.js +639 -48
  267. package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
  268. package/dist/prosemirror/authoredTransformAttrs.js +64 -0
  269. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +9 -1
  270. package/dist/prosemirror/bookmarkBoundaryAttrs.js +22 -3
  271. package/dist/prosemirror/commands/comments.js +68 -53
  272. package/dist/prosemirror/commands/contentControls.js +33 -20
  273. package/dist/prosemirror/commands/image.d.ts +10 -3
  274. package/dist/prosemirror/commands/image.js +46 -29
  275. package/dist/prosemirror/commands/pageBreak.js +1 -1
  276. package/dist/prosemirror/commands/paragraph.d.ts +2 -1
  277. package/dist/prosemirror/commands/propertyChangeScope.d.ts +12 -4
  278. package/dist/prosemirror/commands/propertyChangeScope.js +22 -4
  279. package/dist/prosemirror/commands/sectionBreak.d.ts +33 -1
  280. package/dist/prosemirror/commands/sectionBreak.js +187 -11
  281. package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
  282. package/dist/prosemirror/commentReferenceAttrs.js +33 -0
  283. package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
  284. package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
  285. package/dist/prosemirror/conversion/fromProseDoc.d.ts +17 -12
  286. package/dist/prosemirror/conversion/fromProseDoc.js +553 -207
  287. package/dist/prosemirror/conversion/index.d.ts +2 -2
  288. package/dist/prosemirror/conversion/preservedSinkCarriers.d.ts +39 -0
  289. package/dist/prosemirror/conversion/preservedSinkCarriers.js +19 -0
  290. package/dist/prosemirror/conversion/runShadingMark.js +14 -1
  291. package/dist/prosemirror/conversion/sdtAttrs.d.ts +10 -1
  292. package/dist/prosemirror/conversion/sdtAttrs.js +19 -5
  293. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
  294. package/dist/prosemirror/conversion/toProseDoc.js +801 -411
  295. package/dist/prosemirror/emptyRangeAnchor.d.ts +53 -0
  296. package/dist/prosemirror/emptyRangeAnchor.js +158 -0
  297. package/dist/prosemirror/extensions/StarterKit.js +14 -58
  298. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  299. package/dist/prosemirror/extensions/core/ParagraphExtension.js +21 -16
  300. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +48 -29
  301. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
  302. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
  303. package/dist/prosemirror/extensions/features/ListExtension.js +43 -24
  304. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
  305. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +4 -3
  306. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +3 -3
  307. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +5 -5
  308. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +3 -1
  309. package/dist/prosemirror/extensions/features/pasteCleanup.js +56 -2
  310. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +5 -4
  311. package/dist/prosemirror/extensions/markRegistry.d.ts +65 -0
  312. package/dist/prosemirror/extensions/markRegistry.js +138 -0
  313. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
  314. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +77 -0
  315. package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +26 -0
  316. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +134 -0
  317. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -1
  318. package/dist/prosemirror/extensions/marks/UnderlineExtension.js +12 -18
  319. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  320. package/dist/prosemirror/extensions/marks/markUtils.js +49 -5
  321. package/dist/prosemirror/extensions/nodes/BlockBookmarkBoundaryExtension.d.ts +29 -0
  322. package/dist/prosemirror/extensions/nodes/BlockBookmarkBoundaryExtension.js +46 -0
  323. package/dist/prosemirror/extensions/nodes/BlockSdtExtension.d.ts +4 -3
  324. package/dist/prosemirror/extensions/nodes/BlockSdtExtension.js +18 -13
  325. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +28 -53
  326. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
  327. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +97 -0
  328. package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
  329. package/dist/prosemirror/extensions/nodes/ImageExtension.js +24 -1
  330. package/dist/prosemirror/extensions/nodes/MoveRangeBoundaryExtension.d.ts +11 -0
  331. package/dist/prosemirror/extensions/nodes/MoveRangeBoundaryExtension.js +43 -0
  332. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
  333. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
  334. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
  335. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
  336. package/dist/prosemirror/extensions/nodes/RangeAnchorExtension.d.ts +13 -0
  337. package/dist/prosemirror/extensions/nodes/RangeAnchorExtension.js +65 -0
  338. package/dist/prosemirror/extensions/nodes/SdtExtension.d.ts +3 -1
  339. package/dist/prosemirror/extensions/nodes/SdtExtension.js +22 -16
  340. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +7 -0
  341. package/dist/prosemirror/extensions/nodes/TableExtension.js +142 -123
  342. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +9 -1
  343. package/dist/prosemirror/extensions/nodes/bookmarkBoundaryDom.d.ts +23 -0
  344. package/dist/prosemirror/extensions/nodes/bookmarkBoundaryDom.js +57 -0
  345. package/dist/prosemirror/extensions/types.d.ts +12 -3
  346. package/dist/prosemirror/index.d.ts +4 -2
  347. package/dist/prosemirror/index.js +2 -1
  348. package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
  349. package/dist/prosemirror/inlineWrapperStack.js +92 -0
  350. package/dist/prosemirror/listMarker.d.ts +2 -2
  351. package/dist/prosemirror/listMarker.js +7 -8
  352. package/dist/prosemirror/listRenderingAttrs.js +11 -1
  353. package/dist/prosemirror/listState.d.ts +62 -0
  354. package/dist/prosemirror/listState.js +49 -0
  355. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +13 -0
  356. package/dist/prosemirror/moveRangeBoundaryAttrs.js +114 -0
  357. package/dist/prosemirror/numberedRefFields.js +4 -4
  358. package/dist/prosemirror/numberingAttr.d.ts +55 -0
  359. package/dist/prosemirror/numberingAttr.js +74 -0
  360. package/dist/prosemirror/outlineLevelAttr.d.ts +29 -0
  361. package/dist/prosemirror/outlineLevelAttr.js +42 -0
  362. package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
  363. package/dist/prosemirror/pageBreakRunProjection.js +19 -9
  364. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +9 -0
  365. package/dist/prosemirror/paragraphFormattingProvenance.js +4 -1
  366. package/dist/prosemirror/plugins/selectionTracker.d.ts +3 -6
  367. package/dist/prosemirror/plugins/selectionTracker.js +3 -10
  368. package/dist/prosemirror/rangeAnchorAttrs.d.ts +14 -0
  369. package/dist/prosemirror/rangeAnchorAttrs.js +101 -0
  370. package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
  371. package/dist/prosemirror/replacedAnnotations.js +183 -0
  372. package/dist/prosemirror/revisionCarriers.d.ts +86 -2
  373. package/dist/prosemirror/revisionCarriers.js +86 -60
  374. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +5 -1
  375. package/dist/prosemirror/runFormattingInlineCarriers.js +7 -1
  376. package/dist/prosemirror/runIdentity.d.ts +28 -0
  377. package/dist/prosemirror/runIdentity.js +36 -0
  378. package/dist/prosemirror/runStyleFormatting.js +1 -1
  379. package/dist/prosemirror/schema/index.d.ts +3 -3
  380. package/dist/prosemirror/schema/marks.d.ts +47 -4
  381. package/dist/prosemirror/schema/nodes.d.ts +309 -26
  382. package/dist/prosemirror/schema/nodes.js +16 -1
  383. package/dist/prosemirror/sectionCarrier.d.ts +37 -0
  384. package/dist/prosemirror/sectionCarrier.js +40 -0
  385. package/dist/prosemirror/selectionState.d.ts +3 -0
  386. package/dist/prosemirror/selectionState.js +4 -1
  387. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +5 -4
  388. package/dist/prosemirror/styles/resolvedStyleAttrs.js +15 -13
  389. package/dist/prosemirror/tableResize.js +5 -6
  390. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +4 -0
  391. package/dist/prosemirror/trackedRunInlineAtoms.js +4 -0
  392. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +10 -11
  393. package/dist/prosemirror/utils/extractTrackedChanges.js +109 -92
  394. package/dist/prosemirror/validation.js +31 -6
  395. package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
  396. package/dist/prosemirror/yjsDocumentMetadata.js +592 -0
  397. package/dist/prosemirror/zeroWidthAnchors.js +6 -0
  398. package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
  399. package/dist/render-dom/commentAnchorAttributes.js +54 -0
  400. package/dist/server.d.ts +3 -1
  401. package/dist/server.js +3 -1
  402. package/dist/style-sets/extract.js +4 -4
  403. package/dist/style-sets/stellaStyle.js +36 -23
  404. package/dist/types/colors.d.ts +2 -2
  405. package/dist/types/content.d.ts +2 -2
  406. package/dist/types/documentEnumValues.d.ts +53 -26
  407. package/dist/types/documentEnumValues.js +72 -174
  408. package/dist/types/formatting.d.ts +2 -2
  409. package/dist/types/index.d.ts +9 -2
  410. package/dist/utils/borderCss.d.ts +246 -0
  411. package/dist/utils/borderCss.js +325 -0
  412. package/dist/utils/clipboard.d.ts +9 -1
  413. package/dist/utils/clipboard.js +27 -1
  414. package/dist/utils/colorResolver.d.ts +4 -3
  415. package/dist/utils/colorResolver.js +45 -76
  416. package/dist/utils/createDocument.js +24 -6
  417. package/dist/utils/findReplace.js +52 -10
  418. package/dist/utils/formatToStyle.d.ts +84 -2
  419. package/dist/utils/formatToStyle.js +133 -52
  420. package/dist/utils/headingCollector.js +3 -2
  421. package/dist/utils/imageLuminance.d.ts +15 -0
  422. package/dist/utils/imageLuminance.js +32 -0
  423. package/dist/utils/mergeDocumentContent.js +12 -20
  424. package/dist/utils/paragraphFormattingMerge.js +27 -6
  425. package/dist/utils/replaceText.js +1 -0
  426. package/dist/utils/tableOperations.d.ts +5 -3
  427. package/dist/utils/tableOperations.js +14 -1
  428. package/dist/utils/tablePlacement.d.ts +7 -0
  429. package/dist/utils/tablePlacement.js +14 -0
  430. package/dist/utils/textDirectionFlow.d.ts +15 -0
  431. package/dist/utils/textDirectionFlow.js +38 -0
  432. package/dist/utils/textFormattingMerge.js +16 -4
  433. package/package.json +10 -10
  434. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +0 -17
  435. package/dist/ai-edits/__fixtures__/paragraphs.js +0 -33
  436. package/dist/compare/__fixtures__/body-sequence.d.ts +0 -119
  437. package/dist/compare/__fixtures__/body-sequence.js +0 -225
  438. package/dist/compare/__fixtures__/nested-table.d.ts +0 -16
  439. package/dist/compare/__fixtures__/nested-table.js +0 -50
  440. package/dist/compare/__fixtures__/numbered-list.d.ts +0 -34
  441. package/dist/compare/__fixtures__/numbered-list.js +0 -85
  442. package/dist/docx/blockRangeMarkers.d.ts +0 -36
  443. package/dist/docx/blockRangeMarkers.js +0 -59
  444. package/dist/docx/bookmarkPlacement.d.ts +0 -9
  445. package/dist/docx/bookmarkPlacement.js +0 -50
  446. package/dist/fonts/embeddedFontTable.d.ts +0 -45
  447. package/dist/fonts/embeddedFontTable.js +0 -80
  448. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +0 -13
  449. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +0 -39
  450. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
  451. package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
@@ -1,3 +1,5 @@
1
+ import { patchBreaksCommentRangeBalance } from "./commentRangeIntegrity.js";
2
+ import { resolveParagraphIdentities } from "./paraIdAttribute.js";
1
3
  import { captureVerbatimXml } from "./verbatimCapture.js";
2
4
  import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findAttributeByNamespaceUri, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
3
5
  //#region src/docx/selectiveXmlPatch.ts
@@ -94,24 +96,43 @@ function extractParagraphXml(serializedXml, paraId) {
94
96
  function buildParagraphOffsetIndex(xml) {
95
97
  const seenCount = /* @__PURE__ */ new Map();
96
98
  const ranges = /* @__PURE__ */ new Map();
97
- const stack = [];
98
- let pos = 0;
99
- const noteOpen = (paraId) => {
100
- if (paraId) seenCount.set(paraId, (seenCount.get(paraId) ?? 0) + 1);
101
- };
102
- const noteRange = (paraId, start, end) => {
103
- if (paraId) ranges.set(paraId, {
99
+ for (const { start, end, paraId } of scanParagraphs(xml)) {
100
+ if (paraId === void 0) continue;
101
+ seenCount.set(paraId, (seenCount.get(paraId) ?? 0) + 1);
102
+ if (end > start) ranges.set(paraId, {
104
103
  start,
105
104
  end
106
105
  });
107
- };
106
+ }
107
+ const index = /* @__PURE__ */ new Map();
108
+ for (const [id, range] of ranges) if (seenCount.get(id) === 1) index.set(id, range);
109
+ return index;
110
+ }
111
+ /**
112
+ * Every `<w:p>` element of `xml`, in the document order of its opening tags,
113
+ * with the `w14:paraId` that tag carries.
114
+ *
115
+ * Document order is what makes the array an ordinal space: the *n*th entry of
116
+ * the source part and the *n*th entry of the model's serialization name the
117
+ * same paragraph, which is the only way to address a paragraph the producer
118
+ * gave no id. A `<w:p>` nested inside another (inside `mc:AlternateContent`,
119
+ * a text box) is one entry of its own, exactly as
120
+ * {@link countParagraphElements} counts it, so the two never disagree about
121
+ * what an ordinal is. An unterminated paragraph keeps `end === start`: it
122
+ * occupies its ordinal but no splice can be built from it.
123
+ */
124
+ function scanParagraphs(xml) {
125
+ const paragraphs = [];
126
+ const open = [];
127
+ let pos = 0;
108
128
  while (pos < xml.length) {
109
129
  const tagStart = xml.indexOf("<", pos);
110
130
  if (tagStart === -1) break;
111
131
  if (xml.startsWith("</w:p>", tagStart)) {
112
- const frame = stack.pop();
132
+ const slot = open.pop();
113
133
  const end = tagStart + 6;
114
- if (frame) noteRange(frame.paraId, frame.start, end);
134
+ const paragraph = slot === void 0 ? void 0 : paragraphs[slot];
135
+ if (paragraph) paragraph.end = end;
115
136
  pos = end;
116
137
  continue;
117
138
  }
@@ -122,18 +143,23 @@ function buildParagraphOffsetIndex(xml) {
122
143
  const tagEnd = xml.indexOf(">", tagStart);
123
144
  if (tagEnd === -1) break;
124
145
  const openTag = xml.slice(tagStart, tagEnd + 1);
125
- const paraId = /\bw14:paraId="(?<id>[^"]*)"/u.exec(openTag)?.groups?.["id"];
126
- noteOpen(paraId);
127
- if (xml[tagEnd - 1] === "/") noteRange(paraId, tagStart, tagEnd + 1);
128
- else stack.push({
146
+ const paraId = /\bw14:paraId="(?<id>[^"]+)"/u.exec(openTag)?.groups?.["id"];
147
+ if (xml[tagEnd - 1] === "/") paragraphs.push({
129
148
  start: tagStart,
149
+ end: tagEnd + 1,
130
150
  paraId
131
151
  });
152
+ else {
153
+ open.push(paragraphs.length);
154
+ paragraphs.push({
155
+ start: tagStart,
156
+ end: tagStart,
157
+ paraId
158
+ });
159
+ }
132
160
  pos = tagEnd + 1;
133
161
  }
134
- const index = /* @__PURE__ */ new Map();
135
- for (const [id, range] of ranges) if (seenCount.get(id) === 1) index.set(id, range);
136
- return index;
162
+ return paragraphs;
137
163
  }
138
164
  /**
139
165
  * Count <w:p> elements in an XML string (top-level paragraph count).
@@ -162,40 +188,140 @@ function collectParaIds(xml) {
162
188
  }
163
189
  return ids;
164
190
  }
191
+ /** Paragraph ids folio writes and this module may have to remove again. */
192
+ const MINTED_PARA_ID_ATTRIBUTE = /\sw14:(?:para|text)Id="[^"]*"/gu;
165
193
  /**
166
- * Validate that a selective patch can be safely applied.
194
+ * The replacement for a paragraph whose source open tag carries no paraId.
167
195
  *
168
- * Checks:
169
- * - All changed paraIds exist in original XML (exactly once)
170
- * - All changed paraIds exist in serialized XML (exactly once)
171
- * - Paragraph count matches between original and serialized (unless disabled)
196
+ * The model always has a key, because folio mints one for every paragraph that
197
+ * arrives without one, and the serializer writes whatever key the model holds.
198
+ * Writing a minted key into the package would upgrade a producer's id-less
199
+ * document to a partly-id'd one on any edit, declare a `w14` namespace the
200
+ * part never had, and turn an id the minter itself calls positional into an
201
+ * authored one the next reader trusts. So the save keeps the author's
202
+ * convention: `ensureParaIds` is the one pass that gives a package ids, and it
203
+ * runs when a host asks for it. An id the source *does* carry (a `w14:textId`
204
+ * beside no paraId) is the author's and stays.
172
205
  */
173
- function validatePatchSafety(originalXml, serializedXml, changedIds, options = {}) {
174
- if (changedIds.size === 0) return { safe: true };
206
+ const withoutMintedIds = (paragraphXml, sourceOpenTag) => {
207
+ const tagEnd = paragraphXml.indexOf(">");
208
+ if (tagEnd === -1) return paragraphXml;
209
+ return paragraphXml.slice(0, tagEnd + 1).replace(MINTED_PARA_ID_ATTRIBUTE, (attribute) => sourceOpenTag.includes(attribute.trim()) ? attribute : "") + paragraphXml.slice(tagEnd + 1);
210
+ };
211
+ /**
212
+ * Route every changed paragraph from the model's serialization to its region
213
+ * of the source part.
214
+ *
215
+ * One owner for the question the patch turns on: *which paragraph of the file
216
+ * is this model paragraph?* {@link resolveParagraphIdentities} answers it per
217
+ * paragraph, and the union is consumed exhaustively here, so a paragraph the
218
+ * source names by id keeps the id lookup — robust to any reordering — and one
219
+ * the source never named falls back to its ordinal, which is sound exactly
220
+ * when the identity plan says the two sequences line up. A package with ids on
221
+ * some paragraphs and not others therefore needs no special case: its authored
222
+ * ids are the witnesses that prove the ordinals for the rest.
223
+ */
224
+ const routeChangedParagraphs = (originalXml, serializedXml, changedIds) => {
225
+ const original = scanParagraphs(originalXml);
226
+ const serialized = scanParagraphs(serializedXml);
227
+ const { identities, ordinalsAligned } = resolveParagraphIdentities({
228
+ sourceParaIds: original.map(({ paraId }) => paraId),
229
+ serializedParaIds: serialized.map(({ paraId }) => paraId)
230
+ });
175
231
  const originalParaIds = collectParaIds(originalXml);
176
232
  const serializedParaIds = collectParaIds(serializedXml);
233
+ const identityByParaId = /* @__PURE__ */ new Map();
234
+ for (const identity of identities) if (identity.type !== "anonymous" && serializedParaIds.get(identity.paraId) === 1) identityByParaId.set(identity.paraId, identity);
235
+ const splices = [];
177
236
  for (const id of changedIds) {
178
- const origCount = originalParaIds.get(id) || 0;
179
- if (origCount === 0) return {
180
- safe: false,
181
- reason: `paraId-not-found-in-original: ${id}`
182
- };
183
- if (origCount > 1) return {
184
- safe: false,
237
+ const originalCount = originalParaIds.get(id) ?? 0;
238
+ if (originalCount > 1) return {
239
+ type: "refused",
185
240
  reason: `duplicate-paraId-in-original: ${id}`
186
241
  };
187
- }
188
- for (const id of changedIds) {
189
- const serCount = serializedParaIds.get(id) || 0;
190
- if (serCount === 0) return {
191
- safe: false,
242
+ const serializedCount = serializedParaIds.get(id) ?? 0;
243
+ if (originalCount === 1 && serializedCount === 0) return {
244
+ type: "refused",
192
245
  reason: `paraId-not-found-in-serialized: ${id}`
193
246
  };
194
- if (serCount > 1) return {
195
- safe: false,
247
+ if (serializedCount === 0) return {
248
+ type: "refused",
249
+ reason: `paraId-not-found-in-original: ${id}`
250
+ };
251
+ if (serializedCount > 1) return {
252
+ type: "refused",
196
253
  reason: `duplicate-paraId-in-serialized: ${id}`
197
254
  };
255
+ const identity = identityByParaId.get(id);
256
+ if (!identity) return {
257
+ type: "refused",
258
+ reason: `paraId-not-found-in-serialized: ${id}`
259
+ };
260
+ const replacement = serialized[identity.ordinal];
261
+ if (!replacement || replacement.end <= replacement.start) return {
262
+ type: "refused",
263
+ reason: `unterminated-paragraph: ${id}`
264
+ };
265
+ const newXml = serializedXml.slice(replacement.start, replacement.end);
266
+ switch (identity.type) {
267
+ case "authored": {
268
+ const offsets = findParagraphOffsets(originalXml, identity.paraId);
269
+ if (!offsets) return {
270
+ type: "refused",
271
+ reason: `paraId-not-found-in-original: ${id}`
272
+ };
273
+ splices.push({
274
+ start: offsets.start,
275
+ end: offsets.end,
276
+ newXml
277
+ });
278
+ break;
279
+ }
280
+ case "minted": {
281
+ if (!ordinalsAligned) return {
282
+ type: "refused",
283
+ reason: `unaligned-paragraph-ordinals: ${id}`
284
+ };
285
+ const source = original[identity.ordinal];
286
+ if (!source || source.end <= source.start) return {
287
+ type: "refused",
288
+ reason: `paraId-not-found-in-original: ${id}`
289
+ };
290
+ splices.push({
291
+ start: source.start,
292
+ end: source.end,
293
+ newXml: withoutMintedIds(newXml, originalXml.slice(source.start, source.end))
294
+ });
295
+ break;
296
+ }
297
+ case "anonymous": return {
298
+ type: "refused",
299
+ reason: `paraId-not-found-in-serialized: ${id}`
300
+ };
301
+ default: return identity;
302
+ }
198
303
  }
304
+ return {
305
+ type: "routed",
306
+ splices
307
+ };
308
+ };
309
+ /**
310
+ * Validate that a selective patch can be safely applied.
311
+ *
312
+ * Checks:
313
+ * - Every changed paraId routes to one region of the original XML, by its
314
+ * authored id or, when the producer wrote none, by its paragraph ordinal
315
+ * - All changed paraIds exist in serialized XML (exactly once)
316
+ * - Paragraph count matches between original and serialized (unless disabled)
317
+ */
318
+ function validatePatchSafety(originalXml, serializedXml, changedIds, options = {}) {
319
+ if (changedIds.size === 0) return { safe: true };
320
+ const routing = routeChangedParagraphs(originalXml, serializedXml, changedIds);
321
+ if (routing.type === "refused") return {
322
+ safe: false,
323
+ reason: routing.reason
324
+ };
199
325
  if (options.checkParagraphCount === false) return { safe: true };
200
326
  const originalCount = countParagraphElements(originalXml);
201
327
  const serializedCount = countParagraphElements(serializedXml);
@@ -237,28 +363,31 @@ function buildPatchedNoteXml(originalXml, serializedXml, changedIds) {
237
363
  return spliceChangedParagraphs(originalXml, serializedXml, changedIds);
238
364
  }
239
365
  /**
366
+ * Apply `splices` to `xml`, end-to-start so earlier offsets stay valid.
367
+ *
368
+ * Every selective patch is a splice, and every splice goes through here, so
369
+ * the refusal below is a property of the operation rather than a check each
370
+ * site has to remember. A patch rewrites the regions an edit touched and keeps
371
+ * the rest of the part byte-for-byte, so it can write half a comment range:
372
+ * invalid OOXML that anchors the comment to nothing. Answers null when it
373
+ * would, leaving the caller to rewrite a wider region — ultimately the whole
374
+ * part from the model, which is balanced with itself.
375
+ */
376
+ const spliceXml = (xml, splices) => {
377
+ let result = xml;
378
+ for (const { start, end, newXml } of [...splices].toSorted((a, b) => b.start - a.start)) result = result.slice(0, start) + newXml + result.slice(end);
379
+ return patchBreaksCommentRangeBalance(xml, result) ? null : result;
380
+ };
381
+ /**
240
382
  * Replace each changed paragraph in `originalXml` with its re-serialized form
241
- * extracted from `serializedXml`, splicing end-to-start so earlier offsets stay
242
- * valid. Assumes safety has already been validated. Returns null if an offset
243
- * or extraction unexpectedly fails.
383
+ * extracted from `serializedXml`. Assumes safety has already been validated.
384
+ * Returns null if an offset or extraction unexpectedly fails, or if
385
+ * {@link spliceXml} refuses the result; the caller then falls back to a full
386
+ * repack, whose parts are all re-serialized from one model.
244
387
  */
245
388
  function spliceChangedParagraphs(originalXml, serializedXml, changedIds) {
246
- const replacements = [];
247
- for (const paraId of changedIds) {
248
- const origOffsets = findParagraphOffsets(originalXml, paraId);
249
- if (!origOffsets) return null;
250
- const newXml = extractParagraphXml(serializedXml, paraId);
251
- if (!newXml) return null;
252
- replacements.push({
253
- start: origOffsets.start,
254
- end: origOffsets.end,
255
- newXml
256
- });
257
- }
258
- replacements.sort((a, b) => b.start - a.start);
259
- let result = originalXml;
260
- for (const { start, end, newXml } of replacements) result = result.slice(0, start) + newXml + result.slice(end);
261
- return result;
389
+ const routing = routeChangedParagraphs(originalXml, serializedXml, changedIds);
390
+ return routing.type === "refused" ? null : spliceXml(originalXml, routing.splices);
262
391
  }
263
392
  /**
264
393
  * Depth-count the end of the element that opens at `start` (an `<openLiteral…>`
@@ -424,11 +553,6 @@ const collectChangedNoteParaIds = (baselineXml, currentXml) => {
424
553
  }
425
554
  return changed;
426
555
  };
427
- const replaceRanges = (xml, replacements) => {
428
- let result = xml;
429
- for (const { start, end, newXml } of [...replacements].toSorted((a, b) => b.start - a.start)) result = result.slice(0, start) + newXml + result.slice(end);
430
- return result;
431
- };
432
556
  /**
433
557
  * Patch an existing note part from its model serialization.
434
558
  *
@@ -440,56 +564,79 @@ const replaceRanges = (xml, replacements) => {
440
564
  * notes, and unaffected equal-shape paragraphs remain byte-exact.
441
565
  * `replacementXml` also supplies synthesized automatic note-reference marks,
442
566
  * which the parsed model intentionally omits.
567
+ *
568
+ * A comment can be anchored on a note's own text, so its range spans that
569
+ * note's paragraphs and an edit inside the span moves a range half from one
570
+ * paragraph to another. Replacing only the dirty paragraph then drops the half
571
+ * it held and leaves the other standing, so {@link spliceXml} refuses the
572
+ * result and the changed notes are rewritten whole instead — model content on
573
+ * both sides of the range, with every other note still byte-exact.
443
574
  */
444
575
  function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }) {
445
576
  const currentElements = collectNoteElementSyntax(serializedXml, elementName);
446
577
  const originalElements = collectNoteElementSyntax(originalXml, elementName);
447
578
  const replacementElements = collectNoteElementSyntax(replacementXml, elementName);
448
579
  const replacementXmlnsDeclarations = collectXmlnsFromOpeningTag(replacementXml);
449
- const ordinalReplacements = [];
580
+ const paragraphSplices = [];
581
+ /** The same edits at note granularity, should the paragraph splices be refused. */
582
+ const noteSplices = [];
450
583
  const serializedParaIds = collectParaIds(serializedXml);
451
584
  const effectiveChangedParaIds = changedParaIds ?? collectChangedNoteParaIds(baselineXml, serializedXml);
452
585
  const unroutedChangedParaIds = new Set([...effectiveChangedParaIds].filter((paraId) => serializedParaIds.has(paraId)));
453
586
  for (const [id, currentSyntaxEntries] of currentElements) {
454
587
  const originalSyntaxEntries = originalElements.get(id);
455
588
  const replacementSyntaxEntries = replacementElements.get(id);
456
- if (currentSyntaxEntries.length !== 1 || originalSyntaxEntries?.length !== 1 || replacementSyntaxEntries?.length !== 1) return null;
589
+ if (currentSyntaxEntries.length !== 1 || originalSyntaxEntries?.length !== 1 || replacementSyntaxEntries?.length !== 1) return {
590
+ type: "refused",
591
+ reason: "unroutable-paragraph"
592
+ };
457
593
  const currentSyntax = currentSyntaxEntries[0];
458
594
  const originalSyntax = originalSyntaxEntries[0];
459
595
  const replacementSyntax = replacementSyntaxEntries[0];
460
- if (!currentSyntax || !originalSyntax || !replacementSyntax) return null;
596
+ if (!currentSyntax || !originalSyntax || !replacementSyntax) return {
597
+ type: "refused",
598
+ reason: "unroutable-paragraph"
599
+ };
461
600
  const currentNote = extractNoteElement(serializedXml, currentSyntax, id);
462
601
  const originalOffsets = findNoteElement(originalXml, originalSyntax, id);
463
602
  const replacementNote = extractNoteElement(replacementXml, replacementSyntax, id);
464
- if (!currentNote || !originalOffsets || !replacementNote) return null;
603
+ if (!currentNote || !originalOffsets || !replacementNote) return {
604
+ type: "refused",
605
+ reason: "unroutable-paragraph"
606
+ };
465
607
  const originalNote = originalXml.slice(originalOffsets.start, originalOffsets.end);
466
608
  const currentParagraphs = paragraphRanges(currentNote, currentSyntax.elementPrefix);
467
609
  const originalParagraphs = paragraphRanges(originalNote, originalSyntax.elementPrefix);
468
610
  const replacementParagraphs = paragraphRanges(replacementNote, replacementSyntax.elementPrefix);
469
611
  const noteChangedParaIds = [...collectParaIds(currentNote).keys()].filter((paraId) => unroutedChangedParaIds.has(paraId));
470
612
  if (noteChangedParaIds.length === 0) continue;
613
+ const wholeNoteSplice = {
614
+ start: originalOffsets.start,
615
+ end: originalOffsets.end,
616
+ newXml: rewriteWordprocessingPrefixes(replacementNote, {
617
+ source: replacementSyntax,
618
+ target: originalSyntax,
619
+ sourceXmlnsDeclarations: replacementXmlnsDeclarations
620
+ })
621
+ };
622
+ noteSplices.push(wholeNoteSplice);
471
623
  if (currentParagraphs.length !== originalParagraphs.length || currentParagraphs.length !== replacementParagraphs.length) {
472
624
  for (const paraId of noteChangedParaIds) unroutedChangedParaIds.delete(paraId);
473
- ordinalReplacements.push({
474
- start: originalOffsets.start,
475
- end: originalOffsets.end,
476
- newXml: rewriteWordprocessingPrefixes(replacementNote, {
477
- source: replacementSyntax,
478
- target: originalSyntax,
479
- sourceXmlnsDeclarations: replacementXmlnsDeclarations
480
- })
481
- });
625
+ paragraphSplices.push(wholeNoteSplice);
482
626
  continue;
483
627
  }
484
628
  for (let index = 0; index < currentParagraphs.length; index++) {
485
629
  const currentRange = currentParagraphs[index];
486
630
  const originalRange = originalParagraphs[index];
487
631
  const replacementRange = replacementParagraphs[index];
488
- if (!currentRange || !originalRange || !replacementRange) return null;
632
+ if (!currentRange || !originalRange || !replacementRange) return {
633
+ type: "refused",
634
+ reason: "unroutable-paragraph"
635
+ };
489
636
  const routedIds = [...collectParaIds(currentNote.slice(currentRange.start, currentRange.end)).keys()].filter((paraId) => unroutedChangedParaIds.has(paraId));
490
637
  if (routedIds.length === 0) continue;
491
638
  for (const paraId of routedIds) unroutedChangedParaIds.delete(paraId);
492
- ordinalReplacements.push({
639
+ paragraphSplices.push({
493
640
  start: originalOffsets.start + originalRange.start,
494
641
  end: originalOffsets.start + originalRange.end,
495
642
  newXml: rewriteWordprocessingPrefixes(replacementNote.slice(replacementRange.start, replacementRange.end), {
@@ -500,8 +647,23 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
500
647
  });
501
648
  }
502
649
  }
503
- if (unroutedChangedParaIds.size > 0) return null;
504
- return replaceRanges(originalXml, ordinalReplacements);
650
+ if (unroutedChangedParaIds.size > 0) return {
651
+ type: "refused",
652
+ reason: "unroutable-paragraph"
653
+ };
654
+ const patched = spliceXml(originalXml, paragraphSplices);
655
+ if (patched !== null) return {
656
+ type: "patched",
657
+ xml: patched
658
+ };
659
+ const wholeNotes = spliceXml(originalXml, noteSplices);
660
+ return wholeNotes === null ? {
661
+ type: "refused",
662
+ reason: "comment-range-balance"
663
+ } : {
664
+ type: "patched",
665
+ xml: wholeNotes
666
+ };
505
667
  }
506
668
  /**
507
669
  * The full range of the first `<openLiteral …>…</closeTag>` element, or null.
@@ -568,7 +730,7 @@ function buildNumberingElementOffsetIndex(xml, kind) {
568
730
  }
569
731
  return index;
570
732
  }
571
- const SYNTHETIC_NUM_FMT_PATTERN = /<w:numFmt w:val="decimalZero(?<width>[345])"\/>/u;
733
+ const CUSTOM_NUM_FMT_PATTERN = /<w:numFmt w:val="custom"(?: w:format="(?<format>[^"]*)")?\/>/u;
572
734
  /**
573
735
  * The numbering definitions whose current serialization differs from the
574
736
  * baseline (re-parsed original) serialization — the ones actually edited. An id
@@ -605,30 +767,15 @@ function extractLevelNumFmtElement(levelXml) {
605
767
  return match ? match[0] : null;
606
768
  }
607
769
  /**
608
- * A valid OOXML custom number format equivalent to a `decimalZero{width}`
609
- * synthetic value: a zero-padded first token folio's parser reads back to the
610
- * same width. Used only when the original element cannot be resolved, so a
611
- * changed definition never emits a non-OOXML synthetic value.
770
+ * The `@w:format` an original level's number-format element declares, or null
771
+ * when it declares no custom format at all. Telling a preserved format from an
772
+ * edited one is what keeps a genuine change from being reverted.
612
773
  */
613
- function reconstructCustomNumFmt(width) {
614
- return `<w:numFmt w:val="custom" w:format="${`${"0".repeat(Number.parseInt(width, 10) - 1)}1`}"/>`;
615
- }
616
- /**
617
- * The synthetic `decimalZero{width}` width the parser would derive from an
618
- * original level's number-format element (its custom `w:format` first token),
619
- * as the same digit-count string the SYNTHETIC_NUM_FMT_PATTERN captures. Mirrors
620
- * `parseCustomNumberFormat` (first comma-separated token, `^0+1$`, clamped to 5).
621
- * Returns null when the element carries no zero-padded custom format, so a
622
- * genuine format change is never mistaken for the original.
623
- */
624
- function originalNumFmtWidth(numFmtElement) {
774
+ function originalCustomNumFmtFormat(numFmtElement) {
625
775
  for (const match of numFmtElement.matchAll(/<w:numFmt\b[^>]*\/>/gu)) {
626
776
  const tag = match[0];
627
777
  if (!/\bw:val="custom"/u.test(tag)) continue;
628
- const format = /\bw:format="(?<format>[^"]*)"/u.exec(tag)?.groups?.["format"];
629
- if (format === void 0) continue;
630
- const firstToken = format.split(",")[0]?.trim() ?? "";
631
- return /^0+1$/u.test(firstToken) ? String(Math.min(firstToken.length, 5)) : null;
778
+ return /\bw:format="(?<format>[^"]*)"/u.exec(tag)?.groups?.["format"] ?? null;
632
779
  }
633
780
  return null;
634
781
  }
@@ -661,21 +808,19 @@ function withXmlnsDeclarations(fragmentXml, xmlnsDecls) {
661
808
  return fragmentXml.slice(0, nameEnd) + additions.join("") + fragmentXml.slice(nameEnd);
662
809
  }
663
810
  /**
664
- * Swap each synthetic `decimalZero{3,4,5}` numFmt in a re-serialized definition
665
- * back to the original level's number-format element.
811
+ * Swap each re-serialized custom numFmt back to the original level's
812
+ * number-format element.
666
813
  *
667
- * folio's parser collapses a Word custom / mc:AlternateContent number format
668
- * into a synthetic `decimalZero{3,4,5}` value the model cannot re-emit as valid
669
- * OOXML. When an unrelated field of the definition is edited, the whole
670
- * definition is re-serialized, so without this pass the level's custom format
671
- * would be replaced by an invalid value and lost on reparse. For each level
672
- * whose re-emitted numFmt is synthetic, restore the original level's numFmt
673
- * element by ilvl ONLY when the model's synthetic value still matches the
674
- * original's (the format was not edited, just preserved past an unrelated
675
- * change). When the model deliberately changed the width (e.g. decimalZero4 ->
676
- * decimalZero5), or the original cannot be resolved, emit a valid OOXML custom
677
- * format for the CURRENT width instead — honoring the edit and never leaving a
678
- * synthetic value.
814
+ * The model holds a custom format as `custom` plus its `@w:format`, which the
815
+ * serializer re-emits as a bare `<w:numFmt w:val="custom" w:format="…"/>`. That
816
+ * is valid OOXML and reparses to the same model, but it is not what the source
817
+ * wrote: Word wraps a custom format in an `mc:AlternateContent` whose Fallback
818
+ * carries a plain format for pre-w14 readers, and the model has no field for
819
+ * the Fallback. When an unrelated field of the definition is edited the whole
820
+ * definition is re-serialized, so without this pass that wrapper would be
821
+ * flattened away. Restore it by ilvl only when the model's format still matches
822
+ * the original's; a level whose format was actually edited keeps the model's
823
+ * own element, which is the edit.
679
824
  */
680
825
  function restoreLevelNumFmts(originalDefXml, currentDefXml) {
681
826
  const replacements = [];
@@ -684,28 +829,29 @@ function restoreLevelNumFmts(originalDefXml, currentDefXml) {
684
829
  const curOffsets = findElementByIdAttr(currentDefXml, LEVEL_OPEN_LITERAL, LEVEL_CLOSE_TAG, LEVEL_ID_ATTR, ilvl);
685
830
  if (!curOffsets) continue;
686
831
  const curLevel = currentDefXml.slice(curOffsets.start, curOffsets.end);
687
- const synthetic = SYNTHETIC_NUM_FMT_PATTERN.exec(curLevel);
688
- if (!synthetic) continue;
832
+ const custom = CUSTOM_NUM_FMT_PATTERN.exec(curLevel);
833
+ if (!custom) continue;
689
834
  const origLevel = extractElementByIdAttr(originalDefXml, LEVEL_OPEN_LITERAL, LEVEL_CLOSE_TAG, LEVEL_ID_ATTR, ilvl);
690
- const currentWidth = synthetic.groups["width"];
835
+ const currentFormat = custom.groups?.["format"] ?? null;
691
836
  const original = origLevel ? extractLevelNumFmtElement(origLevel) : null;
692
- let replacement;
693
- if (original && origLevel && originalNumFmtWidth(original) === currentWidth) replacement = withXmlnsDeclarations(original, {
837
+ if (!original || !origLevel || originalCustomNumFmtFormat(original) !== currentFormat) continue;
838
+ const ancestorXmlns = {
694
839
  ...collectXmlnsFromOpeningTag(originalDefXml),
695
840
  ...collectXmlnsFromOpeningTag(origLevel)
696
- });
697
- else replacement = reconstructCustomNumFmt(currentWidth);
698
- const restoredLevel = curLevel.slice(0, synthetic.index) + replacement + curLevel.slice(synthetic.index + synthetic[0].length);
841
+ };
842
+ const restoredLevel = spliceXml(curLevel, [{
843
+ start: custom.index,
844
+ end: custom.index + custom[0].length,
845
+ newXml: withXmlnsDeclarations(original, ancestorXmlns)
846
+ }]);
847
+ if (restoredLevel === null) continue;
699
848
  replacements.push({
700
849
  start: curOffsets.start,
701
850
  end: curOffsets.end,
702
851
  newXml: restoredLevel
703
852
  });
704
853
  }
705
- replacements.sort((a, b) => b.start - a.start);
706
- let result = currentDefXml;
707
- for (const { start, end, newXml } of replacements) result = result.slice(0, start) + newXml + result.slice(end);
708
- return result;
854
+ return spliceXml(currentDefXml, replacements) ?? currentDefXml;
709
855
  }
710
856
  /**
711
857
  * Build a patched `word/numbering.xml` by splicing the changed `w:abstractNum` /
@@ -733,10 +879,7 @@ function buildPatchedNumberingXml(originalXml, currentXml, changed) {
733
879
  return true;
734
880
  };
735
881
  if (!collect("abstractNum", changed.abstractNums) || !collect("num", changed.nums)) return null;
736
- replacements.sort((a, b) => b.start - a.start);
737
- let result = originalXml;
738
- for (const { start, end, newXml } of replacements) result = result.slice(0, start) + newXml + result.slice(end);
739
- return result;
882
+ return spliceXml(originalXml, replacements);
740
883
  }
741
884
  /**
742
885
  * Numbering definitions present in `currentXml` (the model's serialization)
@@ -851,12 +994,12 @@ function appendNumberingDefs(xml, currentXml, added) {
851
994
  return head + abstractXmls.join("") + middle + numXmls.join("") + tail;
852
995
  }
853
996
  /**
854
- * For an added abstract definition that still carries a synthetic numFmt, find
997
+ * For an added abstract definition that carries a flattened custom numFmt, find
855
998
  * the original abstract it was cloned from (same serialized body apart from the
856
999
  * id) and restore the level formats from it.
857
1000
  */
858
1001
  function restoreClonedLevelNumFmts(originalXml, currentXml, addedDefXml, addedId) {
859
- if (!SYNTHETIC_NUM_FMT_PATTERN.test(addedDefXml)) return addedDefXml;
1002
+ if (!CUSTOM_NUM_FMT_PATTERN.test(addedDefXml)) return addedDefXml;
860
1003
  const addedBody = stripAbstractNumId(addedDefXml, addedId);
861
1004
  const originalIds = collectElementIds(originalXml, NUMBERING_OPEN_LITERAL.abstractNum, NUMBERING_ID_ATTR.abstractNum);
862
1005
  for (const [id] of originalIds) {
@@ -872,4 +1015,4 @@ function escapeRegExp(str) {
872
1015
  return str.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
873
1016
  }
874
1017
  //#endregion
875
- export { appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, validatePatchSafety };
1018
+ export { appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, scanParagraphs, spliceXml, validatePatchSafety };