@stll/folio-core 0.43.0 → 0.45.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 (237) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +7 -5
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/ai-edits/snapshot.js +13 -9
  6. package/dist/compare/content-alignment.js +94 -54
  7. package/dist/compare/inline-atoms.js +34 -20
  8. package/dist/compare/style-resources.js +6 -0
  9. package/dist/content-controls/mutateContentControls.js +4 -2
  10. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  11. package/dist/document-operations.js +14 -3
  12. package/dist/docx/appVersionNormalization.d.ts +0 -18
  13. package/dist/docx/blockContentParser.js +8 -0
  14. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  15. package/dist/docx/blockRangeMarkers.js +59 -0
  16. package/dist/docx/bookmarkParser.d.ts +2 -20
  17. package/dist/docx/bookmarkParser.js +6 -30
  18. package/dist/docx/borderParser.d.ts +13 -0
  19. package/dist/docx/borderParser.js +71 -0
  20. package/dist/docx/builtInStyles.d.ts +165 -0
  21. package/dist/docx/builtInStyles.js +239 -0
  22. package/dist/docx/commentIdNormalization.d.ts +3 -1
  23. package/dist/docx/commentIdNormalization.js +18 -1
  24. package/dist/docx/commentParser.d.ts +2 -1
  25. package/dist/docx/commentParser.js +80 -42
  26. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  27. package/dist/docx/commentReferenceNormalization.js +23 -14
  28. package/dist/docx/commentThreadKey.d.ts +18 -0
  29. package/dist/docx/commentThreadKey.js +22 -0
  30. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  31. package/dist/docx/danglingRelationshipReferences.js +30 -0
  32. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  33. package/dist/docx/defaultParagraphStyle.js +23 -1
  34. package/dist/docx/diagramPreview.js +87 -27
  35. package/dist/docx/documentParser.d.ts +2 -1
  36. package/dist/docx/documentParser.js +2 -2
  37. package/dist/docx/drawingUtils.d.ts +8 -1
  38. package/dist/docx/drawingUtils.js +12 -3
  39. package/dist/docx/fieldParser.js +3 -5
  40. package/dist/docx/footnoteParser.d.ts +3 -2
  41. package/dist/docx/footnoteParser.js +19 -4
  42. package/dist/docx/groupDrawingParser.js +4 -4
  43. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  44. package/dist/docx/headerFooterRefParser.js +42 -12
  45. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  46. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  47. package/dist/docx/hyperlinkParser.js +13 -17
  48. package/dist/docx/imageParser.d.ts +10 -2
  49. package/dist/docx/imageParser.js +80 -30
  50. package/dist/docx/imageRawXml.d.ts +14 -1
  51. package/dist/docx/imageRawXml.js +35 -11
  52. package/dist/docx/markupRangeMarker.d.ts +15 -0
  53. package/dist/docx/markupRangeMarker.js +44 -0
  54. package/dist/docx/mathToMathml.js +12 -14
  55. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  56. package/dist/docx/nonVisualDrawingProps.js +46 -0
  57. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  58. package/dist/docx/noteReferenceStyles.js +70 -0
  59. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  60. package/dist/docx/numberingReferenceNormalization.js +20 -1
  61. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  62. package/dist/docx/paragraphParser.js +66 -99
  63. package/dist/docx/paragraphPropertySource.js +1 -0
  64. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  65. package/dist/docx/paragraphTraversal.d.ts +37 -1
  66. package/dist/docx/paragraphTraversal.js +84 -1
  67. package/dist/docx/parseContext.d.ts +37 -0
  68. package/dist/docx/parseContext.js +67 -0
  69. package/dist/docx/parseWarningMessage.d.ts +6 -0
  70. package/dist/docx/parseWarningMessage.js +44 -0
  71. package/dist/docx/parser.js +83 -29
  72. package/dist/docx/previewBudget.d.ts +64 -0
  73. package/dist/docx/previewBudget.js +88 -0
  74. package/dist/docx/relsParser.d.ts +28 -11
  75. package/dist/docx/relsParser.js +26 -13
  76. package/dist/docx/revisionIdNormalization.js +96 -10
  77. package/dist/docx/rezip.js +80 -40
  78. package/dist/docx/runConsolidator.js +1 -2
  79. package/dist/docx/runParser.d.ts +8 -1
  80. package/dist/docx/runParser.js +30 -48
  81. package/dist/docx/sdtPropertiesPatch.js +24 -18
  82. package/dist/docx/sectionParser.d.ts +2 -1
  83. package/dist/docx/sectionParser.js +21 -65
  84. package/dist/docx/sectionReferenceHistory.js +2 -2
  85. package/dist/docx/selectiveSave.js +6 -6
  86. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  87. package/dist/docx/serializer/borderSerializer.d.ts +2 -3
  88. package/dist/docx/serializer/borderSerializer.js +13 -12
  89. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  90. package/dist/docx/serializer/commentSerializer.js +82 -72
  91. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  92. package/dist/docx/serializer/documentSerializer.js +6 -16
  93. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  94. package/dist/docx/serializer/headerFooterSerializer.js +10 -5
  95. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  96. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  97. package/dist/docx/serializer/noteSerializer.js +5 -0
  98. package/dist/docx/serializer/numberingSerializer.js +7 -6
  99. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  100. package/dist/docx/serializer/paragraphSerializer.js +47 -52
  101. package/dist/docx/serializer/partNamespaces.js +2 -2
  102. package/dist/docx/serializer/runSerializer.js +57 -31
  103. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  104. package/dist/docx/serializer/settingsSerializer.js +4 -3
  105. package/dist/docx/serializer/stylesSerializer.js +6 -6
  106. package/dist/docx/serializer/tableSerializer.js +37 -21
  107. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  108. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  109. package/dist/docx/serializer/themeSerializer.js +6 -6
  110. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  111. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  112. package/dist/docx/serializer/xmlUtils.js +1 -13
  113. package/dist/docx/server/boundedArchive.d.ts +12 -0
  114. package/dist/docx/server/boundedArchive.js +20 -1
  115. package/dist/docx/server/build.js +8 -1
  116. package/dist/docx/server/createBilingualDocument.js +10 -18
  117. package/dist/docx/server/extractDocxText.js +3 -4
  118. package/dist/docx/server/validateDocxConformance.js +22 -1
  119. package/dist/docx/shadingParser.d.ts +6 -0
  120. package/dist/docx/shadingParser.js +32 -0
  121. package/dist/docx/shapeParser.js +10 -8
  122. package/dist/docx/styleParser.js +13 -87
  123. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  124. package/dist/docx/styleReferenceResolution.js +51 -0
  125. package/dist/docx/tableLook.d.ts +57 -0
  126. package/dist/docx/tableLook.js +63 -0
  127. package/dist/docx/tableParser.d.ts +7 -9
  128. package/dist/docx/tableParser.js +64 -110
  129. package/dist/docx/textBoxParser.js +11 -6
  130. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  131. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  132. package/dist/docx/transitionalSpelling.d.ts +13 -2
  133. package/dist/docx/transitionalSpelling.js +23 -1
  134. package/dist/docx/unzip.d.ts +23 -0
  135. package/dist/docx/unzip.js +32 -22
  136. package/dist/docx/verbatimCapture.js +5 -12
  137. package/dist/docx/vmlImageParser.js +5 -4
  138. package/dist/docx/vmlPreview.d.ts +1 -3
  139. package/dist/docx/vmlPreview.js +2 -30
  140. package/dist/docx/watermarkParser.js +2 -2
  141. package/dist/docx/xmlParser.d.ts +38 -33
  142. package/dist/docx/xmlParser.js +92 -47
  143. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  144. package/dist/docx/xmlResourceLimits.js +105 -24
  145. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  146. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  147. package/dist/internal/paragraphFormattingSerialization.js +29 -8
  148. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  149. package/dist/layout-engine/index.d.ts +2 -2
  150. package/dist/layout-engine/index.js +2 -2
  151. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  152. package/dist/layout-engine/types.d.ts +8 -2
  153. package/dist/layout-engine/types.js +35 -2
  154. package/dist/layout-painter/renderImage.js +4 -3
  155. package/dist/layout-painter/renderParagraph.js +4 -3
  156. package/dist/managers/autoSaveCodec.js +2 -8
  157. package/dist/markdown/images.js +1 -4
  158. package/dist/markdown/index.js +1 -1
  159. package/dist/markdown/internals.d.ts +6 -1
  160. package/dist/markdown/internals.js +14 -1
  161. package/dist/markdown/renderBlock.js +35 -21
  162. package/dist/markdown/renderParagraph.js +14 -5
  163. package/dist/markdown/renderRuns.js +4 -3
  164. package/dist/markdown/renderTable.js +4 -3
  165. package/dist/markdown/trailers.js +41 -7
  166. package/dist/markdown/types.d.ts +3 -7
  167. package/dist/prosemirror/attrs/index.js +71 -5
  168. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  169. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  170. package/dist/prosemirror/commands/image.js +1 -0
  171. package/dist/prosemirror/commands/index.d.ts +3 -3
  172. package/dist/prosemirror/commands/index.js +2 -2
  173. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  174. package/dist/prosemirror/commands/paragraph.js +2 -2
  175. package/dist/prosemirror/commentIdAllocator.js +2 -7
  176. package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
  177. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  178. package/dist/prosemirror/conversion/toProseDoc.js +458 -335
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  180. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  181. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  182. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  183. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  184. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  185. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  186. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  187. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  188. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  189. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  190. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
  191. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
  192. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  193. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  194. package/dist/prosemirror/extensions/types.d.ts +2 -2
  195. package/dist/prosemirror/index.d.ts +3 -3
  196. package/dist/prosemirror/index.js +3 -3
  197. package/dist/prosemirror/insertOperations.d.ts +9 -2
  198. package/dist/prosemirror/insertOperations.js +9 -4
  199. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
  200. package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
  201. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  202. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  203. package/dist/prosemirror/plugins/index.d.ts +2 -2
  204. package/dist/prosemirror/plugins/index.js +2 -2
  205. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  206. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  207. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  208. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  209. package/dist/prosemirror/schema/nodes.d.ts +81 -1
  210. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  211. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  212. package/dist/prosemirror/styles/styleResolver.js +12 -0
  213. package/dist/style-engine/styleEngine.d.ts +3 -0
  214. package/dist/style-engine/styleEngine.js +3 -0
  215. package/dist/style-sets/extract.js +1 -23
  216. package/dist/style-sets/stellaStyle.js +46 -39
  217. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  218. package/dist/style-sets/styleSetNormalization.js +99 -0
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/base64.d.ts +36 -0
  221. package/dist/utils/base64.js +40 -0
  222. package/dist/utils/clipboard.js +2 -1
  223. package/dist/utils/createDocument.js +145 -20
  224. package/dist/utils/headingCollector.d.ts +8 -5
  225. package/dist/utils/headingCollector.js +23 -25
  226. package/dist/utils/tableOfContentsStyle.js +9 -2
  227. package/dist/utils/units.d.ts +10 -1
  228. package/dist/utils/units.js +12 -1
  229. package/dist/utils/urlSecurity.d.ts +8 -2
  230. package/dist/utils/urlSecurity.js +21 -3
  231. package/package.json +2 -2
  232. package/dist/docx/textWhitespace.d.ts +0 -4
  233. package/dist/docx/textWhitespace.js +0 -4
  234. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  235. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  236. package/dist/markdown/headings.d.ts +0 -13
  237. package/dist/markdown/headings.js +0 -20
@@ -1,8 +1,9 @@
1
1
  import { deterministicHexId } from "../../utils/hexId.js";
2
+ import { commentThreadParaId } from "../commentThreadKey.js";
2
3
  import { serializeParagraph } from "./paragraphSerializer.js";
3
4
  import { serializePartElement } from "./partNamespaces.js";
4
5
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
5
- import { escapeXml } from "./xmlUtils.js";
6
+ import { escapeXmlAttribute } from "@stll/docx-core";
6
7
  //#region src/docx/serializer/commentSerializer.ts
7
8
  /**
8
9
  * Comment Serializer
@@ -26,9 +27,9 @@ function serializeParagraphWithAnnotationRef(paragraph, formatting) {
26
27
  return xml.replace(/^<w:p(?=[\s>])[^>]*>/u, (openingTag) => `${openingTag}${annotationReference}`);
27
28
  }
28
29
  function serializeComment(comment) {
29
- const attrs = [`w:id="${comment.id}"`, `w:author="${escapeXml(comment.author)}"`];
30
- if (comment.initials !== void 0) attrs.push(`w:initials="${escapeXml(comment.initials)}"`);
31
- if (comment.date) attrs.push(`w:date="${escapeXml(comment.date)}"`);
30
+ const attrs = [`w:id="${comment.id}"`, `w:author="${escapeXmlAttribute(comment.author)}"`];
31
+ if (comment.initials !== void 0) attrs.push(`w:initials="${escapeXmlAttribute(comment.initials)}"`);
32
+ if (comment.date) attrs.push(`w:date="${escapeXmlAttribute(comment.date)}"`);
32
33
  let xml = `<w:comment ${attrs.join(" ")}>`;
33
34
  if (comment.content.length > 0) {
34
35
  xml += serializeParagraphWithAnnotationRef(comment.content[0], comment.annotationReferenceFormatting);
@@ -61,33 +62,6 @@ const COMMENTS_EXTENDED_BASELINE_PREFIXES = [
61
62
  ];
62
63
  const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
63
64
  /**
64
- * Serialize comments array to comments.xml content. Returns a valid empty
65
- * `<w:comments/>` document for an empty array so callers can overwrite an
66
- * existing `word/comments.xml` part when the editor has removed the last
67
- * comment — leaving the previous file in place would otherwise re-emit
68
- * the orphaned comment threads on every save.
69
- */
70
- function serializeComments(comments, sourceBindings) {
71
- const topLevel = [];
72
- const replies = [];
73
- for (const c of comments) {
74
- const { parentId } = c;
75
- (parentId === null || parentId === void 0 ? topLevel : replies).push(c);
76
- }
77
- const body = topLevel.map((comment) => serializeComment(comment)).join("") + replies.map((reply) => serializeComment(reply)).join("");
78
- return XML_DECLARATION + serializePartElement({
79
- partPath: "word/comments.xml",
80
- rootName: "w:comments",
81
- baselinePrefixes: COMMENTS_BASELINE_PREFIXES,
82
- sourceBindings,
83
- body
84
- });
85
- }
86
- /** The `w14:paraId` Word threads a comment by: its LAST paragraph's paraId. */
87
- function commentThreadParaId(comment) {
88
- return comment.content?.at(-1)?.paraId;
89
- }
90
- /**
91
65
  * The ids of comments that need a `commentsExtended.xml` entry: a reply, a
92
66
  * reply's parent (the thread root), or any comment carrying a resolved state.
93
67
  * A plain top-level comment set yields an empty set, so no part is written.
@@ -108,29 +82,71 @@ const commentPlainText = (comment) => {
108
82
  return text;
109
83
  };
110
84
  /**
111
- * Assign a deterministic `w14:paraId` to the LAST paragraph of every comment
112
- * that needs a commentsExtended entry (a reply, a reply's parent, or a resolved
113
- * comment) but has none. A document authored or loaded without comment paraIds
114
- * would otherwise have no stable key to thread through commentsExtended.xml, and
115
- * the thread link would be silently dropped. Word-authored ids are preserved;
116
- * only threaded, id-less paragraphs are filled. Deterministic (content- and
117
- * id-derived) so repeated saves mint the SAME id. MUST run before serializing
118
- * BOTH comments.xml and commentsExtended.xml so the two reference the same id.
85
+ * Plan both comment parts from the model, in the model's own order.
86
+ *
87
+ * The order is the document's: the order `word/comments.xml` listed the
88
+ * comments in is the order it is written back in, so a save neither reshuffles
89
+ * a reviewer's threads nor hands the next parse a different `comments[]` than
90
+ * the one it read.
91
+ *
92
+ * Planning also mints the `w14:paraId` a thread needs when the model has none —
93
+ * a comment written in the editor has no Word-authored id, and without one the
94
+ * `commentsExtended.xml` link would be dropped. Minting is deterministic
95
+ * (comment id and text derived) so repeated saves mint the same id, and it
96
+ * happens here rather than at each call site because both parts must see it.
119
97
  */
120
- function ensureThreadedCommentParaIds(comments) {
121
- const threaded = threadedCommentIds(comments);
122
- if (threaded.size === 0) return;
123
- const used = /* @__PURE__ */ new Set();
124
- for (const comment of comments) for (const paragraph of comment.content ?? []) if (paragraph.paraId) used.add(paragraph.paraId.toUpperCase());
98
+ const planCommentParts = (comments) => {
99
+ const byId = /* @__PURE__ */ new Map();
100
+ const order = [];
125
101
  for (const comment of comments) {
126
- if (!threaded.has(comment.id)) continue;
102
+ if (byId.has(comment.id)) continue;
103
+ byId.set(comment.id, comment);
104
+ order.push(comment.id);
105
+ }
106
+ const planned = [...byId.values()];
107
+ const threadedIds = threadedCommentIds(planned);
108
+ const used = /* @__PURE__ */ new Set();
109
+ for (const comment of planned) for (const paragraph of comment.content ?? []) if (paragraph.paraId) used.add(paragraph.paraId.toUpperCase());
110
+ const threadParaIdById = /* @__PURE__ */ new Map();
111
+ for (const id of order) {
112
+ const comment = byId.get(id);
113
+ const existing = commentThreadParaId((comment.content ?? []).map(({ paraId }) => paraId));
114
+ if (existing) {
115
+ threadParaIdById.set(id, existing);
116
+ continue;
117
+ }
127
118
  const last = (comment.content ?? []).at(-1);
128
- if (!last || last.paraId) continue;
129
- let paraId = deterministicHexId(`comment:${comment.id}:${commentPlainText(comment)}`);
130
- for (let salt = 1; used.has(paraId.toUpperCase()); salt++) paraId = deterministicHexId(`comment:${comment.id}:${salt}`);
131
- used.add(paraId.toUpperCase());
132
- last.paraId = paraId;
119
+ if (!threadedIds.has(id) || !last) continue;
120
+ let minted = deterministicHexId(`comment:${id}:${commentPlainText(comment)}`);
121
+ for (let salt = 1; used.has(minted.toUpperCase()); salt++) minted = deterministicHexId(`comment:${id}:${salt}`);
122
+ used.add(minted.toUpperCase());
123
+ last.paraId = minted;
124
+ threadParaIdById.set(id, minted);
133
125
  }
126
+ return {
127
+ order,
128
+ byId,
129
+ threadParaIdById,
130
+ threadedIds
131
+ };
132
+ };
133
+ /**
134
+ * Serialize a comment plan to comments.xml content. Returns a valid empty
135
+ * `<w:comments/>` document for an empty plan so callers can overwrite an
136
+ * existing `word/comments.xml` part when the editor has removed the last
137
+ * comment — leaving the previous file in place would otherwise re-emit
138
+ * the orphaned comment threads on every save.
139
+ */
140
+ function serializeComments({ order, byId }, sourceBindings) {
141
+ let body = "";
142
+ for (const id of order) body += serializeComment(byId.get(id));
143
+ return XML_DECLARATION + serializePartElement({
144
+ partPath: "word/comments.xml",
145
+ rootName: "w:comments",
146
+ baselinePrefixes: COMMENTS_BASELINE_PREFIXES,
147
+ sourceBindings,
148
+ body
149
+ });
134
150
  }
135
151
  /**
136
152
  * Build the `commentsExtended.xml` entries for the comments that participate in
@@ -139,29 +155,23 @@ function ensureThreadedCommentParaIds(comments) {
139
155
  * leave any existing part untouched — a plain top-level comment set gets no
140
156
  * `commentsExtended.xml`, matching how Word omits it.
141
157
  *
142
- * A `w15:commentEx` keys on the comment's LAST paragraph paraId; a reply's
143
- * `w15:paraIdParent` points at its parent comment's last-paragraph paraId. Ids
144
- * are guaranteed present by {@link ensureThreadedCommentParaIds}; the `!paraId`
145
- * skip is a last-ditch safety for a malformed model.
158
+ * A `w15:commentEx` keys on the comment's thread paraId; a reply's
159
+ * `w15:paraIdParent` on its parent's. Both come from the plan, so a reply and
160
+ * its parent name each other by `w:id` right up to the attribute.
146
161
  */
147
- function buildCommentExtendedEntries(comments) {
148
- const threaded = threadedCommentIds(comments);
149
- if (threaded.size === 0) return null;
150
- const paraIdByCommentId = /* @__PURE__ */ new Map();
151
- for (const comment of comments) {
152
- const paraId = commentThreadParaId(comment);
153
- if (paraId) paraIdByCommentId.set(comment.id, paraId);
154
- }
162
+ function buildCommentExtendedEntries({ order, byId, threadParaIdById, threadedIds }) {
163
+ if (threadedIds.size === 0) return null;
155
164
  const entries = [];
156
- for (const comment of comments) {
157
- if (!threaded.has(comment.id)) continue;
158
- const paraId = paraIdByCommentId.get(comment.id);
165
+ for (const id of order) {
166
+ if (!threadedIds.has(id)) continue;
167
+ const paraId = threadParaIdById.get(id);
159
168
  if (!paraId) continue;
160
- const parentParaId = comment.parentId !== void 0 ? paraIdByCommentId.get(comment.parentId) : void 0;
169
+ const { parentId, done } = byId.get(id);
170
+ const parentParaId = parentId !== void 0 ? threadParaIdById.get(parentId) : void 0;
161
171
  entries.push({
162
172
  paraId,
163
173
  ...parentParaId !== void 0 ? { paraIdParent: parentParaId } : {},
164
- done: comment.done ?? false
174
+ done: done ?? false
165
175
  });
166
176
  }
167
177
  return entries.length > 0 ? entries : null;
@@ -172,12 +182,12 @@ function buildCommentExtendedEntries(comments) {
172
182
  * {@link buildCommentExtendedEntries}). This is the part that makes Word render
173
183
  * a comment as a REPLY rather than a separate top-level thread.
174
184
  */
175
- function serializeCommentsExtended(comments) {
176
- const entries = buildCommentExtendedEntries(comments);
185
+ function serializeCommentsExtended(plan) {
186
+ const entries = buildCommentExtendedEntries(plan);
177
187
  if (!entries) return null;
178
188
  const body = entries.map((entry) => {
179
- const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
180
- return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
189
+ const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${escapeXmlAttribute(entry.paraIdParent)}"` : "";
190
+ return `<w15:commentEx w15:paraId="${escapeXmlAttribute(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
181
191
  }).join("");
182
192
  return XML_DECLARATION + serializePartElement({
183
193
  partPath: "word/commentsExtended.xml",
@@ -188,4 +198,4 @@ function serializeCommentsExtended(comments) {
188
198
  });
189
199
  }
190
200
  //#endregion
191
- export { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended };
201
+ export { planCommentParts, serializeComments, serializeCommentsExtended };
@@ -47,10 +47,6 @@ declare function getDocumentParagraphCount(doc: document_d_exports.Document): nu
47
47
  * Get table count in document
48
48
  */
49
49
  declare function getDocumentTableCount(doc: document_d_exports.Document): number;
50
- /**
51
- * Get plain text from document (for comparison/debugging)
52
- */
53
- declare function getDocumentPlainText(doc: document_d_exports.Document): string;
54
50
  /**
55
51
  * Create an empty document
56
52
  */
@@ -63,4 +59,4 @@ declare function createSimpleDocument(paragraphs: {
63
59
  styleId?: string;
64
60
  }[]): document_d_exports.Document;
65
61
  //#endregion
66
- export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentPlainText, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
62
+ export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
@@ -1,3 +1,4 @@
1
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
1
2
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
3
  import { serializeParagraph } from "./paragraphSerializer.js";
3
4
  import { serializePartElement } from "./partNamespaces.js";
@@ -40,6 +41,10 @@ const DOCUMENT_BASELINE_PREFIXES = [
40
41
  * Serialize a single block content item (paragraph, table, or block-level SDT).
41
42
  */
42
43
  function serializeBlockContent(block) {
44
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
45
+ }
46
+ /** The block itself, without the range markers that stood around it. */
47
+ function serializeBlockBody(block) {
43
48
  if (block.type === "paragraph") return serializeParagraph(block);
44
49
  if (block.type === "table") return serializeTable(block, serializeParagraph);
45
50
  return serializeBlockSdt(block, serializeBlockContent);
@@ -127,21 +132,6 @@ function getDocumentTableCount(doc) {
127
132
  return doc.package.document.content.filter((b) => b.type === "table").length;
128
133
  }
129
134
  /**
130
- * Get plain text from document (for comparison/debugging)
131
- */
132
- function getDocumentPlainText(doc) {
133
- const texts = [];
134
- for (const block of doc.package.document.content) if (block.type === "paragraph") {
135
- for (const content of block.content) if (content.type === "run") {
136
- for (const item of content.content) if (item.type === "text") texts.push(item.text);
137
- else if (item.type === "tab") texts.push(" ");
138
- else if (item.type === "break") texts.push("\n");
139
- }
140
- texts.push("\n");
141
- }
142
- return texts.join("");
143
- }
144
- /**
145
135
  * Create an empty document
146
136
  */
147
137
  function createEmptyDocument() {
@@ -164,4 +154,4 @@ function createSimpleDocument(paragraphs) {
164
154
  })) } } };
165
155
  }
166
156
  //#endregion
167
- export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentPlainText, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
157
+ export { createEmptyDocument, createSimpleDocument, getDocumentContentCount, getDocumentParagraphCount, getDocumentTableCount, hasDocumentContent, hasDocumentSections, hasSectionProperties, serializeDocument, serializeDocumentBody, serializeDocumentBodyElement };
@@ -1,5 +1,5 @@
1
1
  import { serializePartElement } from "./partNamespaces.js";
2
- import { escapeXml } from "./xmlUtils.js";
2
+ import { escapeXmlAttribute } from "@stll/docx-core";
3
3
  //#region src/docx/serializer/fontTableSerializer.ts
4
4
  const serializeFontTableXml = (fontTable) => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
5
5
  partPath: "word/fontTable.xml",
@@ -10,20 +10,20 @@ const serializeFontTableXml = (fontTable) => "<?xml version=\"1.0\" encoding=\"U
10
10
  });
11
11
  const serializeFont = (font) => {
12
12
  const parts = [];
13
- if (font.altName) parts.push(`<w:altName w:val="${escapeXml(font.altName)}"/>`);
14
- if (font.panose1) parts.push(`<w:panose1 w:val="${escapeXml(font.panose1)}"/>`);
15
- if (font.charset) parts.push(`<w:charset w:val="${escapeXml(font.charset)}"/>`);
13
+ if (font.altName) parts.push(`<w:altName w:val="${escapeXmlAttribute(font.altName)}"/>`);
14
+ if (font.panose1) parts.push(`<w:panose1 w:val="${escapeXmlAttribute(font.panose1)}"/>`);
15
+ if (font.charset) parts.push(`<w:charset w:val="${escapeXmlAttribute(font.charset)}"/>`);
16
16
  if (font.family) parts.push(`<w:family w:val="${font.family}"/>`);
17
17
  if (font.pitch) parts.push(`<w:pitch w:val="${font.pitch}"/>`);
18
18
  if (font.sig) {
19
19
  const attrs = [];
20
20
  for (const [key, value] of Object.entries(font.sig)) {
21
21
  if (value === void 0) continue;
22
- attrs.push(`w:${key}="${escapeXml(value)}"`);
22
+ attrs.push(`w:${key}="${escapeXmlAttribute(value)}"`);
23
23
  }
24
24
  if (attrs.length > 0) parts.push(`<w:sig ${attrs.join(" ")}/>`);
25
25
  }
26
- return `<w:font w:name="${escapeXml(font.name)}">${parts.join("")}</w:font>`;
26
+ return `<w:font w:name="${escapeXmlAttribute(font.name)}">${parts.join("")}</w:font>`;
27
27
  };
28
28
  //#endregion
29
29
  export { serializeFontTableXml };
@@ -1,3 +1,4 @@
1
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
1
2
  import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
2
3
  import { isEmptyParagraph } from "../paragraphParser.js";
3
4
  import { captureVerbatimXml } from "../verbatimCapture.js";
@@ -6,7 +7,7 @@ import { serializeBlockSdt } from "./blockSdtSerializer.js";
6
7
  import { serializeParagraph } from "./paragraphSerializer.js";
7
8
  import { serializePartElement } from "./partNamespaces.js";
8
9
  import { serializeTable } from "./tableSerializer.js";
9
- import { escapeXml } from "./xmlUtils.js";
10
+ import { escapeXmlAttribute } from "@stll/docx-core";
10
11
  //#region src/docx/serializer/headerFooterSerializer.ts
11
12
  const HEADER_FOOTER_BASELINE_PREFIXES = [
12
13
  "wpc",
@@ -36,6 +37,10 @@ const HEADER_FOOTER_BASELINE_PREFIXES = [
36
37
  * header/footer.
37
38
  */
38
39
  function serializeBlock(block) {
40
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
41
+ }
42
+ /** The block itself, without the range markers that stood around it. */
43
+ function serializeBlockBody(block) {
39
44
  if (block.type === "paragraph") return serializeParagraph(block);
40
45
  if (block.type === "table") return serializeTable(block, serializeParagraph);
41
46
  return serializeBlockSdt(block, serializeBlock);
@@ -80,7 +85,7 @@ function serializeRawWatermarkIntoHost({ hostXml, rawWatermarkXml }) {
80
85
  if (!rawHost || getLocalName(rawHost.name ?? "") !== "p") return rawWatermarkXml;
81
86
  const watermarkContent = (rawHost.elements ?? []).filter((child) => child.type === "element" && getLocalName(child.name ?? "") !== "pPr").map((child) => captureVerbatimXml(child)).join("");
82
87
  if (!watermarkContent) return rawWatermarkXml;
83
- const namespaceAttributes = Object.entries(rawHost.attributes ?? {}).filter(([name, value]) => value !== void 0 && (name === "xmlns" || name.startsWith("xmlns:"))).map(([name, value]) => ` ${name}="${escapeXml(String(value))}"`).join("");
88
+ const namespaceAttributes = Object.entries(rawHost.attributes ?? {}).filter(([name, value]) => value !== void 0 && (name === "xmlns" || name.startsWith("xmlns:"))).map(([name, value]) => ` ${name}="${escapeXmlAttribute(String(value))}"`).join("");
84
89
  return hostXml.replace(/^<w:p(?=[\s>])/u, `<w:p${namespaceAttributes}`).replace("</w:p>", `${watermarkContent}</w:p>`);
85
90
  }
86
91
  function serializeWatermarkParagraph(hf) {
@@ -96,15 +101,15 @@ function synthesizeTextWatermark(watermark) {
96
101
  const rotation = watermark.diagonal === false ? 0 : 315;
97
102
  const fillcolor = watermark.color && watermark.color !== "auto" ? `#${watermark.color}` : "#C0C0C0";
98
103
  const fontFamily = watermark.font ?? "Calibri";
99
- const text = escapeXml(watermark.text);
100
- return `<w:p><w:r><w:pict><v:shape id="PowerPlusWaterMarkObject1" type="#_x0000_t136" style="position:absolute;margin-left:0;margin-top:0;width:415pt;height:207pt;rotation:${rotation};z-index:-251658240;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin" fillcolor="${fillcolor}" stroked="f">${watermark.opacity !== void 0 ? `<v:fill opacity="${watermark.opacity}"/>` : ""}<v:textpath style="font-family:&quot;${escapeXml(fontFamily)}&quot;;font-size:1pt" string="${text}"/></v:shape></w:pict></w:r></w:p>`;
104
+ const text = escapeXmlAttribute(watermark.text);
105
+ return `<w:p><w:r><w:pict><v:shape id="PowerPlusWaterMarkObject1" type="#_x0000_t136" style="position:absolute;margin-left:0;margin-top:0;width:415pt;height:207pt;rotation:${rotation};z-index:-251658240;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin" fillcolor="${fillcolor}" stroked="f">${watermark.opacity !== void 0 ? `<v:fill opacity="${watermark.opacity}"/>` : ""}<v:textpath style="font-family:&quot;${escapeXmlAttribute(fontFamily)}&quot;;font-size:1pt" string="${text}"/></v:shape></w:pict></w:r></w:p>`;
101
106
  }
102
107
  const PICTURE_WATERMARK_DEFAULT_WIDTH_PT = 415;
103
108
  const PICTURE_WATERMARK_DEFAULT_HEIGHT_PT = 207;
104
109
  const PICTURE_WATERMARK_WASHOUT_GAIN = "19661f";
105
110
  const PICTURE_WATERMARK_WASHOUT_BLACKLEVEL = "22938f";
106
111
  function synthesizePictureWatermark(watermark) {
107
- const rId = escapeXml(watermark.imageRId);
112
+ const rId = escapeXmlAttribute(watermark.imageRId);
108
113
  return `<w:p><w:r><w:pict><v:shape id="WordPictureWatermark1" type="#_x0000_t75" style="position:absolute;margin-left:0;margin-top:0;width:${watermark.scale === void 0 ? watermark.widthPt ?? PICTURE_WATERMARK_DEFAULT_WIDTH_PT : PICTURE_WATERMARK_DEFAULT_WIDTH_PT * watermark.scale}pt;height:${watermark.scale === void 0 ? watermark.heightPt ?? PICTURE_WATERMARK_DEFAULT_HEIGHT_PT : PICTURE_WATERMARK_DEFAULT_HEIGHT_PT * watermark.scale}pt;z-index:-251658240;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin"><v:imagedata r:id="${rId}" o:title=""${watermark.washout === false ? "" : ` gain="${PICTURE_WATERMARK_WASHOUT_GAIN}" blacklevel="${PICTURE_WATERMARK_WASHOUT_BLACKLEVEL}"`}/></v:shape></w:pict></w:r></w:p>`;
109
114
  }
110
115
  //#endregion
@@ -0,0 +1,8 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ //#region src/docx/serializer/markupRangeAttributes.d.ts
3
+ declare const markupRangeAttributes: (marker: document_d_exports.MarkupRangeMarker) => string[];
4
+ declare const bookmarkRangeAttributes: (marker: document_d_exports.BookmarkRangeMarker) => string[];
5
+ /** `w:author` is required, so it is always written; `w:date` only when known. */
6
+ declare const moveBookmarkAttributes: (marker: document_d_exports.MoveBookmarkMarker) => string[];
7
+ //#endregion
8
+ export { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes };
@@ -0,0 +1,24 @@
1
+ import { escapeXmlAttribute } from "@stll/docx-core";
2
+ //#region src/docx/serializer/markupRangeAttributes.ts
3
+ const attribute = (name, value) => `${name}="${escapeXmlAttribute(String(value))}"`;
4
+ const markupRangeAttributes = (marker) => {
5
+ const attributes = [attribute("w:id", marker.id)];
6
+ if (marker.displacedByCustomXml !== void 0) attributes.push(attribute("w:displacedByCustomXml", marker.displacedByCustomXml));
7
+ return attributes;
8
+ };
9
+ const bookmarkRangeAttributes = (marker) => {
10
+ const attributes = markupRangeAttributes(marker);
11
+ if (marker.colFirst !== void 0) attributes.push(attribute("w:colFirst", marker.colFirst));
12
+ if (marker.colLast !== void 0) attributes.push(attribute("w:colLast", marker.colLast));
13
+ attributes.push(attribute("w:name", marker.name));
14
+ return attributes;
15
+ };
16
+ /** `w:author` is required, so it is always written; `w:date` only when known. */
17
+ const moveBookmarkAttributes = (marker) => {
18
+ const attributes = bookmarkRangeAttributes(marker);
19
+ attributes.push(attribute("w:author", marker.author.trim() || "Unknown"));
20
+ if (marker.date !== void 0 && marker.date.trim().length > 0) attributes.push(attribute("w:date", marker.date.trim()));
21
+ return attributes;
22
+ };
23
+ //#endregion
24
+ export { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes };
@@ -1,3 +1,4 @@
1
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
1
2
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
3
  import { serializeParagraph } from "./paragraphSerializer.js";
3
4
  import { serializePartElement } from "./partNamespaces.js";
@@ -38,6 +39,10 @@ const serializeNotePart = (elementName, body) => "<?xml version=\"1.0\" encoding
38
39
  * note body.
39
40
  */
40
41
  function serializeBlock(block) {
42
+ return withBlockRangeMarkers(block, serializeBlockBody(block));
43
+ }
44
+ /** The block itself, without the range markers that stood around it. */
45
+ function serializeBlockBody(block) {
41
46
  if (block.type === "paragraph") return serializeParagraph(block);
42
47
  if (block.type === "table") return serializeTable(block, serializeParagraph);
43
48
  return serializeBlockSdt(block, serializeBlock);
@@ -1,6 +1,7 @@
1
1
  import { serializePartElement } from "./partNamespaces.js";
2
2
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
3
- import { escapeXml, intAttr } from "./xmlUtils.js";
3
+ import { intAttr } from "./xmlUtils.js";
4
+ import { escapeXmlAttribute } from "@stll/docx-core";
4
5
  //#region src/docx/serializer/numberingSerializer.ts
5
6
  /**
6
7
  * Serialize a level's paragraph properties — the modeled subset is indentation
@@ -36,11 +37,11 @@ function serializeLevelParagraphProps(pPr) {
36
37
  function serializeLevel(level) {
37
38
  const parts = [];
38
39
  if (level.start !== void 0) parts.push(`<w:start w:val="${intAttr(level.start)}"/>`);
39
- parts.push(`<w:numFmt w:val="${escapeXml(level.numFmt)}"/>`);
40
+ parts.push(`<w:numFmt w:val="${escapeXmlAttribute(level.numFmt)}"/>`);
40
41
  if (level.lvlRestart !== void 0) parts.push(`<w:lvlRestart w:val="${intAttr(level.lvlRestart)}"/>`);
41
42
  if (level.isLgl) parts.push("<w:isLgl/>");
42
43
  if (level.suffix) parts.push(`<w:suff w:val="${level.suffix}"/>`);
43
- parts.push(`<w:lvlText w:val="${escapeXml(level.lvlText)}"/>`);
44
+ parts.push(`<w:lvlText w:val="${escapeXmlAttribute(level.lvlText)}"/>`);
44
45
  if (level.legacy) {
45
46
  const legacyAttrs = [`w:legacy="${level.legacy.legacy ? 1 : 0}"`];
46
47
  if (level.legacy.legacySpace !== void 0) legacyAttrs.push(`w:legacySpace="${intAttr(level.legacy.legacySpace)}"`);
@@ -60,9 +61,9 @@ function serializeLevel(level) {
60
61
  function serializeAbstractNum(abstractNum) {
61
62
  const parts = [];
62
63
  if (abstractNum.multiLevelType) parts.push(`<w:multiLevelType w:val="${abstractNum.multiLevelType}"/>`);
63
- if (abstractNum.name !== void 0) parts.push(`<w:name w:val="${escapeXml(abstractNum.name)}"/>`);
64
- if (abstractNum.styleLink !== void 0) parts.push(`<w:styleLink w:val="${escapeXml(abstractNum.styleLink)}"/>`);
65
- if (abstractNum.numStyleLink !== void 0) parts.push(`<w:numStyleLink w:val="${escapeXml(abstractNum.numStyleLink)}"/>`);
64
+ if (abstractNum.name !== void 0) parts.push(`<w:name w:val="${escapeXmlAttribute(abstractNum.name)}"/>`);
65
+ if (abstractNum.styleLink !== void 0) parts.push(`<w:styleLink w:val="${escapeXmlAttribute(abstractNum.styleLink)}"/>`);
66
+ if (abstractNum.numStyleLink !== void 0) parts.push(`<w:numStyleLink w:val="${escapeXmlAttribute(abstractNum.numStyleLink)}"/>`);
66
67
  const levels = [...abstractNum.levels].sort((a, b) => a.ilvl - b.ilvl);
67
68
  for (const level of levels) parts.push(serializeLevel(level));
68
69
  return `<w:abstractNum w:abstractNumId="${intAttr(abstractNum.abstractNumId)}">${parts.join("")}</w:abstractNum>`;
@@ -23,10 +23,6 @@ declare function hasParagraphContent(paragraph: document_d_exports.Paragraph): b
23
23
  * Check if a paragraph has formatting
24
24
  */
25
25
  declare function hasParagraphFormatting(paragraph: document_d_exports.Paragraph): boolean;
26
- /**
27
- * Get plain text from a paragraph (for comparison/debugging)
28
- */
29
- declare function getParagraphPlainText(paragraph: document_d_exports.Paragraph): string;
30
26
  /**
31
27
  * Create an empty paragraph
32
28
  */
@@ -44,4 +40,4 @@ declare function isListParagraph(paragraph: document_d_exports.Paragraph): boole
44
40
  */
45
41
  declare function getListLevel(paragraph: document_d_exports.Paragraph): number;
46
42
  //#endregion
47
- export { createEmptyParagraph, createTextParagraph, getListLevel, getParagraphPlainText, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
43
+ export { createEmptyParagraph, createTextParagraph, getListLevel, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };