@stll/folio-core 0.35.1 → 0.37.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 (236) hide show
  1. package/README.md +59 -1
  2. package/dist/ai-edits/apply.js +878 -377
  3. package/dist/ai-edits/block-identity.d.ts +4 -1
  4. package/dist/ai-edits/block-identity.js +4 -1
  5. package/dist/ai-edits/blockRange.js +14 -14
  6. package/dist/ai-edits/clean-text.d.ts +40 -1
  7. package/dist/ai-edits/clean-text.js +52 -2
  8. package/dist/ai-edits/headless.d.ts +3 -1
  9. package/dist/ai-edits/headless.js +141 -73
  10. package/dist/ai-edits/index.d.ts +3 -3
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.js +38 -20
  13. package/dist/ai-edits/snapshot.d.ts +14 -6
  14. package/dist/ai-edits/snapshot.js +105 -20
  15. package/dist/ai-edits/table-cell-mutations.js +1 -1
  16. package/dist/ai-edits/table-row-column-mutations.js +7 -0
  17. package/dist/ai-edits/table-template.d.ts +11 -1
  18. package/dist/ai-edits/table-template.js +49 -17
  19. package/dist/ai-edits/types.d.ts +62 -61
  20. package/dist/ai-edits/word-diff.d.ts +18 -3
  21. package/dist/ai-edits/word-diff.js +556 -67
  22. package/dist/ai-suggestions/conflict.js +1 -4
  23. package/dist/ai-suggestions/text-positions.d.ts +8 -0
  24. package/dist/ai-suggestions/text-positions.js +27 -5
  25. package/dist/compare/__fixtures__/body-sequence.js +3 -4
  26. package/dist/compare/column-alignment.d.ts +13 -13
  27. package/dist/compare/column-alignment.js +78 -39
  28. package/dist/compare/compare.d.ts +4 -3
  29. package/dist/compare/compare.js +48 -21
  30. package/dist/compare/content-alignment.d.ts +95 -0
  31. package/dist/compare/content-alignment.js +1450 -0
  32. package/dist/compare/content-types.d.ts +100 -0
  33. package/dist/compare/content-types.js +0 -0
  34. package/dist/compare/content.d.ts +209 -0
  35. package/dist/compare/content.js +885 -0
  36. package/dist/compare/formatting.d.ts +5 -5
  37. package/dist/compare/formatting.js +6 -4
  38. package/dist/compare/plan.d.ts +6 -1
  39. package/dist/compare/plan.js +171 -566
  40. package/dist/compare/scenario.d.ts +2 -2
  41. package/dist/compare/types.d.ts +3 -3
  42. package/dist/compare/verification.d.ts +8 -9
  43. package/dist/compare/verification.js +72 -50
  44. package/dist/compat/eigenpal.d.ts +5 -3
  45. package/dist/compat/eigenpal.js +3 -2
  46. package/dist/controller/headerFooterEditorManager.js +18 -10
  47. package/dist/controller/hiddenEditorApi.js +1 -1
  48. package/dist/controller/hiddenEditorManager.d.ts +7 -2
  49. package/dist/controller/hiddenEditorManager.js +15 -6
  50. package/dist/controller/layoutPipeline.js +5 -3
  51. package/dist/controller/noteEditorManager.js +12 -7
  52. package/dist/display-list/build/headerFooterPrimitives.js +1 -0
  53. package/dist/display-list/build/storyPrimitives.d.ts +5 -2
  54. package/dist/display-list/build/storyPrimitives.js +60 -3
  55. package/dist/display-list/build/tablePrimitives.js +30 -0
  56. package/dist/display-list/primitives.d.ts +1 -1
  57. package/dist/document-operations.d.ts +8 -4
  58. package/dist/document-operations.js +104 -12
  59. package/dist/docx/blockContentParser.js +6 -27
  60. package/dist/docx/commentParser.js +2 -4
  61. package/dist/docx/commentRangeIntegrity.js +2 -4
  62. package/dist/docx/documentClone.d.ts +2 -0
  63. package/dist/docx/documentClone.js +2 -0
  64. package/dist/docx/ensureParaIds.js +9 -4
  65. package/dist/docx/headerFooterParser.js +2 -4
  66. package/dist/docx/normalizeBaseDirection.js +5 -7
  67. package/dist/docx/numberingParser.d.ts +3 -1
  68. package/dist/docx/numberingParser.js +4 -1
  69. package/dist/docx/paragraphParser.js +34 -35
  70. package/dist/docx/paragraphPropertySource.d.ts +49 -0
  71. package/dist/docx/paragraphPropertySource.js +113 -0
  72. package/dist/docx/paragraphTraversal.js +30 -34
  73. package/dist/docx/rezip.d.ts +0 -8
  74. package/dist/docx/rezip.js +41 -16
  75. package/dist/docx/runConsolidator.js +2 -4
  76. package/dist/docx/runParser.js +6 -17
  77. package/dist/docx/sectionParser.js +1 -15
  78. package/dist/docx/serializer/paragraphSerializer.js +289 -30
  79. package/dist/docx/serializer/runSerializer.js +6 -12
  80. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
  81. package/dist/docx/serializer/tableSerializer.js +4 -23
  82. package/dist/docx/serializer/trackedChangeAttributes.d.ts +11 -0
  83. package/dist/docx/serializer/trackedChangeAttributes.js +31 -0
  84. package/dist/docx/server/build.d.ts +1 -1
  85. package/dist/docx/server/createBilingualDocument.js +8 -10
  86. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
  87. package/dist/docx/server/validateDocxConformance.js +17 -13
  88. package/dist/docx/tableParser.js +1 -37
  89. package/dist/docx/trackedChangeInfo.d.ts +11 -0
  90. package/dist/docx/trackedChangeInfo.js +38 -0
  91. package/dist/docx/verbatimCapture.d.ts +20 -9
  92. package/dist/docx/verbatimCapture.js +208 -8
  93. package/dist/docx/xmlParser.d.ts +3 -1
  94. package/dist/docx/xmlParser.js +6 -1
  95. package/dist/docx/xmlSafety.d.ts +1 -1
  96. package/dist/docx/xmlSafety.js +157 -0
  97. package/dist/headless-layout.js +5 -4
  98. package/dist/index.d.ts +5 -3
  99. package/dist/index.js +3 -2
  100. package/dist/internal/headlessRevisionResolution.d.ts +23 -0
  101. package/dist/internal/headlessRevisionResolution.js +155 -0
  102. package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
  103. package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
  104. package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
  105. package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
  106. package/dist/internal/pageBreakRunPartition.d.ts +26 -0
  107. package/dist/internal/pageBreakRunPartition.js +35 -0
  108. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
  109. package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
  110. package/dist/internal/sectionEndpointResolution.d.ts +32 -0
  111. package/dist/internal/sectionEndpointResolution.js +38 -0
  112. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  113. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
  114. package/dist/layout-bridge/convert/headerFooterLayout.js +52 -9
  115. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
  116. package/dist/layout-bridge/convert/toFlowBlocks.js +391 -82
  117. package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
  118. package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
  119. package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
  120. package/dist/layout-bridge/dom/textStreamDom.js +75 -0
  121. package/dist/layout-bridge/headerFooterLayout.js +16 -30
  122. package/dist/layout-engine/index.js +12 -3
  123. package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
  124. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
  125. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  126. package/dist/layout-engine/types.d.ts +7 -0
  127. package/dist/layout-painter/imageLayout.d.ts +1 -1
  128. package/dist/layout-painter/renderTable.js +20 -0
  129. package/dist/markdown/renderBlock.js +7 -10
  130. package/dist/model.d.ts +1 -1
  131. package/dist/paged-layout/rangeProjection.js +6 -9
  132. package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
  133. package/dist/paged-layout/transactionDirtyRange.js +23 -1
  134. package/dist/prosemirror/attrs/index.d.ts +7 -3
  135. package/dist/prosemirror/attrs/index.js +128 -7
  136. package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
  137. package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
  138. package/dist/prosemirror/commands/comments.d.ts +10 -1
  139. package/dist/prosemirror/commands/comments.js +551 -87
  140. package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
  141. package/dist/prosemirror/commands/formatPainter.js +319 -63
  142. package/dist/prosemirror/commands/index.d.ts +2 -2
  143. package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
  144. package/dist/prosemirror/commands/pageBreak.js +18 -36
  145. package/dist/prosemirror/commands/propertyChangeScope.d.ts +27 -3
  146. package/dist/prosemirror/commands/propertyChangeScope.js +85 -14
  147. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -5
  148. package/dist/prosemirror/conversion/fromProseDoc.js +935 -223
  149. package/dist/prosemirror/conversion/runShadingMark.js +2 -2
  150. package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
  151. package/dist/prosemirror/conversion/toProseDoc.js +439 -291
  152. package/dist/prosemirror/extensions/StarterKit.js +4 -0
  153. package/dist/prosemirror/extensions/core/ParagraphExtension.js +54 -6
  154. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
  155. package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
  156. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +19 -6
  157. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
  158. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
  159. package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
  160. package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
  161. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
  162. package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
  163. package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
  164. package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
  165. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
  166. package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
  167. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
  168. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
  169. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  170. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
  171. package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
  172. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
  173. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  174. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
  175. package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
  176. package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
  177. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
  178. package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
  179. package/dist/prosemirror/findReplaceSelection.js +8 -3
  180. package/dist/prosemirror/index.d.ts +3 -3
  181. package/dist/prosemirror/listMarker.d.ts +26 -1
  182. package/dist/prosemirror/listMarker.js +108 -14
  183. package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
  184. package/dist/prosemirror/pageBreakRunProjection.js +47 -0
  185. package/dist/prosemirror/paraText.js +6 -0
  186. package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
  187. package/dist/prosemirror/paragraphAlignment.js +13 -0
  188. package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
  189. package/dist/prosemirror/paragraphSpacing.js +125 -0
  190. package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
  191. package/dist/prosemirror/plugins/documentNumbering.js +18 -0
  192. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
  193. package/dist/prosemirror/plugins/pmTextScan.js +14 -4
  194. package/dist/prosemirror/plugins/suggestionMode.js +9 -11
  195. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
  196. package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
  197. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
  198. package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
  199. package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
  200. package/dist/prosemirror/runFormattingProvenance.js +48 -0
  201. package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
  202. package/dist/prosemirror/runFormattingReconciliation.js +54 -0
  203. package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
  204. package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
  205. package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
  206. package/dist/prosemirror/runStyleFormatting.js +105 -0
  207. package/dist/prosemirror/schema/index.d.ts +3 -3
  208. package/dist/prosemirror/schema/marks.d.ts +84 -9
  209. package/dist/prosemirror/schema/marks.js +73 -1
  210. package/dist/prosemirror/schema/nodes.d.ts +30 -6
  211. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
  212. package/dist/prosemirror/styles/resolvedStyleAttrs.js +45 -23
  213. package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
  214. package/dist/prosemirror/styles/styleResolver.js +3 -3
  215. package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
  216. package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
  217. package/dist/prosemirror/tableGridMutation.d.ts +20 -0
  218. package/dist/prosemirror/tableGridMutation.js +30 -0
  219. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
  220. package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
  221. package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
  222. package/dist/prosemirror/validation.js +23 -4
  223. package/dist/redline.js +4 -1
  224. package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
  225. package/dist/render-dom/RenderedDomContext.js +17 -34
  226. package/dist/server.d.ts +4 -4
  227. package/dist/server.js +2 -2
  228. package/dist/style-engine/styleEngine.d.ts +1 -1
  229. package/dist/utils/clipboard.js +1 -1
  230. package/dist/utils/findReplace.js +38 -21
  231. package/dist/utils/mergeDocumentContent.js +3 -3
  232. package/dist/utils/replaceText.js +2 -1
  233. package/dist/version-comparison.d.ts +38 -44
  234. package/dist/version-comparison.js +270 -399
  235. package/dist/watermark/index.js +2 -4
  236. package/package.json +8 -2
@@ -0,0 +1,2 @@
1
+ import { cloneDocumentWithParagraphPropertySources } from "./paragraphPropertySource.js";
2
+ export { cloneDocumentWithParagraphPropertySources };
@@ -392,10 +392,15 @@ const ensureParaIdsInternal = async (docx, options) => {
392
392
  };
393
393
  const zip = await JSZip.loadAsync(docx);
394
394
  if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw createEnsureParaIdsError("Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable.");
395
- for (const [partPath, content] of updates) zip.file(partPath, content, {
396
- compression: "DEFLATE",
397
- compressionOptions: { level: 6 }
398
- });
395
+ for (const [partPath, content] of updates) {
396
+ const sourceEntry = zip.file(partPath);
397
+ if (sourceEntry === null) throw createEnsureParaIdsError(`Package part disappeared during normalization: ${partPath}`);
398
+ zip.file(partPath, content, {
399
+ compression: "DEFLATE",
400
+ compressionOptions: { level: 6 },
401
+ date: sourceEntry.date
402
+ });
403
+ }
399
404
  return {
400
405
  docx: await zip.generateAsync({
401
406
  type: "uint8array",
@@ -1,6 +1,7 @@
1
1
  import { parseBlockContent } from "./blockContentParser.js";
2
2
  import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
3
3
  import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
4
+ import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
4
5
  import { parseWatermark } from "./watermarkParser.js";
5
6
  import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
6
7
  //#region src/docx/headerFooterParser.ts
@@ -37,10 +38,7 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
37
38
  });
38
39
  if (watermarkResult) {
39
40
  const host = result.content.at(watermarkResult.blockIndex);
40
- if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = {
41
- ...host,
42
- content: []
43
- };
41
+ if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = cloneParagraphWithPropertySource(host, { content: [] });
44
42
  }
45
43
  assignHeaderFooterVerbatimXml(result, headerXml);
46
44
  return result;
@@ -1,5 +1,6 @@
1
1
  import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
2
2
  import { detectBaseDirection } from "../utils/baseDirection.js";
3
+ import { cloneParagraphWithoutPropertySource } from "./paragraphPropertySource.js";
3
4
  //#region src/docx/normalizeBaseDirection.ts
4
5
  const runText = (run) => {
5
6
  let text = "";
@@ -32,13 +33,10 @@ const paragraphText = (paragraph) => paragraphContentText(paragraph.content);
32
33
  const normalizeParagraph = (paragraph, styles) => {
33
34
  if ((paragraph.formatting?.bidi ?? styles.resolveParagraphStyle(paragraph.formatting?.styleId).paragraphFormatting?.bidi) != null) return paragraph;
34
35
  if (detectBaseDirection(paragraphText(paragraph)) !== "rtl") return paragraph;
35
- return {
36
- ...paragraph,
37
- formatting: {
38
- ...paragraph.formatting,
39
- bidi: true
40
- }
41
- };
36
+ return cloneParagraphWithoutPropertySource(paragraph, { formatting: {
37
+ ...paragraph.formatting,
38
+ bidi: true
39
+ } });
42
40
  };
43
41
  const normalizeTable = (table, styles) => ({
44
42
  ...table,
@@ -80,5 +80,7 @@ declare function getBulletCharacter(level: document_d_exports.ListLevel): string
80
80
  * Check if a list level is a bullet (not numbered)
81
81
  */
82
82
  declare function isBulletLevel(level: document_d_exports.ListLevel): boolean;
83
+ /** Whether a numbering level reserves horizontal space for a visible marker. */
84
+ declare const numberingLevelHasMarkerSlot: (level: Pick<document_d_exports.ListLevel, "numFmt">) => boolean;
83
85
  //#endregion
84
- export { NumberingMap, computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
86
+ export { NumberingMap, computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, numberingLevelHasMarkerSlot, padDecimal, parseNumbering, renderListMarker };
@@ -542,6 +542,7 @@ function computeListRendering(numPr, numbering) {
542
542
  level: ilvl,
543
543
  numId,
544
544
  marker: level.lvlText,
545
+ markerTemplate: level.lvlText,
545
546
  isBullet: level.numFmt === "bullet",
546
547
  numFmt: level.isLgl ? "decimal" : level.numFmt,
547
548
  levelNumFmts,
@@ -609,5 +610,7 @@ function getBulletCharacter(level) {
609
610
  function isBulletLevel(level) {
610
611
  return level.numFmt === "bullet" || level.numFmt === "none";
611
612
  }
613
+ /** Whether a numbering level reserves horizontal space for a visible marker. */
614
+ const numberingLevelHasMarkerSlot = (level) => level.numFmt !== "none";
612
615
  //#endregion
613
- export { computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
616
+ export { computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, numberingLevelHasMarkerSlot, padDecimal, parseNumbering, renderListMarker };
@@ -1,19 +1,22 @@
1
+ import { canonicalJson } from "../utils/canonicalJson.js";
1
2
  import { isValidHexColor } from "../utils/colorResolver.js";
2
3
  import { isValidHexId } from "../utils/hexId.js";
3
4
  import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
4
5
  import { parseFieldType } from "./fieldParser.js";
5
6
  import { parseHyperlink as parseHyperlink$1, parseHyperlinkChild } from "./hyperlinkParser.js";
6
- import { markerFormattingFromLevel } from "./numberingParser.js";
7
+ import { markerFormattingFromLevel, numberingLevelHasMarkerSlot } from "./numberingParser.js";
7
8
  import { paraIdInRange } from "./paraIdRangeNormalization.js";
9
+ import { assignParagraphPropertySource } from "./paragraphPropertySource.js";
8
10
  import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
9
11
  import { consolidateParagraphContent } from "./runConsolidator.js";
10
12
  import { parseRun, parseRunProperties } from "./runParser.js";
11
13
  import { parseSdtProperties } from "./sdtProperties.js";
12
14
  import { parseSectionProperties } from "./sectionParser.js";
15
+ import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
13
16
  import { captureVerbatimXml } from "./verbatimCapture.js";
14
- import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildren, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
17
+ import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildByNamespaceUri, findChildren, findChildrenByNamespaceUri, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
15
18
  import { panic } from "better-result";
16
- import { PARAGRAPH_MARK_CHANGE_KINDS, normalizeRevisionId } from "@stll/docx-core/model";
19
+ import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
17
20
  //#region src/docx/paragraphParser.ts
18
21
  /**
19
22
  * Extract plain text from a math element (recursive text content extraction)
@@ -373,6 +376,21 @@ function parseParagraphProperties(pPr, theme, styles) {
373
376
  }
374
377
  return Object.keys(formatting).length > 0 ? formatting : void 0;
375
378
  }
379
+ /**
380
+ * Capture the authored paragraph properties separately from structural and
381
+ * revision children, which have their own model fields and lifecycles.
382
+ */
383
+ const captureParagraphPropertySource = (pPr) => captureVerbatimXml(cloneElement(pPr, { elements: (pPr.elements ?? []).flatMap((child) => {
384
+ if (child.type !== "element") return [child];
385
+ const localName = getLocalName(child.name);
386
+ if (WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "") && (localName === "sectPr" || localName === "pPrChange")) return [];
387
+ if (localName !== "rPr" || !WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "")) return [child];
388
+ return [cloneElement(child, { elements: (child.elements ?? []).filter((runProperty) => {
389
+ if (runProperty.type !== "element") return true;
390
+ const runPropertyName = getLocalName(runProperty.name);
391
+ return !WORDPROCESSINGML_NAMESPACE_URIS.has(runProperty.namespaceUri ?? "") || !PARAGRAPH_MARK_CHANGE_KINDS.some((kind) => kind === runPropertyName);
392
+ }) })];
393
+ }) }));
376
394
  const RENDERED_BREAK_INLINE_WRAPPERS = /* @__PURE__ */ new Set([
377
395
  "hyperlink",
378
396
  "smartTag",
@@ -477,39 +495,15 @@ function normalizeDeletionContentElement(node) {
477
495
  let mappedName = node.name;
478
496
  if (localName === "delText") mappedName = replaceLocalName(node.name, "t");
479
497
  else if (localName === "delInstrText") mappedName = replaceLocalName(node.name, "instrText");
480
- const result = { ...node };
481
- if (mappedName !== void 0) result.name = mappedName;
482
- if (node.elements) result.elements = node.elements.map(normalizeDeletionContentElement);
483
- return result;
484
- }
485
- function parseTrackedChangeInfo(node) {
486
- const rawId = getAttribute(node, "w", "id");
487
- const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
488
- const rawAuthor = getAttribute(node, "w", "author");
489
- const rawDate = getAttribute(node, "w", "date");
490
- const author = rawAuthor?.trim() ?? "";
491
- const date = rawDate?.trim() ?? "";
492
- const initials = (getAttribute(node, "w", "initials") ?? "").trim();
493
- const info = {
494
- id: normalizeRevisionId(parsedId),
495
- author: author.length > 0 ? author : "Unknown"
496
- };
497
- if (date.length > 0) info.date = date;
498
- if (initials.length > 0) info.initials = initials;
499
- return info;
500
- }
501
- function parsePropertyChangeInfo(node) {
502
- const base = parseTrackedChangeInfo(node);
503
- const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
504
- return rsid.length > 0 ? {
505
- ...base,
506
- rsid
507
- } : base;
498
+ return cloneElement(node, {
499
+ ...mappedName !== void 0 ? { name: mappedName } : {},
500
+ ...node.elements ? { elements: node.elements.map(normalizeDeletionContentElement) } : {}
501
+ });
508
502
  }
509
503
  function parseParagraphPropertyChanges(pPr, theme, styles, currentFormatting) {
510
504
  if (!pPr) return;
511
- const changes = findChildren(pPr, "w", "pPrChange").map((changeElement) => {
512
- const previousFormatting = parseParagraphProperties(findChild(changeElement, "w", "pPr"), theme, styles ?? void 0);
505
+ const changes = findChildrenByNamespaceUri(pPr, WORDPROCESSINGML_NAMESPACE_URIS, "pPrChange").map((changeElement) => {
506
+ const previousFormatting = parseParagraphProperties(findChildByNamespaceUri(changeElement, WORDPROCESSINGML_NAMESPACE_URIS, "pPr"), theme, styles ?? void 0);
513
507
  const change = {
514
508
  type: "paragraphPropertyChange",
515
509
  info: parsePropertyChangeInfo(changeElement)
@@ -528,7 +522,7 @@ function parseParagraphPropertyChanges(pPr, theme, styles, currentFormatting) {
528
522
  */
529
523
  function parseParagraphMarkChange(pPr) {
530
524
  if (!pPr) return;
531
- const rPr = findChild(pPr, "w", "rPr");
525
+ const rPr = findChildByNamespaceUri(pPr, WORDPROCESSINGML_NAMESPACE_URIS, "rPr");
532
526
  if (!rPr) return;
533
527
  for (const kind of PARAGRAPH_MARK_CHANGE_KINDS) {
534
528
  const element = findChildByNamespaceUri(rPr, WORDPROCESSINGML_NAMESPACE_URIS, kind);
@@ -1128,6 +1122,7 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
1128
1122
  level: ilvl,
1129
1123
  numId,
1130
1124
  marker: level.lvlText,
1125
+ markerTemplate: level.lvlText,
1131
1126
  isBullet: level.numFmt === "bullet",
1132
1127
  levelNumFmts,
1133
1128
  levelStarts
@@ -1192,7 +1187,7 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
1192
1187
  const directHanging = directInd ? parseNumericAttribute(directInd, "w", "hanging") : void 0;
1193
1188
  const hasDirectFirstLineOrHanging = directFirstLine !== void 0 && directFirstLine !== 0 || directHanging !== void 0 && directHanging !== 0;
1194
1189
  if (!hasDirectLeft && !chainInd.left && level.pPr.indentLeft !== void 0) paragraph.formatting.indentLeft = level.pPr.indentLeft;
1195
- if (!hasDirectFirstLineOrHanging && !chainInd.firstLine) {
1190
+ if (!hasDirectFirstLineOrHanging && !chainInd.firstLine && numberingLevelHasMarkerSlot(level)) {
1196
1191
  if (level.pPr.indentFirstLine !== void 0) paragraph.formatting.indentFirstLine = level.pPr.indentFirstLine;
1197
1192
  if (level.pPr.hangingIndent !== void 0) paragraph.formatting.hangingIndent = level.pPr.hangingIndent;
1198
1193
  }
@@ -1200,6 +1195,10 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
1200
1195
  }
1201
1196
  }
1202
1197
  }
1198
+ if (pPr) assignParagraphPropertySource(paragraph, {
1199
+ xml: captureParagraphPropertySource(pPr),
1200
+ formattingJson: canonicalJson(paragraph.formatting ?? {})
1201
+ });
1203
1202
  return paragraph;
1204
1203
  }
1205
1204
  /**
@@ -0,0 +1,49 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { Transaction } from "prosemirror-state";
3
+ import { Fragment, Mark, Node } from "prosemirror-model";
4
+ //#region src/docx/paragraphPropertySource.d.ts
5
+ type ParagraphPropertySource = {
6
+ xml: string;
7
+ formattingJson: string;
8
+ };
9
+ declare const assignParagraphPropertySource: (paragraph: document_d_exports.Paragraph, source: ParagraphPropertySource) => void;
10
+ declare const getParagraphPropertySource: (paragraph: document_d_exports.Paragraph) => ParagraphPropertySource | undefined;
11
+ declare const copyParagraphPropertySource: (target: document_d_exports.Paragraph, source: document_d_exports.Paragraph) => void;
12
+ type ParagraphCloneOverrides = Omit<Partial<document_d_exports.Paragraph>, "type">;
13
+ /** Clone a paragraph while deliberately retaining its parsed `w:pPr` owner. */
14
+ declare const cloneParagraphWithPropertySource: (paragraph: document_d_exports.Paragraph, overrides: ParagraphCloneOverrides) => document_d_exports.Paragraph;
15
+ /** Clone a paragraph whose formatting provenance is deliberately no longer applicable. */
16
+ declare const cloneParagraphWithoutPropertySource: (paragraph: document_d_exports.Paragraph, overrides: ParagraphCloneOverrides) => document_d_exports.Paragraph;
17
+ /**
18
+ * Deep-clone a document and transfer each private paragraph capture across the
19
+ * exact graph clone. `structuredClone` preserves graph topology, so any count
20
+ * mismatch is an internal invariant failure rather than a position heuristic.
21
+ */
22
+ declare const cloneDocumentWithParagraphPropertySources: (document: document_d_exports.Document) => document_d_exports.Document;
23
+ declare const linkProseParagraphPropertySource: (proseParagraph: Node, sourceParagraph: document_d_exports.Paragraph) => void;
24
+ type RecreateProseNodeOptions = {
25
+ attrs?: Node["attrs"];
26
+ content?: Fragment | Node | readonly Node[] | null;
27
+ marks?: readonly Mark[];
28
+ };
29
+ /** Rebuild a PM node and retain paragraph provenance when the node is one. */
30
+ declare const recreateProseNodeWithParagraphPropertySource: (source: Node, options?: RecreateProseNodeOptions) => Node;
31
+ type SetProseParagraphMarkupOptions = {
32
+ attrs: Node["attrs"];
33
+ ownership: "preserve" | "transfer-allocated-id";
34
+ pos: number;
35
+ transaction: Transaction;
36
+ };
37
+ /** Replace paragraph markup without losing its private parser-owner link. */
38
+ declare const setProseParagraphMarkupWithPropertySource: ({ attrs, ownership, pos, transaction }: SetProseParagraphMarkupOptions) => void;
39
+ /**
40
+ * Carry the parser-owned source identity across load-time paraId allocation.
41
+ * The generated id is safe to use later because it was assigned while the
42
+ * ProseMirror node still had an unambiguous source paragraph owner.
43
+ */
44
+ declare const transferProseParagraphPropertySource: (target: Node, source: Node, paraId: string) => void;
45
+ declare const linkParagraphPropertySourceCandidate: (target: document_d_exports.Paragraph, source: Node) => void;
46
+ declare const getParagraphPropertySourceCandidate: (paragraph: document_d_exports.Paragraph) => document_d_exports.Paragraph | undefined;
47
+ declare const getParagraphPropertySourceTransferId: (paragraph: document_d_exports.Paragraph) => string | undefined;
48
+ //#endregion
49
+ export { assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource };
@@ -0,0 +1,113 @@
1
+ import { visitDocxParagraphs } from "./paragraphTraversal.js";
2
+ import { panic } from "better-result";
3
+ //#region src/docx/paragraphPropertySource.ts
4
+ const paragraphPropertySources = /* @__PURE__ */ new WeakMap();
5
+ const paragraphPropertySourceOwners = /* @__PURE__ */ new WeakMap();
6
+ const proseParagraphSourceOwners = /* @__PURE__ */ new WeakMap();
7
+ const paragraphPropertySourceCandidates = /* @__PURE__ */ new WeakMap();
8
+ const paragraphPropertySourceTransferIds = /* @__PURE__ */ new WeakMap();
9
+ const assignParagraphPropertySource = (paragraph, source) => {
10
+ paragraphPropertySources.set(paragraph, source);
11
+ paragraphPropertySourceOwners.set(paragraph, paragraph);
12
+ };
13
+ const getParagraphPropertySource = (paragraph) => paragraphPropertySources.get(paragraph);
14
+ const copyParagraphPropertySource = (target, source) => {
15
+ const propertySource = paragraphPropertySources.get(source);
16
+ if (propertySource) {
17
+ paragraphPropertySources.set(target, { ...propertySource });
18
+ paragraphPropertySourceOwners.set(target, paragraphPropertySourceOwners.get(source) ?? source);
19
+ }
20
+ const transferId = paragraphPropertySourceTransferIds.get(source);
21
+ if (transferId) paragraphPropertySourceTransferIds.set(target, transferId);
22
+ const candidate = paragraphPropertySourceCandidates.get(source);
23
+ if (candidate) paragraphPropertySourceCandidates.set(target, candidate);
24
+ };
25
+ /** Clone a paragraph while deliberately retaining its parsed `w:pPr` owner. */
26
+ const cloneParagraphWithPropertySource = (paragraph, overrides) => {
27
+ const cloned = {
28
+ ...paragraph,
29
+ ...overrides
30
+ };
31
+ copyParagraphPropertySource(cloned, paragraph);
32
+ return cloned;
33
+ };
34
+ /** Clone a paragraph whose formatting provenance is deliberately no longer applicable. */
35
+ const cloneParagraphWithoutPropertySource = (paragraph, overrides) => ({
36
+ ...paragraph,
37
+ ...overrides
38
+ });
39
+ const paragraphsIn = (document) => {
40
+ const paragraphs = [];
41
+ visitDocxParagraphs({
42
+ documentBody: document.package.document,
43
+ headers: document.package.headers,
44
+ footers: document.package.footers,
45
+ footnotes: document.package.footnotes,
46
+ endnotes: document.package.endnotes
47
+ }, (paragraph) => paragraphs.push(paragraph));
48
+ return paragraphs;
49
+ };
50
+ /**
51
+ * Deep-clone a document and transfer each private paragraph capture across the
52
+ * exact graph clone. `structuredClone` preserves graph topology, so any count
53
+ * mismatch is an internal invariant failure rather than a position heuristic.
54
+ */
55
+ const cloneDocumentWithParagraphPropertySources = (document) => {
56
+ const cloned = structuredClone(document);
57
+ const sources = paragraphsIn(document);
58
+ const targets = paragraphsIn(cloned);
59
+ if (sources.length !== targets.length) panic("The cloned document changed paragraph graph ownership.");
60
+ for (const [index, source] of sources.entries()) {
61
+ const target = targets.at(index);
62
+ if (!target) panic("The cloned document lost a paragraph owner.");
63
+ copyParagraphPropertySource(target, source);
64
+ }
65
+ return cloned;
66
+ };
67
+ const linkProseParagraphPropertySource = (proseParagraph, sourceParagraph) => {
68
+ if (paragraphPropertySources.has(sourceParagraph)) proseParagraphSourceOwners.set(proseParagraph, paragraphPropertySourceOwners.get(sourceParagraph) ?? sourceParagraph);
69
+ };
70
+ /** Carry a parser-linked paragraph owner across an immutable PM node rebuild. */
71
+ const copyProseParagraphPropertySource = (target, source) => {
72
+ if (target.type.name !== "paragraph" || source.type.name !== "paragraph") return;
73
+ const sourceOwner = proseParagraphSourceOwners.get(source);
74
+ if (sourceOwner) proseParagraphSourceOwners.set(target, sourceOwner);
75
+ };
76
+ /** Rebuild a PM node and retain paragraph provenance when the node is one. */
77
+ const recreateProseNodeWithParagraphPropertySource = (source, options = {}) => {
78
+ const target = source.type.create(options.attrs ?? source.attrs, options.content === void 0 ? source.content : options.content, options.marks ?? source.marks);
79
+ copyProseParagraphPropertySource(target, source);
80
+ return target;
81
+ };
82
+ /** Replace paragraph markup without losing its private parser-owner link. */
83
+ const setProseParagraphMarkupWithPropertySource = ({ attrs, ownership, pos, transaction }) => {
84
+ const source = transaction.doc.nodeAt(pos);
85
+ transaction.setNodeMarkup(pos, void 0, attrs);
86
+ const target = transaction.doc.nodeAt(pos);
87
+ if (!source || !target) return;
88
+ if (ownership === "preserve") {
89
+ copyProseParagraphPropertySource(target, source);
90
+ return;
91
+ }
92
+ const paraId = target.attrs["paraId"];
93
+ if (typeof paraId === "string") transferProseParagraphPropertySource(target, source, paraId);
94
+ };
95
+ /**
96
+ * Carry the parser-owned source identity across load-time paraId allocation.
97
+ * The generated id is safe to use later because it was assigned while the
98
+ * ProseMirror node still had an unambiguous source paragraph owner.
99
+ */
100
+ const transferProseParagraphPropertySource = (target, source, paraId) => {
101
+ const sourceOwner = proseParagraphSourceOwners.get(source);
102
+ if (!sourceOwner) return;
103
+ proseParagraphSourceOwners.set(target, sourceOwner);
104
+ paragraphPropertySourceTransferIds.set(sourceOwner, paraId);
105
+ };
106
+ const linkParagraphPropertySourceCandidate = (target, source) => {
107
+ const sourceOwner = proseParagraphSourceOwners.get(source);
108
+ if (sourceOwner) paragraphPropertySourceCandidates.set(target, sourceOwner);
109
+ };
110
+ const getParagraphPropertySourceCandidate = (paragraph) => paragraphPropertySourceCandidates.get(paragraph);
111
+ const getParagraphPropertySourceTransferId = (paragraph) => paragraphPropertySourceTransferIds.get(paragraph);
112
+ //#endregion
113
+ export { assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource };
@@ -1,41 +1,37 @@
1
1
  //#region src/docx/paragraphTraversal.ts
2
2
  /** Visit every run directly owned by a paragraph's inline-content tree. */
3
3
  const visitParagraphRuns = (paragraph, visit) => {
4
- const visitRun = (run) => {
5
- visit(run);
6
- };
7
- const visitHyperlink = (hyperlink) => {
8
- for (const child of hyperlink.children) if (child.type === "run") visitRun(child);
9
- };
10
- const visitInlineContent = (content) => {
11
- for (const child of content) {
12
- if (child.type === "run") {
13
- visitRun(child);
14
- continue;
15
- }
16
- if (child.type === "hyperlink") visitHyperlink(child);
17
- }
18
- };
19
4
  const visitParagraphContent = (content) => {
20
- if (content.type === "run") {
21
- visitRun(content);
22
- return;
23
- }
24
- if (content.type === "hyperlink") {
25
- visitHyperlink(content);
26
- return;
27
- }
28
- if (content.type === "simpleField" || content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") {
29
- visitInlineContent(content.content);
30
- return;
31
- }
32
- if (content.type === "inlineSdt") {
33
- for (const child of content.content) visitParagraphContent(child);
34
- return;
35
- }
36
- if (content.type === "complexField") {
37
- for (const run of content.fieldCode) visitRun(run);
38
- for (const run of content.fieldResult) visitRun(run);
5
+ switch (content.type) {
6
+ case "run":
7
+ visit(content);
8
+ return;
9
+ case "hyperlink":
10
+ for (const child of content.children) visitParagraphContent(child);
11
+ return;
12
+ case "simpleField":
13
+ case "insertion":
14
+ case "deletion":
15
+ case "moveFrom":
16
+ case "moveTo":
17
+ case "inlineSdt":
18
+ for (const child of content.content) visitParagraphContent(child);
19
+ return;
20
+ case "complexField":
21
+ for (const run of content.fieldCode) visit(run);
22
+ for (const run of content.fieldResult) visit(run);
23
+ return;
24
+ case "bookmarkStart":
25
+ case "bookmarkEnd":
26
+ case "commentRangeStart":
27
+ case "commentRangeEnd":
28
+ case "commentReference":
29
+ case "moveFromRangeStart":
30
+ case "moveFromRangeEnd":
31
+ case "moveToRangeStart":
32
+ case "moveToRangeEnd":
33
+ case "mathEquation": return;
34
+ default: return content;
39
35
  }
40
36
  };
41
37
  for (const content of paragraph.content) visitParagraphContent(content);
@@ -28,14 +28,6 @@ type RepackOptions = {
28
28
  /** Changed note paragraphs that must be serialized even without source paraIds. */
29
29
  changedNoteParaIds?: ReadonlySet<string>;
30
30
  };
31
- /**
32
- * Repack a Document into a valid DOCX file
33
- *
34
- * @param doc - Document with modified content
35
- * @param options - Optional repack options
36
- * @returns Promise resolving to DOCX as ArrayBuffer
37
- * @throws {Error} if document has no original buffer for round-trip
38
- */
39
31
  declare function repackDocx(doc: document_d_exports.Document, options?: RepackOptions): Promise<ArrayBuffer>;
40
32
  /**
41
33
  * Repack a Document using raw content for more control
@@ -1,3 +1,4 @@
1
+ import { consumeTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
1
2
  import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
2
3
  import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
3
4
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
@@ -115,11 +116,34 @@ const extractHeaderFooterReferences = (xml) => {
115
116
  const hasParsedHeaderFooterPart = (doc, ref) => {
116
117
  return (ref.element === "headerReference" ? doc.package.headers : doc.package.footers)?.has(ref.rId) ?? false;
117
118
  };
118
- function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXml, doc) {
119
+ const headerFooterReferenceKey = ({ element, type, rId }) => `${element}:${type}:${rId}`;
120
+ const incrementReferenceCount = (counts, key) => {
121
+ counts.set(key, (counts.get(key) ?? 0) + 1);
122
+ };
123
+ const consumeReferenceCount = (counts, key) => {
124
+ const count = counts.get(key) ?? 0;
125
+ if (count === 0) return false;
126
+ counts.set(key, count - 1);
127
+ return true;
128
+ };
129
+ function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXml, doc, sectionEndpointRemoval) {
119
130
  const originalSectionCount = countDocumentSections(originalDocumentXml);
120
- if (countDocumentSections(serializedDocumentXml) < originalSectionCount) throw new DocxPackageFidelityError("Full DOCX repack would drop section properties. Use selective patching instead.");
121
- const serializedRefs = new Set(extractHeaderFooterReferences(serializedDocumentXml).map((ref) => `${ref.element}:${ref.type}:${ref.rId}`));
122
- if (extractHeaderFooterReferences(originalDocumentXml).filter((ref) => hasParsedHeaderFooterPart(doc, ref) && !serializedRefs.has(`${ref.element}:${ref.type}:${ref.rId}`)).length > 0) throw new DocxPackageFidelityError("Full DOCX repack would drop header/footer references. Use selective patching instead.");
131
+ const serializedSectionCount = countDocumentSections(serializedDocumentXml);
132
+ const trackedRemovedSectionCount = sectionEndpointRemoval ? sectionEndpointRemoval.sourceParagraphEndpointCount - sectionEndpointRemoval.expectedParagraphEndpointCount : 0;
133
+ const exactTrackedSectionRemoval = trackedRemovedSectionCount > 0 && originalSectionCount - trackedRemovedSectionCount === serializedSectionCount && serializedSectionCount < originalSectionCount;
134
+ if (serializedSectionCount < originalSectionCount && !exactTrackedSectionRemoval) throw new DocxPackageFidelityError("Full DOCX repack would drop section properties. Use selective patching instead.");
135
+ const serializedReferenceCounts = /* @__PURE__ */ new Map();
136
+ for (const reference of extractHeaderFooterReferences(serializedDocumentXml)) incrementReferenceCount(serializedReferenceCounts, headerFooterReferenceKey(reference));
137
+ const removedReferenceCounts = /* @__PURE__ */ new Map();
138
+ if (exactTrackedSectionRemoval && sectionEndpointRemoval) for (const { part, type, relationshipId } of sectionEndpointRemoval.removedReferences) incrementReferenceCount(removedReferenceCounts, `${part}Reference:${type}:${relationshipId}`);
139
+ const missingRefs = [];
140
+ for (const reference of extractHeaderFooterReferences(originalDocumentXml)) {
141
+ if (!hasParsedHeaderFooterPart(doc, reference)) continue;
142
+ const key = headerFooterReferenceKey(reference);
143
+ if (consumeReferenceCount(serializedReferenceCounts, key) || consumeReferenceCount(removedReferenceCounts, key)) continue;
144
+ missingRefs.push(reference);
145
+ }
146
+ if (missingRefs.length > 0) throw new DocxPackageFidelityError("Full DOCX repack would drop header/footer references. Use selective patching instead.");
123
147
  }
124
148
  async function serializeCommentsToZip(doc, zip, compressionLevel) {
125
149
  const comments = doc.package.document.comments ?? [];
@@ -488,7 +512,7 @@ const cloneDocxZip = (source) => {
488
512
  clone.files = { ...source.files };
489
513
  return clone;
490
514
  };
491
- const finishRepack = async ({ document, originalZip, outputZip, originalDocumentXml, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds }) => {
515
+ const finishRepack = async ({ document, originalZip, outputZip, originalDocumentXml, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval }) => {
492
516
  await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
493
517
  const parts = collectDocxParts(document, outputZip);
494
518
  await processNewImages(parts, outputZip, compressionLevel);
@@ -496,7 +520,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
496
520
  assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
497
521
  applyReplyThreadMarkers(document);
498
522
  const documentXml = serializeDocument(document, originalDocumentXml === void 0 ? void 0 : readRootNamespaceBindings(originalDocumentXml));
499
- if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document);
523
+ if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document, sectionEndpointRemoval);
500
524
  outputZip.file("word/document.xml", documentXml, {
501
525
  compression: "DEFLATE",
502
526
  compressionOptions: { level: compressionLevel }
@@ -526,15 +550,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
526
550
  }
527
551
  return generateDocxZip(outputZip, compressionLevel);
528
552
  };
529
- /**
530
- * Repack a Document into a valid DOCX file
531
- *
532
- * @param doc - Document with modified content
533
- * @param options - Optional repack options
534
- * @returns Promise resolving to DOCX as ArrayBuffer
535
- * @throws {Error} if document has no original buffer for round-trip
536
- */
537
- async function repackDocx(doc, options = {}) {
553
+ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, sectionEndpointRemoval }) {
538
554
  if (!doc.originalBuffer) panic("Cannot repack document: no original buffer for round-trip. Use createDocx() for new documents.");
539
555
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
540
556
  const exportDocument = withoutOrphanCommentRanges(doc);
@@ -551,7 +567,16 @@ async function repackDocx(doc, options = {}) {
551
567
  compressionLevel,
552
568
  updateModifiedDate,
553
569
  ...modifiedBy !== void 0 ? { modifiedBy } : {},
554
- ...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {}
570
+ ...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
571
+ ...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {}
572
+ });
573
+ }
574
+ function repackDocx(doc, options = {}) {
575
+ const sectionEndpointRemoval = consumeTrackedSectionEndpointRemoval(doc);
576
+ return repackDocxWithSectionEndpointRemoval({
577
+ document: doc,
578
+ options,
579
+ ...sectionEndpointRemoval ? { sectionEndpointRemoval } : {}
555
580
  });
556
581
  }
557
582
  /**
@@ -1,3 +1,4 @@
1
+ import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
1
2
  //#region src/docx/runConsolidator.ts
2
3
  /**
3
4
  * Check if two TextFormatting objects are equivalent
@@ -193,10 +194,7 @@ function consolidateParagraphContent(content) {
193
194
  */
194
195
  function consolidateParagraph(paragraph) {
195
196
  if (paragraph.content.length === 0) return paragraph;
196
- return {
197
- ...paragraph,
198
- content: consolidateParagraphContent(paragraph.content)
199
- };
197
+ return cloneParagraphWithPropertySource(paragraph, { content: consolidateParagraphContent(paragraph.content) });
200
198
  }
201
199
  /**
202
200
  * Get the number of runs in a paragraph (for debugging/metrics)
@@ -7,10 +7,11 @@ import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapePar
7
7
  import { isTextBoxDrawing } from "./textBoxParser.js";
8
8
  import { requiresXmlSpacePreserve } from "./textWhitespace.js";
9
9
  import { resolveThemeFontRef } from "./themeParser.js";
10
+ import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
10
11
  import { captureVerbatimXml } from "./verbatimCapture.js";
11
12
  import { parseVmlImageContent } from "./vmlImageParser.js";
12
13
  import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
13
- import { DRAWING_RAW_XML_MODES, normalizeRevisionId } from "@stll/docx-core/model";
14
+ import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
14
15
  //#region src/docx/runParser.ts
15
16
  /**
16
17
  * Sanity cap on `w:lang` `@w:val`/`@w:eastAsia`/`@w:bidi` tag length. BCP-47
@@ -40,9 +41,11 @@ function parseShadingProperties(shd) {
40
41
  if (!shd) return;
41
42
  const props = {};
42
43
  const color = getAttribute(shd, "w", "color");
43
- if (color && color !== "auto" && isValidHexColor(color)) props.color = { rgb: color };
44
+ if (color === "auto") props.color = { auto: true };
45
+ else if (color && isValidHexColor(color)) props.color = { rgb: color };
44
46
  const fill = getAttribute(shd, "w", "fill");
45
- if (fill && fill !== "auto" && isValidHexColor(fill)) props.fill = { rgb: fill };
47
+ if (fill === "auto") props.fill = { auto: true };
48
+ else if (fill && isValidHexColor(fill)) props.fill = { rgb: fill };
46
49
  const validatedThemeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
47
50
  if (validatedThemeFill) {
48
51
  if (!props.fill) props.fill = {};
@@ -341,20 +344,6 @@ function parseRunProperties(rPr, theme, _styles) {
341
344
  }
342
345
  return Object.keys(formatting).length > 0 ? formatting : void 0;
343
346
  }
344
- function parsePropertyChangeInfo(changeElement) {
345
- const rawId = getAttribute(changeElement, "w", "id");
346
- const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
347
- const author = (getAttribute(changeElement, "w", "author") ?? "").trim();
348
- const date = (getAttribute(changeElement, "w", "date") ?? "").trim();
349
- const rsid = (getAttribute(changeElement, "w", "rsid") ?? "").trim();
350
- const info = {
351
- id: normalizeRevisionId(parsedId),
352
- author: author.length > 0 ? author : "Unknown"
353
- };
354
- if (date.length > 0) info.date = date;
355
- if (rsid.length > 0) info.rsid = rsid;
356
- return info;
357
- }
358
347
  function parseRunPropertyChanges(rPr, theme, styles, currentFormatting) {
359
348
  if (!rPr) return;
360
349
  const changes = findChildren(rPr, "w", "rPrChange").map((changeElement) => {