@portone/docx-editor 0.2.1 → 0.4.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 (253) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/CONTRIBUTING.md +5 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +13 -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 +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  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 +16 -92
  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.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +59 -112
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +88 -74
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +45 -0
  33. package/dist/docx/formatting/attrs.js +86 -0
  34. package/dist/docx/formatting/context.d.ts +39 -0
  35. package/dist/docx/formatting/context.js +60 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +51 -0
  39. package/dist/docx/formatting/resolve.js +160 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.d.ts +25 -2
  43. package/dist/docx/formatting/styles.js +87 -6
  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 +6 -13
  48. package/dist/docx/hyperlink.d.ts +14 -4
  49. package/dist/docx/hyperlink.js +4 -1
  50. package/dist/docx/identities.d.ts +67 -0
  51. package/dist/docx/identities.js +174 -0
  52. package/dist/docx/importDocx.d.ts +12 -1
  53. package/dist/docx/importDocx.js +90 -88
  54. package/dist/docx/importParagraph.d.ts +1 -1
  55. package/dist/docx/importParagraph.js +4 -3
  56. package/dist/docx/importTable.d.ts +2 -2
  57. package/dist/docx/importTable.js +69 -56
  58. package/dist/docx/invariants.d.ts +33 -0
  59. package/dist/docx/invariants.js +269 -0
  60. package/dist/docx/media.d.ts +6 -4
  61. package/dist/docx/media.js +6 -37
  62. package/dist/docx/newLists.d.ts +50 -0
  63. package/dist/docx/newLists.js +59 -0
  64. package/dist/docx/notes.js +3 -7
  65. package/dist/docx/numberingPlanner.d.ts +11 -0
  66. package/dist/docx/numberingPlanner.js +50 -0
  67. package/dist/docx/packageParts.d.ts +42 -0
  68. package/dist/docx/packageParts.js +135 -0
  69. package/dist/docx/pageGeometry.d.ts +2 -0
  70. package/dist/docx/pageGeometry.js +18 -9
  71. package/dist/docx/paraProps.d.ts +10 -22
  72. package/dist/docx/paraProps.js +47 -76
  73. package/dist/docx/partPlan.d.ts +36 -0
  74. package/dist/docx/partPlan.js +59 -0
  75. package/dist/docx/protectionPolicy.d.ts +127 -0
  76. package/dist/docx/protectionPolicy.js +169 -0
  77. package/dist/docx/relationships.d.ts +1 -1
  78. package/dist/docx/relationships.js +8 -13
  79. package/dist/docx/runProps.d.ts +9 -22
  80. package/dist/docx/runProps.js +15 -168
  81. package/dist/docx/scan.js +20 -52
  82. package/dist/docx/sdt.js +9 -34
  83. package/dist/docx/sdtProps.d.ts +8 -1
  84. package/dist/docx/sdtProps.js +10 -0
  85. package/dist/docx/serializeBlock.d.ts +2 -0
  86. package/dist/docx/serializeBlock.js +8 -5
  87. package/dist/docx/serializeTable.js +32 -15
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +53 -17
  90. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  91. package/dist/docx/tableFormatting/conditions.js +196 -0
  92. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  93. package/dist/docx/tableFormatting/editing.js +111 -143
  94. package/dist/docx/tableFormatting/reading.d.ts +63 -23
  95. package/dist/docx/tableFormatting/reading.js +132 -38
  96. package/dist/docx/tableFormatting.d.ts +1 -0
  97. package/dist/docx/tableFormatting.js +1 -0
  98. package/dist/docx/tableTemplate.js +30 -19
  99. package/dist/download.d.ts +8 -5
  100. package/dist/download.js +2 -0
  101. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  102. package/dist/editor/commands/comments/editing.d.ts +6 -2
  103. package/dist/editor/commands/comments/editing.js +19 -31
  104. package/dist/editor/commands/exportQueries.d.ts +15 -0
  105. package/dist/editor/commands/exportQueries.js +14 -0
  106. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  107. package/dist/editor/commands/fidelityQueries.js +8 -0
  108. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  109. package/dist/editor/commands/formatting/editing.js +26 -108
  110. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  111. package/dist/editor/commands/formatting/propertyCommands.js +108 -0
  112. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  113. package/dist/editor/commands/formatting/shared.js +5 -2
  114. package/dist/editor/commands/indentCommands.js +5 -4
  115. package/dist/editor/commands/index.d.ts +11 -0
  116. package/dist/editor/commands/index.js +5 -0
  117. package/dist/editor/commands/linkCommands.js +5 -6
  118. package/dist/editor/commands/listCommands.d.ts +6 -3
  119. package/dist/editor/commands/listCommands.js +73 -30
  120. package/dist/editor/commands/lockCommands.d.ts +7 -1
  121. package/dist/editor/commands/lockCommands.js +35 -46
  122. package/dist/editor/commands/paragraphCommands.js +33 -31
  123. package/dist/editor/commands/spacingCommands.js +1 -1
  124. package/dist/editor/createEditor.d.ts +17 -29
  125. package/dist/editor/createEditor.js +58 -58
  126. package/dist/editor/documentStyles.d.ts +11 -35
  127. package/dist/editor/documentStyles.js +9 -52
  128. package/dist/editor/editorDocument.d.ts +57 -0
  129. package/dist/editor/editorDocument.js +86 -0
  130. package/dist/editor/externalClipboard.d.ts +13 -2
  131. package/dist/editor/externalClipboard.js +76 -75
  132. package/dist/editor/insertTable.js +4 -3
  133. package/dist/editor/paragraphEdits.d.ts +27 -19
  134. package/dist/editor/paragraphEdits.js +18 -19
  135. package/dist/editor/paragraphPlacement.d.ts +11 -0
  136. package/dist/editor/paragraphPlacement.js +18 -0
  137. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  138. package/dist/editor/plugins/displayDerivation.js +78 -0
  139. package/dist/editor/plugins/imagePaste.js +11 -7
  140. package/dist/editor/plugins/keymap.js +18 -3
  141. package/dist/editor/plugins/numberingDecorations.d.ts +14 -14
  142. package/dist/editor/plugins/numberingDecorations.js +50 -19
  143. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  144. package/dist/editor/plugins/paragraphDisplay.js +57 -0
  145. package/dist/editor/plugins/tabLayout.js +1 -1
  146. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  147. package/dist/editor/plugins/tableDisplay.js +30 -0
  148. package/dist/index.d.ts +2 -0
  149. package/dist/model/format.d.ts +80 -8
  150. package/dist/model/format.js +100 -22
  151. package/dist/model/tabStops.d.ts +9 -0
  152. package/dist/model/tabStops.js +18 -0
  153. package/dist/numbering/listRegistry.d.ts +37 -0
  154. package/dist/numbering/listRegistry.js +112 -0
  155. package/dist/numbering/listTemplate.d.ts +26 -17
  156. package/dist/numbering/listTemplate.js +88 -25
  157. package/dist/numbering/markers.d.ts +15 -6
  158. package/dist/numbering/markers.js +26 -59
  159. package/dist/numbering/parseNumbering.d.ts +84 -6
  160. package/dist/numbering/parseNumbering.js +131 -44
  161. package/dist/numbering/spellers.d.ts +15 -0
  162. package/dist/numbering/spellers.js +96 -0
  163. package/dist/numbering/writeNumbering.d.ts +8 -7
  164. package/dist/numbering/writeNumbering.js +28 -39
  165. package/dist/ooxml/childOrder.d.ts +34 -0
  166. package/dist/ooxml/childOrder.js +496 -0
  167. package/dist/ooxml/conformance.d.ts +28 -0
  168. package/dist/ooxml/conformance.js +20 -0
  169. package/dist/ooxml/element.d.ts +24 -11
  170. package/dist/ooxml/element.js +31 -12
  171. package/dist/ooxml/errors.d.ts +6 -5
  172. package/dist/ooxml/image.d.ts +4 -0
  173. package/dist/ooxml/image.js +2 -5
  174. package/dist/ooxml/names.d.ts +9 -0
  175. package/dist/ooxml/names.js +8 -1
  176. package/dist/ooxml/partSplice.d.ts +67 -0
  177. package/dist/ooxml/partSplice.js +176 -0
  178. package/dist/ooxml/props.d.ts +112 -0
  179. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  180. package/dist/ooxml/simpleTypes.d.ts +103 -0
  181. package/dist/ooxml/simpleTypes.js +182 -0
  182. package/dist/ooxml/tabStops.js +8 -27
  183. package/dist/ooxml/tagScan.d.ts +34 -0
  184. package/dist/ooxml/tagScan.js +108 -0
  185. package/dist/ooxml/units.d.ts +25 -13
  186. package/dist/ooxml/units.js +65 -31
  187. package/dist/ooxml/xml.d.ts +35 -15
  188. package/dist/ooxml/xml.js +43 -11
  189. package/dist/page/PageGuides.d.ts +1 -1
  190. package/dist/page/PageGuides.js +0 -13
  191. package/dist/page/blockKinds.d.ts +61 -0
  192. package/dist/page/blockKinds.js +11 -0
  193. package/dist/page/kinds/index.d.ts +6 -0
  194. package/dist/page/kinds/index.js +10 -0
  195. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  196. package/dist/page/kinds/paragraphKind.js +71 -0
  197. package/dist/page/kinds/tableKind.d.ts +10 -0
  198. package/dist/page/kinds/tableKind.js +178 -0
  199. package/dist/page/measureBlocks.d.ts +3 -14
  200. package/dist/page/measureBlocks.js +24 -45
  201. package/dist/page/pageDecorations.d.ts +27 -44
  202. package/dist/page/pageDecorations.js +70 -152
  203. package/dist/page/pageLayout.d.ts +2 -29
  204. package/dist/page/pageLayout.js +39 -16
  205. package/dist/page/usePageLayout.d.ts +2 -24
  206. package/dist/page/usePageLayout.js +5 -48
  207. package/dist/schema/attrRoles.d.ts +25 -18
  208. package/dist/schema/attrRoles.js +110 -68
  209. package/dist/schema/displayDerivation.d.ts +82 -0
  210. package/dist/schema/displayDerivation.js +130 -0
  211. package/dist/schema/docxSchema.d.ts +3 -0
  212. package/dist/schema/docxSchema.js +58 -17
  213. package/dist/schema/editGuard.d.ts +1 -1
  214. package/dist/schema/guards.d.ts +37 -14
  215. package/dist/schema/guards.js +29 -3
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +11 -44
  219. package/dist/schema/locks.js +0 -9
  220. package/dist/schema/preservedGuards.d.ts +26 -6
  221. package/dist/schema/preservedGuards.js +31 -1
  222. package/dist/schema/protection.d.ts +7 -1
  223. package/dist/schema/protection.js +2 -1
  224. package/dist/schema/sourceEquality.d.ts +1 -9
  225. package/dist/schema/sourceEquality.js +1 -27
  226. package/dist/styles/classNames.d.ts +13 -2
  227. package/dist/styles/classNames.js +13 -2
  228. package/dist/styles/inlineStyle.js +15 -6
  229. package/dist/styles.css +16 -18
  230. package/dist/table/cellFormatting.d.ts +8 -1
  231. package/dist/table/cellFormatting.js +11 -20
  232. package/dist/table/commands.js +20 -18
  233. package/dist/table/format.d.ts +3 -0
  234. package/dist/table/format.js +3 -9
  235. package/dist/table/gridBorders.d.ts +24 -30
  236. package/dist/table/gridBorders.js +35 -59
  237. package/dist/table/merge.d.ts +2 -6
  238. package/dist/table/merge.js +5 -5
  239. package/package.json +7 -3
  240. package/dist/docx/comments/contentTypes.d.ts +0 -7
  241. package/dist/docx/comments/contentTypes.js +0 -38
  242. package/dist/docx/comments/verifying.js +0 -206
  243. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  244. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  245. package/dist/docx/propsXml.d.ts +0 -67
  246. package/dist/docx/uniqueControls.d.ts +0 -14
  247. package/dist/docx/uniqueControls.js +0 -62
  248. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  249. package/dist/editor/plugins/commentReservations.js +0 -26
  250. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  251. package/dist/editor/plugins/styledParagraphs.js +0 -65
  252. package/dist/page/tableMeasurements.d.ts +0 -18
  253. package/dist/page/tableMeasurements.js +0 -119
@@ -1,7 +1,7 @@
1
1
  // src/docx/comments/people.ts
2
2
  import { xmlnsAttr } from "../../ooxml/element.js";
3
- import { DocxExportError } from "../../ooxml/errors.js";
4
3
  import { xmlnsDecl } from "../../ooxml/names.js";
4
+ import { splicePart } from "../../ooxml/partSplice.js";
5
5
  import {
6
6
  attributeByLocalName,
7
7
  childByLocalName,
@@ -10,20 +10,9 @@ import {
10
10
  encodeUtf8,
11
11
  parseXml
12
12
  } from "../../ooxml/xml.js";
13
- import {
14
- directoryOf,
15
- readRelationships,
16
- relsPathOf,
17
- resolveTarget
18
- } from "../relationships.js";
19
- import {
20
- COMMENT_AUTHOR_PROVIDER,
21
- CONTENT_TYPES_PATH,
22
- PEOPLE_CONTENT_TYPE,
23
- PEOPLE_REL_TYPE,
24
- W15_NS
25
- } from "./constants.js";
26
- import { withContentType } from "./contentTypes.js";
13
+ import { relatedPartPath } from "../packageParts.js";
14
+ import { directoryOf } from "../relationships.js";
15
+ import { COMMENT_AUTHOR_PROVIDER, PEOPLE_REL_TYPE, W15_NS } from "./constants.js";
27
16
  import { renderPerson } from "./grammar.js";
28
17
  var NO_PEOPLE = {
29
18
  partPath: null,
@@ -32,11 +21,8 @@ var NO_PEOPLE = {
32
21
  byAuthor: /* @__PURE__ */ new Map()
33
22
  };
34
23
  function readPeople(parts, mainPartPath) {
35
- const relationship = readRelationships(parts, relsPathOf(mainPartPath)).find(
36
- (entry) => entry.type === PEOPLE_REL_TYPE && !entry.external
37
- );
38
- if (!relationship) return NO_PEOPLE;
39
- const partPath = resolveTarget(mainPartPath, relationship.target);
24
+ const partPath = relatedPartPath(parts, mainPartPath, PEOPLE_REL_TYPE);
25
+ if (partPath === null) return NO_PEOPLE;
40
26
  const bytes = parts.get(partPath);
41
27
  if (!bytes) return { ...NO_PEOPLE, partPath };
42
28
  const { text, hadBom } = decodeUtf8(bytes);
@@ -85,43 +71,6 @@ function w15Prefix(root) {
85
71
  const prefix = declaration.name.slice("xmlns:".length);
86
72
  return prefix === "" ? "" : `${prefix}:`;
87
73
  }
88
- function rootTagStart(xml) {
89
- let at = 0;
90
- for (; ; ) {
91
- const opens = xml.indexOf("<", at);
92
- if (opens === -1) return -1;
93
- if (xml.startsWith("<!--", opens)) {
94
- at = xml.indexOf("-->", opens + 4) + 3;
95
- continue;
96
- }
97
- if (xml.startsWith("<?", opens)) {
98
- at = xml.indexOf("?>", opens + 2) + 2;
99
- continue;
100
- }
101
- return opens;
102
- }
103
- }
104
- function openTagEnd(xml, at) {
105
- let quote = null;
106
- for (let i = at; i < xml.length; i += 1) {
107
- const character = xml[i];
108
- if (quote !== null) {
109
- if (character === quote) quote = null;
110
- continue;
111
- }
112
- if (character === '"' || character === "'") {
113
- quote = character;
114
- continue;
115
- }
116
- if (character === ">") {
117
- return { end: i + 1, selfClosing: xml[i - 1] === "/" };
118
- }
119
- }
120
- return null;
121
- }
122
- function malformed(detail) {
123
- return new DocxExportError("malformed-xml", `the people part ${detail}`);
124
- }
125
74
  function peopleXml(people, added) {
126
75
  const xml = people.xml;
127
76
  if (xml === null) {
@@ -131,11 +80,7 @@ function peopleXml(people, added) {
131
80
  );
132
81
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:people ${xmlnsDecl("w15")}>${persons2.join("")}</w15:people>`;
133
82
  }
134
- const root = parseXml(xml).documentElement;
135
- if (root.localName !== "people") {
136
- throw malformed("has no people root element");
137
- }
138
- const prefix = w15Prefix(root);
83
+ const prefix = w15Prefix(parseXml(xml).documentElement);
139
84
  const persons = Array.from(
140
85
  added,
141
86
  ([author, userId]) => renderPerson(
@@ -145,46 +90,24 @@ function peopleXml(people, added) {
145
90
  prefix === null ? xmlnsAttr("w15") : null
146
91
  )
147
92
  ).join("");
148
- const start = rootTagStart(xml);
149
- const open = start === -1 ? null : openTagEnd(xml, start);
150
- if (open === null) throw malformed("has no people root element");
151
- if (open.selfClosing) {
152
- return `${xml.slice(0, open.end - 2)}>${persons}</${root.nodeName}>` + xml.slice(open.end);
153
- }
154
- const close = xml.lastIndexOf(`</${root.nodeName}`);
155
- if (close === -1) throw malformed("has no closing people tag");
156
- return xml.slice(0, close) + persons + xml.slice(close);
157
- }
158
- function availablePeoplePath(session) {
159
- const directory = directoryOf(session.mainPartPath);
160
- for (let suffix = 0; ; suffix += 1) {
161
- const name = suffix === 0 ? "people.xml" : `people${suffix + 1}.xml`;
162
- const path = directory + name;
163
- if (!session.parts.has(path)) return path;
164
- }
93
+ return splicePart(xml, { root: "people", append: persons });
165
94
  }
166
- function planPeoplePart(bodies, session, relationships, currentContentTypes) {
95
+ function planPeoplePart(part, bodies, session, context) {
167
96
  const people = session.comments.people;
168
97
  const added = unrecordedAuthors(bodies, people);
169
98
  if (added.size === 0) return null;
170
99
  const parts = /* @__PURE__ */ new Map();
171
- const addingPart = people.partPath === null;
172
- const partPath = people.partPath ?? availablePeoplePath(session);
100
+ const addingPart = part.pathIn(session) === null;
101
+ const partPath = part.writePathIn(session);
173
102
  if (addingPart) {
174
- relationships.add({
175
- type: PEOPLE_REL_TYPE,
103
+ context.relationships.add({
104
+ type: part.relType,
176
105
  target: partPath.slice(directoryOf(session.mainPartPath).length)
177
106
  });
178
107
  }
179
108
  parts.set(partPath, encodeUtf8(peopleXml(people, added), people.hadBom));
180
109
  if (addingPart || people.xml === null) {
181
- const contentTypes = withContentType(
182
- session.parts,
183
- partPath,
184
- PEOPLE_CONTENT_TYPE,
185
- currentContentTypes
186
- );
187
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
110
+ context.contentTypes.addOverride(partPath, part.contentType);
188
111
  }
189
112
  return parts;
190
113
  }
@@ -192,5 +115,6 @@ export {
192
115
  NO_PEOPLE,
193
116
  commentAuthorId,
194
117
  planPeoplePart,
195
- readPeople
118
+ readPeople,
119
+ unrecordedAuthors
196
120
  };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The protection that takes comments and nothing else, as the policy both the part planners and
3
+ * the server verifier read (`../protectionPolicy`).
4
+ */
5
+ type StoryReason = "body-changed" | "comment-not-owned" | "comment-author-forged";
6
+ export declare const commentsPolicy: import("../protectionPolicy").ProtectionPolicy<StoryReason, "comment-markup-rejected">;
7
+ export {};
@@ -0,0 +1,43 @@
1
+ // src/docx/comments/policy.ts
2
+ import {
3
+ commentAdditionsBy,
4
+ commentEditsOwned,
5
+ commentIdentitiesKept,
6
+ withoutComments
7
+ } from "../../schema/protection.js";
8
+ import {
9
+ registerProtectionPolicy
10
+ } from "../protectionPolicy.js";
11
+ import { comparableStory } from "../storyProjection.js";
12
+ import { COMMENT_STORY_PARTS } from "./parts.js";
13
+ var refused = (reason) => ({ ok: false, reason });
14
+ function sameBody(before, after) {
15
+ const was = comparableStory(before, withoutComments);
16
+ const now = comparableStory(after, withoutComments);
17
+ return was !== null && now !== null && was.length === now.length && was.every((block, at) => block === now[at]);
18
+ }
19
+ function commentsStoryKept(before, after, authorId, { editableComments }) {
20
+ if (!sameBody(before, after)) return refused("body-changed");
21
+ if (!commentIdentitiesKept(before.doc, after.doc) || !commentAdditionsBy(before.doc, after.doc, authorId)) {
22
+ return refused("comment-author-forged");
23
+ }
24
+ if (!commentEditsOwned(before.doc, after.doc, {
25
+ protection: "comments",
26
+ authorId,
27
+ editableComments
28
+ })) {
29
+ return refused("comment-not-owned");
30
+ }
31
+ return { ok: true };
32
+ }
33
+ var commentsPolicy = registerProtectionPolicy(
34
+ {
35
+ level: "comments",
36
+ parts: COMMENT_STORY_PARTS,
37
+ rejectedMarkup: "comment-markup-rejected",
38
+ storyKept: commentsStoryKept
39
+ }
40
+ );
41
+ export {
42
+ commentsPolicy
43
+ };
@@ -7,7 +7,7 @@ import {
7
7
  serializeXml,
8
8
  W_NS
9
9
  } from "../../ooxml/xml.js";
10
- import { readRelationships, relsPathOf, resolveTarget } from "../relationships.js";
10
+ import { relatedPartPath } from "../packageParts.js";
11
11
  import { COMMENTS_EXTENDED_REL_TYPE, COMMENTS_REL_TYPE } from "./constants.js";
12
12
  import { lastBodyParagraph } from "./grammar.js";
13
13
  import {
@@ -43,10 +43,12 @@ function lastParagraphId(comment) {
43
43
  return last === null ? null : attributeByLocalName(last, "paraId");
44
44
  }
45
45
  function readCommentExtensions(parts, mainPartPath) {
46
- const relationship = readRelationships(parts, relsPathOf(mainPartPath)).find(
47
- (entry) => entry.type === COMMENTS_EXTENDED_REL_TYPE && !entry.external
46
+ const partPath = relatedPartPath(
47
+ parts,
48
+ mainPartPath,
49
+ COMMENTS_EXTENDED_REL_TYPE
48
50
  );
49
- if (!relationship) {
51
+ if (partPath === null) {
50
52
  return {
51
53
  partPath: null,
52
54
  xml: null,
@@ -55,7 +57,6 @@ function readCommentExtensions(parts, mainPartPath) {
55
57
  ordered: []
56
58
  };
57
59
  }
58
- const partPath = resolveTarget(mainPartPath, relationship.target);
59
60
  const bytes = parts.get(partPath);
60
61
  if (!bytes) {
61
62
  return {
@@ -97,11 +98,8 @@ function readComments(parts, mainPartPath) {
97
98
  extendedHadBom: extensions.hadBom,
98
99
  extendedOrdered: extensions.ordered
99
100
  };
100
- const relationship = readRelationships(parts, relsPathOf(mainPartPath)).find(
101
- (entry) => entry.type === COMMENTS_REL_TYPE && !entry.external
102
- );
103
- if (!relationship) return { ...NO_COMMENTS, ...aside };
104
- const partPath = resolveTarget(mainPartPath, relationship.target);
101
+ const partPath = relatedPartPath(parts, mainPartPath, COMMENTS_REL_TYPE);
102
+ if (partPath === null) return { ...NO_COMMENTS, ...aside };
105
103
  const bytes = parts.get(partPath);
106
104
  if (!bytes) return { ...NO_COMMENTS, partPath, ...aside };
107
105
  const { text, hadBom } = decodeUtf8(bytes);
@@ -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,17 +1,13 @@
1
1
  // src/docx/comments/writing.ts
2
- import { elementXml, xmlnsAttr } from "../../ooxml/element.js";
3
- import { DocxExportError } from "../../ooxml/errors.js";
4
- import { wName, xmlnsDecl } from "../../ooxml/names.js";
2
+ import { elementXml } 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";
5
9
  import { encodeUtf8 } from "../../ooxml/xml.js";
6
10
  import { directoryOf } from "../relationships.js";
7
- import {
8
- COMMENTS_CONTENT_TYPE,
9
- COMMENTS_EXTENDED_CONTENT_TYPE,
10
- COMMENTS_EXTENDED_REL_TYPE,
11
- COMMENTS_REL_TYPE,
12
- CONTENT_TYPES_PATH
13
- } from "./constants.js";
14
- import { withContentType } from "./contentTypes.js";
15
11
  import {
16
12
  arrivedEntries,
17
13
  renderCommentBody,
@@ -21,6 +17,7 @@ import {
21
17
  import {
22
18
  commentReferencesIn
23
19
  } from "./model.js";
20
+ import { commentsExtendedPart, commentsPart, peoplePart } from "./parts.js";
24
21
  import { planPeoplePart } from "./people.js";
25
22
  function needsThreadKey(id, comment, session) {
26
23
  return comment.imported && carriesThreadMetadata(comment) && (session.comments.byId.get(id)?.paraId ?? null) === null;
@@ -77,34 +74,21 @@ function extensionsXml(references, comments, originallyReferenced) {
77
74
  if (comments.extendedXml === null) {
78
75
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx ${xmlnsDecl("w15")}>${pieces.join("")}</w15:commentsEx>`;
79
76
  }
80
- const open = /<(?:[^\s<>/:="']+:)?commentsEx\b[^>]*>/.exec(
81
- comments.extendedXml
77
+ return ensureRootDeclarations(
78
+ splicePart(comments.extendedXml, {
79
+ root: EXTENSIONS_ROOT,
80
+ replaceChildren: pieces.join("")
81
+ }),
82
+ { namespaces: { w15: NAMESPACES.w15 } }
82
83
  );
83
- if (!open) {
84
- throw new DocxExportError(
85
- "malformed-xml",
86
- "the Comments Extended part has no commentsEx root element"
87
- );
88
- }
89
- const name = /^<([^\s>]+)/.exec(open[0])?.[1];
90
- if (name === void 0) {
91
- throw new DocxExportError(
92
- "malformed-xml",
93
- "the Comments Extended part has no commentsEx root element"
94
- );
95
- }
96
- const bodyAt = open.index + open[0].length;
97
- if (open[0].endsWith("/>")) {
98
- return comments.extendedXml.slice(0, open.index) + `${open[0].slice(0, -2)}>${pieces.join("")}</${name}>` + comments.extendedXml.slice(bodyAt);
99
- }
100
- const close = comments.extendedXml.lastIndexOf(`</${name}>`);
101
- if (close === -1) {
102
- throw new DocxExportError(
103
- "malformed-xml",
104
- "the Comments Extended part has no closing commentsEx tag"
105
- );
106
- }
107
- return comments.extendedXml.slice(0, bodyAt) + pieces.join("") + comments.extendedXml.slice(close);
84
+ }
85
+ var COMMENTS_ROOT = "comments";
86
+ var EXTENSIONS_ROOT = "commentsEx";
87
+ function commentsRootProblem(xml) {
88
+ return partRootProblem(xml, COMMENTS_ROOT);
89
+ }
90
+ function extensionsRootProblem(xml) {
91
+ return partRootProblem(xml, EXTENSIONS_ROOT);
108
92
  }
109
93
  function extensionsChanged(doc, session) {
110
94
  const current = commentReferencesIn(doc);
@@ -136,7 +120,6 @@ function renderedComment(comment, arrivedKeyed, arrived) {
136
120
  ) : comment.commentXml;
137
121
  }
138
122
  const attrs = [
139
- xmlnsAttr("w"),
140
123
  [wName("id"), comment.id],
141
124
  comment.author === null ? null : [wName("author"), comment.author],
142
125
  comment.date === null ? null : [wName("date"), comment.date],
@@ -150,23 +133,11 @@ function renderedComment(comment, arrivedKeyed, arrived) {
150
133
  [body]
151
134
  );
152
135
  }
153
- function withThreadMarkupCompatibility(openTag) {
154
- let updated = openTag;
155
- if (!/\sxmlns:w14\s*=/.test(updated)) {
156
- updated = updated.replace(/>$/, ` ${xmlnsDecl("w14")}>`);
157
- }
158
- if (!/\sxmlns:mc\s*=/.test(updated)) {
159
- updated = updated.replace(/>$/, ` ${xmlnsDecl("mc")}>`);
160
- }
161
- const ignorable = /\smc:Ignorable\s*=\s*(["'])([^"']*)\1/.exec(updated);
162
- if (!ignorable) {
163
- return updated.replace(/>$/, ' mc:Ignorable="w14">');
164
- }
165
- const tokens = ignorable[2].split(/\s+/).filter(Boolean);
166
- if (tokens.includes("w14")) return updated;
167
- const replacement = ` mc:Ignorable=${ignorable[1]}${[...tokens, "w14"].join(" ")}${ignorable[1]}`;
168
- return updated.slice(0, ignorable.index) + replacement + updated.slice(ignorable.index + ignorable[0].length);
169
- }
136
+ var COMMENT_MARKUP = { namespaces: { w: NAMESPACES.w } };
137
+ var THREAD_MARKUP = {
138
+ namespaces: { w: NAMESPACES.w, w14: NAMESPACES.w14, mc: NAMESPACES.mc },
139
+ ignorable: ["w14"]
140
+ };
170
141
  function currentCommentBodies(references) {
171
142
  const comments = /* @__PURE__ */ new Map();
172
143
  for (const [id, comment] of references) {
@@ -224,51 +195,27 @@ function commentsXml(references, comments, originallyReferenced) {
224
195
  const compatibility = hasThreadMetadata ? ` ${xmlnsDecl("w14")} ${xmlnsDecl("mc")} mc:Ignorable="w14"` : "";
225
196
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:comments ${xmlnsDecl("w")}${compatibility}>${pieces.join("")}</w:comments>`;
226
197
  }
227
- const open = /<(?:[^\s<>/:="']+:)?comments\b[^>]*>/.exec(comments.xml);
228
- if (!open) {
229
- throw new DocxExportError(
230
- "malformed-xml",
231
- "the Comments part has no comments root element"
232
- );
233
- }
234
- const name = /^<([^\s>]+)/.exec(open[0])?.[1];
235
- const close = name ? comments.xml.lastIndexOf(`</${name}>`) : -1;
236
- if (close === -1) {
237
- throw new DocxExportError(
238
- "malformed-xml",
239
- "the Comments part has no closing comments tag"
240
- );
241
- }
242
- const openTag = hasThreadMetadata ? withThreadMarkupCompatibility(open[0]) : open[0];
243
- return comments.xml.slice(0, open.index) + openTag + pieces.join("") + comments.xml.slice(close);
244
- }
245
- function availableCommentsPath(session) {
246
- const directory = directoryOf(session.mainPartPath);
247
- for (let suffix = 0; ; suffix += 1) {
248
- const name = suffix === 0 ? "comments.xml" : `comments${suffix + 1}.xml`;
249
- const path = directory + name;
250
- if (!session.parts.has(path)) return path;
251
- }
252
- }
253
- function availableCommentsExtendedPath(session) {
254
- const directory = directoryOf(session.mainPartPath);
255
- for (let suffix = 0; ; suffix += 1) {
256
- const name = suffix === 0 ? "commentsExtended.xml" : `commentsExtended${suffix + 1}.xml`;
257
- const path = directory + name;
258
- if (!session.parts.has(path)) return path;
259
- }
198
+ const rewritten = splicePart(comments.xml, {
199
+ root: COMMENTS_ROOT,
200
+ replaceChildren: pieces.join("")
201
+ });
202
+ return ensureRootDeclarations(
203
+ rewritten,
204
+ hasThreadMetadata ? THREAD_MARKUP : COMMENT_MARKUP
205
+ );
260
206
  }
261
- function planCommentParts(doc, session, relationships, currentContentTypes) {
207
+ function planCommentParts(doc, session, context) {
262
208
  const bodyChanged = commentsChanged(doc, session);
263
209
  const threadChanged = extensionsChanged(doc, session);
264
210
  if (!bodyChanged && !threadChanged) return null;
211
+ const { relationships, contentTypes } = context;
265
212
  const references = commentReferencesIn(doc);
266
- const addingPart = session.comments.partPath === null;
267
- const partPath = session.comments.partPath ?? availableCommentsPath(session);
213
+ const addingPart = commentsPart.pathIn(session) === null;
214
+ const partPath = commentsPart.writePathIn(session);
268
215
  const parts = /* @__PURE__ */ new Map();
269
216
  if (addingPart) {
270
217
  const target = partPath.slice(directoryOf(session.mainPartPath).length);
271
- relationships.add({ type: COMMENTS_REL_TYPE, target });
218
+ relationships.add({ type: commentsPart.relType, target });
272
219
  }
273
220
  if (bodyChanged) {
274
221
  parts.set(
@@ -280,22 +227,16 @@ function planCommentParts(doc, session, relationships, currentContentTypes) {
280
227
  );
281
228
  }
282
229
  if (addingPart || session.comments.xml === null) {
283
- const contentTypes = withContentType(
284
- session.parts,
285
- partPath,
286
- COMMENTS_CONTENT_TYPE,
287
- currentContentTypes
288
- );
289
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
230
+ contentTypes.addOverride(partPath, commentsPart.contentType);
290
231
  }
291
232
  if (threadChanged && (references.size > 0 || session.comments.extendedPartPath !== null)) {
292
- const addingExtendedPart = session.comments.extendedPartPath === null;
293
- const extendedPartPath = session.comments.extendedPartPath ?? availableCommentsExtendedPath(session);
233
+ const addingExtendedPart = commentsExtendedPart.pathIn(session) === null;
234
+ const extendedPartPath = commentsExtendedPart.writePathIn(session);
294
235
  if (addingExtendedPart) {
295
236
  const target = extendedPartPath.slice(
296
237
  directoryOf(session.mainPartPath).length
297
238
  );
298
- relationships.add({ type: COMMENTS_EXTENDED_REL_TYPE, target });
239
+ relationships.add({ type: commentsExtendedPart.relType, target });
299
240
  }
300
241
  parts.set(
301
242
  extendedPartPath,
@@ -309,26 +250,32 @@ function planCommentParts(doc, session, relationships, currentContentTypes) {
309
250
  )
310
251
  );
311
252
  if (addingExtendedPart || session.comments.extendedXml === null) {
312
- const contentTypes = withContentType(
313
- session.parts,
253
+ contentTypes.addOverride(
314
254
  extendedPartPath,
315
- COMMENTS_EXTENDED_CONTENT_TYPE,
316
- parts.get(CONTENT_TYPES_PATH) ?? currentContentTypes
255
+ commentsExtendedPart.contentType
317
256
  );
318
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
319
257
  }
320
258
  }
321
259
  if (bodyChanged) {
322
260
  const people = planPeoplePart(
261
+ peoplePart,
323
262
  currentCommentBodies(references).values(),
324
263
  session,
325
- relationships,
326
- parts.get(CONTENT_TYPES_PATH) ?? currentContentTypes
264
+ context
327
265
  );
328
266
  for (const [path, bytes] of people ?? []) parts.set(path, bytes);
329
267
  }
330
- return { parts };
268
+ return parts;
331
269
  }
270
+ var commentsPlanner = {
271
+ name: "comments",
272
+ plan: planCommentParts
273
+ };
332
274
  export {
333
- planCommentParts
275
+ commentsChanged,
276
+ commentsPlanner,
277
+ commentsRootProblem,
278
+ currentCommentBodies,
279
+ extensionsChanged,
280
+ extensionsRootProblem
334
281
  };
@@ -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;