@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
@@ -0,0 +1,279 @@
1
+ // src/docx/comments/parts.ts
2
+ import { NAMESPACES } from "../../ooxml/names.js";
3
+ import {
4
+ attributeByLocalName,
5
+ isElement,
6
+ parseXml,
7
+ serializeXml,
8
+ W_NS
9
+ } from "../../ooxml/xml.js";
10
+ import { availablePartPath } from "../packageParts.js";
11
+ import {
12
+ COMMENTS_CONTENT_TYPE,
13
+ COMMENTS_EXTENDED_CONTENT_TYPE,
14
+ COMMENTS_EXTENDED_REL_TYPE,
15
+ COMMENTS_REL_TYPE,
16
+ PEOPLE_CONTENT_TYPE,
17
+ PEOPLE_REL_TYPE,
18
+ W14_NS,
19
+ W15_NS
20
+ } from "./constants.js";
21
+ import {
22
+ attributesWithin,
23
+ COMMENT_ATTRIBUTES,
24
+ declarationWritten,
25
+ lastBodyParagraph,
26
+ readStrictCommentBody,
27
+ recordedIdentity,
28
+ wellFormedCommentExtension,
29
+ wellFormedPerson
30
+ } from "./grammar.js";
31
+ import { commentReferencesIn } from "./model.js";
32
+ import { commentAuthorId } from "./people.js";
33
+ import { lastParagraphId } from "./reading.js";
34
+ var COMMENT_IDENTITY = [
35
+ "id",
36
+ "author",
37
+ "date",
38
+ "initials"
39
+ ];
40
+ function sameAttributes(entry, original, names) {
41
+ return names.every(
42
+ (name) => attributeByLocalName(entry, name) === attributeByLocalName(original, name)
43
+ );
44
+ }
45
+ function wellFormedEntry(entry) {
46
+ if (entry.namespaceURI === W_NS && entry.localName === "comment") {
47
+ return attributesWithin(entry, COMMENT_ATTRIBUTES) && readStrictCommentBody(entry) !== null;
48
+ }
49
+ if (entry.namespaceURI === W15_NS && entry.localName === "commentEx") {
50
+ return wellFormedCommentExtension(entry);
51
+ }
52
+ if (entry.namespaceURI === W15_NS && entry.localName === "person") {
53
+ return wellFormedPerson(entry);
54
+ }
55
+ return false;
56
+ }
57
+ function withoutThreadKey(entry) {
58
+ const copy = entry.cloneNode(true);
59
+ if (!isElement(copy)) return serializeXml(entry);
60
+ const last = lastBodyParagraph(copy);
61
+ last?.removeAttributeNS(W14_NS, "paraId");
62
+ return serializeXml(copy);
63
+ }
64
+ function threadKeyAlone(entry, original) {
65
+ const before = lastParagraphId(original);
66
+ const after = lastParagraphId(entry);
67
+ const kept = before === null || after === before;
68
+ return kept && withoutThreadKey(entry) === withoutThreadKey(original);
69
+ }
70
+ function entryAllowed(entry, original, authorId, editableComments, people) {
71
+ if (entry.namespaceURI === W_NS && entry.localName === "comment") {
72
+ const author = attributeByLocalName(entry, "author");
73
+ const recorded = author === null ? null : commentAuthorId(people, author);
74
+ if (original === null) return recorded === authorId;
75
+ const paraId = lastParagraphId(original);
76
+ if (!sameAttributes(entry, original, COMMENT_IDENTITY) || paraId !== null && lastParagraphId(entry) !== paraId) {
77
+ return false;
78
+ }
79
+ return editableComments === "all" || recorded === null || recorded === authorId;
80
+ }
81
+ if (entry.namespaceURI === W15_NS && entry.localName === "commentEx") {
82
+ if (original === null) return true;
83
+ return sameAttributes(entry, original, ["paraId", "paraIdParent"]);
84
+ }
85
+ if (entry.namespaceURI === W15_NS && entry.localName === "person") {
86
+ return original === null && recordedIdentity(entry) === authorId;
87
+ }
88
+ return false;
89
+ }
90
+ var TEXT_NODE = 3;
91
+ var COMPATIBILITY = /* @__PURE__ */ new Map([
92
+ ["xmlns:w14", NAMESPACES.w14],
93
+ ["xmlns:mc", NAMESPACES.mc]
94
+ ]);
95
+ var IGNORABLE = "mc:Ignorable";
96
+ var IGNORED = "w14";
97
+ function isLayout(node) {
98
+ return node.nodeType === TEXT_NODE && /^[ \t\r\n]*$/.test(node.nodeValue ?? "");
99
+ }
100
+ function surroundingNodes(parent) {
101
+ return parent === null ? [] : Array.from(parent.childNodes).filter(
102
+ (node) => !isElement(node) && !isLayout(node)
103
+ );
104
+ }
105
+ function sameNodes(now, was) {
106
+ return now.length === was.length && now.every((node, at) => {
107
+ const original = was[at];
108
+ return node.nodeType === original.nodeType && node.nodeName === original.nodeName && node.nodeValue === original.nodeValue;
109
+ });
110
+ }
111
+ function interEntryContentKept(now, was, rewritesContents) {
112
+ const current = surroundingNodes(now);
113
+ if (sameNodes(current, surroundingNodes(was))) return true;
114
+ return rewritesContents && current.length === 0 && was !== null && Array.from(now.children, serializeXml).join("") !== Array.from(was.children, serializeXml).join("");
115
+ }
116
+ function ignorableTokens(value) {
117
+ return new Set(value.split(/\s+/).filter(Boolean));
118
+ }
119
+ function ignorableKept(now, was) {
120
+ const gained = ignorableTokens(now);
121
+ const had = was === null ? /* @__PURE__ */ new Set() : ignorableTokens(was);
122
+ return Array.from(had).every((token) => gained.has(token)) && Array.from(gained).every((token) => had.has(token) || token === IGNORED);
123
+ }
124
+ function writtenRoot(root, shape) {
125
+ if (root.namespaceURI !== shape.namespace) return false;
126
+ if (root.localName !== shape.rootName) return false;
127
+ return Array.from(root.attributes).every(
128
+ (attr) => attr.name === IGNORABLE ? ignorableKept(attr.value, null) : declarationWritten(attr) || COMPATIBILITY.get(attr.name) === attr.value
129
+ );
130
+ }
131
+ function rootKeptAgainst(now, was, shape) {
132
+ if (was === null) return writtenRoot(now, shape);
133
+ if (now.nodeName !== was.nodeName) return false;
134
+ const had = new Map(
135
+ Array.from(was.attributes, (attr) => [attr.name, attr.value])
136
+ );
137
+ const kept = Array.from(now.attributes).every((attr) => {
138
+ const before = had.get(attr.name) ?? null;
139
+ if (attr.name === IGNORABLE) return ignorableKept(attr.value, before);
140
+ if (before === null) return COMPATIBILITY.get(attr.name) === attr.value;
141
+ return before === attr.value;
142
+ });
143
+ return kept && Array.from(had.keys()).every((name) => now.hasAttribute(name));
144
+ }
145
+ function entriesOf(shape, xml, reading) {
146
+ const entries = /* @__PURE__ */ new Map();
147
+ if (xml === null) return entries;
148
+ const strict = reading === "submitted";
149
+ for (const node of Array.from(parseXml(xml).documentElement.childNodes)) {
150
+ if (!isElement(node)) continue;
151
+ const el = node;
152
+ const named = strict ? el.namespaceURI === shape.namespace && el.localName === shape.localName : el.localName === shape.localName;
153
+ if (!named) {
154
+ if (strict) return null;
155
+ continue;
156
+ }
157
+ const id = attributeByLocalName(el, shape.idAttr);
158
+ if (id === null || entries.has(id)) {
159
+ if (strict) return null;
160
+ continue;
161
+ }
162
+ entries.set(id, { el, xml: serializeXml(el) });
163
+ }
164
+ return entries;
165
+ }
166
+ function arrivedIn(shape, session) {
167
+ return entriesOf(shape, shape.xmlIn(session), "arrived") ?? /* @__PURE__ */ new Map();
168
+ }
169
+ function storyPart(shape) {
170
+ return {
171
+ relType: shape.relType,
172
+ contentType: shape.contentType,
173
+ pathIn: shape.pathIn,
174
+ writePathIn: (session) => shape.pathIn(session) ?? availablePartPath(session.parts, session.mainPartPath, shape.baseName),
175
+ entriesIn: (session, reading) => entriesOf(shape, shape.xmlIn(session), reading),
176
+ rootKept: (before, after) => {
177
+ const submitted = shape.xmlIn(after);
178
+ if (submitted === null) return true;
179
+ const arrived = shape.xmlIn(before);
180
+ const now = parseXml(submitted);
181
+ const was = arrived === null ? null : parseXml(arrived);
182
+ return rootKeptAgainst(
183
+ now.documentElement,
184
+ was?.documentElement ?? null,
185
+ shape
186
+ ) && sameNodes(surroundingNodes(now), surroundingNodes(was)) && interEntryContentKept(
187
+ now.documentElement,
188
+ was?.documentElement ?? null,
189
+ shape.rewritesContents
190
+ );
191
+ },
192
+ referents: shape.referents,
193
+ wellFormed: wellFormedEntry,
194
+ anyonesChange: threadKeyAlone,
195
+ allowed: (entry, original, authorId, options, session) => entryAllowed(
196
+ entry,
197
+ original,
198
+ authorId,
199
+ options.editableComments,
200
+ session.comments.people
201
+ )
202
+ };
203
+ }
204
+ function referencedCommentIds(doc) {
205
+ const ids = /* @__PURE__ */ new Set();
206
+ for (const [id, comment] of commentReferencesIn(doc)) {
207
+ ids.add(id);
208
+ for (const reply of comment.replies) ids.add(reply.id);
209
+ }
210
+ return ids;
211
+ }
212
+ var commentsShape = {
213
+ relType: COMMENTS_REL_TYPE,
214
+ contentType: COMMENTS_CONTENT_TYPE,
215
+ baseName: "comments",
216
+ namespace: W_NS,
217
+ rootName: "comments",
218
+ rewritesContents: true,
219
+ localName: "comment",
220
+ idAttr: "id",
221
+ xmlIn: (session) => session.comments.xml,
222
+ pathIn: (session) => session.comments.partPath,
223
+ referents: (story) => referencedCommentIds(story.doc)
224
+ };
225
+ function commentEntries(story) {
226
+ return Array.from(
227
+ arrivedIn(commentsShape, story.session).values(),
228
+ (entry) => entry.el
229
+ );
230
+ }
231
+ var commentsPart = storyPart(commentsShape);
232
+ var commentsExtendedPart = storyPart({
233
+ relType: COMMENTS_EXTENDED_REL_TYPE,
234
+ contentType: COMMENTS_EXTENDED_CONTENT_TYPE,
235
+ baseName: "commentsExtended",
236
+ namespace: W15_NS,
237
+ rootName: "commentsEx",
238
+ rewritesContents: true,
239
+ localName: "commentEx",
240
+ idAttr: "paraId",
241
+ xmlIn: (session) => session.comments.extendedXml,
242
+ pathIn: (session) => session.comments.extendedPartPath,
243
+ // Thread state stands for a comment, and it is keyed by the comment's own thread key
244
+ referents: (story) => new Set(
245
+ commentEntries(story).flatMap((el) => {
246
+ const key = lastParagraphId(el);
247
+ return key === null ? [] : [key];
248
+ })
249
+ )
250
+ });
251
+ var peoplePart = storyPart({
252
+ relType: PEOPLE_REL_TYPE,
253
+ contentType: PEOPLE_CONTENT_TYPE,
254
+ baseName: "people",
255
+ namespace: W15_NS,
256
+ rootName: "people",
257
+ rewritesContents: false,
258
+ localName: "person",
259
+ idAttr: "author",
260
+ xmlIn: (session) => session.comments.people.xml,
261
+ pathIn: (session) => session.comments.people.partPath,
262
+ // An identity stands for a name somebody writes comments under
263
+ referents: (story) => new Set(
264
+ commentEntries(story).flatMap((el) => {
265
+ const author = attributeByLocalName(el, "author");
266
+ return author === null ? [] : [author];
267
+ })
268
+ )
269
+ });
270
+ var COMMENT_STORY_PARTS = [commentsPart, commentsExtendedPart, peoplePart];
271
+ export {
272
+ COMMENT_STORY_PARTS,
273
+ commentsExtendedPart,
274
+ commentsPart,
275
+ entryAllowed,
276
+ peoplePart,
277
+ threadKeyAlone,
278
+ wellFormedEntry
279
+ };
@@ -9,7 +9,8 @@
9
9
  * it stands and never appended to. Appending a second person for it would leave a file naming two
10
10
  * identities for one name, where a reader keying by name hands one author's comments to the other.
11
11
  */
12
- import { type RelationshipWriter } from "../relationships";
12
+ import type { PartPlanContext } from "../partPlan";
13
+ import type { StoryPartKind } from "../protectionPolicy";
13
14
  import type { SessionStore } from "../session";
14
15
  import type { CommentReferenceData, CommentReplyData } from "./model";
15
16
  export interface ImportedPeople {
@@ -31,9 +32,17 @@ export declare function readPeople(parts: Map<string, Uint8Array>, mainPartPath:
31
32
  * record, and for one the part records under more than one identity.
32
33
  */
33
34
  export declare function commentAuthorId(people: ImportedPeople, author: string): string | null;
35
+ /**
36
+ * The authors the part records no person for at all, each under the name they write as. A name it
37
+ * already records is left as it stands, whichever provider recorded it and whatever it resolves to.
38
+ */
39
+ export declare function unrecordedAuthors(bodies: Iterable<CommentReferenceData | CommentReplyData>, people: ImportedPeople): Map<string, string>;
34
40
  /**
35
41
  * Plans the people part, its relationship and its content type for every identity the current
36
42
  * comments carry that the document has not recorded. Null when it has recorded them all, which
37
43
  * leaves the part as it arrived.
44
+ *
45
+ * `part` is the description the verifier reads the part back through (`./parts`), so where this
46
+ * writes it and what it declares it as are the same facts on both sides.
38
47
  */
39
- export declare function planPeoplePart(bodies: Iterable<CommentReferenceData | CommentReplyData>, session: SessionStore, relationships: RelationshipWriter, currentContentTypes: Uint8Array | undefined): ReadonlyMap<string, Uint8Array> | null;
48
+ export declare function planPeoplePart(part: StoryPartKind, bodies: Iterable<CommentReferenceData | CommentReplyData>, session: SessionStore, context: PartPlanContext): ReadonlyMap<string, Uint8Array> | null;
@@ -1,42 +1,28 @@
1
1
  // src/docx/comments/people.ts
2
- import { DocxExportError } from "../../ooxml/errors.js";
2
+ import { xmlnsAttr } from "../../ooxml/element.js";
3
+ import { xmlnsDecl } from "../../ooxml/names.js";
4
+ import { splicePart } from "../../ooxml/partSplice.js";
3
5
  import {
6
+ attributeByLocalName,
4
7
  childByLocalName,
5
8
  decodeUtf8,
6
9
  elementChildren,
7
10
  encodeUtf8,
8
- escapeXml,
9
11
  parseXml
10
12
  } from "../../ooxml/xml.js";
11
- import {
12
- directoryOf,
13
- readRelationships,
14
- relsPathOf,
15
- resolveTarget
16
- } from "../relationships.js";
17
- import {
18
- COMMENT_AUTHOR_PROVIDER,
19
- CONTENT_TYPES_PATH,
20
- PEOPLE_CONTENT_TYPE,
21
- PEOPLE_REL_TYPE,
22
- W15_NS
23
- } from "./constants.js";
24
- 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";
16
+ import { renderPerson } from "./grammar.js";
25
17
  var NO_PEOPLE = {
26
18
  partPath: null,
27
19
  xml: null,
28
20
  hadBom: false,
29
21
  byAuthor: /* @__PURE__ */ new Map()
30
22
  };
31
- function attribute(el, localName) {
32
- return Array.from(el.attributes).find((entry) => entry.localName === localName)?.value ?? null;
33
- }
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);
@@ -45,14 +31,15 @@ function readPeople(parts, mainPartPath) {
45
31
  const byAuthor = /* @__PURE__ */ new Map();
46
32
  for (const el of elementChildren(root)) {
47
33
  if (el.localName !== "person") continue;
48
- const author = attribute(el, "author");
34
+ const author = attributeByLocalName(el, "author");
49
35
  if (author === null) continue;
50
36
  byAuthor.set(author, null);
51
37
  const presence = childByLocalName(el, "presenceInfo");
52
38
  if (presence === null) continue;
53
- if (attribute(presence, "providerId") !== COMMENT_AUTHOR_PROVIDER) continue;
39
+ if (attributeByLocalName(presence, "providerId") !== COMMENT_AUTHOR_PROVIDER)
40
+ continue;
54
41
  const ids = ourIds.get(author) ?? /* @__PURE__ */ new Set();
55
- ids.add(attribute(presence, "userId"));
42
+ ids.add(attributeByLocalName(presence, "userId"));
56
43
  ourIds.set(author, ids);
57
44
  }
58
45
  for (const [author, ids] of ourIds) {
@@ -64,9 +51,6 @@ function readPeople(parts, mainPartPath) {
64
51
  function commentAuthorId(people, author) {
65
52
  return people.byAuthor.get(author) ?? null;
66
53
  }
67
- function personXml(author, userId, prefix, declaration) {
68
- return `<${prefix}person${declaration} ${prefix}author="${escapeXml(author)}"><${prefix}presenceInfo ${prefix}providerId="${COMMENT_AUTHOR_PROVIDER}" ${prefix}userId="${escapeXml(userId)}"/></${prefix}person>`;
69
- }
70
54
  function unrecordedAuthors(bodies, people) {
71
55
  const unrecorded = /* @__PURE__ */ new Map();
72
56
  for (const body of bodies) {
@@ -87,106 +71,43 @@ function w15Prefix(root) {
87
71
  const prefix = declaration.name.slice("xmlns:".length);
88
72
  return prefix === "" ? "" : `${prefix}:`;
89
73
  }
90
- function rootTagStart(xml) {
91
- let at = 0;
92
- for (; ; ) {
93
- const opens = xml.indexOf("<", at);
94
- if (opens === -1) return -1;
95
- if (xml.startsWith("<!--", opens)) {
96
- at = xml.indexOf("-->", opens + 4) + 3;
97
- continue;
98
- }
99
- if (xml.startsWith("<?", opens)) {
100
- at = xml.indexOf("?>", opens + 2) + 2;
101
- continue;
102
- }
103
- return opens;
104
- }
105
- }
106
- function openTagEnd(xml, at) {
107
- let quote = null;
108
- for (let i = at; i < xml.length; i += 1) {
109
- const character = xml[i];
110
- if (quote !== null) {
111
- if (character === quote) quote = null;
112
- continue;
113
- }
114
- if (character === '"' || character === "'") {
115
- quote = character;
116
- continue;
117
- }
118
- if (character === ">") {
119
- return { end: i + 1, selfClosing: xml[i - 1] === "/" };
120
- }
121
- }
122
- return null;
123
- }
124
- function malformed(detail) {
125
- return new DocxExportError("malformed-xml", `the people part ${detail}`);
126
- }
127
74
  function peopleXml(people, added) {
128
75
  const xml = people.xml;
129
76
  if (xml === null) {
130
77
  const persons2 = Array.from(
131
78
  added,
132
- ([author, userId]) => personXml(author, userId, "w15:", "")
79
+ ([author, userId]) => renderPerson(author, userId, "w15:", null)
133
80
  );
134
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:people xmlns:w15="${W15_NS}">${persons2.join("")}</w15:people>`;
81
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:people ${xmlnsDecl("w15")}>${persons2.join("")}</w15:people>`;
135
82
  }
136
- const root = parseXml(xml).documentElement;
137
- if (root.localName !== "people") {
138
- throw malformed("has no people root element");
139
- }
140
- const prefix = w15Prefix(root);
83
+ const prefix = w15Prefix(parseXml(xml).documentElement);
141
84
  const persons = Array.from(
142
85
  added,
143
- ([author, userId]) => personXml(
86
+ ([author, userId]) => renderPerson(
144
87
  author,
145
88
  userId,
146
89
  prefix ?? "w15:",
147
- prefix === null ? ` xmlns:w15="${W15_NS}"` : ""
90
+ prefix === null ? xmlnsAttr("w15") : null
148
91
  )
149
92
  ).join("");
150
- const start = rootTagStart(xml);
151
- const open = start === -1 ? null : openTagEnd(xml, start);
152
- if (open === null) throw malformed("has no people root element");
153
- if (open.selfClosing) {
154
- return `${xml.slice(0, open.end - 2)}>${persons}</${root.nodeName}>` + xml.slice(open.end);
155
- }
156
- const close = xml.lastIndexOf(`</${root.nodeName}`);
157
- if (close === -1) throw malformed("has no closing people tag");
158
- return xml.slice(0, close) + persons + xml.slice(close);
93
+ return splicePart(xml, { root: "people", append: persons });
159
94
  }
160
- function availablePeoplePath(session) {
161
- const directory = directoryOf(session.mainPartPath);
162
- for (let suffix = 0; ; suffix += 1) {
163
- const name = suffix === 0 ? "people.xml" : `people${suffix + 1}.xml`;
164
- const path = directory + name;
165
- if (!session.parts.has(path)) return path;
166
- }
167
- }
168
- function planPeoplePart(bodies, session, relationships, currentContentTypes) {
95
+ function planPeoplePart(part, bodies, session, context) {
169
96
  const people = session.comments.people;
170
97
  const added = unrecordedAuthors(bodies, people);
171
98
  if (added.size === 0) return null;
172
99
  const parts = /* @__PURE__ */ new Map();
173
- const addingPart = people.partPath === null;
174
- const partPath = people.partPath ?? availablePeoplePath(session);
100
+ const addingPart = part.pathIn(session) === null;
101
+ const partPath = part.writePathIn(session);
175
102
  if (addingPart) {
176
- relationships.add({
177
- type: PEOPLE_REL_TYPE,
103
+ context.relationships.add({
104
+ type: part.relType,
178
105
  target: partPath.slice(directoryOf(session.mainPartPath).length)
179
106
  });
180
107
  }
181
108
  parts.set(partPath, encodeUtf8(peopleXml(people, added), people.hadBom));
182
109
  if (addingPart || people.xml === null) {
183
- const contentTypes = withContentType(
184
- session.parts,
185
- partPath,
186
- PEOPLE_CONTENT_TYPE,
187
- currentContentTypes
188
- );
189
- if (contentTypes) parts.set(CONTENT_TYPES_PATH, contentTypes);
110
+ context.contentTypes.addOverride(partPath, part.contentType);
190
111
  }
191
112
  return parts;
192
113
  }
@@ -194,5 +115,6 @@ export {
194
115
  NO_PEOPLE,
195
116
  commentAuthorId,
196
117
  planPeoplePart,
197
- readPeople
118
+ readPeople,
119
+ unrecordedAuthors
198
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
+ };
@@ -41,11 +41,13 @@ export interface ImportedCommentExtension {
41
41
  resolved: boolean;
42
42
  xml: string;
43
43
  }
44
+ /** The thread key of a comment, which the writer puts on the last paragraph of its body */
45
+ export declare function lastParagraphId(comment: Element): string | null;
44
46
  /**
45
47
  * Reads the Comments part related from the main document story.
46
48
  *
47
- * The people part is read whether or not there are comments: a document may carry one with no
48
- * comment left, and a comment added to it then has to be recorded in that part rather than in a
49
- * second one.
49
+ * The people part and the extended part are read whether or not there are comments: a document may
50
+ * carry either with no comment left, and a comment added to it then has to be written into the
51
+ * part it already has rather than into a second one beside it.
50
52
  */
51
53
  export declare function readComments(parts: Map<string, Uint8Array>, mainPartPath: string): ImportedComments;