@stll/folio-core 0.45.0 → 0.46.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 (207) hide show
  1. package/dist/ai-edits/apply.js +59 -19
  2. package/dist/ai-edits/headless.js +1 -1
  3. package/dist/compare/compare.js +2 -5
  4. package/dist/compare/scenario.js +2 -5
  5. package/dist/compare/types.d.ts +9 -1
  6. package/dist/compare/types.js +12 -1
  7. package/dist/compat/eigenpal.d.ts +2 -1
  8. package/dist/compat/eigenpal.js +2 -1
  9. package/dist/content-controls/checkboxDisplay.d.ts +11 -0
  10. package/dist/content-controls/checkboxDisplay.js +40 -0
  11. package/dist/content-controls/findContentControls.js +3 -1
  12. package/dist/content-controls/mutateContentControls.js +17 -2
  13. package/dist/controller/contentControlWidgetController.d.ts +1 -0
  14. package/dist/controller/contentControlWidgetController.js +3 -0
  15. package/dist/controller/hiddenEditorApi.js +3 -1
  16. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  17. package/dist/controller/hiddenEditorManager.js +6 -3
  18. package/dist/display-list/build/buildDisplayList.js +20 -1
  19. package/dist/display-list/build/imagePrimitives.d.ts +1 -1
  20. package/dist/display-list/build/imagePrimitives.js +3 -1
  21. package/dist/display-list/build/paragraphPrimitives.js +21 -2
  22. package/dist/display-list/dom/renderDisplayListToDom.js +10 -2
  23. package/dist/display-list/types.d.ts +17 -0
  24. package/dist/docx/attributeRemainder.d.ts +43 -0
  25. package/dist/docx/attributeRemainder.js +65 -0
  26. package/dist/docx/blockContentParser.d.ts +1 -1
  27. package/dist/docx/blockContentParser.js +87 -63
  28. package/dist/docx/blockPlainText.d.ts +3 -4
  29. package/dist/docx/blockPlainText.js +1 -0
  30. package/dist/docx/bookmarkPlacement.js +2 -0
  31. package/dist/docx/commentAnchorIndex.d.ts +30 -0
  32. package/dist/docx/commentAnchorIndex.js +50 -0
  33. package/dist/docx/commentParser.d.ts +1 -1
  34. package/dist/docx/commentParser.js +54 -37
  35. package/dist/docx/commentRangeIntegrity.d.ts +16 -1
  36. package/dist/docx/commentRangeIntegrity.js +42 -1
  37. package/dist/docx/commentRangeJoin.d.ts +9 -0
  38. package/dist/docx/commentRangeJoin.js +33 -0
  39. package/dist/docx/commentReferenceCompletion.d.ts +9 -0
  40. package/dist/docx/commentReferenceCompletion.js +47 -0
  41. package/dist/docx/commentReferenceNormalization.js +1 -0
  42. package/dist/docx/commentReplyMarkers.js +11 -19
  43. package/dist/docx/compatibility.js +1 -0
  44. package/dist/docx/containerChildren.d.ts +111 -0
  45. package/dist/docx/containerChildren.gen.d.ts +28 -0
  46. package/dist/docx/containerChildren.gen.js +246 -0
  47. package/dist/docx/containerChildren.js +103 -0
  48. package/dist/docx/documentParser.d.ts +1 -1
  49. package/dist/docx/documentParser.js +3 -3
  50. package/dist/docx/ensureParaIds.js +17 -7
  51. package/dist/docx/fieldParser.d.ts +1 -1
  52. package/dist/docx/fieldParser.js +4 -4
  53. package/dist/docx/fieldState.d.ts +26 -0
  54. package/dist/docx/fieldState.js +53 -0
  55. package/dist/docx/footnoteParser.d.ts +1 -1
  56. package/dist/docx/graphicFrameLocks.d.ts +16 -4
  57. package/dist/docx/graphicFrameLocks.js +19 -6
  58. package/dist/docx/headerFooterParser.js +1 -1
  59. package/dist/docx/headerFooterReferenceNormalization.js +1 -0
  60. package/dist/docx/hyperlinkParser.d.ts +27 -8
  61. package/dist/docx/hyperlinkParser.js +76 -18
  62. package/dist/docx/imageParser.js +33 -8
  63. package/dist/docx/inlineWrapperContent.d.ts +85 -0
  64. package/dist/docx/inlineWrapperContent.js +84 -0
  65. package/dist/docx/normalizeBaseDirection.js +10 -1
  66. package/dist/docx/paraIdAttribute.d.ts +21 -0
  67. package/dist/docx/paraIdAttribute.js +64 -0
  68. package/dist/docx/paragraphParser.d.ts +1 -1
  69. package/dist/docx/paragraphParser.js +277 -168
  70. package/dist/docx/paragraphPropertySource.js +5 -1
  71. package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
  72. package/dist/docx/paragraphTextBoxEnrichment.js +6 -52
  73. package/dist/docx/paragraphTraversal.js +7 -4
  74. package/dist/docx/parseWarningMessage.js +2 -1
  75. package/dist/docx/preservedRunContent.d.ts +32 -0
  76. package/dist/docx/preservedRunContent.js +86 -0
  77. package/dist/docx/renderedPageBreakNormalization.js +2 -4
  78. package/dist/docx/rezip.js +74 -18
  79. package/dist/docx/runParser.d.ts +1 -1
  80. package/dist/docx/runParser.js +16 -12
  81. package/dist/docx/sectionParser.js +8 -1
  82. package/dist/docx/selectiveXmlPatch.d.ts +46 -2
  83. package/dist/docx/selectiveXmlPatch.js +86 -39
  84. package/dist/docx/serializer/borderSerializer.js +2 -2
  85. package/dist/docx/serializer/commentSerializer.js +5 -7
  86. package/dist/docx/serializer/documentSerializer.js +8 -11
  87. package/dist/docx/serializer/headerFooterSerializer.js +7 -9
  88. package/dist/docx/serializer/noteSerializer.js +8 -8
  89. package/dist/docx/serializer/paragraphSerializer.js +76 -56
  90. package/dist/docx/serializer/runSerializer.js +25 -16
  91. package/dist/docx/serializer/sectionPropertiesSerializer.js +8 -4
  92. package/dist/docx/serializer/tableSerializer.js +18 -8
  93. package/dist/docx/server/createBilingualDocument.js +3 -1
  94. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  95. package/dist/docx/server/materializeYjsDocx.js +9 -1
  96. package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
  97. package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
  98. package/dist/docx/shapeParser.js +5 -5
  99. package/dist/docx/tableParser.d.ts +1 -1
  100. package/dist/docx/tableParser.js +206 -85
  101. package/dist/docx/textBoxParser.d.ts +25 -2
  102. package/dist/docx/textBoxParser.js +52 -2
  103. package/dist/docx/unzip.js +5 -4
  104. package/dist/docx/vmlImageParser.d.ts +17 -1
  105. package/dist/docx/vmlImageParser.js +17 -1
  106. package/dist/docx/xmlEncoding.d.ts +5 -0
  107. package/dist/docx/xmlEncoding.js +12 -0
  108. package/dist/docx/xmlParser.d.ts +10 -1
  109. package/dist/docx/xmlParser.js +14 -1
  110. package/dist/headless-layout.js +10 -1
  111. package/dist/index.d.ts +2 -1
  112. package/dist/index.js +2 -1
  113. package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
  114. package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
  115. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  116. package/dist/layout-engine/measure/tableCellFloating.js +2 -0
  117. package/dist/layout-engine/types.d.ts +23 -1
  118. package/dist/layout-painter/renderImage.d.ts +5 -1
  119. package/dist/layout-painter/renderImage.js +10 -3
  120. package/dist/layout-painter/renderPage.d.ts +2 -0
  121. package/dist/layout-painter/renderPage.js +4 -0
  122. package/dist/layout-painter/renderParagraph.d.ts +13 -1
  123. package/dist/layout-painter/renderParagraph.js +22 -3
  124. package/dist/managers/autoSaveCodec.d.ts +9 -1
  125. package/dist/managers/autoSaveCodec.js +12 -4
  126. package/dist/markdown/renderBlock.js +1 -0
  127. package/dist/markdown/renderRuns.d.ts +1 -1
  128. package/dist/markdown/renderRuns.js +28 -5
  129. package/dist/markdown/renderTable.js +22 -5
  130. package/dist/markdown/trailers.js +4 -1
  131. package/dist/pdf/images.js +142 -6
  132. package/dist/prosemirror/attrs/index.d.ts +14 -3
  133. package/dist/prosemirror/attrs/index.js +155 -2
  134. package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
  135. package/dist/prosemirror/authoredTransformAttrs.js +64 -0
  136. package/dist/prosemirror/commands/contentControls.js +10 -2
  137. package/dist/prosemirror/commands/image.d.ts +9 -2
  138. package/dist/prosemirror/commands/image.js +42 -28
  139. package/dist/prosemirror/commands/pageBreak.js +1 -1
  140. package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
  141. package/dist/prosemirror/commentReferenceAttrs.js +33 -0
  142. package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
  143. package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
  144. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  145. package/dist/prosemirror/conversion/fromProseDoc.js +269 -70
  146. package/dist/prosemirror/conversion/index.d.ts +2 -2
  147. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
  148. package/dist/prosemirror/conversion/toProseDoc.js +468 -308
  149. package/dist/prosemirror/extensions/StarterKit.js +8 -58
  150. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  151. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
  152. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
  153. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
  154. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
  156. package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
  157. package/dist/prosemirror/extensions/markRegistry.js +75 -0
  158. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
  159. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
  160. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  161. package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
  162. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
  163. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
  164. package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
  165. package/dist/prosemirror/extensions/nodes/ImageExtension.js +16 -0
  166. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
  167. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
  168. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
  169. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
  170. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +3 -0
  171. package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
  172. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  173. package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
  174. package/dist/prosemirror/inlineWrapperStack.js +75 -0
  175. package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
  176. package/dist/prosemirror/pageBreakRunProjection.js +19 -9
  177. package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
  178. package/dist/prosemirror/replacedAnnotations.js +165 -0
  179. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
  180. package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
  181. package/dist/prosemirror/schema/index.d.ts +3 -3
  182. package/dist/prosemirror/schema/marks.d.ts +25 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +90 -3
  184. package/dist/prosemirror/schema/nodes.js +16 -1
  185. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
  186. package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
  187. package/dist/prosemirror/validation.js +16 -2
  188. package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
  189. package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
  190. package/dist/prosemirror/zeroWidthAnchors.js +2 -0
  191. package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
  192. package/dist/render-dom/commentAnchorAttributes.js +54 -0
  193. package/dist/server.d.ts +3 -1
  194. package/dist/server.js +3 -1
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/clipboard.d.ts +9 -1
  197. package/dist/utils/clipboard.js +27 -1
  198. package/dist/utils/findReplace.js +1 -1
  199. package/dist/utils/imageLuminance.d.ts +15 -0
  200. package/dist/utils/imageLuminance.js +32 -0
  201. package/dist/utils/mergeDocumentContent.js +7 -17
  202. package/dist/utils/replaceText.js +1 -0
  203. package/package.json +2 -2
  204. package/dist/docx/blockRangeMarkers.d.ts +0 -36
  205. package/dist/docx/blockRangeMarkers.js +0 -59
  206. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
  207. package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
@@ -1,4 +1,6 @@
1
+ import { rangedCommentIds } from "../../docx/commentAnchorIndex.js";
1
2
  import { resolveColorValueToHex } from "../../docx/drawingUtils.js";
3
+ import { isInlineSdtContent, isTrackedChangeWrapperChild } from "../../docx/inlineWrapperContent.js";
2
4
  import { isNumberingReference } from "../../docx/numberingReference.js";
3
5
  import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, createProseParagraphWithPropertySource, getDocumentParagraphPropertySourceContract, recreateProseNodeWithParagraphPropertySource, transportTableCellsWithParagraphPropertySources } from "../../docx/paragraphPropertySource.js";
4
6
  import { isBaselineVertAlign } from "../../docx/runParser.js";
@@ -11,8 +13,11 @@ import { mergeParagraphFormatting, mergeParagraphTabStops } from "../../utils/pa
11
13
  import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
12
14
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
13
15
  import { emuToPixels, emuToStrokePixels } from "../../utils/units.js";
16
+ import { expectInlineWrapperMarkAttrs } from "../attrs/index.js";
17
+ import { authoredTransformAttrs } from "../authoredTransformAttrs.js";
14
18
  import { setAutospacingBaseValue } from "../autospacingBase.js";
15
19
  import { textFormattingToMarks } from "../extensions/marks/markUtils.js";
20
+ import { inlineWrapperLayer } from "../inlineWrapperStack.js";
16
21
  import { listRenderingAttrPatch } from "../listRenderingAttrs.js";
17
22
  import { stampNumberedRefFieldBaselines } from "../numberedRefFields.js";
18
23
  import { pageBreakRunParagraphProjectionDispositionForFeatures } from "../pageBreakRunProjection.js";
@@ -21,17 +26,23 @@ import { styleResolvedParagraphFormatting } from "../paragraphFormattingProvenan
21
26
  import { lineSpacingProvenanceFromSpacing } from "../paragraphSpacing.js";
22
27
  import { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, resolveParagraphBodyRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting } from "../runStyleFormatting.js";
23
28
  import { schema } from "../schema/index.js";
29
+ import { PRESERVED_XML_LEVELS } from "../schema/nodes.js";
24
30
  import { cascadeStyleTextFormatting } from "../styles/styleToggleCascade.js";
25
31
  import { canCarryTrackedRunMark, trackedRunInlineAtomDisposition } from "../trackedRunInlineAtoms.js";
26
32
  import { assertValidProseMirrorDocument } from "../validation.js";
27
33
  import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
28
34
  import { sdtAttrsFromProperties } from "./sdtAttrs.js";
29
- import { TaggedError, panic } from "better-result";
30
- import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
35
+ import { panic } from "better-result";
36
+ import { DRAWING_RAW_XML_MODES, PARSE_WARNING_CODES } from "@stll/docx-core/model";
31
37
  //#region src/prosemirror/conversion/toProseDoc.ts
32
- /** DOCX content that cannot be preserved by the editable ProseMirror model. */
33
- var UnsupportedDocxToProseMirrorConversionError = class extends TaggedError("UnsupportedDocxToProseMirrorConversionError") {};
34
38
  const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
39
+ const noPageBreakProjectionWarning = () => {};
40
+ const pageBreakProjectionWarn = (warn) => warn === void 0 ? noPageBreakProjectionWarning : (detail) => {
41
+ warn({
42
+ code: PARSE_WARNING_CODES.pageBreakProjectionApproximated,
43
+ detail
44
+ });
45
+ };
35
46
  /**
36
47
  * Build a `nextTextBoxGroupId()` generator salted with a random per-load
37
48
  * nonce, so minted text-box anchor ids (`<salt>:<group>:<index>`) are unique
@@ -89,6 +100,7 @@ const collectPairedBookmarkIds = (blocks) => {
89
100
  case "run":
90
101
  visitRun(child);
91
102
  break;
103
+ case "preservedInline": break;
92
104
  default: panic(`Unsupported hyperlink child: ${JSON.stringify(child)}`);
93
105
  }
94
106
  };
@@ -108,7 +120,7 @@ const collectPairedBookmarkIds = (blocks) => {
108
120
  return;
109
121
  case "simpleField":
110
122
  for (const child of content.content) if (child.type === "hyperlink") visitHyperlink(child);
111
- else visitRun(child);
123
+ else if (child.type === "run") visitRun(child);
112
124
  return;
113
125
  case "complexField":
114
126
  for (const run of [...content.fieldCode ?? [], ...content.fieldResult]) visitRun(run);
@@ -118,7 +130,7 @@ const collectPairedBookmarkIds = (blocks) => {
118
130
  case "deletion":
119
131
  case "moveFrom":
120
132
  case "moveTo":
121
- case "bidiWrapper":
133
+ case "inlineWrapper":
122
134
  for (const child of content.content) visitParagraphContent(child);
123
135
  return;
124
136
  case "commentRangeStart":
@@ -128,7 +140,8 @@ const collectPairedBookmarkIds = (blocks) => {
128
140
  case "moveFromRangeEnd":
129
141
  case "moveToRangeStart":
130
142
  case "moveToRangeEnd":
131
- case "mathEquation": return;
143
+ case "mathEquation":
144
+ case "preservedInline": return;
132
145
  default: panic(`Unsupported paragraph content: ${JSON.stringify(content)}`);
133
146
  }
134
147
  };
@@ -143,6 +156,7 @@ const collectPairedBookmarkIds = (blocks) => {
143
156
  case "blockSdt":
144
157
  visitBlocks(block.content);
145
158
  break;
159
+ case "preservedBlock": break;
146
160
  default: panic(`Unsupported block content: ${JSON.stringify(block)}`);
147
161
  }
148
162
  };
@@ -166,7 +180,10 @@ function toProseDoc(document, options) {
166
180
  nextTextBoxGroupId,
167
181
  nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator(),
168
182
  pairedBookmarkIds: collectPairedBookmarkIds(paragraphs),
169
- pageBreakRunSourceDescendants: buildPageBreakRunSourceDescendantIndex(paragraphs)
183
+ pageBreakRunSourceDescendants: buildPageBreakRunSourceDescendantIndex(paragraphs),
184
+ storyRangedCommentIds: rangedCommentIds(paragraphs),
185
+ openCommentIds: /* @__PURE__ */ new Set(),
186
+ warnPageBreakProjection: pageBreakProjectionWarn(options?.warn)
170
187
  };
171
188
  const convertBodyBlocks = (blocks) => {
172
189
  const out = [];
@@ -183,6 +200,9 @@ function toProseDoc(document, options) {
183
200
  case "blockSdt":
184
201
  out.push(convertBlockSdt(block, convertBodyBlocks));
185
202
  break;
203
+ case "preservedBlock":
204
+ out.push(convertPreservedBlock(block));
205
+ break;
186
206
  default: panic(`Unsupported block content: ${JSON.stringify(block)}`);
187
207
  }
188
208
  return out;
@@ -200,6 +220,30 @@ function toProseDoc(document, options) {
200
220
  return pmDoc;
201
221
  }
202
222
  /**
223
+ * Carry a block folio does not model into the editor as a zero-width node.
224
+ *
225
+ * The node's place in the document is the whole of its position, so it needs
226
+ * no index and nothing has to keep one honest as the blocks around it change.
227
+ */
228
+ function convertPreservedBlock(block) {
229
+ return schema.node("preservedBlock", { xml: block.xml });
230
+ }
231
+ /**
232
+ * Carry an inline child folio does not model into the editor as an opaque atom.
233
+ *
234
+ * The same atom the run level uses, tagged with the level it came from: the
235
+ * markup is a paragraph child and the save path must not put it back inside a
236
+ * `w:r`. It carries whatever marks surround it, so a capture inside a
237
+ * `w:ins` keeps the insertion and is accepted or rejected with it.
238
+ */
239
+ function preservedInlineNode(content) {
240
+ return schema.node("preservedXml", {
241
+ xml: content.xml,
242
+ text: content.text,
243
+ level: PRESERVED_XML_LEVELS.inline
244
+ });
245
+ }
246
+ /**
203
247
  * Convert a `BlockSdt` model node into a `blockSdt` PM node, recursively
204
248
  * converting its children with the caller-supplied block converter. Pass
205
249
  * `rawPropertiesXml` / `rawEndPropertiesXml` through as attrs so the
@@ -236,26 +280,27 @@ function convertBlockSdt(blockSdt, convertBlocks) {
236
280
  * Resolves style-based text formatting and passes it to runs so that
237
281
  * paragraph styles (like Heading1) apply their font size, color, etc.
238
282
  */
239
- function convertParagraph(paragraph, styleResolver, context, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
240
- const { nextHyperlinkInstanceIndex, pairedBookmarkIds, pageBreakRunSourceDescendants } = context;
283
+ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
284
+ const { nextHyperlinkInstanceIndex, pairedBookmarkIds, pageBreakRunSourceDescendants, storyRangedCommentIds, openCommentIds } = context;
241
285
  let pageBreakRunOwnerId = 0;
242
286
  const nextPageBreakRunOwnerId = () => pageBreakRunOwnerId++;
243
287
  const { attrs, effectiveFrame } = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
244
- assertParagraphPageBreakCanBeProjected({
288
+ reportParagraphPageBreakProjection({
245
289
  paragraph,
246
290
  attrs,
247
291
  effectiveFrame,
248
- sourceDescendants: pageBreakRunSourceDescendants
292
+ sourceDescendants: pageBreakRunSourceDescendants,
293
+ warn: context.warnPageBreakProjection
249
294
  });
250
295
  const isTocParagraph = attrs._tableOfContentsLevel !== void 0;
251
296
  const inlineNodes = [];
252
297
  let inlineOffset = 0;
253
298
  let bookmarksArr;
254
299
  let emptyHyperlinks;
255
- const commentIds = activeCommentIds ?? /* @__PURE__ */ new Set();
300
+ let wrapperStack = [];
256
301
  const emitInlineNodes = (nodes) => {
257
302
  if (nodes.length === 0) return;
258
- const markedNodes = applyCommentMarks(nodes, commentIds);
303
+ const markedNodes = applyCommentMarks(withInlineWrapperMark(nodes, wrapperStack), openCommentIds);
259
304
  inlineNodes.push(...markedNodes);
260
305
  for (const node of markedNodes) inlineOffset += node.nodeSize;
261
306
  };
@@ -321,97 +366,104 @@ function convertParagraph(paragraph, styleResolver, context, activeCommentIds, e
321
366
  };
322
367
  };
323
368
  const emitTrackedChange = (change, markType, moveKind) => {
324
- emitInlineNodes(convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
369
+ emitInlineNodes(convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors, wrapperStack));
325
370
  };
326
- for (const content of withoutBidiWrappers(paragraph.content)) switch (content.type) {
327
- case "commentRangeStart":
328
- commentIds.add(content.id);
329
- break;
330
- case "commentRangeEnd":
331
- commentIds.delete(content.id);
332
- break;
333
- case "commentReference":
334
- anchorPointComment(inlineNodes, content.id);
335
- break;
336
- case "run":
337
- emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
338
- break;
339
- case "hyperlink": {
340
- const linkNodes = convertHyperlink(content, {
341
- getInheritedRunFormatting,
342
- styleResolver,
343
- hyperlinkIndex: nextHyperlinkInstanceIndex(),
344
- textBoxAnchors,
345
- nextPageBreakRunOwnerId
346
- });
347
- if (linkNodes.length === 0) {
348
- emptyHyperlinks ??= [];
349
- emptyHyperlinks.push({
350
- offset: inlineOffset,
351
- ...content.href !== void 0 ? { href: content.href } : {},
352
- ...content.anchor !== void 0 ? { anchor: content.anchor } : {},
353
- ...content.tooltip !== void 0 ? { tooltip: content.tooltip } : {},
354
- ...content.rId !== void 0 ? { rId: content.rId } : {}
371
+ for (const { content, stack } of withInlineWrapperStacks(paragraph.content)) {
372
+ wrapperStack = stack;
373
+ switch (content.type) {
374
+ case "commentRangeStart":
375
+ openCommentIds.add(content.id);
376
+ break;
377
+ case "commentRangeEnd":
378
+ openCommentIds.delete(content.id);
379
+ break;
380
+ case "commentReference":
381
+ if (!storyRangedCommentIds.has(content.id)) anchorPointComment(inlineNodes, content.id);
382
+ emitInlineNode(schema.node("commentReference", { commentId: content.id }));
383
+ break;
384
+ case "run":
385
+ emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
386
+ break;
387
+ case "hyperlink": {
388
+ const linkNodes = convertHyperlink(content, {
389
+ getInheritedRunFormatting,
390
+ styleResolver,
391
+ hyperlinkIndex: nextHyperlinkInstanceIndex(),
392
+ textBoxAnchors,
393
+ nextPageBreakRunOwnerId
355
394
  });
395
+ if (linkNodes.length === 0) {
396
+ emptyHyperlinks ??= [];
397
+ emptyHyperlinks.push({
398
+ offset: inlineOffset,
399
+ ...content.href !== void 0 ? { href: content.href } : {},
400
+ ...content.anchor !== void 0 ? { anchor: content.anchor } : {},
401
+ ...content.tooltip !== void 0 ? { tooltip: content.tooltip } : {},
402
+ ...content.rId !== void 0 ? { rId: content.rId } : {}
403
+ });
404
+ break;
405
+ }
406
+ emitInlineNodes(linkNodes);
356
407
  break;
357
408
  }
358
- emitInlineNodes(linkNodes);
359
- break;
360
- }
361
- case "simpleField":
362
- case "complexField":
363
- emitInlineNode(convertField(content, {
364
- getInheritedRunFormatting,
365
- styleResolver,
366
- nextHyperlinkInstanceIndex,
367
- nextPageBreakRunOwnerId,
368
- textBoxAnchors
369
- }));
370
- break;
371
- case "inlineSdt":
372
- emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors));
373
- break;
374
- case "insertion":
375
- case "moveTo":
376
- emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
377
- break;
378
- case "deletion":
379
- case "moveFrom":
380
- emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
381
- break;
382
- case "mathEquation":
383
- emitInlineNode(convertMathEquation(content));
384
- break;
385
- case "bookmarkStart":
386
- if (pairedBookmarkIds.has(content.id)) {
387
- emitInlineNode(schema.node("bookmarkBoundary", {
388
- type: "start",
409
+ case "simpleField":
410
+ case "complexField":
411
+ emitInlineNode(convertField(content, {
412
+ getInheritedRunFormatting,
413
+ styleResolver,
414
+ nextHyperlinkInstanceIndex,
415
+ nextPageBreakRunOwnerId,
416
+ textBoxAnchors
417
+ }));
418
+ break;
419
+ case "inlineSdt":
420
+ emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors, stack));
421
+ break;
422
+ case "insertion":
423
+ case "moveTo":
424
+ emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
425
+ break;
426
+ case "deletion":
427
+ case "moveFrom":
428
+ emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
429
+ break;
430
+ case "mathEquation":
431
+ emitInlineNode(convertMathEquation(content));
432
+ break;
433
+ case "bookmarkStart":
434
+ if (pairedBookmarkIds.has(content.id)) {
435
+ emitInlineNode(schema.node("bookmarkBoundary", {
436
+ type: "start",
437
+ id: content.id,
438
+ name: content.name,
439
+ colFirst: content.colFirst,
440
+ colLast: content.colLast,
441
+ displacedByCustomXml: content.displacedByCustomXml
442
+ }));
443
+ break;
444
+ }
445
+ if (!bookmarksArr) bookmarksArr = [];
446
+ bookmarksArr.push({
447
+ id: content.id,
448
+ name: content.name
449
+ });
450
+ break;
451
+ case "bookmarkEnd":
452
+ if (pairedBookmarkIds.has(content.id)) emitInlineNode(schema.node("bookmarkBoundary", {
453
+ type: "end",
389
454
  id: content.id,
390
- name: content.name,
391
- colFirst: content.colFirst,
392
- colLast: content.colLast,
393
455
  displacedByCustomXml: content.displacedByCustomXml
394
456
  }));
395
457
  break;
396
- }
397
- if (!bookmarksArr) bookmarksArr = [];
398
- bookmarksArr.push({
399
- id: content.id,
400
- name: content.name
401
- });
402
- break;
403
- case "bookmarkEnd":
404
- if (pairedBookmarkIds.has(content.id)) emitInlineNode(schema.node("bookmarkBoundary", {
405
- type: "end",
406
- id: content.id,
407
- displacedByCustomXml: content.displacedByCustomXml
408
- }));
409
- break;
410
- case "moveFromRangeStart":
411
- case "moveFromRangeEnd":
412
- case "moveToRangeStart":
413
- case "moveToRangeEnd": break;
414
- default: panic(`Unsupported paragraph content: ${JSON.stringify(content)}`);
458
+ case "moveFromRangeStart":
459
+ case "moveFromRangeEnd":
460
+ case "moveToRangeStart":
461
+ case "moveToRangeEnd": break;
462
+ case "preservedInline":
463
+ emitInlineNode(preservedInlineNode(content));
464
+ break;
465
+ default: panic(`Unsupported paragraph content: ${JSON.stringify(content)}`);
466
+ }
415
467
  }
416
468
  if (bookmarksArr) attrs.bookmarks = bookmarksArr;
417
469
  if (emptyHyperlinks) attrs._emptyHyperlinks = emptyHyperlinks;
@@ -449,48 +501,57 @@ function anchorPointComment(nodes, commentId) {
449
501
  * Convert tracked change (insertion or deletion) content to PM nodes with
450
502
  * an insertion/deletion mark applied.
451
503
  */
452
- function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
504
+ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors, wrappedBy = []) {
453
505
  const nodes = [];
454
- for (const item of change.content) if (item.type === "run") nodes.push(...convertRun(item, getInheritedRunFormatting(item.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
455
- else if (item.type === "hyperlink") {
456
- const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
457
- nodes.push(...convertHyperlink(item, {
458
- getInheritedRunFormatting,
459
- styleResolver,
460
- hyperlinkIndex: currentHyperlinkIndex,
461
- textBoxAnchors,
462
- nextPageBreakRunOwnerId
506
+ for (const { content: item, stack } of withInlineWrapperStacks(change.content)) {
507
+ if (!isTrackedChangeWrapperChild(item)) continue;
508
+ const itemNodes = [];
509
+ if (item.type === "run") itemNodes.push(...convertRun(item, getInheritedRunFormatting(item.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
510
+ else if (item.type === "hyperlink") {
511
+ const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
512
+ itemNodes.push(...convertHyperlink(item, {
513
+ getInheritedRunFormatting,
514
+ styleResolver,
515
+ hyperlinkIndex: currentHyperlinkIndex,
516
+ textBoxAnchors,
517
+ nextPageBreakRunOwnerId
518
+ }));
519
+ } else if (item.type === "simpleField" || item.type === "complexField") {
520
+ const fieldNode = convertField(item, {
521
+ getInheritedRunFormatting,
522
+ styleResolver,
523
+ nextHyperlinkInstanceIndex,
524
+ nextPageBreakRunOwnerId,
525
+ textBoxAnchors
526
+ });
527
+ if (fieldNode) itemNodes.push(fieldNode);
528
+ } else if (item.type === "mathEquation") {
529
+ const mathNode = convertMathEquation(item);
530
+ if (mathNode) itemNodes.push(mathNode);
531
+ } else if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") {
532
+ const nestedMarkType = item.type === "insertion" || item.type === "moveTo" ? "insertion" : "deletion";
533
+ const nestedMoveKind = item.type === "moveFrom" || item.type === "moveTo" ? item.type : null;
534
+ itemNodes.push(...convertTrackedChange(item, nestedMarkType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, nestedMoveKind, textBoxAnchors));
535
+ } else if (item.type === "inlineSdt") {
536
+ const sdtNode = convertInlineSdt(item, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors, [...wrappedBy, ...stack]);
537
+ if (sdtNode) itemNodes.push(sdtNode);
538
+ } else if (item.type === "bookmarkStart") itemNodes.push(schema.node("bookmarkBoundary", {
539
+ type: "start",
540
+ id: item.id,
541
+ name: item.name,
542
+ colFirst: item.colFirst,
543
+ colLast: item.colLast,
544
+ displacedByCustomXml: item.displacedByCustomXml
463
545
  }));
464
- } else if (item.type === "simpleField" || item.type === "complexField") {
465
- const fieldNode = convertField(item, {
466
- getInheritedRunFormatting,
467
- styleResolver,
468
- nextHyperlinkInstanceIndex,
469
- nextPageBreakRunOwnerId,
470
- textBoxAnchors
471
- });
472
- if (fieldNode) nodes.push(fieldNode);
473
- } else if (item.type === "mathEquation") {
474
- const mathNode = convertMathEquation(item);
475
- if (mathNode) nodes.push(mathNode);
476
- } else if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") {
477
- const nestedMarkType = item.type === "insertion" || item.type === "moveTo" ? "insertion" : "deletion";
478
- const nestedMoveKind = item.type === "moveFrom" || item.type === "moveTo" ? item.type : null;
479
- nodes.push(...convertTrackedChange(item, nestedMarkType, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, nestedMoveKind, textBoxAnchors));
480
- } else if (item.type === "bookmarkStart") nodes.push(schema.node("bookmarkBoundary", {
481
- type: "start",
482
- id: item.id,
483
- name: item.name,
484
- colFirst: item.colFirst,
485
- colLast: item.colLast,
486
- displacedByCustomXml: item.displacedByCustomXml
487
- }));
488
- else if (item.type === "bookmarkEnd") nodes.push(schema.node("bookmarkBoundary", {
489
- type: "end",
490
- id: item.id,
491
- displacedByCustomXml: item.displacedByCustomXml
492
- }));
493
- else panic(`Unsupported tracked-run content: ${JSON.stringify(item)}`);
546
+ else if (item.type === "bookmarkEnd") itemNodes.push(schema.node("bookmarkBoundary", {
547
+ type: "end",
548
+ id: item.id,
549
+ displacedByCustomXml: item.displacedByCustomXml
550
+ }));
551
+ else if (item.type === "preservedInline") itemNodes.push(preservedInlineNode(item));
552
+ else panic(`Unsupported tracked-run content: ${JSON.stringify(item)}`);
553
+ nodes.push(...withInlineWrapperMark(itemNodes, stack));
554
+ }
494
555
  const mark = schema.marks[markType].create({
495
556
  revisionId: change.info.id,
496
557
  author: change.info.author,
@@ -525,6 +586,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
525
586
  if (paragraph.listRendering) Object.assign(attrs, listRenderingAttrPatch(paragraph.listRendering));
526
587
  if (formatting) attrs._originalFormatting = formatting;
527
588
  if (paragraph.propertyChanges && paragraph.propertyChanges.length > 0) attrs._propertyChanges = [...paragraph.propertyChanges];
589
+ if (paragraph.preservedAttributes && paragraph.preservedAttributes.length > 0) attrs._preservedAttributes = paragraph.preservedAttributes;
528
590
  if (paragraph.pPrMark) attrs.pPrMark = paragraph.pPrMark;
529
591
  const set = (key, val) => {
530
592
  if (val !== void 0) attrs[key] = val;
@@ -850,6 +912,7 @@ function tableCellHasMeaningfulContent(cell) {
850
912
  return cell.content.some(blockHasMeaningfulContent);
851
913
  }
852
914
  function blockHasMeaningfulContent(block) {
915
+ if (block.type === "preservedBlock") return true;
853
916
  if (block.type === "table") return block.rows.some((row) => row.cells.some((cell) => tableCellHasMeaningfulContent(cell)));
854
917
  return block.content.some(paragraphContentHasMeaningfulContent);
855
918
  }
@@ -861,12 +924,11 @@ function paragraphContentHasMeaningfulContent(content) {
861
924
  }
862
925
  function convertTable(table, styleResolver, context) {
863
926
  for (const row of table.rows) for (const cell of row.cells) {
864
- if (isCellMergeContinuation(cell) && context.pageBreakRunSourceDescendants.containsPageBreakRun(cell.content)) throw new UnsupportedDocxToProseMirrorConversionError({
865
- message: `${PAGE_BREAK_CONTAINER_DESCRIPTIONS["table-cell"]} cannot be represented in the editor model`,
866
- owner: "table-cell",
867
- contentType: "break"
868
- });
869
- assertSourceContainerHasNoPageBreakRun(cell.content, "table-cell", context.pageBreakRunSourceDescendants);
927
+ if (isCellMergeContinuation(cell) && context.pageBreakRunSourceDescendants.containsPageBreakRun(cell.content)) {
928
+ context.warnPageBreakProjection("A page break inside a vertically merged table cell paginates with the merged row");
929
+ continue;
930
+ }
931
+ reportSourceContainerPageBreakRun(cell.content, "table-cell", context.pageBreakRunSourceDescendants, context.warnPageBreakProjection);
870
932
  }
871
933
  const rowSpanMap = calculateRowSpans(table);
872
934
  const columnWidths = table.columnWidths;
@@ -972,6 +1034,7 @@ function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths,
972
1034
  if (resolvedJustification) attrsWithoutStructuralChange._resolvedJustification = resolvedJustification;
973
1035
  if (row.formatting) attrsWithoutStructuralChange._originalFormatting = row.formatting;
974
1036
  if (row.propertyChanges && row.propertyChanges.length > 0) attrsWithoutStructuralChange.trPrChange = [...row.propertyChanges];
1037
+ if (row.preservedAttributes && row.preservedAttributes.length > 0) attrsWithoutStructuralChange._preservedAttributes = row.preservedAttributes;
975
1038
  let attrs = attrsWithoutStructuralChange;
976
1039
  const rowStructuralChange = row.structuralChange;
977
1040
  if (rowStructuralChange) {
@@ -1168,13 +1231,23 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
1168
1231
  if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
1169
1232
  if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = transportTableCellsWithParagraphPropertySources(vMergeContinuationCells);
1170
1233
  const contentNodes = [];
1171
- for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(...convertParagraphWithTextBoxes(content, styleResolver, {
1172
- textBoxGroupId: context.nextTextBoxGroupId(),
1173
- context,
1174
- ...conditionalStyle?.rPr !== void 0 ? { extraRunFormatting: conditionalStyle.rPr } : {},
1175
- ...conditionalStyle?.pPr !== void 0 ? { tableParagraphOverlay: conditionalStyle.pPr } : {}
1176
- }));
1177
- else contentNodes.push(convertTable(content, styleResolver, context));
1234
+ for (const content of cell.content) switch (content.type) {
1235
+ case "paragraph":
1236
+ contentNodes.push(...convertParagraphWithTextBoxes(content, styleResolver, {
1237
+ textBoxGroupId: context.nextTextBoxGroupId(),
1238
+ context,
1239
+ ...conditionalStyle?.rPr !== void 0 ? { extraRunFormatting: conditionalStyle.rPr } : {},
1240
+ ...conditionalStyle?.pPr !== void 0 ? { tableParagraphOverlay: conditionalStyle.pPr } : {}
1241
+ }));
1242
+ break;
1243
+ case "table":
1244
+ contentNodes.push(convertTable(content, styleResolver, context));
1245
+ break;
1246
+ case "preservedBlock":
1247
+ contentNodes.push(convertPreservedBlock(content));
1248
+ break;
1249
+ default: panic(`Unsupported table cell content: ${JSON.stringify(content)}`);
1250
+ }
1178
1251
  if (contentNodes.length === 0) contentNodes.push(schema.node("paragraph", {}, []));
1179
1252
  const nodeType = isHeader ? "tableHeader" : "tableCell";
1180
1253
  return schema.node(nodeType, attrs, contentNodes);
@@ -1183,7 +1256,7 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
1183
1256
  const nextTextBoxGroupId = createTextBoxGroupIdFactory();
1184
1257
  const nextHyperlinkInstanceIndex = createHyperlinkInstanceIndexAllocator();
1185
1258
  const pageBreakRunSourceDescendants = buildPageBreakRunSourceDescendantIndex(cell.content);
1186
- assertSourceContainerHasNoPageBreakRun(cell.content, "table-cell", pageBreakRunSourceDescendants);
1259
+ reportSourceContainerPageBreakRun(cell.content, "table-cell", pageBreakRunSourceDescendants, noPageBreakProjectionWarning);
1187
1260
  return convertTableCell({
1188
1261
  cell,
1189
1262
  styleResolver: null,
@@ -1192,7 +1265,10 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
1192
1265
  nextTextBoxGroupId,
1193
1266
  nextHyperlinkInstanceIndex,
1194
1267
  pairedBookmarkIds: collectPairedBookmarkIds(cell.content),
1195
- pageBreakRunSourceDescendants
1268
+ pageBreakRunSourceDescendants,
1269
+ storyRangedCommentIds: rangedCommentIds(cell.content),
1270
+ openCommentIds: /* @__PURE__ */ new Set(),
1271
+ warnPageBreakProjection: noPageBreakProjectionWarning
1196
1272
  },
1197
1273
  isHeader: nodeType === "tableHeader",
1198
1274
  gridWidthPercent: void 0,
@@ -1210,15 +1286,7 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1210
1286
  let fieldFormatting;
1211
1287
  let fieldPropertyChanges;
1212
1288
  const inlineNodes = [];
1213
- const runHasPageBreak = (run) => run.content.some((content) => content.type === "break" && content.breakType === "page");
1214
- if (field.type === "complexField" && field.fieldCode.some(runHasPageBreak)) throw new UnsupportedDocxToProseMirrorConversionError({
1215
- message: "A complex-field instruction containing an explicit page break cannot be represented in the editor model",
1216
- owner: "complex-field-instruction",
1217
- contentType: "break"
1218
- });
1219
- const hasPageBreakContent = field.type === "simpleField" ? field.content.some((content) => content.type === "run" ? runHasPageBreak(content) : content.children.some((child) => child.type === "run" && runHasPageBreak(child))) : field.fieldResult.some(runHasPageBreak);
1220
- if (hasPageBreakContent) assertPageBreakFieldResultIsRepresentable(field);
1221
- const hasStructuredSourceContent = hasPageBreakContent || field.type === "simpleField" && field.content.some((content) => content.type === "hyperlink");
1289
+ const hasStructuredSourceContent = fieldResultHasPageBreakContent(field) || field.type === "simpleField" && field.content.some((content) => content.type === "hyperlink" || content.type === "preservedInline");
1222
1290
  const appendRun = (run) => {
1223
1291
  for (const content of run.content) if (content.type === "text") displayText += content.text;
1224
1292
  fieldFormatting ??= run.formatting;
@@ -1231,6 +1299,10 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1231
1299
  appendRun(content);
1232
1300
  continue;
1233
1301
  }
1302
+ if (content.type === "preservedInline") {
1303
+ inlineNodes.push(preservedInlineNode(content));
1304
+ continue;
1305
+ }
1234
1306
  for (const child of content.children) if (child.type === "run") {
1235
1307
  for (const runContent of child.content) if (runContent.type === "text") displayText += runContent.text;
1236
1308
  fieldFormatting ??= child.formatting;
@@ -1249,36 +1321,64 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1249
1321
  const inheritedFormatting = getInheritedRunFormatting(fieldFormatting, field.fieldType);
1250
1322
  const { marks } = buildRunMarks(fieldFormatting, inheritedFormatting, styleResolver);
1251
1323
  const hasConvertedHyperlinkContent = inlineNodes.some((node) => node.marks.some((mark) => mark.type.name === "hyperlink"));
1252
- const createStructuredField = inlineNodes.some((node) => node.type.name === "pageBreakRun") || hasStructuredSourceContent && hasConvertedHyperlinkContent;
1324
+ const hasConvertedPageBreakContent = inlineNodes.some((node) => node.type.name === "pageBreakRun");
1325
+ const hasConvertedPreservedContent = inlineNodes.some((node) => node.type.name === "preservedXml");
1326
+ const createStructuredField = hasConvertedPageBreakContent || hasConvertedPreservedContent || hasStructuredSourceContent && hasConvertedHyperlinkContent;
1253
1327
  if (!createStructuredField && fieldPropertyChanges && fieldPropertyChanges.length > 0) marks.push(schema.mark("runPropertyChange", { changes: [...fieldPropertyChanges] }));
1254
1328
  return schema.node(createStructuredField ? "structuredField" : "field", {
1255
1329
  fieldType: field.fieldType,
1256
1330
  instruction: field.instruction,
1257
1331
  displayText,
1258
1332
  fieldKind: field.type === "simpleField" ? "simple" : "complex",
1259
- fldLock: field.fldLock ?? false,
1260
- dirty: field.dirty ?? false
1333
+ fldLock: field.fldLock ?? null,
1334
+ dirty: field.dirty ?? null
1261
1335
  }, createStructuredField ? inlineNodes : void 0, marks);
1262
1336
  }
1263
1337
  /**
1264
- * Convert a MathEquation to a ProseMirror math node.
1338
+ * Lift the transparent wrappers out of an inline content list, remembering for
1339
+ * each item which wrappers it sat inside.
1340
+ *
1341
+ * The tree becomes one sequence because every inline loop below narrows by a
1342
+ * chain of `else if` rather than by exhaustion, and a wrapper left in it would
1343
+ * reach whichever branch happens to be last. What the wrapper said is not lost
1344
+ * with the tree: it rides the `inlineWrapper` mark on the leaves it held, and
1345
+ * ProseMirror maintains that range across edits instead of an index.
1346
+ *
1347
+ * It is applied to the content of whatever holds the wrapper, never around it:
1348
+ * a wrapper inside a revision or a content control is lifted inside that
1349
+ * wrapper, so its runs keep the revision mark or the control they were
1350
+ * authored under.
1265
1351
  */
1352
+ const withInlineWrapperStacks = (content, stack = []) => content.flatMap((item) => item.type === "inlineWrapper" ? withInlineWrapperStacks(item.content, [...stack, inlineWrapperLayer(item)]) : [{
1353
+ content: item,
1354
+ stack
1355
+ }]);
1266
1356
  /**
1267
- * Flatten `w:bdo`/`w:dir` out of an inline content list.
1357
+ * `nodes` with `stack` recorded outside whatever wrapper they already carry.
1268
1358
  *
1269
- * The editor has no projection for a bidirectional wrapper yet, and the
1270
- * alternative to flattening it is worse than losing the direction: every
1271
- * inline loop below narrows by a chain of `else if` rather than by
1272
- * exhaustion, so an unhandled member reaches whichever branch happens to be
1273
- * last and is read as that. Flattening keeps the wrapper's content and loses
1274
- * only its direction, which the survival census records as an
1275
- * editor-projection loss until the mark that carries it exists.
1359
+ * A revision converts its own content first, so a node that arrives here
1360
+ * already marked sat inside the item this stack wraps: the two stacks
1361
+ * concatenate, outermost first. Concatenating is what the schema forces — the
1362
+ * mark excludes itself, so a second one replaces the first, and the inner
1363
+ * wrapper would be the one lost.
1276
1364
  *
1277
- * The save path is unaffected: a document opened and saved without being
1278
- * edited replays its markup, and one that is edited keeps the wrapper because
1279
- * `fromProseDoc` rebuilds from the source paragraph.
1365
+ * A node that holds its own leaves, a content control, is marked here as the
1366
+ * one node it is; its leaves were marked with the whole enclosing stack when
1367
+ * it was built, because the painter reads the wrapper off the leaf.
1368
+ */
1369
+ const withInlineWrapperMark = (nodes, stack) => {
1370
+ const markType = schema.marks["inlineWrapper"];
1371
+ if (stack.length === 0 || !markType) return nodes;
1372
+ return nodes.map((node) => {
1373
+ if (!node.isText && (!node.isInline || !node.type.allowsMarkType(markType))) return node;
1374
+ const inner = node.marks.find((mark) => mark.type === markType);
1375
+ const layers = inner === void 0 ? stack : [...stack, ...expectInlineWrapperMarkAttrs(inner).stack];
1376
+ return node.mark(markType.create({ stack: layers }).addToSet(node.marks));
1377
+ });
1378
+ };
1379
+ /**
1380
+ * Convert a MathEquation to a ProseMirror math node.
1280
1381
  */
1281
- const withoutBidiWrappers = (content) => content.flatMap((item) => item.type === "bidiWrapper" ? withoutBidiWrappers(item.content) : [item]);
1282
1382
  function convertMathEquation(math) {
1283
1383
  return schema.node("math", {
1284
1384
  display: math.display,
@@ -1288,54 +1388,66 @@ function convertMathEquation(math) {
1288
1388
  }
1289
1389
  /**
1290
1390
  * Convert an InlineSdt to a ProseMirror sdt node with inline content.
1391
+ *
1392
+ * `wrappedBy` is the stack the control itself sits inside. The control holds
1393
+ * its own leaves, so nothing outside can mark them afterwards: the caller's
1394
+ * stack has to reach them here, under the wrappers written inside the control.
1291
1395
  */
1292
- function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors) {
1396
+ function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors, wrappedBy = []) {
1293
1397
  const props = sdt.properties;
1294
1398
  const inlineNodes = [];
1295
- for (const content of sdt.content) switch (content.type) {
1296
- case "run":
1297
- inlineNodes.push(...convertRun(content, getInheritedRunFormatting(content.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
1298
- break;
1299
- case "hyperlink": {
1300
- const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
1301
- inlineNodes.push(...convertHyperlink(content, {
1302
- getInheritedRunFormatting,
1303
- styleResolver,
1304
- hyperlinkIndex: currentHyperlinkIndex,
1305
- textBoxAnchors,
1306
- nextPageBreakRunOwnerId
1307
- }));
1308
- break;
1309
- }
1310
- case "simpleField":
1311
- case "complexField": {
1312
- const fieldNode = convertField(content, {
1313
- getInheritedRunFormatting,
1314
- styleResolver,
1315
- nextHyperlinkInstanceIndex,
1316
- nextPageBreakRunOwnerId,
1317
- textBoxAnchors
1318
- });
1319
- if (fieldNode) inlineNodes.push(fieldNode);
1320
- break;
1321
- }
1322
- case "inlineSdt": {
1323
- const nestedSdt = convertInlineSdt(content, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors);
1324
- if (nestedSdt) inlineNodes.push(nestedSdt);
1325
- break;
1326
- }
1327
- case "insertion":
1328
- case "deletion":
1329
- case "moveTo":
1330
- case "moveFrom":
1331
- inlineNodes.push(...convertTrackedChange(content, content.type === "insertion" || content.type === "moveTo" ? "insertion" : "deletion", nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, content.type === "moveTo" || content.type === "moveFrom" ? content.type : null, textBoxAnchors));
1332
- break;
1333
- case "mathEquation": {
1334
- const mathNode = convertMathEquation(content);
1335
- if (mathNode) inlineNodes.push(mathNode);
1336
- break;
1399
+ for (const { content, stack } of withInlineWrapperStacks(sdt.content, wrappedBy)) {
1400
+ if (!isInlineSdtContent(content)) continue;
1401
+ const itemNodes = [];
1402
+ switch (content.type) {
1403
+ case "run":
1404
+ itemNodes.push(...convertRun(content, getInheritedRunFormatting(content.formatting), nextPageBreakRunOwnerId, styleResolver, textBoxAnchors));
1405
+ break;
1406
+ case "hyperlink": {
1407
+ const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
1408
+ itemNodes.push(...convertHyperlink(content, {
1409
+ getInheritedRunFormatting,
1410
+ styleResolver,
1411
+ hyperlinkIndex: currentHyperlinkIndex,
1412
+ textBoxAnchors,
1413
+ nextPageBreakRunOwnerId
1414
+ }));
1415
+ break;
1416
+ }
1417
+ case "simpleField":
1418
+ case "complexField": {
1419
+ const fieldNode = convertField(content, {
1420
+ getInheritedRunFormatting,
1421
+ styleResolver,
1422
+ nextHyperlinkInstanceIndex,
1423
+ nextPageBreakRunOwnerId,
1424
+ textBoxAnchors
1425
+ });
1426
+ if (fieldNode) itemNodes.push(fieldNode);
1427
+ break;
1428
+ }
1429
+ case "inlineSdt": {
1430
+ const nestedSdt = convertInlineSdt(content, nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, textBoxAnchors, stack);
1431
+ if (nestedSdt) itemNodes.push(nestedSdt);
1432
+ break;
1433
+ }
1434
+ case "insertion":
1435
+ case "deletion":
1436
+ case "moveTo":
1437
+ case "moveFrom":
1438
+ itemNodes.push(...convertTrackedChange(content, content.type === "insertion" || content.type === "moveTo" ? "insertion" : "deletion", nextHyperlinkInstanceIndex, nextPageBreakRunOwnerId, getInheritedRunFormatting, styleResolver, content.type === "moveTo" || content.type === "moveFrom" ? content.type : null, textBoxAnchors));
1439
+ break;
1440
+ case "mathEquation": {
1441
+ const mathNode = convertMathEquation(content);
1442
+ if (mathNode) itemNodes.push(mathNode);
1443
+ break;
1444
+ }
1445
+ case "preservedInline":
1446
+ itemNodes.push(preservedInlineNode(content));
1447
+ break;
1448
+ default: panic(`Unsupported inline SDT content: ${JSON.stringify(content)}`);
1337
1449
  }
1338
- default: panic(`Unsupported inline SDT content: ${JSON.stringify(content)}`);
1450
+ inlineNodes.push(...withInlineWrapperMark(itemNodes, stack));
1339
1451
  }
1340
1452
  return schema.node("sdt", sdtAttrsFromProperties(props), inlineNodes.length > 0 ? inlineNodes : void 0);
1341
1453
  }
@@ -1346,7 +1458,6 @@ function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, nextPageBreakRunOwner
1346
1458
  * @param resolvedStyleFormatting - Paragraph formatting and its explicit style-toggle state
1347
1459
  */
1348
1460
  function convertRun(run, resolvedStyleFormatting, nextPageBreakRunOwnerId, styleResolver, textBoxAnchors) {
1349
- assertPageBreakSourceRunIsRepresentable(run);
1350
1461
  const nodes = [];
1351
1462
  const { marks, mergedFormatting } = buildRunMarks(run.formatting, resolvedStyleFormatting, styleResolver);
1352
1463
  if (run.propertyChanges && run.propertyChanges.length > 0) marks.push(schema.mark("runPropertyChange", { changes: [...run.propertyChanges] }));
@@ -1357,28 +1468,35 @@ function convertRun(run, resolvedStyleFormatting, nextPageBreakRunOwnerId, style
1357
1468
  }
1358
1469
  return nodes;
1359
1470
  }
1360
- function assertPageBreakSourceRunIsRepresentable(run) {
1361
- if (!run.content.some((content) => content.type === "break" && content.breakType === "page")) return;
1362
- assertRunContentIsRepresentableBesidePageBreak(run, "page-break-bearing-run");
1471
+ const runHasPageBreakContent = (run) => run.content.some((content) => content.type === "break" && content.breakType === "page");
1472
+ /**
1473
+ * Whether a field's result holds an explicit page break.
1474
+ *
1475
+ * `convertField` re-cuts the whole result into a structured field when it
1476
+ * does, so this answers both who gets re-cut and who gets reported. One
1477
+ * predicate for the two, because a reporter that disagreed with the converter
1478
+ * would name a loss nobody suffered.
1479
+ */
1480
+ const fieldResultHasPageBreakContent = (field) => field.type === "simpleField" ? field.content.some((content) => {
1481
+ switch (content.type) {
1482
+ case "run": return runHasPageBreakContent(content);
1483
+ case "hyperlink": return content.children.some((child) => child.type === "run" && runHasPageBreakContent(child));
1484
+ case "preservedInline": return false;
1485
+ default: panic(`Unsupported simple-field content: ${JSON.stringify(content)}`);
1486
+ }
1487
+ }) : field.fieldResult.some(runHasPageBreakContent);
1488
+ function reportPageBreakSourceRunContent(run, warn) {
1489
+ if (!runHasPageBreakContent(run)) return;
1490
+ reportRunContentBesidePageBreak(run, "page-break-bearing-run", warn);
1363
1491
  }
1364
1492
  const PAGE_BREAK_CONTAINER_DESCRIPTIONS = {
1365
- "table-cell": "A table cell containing an explicit page break",
1366
- "text-box": "A text box containing an explicit page break"
1367
- };
1368
- const PAGE_BREAK_PARAGRAPH_OWNERS = {
1369
- borders: "paragraph-borders",
1370
- frame: "paragraph-frame",
1371
- outline: "paragraph-outline",
1372
- textBoxAnchor: "paragraph-text-box-anchor"
1493
+ "table-cell": "An interior page break in a table cell does not paginate the cell; its row moves as a unit",
1494
+ "text-box": "A page break inside a text box does not paginate the text box"
1373
1495
  };
1374
- function assertSourceContainerHasNoPageBreakRun(content, owner, sourceDescendants) {
1496
+ function reportSourceContainerPageBreakRun(content, owner, sourceDescendants, warn) {
1375
1497
  if (!sourceDescendants.containsPageBreakRun(content)) return;
1376
1498
  if (owner === "table-cell" && hasSingleLeadingTableCellPageBreak(content, sourceDescendants)) return;
1377
- throw new UnsupportedDocxToProseMirrorConversionError({
1378
- message: `${PAGE_BREAK_CONTAINER_DESCRIPTIONS[owner]} cannot be represented in the editor model`,
1379
- owner,
1380
- contentType: "break"
1381
- });
1499
+ warn(PAGE_BREAK_CONTAINER_DESCRIPTIONS[owner]);
1382
1500
  }
1383
1501
  /**
1384
1502
  * A page break opening a cell advances the whole row during pagination.
@@ -1419,7 +1537,7 @@ const scanLeadingPageBreakContent = (content, scan) => {
1419
1537
  case "moveFrom":
1420
1538
  case "moveTo":
1421
1539
  case "inlineSdt":
1422
- case "bidiWrapper":
1540
+ case "inlineWrapper":
1423
1541
  for (const child of content.content) scanLeadingPageBreakContent(child, scan);
1424
1542
  return;
1425
1543
  case "simpleField":
@@ -1438,6 +1556,7 @@ const scanLeadingPageBreakContent = (content, scan) => {
1438
1556
  case "moveToRangeEnd": return;
1439
1557
  case "commentReference":
1440
1558
  case "mathEquation":
1559
+ case "preservedInline":
1441
1560
  if (scan.pageBreaks === 0) scan.contentBeforeBreak = true;
1442
1561
  return;
1443
1562
  default: return content;
@@ -1456,64 +1575,103 @@ const hasSingleLeadingParagraphPageBreak = (paragraph) => {
1456
1575
  const scan = scanParagraphPageBreaks(paragraph);
1457
1576
  return !scan.contentBeforeBreak && scan.pageBreaks === 1;
1458
1577
  };
1459
- function assertParagraphPageBreakCanBeProjected({ paragraph, attrs, effectiveFrame, sourceDescendants }) {
1578
+ function reportParagraphPageBreakProjection({ paragraph, attrs, effectiveFrame, sourceDescendants, warn }) {
1460
1579
  const sourceFeatures = sourceDescendants.paragraphFeatures(paragraph);
1461
1580
  if (!sourceFeatures.hasPageBreakRun) return;
1581
+ reportParagraphPageBreakRunContent(paragraph, warn);
1462
1582
  const disposition = pageBreakRunParagraphProjectionDispositionForFeatures({
1463
1583
  attrs,
1464
1584
  effectiveFrame,
1465
1585
  textBoxAnchorAfterPageBreak: sourceFeatures.hasTextBoxShape && !sourceFeatures.pageBreakSharesTextBoxShape && scanParagraphPageBreaks(paragraph).textBoxShapeAfterBreak
1466
1586
  });
1467
- if (disposition.status === "supported") return;
1587
+ if (disposition.status === "exact") return;
1468
1588
  if (disposition.reason !== "textBoxAnchor" && hasSingleLeadingParagraphPageBreak(paragraph)) return;
1469
- throw new UnsupportedDocxToProseMirrorConversionError({
1470
- message: disposition.message,
1471
- owner: PAGE_BREAK_PARAGRAPH_OWNERS[disposition.reason],
1472
- contentType: "break"
1473
- });
1589
+ warn(disposition.message);
1474
1590
  }
1475
- function assertPageBreakFieldResultIsRepresentable(field) {
1476
- if (field.type === "complexField") {
1477
- for (const run of field.fieldResult) assertRunContentIsRepresentableBesidePageBreak(run, "field-result");
1478
- return;
1479
- }
1480
- for (const content of field.content) {
1481
- if (content.type === "run") {
1482
- assertRunContentIsRepresentableBesidePageBreak(content, "field-result");
1483
- continue;
1591
+ /**
1592
+ * Report every run-level loss a paragraph's page breaks bring with them.
1593
+ *
1594
+ * One walk of the paragraph rather than a check at each `convertRun` and
1595
+ * `convertField`: the reporter is a conversion-wide fact, and a per-call-site
1596
+ * check would have to be threaded through every nested converter to reach it.
1597
+ */
1598
+ function reportParagraphPageBreakRunContent(paragraph, warn) {
1599
+ const visitField = (field) => {
1600
+ if (field.type === "complexField") {
1601
+ if (field.fieldCode.some(runHasPageBreakContent)) warn("A complex-field instruction holds an explicit page break");
1602
+ if (!fieldResultHasPageBreakContent(field)) return;
1603
+ for (const run of field.fieldResult) reportRunContentBesidePageBreak(run, "field-result", warn);
1604
+ return;
1484
1605
  }
1485
- for (const child of content.children) if (child.type === "run") assertRunContentIsRepresentableBesidePageBreak(child, "field-result");
1486
- }
1606
+ if (!fieldResultHasPageBreakContent(field)) return;
1607
+ for (const content of field.content) {
1608
+ if (content.type === "run") {
1609
+ reportRunContentBesidePageBreak(content, "field-result", warn);
1610
+ continue;
1611
+ }
1612
+ if (content.type !== "hyperlink") continue;
1613
+ for (const child of content.children) if (child.type === "run") reportRunContentBesidePageBreak(child, "field-result", warn);
1614
+ }
1615
+ };
1616
+ const visitContent = (content) => {
1617
+ switch (content.type) {
1618
+ case "run":
1619
+ reportPageBreakSourceRunContent(content, warn);
1620
+ return;
1621
+ case "hyperlink":
1622
+ for (const child of content.children) if (child.type === "run") reportPageBreakSourceRunContent(child, warn);
1623
+ return;
1624
+ case "simpleField":
1625
+ case "complexField":
1626
+ visitField(content);
1627
+ return;
1628
+ case "insertion":
1629
+ case "deletion":
1630
+ case "moveFrom":
1631
+ case "moveTo":
1632
+ case "inlineSdt":
1633
+ case "inlineWrapper":
1634
+ for (const child of content.content) visitContent(child);
1635
+ return;
1636
+ default: return;
1637
+ }
1638
+ };
1639
+ for (const content of paragraph.content) visitContent(content);
1487
1640
  }
1488
1641
  const PAGE_BREAK_OWNER_DESCRIPTIONS = {
1489
1642
  "field-result": "A field result with an explicit page break",
1490
1643
  "page-break-bearing-run": "A page-break-bearing run"
1491
1644
  };
1492
- const unsupportedPageBreakContent = (owner, contentType) => {
1493
- const description = contentType === "shape" ? "a text-box shape" : contentType;
1494
- throw new UnsupportedDocxToProseMirrorConversionError({
1495
- message: `${PAGE_BREAK_OWNER_DESCRIPTIONS[owner]} containing ${description} cannot be represented in the editor model`,
1496
- owner,
1497
- contentType
1498
- });
1499
- };
1500
- function assertRunContentIsRepresentableBesidePageBreak(run, owner) {
1645
+ /**
1646
+ * Record what a page-break-bearing run loses when the editor re-cuts it.
1647
+ *
1648
+ * The run is split into one node per inline atom and rebuilt from the
1649
+ * `pageBreakRunOwner` mark, and the kinds below have no node of their own:
1650
+ * a hyphen returns as its character, a field character or instruction leaves
1651
+ * no trace, and a text-box shape is hoisted to its own block. Every one of
1652
+ * those losses is what the same run suffers with no page break in it, so
1653
+ * refusing the document here declined to open a file folio otherwise reads.
1654
+ */
1655
+ function reportRunContentBesidePageBreak(run, owner, warn) {
1501
1656
  for (const content of run.content) switch (content.type) {
1502
1657
  case "break":
1503
1658
  case "drawing":
1504
1659
  case "endnoteRef":
1505
1660
  case "footnoteRef":
1661
+ case "preservedXml":
1506
1662
  case "renderedPageBreak":
1507
1663
  case "symbol":
1508
1664
  case "tab":
1509
1665
  case "text": continue;
1510
1666
  case "shape":
1511
- if (content.shape.textBody) unsupportedPageBreakContent(owner, content.type);
1667
+ if (content.shape.textBody) warn(`${PAGE_BREAK_OWNER_DESCRIPTIONS[owner]} also holds a text-box shape`);
1512
1668
  continue;
1513
1669
  case "fieldChar":
1514
1670
  case "instrText":
1515
1671
  case "noBreakHyphen":
1516
- case "softHyphen": return unsupportedPageBreakContent(owner, content.type);
1672
+ case "softHyphen":
1673
+ warn(`${PAGE_BREAK_OWNER_DESCRIPTIONS[owner]} also holds ${content.type}`);
1674
+ continue;
1517
1675
  default: panic(`Unsupported page-break-bearing run content: ${JSON.stringify(content)}`);
1518
1676
  }
1519
1677
  }
@@ -1761,6 +1919,11 @@ function convertRunContent(content, marks, formatting, textBoxAnchors, authoredF
1761
1919
  }
1762
1920
  case "fieldChar":
1763
1921
  case "instrText": return [];
1922
+ case "preservedXml": return [schema.node("preservedXml", {
1923
+ xml: content.xml,
1924
+ text: content.text,
1925
+ level: PRESERVED_XML_LEVELS.run
1926
+ }).mark(marks)];
1764
1927
  case "noBreakHyphen": return [schema.text("‑", marks)];
1765
1928
  case "softHyphen": return [schema.text("­", marks)];
1766
1929
  case "symbol": return [schema.node("symbol", {
@@ -1808,14 +1971,7 @@ function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint, runForma
1808
1971
  else if (wrapType === "behind" || wrapType === "inFront") displayMode = "float";
1809
1972
  else if (cssFloat !== "none") displayMode = "float";
1810
1973
  else displayMode = "block";
1811
- let transform;
1812
- if (image.transform) {
1813
- const transforms = [];
1814
- if (image.transform.rotation) transforms.push(`rotate(${image.transform.rotation}deg)`);
1815
- if (image.transform.flipH) transforms.push("scaleX(-1)");
1816
- if (image.transform.flipV) transforms.push("scaleY(-1)");
1817
- if (transforms.length > 0) transform = transforms.join(" ");
1818
- }
1974
+ const transformAttrs = authoredTransformAttrs(image.transform);
1819
1975
  const distTop = image.wrap.distT != null ? emuToPixels(image.wrap.distT) : void 0;
1820
1976
  const distBottom = image.wrap.distB != null ? emuToPixels(image.wrap.distB) : void 0;
1821
1977
  const distLeft = image.wrap.distL != null ? emuToPixels(image.wrap.distL) : void 0;
@@ -1867,8 +2023,10 @@ function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint, runForma
1867
2023
  wrapType,
1868
2024
  displayMode,
1869
2025
  cssFloat,
1870
- transform,
2026
+ ...transformAttrs,
1871
2027
  opacity: image.opacity,
2028
+ brightness: image.effects?.brightness,
2029
+ contrast: image.effects?.contrast,
1872
2030
  distTop,
1873
2031
  distBottom,
1874
2032
  distLeft,
@@ -1935,8 +2093,11 @@ function convertHyperlink(hyperlink, { getInheritedRunFormatting, styleResolver,
1935
2093
  }, void 0, [linkMark]));
1936
2094
  continue;
1937
2095
  }
2096
+ if (child.type === "preservedInline") {
2097
+ nodes.push(preservedInlineNode(child).mark([linkMark]));
2098
+ continue;
2099
+ }
1938
2100
  if (child.type === "run") {
1939
- assertPageBreakSourceRunIsRepresentable(child);
1940
2101
  const inheritedFormatting = getInheritedRunFormatting(child.formatting);
1941
2102
  const { marks: runMarks, mergedFormatting } = buildRunMarks(child.formatting, inheritedFormatting, styleResolver);
1942
2103
  if (child.propertyChanges && child.propertyChanges.length > 0) runMarks.push(schema.mark("runPropertyChange", { changes: [...child.propertyChanges] }));
@@ -1997,14 +2158,7 @@ function convertShape(shape, runFormatting) {
1997
2158
  outlineHeadEnd = shape.outline.headEnd;
1998
2159
  outlineTailEnd = shape.outline.tailEnd;
1999
2160
  } else outlineWidth = 0;
2000
- let transform;
2001
- if (shape.transform) {
2002
- const transforms = [];
2003
- if (shape.transform.rotation) transforms.push(`rotate(${shape.transform.rotation}deg)`);
2004
- if (shape.transform.flipH) transforms.push("scaleX(-1)");
2005
- if (shape.transform.flipV) transforms.push("scaleY(-1)");
2006
- if (transforms.length > 0) transform = transforms.join(" ");
2007
- }
2161
+ const transformAttrs = authoredTransformAttrs(shape.transform);
2008
2162
  const wrapType = shape.wrap?.type ?? "inline";
2009
2163
  const displayMode = wrapType === "inline" ? "inline" : "float";
2010
2164
  let cssFloat = "none";
@@ -2053,7 +2207,7 @@ function convertShape(shape, runFormatting) {
2053
2207
  outlineJoin,
2054
2208
  outlineHeadEnd,
2055
2209
  outlineTailEnd,
2056
- transform,
2210
+ ...transformAttrs,
2057
2211
  displayMode,
2058
2212
  cssFloat,
2059
2213
  wrapType,
@@ -2067,18 +2221,20 @@ function convertShape(shape, runFormatting) {
2067
2221
  }
2068
2222
  function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, preserveEmptyWrapper, tableParagraphOverlay }) {
2069
2223
  const { textBoxes, textBoxAnchors } = extractTextBoxesFromParagraph(block, textBoxGroupId);
2070
- const pmParagraph = convertParagraph(block, styleResolver, context, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
2224
+ const pmParagraph = convertParagraph(block, styleResolver, context, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
2071
2225
  const nodes = [];
2072
2226
  const isEmptyAfterExtraction = textBoxes.length > 0 && !hasContentBesidesTextBoxAnchors(pmParagraph);
2073
2227
  const keepWrapperParagraph = isEmptyAfterExtraction && (preserveEmptyWrapper === true || hasParagraphBoundaryPayload(block, pmParagraph));
2074
2228
  if (!isEmptyAfterExtraction || keepWrapperParagraph) nodes.push(pmParagraph);
2075
- for (const { textBox, anchorId, trackedChange, inlineSdts } of textBoxes) nodes.push(convertTextBox(textBox, styleResolver, {
2076
- placement: isEmptyAfterExtraction && !keepWrapperParagraph ? "standalone" : "inlineWithPrevious",
2229
+ const standalone = isEmptyAfterExtraction && !keepWrapperParagraph;
2230
+ for (const [index, { textBox, anchorId, trackedChange, inlineSdts }] of textBoxes.entries()) nodes.push(convertTextBox(textBox, styleResolver, {
2231
+ placement: standalone ? "standalone" : "inlineWithPrevious",
2077
2232
  groupId: textBoxGroupId,
2078
2233
  anchorId,
2079
2234
  context,
2080
2235
  trackedChange,
2081
- inlineSdts
2236
+ inlineSdts,
2237
+ ...standalone && index === 0 && block.preservedAttributes ? { hostPreservedAttributes: block.preservedAttributes } : {}
2082
2238
  }));
2083
2239
  return nodes;
2084
2240
  }
@@ -2225,7 +2381,7 @@ function textBoxFromShape(shape, textBody) {
2225
2381
  * Convert a TextBox to a ProseMirror textBox node
2226
2382
  */
2227
2383
  function convertTextBox(textBox, styleResolver, options) {
2228
- assertSourceContainerHasNoPageBreakRun(textBox.content, "text-box", options.context.pageBreakRunSourceDescendants);
2384
+ reportSourceContainerPageBreakRun(textBox.content, "text-box", options.context.pageBreakRunSourceDescendants, options.context.warnPageBreakProjection);
2229
2385
  const textBoxSize = textBox.size;
2230
2386
  const widthPx = textBoxSize?.width ? emuToPixels(textBoxSize.width) : 200;
2231
2387
  const heightPx = textBoxSize?.height ? emuToPixels(textBoxSize.height) : void 0;
@@ -2239,28 +2395,25 @@ function convertTextBox(textBox, styleResolver, options) {
2239
2395
  if (textBox.outline.color?.rgb) outlineColor = `#${textBox.outline.color.rgb}`;
2240
2396
  outlineStyle = textBox.outline.style || "solid";
2241
2397
  }
2242
- let transform;
2243
- if (textBox.transform) {
2244
- const transforms = [];
2245
- if (textBox.transform.rotation) transforms.push(`rotate(${textBox.transform.rotation}deg)`);
2246
- if (textBox.transform.flipH) transforms.push("scaleX(-1)");
2247
- if (textBox.transform.flipV) transforms.push("scaleY(-1)");
2248
- if (transforms.length > 0) transform = transforms.join(" ");
2249
- }
2398
+ const transformAttrs = authoredTransformAttrs(textBox.transform);
2250
2399
  const marginTop = textBox.margins?.top !== void 0 ? emuToPixels(textBox.margins.top) : void 0;
2251
2400
  const marginBottom = textBox.margins?.bottom !== void 0 ? emuToPixels(textBox.margins.bottom) : void 0;
2252
2401
  const marginLeft = textBox.margins?.left !== void 0 ? emuToPixels(textBox.margins.left) : void 0;
2253
2402
  const marginRight = textBox.margins?.right !== void 0 ? emuToPixels(textBox.margins.right) : void 0;
2403
+ const textBoxContext = {
2404
+ ...options.context,
2405
+ openCommentIds: /* @__PURE__ */ new Set()
2406
+ };
2254
2407
  const contentNodes = [];
2255
2408
  for (const block of textBox.content) {
2256
2409
  if (block.type === "paragraph") {
2257
2410
  contentNodes.push(...convertParagraphWithTextBoxes(block, styleResolver, {
2258
- textBoxGroupId: options.context.nextTextBoxGroupId(),
2259
- context: options.context
2411
+ textBoxGroupId: textBoxContext.nextTextBoxGroupId(),
2412
+ context: textBoxContext
2260
2413
  }));
2261
2414
  continue;
2262
2415
  }
2263
- contentNodes.push(convertTable(block, styleResolver, options.context));
2416
+ contentNodes.push(convertTable(block, styleResolver, textBoxContext));
2264
2417
  }
2265
2418
  if (contentNodes.length === 0) contentNodes.push(schema.node("paragraph", {}, []));
2266
2419
  const wrapType = textBox.wrap?.type;
@@ -2323,7 +2476,7 @@ function convertTextBox(textBox, styleResolver, options) {
2323
2476
  outlineWidth,
2324
2477
  outlineColor,
2325
2478
  outlineStyle,
2326
- transform,
2479
+ ...transformAttrs,
2327
2480
  marginTop,
2328
2481
  marginBottom,
2329
2482
  marginLeft,
@@ -2342,7 +2495,8 @@ function convertTextBox(textBox, styleResolver, options) {
2342
2495
  _docxAnchorId: options.anchorId,
2343
2496
  _docxTextBodyContentState: textBox.content.length === 0 ? { type: "source-empty" } : { type: "authored" },
2344
2497
  _docxTrackedChange: options.trackedChange,
2345
- _docxInlineSdts: options.inlineSdts.length > 0 ? options.inlineSdts : void 0
2498
+ _docxInlineSdts: options.inlineSdts.length > 0 ? options.inlineSdts : void 0,
2499
+ _preservedAttributes: options.hostPreservedAttributes
2346
2500
  }, contentNodes);
2347
2501
  }
2348
2502
  /**
@@ -2363,7 +2517,10 @@ function headerFooterToProseDoc(content, options) {
2363
2517
  nextTextBoxGroupId,
2364
2518
  nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator(),
2365
2519
  pairedBookmarkIds: collectPairedBookmarkIds(content),
2366
- pageBreakRunSourceDescendants: buildPageBreakRunSourceDescendantIndex(content)
2520
+ pageBreakRunSourceDescendants: buildPageBreakRunSourceDescendantIndex(content),
2521
+ storyRangedCommentIds: rangedCommentIds(content),
2522
+ openCommentIds: /* @__PURE__ */ new Set(),
2523
+ warnPageBreakProjection: pageBreakProjectionWarn(options?.warn)
2367
2524
  };
2368
2525
  const convertBlocks = (blocks) => {
2369
2526
  const out = [];
@@ -2392,6 +2549,9 @@ function headerFooterToProseDoc(content, options) {
2392
2549
  case "blockSdt":
2393
2550
  out.push(convertBlockSdt(block, convertBlocks));
2394
2551
  break;
2552
+ case "preservedBlock":
2553
+ out.push(convertPreservedBlock(block));
2554
+ break;
2395
2555
  default: panic(`Unsupported block content: ${JSON.stringify(block)}`);
2396
2556
  }
2397
2557
  return out;
@@ -2412,4 +2572,4 @@ function createEmptyDoc() {
2412
2572
  return schema.node("doc", null, [schema.node("paragraph", {}, [])]);
2413
2573
  }
2414
2574
  //#endregion
2415
- export { UnsupportedDocxToProseMirrorConversionError, createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, resolveParagraphDefaultTextFormatting, standaloneTableCellToProseMirror, toProseDoc };
2575
+ export { createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, resolveParagraphDefaultTextFormatting, standaloneTableCellToProseMirror, toProseDoc };