@stll/folio-core 0.56.0 → 0.57.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 (142) hide show
  1. package/dist/ai-edits/apply.js +45 -14
  2. package/dist/ai-edits/batch-claims.d.ts +3 -1
  3. package/dist/ai-edits/batch-claims.js +25 -1
  4. package/dist/ai-edits/table-row-column-mutations.js +17 -3
  5. package/dist/ai-suggestions/apply.js +20 -1
  6. package/dist/compare/inline-atoms.js +40 -16
  7. package/dist/compare/inline-provenance.js +12 -5
  8. package/dist/compat/eigenpal.d.ts +2 -1
  9. package/dist/compat/eigenpal.js +2 -1
  10. package/dist/controller/canonicalClipboard.js +7 -1
  11. package/dist/controller/canonicalComments.d.ts +41 -0
  12. package/dist/controller/canonicalComments.js +152 -0
  13. package/dist/controller/canonicalComposition.d.ts +4 -0
  14. package/dist/controller/canonicalComposition.js +43 -11
  15. package/dist/controller/canonicalInlineProjection.d.ts +6 -1
  16. package/dist/controller/canonicalInlineProjection.js +86 -3
  17. package/dist/controller/canonicalInput.js +10 -1
  18. package/dist/controller/canonicalInputTimer.d.ts +10 -1
  19. package/dist/controller/canonicalInputTimer.js +24 -1
  20. package/dist/controller/canonicalPublicOperations.d.ts +2 -2
  21. package/dist/controller/canonicalPublicOperations.js +107 -15
  22. package/dist/controller/canonicalSelection.d.ts +18 -0
  23. package/dist/controller/canonicalSelection.js +32 -0
  24. package/dist/controller/canonicalSession.d.ts +3 -0
  25. package/dist/controller/canonicalSession.js +58 -24
  26. package/dist/controller/canonicalStoryEditor.d.ts +2 -0
  27. package/dist/controller/canonicalStoryEditor.js +20 -0
  28. package/dist/controller/folioEditor.js +13 -0
  29. package/dist/controller/headerFooterEditorManager.d.ts +1 -1
  30. package/dist/controller/headerFooterEditorManager.js +25 -11
  31. package/dist/controller/hiddenEditorApi.d.ts +13 -2
  32. package/dist/controller/hiddenEditorApi.js +4 -0
  33. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  34. package/dist/controller/hiddenEditorManager.js +166 -13
  35. package/dist/controller/noteEditorManager.d.ts +1 -1
  36. package/dist/controller/noteEditorManager.js +28 -13
  37. package/dist/docx/builtInStyles.js +13 -6
  38. package/dist/docx/containerChildren.gen.d.ts +3 -1
  39. package/dist/docx/containerChildren.gen.js +33 -1
  40. package/dist/docx/fieldParser.js +14 -2
  41. package/dist/docx/hyperlinkParser.d.ts +1 -1
  42. package/dist/docx/hyperlinkParser.js +20 -1
  43. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  44. package/dist/docx/inlineWrapperContent.js +4 -4
  45. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  46. package/dist/docx/linkedSdtPreservation.js +37 -0
  47. package/dist/docx/paragraphParser.js +88 -67
  48. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  49. package/dist/docx/paragraphPropertySource.js +107 -1
  50. package/dist/docx/preservedRunContent.js +2 -1
  51. package/dist/docx/relsParser.js +26 -2
  52. package/dist/docx/rezip.js +9 -6
  53. package/dist/docx/selectiveSave.js +10 -8
  54. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  55. package/dist/docx/serializer/commentSerializer.js +7 -3
  56. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  57. package/dist/docx/server/materializeYjsDocx.js +14 -6
  58. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  59. package/dist/i18n/messages/ar.gen.js +4 -0
  60. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  61. package/dist/i18n/messages/catalogs.gen.js +68 -0
  62. package/dist/i18n/messages/cs.gen.js +4 -0
  63. package/dist/i18n/messages/de.gen.js +4 -0
  64. package/dist/i18n/messages/en.gen.js +4 -0
  65. package/dist/i18n/messages/es.gen.js +4 -0
  66. package/dist/i18n/messages/et.gen.js +4 -0
  67. package/dist/i18n/messages/fr.gen.js +4 -0
  68. package/dist/i18n/messages/he.gen.js +4 -0
  69. package/dist/i18n/messages/hi.gen.js +4 -0
  70. package/dist/i18n/messages/hu.gen.js +4 -0
  71. package/dist/i18n/messages/lt.gen.js +4 -0
  72. package/dist/i18n/messages/lv.gen.js +4 -0
  73. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  74. package/dist/i18n/messages/pl.gen.js +4 -0
  75. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  76. package/dist/i18n/messages/sk.gen.js +4 -0
  77. package/dist/i18n/messages/tr.gen.js +4 -0
  78. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +2 -1
  81. package/dist/internal/revisionResolutionInline.js +25 -3
  82. package/dist/markdown/renderRuns.js +1 -11
  83. package/dist/panel-layout.d.ts +11 -9
  84. package/dist/panel-layout.js +13 -9
  85. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  86. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  87. package/dist/prosemirror/attrs/index.js +5 -0
  88. package/dist/prosemirror/commands/comments.js +60 -35
  89. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  90. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  91. package/dist/prosemirror/commentOps.js +15 -1
  92. package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
  93. package/dist/prosemirror/conversion/toProseDoc.js +119 -18
  94. package/dist/prosemirror/extensions/core/ParagraphExtension.js +22 -5
  95. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  96. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  97. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +22 -14
  98. package/dist/prosemirror/extensions/marks/markUtils.js +8 -7
  99. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  100. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  101. package/dist/prosemirror/fieldRepresentation.js +18 -0
  102. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  103. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  104. package/dist/prosemirror/listNumbering.js +11 -5
  105. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  106. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  107. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  108. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  109. package/dist/prosemirror/noteReferenceReview.js +14 -1
  110. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  111. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  112. package/dist/prosemirror/plugins/suggestionMode.js +47 -49
  113. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  114. package/dist/prosemirror/preservedXmlReview.js +35 -0
  115. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  116. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  117. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  118. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  119. package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
  120. package/dist/prosemirror/runStyleFormatting.js +3 -1
  121. package/dist/prosemirror/schema/marks.d.ts +1 -0
  122. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  123. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  124. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  125. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  126. package/dist/prosemirror/textInput.d.ts +5 -1
  127. package/dist/prosemirror/textInput.js +3 -3
  128. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  129. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  130. package/dist/prosemirror/validation.js +3 -5
  131. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  132. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  133. package/dist/types/canonicalCapabilities.d.ts +6 -20
  134. package/dist/types/canonicalCapabilities.js +6 -20
  135. package/dist/types/canonicalComments.d.ts +51 -0
  136. package/dist/types/canonicalComments.js +0 -0
  137. package/dist/types/canonicalSections.d.ts +12 -0
  138. package/dist/types/canonicalSections.js +0 -0
  139. package/dist/utils/headingCollector.js +45 -4
  140. package/dist/utils/outlineDepth.d.ts +9 -0
  141. package/dist/utils/outlineDepth.js +9 -0
  142. package/package.json +2 -2
@@ -0,0 +1,37 @@
1
+ import { preserveInlineChild } from "./preservedRunContent.js";
2
+ import { WORDPROCESSINGML_NAMESPACE_URIS, getChildElements, getLocalName } from "./xmlParser.js";
3
+ import { TaggedError } from "better-result";
4
+ //#region src/docx/linkedSdtPreservation.ts
5
+ var UnrepresentableLinkedSdtRevisionError = class extends TaggedError("UnrepresentableLinkedSdtRevisionError") {};
6
+ /** Capture only the unsupported hyperlink child, never its enclosing revision. */
7
+ const preserveLinkedSdt = (root) => {
8
+ const pending = getChildElements(root);
9
+ while (pending.length > 0) {
10
+ const node = pending.pop();
11
+ if (!node) continue;
12
+ const wordprocessing = WORDPROCESSINGML_NAMESPACE_URIS.has(node.namespaceUri ?? "");
13
+ const name = getLocalName(node.name);
14
+ if (wordprocessing && [
15
+ "ins",
16
+ "del",
17
+ "moveFrom",
18
+ "moveTo",
19
+ "rPrChange",
20
+ "pPrChange",
21
+ "sectPrChange",
22
+ "tblPrChange",
23
+ "tblGridChange",
24
+ "trPrChange",
25
+ "tblPrExChange",
26
+ "tcPrChange",
27
+ "cellIns",
28
+ "cellDel",
29
+ "cellMerge",
30
+ "numberingChange"
31
+ ].includes(name)) throw new UnrepresentableLinkedSdtRevisionError({ message: "A content control under a hyperlink cannot preserve nested revisions as opaque XML." });
32
+ pending.push(...getChildElements(node));
33
+ }
34
+ return preserveInlineChild(root);
35
+ };
36
+ //#endregion
37
+ export { UnrepresentableLinkedSdtRevisionError, preserveLinkedSdt };
@@ -7,8 +7,9 @@ import { parseFieldType } from "./fieldParser.js";
7
7
  import { fieldStateOf, parseFieldState } from "./fieldState.js";
8
8
  import { foldListNumberFields, isListNumberField, isListNumberGapMarker, isTabOnlyRun } from "./foldedListNumberFields.js";
9
9
  import { HYPERLINK_CHILD_HANDLERS, parseHyperlink as parseHyperlink$1, parseHyperlinkShell } from "./hyperlinkParser.js";
10
- import { isHyperlinkContent, isInlineSdtContent, isTrackedChangeWrapperChild } from "./inlineWrapperContent.js";
10
+ import { isHyperlinkContent, isInlineSdtContent, isSimpleFieldContent, isTrackedChangeWrapperChild } from "./inlineWrapperContent.js";
11
11
  import { inlineWrapperOf } from "./inlineWrapperParser.js";
12
+ import { preserveLinkedSdt } from "./linkedSdtPreservation.js";
12
13
  import { parseMarkupRangeMarker, parseMoveBookmarkMarker } from "./markupRangeMarker.js";
13
14
  import { counterFormatOf, markerAlignmentForLevel, markerFormattingFromLevel, numberingLevelHasMarkerSlot } from "./numberingParser.js";
14
15
  import { mergeParagraphNumbering as mergeParagraphNumbering$1, paragraphNumberingReferenceId as paragraphNumberingReferenceId$1, resolveParagraphNumbering as resolveParagraphNumbering$1 } from "./numberingReference.js";
@@ -357,15 +358,14 @@ const mathContentOf = (child) => {
357
358
  if (plainText) equation.plainText = plainText;
358
359
  return equation;
359
360
  };
360
- const hoistRevision = (wrapper) => (child, { styles, theme, rels, media, previews, inScopeXmlns, items, linkOver }) => {
361
- const wrapped = parseParagraphContents(child, styles, theme, null, rels, media, previews, wrapper === "deletion" || wrapper === "moveFrom" ? "deletion" : "default", inScopeXmlns);
361
+ /** Preserve a link across nested revision owners, rather than dropping its shell. */
362
+ const linkedRevisionContent = (wrapped, linkOver) => {
362
363
  const content = [];
363
364
  let linked = [];
364
365
  const flushLinked = () => {
365
- if (linked.length > 0) {
366
- content.push(linkOver(linked));
367
- linked = [];
368
- }
366
+ if (linked.length === 0) return;
367
+ content.push(linkOver(linked));
368
+ linked = [];
369
369
  };
370
370
  for (const item of wrapped) {
371
371
  if (isHyperlinkContent(item)) {
@@ -373,9 +373,28 @@ const hoistRevision = (wrapper) => (child, { styles, theme, rels, media, preview
373
373
  continue;
374
374
  }
375
375
  flushLinked();
376
- if (isTrackedChangeWrapperChild(item)) content.push(item);
376
+ switch (item.type) {
377
+ case "insertion":
378
+ case "deletion":
379
+ case "moveFrom":
380
+ case "moveTo":
381
+ content.push({
382
+ ...item,
383
+ content: linkedRevisionContent(item.content, linkOver)
384
+ });
385
+ break;
386
+ case "inlineSdt": return panic("An SDT under a hyperlink must retain its opaque source span");
387
+ default:
388
+ if (isTrackedChangeWrapperChild(item)) content.push(item);
389
+ break;
390
+ }
377
391
  }
378
392
  flushLinked();
393
+ return content;
394
+ };
395
+ const hoistRevision = (wrapper) => (child, { styles, theme, rels, media, previews, inScopeXmlns, items, linkOver }) => {
396
+ const wrapped = parseParagraphContents(child, styles, theme, null, rels, media, previews, wrapper === "deletion" || wrapper === "moveFrom" ? "deletion" : "default", inScopeXmlns, "hyperlink");
397
+ const content = linkedRevisionContent(wrapped, linkOver);
379
398
  items.push({
380
399
  type: "hoistedRevision",
381
400
  wrapper,
@@ -503,6 +522,27 @@ function parseSimpleField(node, styles, theme, rels, media, rootXmlns, previews)
503
522
  const fieldInlineWrapper = (element) => (child, context) => {
504
523
  context.push(parseFieldInlineWrapper(element, child, context));
505
524
  };
525
+ /** Read a field's revision with the same ownership rules as a paragraph revision. */
526
+ const fieldTrackedChange = (type) => (child, { push, styles, theme, rels, media, previews, inScopeXmlns }) => {
527
+ const parsedContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, type === "deletion" || type === "moveFrom" ? "deletion" : "default", inScopeXmlns);
528
+ if (parsedContent.length === 0 || !parsedContent.every(isTrackedChangeWrapperChild)) {
529
+ push(preserveInlineChild(child));
530
+ return;
531
+ }
532
+ const contents = [];
533
+ pushTrackedChangeWrapper({
534
+ contents,
535
+ type,
536
+ info: parseTrackedChangeInfo(child),
537
+ resolutionJoins: parseResolutionJoins(child),
538
+ content: parsedContent,
539
+ preserveEmpty: true
540
+ });
541
+ for (const content of contents) {
542
+ if (!isSimpleFieldContent(content)) panic("A parsed field revision must be a field child");
543
+ push(content);
544
+ }
545
+ };
506
546
  /**
507
547
  * What a `w:fldSimple` does with every child its content model declares.
508
548
  *
@@ -516,7 +556,10 @@ const SIMPLE_FIELD_CHILD_HANDLERS = {
516
556
  push(parseRun(child, styles, theme, rels, media, inScopeXmlns, previews));
517
557
  },
518
558
  hyperlink: (child, { push, styles, theme, rels, media, previews, inScopeXmlns }) => {
519
- push(parseHyperlink(child, rels, styles, theme, media, inScopeXmlns, previews));
559
+ for (const content of parseHyperlinkParagraphContents(child, rels, styles, theme, media, inScopeXmlns, previews)) {
560
+ if (!isSimpleFieldContent(content)) panic("A parsed field hyperlink must be a field child");
561
+ push(content);
562
+ }
520
563
  },
521
564
  bdo: fieldInlineWrapper("bdo"),
522
565
  dir: fieldInlineWrapper("dir"),
@@ -534,16 +577,16 @@ const SIMPLE_FIELD_CHILD_HANDLERS = {
534
577
  customXmlMoveFromRangeStart: CAPTURE,
535
578
  customXmlMoveToRangeEnd: CAPTURE,
536
579
  customXmlMoveToRangeStart: CAPTURE,
537
- del: CAPTURE,
580
+ del: fieldTrackedChange("deletion"),
538
581
  fldData: CAPTURE,
539
582
  fldSimple: (child, { push }) => {
540
583
  push(preserveInlineChild(child));
541
584
  },
542
- ins: CAPTURE,
543
- moveFrom: CAPTURE,
585
+ ins: fieldTrackedChange("insertion"),
586
+ moveFrom: fieldTrackedChange("moveFrom"),
544
587
  moveFromRangeEnd: CAPTURE,
545
588
  moveFromRangeStart: CAPTURE,
546
- moveTo: CAPTURE,
589
+ moveTo: fieldTrackedChange("moveTo"),
547
590
  moveToRangeEnd: CAPTURE,
548
591
  moveToRangeStart: CAPTURE,
549
592
  permEnd: CAPTURE,
@@ -819,7 +862,7 @@ const assembledFieldBeginsOf = (container) => {
819
862
  if (hasBegin) open.push(child);
820
863
  if (hasEnd && open.length > 0) {
821
864
  const closed = open.pop();
822
- if (closed !== void 0 && open.length === 0) assembled.add(closed);
865
+ if (closed !== void 0 && open.length === 0 && !findWordprocessingChild(beginFieldCharOf(closed), "fldData")) assembled.add(closed);
823
866
  }
824
867
  }
825
868
  return assembled;
@@ -830,17 +873,29 @@ const assembledFieldBeginsOf = (container) => {
830
873
  * recursion is this same walk and `inlineWrapperOf` reads what the element
831
874
  * adds.
832
875
  */
833
- const paragraphInlineWrapper = (element) => (child, { styles, theme, rels, media, previews, trackedContext, inScopeXmlns, contents }) => {
834
- contents.push(inlineWrapperOf(element, child, parseParagraphContents(child, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child))));
876
+ const paragraphInlineWrapper = (element) => (child, { styles, theme, rels, media, previews, trackedContext, inScopeXmlns, inlineScope, contents }) => {
877
+ contents.push(inlineWrapperOf(element, child, parseParagraphContents(child, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child), inlineScope)));
835
878
  };
836
- const PARAGRAPH_CONTENT_UNDECLARED = { AlternateContent: (child, { contents, styles, theme, rels, media, previews, trackedContext, inScopeXmlns }) => {
879
+ const PARAGRAPH_CONTENT_UNDECLARED = { AlternateContent: (child, { contents, styles, theme, rels, media, previews, trackedContext, inScopeXmlns, inlineScope }) => {
837
880
  const selectedBranch = selectAlternateContentBranch(child);
838
- if (selectedBranch) contents.push(...parseParagraphContents(selectedBranch, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child)));
881
+ if (selectedBranch) contents.push(...parseParagraphContents(selectedBranch, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child), inlineScope));
839
882
  } };
840
883
  const PARAGRAPH_CONTENT_UNDECLARED_NAMESPACES = { [OMML_NAMESPACE]: (child, { contents }) => {
841
884
  const equation = mathContentOf(child);
842
885
  if (equation !== void 0) contents.push(equation);
843
886
  } };
887
+ /** All revision wrappers retain typed review ownership while parsing their children. */
888
+ const trackedParagraphRevision = (type) => (child, { contents, styles, theme, rels, media, previews, inScopeXmlns, inlineScope }) => {
889
+ const resolutionJoins = parseResolutionJoins(child);
890
+ const parsedContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, type === "deletion" || type === "moveFrom" ? "deletion" : "default", inScopeXmlns, inlineScope);
891
+ pushTrackedChangeSegments({
892
+ contents,
893
+ type,
894
+ info: parseTrackedChangeInfo(child),
895
+ resolutionJoins,
896
+ parsedContent
897
+ });
898
+ };
844
899
  const PARAGRAPH_CONTENT_HANDLERS = {
845
900
  r: (child, { contents, styles, theme, rels, media, previews, trackedContext, inScopeXmlns, scan }) => {
846
901
  const runElement = trackedContext === "deletion" ? normalizeDeletionContentElement(child) : child;
@@ -977,8 +1032,9 @@ const PARAGRAPH_CONTENT_HANDLERS = {
977
1032
  contents.push(kept);
978
1033
  }
979
1034
  },
980
- hyperlink: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns }) => {
981
- contents.push(...parseHyperlinkParagraphContents(child, rels, styles, theme, media, inScopeXmlns, previews));
1035
+ hyperlink: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns, trackedContext }) => {
1036
+ const linkElement = trackedContext === "deletion" ? normalizeDeletionContentElement(child) : child;
1037
+ contents.push(...parseHyperlinkParagraphContents(linkElement, rels, styles, theme, media, inScopeXmlns, previews));
982
1038
  },
983
1039
  bookmarkStart: (child, { contents }) => {
984
1040
  contents.push(parseBookmarkStart(child));
@@ -1014,12 +1070,16 @@ const PARAGRAPH_CONTENT_HANDLERS = {
1014
1070
  customXmlMoveToRangeStart: CAPTURE,
1015
1071
  smartTagPr: SMART_TAG_PROPERTIES_OWNER,
1016
1072
  customXmlPr: CUSTOM_XML_PROPERTIES_OWNER,
1017
- sdt: (child, { contents, styles, theme, rels, media, previews, trackedContext, inScopeXmlns }) => {
1073
+ sdt: (child, { contents, styles, theme, rels, media, previews, trackedContext, inScopeXmlns, inlineScope }) => {
1074
+ if (inlineScope === "hyperlink") {
1075
+ contents.push(preserveLinkedSdt(child));
1076
+ return;
1077
+ }
1018
1078
  const sdtPr = findWordprocessingChild(child, "sdtPr");
1019
1079
  const sdtEndPr = findWordprocessingChild(child, "sdtEndPr");
1020
1080
  const sdtContentEl = findWordprocessingChild(child, "sdtContent");
1021
1081
  if (sdtContentEl) {
1022
- const sdtParsed = parseParagraphContents(sdtContentEl, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child));
1082
+ const sdtParsed = parseParagraphContents(sdtContentEl, styles, theme, null, rels, media, previews, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child), inlineScope);
1023
1083
  const properties = parseSdtProperties(sdtPr, sdtEndPr);
1024
1084
  const captured = captureSdtSiblingMarkers(child);
1025
1085
  if (captured.before.length > 0) properties.rawSdtChildrenBeforeContent = captured.before;
@@ -1031,50 +1091,10 @@ const PARAGRAPH_CONTENT_HANDLERS = {
1031
1091
  });
1032
1092
  }
1033
1093
  },
1034
- ins: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns }) => {
1035
- const insInfo = parseTrackedChangeInfo(child);
1036
- const insContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, "default", inScopeXmlns);
1037
- pushTrackedChangeSegments({
1038
- contents,
1039
- type: "insertion",
1040
- info: insInfo,
1041
- resolutionJoins: parseResolutionJoins(child),
1042
- parsedContent: insContent
1043
- });
1044
- },
1045
- del: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns }) => {
1046
- const delInfo = parseTrackedChangeInfo(child);
1047
- const delContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, "deletion", inScopeXmlns);
1048
- pushTrackedChangeSegments({
1049
- contents,
1050
- type: "deletion",
1051
- info: delInfo,
1052
- resolutionJoins: parseResolutionJoins(child),
1053
- parsedContent: delContent
1054
- });
1055
- },
1056
- moveFrom: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns }) => {
1057
- const moveFromInfo = parseTrackedChangeInfo(child);
1058
- const moveFromContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, "deletion", inScopeXmlns);
1059
- pushTrackedChangeSegments({
1060
- contents,
1061
- type: "moveFrom",
1062
- info: moveFromInfo,
1063
- resolutionJoins: parseResolutionJoins(child),
1064
- parsedContent: moveFromContent
1065
- });
1066
- },
1067
- moveTo: (child, { contents, styles, theme, rels, media, previews, inScopeXmlns }) => {
1068
- const moveToInfo = parseTrackedChangeInfo(child);
1069
- const moveToContent = parseParagraphContents(child, styles, theme, null, rels, media, previews, "default", inScopeXmlns);
1070
- pushTrackedChangeSegments({
1071
- contents,
1072
- type: "moveTo",
1073
- info: moveToInfo,
1074
- resolutionJoins: parseResolutionJoins(child),
1075
- parsedContent: moveToContent
1076
- });
1077
- },
1094
+ ins: trackedParagraphRevision("insertion"),
1095
+ del: trackedParagraphRevision("deletion"),
1096
+ moveFrom: trackedParagraphRevision("moveFrom"),
1097
+ moveTo: trackedParagraphRevision("moveTo"),
1078
1098
  smartTag: paragraphInlineWrapper("smartTag"),
1079
1099
  moveFromRangeStart: (child, { contents }) => {
1080
1100
  contents.push({
@@ -1120,7 +1140,7 @@ const PARAGRAPH_CONTENT_HANDLERS = {
1120
1140
  *
1121
1141
  * Returns the parsed content and any complex fields that span multiple runs
1122
1142
  */
1123
- function parseParagraphContents(paraElement, styles, theme, _numbering, rels, media, previews, trackedContext = "default", rootXmlns = {}) {
1143
+ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, media, previews, trackedContext = "default", rootXmlns = {}, inlineScope = "paragraph") {
1124
1144
  const contents = [];
1125
1145
  const inScopeXmlns = mergeXmlnsDeclarations(rootXmlns, paraElement);
1126
1146
  const scan = {
@@ -1154,6 +1174,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
1154
1174
  media,
1155
1175
  previews,
1156
1176
  trackedContext,
1177
+ inlineScope,
1157
1178
  inScopeXmlns,
1158
1179
  contents,
1159
1180
  scan
@@ -4,10 +4,27 @@ import { Fragment, Mark, Node, NodeType } from "prosemirror-model";
4
4
  import { Transaction } from "prosemirror-state";
5
5
  //#region src/docx/paragraphPropertySource.d.ts
6
6
  declare const paragraphPropertySourceEmissionFingerprint: unique symbol;
7
+ declare const paragraphPropertySourceIndentationBaseline: unique symbol;
8
+ declare const INDENTATION_FIELDS: readonly ["indentLeft", "indentRight", "indentFirstLine", "hangingIndent"];
7
9
  type ParagraphPropertySource = Readonly<{
8
10
  xml: string;
9
11
  [paragraphPropertySourceEmissionFingerprint]: string;
12
+ [paragraphPropertySourceIndentationBaseline]: Readonly<Pick<document_d_exports.ParagraphFormatting, (typeof INDENTATION_FIELDS)[number]>>;
10
13
  }>;
14
+ type IndentationFormatting = Pick<document_d_exports.ParagraphFormatting, (typeof INDENTATION_FIELDS)[number]>;
15
+ type CopyParagraphIndentationProjectionOptions = {
16
+ target: document_d_exports.Paragraph;
17
+ source: document_d_exports.Paragraph;
18
+ };
19
+ /** Retain effective-value provenance without copying a durable source identity. */
20
+ declare const copyParagraphIndentationProjection: ({ target, source }: CopyParagraphIndentationProjectionOptions) => void;
21
+ type AssignParagraphIndentationProjectionOptions = {
22
+ paragraph: document_d_exports.Paragraph;
23
+ authored: document_d_exports.ParagraphFormatting | undefined;
24
+ inherited: IndentationFormatting;
25
+ };
26
+ /** Keep the model's effective indentation without making inherited values authored. */
27
+ declare const assignParagraphIndentationProjection: ({ paragraph, authored, inherited }: AssignParagraphIndentationProjectionOptions) => void;
11
28
  declare const PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR = "_docxParagraphSourceToken";
12
29
  declare const PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR = "_docxParagraphSourceContract";
13
30
  /** Durable identity nested inside opaque collapsed-cell ProseMirror attrs; stripped on restore. */
@@ -30,6 +47,10 @@ declare const paragraphPropertySourceTokenMatchesContract: (token: unknown, cont
30
47
  declare const visitDocumentStoryParagraphs: (content: document_d_exports.Document["package"]["document"]["content"], visit: (paragraph: document_d_exports.Paragraph) => void) => void;
31
48
  declare const assignParagraphPropertySource: (paragraph: document_d_exports.Paragraph, xml: string) => void;
32
49
  declare const getParagraphPropertySource: (paragraph: document_d_exports.Paragraph) => ParagraphPropertySource | undefined;
50
+ /** Read authored indentation while preserving edits to the parsed effective values. */
51
+ declare const paragraphFormattingWithAuthoredIndentation: (paragraph: document_d_exports.Paragraph) => document_d_exports.ParagraphFormatting | undefined;
52
+ /** Equal effective values must not replay a capture that lacks newly authored indentation. */
53
+ declare const paragraphPropertySourceMatchesAuthoredIndentation: (paragraph: document_d_exports.Paragraph) => boolean;
33
54
  declare const paragraphPropertySourceMatchesEmission: (source: ParagraphPropertySource, emission: ModeledParagraphFormattingEmission) => boolean;
34
55
  /** Copy the captured `w:pPr` without claiming the source paragraph's durable identity. */
35
56
  declare const copyParagraphPropertyCapture: (target: document_d_exports.Paragraph, source: document_d_exports.Paragraph) => void;
@@ -150,4 +171,4 @@ declare const linkParagraphPropertySourceCandidate: (target: document_d_exports.
150
171
  declare const getParagraphPropertySourceCandidate: (paragraph: document_d_exports.Paragraph) => document_d_exports.Paragraph | undefined;
151
172
  declare const getParagraphPropertySourceTransferId: (paragraph: document_d_exports.Paragraph) => string | undefined;
152
173
  //#endregion
153
- export { DecodedTableCellParagraphSourcePayload, PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES, PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, ParagraphPropertySourceTransfer, ParagraphPropertySourceValidationCode, ParagraphPropertySourceValidationError, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR, TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS, TableCellParagraphPropertySourceBinding, TableCellParagraphSourcePayloadErrorClassification, assignDocumentParagraphPropertySourceContract, assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, cloneTableCellsWithParagraphPropertyCaptures, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, createProseParagraphWithPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getExplicitParagraphPropertySourceTransfers, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, inspectParagraphSourceIdentity, isParagraphPropertySourceToken, joinProseParagraphsWithRightPropertySource, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, markParagraphPropertySourceTransfers, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceMatchesEmission, paragraphPropertySourceTokenMatchesContract, proseParagraphAttrsWithoutPropertySource, recreateProseNodeWithDetachedParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource, transportTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings };
174
+ export { DecodedTableCellParagraphSourcePayload, PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES, PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, ParagraphPropertySourceTransfer, ParagraphPropertySourceValidationCode, ParagraphPropertySourceValidationError, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR, TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS, TableCellParagraphPropertySourceBinding, TableCellParagraphSourcePayloadErrorClassification, assignDocumentParagraphPropertySourceContract, assignParagraphIndentationProjection, assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, cloneTableCellsWithParagraphPropertyCaptures, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphIndentationProjection, copyParagraphPropertyCapture, copyParagraphPropertySource, createProseParagraphWithPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getExplicitParagraphPropertySourceTransfers, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, inspectParagraphSourceIdentity, isParagraphPropertySourceToken, joinProseParagraphsWithRightPropertySource, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, markParagraphPropertySourceTransfers, paragraphFormattingWithAuthoredIndentation, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceMatchesAuthoredIndentation, paragraphPropertySourceMatchesEmission, paragraphPropertySourceTokenMatchesContract, proseParagraphAttrsWithoutPropertySource, recreateProseNodeWithDetachedParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource, transportTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings };
@@ -1,12 +1,66 @@
1
1
  import { modelParagraphFormattingEmission } from "../internal/paragraphFormattingSerialization.js";
2
2
  import { canonicalJson } from "../utils/canonicalJson.js";
3
3
  import { copyHeaderFooterBaselineHandles, copyHeaderFooterPackageBaselines } from "./headerFooterVerbatim.js";
4
+ import { parseParagraphProperties } from "./paragraphProperties.js";
4
5
  import { visitDocxParagraphs } from "./paragraphTraversal.js";
6
+ import { parseXmlDocument } from "./xmlParser.js";
5
7
  import { TaggedError, panic } from "better-result";
6
8
  import { PluginKey } from "prosemirror-state";
7
9
  //#region src/docx/paragraphPropertySource.ts
8
10
  const paragraphPropertySourceEmissionFingerprint = Symbol("paragraphPropertySourceEmissionFingerprint");
11
+ const paragraphPropertySourceIndentationBaseline = Symbol("paragraphPropertySourceIndentationBaseline");
12
+ const INDENTATION_FIELDS = [
13
+ "indentLeft",
14
+ "indentRight",
15
+ "indentFirstLine",
16
+ "hangingIndent"
17
+ ];
9
18
  const paragraphPropertyCapture = Symbol("paragraphPropertyCapture");
19
+ const paragraphIndentationProjection = Symbol("paragraphIndentationProjection");
20
+ const indentationProjections = /* @__PURE__ */ new WeakMap();
21
+ const indentationProjectionFor = (paragraph) => {
22
+ const handle = Object.getOwnPropertyDescriptor(paragraph, paragraphIndentationProjection)?.value;
23
+ return typeof handle === "object" && handle !== null ? indentationProjections.get(handle) : void 0;
24
+ };
25
+ /** Retain effective-value provenance without copying a durable source identity. */
26
+ const copyParagraphIndentationProjection = ({ target, source }) => {
27
+ if (Object.hasOwn(target, paragraphIndentationProjection)) return;
28
+ const descriptor = Object.getOwnPropertyDescriptor(source, paragraphIndentationProjection);
29
+ if (descriptor && indentationProjectionFor(source)) Object.defineProperty(target, paragraphIndentationProjection, descriptor);
30
+ };
31
+ const indentationFields = (formatting) => ({
32
+ indentLeft: formatting?.indentLeft,
33
+ indentRight: formatting?.indentRight,
34
+ indentFirstLine: formatting?.indentFirstLine,
35
+ hangingIndent: formatting?.hangingIndent
36
+ });
37
+ /** Keep the model's effective indentation without making inherited values authored. */
38
+ const assignParagraphIndentationProjection = ({ paragraph, authored, inherited }) => {
39
+ const effective = {
40
+ ...inherited,
41
+ ...indentationFields(authored)
42
+ };
43
+ for (const key of INDENTATION_FIELDS) if (authored?.[key] === void 0) {
44
+ const value = inherited[key];
45
+ if (value === void 0) Reflect.deleteProperty(effective, key);
46
+ else Object.assign(effective, { [key]: value });
47
+ }
48
+ if (authored?.indentFirstLine !== void 0 && authored.hangingIndent === void 0) Reflect.deleteProperty(effective, "hangingIndent");
49
+ paragraph.formatting = {
50
+ ...authored,
51
+ ...effective
52
+ };
53
+ const handle = Object.freeze({});
54
+ indentationProjections.set(handle, Object.freeze({
55
+ effective: Object.freeze(indentationFields(paragraph.formatting)),
56
+ authored: Object.freeze(indentationFields(authored))
57
+ }));
58
+ Object.defineProperty(paragraph, paragraphIndentationProjection, {
59
+ value: handle,
60
+ enumerable: true,
61
+ configurable: true
62
+ });
63
+ };
10
64
  const paragraphPropertyCaptures = /* @__PURE__ */ new WeakMap();
11
65
  const paragraphSourceRecordKinds = /* @__PURE__ */ new WeakMap();
12
66
  const captureForParagraph = (paragraph) => {
@@ -27,6 +81,12 @@ const paragraphFormattingEmissionFingerprint = (emission) => canonicalJson(emiss
27
81
  const ownedParagraphPropertySource = (paragraph, xml) => {
28
82
  return Object.freeze({
29
83
  xml,
84
+ [paragraphPropertySourceIndentationBaseline]: Object.freeze({
85
+ indentLeft: paragraph.formatting?.indentLeft,
86
+ indentRight: paragraph.formatting?.indentRight,
87
+ indentFirstLine: paragraph.formatting?.indentFirstLine,
88
+ hangingIndent: paragraph.formatting?.hangingIndent
89
+ }),
30
90
  [paragraphPropertySourceEmissionFingerprint]: paragraphFormattingEmissionFingerprint(modelParagraphFormattingEmission(paragraph.formatting))
31
91
  });
32
92
  };
@@ -113,9 +173,54 @@ const assignParagraphPropertySource = (paragraph, xml) => {
113
173
  });
114
174
  };
115
175
  const getParagraphPropertySource = (paragraph) => captureForParagraph(paragraph)?.source;
176
+ const directFormattingBySource = /* @__PURE__ */ new WeakMap();
177
+ /** Read authored indentation while preserving edits to the parsed effective values. */
178
+ const paragraphFormattingWithAuthoredIndentation = (paragraph) => {
179
+ const source = getParagraphPropertySource(paragraph);
180
+ const projection = indentationProjectionFor(paragraph);
181
+ if (source === void 0 && projection === void 0) return paragraph.formatting;
182
+ let direct = source === void 0 ? void 0 : directFormattingBySource.get(source);
183
+ if (source !== void 0 && direct === void 0) {
184
+ direct = { formatting: parseParagraphProperties(parseXmlDocument(source.xml), null) };
185
+ directFormattingBySource.set(source, direct);
186
+ }
187
+ const result = { ...paragraph.formatting };
188
+ const baseline = projection?.effective ?? source?.[paragraphPropertySourceIndentationBaseline];
189
+ if (baseline === void 0) return paragraph.formatting;
190
+ const authored = projection?.authored ?? direct?.formatting;
191
+ const firstLineChanged = paragraph.formatting?.indentFirstLine !== baseline.indentFirstLine || paragraph.formatting?.hangingIndent !== baseline.hangingIndent;
192
+ for (const key of INDENTATION_FIELDS) {
193
+ if ((key === "indentFirstLine" || key === "hangingIndent") && firstLineChanged) continue;
194
+ if (paragraph.formatting?.[key] !== baseline[key]) continue;
195
+ Reflect.deleteProperty(result, key);
196
+ const value = authored?.[key];
197
+ if (value !== void 0) Object.assign(result, { [key]: value });
198
+ }
199
+ return Object.keys(result).length === 0 ? void 0 : result;
200
+ };
201
+ /** Equal effective values must not replay a capture that lacks newly authored indentation. */
202
+ const paragraphPropertySourceMatchesAuthoredIndentation = (paragraph) => {
203
+ const source = getParagraphPropertySource(paragraph);
204
+ if (source === void 0) return true;
205
+ const authored = paragraphFormattingWithAuthoredIndentation(paragraph);
206
+ const original = directFormattingBySource.get(source)?.formatting;
207
+ const indentationEmission = (formatting) => {
208
+ const indentation = {};
209
+ for (const key of INDENTATION_FIELDS) {
210
+ const value = formatting?.[key];
211
+ if (value !== void 0) Object.assign(indentation, { [key]: value });
212
+ }
213
+ return modelParagraphFormattingEmission(indentation);
214
+ };
215
+ return canonicalJson(indentationEmission(authored)) === canonicalJson(indentationEmission(original));
216
+ };
116
217
  const paragraphPropertySourceMatchesEmission = (source, emission) => source[paragraphPropertySourceEmissionFingerprint] === paragraphFormattingEmissionFingerprint(emission);
117
218
  /** Copy the captured `w:pPr` without claiming the source paragraph's durable identity. */
118
219
  const copyParagraphPropertyCapture = (target, source) => {
220
+ copyParagraphIndentationProjection({
221
+ target,
222
+ source
223
+ });
119
224
  const capture = captureForParagraph(source);
120
225
  if (capture) attachParagraphCapture(target, capture);
121
226
  const kind = paragraphPropertySourceTokens.has(source) || paragraphSourceRecordKinds.get(source) === "durable" ? "durable" : "capture-only";
@@ -215,6 +320,7 @@ const cloneParagraphWithoutPropertySource = (paragraph, overrides) => {
215
320
  ...overrides
216
321
  };
217
322
  Reflect.deleteProperty(cloned, paragraphPropertyCapture);
323
+ Reflect.deleteProperty(cloned, paragraphIndentationProjection);
218
324
  return cloned;
219
325
  };
220
326
  const paragraphsIn = (document) => {
@@ -766,4 +872,4 @@ const linkParagraphPropertySourceCandidate = (target, source) => {
766
872
  const getParagraphPropertySourceCandidate = (paragraph) => paragraphPropertySourceCandidates.get(paragraph);
767
873
  const getParagraphPropertySourceTransferId = (paragraph) => paragraphPropertySourceTransferIds.get(paragraph);
768
874
  //#endregion
769
- export { PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES, PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, ParagraphPropertySourceValidationError, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR, TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS, assignDocumentParagraphPropertySourceContract, assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, cloneTableCellsWithParagraphPropertyCaptures, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, createProseParagraphWithPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getExplicitParagraphPropertySourceTransfers, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, inspectParagraphSourceIdentity, isParagraphPropertySourceToken, joinProseParagraphsWithRightPropertySource, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, markParagraphPropertySourceTransfers, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceMatchesEmission, paragraphPropertySourceTokenMatchesContract, proseParagraphAttrsWithoutPropertySource, recreateProseNodeWithDetachedParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource, transportTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings };
875
+ export { PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES, PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, ParagraphPropertySourceValidationError, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR, TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS, assignDocumentParagraphPropertySourceContract, assignParagraphIndentationProjection, assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, cloneTableCellsWithParagraphPropertyCaptures, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphIndentationProjection, copyParagraphPropertyCapture, copyParagraphPropertySource, createProseParagraphWithPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getExplicitParagraphPropertySourceTransfers, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, inspectParagraphSourceIdentity, isParagraphPropertySourceToken, joinProseParagraphsWithRightPropertySource, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, markParagraphPropertySourceTransfers, paragraphFormattingWithAuthoredIndentation, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceMatchesAuthoredIndentation, paragraphPropertySourceMatchesEmission, paragraphPropertySourceTokenMatchesContract, proseParagraphAttrsWithoutPropertySource, recreateProseNodeWithDetachedParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource, transportTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings };
@@ -14,7 +14,7 @@ const VISIBLE_TEXT_CHILDREN = /* @__PURE__ */ new Map([["ruby", ["rubyBase"]]]);
14
14
  const MAX_PRESERVED_TEXT_LENGTH = 1e5;
15
15
  const collectTextContent = (element, into) => {
16
16
  for (const child of getChildElements(element)) {
17
- if (getLocalName(child.name) === "t") {
17
+ if (getLocalName(child.name) === "t" || getLocalName(child.name) === "delText") {
18
18
  into.push(getTextContent(child));
19
19
  continue;
20
20
  }
@@ -67,6 +67,7 @@ const VISIBLE_TEXT_INLINE_CHILDREN = /* @__PURE__ */ new Set([
67
67
  "customXml",
68
68
  "fldSimple",
69
69
  "hyperlink",
70
+ "sdt",
70
71
  "smartTag"
71
72
  ]);
72
73
  /**
@@ -1,6 +1,30 @@
1
1
  import { getAttribute, getChildElements, parseXmlDocument } from "./xmlParser.js";
2
+ import { COMMENT_PART_RELATIONSHIPS } from "@stll/docx-core/model";
2
3
  //#region src/docx/relsParser.ts
3
4
  /**
5
+ * Relationship Parser
6
+ *
7
+ * Parses .rels files from DOCX packages to map relationship IDs (rId)
8
+ * to their targets (images, hyperlinks, headers, footers, etc.).
9
+ *
10
+ * .rels files are XML with structure:
11
+ * <Relationships xmlns="...">
12
+ * <Relationship Id="rId1" Type="..." Target="..." TargetMode="External|Internal"/>
13
+ * </Relationships>
14
+ *
15
+ * Key relationship types:
16
+ * - image: Embedded images (word/media/*)
17
+ * - hyperlink: External URLs (TargetMode="External")
18
+ * - header: Header XML files
19
+ * - footer: Footer XML files
20
+ * - footnotes: Footnotes XML
21
+ * - endnotes: Endnotes XML
22
+ * - styles: styles.xml
23
+ * - numbering: numbering.xml
24
+ * - fontTable: fontTable.xml
25
+ * - theme: theme/theme1.xml
26
+ */
27
+ /**
4
28
  * Relationship type constants for common types
5
29
  */
6
30
  const RELATIONSHIP_TYPES = {
@@ -24,8 +48,8 @@ const RELATIONSHIP_TYPES = {
24
48
  extendedProperties: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
25
49
  customProperties: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties",
26
50
  customXml: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml",
27
- comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
28
- commentsExtended: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended"
51
+ comments: COMMENT_PART_RELATIONSHIPS.comments.type,
52
+ commentsExtended: COMMENT_PART_RELATIONSHIPS.commentsExtended.type
29
53
  };
30
54
  const STRICT_OFFICE_RELATIONSHIP_PREFIX = "http://purl.oclc.org/ooxml/officeDocument/relationships/";
31
55
  const TRANSITIONAL_OFFICE_RELATIONSHIP_PREFIX = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/";
@@ -25,7 +25,7 @@ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./r
25
25
  import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
26
26
  import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
27
27
  import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions, spliceXml } from "./selectiveXmlPatch.js";
28
- import { planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
28
+ import { hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
29
29
  import { serializeDocument } from "./serializer/documentSerializer.js";
30
30
  import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
31
31
  import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
@@ -202,7 +202,8 @@ async function serializeCommentsToZip(doc, zip, compressionLevel) {
202
202
  const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
203
203
  const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
204
204
  if (comments.length === 0) {
205
- if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
205
+ const ownsCommentsPart = hasOwnedCommentsPart(doc);
206
+ if (sourceCommentsXml === void 0 ? !ownsCommentsPart : !hasCommentEntries(sourceCommentsXml)) return;
206
207
  }
207
208
  const plan = planCommentParts(comments);
208
209
  const commentsXml = serializeComments(plan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
@@ -1224,8 +1225,9 @@ async function ensureCommentsContentType(zip, compressionLevel) {
1224
1225
  const ctFile = zip.file("[Content_Types].xml");
1225
1226
  if (!ctFile) return;
1226
1227
  let ctXml = await ctFile.async("text");
1227
- if (ctXml.includes("/word/comments.xml")) return;
1228
- ctXml = ctXml.replace("</Types>", `<Override PartName="/word/comments.xml" ContentType="${COMMENTS_CONTENT_TYPE}"/></Types>`);
1228
+ if (ctXml.toLowerCase().includes("/word/comments.xml")) return;
1229
+ const commentsPartPath = findZipEntryCaseInsensitive(zip, "word/comments.xml")?.name ?? "word/comments.xml";
1230
+ ctXml = ctXml.replace("</Types>", `<Override PartName="/${commentsPartPath}" ContentType="${COMMENTS_CONTENT_TYPE}"/></Types>`);
1229
1231
  writeZipPart({
1230
1232
  zip,
1231
1233
  path: "[Content_Types].xml",
@@ -1245,9 +1247,10 @@ async function ensureCommentsRelationship(zip, compressionLevel) {
1245
1247
  const relsFile = zip.file(relsPath);
1246
1248
  if (!relsFile) return;
1247
1249
  let relsXml = await relsFile.async("text");
1248
- if (relsXml.includes("comments.xml")) return;
1250
+ if (relsXml.toLowerCase().includes("comments.xml")) return;
1251
+ const commentsPartPath = findZipEntryCaseInsensitive(zip, "word/comments.xml")?.name ?? "word/comments.xml";
1249
1252
  const newRId = `rId${findMaxRId(relsXml) + 1}`;
1250
- relsXml = relsXml.replace("</Relationships>", `<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.comments}" Target="comments.xml"/></Relationships>`);
1253
+ relsXml = relsXml.replace("</Relationships>", `<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.comments}" Target="${commentsPartPath.slice(5)}"/></Relationships>`);
1251
1254
  writeZipPart({
1252
1255
  zip,
1253
1256
  path: relsPath,