@portone/docx-editor 0.2.0 → 0.3.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 (256) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/CONTRIBUTING.md +9 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +12 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +14 -7
  10. package/dist/docx/commentOnlyChange.js +9 -139
  11. package/dist/docx/comments/constants.d.ts +3 -4
  12. package/dist/docx/comments/constants.js +4 -5
  13. package/dist/docx/comments/grammar.d.ts +86 -0
  14. package/dist/docx/comments/grammar.js +223 -0
  15. package/dist/docx/comments/parts.d.ts +52 -0
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +28 -106
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.d.ts +5 -3
  22. package/dist/docx/comments/reading.js +30 -29
  23. package/dist/docx/comments/writing.d.ts +19 -7
  24. package/dist/docx/comments/writing.js +105 -137
  25. package/dist/docx/documentSettings.d.ts +7 -0
  26. package/dist/docx/documentSettings.js +10 -2
  27. package/dist/docx/exportDocx.d.ts +40 -5
  28. package/dist/docx/exportDocx.js +71 -72
  29. package/dist/docx/exportRefs.d.ts +5 -2
  30. package/dist/docx/exportRefs.js +3 -1
  31. package/dist/docx/fidelity.d.ts +43 -0
  32. package/dist/docx/fidelity.js +64 -0
  33. package/dist/docx/formatting/attrs.d.ts +27 -0
  34. package/dist/docx/formatting/attrs.js +31 -0
  35. package/dist/docx/formatting/context.d.ts +31 -0
  36. package/dist/docx/formatting/context.js +42 -0
  37. package/dist/docx/formatting/direct.d.ts +6 -5
  38. package/dist/docx/formatting/direct.js +52 -81
  39. package/dist/docx/formatting/resolve.d.ts +45 -0
  40. package/dist/docx/formatting/resolve.js +140 -0
  41. package/dist/docx/formatting/runProperties.d.ts +93 -0
  42. package/dist/docx/formatting/runProperties.js +316 -0
  43. package/dist/docx/formatting/styles.js +3 -3
  44. package/dist/docx/formatting/tabStops.js +7 -22
  45. package/dist/docx/formatting.d.ts +4 -1
  46. package/dist/docx/formatting.js +4 -1
  47. package/dist/docx/headersFooters.js +13 -22
  48. package/dist/docx/identities.d.ts +67 -0
  49. package/dist/docx/identities.js +174 -0
  50. package/dist/docx/importDocx.d.ts +12 -1
  51. package/dist/docx/importDocx.js +72 -79
  52. package/dist/docx/importParagraph.d.ts +1 -1
  53. package/dist/docx/importParagraph.js +4 -3
  54. package/dist/docx/importTable.d.ts +1 -1
  55. package/dist/docx/importTable.js +17 -1
  56. package/dist/docx/invariants.d.ts +33 -0
  57. package/dist/docx/invariants.js +256 -0
  58. package/dist/docx/media.d.ts +6 -4
  59. package/dist/docx/media.js +6 -37
  60. package/dist/docx/newLists.d.ts +20 -0
  61. package/dist/docx/newLists.js +36 -0
  62. package/dist/docx/notes.js +6 -12
  63. package/dist/docx/numberingPlanner.d.ts +8 -0
  64. package/dist/docx/numberingPlanner.js +19 -0
  65. package/dist/docx/packageParts.d.ts +42 -0
  66. package/dist/docx/packageParts.js +135 -0
  67. package/dist/docx/pageGeometry.d.ts +2 -0
  68. package/dist/docx/pageGeometry.js +18 -9
  69. package/dist/docx/paraProps.d.ts +10 -22
  70. package/dist/docx/paraProps.js +75 -99
  71. package/dist/docx/partPlan.d.ts +36 -0
  72. package/dist/docx/partPlan.js +59 -0
  73. package/dist/docx/protectionPolicy.d.ts +127 -0
  74. package/dist/docx/protectionPolicy.js +169 -0
  75. package/dist/docx/relationships.d.ts +1 -1
  76. package/dist/docx/relationships.js +15 -15
  77. package/dist/docx/runProps.d.ts +9 -22
  78. package/dist/docx/runProps.js +15 -168
  79. package/dist/docx/scan.js +20 -52
  80. package/dist/docx/sdt.js +14 -37
  81. package/dist/docx/sdtProps.d.ts +8 -1
  82. package/dist/docx/sdtProps.js +10 -0
  83. package/dist/docx/serializeBlock.d.ts +2 -0
  84. package/dist/docx/serializeBlock.js +9 -7
  85. package/dist/docx/serializeParagraph.d.ts +2 -1
  86. package/dist/docx/serializeParagraph.js +25 -14
  87. package/dist/docx/serializeTable.js +63 -32
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +43 -17
  90. package/dist/docx/storyProjection.d.ts +36 -0
  91. package/dist/docx/storyProjection.js +23 -0
  92. package/dist/docx/tableFormatting/editing.js +130 -185
  93. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  94. package/dist/docx/tableFormatting/reading.js +40 -20
  95. package/dist/docx/tableTemplate.js +37 -5
  96. package/dist/docx/theme.d.ts +0 -6
  97. package/dist/docx/theme.js +0 -8
  98. package/dist/download.d.ts +8 -5
  99. package/dist/download.js +2 -0
  100. package/dist/editor/clipboard/images.js +17 -3
  101. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  102. package/dist/editor/clipboard/inlineFormatting.js +20 -30
  103. package/dist/editor/commands/breakCommands.js +4 -3
  104. package/dist/editor/commands/canRunCommand.d.ts +2 -2
  105. package/dist/editor/commands/canRunCommand.js +1 -1
  106. package/dist/editor/commands/comments/editing.d.ts +6 -2
  107. package/dist/editor/commands/comments/editing.js +21 -36
  108. package/dist/editor/commands/exportQueries.d.ts +15 -0
  109. package/dist/editor/commands/exportQueries.js +14 -0
  110. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  111. package/dist/editor/commands/fidelityQueries.js +8 -0
  112. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  113. package/dist/editor/commands/formatting/editing.js +26 -108
  114. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  115. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  116. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  117. package/dist/editor/commands/formatting/shared.js +5 -2
  118. package/dist/editor/commands/historyCommands.js +2 -1
  119. package/dist/editor/commands/indentCommands.js +5 -4
  120. package/dist/editor/commands/index.d.ts +11 -0
  121. package/dist/editor/commands/index.js +5 -0
  122. package/dist/editor/commands/linkCommands.js +5 -6
  123. package/dist/editor/commands/listCommands.js +8 -10
  124. package/dist/editor/commands/lockCommands.d.ts +7 -1
  125. package/dist/editor/commands/lockCommands.js +35 -37
  126. package/dist/editor/commands/paragraphCommands.js +28 -31
  127. package/dist/editor/commands/spacingCommands.js +1 -1
  128. package/dist/editor/commands/tabCommands.js +4 -3
  129. package/dist/editor/createEditor.d.ts +17 -29
  130. package/dist/editor/createEditor.js +61 -67
  131. package/dist/editor/documentStyles.d.ts +11 -35
  132. package/dist/editor/documentStyles.js +9 -52
  133. package/dist/editor/editorDocument.d.ts +56 -0
  134. package/dist/editor/editorDocument.js +74 -0
  135. package/dist/editor/externalClipboard.js +166 -33
  136. package/dist/editor/insertImage.js +4 -3
  137. package/dist/editor/insertTable.js +4 -3
  138. package/dist/editor/paragraphEdits.d.ts +11 -18
  139. package/dist/editor/paragraphEdits.js +7 -16
  140. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  141. package/dist/editor/plugins/displayDerivation.js +78 -0
  142. package/dist/editor/plugins/documentProtection.d.ts +3 -3
  143. package/dist/editor/plugins/imagePaste.js +4 -3
  144. package/dist/editor/plugins/keymap.js +18 -3
  145. package/dist/editor/plugins/lockedContent.d.ts +4 -4
  146. package/dist/editor/plugins/lockedContent.js +1 -1
  147. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  148. package/dist/editor/plugins/numberingDecorations.js +7 -17
  149. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  150. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  151. package/dist/editor/plugins/tabLayout.js +1 -1
  152. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  153. package/dist/editor/plugins/tableDisplay.js +16 -0
  154. package/dist/index.d.ts +2 -0
  155. package/dist/model/format.d.ts +30 -8
  156. package/dist/model/format.js +35 -22
  157. package/dist/model/tabStops.d.ts +9 -0
  158. package/dist/model/tabStops.js +18 -0
  159. package/dist/numbering/listTemplate.js +49 -10
  160. package/dist/numbering/parseNumbering.d.ts +14 -1
  161. package/dist/numbering/parseNumbering.js +35 -22
  162. package/dist/numbering/writeNumbering.d.ts +3 -4
  163. package/dist/numbering/writeNumbering.js +14 -25
  164. package/dist/ooxml/childOrder.d.ts +34 -0
  165. package/dist/ooxml/childOrder.js +496 -0
  166. package/dist/ooxml/element.d.ts +65 -0
  167. package/dist/ooxml/element.js +68 -0
  168. package/dist/ooxml/errors.d.ts +3 -2
  169. package/dist/ooxml/fragment.d.ts +53 -0
  170. package/dist/ooxml/fragment.js +76 -0
  171. package/dist/ooxml/image.d.ts +4 -0
  172. package/dist/ooxml/image.js +3 -5
  173. package/dist/ooxml/names.d.ts +27 -0
  174. package/dist/ooxml/names.js +29 -0
  175. package/dist/ooxml/partSplice.d.ts +67 -0
  176. package/dist/ooxml/partSplice.js +169 -0
  177. package/dist/ooxml/precedence.d.ts +31 -0
  178. package/dist/ooxml/precedence.js +51 -0
  179. package/dist/ooxml/props.d.ts +112 -0
  180. package/dist/ooxml/props.js +214 -0
  181. package/dist/ooxml/simpleTypes.d.ts +103 -0
  182. package/dist/ooxml/simpleTypes.js +182 -0
  183. package/dist/ooxml/tabStops.js +8 -27
  184. package/dist/ooxml/tagScan.d.ts +34 -0
  185. package/dist/ooxml/tagScan.js +108 -0
  186. package/dist/ooxml/units.d.ts +25 -13
  187. package/dist/ooxml/units.js +65 -31
  188. package/dist/ooxml/xml.d.ts +52 -0
  189. package/dist/ooxml/xml.js +64 -5
  190. package/dist/page/blockKinds.d.ts +103 -0
  191. package/dist/page/blockKinds.js +11 -0
  192. package/dist/page/kinds/index.d.ts +6 -0
  193. package/dist/page/kinds/index.js +10 -0
  194. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  195. package/dist/page/kinds/paragraphKind.js +71 -0
  196. package/dist/page/kinds/tableKind.d.ts +10 -0
  197. package/dist/page/kinds/tableKind.js +178 -0
  198. package/dist/page/measureBlocks.d.ts +3 -1
  199. package/dist/page/measureBlocks.js +24 -25
  200. package/dist/page/pageDecorations.d.ts +27 -28
  201. package/dist/page/pageDecorations.js +70 -151
  202. package/dist/page/pageLayout.d.ts +10 -62
  203. package/dist/page/pageLayout.js +60 -53
  204. package/dist/page/usePageLayout.d.ts +2 -2
  205. package/dist/page/usePageLayout.js +3 -11
  206. package/dist/schema/attrRoles.d.ts +42 -0
  207. package/dist/schema/attrRoles.js +141 -0
  208. package/dist/schema/displayDerivation.d.ts +82 -0
  209. package/dist/schema/displayDerivation.js +130 -0
  210. package/dist/schema/docxSchema.d.ts +9 -0
  211. package/dist/schema/docxSchema.js +237 -114
  212. package/dist/schema/editGuard.d.ts +99 -0
  213. package/dist/schema/editGuard.js +43 -0
  214. package/dist/schema/guards.d.ts +91 -0
  215. package/dist/schema/guards.js +108 -0
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +17 -77
  219. package/dist/schema/locks.js +26 -70
  220. package/dist/schema/preservedGuards.d.ts +50 -0
  221. package/dist/schema/preservedGuards.js +80 -0
  222. package/dist/schema/protection.d.ts +17 -4
  223. package/dist/schema/protection.js +4 -2
  224. package/dist/schema/sourceEquality.d.ts +19 -0
  225. package/dist/schema/sourceEquality.js +39 -0
  226. package/dist/styles/inlineStyle.js +15 -6
  227. package/dist/table/cellFormatting.d.ts +8 -1
  228. package/dist/table/cellFormatting.js +10 -17
  229. package/dist/table/commands.d.ts +2 -2
  230. package/dist/table/commands.js +20 -18
  231. package/dist/table/format.d.ts +3 -0
  232. package/dist/table/format.js +3 -9
  233. package/dist/table/gridBorders.d.ts +14 -16
  234. package/dist/table/gridBorders.js +3 -41
  235. package/dist/table/index.d.ts +2 -2
  236. package/dist/table/merge.d.ts +4 -8
  237. package/dist/table/merge.js +5 -5
  238. package/package.json +10 -3
  239. package/dist/docx/comments/contentTypes.d.ts +0 -7
  240. package/dist/docx/comments/contentTypes.js +0 -38
  241. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  242. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  243. package/dist/docx/propsXml.d.ts +0 -49
  244. package/dist/docx/propsXml.js +0 -302
  245. package/dist/docx/uniqueControls.d.ts +0 -14
  246. package/dist/docx/uniqueControls.js +0 -62
  247. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  248. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  249. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  250. package/dist/editor/plugins/commentReservations.js +0 -26
  251. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  252. package/dist/editor/plugins/noteProtection.js +0 -25
  253. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  254. package/dist/editor/plugins/styledParagraphs.js +0 -65
  255. package/dist/page/tableMeasurements.d.ts +0 -10
  256. package/dist/page/tableMeasurements.js +0 -115
@@ -1,21 +1,20 @@
1
1
  // src/docx/comments/reading.ts
2
2
  import {
3
+ attributeByLocalName,
3
4
  decodeUtf8,
4
5
  elementChildren,
5
6
  parseXml,
6
7
  serializeXml,
7
8
  W_NS
8
9
  } from "../../ooxml/xml.js";
9
- import { readRelationships, relsPathOf, resolveTarget } from "../relationships.js";
10
+ import { relatedPartPath } from "../packageParts.js";
10
11
  import { COMMENTS_EXTENDED_REL_TYPE, COMMENTS_REL_TYPE } from "./constants.js";
12
+ import { lastBodyParagraph } from "./grammar.js";
11
13
  import {
12
14
  commentAuthorId,
13
15
  NO_PEOPLE,
14
16
  readPeople
15
17
  } from "./people.js";
16
- function attribute(el, localName) {
17
- return Array.from(el.attributes).find((entry) => entry.localName === localName)?.value ?? null;
18
- }
19
18
  function inlineCommentText(node) {
20
19
  if (node.localName === "t") return node.textContent ?? "";
21
20
  if (node.localName === "tab") return " ";
@@ -40,14 +39,16 @@ var NO_COMMENTS = {
40
39
  people: NO_PEOPLE
41
40
  };
42
41
  function lastParagraphId(comment) {
43
- const paragraphs = Array.from(comment.getElementsByTagNameNS(W_NS, "p"));
44
- return paragraphs.length === 0 ? null : attribute(paragraphs[paragraphs.length - 1], "paraId");
42
+ const last = lastBodyParagraph(comment);
43
+ return last === null ? null : attributeByLocalName(last, "paraId");
45
44
  }
46
45
  function readCommentExtensions(parts, mainPartPath) {
47
- const relationship = readRelationships(parts, relsPathOf(mainPartPath)).find(
48
- (entry) => entry.type === COMMENTS_EXTENDED_REL_TYPE && !entry.external
46
+ const partPath = relatedPartPath(
47
+ parts,
48
+ mainPartPath,
49
+ COMMENTS_EXTENDED_REL_TYPE
49
50
  );
50
- if (!relationship) {
51
+ if (partPath === null) {
51
52
  return {
52
53
  partPath: null,
53
54
  xml: null,
@@ -56,7 +57,6 @@ function readCommentExtensions(parts, mainPartPath) {
56
57
  ordered: []
57
58
  };
58
59
  }
59
- const partPath = resolveTarget(mainPartPath, relationship.target);
60
60
  const bytes = parts.get(partPath);
61
61
  if (!bytes) {
62
62
  return {
@@ -73,13 +73,13 @@ function readCommentExtensions(parts, mainPartPath) {
73
73
  const ordered = [];
74
74
  for (const el of elementChildren(root)) {
75
75
  if (el.localName !== "commentEx") continue;
76
- const paraId = attribute(el, "paraId");
76
+ const paraId = attributeByLocalName(el, "paraId");
77
77
  if (paraId === null) continue;
78
78
  const extension = {
79
79
  paraId,
80
- parentParaId: attribute(el, "paraIdParent"),
80
+ parentParaId: attributeByLocalName(el, "paraIdParent"),
81
81
  resolved: ["1", "true", "on"].includes(
82
- attribute(el, "done")?.toLowerCase() ?? ""
82
+ attributeByLocalName(el, "done")?.toLowerCase() ?? ""
83
83
  ),
84
84
  xml: serializeXml(el)
85
85
  };
@@ -90,29 +90,33 @@ function readCommentExtensions(parts, mainPartPath) {
90
90
  }
91
91
  function readComments(parts, mainPartPath) {
92
92
  const people = readPeople(parts, mainPartPath);
93
- const relationship = readRelationships(parts, relsPathOf(mainPartPath)).find(
94
- (entry) => entry.type === COMMENTS_REL_TYPE && !entry.external
95
- );
96
- if (!relationship) return { ...NO_COMMENTS, people };
97
- const partPath = resolveTarget(mainPartPath, relationship.target);
93
+ const extensions = readCommentExtensions(parts, mainPartPath);
94
+ const aside = {
95
+ people,
96
+ extendedPartPath: extensions.partPath,
97
+ extendedXml: extensions.xml,
98
+ extendedHadBom: extensions.hadBom,
99
+ extendedOrdered: extensions.ordered
100
+ };
101
+ const partPath = relatedPartPath(parts, mainPartPath, COMMENTS_REL_TYPE);
102
+ if (partPath === null) return { ...NO_COMMENTS, ...aside };
98
103
  const bytes = parts.get(partPath);
99
- if (!bytes) return { ...NO_COMMENTS, partPath, people };
104
+ if (!bytes) return { ...NO_COMMENTS, partPath, ...aside };
100
105
  const { text, hadBom } = decodeUtf8(bytes);
101
106
  const root = parseXml(text).documentElement;
102
- const extensions = readCommentExtensions(parts, mainPartPath);
103
107
  const base = elementChildren(root).filter((el) => el.localName === "comment").flatMap((el) => {
104
- const id = attribute(el, "id");
108
+ const id = attributeByLocalName(el, "id");
105
109
  if (id === null) return [];
106
110
  const paraId = lastParagraphId(el);
107
111
  const extension = paraId ? extensions.byParaId.get(paraId) : void 0;
108
- const author = attribute(el, "author");
112
+ const author = attributeByLocalName(el, "author");
109
113
  return [
110
114
  {
111
115
  id,
112
116
  author,
113
117
  authorId: author === null ? null : commentAuthorId(people, author),
114
- initials: attribute(el, "initials"),
115
- date: attribute(el, "date"),
118
+ initials: attributeByLocalName(el, "initials"),
119
+ date: attributeByLocalName(el, "date"),
116
120
  text: commentText(el),
117
121
  xml: serializeXml(el),
118
122
  paraId,
@@ -146,14 +150,11 @@ function readComments(parts, mainPartPath) {
146
150
  ordered,
147
151
  byId,
148
152
  repliesByParentId,
149
- extendedPartPath: extensions.partPath,
150
- extendedXml: extensions.xml,
151
- extendedHadBom: extensions.hadBom,
152
- extendedOrdered: extensions.ordered,
153
- people
153
+ ...aside
154
154
  };
155
155
  }
156
156
  export {
157
157
  NO_COMMENTS,
158
+ lastParagraphId,
158
159
  readComments
159
160
  };
@@ -2,13 +2,25 @@
2
2
  * Plans changed comment package parts while preserving untouched XML.
3
3
  */
4
4
  import type { Node as PMNode } from "prosemirror-model";
5
- import { type RelationshipWriter } from "../relationships";
5
+ import type { PartPlanner } from "../partPlan";
6
6
  import type { SessionStore } from "../session";
7
- export interface CommentPartChanges {
8
- parts: ReadonlyMap<string, Uint8Array>;
9
- }
7
+ import { type CommentReferenceData, type CommentReplyData } from "./model";
10
8
  /**
11
- * Plans the Comments part, relationship and content type only when comment state changed, and the
12
- * people part beside them for an author whose identity the document has yet to record.
9
+ * Whether the Comments part has to be rewritten: a comment or reply came, went, or changed, or an
10
+ * entry has to gain the key its thread state hangs off. The export invariants ask this as well, so
11
+ * a part the writer would leave alone is one they do not read.
13
12
  */
14
- export declare function planCommentParts(doc: PMNode, session: SessionStore, relationships: RelationshipWriter, currentContentTypes?: Uint8Array): CommentPartChanges | null;
13
+ export declare function commentsChanged(doc: PMNode, session: SessionStore): boolean;
14
+ /**
15
+ * Why the Comments part cannot be rewritten around its root, or null when it can.
16
+ *
17
+ * The same question stands in the export invariant list, so a refusal the writer raises is one a
18
+ * caller could have read there first, in the same words.
19
+ */
20
+ export declare function commentsRootProblem(xml: string): string | null;
21
+ /** Why the extended part cannot be rewritten around its root, or null when it can */
22
+ export declare function extensionsRootProblem(xml: string): string | null;
23
+ /** Whether the extended part has to be rewritten: thread state arrived, changed, or went with a deleted comment */
24
+ export declare function extensionsChanged(doc: PMNode, session: SessionStore): boolean;
25
+ export declare function currentCommentBodies(references: ReadonlyMap<string, CommentReferenceData>): ReadonlyMap<string, CommentReferenceData | CommentReplyData>;
26
+ export declare const commentsPlanner: PartPlanner;
@@ -1,22 +1,27 @@
1
1
  // src/docx/comments/writing.ts
2
- import { DocxExportError } from "../../ooxml/errors.js";
3
- import { encodeUtf8, escapeXml, W_NS } from "../../ooxml/xml.js";
2
+ import { elementXml, xmlnsAttr } from "../../ooxml/element.js";
3
+ import { NAMESPACES, wName, xmlnsDecl } from "../../ooxml/names.js";
4
+ import {
5
+ ensureRootDeclarations,
6
+ partRootProblem,
7
+ splicePart
8
+ } from "../../ooxml/partSplice.js";
9
+ import { encodeUtf8 } from "../../ooxml/xml.js";
4
10
  import { directoryOf } from "../relationships.js";
5
11
  import {
6
- COMMENTS_CONTENT_TYPE,
7
- COMMENTS_EXTENDED_CONTENT_TYPE,
8
- COMMENTS_EXTENDED_REL_TYPE,
9
- COMMENTS_REL_TYPE,
10
- CONTENT_TYPES_PATH,
11
- MC_NS,
12
- W14_NS,
13
- W15_NS
14
- } from "./constants.js";
15
- import { withContentType } from "./contentTypes.js";
12
+ arrivedEntries,
13
+ renderCommentBody,
14
+ renderCommentExtension,
15
+ withThreadKey
16
+ } from "./grammar.js";
16
17
  import {
17
18
  commentReferencesIn
18
19
  } from "./model.js";
20
+ import { commentsExtendedPart, commentsPart, peoplePart } from "./parts.js";
19
21
  import { planPeoplePart } from "./people.js";
22
+ function needsThreadKey(id, comment, session) {
23
+ return comment.imported && carriesThreadMetadata(comment) && (session.comments.byId.get(id)?.paraId ?? null) === null;
24
+ }
20
25
  function commentsChanged(doc, session) {
21
26
  const current = commentReferencesIn(doc);
22
27
  if (current.size !== session.commentReferenceIds.size) return true;
@@ -36,14 +41,11 @@ function commentsChanged(doc, session) {
36
41
  for (const comment of current.values()) {
37
42
  if (comment.replies.some((reply) => !reply.imported)) return true;
38
43
  }
44
+ for (const [id, comment] of current) {
45
+ if (needsThreadKey(id, comment, session)) return true;
46
+ }
39
47
  return false;
40
48
  }
41
- function renderedExtension(comment) {
42
- if (comment.extensionXml !== null) return comment.extensionXml;
43
- const parent = "parentParaId" in comment ? ` w15:paraIdParent="${escapeXml(comment.parentParaId)}"` : "";
44
- const done = "resolved" in comment ? ` w15:done="${comment.resolved ? "1" : "0"}"` : "";
45
- return `<w15:commentEx w15:paraId="${escapeXml(comment.paraId)}"${parent}${done}/>`;
46
- }
47
49
  function extensionsXml(references, comments, originallyReferenced) {
48
50
  const current = currentCommentBodies(references);
49
51
  const originalThreads = originalThreadIds(comments, originallyReferenced);
@@ -58,35 +60,32 @@ function extensionsXml(references, comments, originallyReferenced) {
58
60
  const id = idByParaId.get(original.paraId);
59
61
  const item = id === void 0 ? void 0 : current.get(id);
60
62
  if (id !== void 0 && item && !written.has(id)) {
61
- pieces.push(renderedExtension(item));
63
+ pieces.push(renderCommentExtension(item));
62
64
  written.add(id);
63
65
  } else if (id === void 0 || !originalThreads.has(id)) {
64
66
  pieces.push(original.xml);
65
67
  }
66
68
  }
67
69
  for (const [id, comment] of current) {
68
- if (!written.has(id)) pieces.push(renderedExtension(comment));
70
+ if (!written.has(id) && carriesThreadMetadata(comment)) {
71
+ pieces.push(renderCommentExtension(comment));
72
+ }
69
73
  }
70
74
  if (comments.extendedXml === null) {
71
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx xmlns:w15="${W15_NS}">${pieces.join("")}</w15:commentsEx>`;
72
- }
73
- const open = /<(?:[\w.-]+:)?commentsEx\b[^>]*>/.exec(comments.extendedXml);
74
- if (!open) {
75
- throw new DocxExportError(
76
- "malformed-xml",
77
- "the Comments Extended part has no commentsEx root element"
78
- );
79
- }
80
- const name = /^<([^\s>]+)/.exec(open[0])?.[1];
81
- const close = name ? comments.extendedXml.lastIndexOf(`</${name}>`) : -1;
82
- if (close === -1) {
83
- throw new DocxExportError(
84
- "malformed-xml",
85
- "the Comments Extended part has no closing commentsEx tag"
86
- );
75
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx ${xmlnsDecl("w15")}>${pieces.join("")}</w15:commentsEx>`;
87
76
  }
88
- const bodyAt = open.index + open[0].length;
89
- return comments.extendedXml.slice(0, bodyAt) + pieces.join("") + comments.extendedXml.slice(close);
77
+ return splicePart(comments.extendedXml, {
78
+ root: EXTENSIONS_ROOT,
79
+ replaceChildren: pieces.join("")
80
+ });
81
+ }
82
+ var COMMENTS_ROOT = "comments";
83
+ var EXTENSIONS_ROOT = "commentsEx";
84
+ function commentsRootProblem(xml) {
85
+ return partRootProblem(xml, COMMENTS_ROOT);
86
+ }
87
+ function extensionsRootProblem(xml) {
88
+ return partRootProblem(xml, EXTENSIONS_ROOT);
90
89
  }
91
90
  function extensionsChanged(doc, session) {
92
91
  const current = commentReferencesIn(doc);
@@ -103,51 +102,39 @@ function extensionsChanged(doc, session) {
103
102
  }
104
103
  return false;
105
104
  }
106
- function commentTextXml(text) {
107
- const lines = text.split("\n");
108
- const pieces = [];
109
- lines.forEach((line, index) => {
110
- if (index > 0) pieces.push("<w:br/>");
111
- if (line.length > 0 || lines.length === 1) {
112
- pieces.push(`<w:t xml:space="preserve">${escapeXml(line)}</w:t>`);
113
- }
114
- });
115
- return `<w:r>${pieces.join("")}</w:r>`;
116
- }
117
105
  function carriesThreadMetadata(comment) {
118
106
  return comment.extensionXml !== null || "threadImported" in comment && !comment.threadImported || "parentParaId" in comment;
119
107
  }
120
- function renderedComment(comment) {
108
+ function keyedEntry(comment, arrivedKeyed) {
109
+ return carriesThreadMetadata(comment) || arrivedKeyed.has(comment.id);
110
+ }
111
+ function renderedComment(comment, arrivedKeyed, arrived) {
121
112
  if (comment.imported && comment.commentXml !== null) {
122
- return comment.commentXml;
113
+ return carriesThreadMetadata(comment) ? withThreadKey(
114
+ comment.commentXml,
115
+ comment.paraId,
116
+ arrived.get(comment.id) ?? null
117
+ ) : comment.commentXml;
123
118
  }
124
119
  const attrs = [
125
- `w:id="${escapeXml(comment.id)}"`,
126
- comment.author === null ? null : `w:author="${escapeXml(comment.author)}"`,
127
- comment.date === null ? null : `w:date="${escapeXml(comment.date)}"`,
128
- comment.initials === null ? null : `w:initials="${escapeXml(comment.initials)}"`
129
- ].filter((entry) => entry !== null).join(" ");
130
- const threaded = carriesThreadMetadata(comment);
131
- const paragraphAttrs = threaded ? ` xmlns:w14="${W14_NS}" w14:paraId="${escapeXml(comment.paraId)}"` : "";
132
- return `<w:comment xmlns:w="${W_NS}" ${attrs}><w:p${paragraphAttrs}>${commentTextXml(comment.text)}</w:p></w:comment>`;
133
- }
134
- function withThreadMarkupCompatibility(openTag) {
135
- let updated = openTag;
136
- if (!/\sxmlns:w14\s*=/.test(updated)) {
137
- updated = updated.replace(/>$/, ` xmlns:w14="${W14_NS}">`);
138
- }
139
- if (!/\sxmlns:mc\s*=/.test(updated)) {
140
- updated = updated.replace(/>$/, ` xmlns:mc="${MC_NS}">`);
141
- }
142
- const ignorable = /\smc:Ignorable\s*=\s*(["'])([^"']*)\1/.exec(updated);
143
- if (!ignorable) {
144
- return updated.replace(/>$/, ' mc:Ignorable="w14">');
145
- }
146
- const tokens = ignorable[2].split(/\s+/).filter(Boolean);
147
- if (tokens.includes("w14")) return updated;
148
- const replacement = ` mc:Ignorable=${ignorable[1]}${[...tokens, "w14"].join(" ")}${ignorable[1]}`;
149
- return updated.slice(0, ignorable.index) + replacement + updated.slice(ignorable.index + ignorable[0].length);
120
+ xmlnsAttr("w"),
121
+ [wName("id"), comment.id],
122
+ comment.author === null ? null : [wName("author"), comment.author],
123
+ comment.date === null ? null : [wName("date"), comment.date],
124
+ comment.initials === null ? null : [wName("initials"), comment.initials]
125
+ ];
126
+ const paraId = keyedEntry(comment, arrivedKeyed) ? comment.paraId : null;
127
+ const body = renderCommentBody(comment.text, paraId);
128
+ return elementXml(
129
+ wName("comment"),
130
+ attrs.filter((attr) => attr !== null),
131
+ [body]
132
+ );
150
133
  }
134
+ var THREAD_MARKUP = {
135
+ namespaces: { w14: NAMESPACES.w14, mc: NAMESPACES.mc },
136
+ ignorable: ["w14"]
137
+ };
151
138
  function currentCommentBodies(references) {
152
139
  const comments = /* @__PURE__ */ new Map();
153
140
  for (const [id, comment] of references) {
@@ -172,16 +159,22 @@ function originalThreadIds(comments, rootIds) {
172
159
  }
173
160
  function commentsXml(references, comments, originallyReferenced) {
174
161
  const currentBodies = currentCommentBodies(references);
162
+ const arrivedKeyed = new Set(
163
+ Array.from(comments.byId.values()).flatMap(
164
+ (entry) => entry.paraId === null ? [] : [entry.id]
165
+ )
166
+ );
175
167
  const hasThreadMetadata = Array.from(currentBodies.values()).some(
176
- carriesThreadMetadata
168
+ (comment) => keyedEntry(comment, arrivedKeyed)
177
169
  );
170
+ const arrived = arrivedEntries(comments.xml);
178
171
  const originalThreads = originalThreadIds(comments, originallyReferenced);
179
172
  const pieces = [];
180
173
  const written = /* @__PURE__ */ new Set();
181
174
  for (const original of comments.ordered) {
182
175
  const current = currentBodies.get(original.id);
183
176
  if (current) {
184
- pieces.push(renderedComment(current));
177
+ pieces.push(renderedComment(current, arrivedKeyed, arrived));
185
178
  written.add(original.id);
186
179
  continue;
187
180
  }
@@ -191,57 +184,32 @@ function commentsXml(references, comments, originallyReferenced) {
191
184
  }
192
185
  }
193
186
  for (const [id, comment] of currentBodies) {
194
- if (!written.has(id)) pieces.push(renderedComment(comment));
187
+ if (!written.has(id)) {
188
+ pieces.push(renderedComment(comment, arrivedKeyed, arrived));
189
+ }
195
190
  }
196
191
  if (comments.xml === null) {
197
- const compatibility = hasThreadMetadata ? ` xmlns:w14="${W14_NS}" xmlns:mc="${MC_NS}" mc:Ignorable="w14"` : "";
198
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:comments xmlns:w="${W_NS}"${compatibility}>${pieces.join("")}</w:comments>`;
199
- }
200
- const open = /<(?:[\w.-]+:)?comments\b[^>]*>/.exec(comments.xml);
201
- if (!open) {
202
- throw new DocxExportError(
203
- "malformed-xml",
204
- "the Comments part has no comments root element"
205
- );
206
- }
207
- const name = /^<([^\s>]+)/.exec(open[0])?.[1];
208
- const close = name ? comments.xml.lastIndexOf(`</${name}>`) : -1;
209
- if (close === -1) {
210
- throw new DocxExportError(
211
- "malformed-xml",
212
- "the Comments part has no closing comments tag"
213
- );
214
- }
215
- const openTag = hasThreadMetadata ? withThreadMarkupCompatibility(open[0]) : open[0];
216
- return comments.xml.slice(0, open.index) + openTag + pieces.join("") + comments.xml.slice(close);
217
- }
218
- function availableCommentsPath(session) {
219
- const directory = directoryOf(session.mainPartPath);
220
- for (let suffix = 0; ; suffix += 1) {
221
- const name = suffix === 0 ? "comments.xml" : `comments${suffix + 1}.xml`;
222
- const path = directory + name;
223
- if (!session.parts.has(path)) return path;
224
- }
225
- }
226
- function availableCommentsExtendedPath(session) {
227
- const directory = directoryOf(session.mainPartPath);
228
- for (let suffix = 0; ; suffix += 1) {
229
- const name = suffix === 0 ? "commentsExtended.xml" : `commentsExtended${suffix + 1}.xml`;
230
- const path = directory + name;
231
- if (!session.parts.has(path)) return path;
192
+ const compatibility = hasThreadMetadata ? ` ${xmlnsDecl("w14")} ${xmlnsDecl("mc")} mc:Ignorable="w14"` : "";
193
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:comments ${xmlnsDecl("w")}${compatibility}>${pieces.join("")}</w:comments>`;
232
194
  }
195
+ const rewritten = splicePart(comments.xml, {
196
+ root: COMMENTS_ROOT,
197
+ replaceChildren: pieces.join("")
198
+ });
199
+ return hasThreadMetadata ? ensureRootDeclarations(rewritten, THREAD_MARKUP) : rewritten;
233
200
  }
234
- function planCommentParts(doc, session, relationships, currentContentTypes) {
201
+ function planCommentParts(doc, session, context) {
235
202
  const bodyChanged = commentsChanged(doc, session);
236
203
  const threadChanged = extensionsChanged(doc, session);
237
204
  if (!bodyChanged && !threadChanged) return null;
205
+ const { relationships, contentTypes } = context;
238
206
  const references = commentReferencesIn(doc);
239
- const addingPart = session.comments.partPath === null;
240
- const partPath = session.comments.partPath ?? availableCommentsPath(session);
207
+ const addingPart = commentsPart.pathIn(session) === null;
208
+ const partPath = commentsPart.writePathIn(session);
241
209
  const parts = /* @__PURE__ */ new Map();
242
210
  if (addingPart) {
243
211
  const target = partPath.slice(directoryOf(session.mainPartPath).length);
244
- relationships.add({ type: COMMENTS_REL_TYPE, target });
212
+ relationships.add({ type: commentsPart.relType, target });
245
213
  }
246
214
  if (bodyChanged) {
247
215
  parts.set(
@@ -253,22 +221,16 @@ function planCommentParts(doc, session, relationships, currentContentTypes) {
253
221
  );
254
222
  }
255
223
  if (addingPart || session.comments.xml === null) {
256
- const contentTypes = withContentType(
257
- session.parts,
258
- partPath,
259
- COMMENTS_CONTENT_TYPE,
260
- currentContentTypes
261
- );
262
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
224
+ contentTypes.addOverride(partPath, commentsPart.contentType);
263
225
  }
264
226
  if (threadChanged && (references.size > 0 || session.comments.extendedPartPath !== null)) {
265
- const addingExtendedPart = session.comments.extendedPartPath === null;
266
- const extendedPartPath = session.comments.extendedPartPath ?? availableCommentsExtendedPath(session);
227
+ const addingExtendedPart = commentsExtendedPart.pathIn(session) === null;
228
+ const extendedPartPath = commentsExtendedPart.writePathIn(session);
267
229
  if (addingExtendedPart) {
268
230
  const target = extendedPartPath.slice(
269
231
  directoryOf(session.mainPartPath).length
270
232
  );
271
- relationships.add({ type: COMMENTS_EXTENDED_REL_TYPE, target });
233
+ relationships.add({ type: commentsExtendedPart.relType, target });
272
234
  }
273
235
  parts.set(
274
236
  extendedPartPath,
@@ -282,26 +244,32 @@ function planCommentParts(doc, session, relationships, currentContentTypes) {
282
244
  )
283
245
  );
284
246
  if (addingExtendedPart || session.comments.extendedXml === null) {
285
- const contentTypes = withContentType(
286
- session.parts,
247
+ contentTypes.addOverride(
287
248
  extendedPartPath,
288
- COMMENTS_EXTENDED_CONTENT_TYPE,
289
- parts.get(CONTENT_TYPES_PATH) ?? currentContentTypes
249
+ commentsExtendedPart.contentType
290
250
  );
291
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
292
251
  }
293
252
  }
294
253
  if (bodyChanged) {
295
254
  const people = planPeoplePart(
255
+ peoplePart,
296
256
  currentCommentBodies(references).values(),
297
257
  session,
298
- relationships,
299
- parts.get(CONTENT_TYPES_PATH) ?? currentContentTypes
258
+ context
300
259
  );
301
260
  for (const [path, bytes] of people ?? []) parts.set(path, bytes);
302
261
  }
303
- return { parts };
262
+ return parts;
304
263
  }
264
+ var commentsPlanner = {
265
+ name: "comments",
266
+ plan: planCommentParts
267
+ };
305
268
  export {
306
- planCommentParts
269
+ commentsChanged,
270
+ commentsPlanner,
271
+ commentsRootProblem,
272
+ currentCommentBodies,
273
+ extensionsChanged,
274
+ extensionsRootProblem
307
275
  };
@@ -2,3 +2,10 @@
2
2
  export declare const DEFAULT_TAB_STOP_PT = 36;
3
3
  /** Reads the document-wide automatic tab interval from settings.xml. */
4
4
  export declare function readDefaultTabStop(settings: Document | null): number | null;
5
+ /** The compatibility settings (`w:compat`) the display reads */
6
+ export interface CompatSettings {
7
+ /** Part 4 §14.8.3.31: no custom tab stop is created at a hanging indent */
8
+ noTabHangInd: boolean;
9
+ }
10
+ export declare const NO_COMPAT: CompatSettings;
11
+ export declare function readCompatSettings(settings: Document | null): CompatSettings;
@@ -1,14 +1,22 @@
1
1
  // src/docx/documentSettings.ts
2
- import { twipsToPt, wAttr } from "../ooxml/units.js";
2
+ import { ST_TwipsMeasure } from "../ooxml/simpleTypes.js";
3
+ import { isOn, twipsToPt, wAttr } from "../ooxml/units.js";
3
4
  import { childByLocalName } from "../ooxml/xml.js";
4
5
  var DEFAULT_TAB_STOP_PT = 36;
5
6
  function readDefaultTabStop(settings) {
6
7
  if (!settings) return null;
7
8
  const setting = childByLocalName(settings.documentElement, "defaultTabStop");
8
- const value = setting ? twipsToPt(wAttr(setting, "val")) : null;
9
+ const value = setting ? twipsToPt(ST_TwipsMeasure.parse(wAttr(setting, "val"))) : null;
9
10
  return value !== null && value > 0 ? value : null;
10
11
  }
12
+ var NO_COMPAT = { noTabHangInd: false };
13
+ function readCompatSettings(settings) {
14
+ const compat = settings ? childByLocalName(settings.documentElement, "compat") : null;
15
+ return { noTabHangInd: compat !== null && isOn(compat, "noTabHangInd") };
16
+ }
11
17
  export {
12
18
  DEFAULT_TAB_STOP_PT,
19
+ NO_COMPAT,
20
+ readCompatSettings,
13
21
  readDefaultTabStop
14
22
  };
@@ -1,15 +1,50 @@
1
1
  /**
2
2
  * Turns the edited document back into docx bytes.
3
3
  *
4
- * The body (document.xml) is always rebuilt from preserved and edited blocks. numbering.xml is
5
- * rewritten only when a list was newly started, media parts only when an image was inserted, and
6
- * the Comments part only when comments changed. Content types and relationships change only when
7
- * one of those additions needs them.
4
+ * The body (document.xml) is always rebuilt from preserved and edited blocks. Every other part is
5
+ * written by a planner (`./partPlan`) that answers only when the document gives it something to
6
+ * write: numbering.xml when a list was newly started, the comment parts when comments changed,
7
+ * media parts when an image was inserted. Content types and relationships change only when one
8
+ * of those additions declares itself through the context every planner shares.
8
9
  *
9
10
  * The body is written before that relationships part, because a link asks for its relationship as
10
11
  * it is written (`docx/hyperlink`), and both writers hand out ids through the one writer so that
11
12
  * they cannot pick the same one.
12
13
  */
13
14
  import type { Node as PMNode } from "prosemirror-model";
15
+ import { type XmlParser } from "../ooxml/xml";
16
+ import { type FidelityNote } from "./fidelity";
17
+ import { type PartPlanner } from "./partPlan";
14
18
  import { type DocxSession } from "./session";
15
- export declare function exportDocx(doc: PMNode, session: DocxSession): Uint8Array;
19
+ /** What a caller may say about a write beyond handing over the document and its session */
20
+ export interface ExportOptions {
21
+ /**
22
+ * The parser the original XML kept in the session is read back through. Left out, the
23
+ * `DOMParser` global is used, and a runtime carrying none refuses the write with
24
+ * `no-xml-parser`.
25
+ */
26
+ xmlParser?: XmlParser;
27
+ }
28
+ export declare function exportDocx(doc: PMNode, session: DocxSession, options?: ExportOptions): Uint8Array;
29
+ /**
30
+ * The file to write, and what writing it could not carry across as it stood.
31
+ *
32
+ * The notes are the ones `documentFidelity` reads off the same document, followed by whatever the
33
+ * writer had to approximate on the way out. A host that hands a file to somebody else can say what
34
+ * that file no longer holds without opening it again.
35
+ *
36
+ * The first problem `exportProblems` reports is thrown before anything is written, so a refusal a
37
+ * caller could have asked about ahead of time arrives with the same code and message it would
38
+ * have read there.
39
+ */
40
+ export declare function exportDocxReport(doc: PMNode, session: DocxSession, options?: ExportOptions): {
41
+ bytes: Uint8Array;
42
+ notes: FidelityNote[];
43
+ };
44
+ /**
45
+ * The same export, folding a planner list of the caller's choosing in place of `PART_PLANNERS`.
46
+ *
47
+ * On no entry point. No document can make a correct planner write a part that does not read back,
48
+ * so this is how a test hands the fold such a planner and watches the export refuse it.
49
+ */
50
+ export declare function exportThroughPlanners(planners: readonly PartPlanner[], doc: PMNode, session: DocxSession, options?: ExportOptions): Uint8Array;