@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,5 +1,8 @@
1
1
  import { isValidHexColor } from "../../utils/colorResolver.js";
2
+ import { normalizeImageLuminancePercent } from "../../utils/imageLuminance.js";
3
+ import { serializePreservedAttributes } from "../attributeRemainder.js";
2
4
  import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
5
+ import { fieldStateAttributes } from "../fieldState.js";
3
6
  import { serializeGraphicFrameLocks } from "../graphicFrameLocks.js";
4
7
  import { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE } from "../imageParser.js";
5
8
  import { canReplayEditableImageRawXml } from "../imageRawXml.js";
@@ -103,10 +106,7 @@ function serializeNoteReference(content) {
103
106
  * Serialize field character (w:fldChar)
104
107
  */
105
108
  function serializeFieldChar(content) {
106
- const attrs = [`w:fldCharType="${content.charType}"`];
107
- if (content.fldLock) attrs.push("w:fldLock=\"true\"");
108
- if (content.dirty) attrs.push("w:dirty=\"true\"");
109
- return `<w:fldChar ${attrs.join(" ")}/>`;
109
+ return `<w:fldChar ${[`w:fldCharType="${content.charType}"`, ...fieldStateAttributes(content)].join(" ")}/>`;
110
110
  }
111
111
  /**
112
112
  * Serialize field instruction text (w:instrText)
@@ -209,6 +209,17 @@ function serializeWrap(wrap) {
209
209
  }
210
210
  }
211
211
  /**
212
+ * `a:xfrm` attributes for an authored transform.
213
+ *
214
+ * Written iff authored: `rot="0"` and `flipH="0"` are OOXML's defaults, so a
215
+ * truthiness guard cannot tell "the author said none" from "the author said
216
+ * nothing", and a save erased an authored zero.
217
+ */
218
+ const serializeTransformAttrs = (transform) => {
219
+ if (transform === void 0) return "";
220
+ return `${transform.rotation === void 0 ? "" : ` rot="${Math.round(transform.rotation * 6e4)}"`}${transform.flipH === void 0 ? "" : ` flipH="${transform.flipH ? 1 : 0}"`}${transform.flipV === void 0 ? "" : ` flipV="${transform.flipV ? 1 : 0}"`}`;
221
+ };
222
+ /**
212
223
  * Build the common a:graphic > pic:pic element for images.
213
224
  *
214
225
  * Takes the relationship id as a separate argument so the caller has to have
@@ -221,18 +232,18 @@ function serializePicGraphic(image, imageRId, sharedId) {
221
232
  const rId = escapeXmlAttribute(imageRId);
222
233
  const id = sharedId;
223
234
  const name = image.filename || `image${id}`;
224
- let xfrmAttrs = "";
225
- if (image.transform?.rotation) xfrmAttrs += ` rot="${Math.round(image.transform.rotation * 6e4)}"`;
226
- if (image.transform?.flipH) xfrmAttrs += " flipH=\"1\"";
227
- if (image.transform?.flipV) xfrmAttrs += " flipV=\"1\"";
235
+ const xfrmAttrs = serializeTransformAttrs(image.transform);
228
236
  const cropAttrs = [];
229
237
  if (image.crop?.left) cropAttrs.push(`l="${Math.round(image.crop.left * 1e5)}"`);
230
238
  if (image.crop?.top) cropAttrs.push(`t="${Math.round(image.crop.top * 1e5)}"`);
231
239
  if (image.crop?.right) cropAttrs.push(`r="${Math.round(image.crop.right * 1e5)}"`);
232
240
  if (image.crop?.bottom) cropAttrs.push(`b="${Math.round(image.crop.bottom * 1e5)}"`);
233
241
  const srcRectEl = cropAttrs.length > 0 ? `<a:srcRect ${cropAttrs.join(" ")}/>` : "";
234
- const alphaChild = image.opacity !== void 0 && image.opacity < 1 ? `<a:alphaModFix amt="${Math.round(Math.max(0, image.opacity) * 1e5)}"/>` : "";
235
- const blipEl = alphaChild ? `<a:blip r:embed="${rId}">${alphaChild}</a:blip>` : `<a:blip r:embed="${rId}"/>`;
242
+ const luminanceAttrs = [];
243
+ if (image.effects?.brightness !== void 0 && Number.isFinite(image.effects.brightness)) luminanceAttrs.push(`bright="${Math.round(normalizeImageLuminancePercent(image.effects.brightness) * 1e3)}"`);
244
+ if (image.effects?.contrast !== void 0 && Number.isFinite(image.effects.contrast)) luminanceAttrs.push(`contrast="${Math.round(normalizeImageLuminancePercent(image.effects.contrast) * 1e3)}"`);
245
+ const blipChildren = `${luminanceAttrs.length === 0 ? "" : `<a:lum ${luminanceAttrs.join(" ")}/>`}${image.opacity !== void 0 && image.opacity < 1 ? `<a:alphaModFix amt="${Math.round(Math.max(0, image.opacity) * 1e5)}"/>` : ""}`;
246
+ const blipEl = blipChildren ? `<a:blip r:embed="${rId}">${blipChildren}</a:blip>` : `<a:blip r:embed="${rId}"/>`;
236
247
  return [
237
248
  "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">",
238
249
  "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">",
@@ -373,13 +384,9 @@ function serializeShapeContent(content) {
373
384
  ...shape.alt !== void 0 ? { alt: shape.alt } : {},
374
385
  ...shape.title !== void 0 ? { title: shape.title } : {}
375
386
  });
376
- let xfrmAttrs = "";
377
- if (shape.transform?.rotation) xfrmAttrs += ` rot="${Math.round(shape.transform.rotation * 6e4)}"`;
378
- if (shape.transform?.flipH) xfrmAttrs += " flipH=\"1\"";
379
- if (shape.transform?.flipV) xfrmAttrs += " flipV=\"1\"";
380
387
  const spPr = [
381
388
  "<wps:spPr>",
382
- `<a:xfrm${xfrmAttrs}>`,
389
+ `<a:xfrm${serializeTransformAttrs(shape.transform)}>`,
383
390
  "<a:off x=\"0\" y=\"0\"/>",
384
391
  `<a:ext cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
385
392
  "</a:xfrm>",
@@ -475,6 +482,7 @@ function serializeRunContent(content) {
475
482
  case "softHyphen": return serializeSoftHyphen(content);
476
483
  case "noBreakHyphen": return serializeNoBreakHyphen(content);
477
484
  case "renderedPageBreak": return "<w:lastRenderedPageBreak/>";
485
+ case "preservedXml": return content.xml;
478
486
  case "drawing":
479
487
  if (content.rawXml && canReplayEditableImageRawXml(content)) return content.rawXml;
480
488
  return serializeDrawingContent(content);
@@ -496,7 +504,8 @@ function serializeRun(run) {
496
504
  const contentXml = serializeRunContent(content);
497
505
  if (contentXml) parts.push(contentXml);
498
506
  }
499
- return `<w:r>${parts.join("")}</w:r>`;
507
+ const attrs = serializePreservedAttributes([], run.preservedAttributes);
508
+ return `<w:r${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:r>`;
500
509
  }
501
510
  /**
502
511
  * Serialize multiple runs to OOXML XML
@@ -1,3 +1,4 @@
1
+ import { serializePreservedAttributes } from "../attributeRemainder.js";
1
2
  import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
2
3
  import { serializeSectionReferenceHistory } from "../sectionReferenceHistory.js";
3
4
  import { serializeBorder } from "./borderSerializer.js";
@@ -53,12 +54,12 @@ function serializePaperSource(props) {
53
54
  return attrs.length > 0 ? `<w:paperSrc ${attrs.join(" ")}/>` : "";
54
55
  }
55
56
  function serializeColumns(props) {
56
- if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0) return "";
57
+ if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0 && props.separator === void 0) return "";
57
58
  const attrs = [];
58
59
  if (props.columnCount !== void 0 && props.columnCount >= 1) attrs.push(`w:num="${intAttr(props.columnCount)}"`);
59
60
  if (props.columnSpace !== void 0) attrs.push(`w:space="${intAttr(props.columnSpace)}"`);
60
61
  if (props.equalWidth !== void 0) attrs.push(`w:equalWidth="${props.equalWidth ? "1" : "0"}"`);
61
- if (props.separator) attrs.push("w:sep=\"1\"");
62
+ if (props.separator !== void 0) attrs.push(`w:sep="${props.separator ? "1" : "0"}"`);
62
63
  const colElements = (props.columns ?? []).map((col) => {
63
64
  const colAttrs = [];
64
65
  if (col.width !== void 0) colAttrs.push(`w:w="${intAttr(col.width)}"`);
@@ -178,8 +179,11 @@ function serializeSectionProperties(props) {
178
179
  for (const change of props.propertyChanges ?? []) parts.push(serializeSectionPropertyChange(change));
179
180
  const unserializedChildNames = getUnserializedSectionPropertyChildNames(props);
180
181
  if (unserializedChildNames && unserializedChildNames.length > 0) return "";
181
- if (parts.length > 0) return `<w:sectPr>${parts.join("")}</w:sectPr>`;
182
- if (Object.keys(props).length > 0) return "";
182
+ const attributes = serializePreservedAttributes([], props.preservedAttributes);
183
+ const startTag = attributes.length > 0 ? `<w:sectPr ${attributes.join(" ")}` : "<w:sectPr";
184
+ if (parts.length > 0) return `${startTag}>${parts.join("")}</w:sectPr>`;
185
+ if (Object.keys(props).some((key) => key !== "preservedAttributes")) return "";
186
+ if (attributes.length > 0) return `${startTag}/>`;
183
187
  return "<w:sectPr/>";
184
188
  }
185
189
  //#endregion
@@ -1,6 +1,7 @@
1
1
  import { canonicalJson } from "../../utils/canonicalJson.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
- import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
3
+ import { serializePreservedAttributes } from "../attributeRemainder.js";
4
+ import { serializeWithPreservedChildren } from "../containerChildren.js";
4
5
  import { TABLE_LOOK_FLAGS } from "../tableLook.js";
5
6
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
6
7
  import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
@@ -369,8 +370,14 @@ const replayableGridChangeXml = (gridChangeXml) => sanitizeCapturedXmlElement(gr
369
370
  * Serialize cell content (paragraphs, nested tables)
370
371
  */
371
372
  function serializeCellContent(content, serializeParagraph) {
372
- const parts = [];
373
- for (const item of content) parts.push(withBlockRangeMarkers(item, item.type === "paragraph" ? serializeParagraph(item) : serializeTable(item, serializeParagraph)));
373
+ const parts = content.map((block) => {
374
+ switch (block.type) {
375
+ case "paragraph": return serializeParagraph(block);
376
+ case "table": return serializeTable(block, serializeParagraph);
377
+ case "preservedBlock": return block.xml;
378
+ default: return block;
379
+ }
380
+ });
374
381
  if (parts.length === 0) parts.push("<w:p/>");
375
382
  return parts.join("");
376
383
  }
@@ -391,8 +398,9 @@ function serializeTableRow(row, serializeParagraph) {
391
398
  const parts = [];
392
399
  const trPrXml = serializeTableRowFormatting(row.formatting, row.propertyChanges, row.structuralChange);
393
400
  if (trPrXml) parts.push(trPrXml);
394
- for (const cell of row.cells) parts.push(serializeTableCell(cell, serializeParagraph));
395
- return `<w:tr>${parts.join("")}</w:tr>`;
401
+ parts.push(serializeWithPreservedChildren(row.cells.map((cell) => serializeTableCell(cell, serializeParagraph)), row.preserved));
402
+ const attrs = serializePreservedAttributes([], row.preservedAttributes);
403
+ return `<w:tr${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:tr>`;
396
404
  }
397
405
  /**
398
406
  * Serialize a table to OOXML XML (w:tbl)
@@ -401,9 +409,11 @@ function serializeTableRow(row, serializeParagraph) {
401
409
  * @returns XML string for the table
402
410
  */
403
411
  function serializeTable(table, serializeParagraph) {
404
- const parts = [serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>", serializeTableGrid(table)];
405
- for (const row of table.rows) parts.push(serializeTableRow(row, serializeParagraph));
406
- return `<w:tbl>${parts.join("")}</w:tbl>`;
412
+ return `<w:tbl>${[
413
+ serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>",
414
+ serializeTableGrid(table),
415
+ serializeWithPreservedChildren(table.rows.map((row) => serializeTableRow(row, serializeParagraph)), table.preserved)
416
+ ].join("")}</w:tbl>`;
407
417
  }
408
418
  /**
409
419
  * Serialize multiple tables to OOXML XML
@@ -203,6 +203,7 @@ const flattenBlocks = (content) => {
203
203
  out.push(block);
204
204
  return;
205
205
  }
206
+ if (block.type === "preservedBlock") return;
206
207
  for (const child of block.content) visit(child);
207
208
  };
208
209
  for (const block of content) visit(block);
@@ -462,7 +463,7 @@ const remapListRendering = (rendering, cloner) => {
462
463
  const collectTableParagraphs = (table) => {
463
464
  const out = [];
464
465
  for (const row of table.rows) for (const cell of row.cells) for (const item of cell.content) if (item.type === "paragraph") out.push(item);
465
- else out.push(...collectTableParagraphs(item));
466
+ else if (item.type === "table") out.push(...collectTableParagraphs(item));
466
467
  return out;
467
468
  };
468
469
  const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
@@ -475,6 +476,7 @@ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner
475
476
  ...structuredClone(cell),
476
477
  content: cell.content.map((item) => {
477
478
  if (item.type === "table") return cloneTable(item);
479
+ if (item.type !== "paragraph") return structuredClone(item);
478
480
  const targetParaId = paraIds.mint(item.paraId);
479
481
  const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
480
482
  if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
@@ -4,7 +4,7 @@ declare const FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME = "prosemirror";
4
4
  /** Maximum accepted size of a complete Yjs collaboration state update. */
5
5
  declare const FOLIO_YJS_UPDATE_MAX_BYTES: number;
6
6
  /** Stable failure codes returned by server-side Yjs-to-DOCX materialization. */
7
- declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "update_too_large"];
7
+ declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "stale_attr_schema", "update_too_large"];
8
8
  /** Failure code for a rejected Yjs-to-DOCX materialization request. */
9
9
  type FolioYjsDocxMaterializationErrorCode = (typeof FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES)[number];
10
10
  declare const FolioYjsDocxMaterializationError_base: import("better-result").TaggedErrorClass<"FolioYjsDocxMaterializationError">;
@@ -1,6 +1,6 @@
1
1
  import { fromProseDoc } from "../../prosemirror/conversion/fromProseDoc.js";
2
2
  import { schema } from "../../prosemirror/schema/index.js";
3
- import { readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsParagraphSourceContract.js";
3
+ import { applyAttrSchemaMigrations, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsDocumentMetadata.js";
4
4
  import { ParagraphPropertySourceValidationError } from "../paragraphPropertySource.js";
5
5
  import { parseDocx } from "../parser.js";
6
6
  import { repackDocx } from "../rezip.js";
@@ -18,6 +18,7 @@ const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES = [
18
18
  "invalid_update",
19
19
  "missing_document",
20
20
  "source_mismatch",
21
+ "stale_attr_schema",
21
22
  "update_too_large"
22
23
  ];
23
24
  /** Typed failure raised when a collaboration snapshot cannot be materialized. */
@@ -40,6 +41,13 @@ const readProseMirrorDocument = (yjsUpdate) => {
40
41
  code: "missing_document",
41
42
  message: "Yjs update does not contain a Folio document."
42
43
  });
44
+ const attrSchemaVersion = readYjsAttrSchemaVersion(ydoc);
45
+ if (attrSchemaVersion.isErr()) throw new FolioYjsDocxMaterializationError({
46
+ cause: attrSchemaVersion.error,
47
+ code: "stale_attr_schema",
48
+ message: attrSchemaVersion.error.message
49
+ });
50
+ applyAttrSchemaMigrations(ydoc, fragment, attrSchemaVersion.value);
43
51
  const contract = readYjsParagraphSourceContract(ydoc);
44
52
  if (!contract) throw new FolioYjsDocxMaterializationError({
45
53
  code: "source_mismatch",
@@ -0,0 +1,55 @@
1
+ import { FolioYjsAttrSchemaVersion } from "../../prosemirror/yjsDocumentMetadata.js";
2
+ import { Result } from "better-result";
3
+ //#region src/docx/server/migrateYjsAttrSchema.d.ts
4
+ /** Stable failure codes returned by an offline snapshot migration. */
5
+ declare const FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES: readonly ["invalid_update", "unsupported_version", "update_too_large"];
6
+ /** Failure code for a snapshot that could not be migrated. */
7
+ type FolioYjsSnapshotMigrationErrorCode = (typeof FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES)[number];
8
+ declare const FolioYjsSnapshotMigrationError_base: import("better-result").TaggedErrorClass<"FolioYjsSnapshotMigrationError">;
9
+ /** Typed failure raised when a collaboration snapshot cannot be migrated. */
10
+ declare class FolioYjsSnapshotMigrationError extends FolioYjsSnapshotMigrationError_base<{
11
+ code: FolioYjsSnapshotMigrationErrorCode;
12
+ message: string;
13
+ cause?: unknown;
14
+ }> {}
15
+ /** What an offline migration did to one snapshot. */
16
+ type FolioYjsSnapshotMigrationResult = {
17
+ /** The complete state update to store in place of the input. */
18
+ update: Uint8Array;
19
+ /** The attr-schema version the input was written under. */
20
+ fromVersion: FolioYjsAttrSchemaVersion;
21
+ /** The attr-schema version the output carries. */
22
+ toVersion: FolioYjsAttrSchemaVersion;
23
+ /** Paragraphs whose attrs the migration rewrote. */
24
+ paragraphsRewritten: number;
25
+ };
26
+ /**
27
+ * Carry a stored collaboration snapshot forward to the attr schema this build
28
+ * writes, without a ProseMirror schema and without rebuilding a single node.
29
+ *
30
+ * A Yjs XML element's attributes are ordinary CRDT map entries holding JSON, so
31
+ * {@link applyAttrSchemaMigrations} rewrites values in the fragment directly.
32
+ * Nothing here can trip y-prosemirror's node rebuild, which deletes an element
33
+ * it fails to construct. The function is pure and offline: no network, no
34
+ * DOCX, no editor.
35
+ *
36
+ * A snapshot already at the current version is returned byte for byte, so a
37
+ * host may run this over its whole corpus repeatedly.
38
+ *
39
+ * **The contract a reseed must respect.** This function never reseeds and never
40
+ * touches the paragraph-source contract: it preserves whichever source binding
41
+ * the snapshot already carries. A reseed does the opposite, and Folio holds it
42
+ * to one rule: *the DOCX handed to the seeding editor must be the same bytes
43
+ * the host will later pass to `materializeYjsDocx` as that room's source*.
44
+ * The contract is a digest of those bytes, so seeding from one document while
45
+ * recording another as the room's source makes every later save fail with
46
+ * `source_mismatch`. The reverse mistake is worse and Folio cannot see it: a
47
+ * host that reseeds from the document a room was *created* from, rather than
48
+ * from the latest content it published, produces a perfectly well-formed
49
+ * snapshot whose collaborative history has been replaced by an older document.
50
+ * Rebind the recorded source in the same statement that clears the seed, or do
51
+ * not clear it.
52
+ */
53
+ declare const migrateFolioYjsSnapshot: (update: Uint8Array) => Result<FolioYjsSnapshotMigrationResult, FolioYjsSnapshotMigrationError>;
54
+ //#endregion
55
+ export { FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES, FolioYjsSnapshotMigrationError, FolioYjsSnapshotMigrationErrorCode, FolioYjsSnapshotMigrationResult, migrateFolioYjsSnapshot };
@@ -0,0 +1,95 @@
1
+ import { applyAttrSchemaMigrations, readYjsAttrSchemaVersion } from "../../prosemirror/yjsDocumentMetadata.js";
2
+ import { FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME } from "./materializeYjsDocx.js";
3
+ import { Result, TaggedError } from "better-result";
4
+ import * as Y from "yjs";
5
+ //#region src/docx/server/migrateYjsAttrSchema.ts
6
+ /** Stable failure codes returned by an offline snapshot migration. */
7
+ const FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES = [
8
+ "invalid_update",
9
+ "unsupported_version",
10
+ "update_too_large"
11
+ ];
12
+ /** Typed failure raised when a collaboration snapshot cannot be migrated. */
13
+ var FolioYjsSnapshotMigrationError = class extends TaggedError("FolioYjsSnapshotMigrationError") {};
14
+ /**
15
+ * Carry a stored collaboration snapshot forward to the attr schema this build
16
+ * writes, without a ProseMirror schema and without rebuilding a single node.
17
+ *
18
+ * A Yjs XML element's attributes are ordinary CRDT map entries holding JSON, so
19
+ * {@link applyAttrSchemaMigrations} rewrites values in the fragment directly.
20
+ * Nothing here can trip y-prosemirror's node rebuild, which deletes an element
21
+ * it fails to construct. The function is pure and offline: no network, no
22
+ * DOCX, no editor.
23
+ *
24
+ * A snapshot already at the current version is returned byte for byte, so a
25
+ * host may run this over its whole corpus repeatedly.
26
+ *
27
+ * **The contract a reseed must respect.** This function never reseeds and never
28
+ * touches the paragraph-source contract: it preserves whichever source binding
29
+ * the snapshot already carries. A reseed does the opposite, and Folio holds it
30
+ * to one rule: *the DOCX handed to the seeding editor must be the same bytes
31
+ * the host will later pass to `materializeYjsDocx` as that room's source*.
32
+ * The contract is a digest of those bytes, so seeding from one document while
33
+ * recording another as the room's source makes every later save fail with
34
+ * `source_mismatch`. The reverse mistake is worse and Folio cannot see it: a
35
+ * host that reseeds from the document a room was *created* from, rather than
36
+ * from the latest content it published, produces a perfectly well-formed
37
+ * snapshot whose collaborative history has been replaced by an older document.
38
+ * Rebind the recorded source in the same statement that clears the seed, or do
39
+ * not clear it.
40
+ */
41
+ const migrateFolioYjsSnapshot = (update) => {
42
+ if (update.byteLength === 0) return Result.err(new FolioYjsSnapshotMigrationError({
43
+ code: "invalid_update",
44
+ message: "Cannot migrate an empty Yjs update."
45
+ }));
46
+ if (update.byteLength > 10485760) return Result.err(new FolioYjsSnapshotMigrationError({
47
+ code: "update_too_large",
48
+ message: "Yjs update exceeds the snapshot migration limit."
49
+ }));
50
+ const ydoc = new Y.Doc();
51
+ const applied = Result.try({
52
+ try: () => {
53
+ Y.applyUpdate(ydoc, update);
54
+ },
55
+ catch: (cause) => new FolioYjsSnapshotMigrationError({
56
+ cause,
57
+ code: "invalid_update",
58
+ message: "Yjs update is not a valid Folio collaboration snapshot."
59
+ })
60
+ });
61
+ if (applied.isErr()) {
62
+ ydoc.destroy();
63
+ return Result.err(applied.error);
64
+ }
65
+ const read = readYjsAttrSchemaVersion(ydoc);
66
+ if (read.isErr()) {
67
+ ydoc.destroy();
68
+ return Result.err(new FolioYjsSnapshotMigrationError({
69
+ cause: read.error,
70
+ code: "unsupported_version",
71
+ message: read.error.message
72
+ }));
73
+ }
74
+ const fromVersion = read.value;
75
+ if (fromVersion === 3) {
76
+ ydoc.destroy();
77
+ return Result.ok({
78
+ fromVersion,
79
+ paragraphsRewritten: 0,
80
+ toVersion: 3,
81
+ update
82
+ });
83
+ }
84
+ const paragraphsRewritten = applyAttrSchemaMigrations(ydoc, ydoc.getXmlFragment(FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME), fromVersion);
85
+ const migrated = Y.encodeStateAsUpdate(ydoc);
86
+ ydoc.destroy();
87
+ return Result.ok({
88
+ fromVersion,
89
+ paragraphsRewritten,
90
+ toVersion: 3,
91
+ update: migrated
92
+ });
93
+ };
94
+ //#endregion
95
+ export { FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES, FolioYjsSnapshotMigrationError, migrateFolioYjsSnapshot };
@@ -27,13 +27,13 @@ function parseTransform(xfrm) {
27
27
  height: parseNumericAttribute(ext, null, "cy") ?? 0
28
28
  };
29
29
  const rotation = rotToDegrees(getAttribute(xfrm, null, "rot"));
30
- const flipH = parseOnOffAttribute(xfrm, null, "flipH") === true;
31
- const flipV = parseOnOffAttribute(xfrm, null, "flipV") === true;
32
- if (rotation === void 0 && !flipH && !flipV) return { size };
30
+ const flipH = parseOnOffAttribute(xfrm, null, "flipH");
31
+ const flipV = parseOnOffAttribute(xfrm, null, "flipV");
32
+ if (rotation === void 0 && flipH === void 0 && flipV === void 0) return { size };
33
33
  const transform = {};
34
34
  if (rotation !== void 0) transform.rotation = rotation;
35
- if (flipH) transform.flipH = true;
36
- if (flipV) transform.flipV = true;
35
+ if (flipH !== void 0) transform.flipH = flipH;
36
+ if (flipV !== void 0) transform.flipV = flipV;
37
37
  return {
38
38
  size,
39
39
  transform
@@ -1,7 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
- import { StyleMap } from "./styleParser.js";
4
3
  import { XmlElement } from "./xmlParser.js";
4
+ import { StyleMap } from "./styleParser.js";
5
5
  //#region src/docx/tableParser.d.ts
6
6
  /**
7
7
  * Sanity cap on `w:gridSpan` (and the derived table column count). Word's