@portone/docx-editor 0.3.0 → 0.5.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 (217) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +45 -25
  5. package/dist/core.d.ts +1 -1
  6. package/dist/docx/cloning.js +4 -9
  7. package/dist/docx/commentOnlyChange.d.ts +5 -3
  8. package/dist/docx/comments/grammar.d.ts +27 -10
  9. package/dist/docx/comments/grammar.js +18 -67
  10. package/dist/docx/comments/model.d.ts +5 -6
  11. package/dist/docx/comments/model.js +0 -9
  12. package/dist/docx/comments/parts.d.ts +16 -10
  13. package/dist/docx/comments/parts.js +65 -13
  14. package/dist/docx/comments/people.d.ts +11 -3
  15. package/dist/docx/comments/people.js +20 -6
  16. package/dist/docx/comments/policy.js +7 -1
  17. package/dist/docx/comments/reading.d.ts +4 -2
  18. package/dist/docx/comments/reading.js +1 -14
  19. package/dist/docx/comments/writing.js +78 -35
  20. package/dist/docx/exportDocx.js +30 -18
  21. package/dist/docx/exportRefs.d.ts +8 -3
  22. package/dist/docx/exportRefs.js +2 -1
  23. package/dist/docx/fidelity.js +24 -10
  24. package/dist/docx/fields.d.ts +42 -0
  25. package/dist/docx/fields.js +76 -0
  26. package/dist/docx/formatting/attrs.d.ts +20 -2
  27. package/dist/docx/formatting/attrs.js +57 -2
  28. package/dist/docx/formatting/context.d.ts +10 -2
  29. package/dist/docx/formatting/context.js +23 -5
  30. package/dist/docx/formatting/resolve.d.ts +10 -4
  31. package/dist/docx/formatting/resolve.js +31 -11
  32. package/dist/docx/formatting/styles.d.ts +25 -2
  33. package/dist/docx/formatting/styles.js +84 -3
  34. package/dist/docx/headersFooters.d.ts +89 -20
  35. package/dist/docx/headersFooters.js +163 -172
  36. package/dist/docx/hyperlink.d.ts +14 -4
  37. package/dist/docx/hyperlink.js +4 -1
  38. package/dist/docx/importDocx.d.ts +1 -1
  39. package/dist/docx/importDocx.js +135 -74
  40. package/dist/docx/importParagraph.d.ts +16 -4
  41. package/dist/docx/importParagraph.js +87 -93
  42. package/dist/docx/importPolicy.d.ts +70 -0
  43. package/dist/docx/importPolicy.js +190 -0
  44. package/dist/docx/importPreserved.d.ts +30 -0
  45. package/dist/docx/importPreserved.js +54 -0
  46. package/dist/docx/importTable.d.ts +2 -2
  47. package/dist/docx/importTable.js +107 -66
  48. package/dist/docx/invariants.js +42 -31
  49. package/dist/docx/newLists.d.ts +30 -0
  50. package/dist/docx/newLists.js +24 -1
  51. package/dist/docx/notes.d.ts +6 -1
  52. package/dist/docx/notes.js +6 -16
  53. package/dist/docx/numberingPlanner.d.ts +6 -3
  54. package/dist/docx/numberingPlanner.js +38 -7
  55. package/dist/docx/pageGeometry.d.ts +5 -14
  56. package/dist/docx/pageGeometry.js +0 -12
  57. package/dist/docx/protectionPolicy.d.ts +13 -5
  58. package/dist/docx/protectionPolicy.js +32 -22
  59. package/dist/docx/scan.d.ts +18 -8
  60. package/dist/docx/scan.js +17 -11
  61. package/dist/docx/sections.d.ts +126 -0
  62. package/dist/docx/sections.js +207 -0
  63. package/dist/docx/serializeBlock.d.ts +13 -4
  64. package/dist/docx/serializeBlock.js +16 -18
  65. package/dist/docx/serializeParagraph.d.ts +4 -0
  66. package/dist/docx/serializeParagraph.js +1 -0
  67. package/dist/docx/serializePreserved.d.ts +14 -0
  68. package/dist/docx/serializePreserved.js +24 -0
  69. package/dist/docx/serializeStory.d.ts +17 -0
  70. package/dist/docx/serializeStory.js +17 -0
  71. package/dist/docx/serializeTable.js +10 -13
  72. package/dist/docx/session.d.ts +24 -7
  73. package/dist/docx/session.js +37 -6
  74. package/dist/docx/story.d.ts +140 -0
  75. package/dist/docx/story.js +237 -0
  76. package/dist/docx/storyProjection.js +6 -1
  77. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  78. package/dist/docx/tableFormatting/conditions.js +196 -0
  79. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  80. package/dist/docx/tableFormatting/editing.js +4 -5
  81. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  82. package/dist/docx/tableFormatting/reading.js +109 -35
  83. package/dist/docx/tableFormatting.d.ts +1 -0
  84. package/dist/docx/tableFormatting.js +1 -0
  85. package/dist/docx/tableTemplate.js +12 -12
  86. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  87. package/dist/editor/clipboard/htmlReader.js +302 -0
  88. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  89. package/dist/editor/clipboard/internalChannel.js +58 -0
  90. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  91. package/dist/editor/clipboard/normalizers.js +199 -0
  92. package/dist/editor/clipboard/parser.d.ts +34 -0
  93. package/dist/editor/clipboard/parser.js +58 -0
  94. package/dist/editor/clipboard/plugin.d.ts +21 -0
  95. package/dist/editor/clipboard/plugin.js +320 -0
  96. package/dist/editor/clipboard/readContext.d.ts +24 -0
  97. package/dist/editor/clipboard/readContext.js +19 -0
  98. package/dist/editor/clipboard/readers.d.ts +33 -0
  99. package/dist/editor/clipboard/readers.js +37 -0
  100. package/dist/editor/commands/comments/editing.d.ts +26 -6
  101. package/dist/editor/commands/comments/editing.js +175 -105
  102. package/dist/editor/commands/comments/model.d.ts +34 -17
  103. package/dist/editor/commands/comments/model.js +11 -0
  104. package/dist/editor/commands/comments/reading.d.ts +5 -3
  105. package/dist/editor/commands/comments/reading.js +8 -58
  106. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  107. package/dist/editor/commands/index.d.ts +2 -2
  108. package/dist/editor/commands/index.js +2 -0
  109. package/dist/editor/commands/listCommands.d.ts +12 -3
  110. package/dist/editor/commands/listCommands.js +76 -30
  111. package/dist/editor/commands/noteQueries.d.ts +22 -5
  112. package/dist/editor/commands/noteQueries.js +34 -6
  113. package/dist/editor/commands/paragraphCommands.js +6 -1
  114. package/dist/editor/createEditor.js +14 -4
  115. package/dist/editor/documentStyles.d.ts +26 -4
  116. package/dist/editor/documentStyles.js +9 -4
  117. package/dist/editor/editorDocument.d.ts +7 -6
  118. package/dist/editor/editorDocument.js +18 -6
  119. package/dist/editor/imageFiles.d.ts +2 -2
  120. package/dist/editor/imageFiles.js +2 -0
  121. package/dist/editor/insertTable.d.ts +1 -1
  122. package/dist/editor/insertTable.js +2 -2
  123. package/dist/editor/paragraphEdits.d.ts +16 -1
  124. package/dist/editor/paragraphEdits.js +13 -5
  125. package/dist/editor/paragraphPlacement.d.ts +11 -0
  126. package/dist/editor/paragraphPlacement.js +18 -0
  127. package/dist/editor/plainText.d.ts +12 -0
  128. package/dist/editor/plainText.js +2 -1
  129. package/dist/editor/plugins/columnResize.js +6 -3
  130. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  131. package/dist/editor/plugins/commentComposer.js +46 -0
  132. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  133. package/dist/editor/plugins/commentDecorations.js +89 -15
  134. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  135. package/dist/editor/plugins/documentProjection.js +22 -0
  136. package/dist/editor/plugins/imagePaste.js +20 -10
  137. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  138. package/dist/editor/plugins/linkPanel.js +13 -27
  139. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  140. package/dist/editor/plugins/numberingDecorations.js +43 -2
  141. package/dist/editor/plugins/panelState.d.ts +67 -0
  142. package/dist/editor/plugins/panelState.js +54 -0
  143. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  144. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  145. package/dist/editor/plugins/tableContextMenu.js +30 -47
  146. package/dist/editor/plugins/tableDisplay.js +19 -5
  147. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  148. package/dist/editor/plugins/textContextMenu.js +30 -47
  149. package/dist/model/format.d.ts +50 -0
  150. package/dist/model/format.js +65 -0
  151. package/dist/numbering/listRegistry.d.ts +37 -0
  152. package/dist/numbering/listRegistry.js +112 -0
  153. package/dist/numbering/listTemplate.d.ts +26 -17
  154. package/dist/numbering/listTemplate.js +66 -20
  155. package/dist/numbering/markers.d.ts +15 -6
  156. package/dist/numbering/markers.js +26 -59
  157. package/dist/numbering/parseNumbering.d.ts +70 -5
  158. package/dist/numbering/parseNumbering.js +100 -26
  159. package/dist/numbering/spellers.d.ts +15 -0
  160. package/dist/numbering/spellers.js +96 -0
  161. package/dist/numbering/writeNumbering.d.ts +5 -3
  162. package/dist/numbering/writeNumbering.js +15 -15
  163. package/dist/ooxml/conformance.d.ts +28 -0
  164. package/dist/ooxml/conformance.js +20 -0
  165. package/dist/ooxml/errors.d.ts +5 -5
  166. package/dist/ooxml/fragment.d.ts +11 -0
  167. package/dist/ooxml/fragment.js +14 -4
  168. package/dist/ooxml/image.d.ts +3 -4
  169. package/dist/ooxml/image.js +50 -5
  170. package/dist/ooxml/names.d.ts +15 -0
  171. package/dist/ooxml/names.js +10 -1
  172. package/dist/ooxml/partSplice.d.ts +1 -1
  173. package/dist/ooxml/partSplice.js +10 -3
  174. package/dist/ooxml/props.d.ts +8 -0
  175. package/dist/ooxml/props.js +14 -4
  176. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  177. package/dist/ooxml/rangeMarkers.js +24 -0
  178. package/dist/ooxml/xml.d.ts +8 -9
  179. package/dist/ooxml/xml.js +6 -6
  180. package/dist/page/PageGuides.d.ts +8 -4
  181. package/dist/page/PageGuides.js +13 -37
  182. package/dist/page/pageLayout.d.ts +62 -13
  183. package/dist/page/pageLayout.js +91 -37
  184. package/dist/page/usePageLayout.d.ts +14 -11
  185. package/dist/page/usePageLayout.js +21 -29
  186. package/dist/schema/attrRoles.js +44 -19
  187. package/dist/schema/docxSchema.d.ts +9 -1
  188. package/dist/schema/docxSchema.js +220 -139
  189. package/dist/schema/editGuard.d.ts +1 -1
  190. package/dist/schema/guards.js +2 -2
  191. package/dist/schema/index.d.ts +1 -0
  192. package/dist/schema/preservedFragments.d.ts +12 -0
  193. package/dist/schema/preservedFragments.js +38 -0
  194. package/dist/schema/preservedGuards.d.ts +9 -7
  195. package/dist/schema/preservedGuards.js +112 -11
  196. package/dist/schema/protection.d.ts +34 -5
  197. package/dist/schema/protection.js +42 -14
  198. package/dist/schema/rendering.js +3 -1
  199. package/dist/schema/stories.d.ts +49 -0
  200. package/dist/schema/stories.js +78 -0
  201. package/dist/styles/classNames.d.ts +16 -7
  202. package/dist/styles/classNames.js +16 -7
  203. package/dist/styles.css +90 -30
  204. package/dist/table/cellFormatting.js +1 -2
  205. package/dist/table/gridBorders.d.ts +11 -15
  206. package/dist/table/gridBorders.js +32 -18
  207. package/dist/table/resize.d.ts +5 -3
  208. package/dist/ui/CommentsPanel.d.ts +1 -3
  209. package/dist/ui/CommentsPanel.js +24 -15
  210. package/dist/ui/NotesPanel.js +2 -2
  211. package/dist/ui/TextMenu.d.ts +1 -3
  212. package/dist/ui/TextMenu.js +10 -6
  213. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  214. package/dist/ui/comments/CommentComposer.js +26 -3
  215. package/package.json +1 -1
  216. package/dist/editor/externalClipboard.d.ts +0 -11
  217. package/dist/editor/externalClipboard.js +0 -456
@@ -1,14 +1,9 @@
1
1
  // src/docx/comments/grammar.ts
2
- import {
3
- attrsText,
4
- elementXml,
5
- xmlnsAttr
6
- } from "../../ooxml/element.js";
7
- import { NAMESPACES, qualify, wName } from "../../ooxml/names.js";
2
+ import { attrsText, elementXml } from "../../ooxml/element.js";
3
+ import { NAMESPACES, qualify } from "../../ooxml/names.js";
8
4
  import {
9
5
  attributeByLocalName,
10
6
  elementChildren,
11
- escapeXml,
12
7
  parseXml,
13
8
  W_NS
14
9
  } from "../../ooxml/xml.js";
@@ -16,7 +11,6 @@ import { COMMENT_AUTHOR_PROVIDER, W14_NS, W15_NS } from "./constants.js";
16
11
  var ELEMENT_NODE = 1;
17
12
  var TEXT_NODE = 3;
18
13
  var XMLNS_NS = "http://www.w3.org/2000/xmlns/";
19
- var XML_NS = "http://www.w3.org/XML/1998/namespace";
20
14
  var nameKey = (namespace, localName) => `${namespace ?? ""} ${localName}`;
21
15
  var COMMENT_ATTRIBUTES = /* @__PURE__ */ new Set([
22
16
  nameKey(W_NS, "id"),
@@ -29,12 +23,6 @@ var COMMENT_EX_ATTRIBUTES = /* @__PURE__ */ new Set([
29
23
  nameKey(W15_NS, "paraIdParent"),
30
24
  nameKey(W15_NS, "done")
31
25
  ]);
32
- var PARAGRAPH_ATTRIBUTES = /* @__PURE__ */ new Set([
33
- nameKey(W14_NS, "paraId")
34
- ]);
35
- var TEXT_ATTRIBUTES = /* @__PURE__ */ new Set([
36
- nameKey(XML_NS, "space")
37
- ]);
38
26
  var PERSON_ATTRIBUTES = /* @__PURE__ */ new Set([
39
27
  nameKey(W15_NS, "author")
40
28
  ]);
@@ -42,7 +30,6 @@ var PRESENCE_ATTRIBUTES = /* @__PURE__ */ new Set([
42
30
  nameKey(W15_NS, "providerId"),
43
31
  nameKey(W15_NS, "userId")
44
32
  ]);
45
- var NO_ATTRIBUTES = /* @__PURE__ */ new Set();
46
33
  var THREAD_KEY = /^[0-9A-Fa-f]{8}$/;
47
34
  function threadKeyOrNone(el, localName) {
48
35
  const value = attributeByLocalName(el, localName);
@@ -57,6 +44,11 @@ function declarationWritten(attr) {
57
44
  if (!attr.name.startsWith("xmlns:")) return false;
58
45
  return WRITTEN_NAMESPACES.get(attr.name.slice("xmlns:".length)) === attr.value;
59
46
  }
47
+ function declarationsWritten(el) {
48
+ return Array.from(el.attributes).every(
49
+ (attr) => !attr.name.startsWith("xmlns") || declarationWritten(attr)
50
+ ) && Array.from(el.children).every(declarationsWritten);
51
+ }
60
52
  function attributesWithin(el, allowed) {
61
53
  return Array.from(el.attributes).every(
62
54
  (attr) => attr.namespaceURI === XMLNS_NS ? declarationWritten(attr) : allowed.has(nameKey(attr.namespaceURI, attr.localName))
@@ -70,22 +62,13 @@ function holdsElementsOnly(el) {
70
62
  (node) => node.nodeType === ELEMENT_NODE
71
63
  );
72
64
  }
73
- function holdsTextOnly(el) {
74
- return Array.from(el.childNodes).every((node) => node.nodeType === TEXT_NODE);
75
- }
76
- function renderCommentBody(text, paraId) {
77
- const lines = text.split("\n");
78
- const pieces = [];
79
- lines.forEach((line, index) => {
80
- if (index > 0) pieces.push(elementXml(wName("br"), []));
81
- if (line.length > 0 || lines.length === 1) {
82
- pieces.push(
83
- elementXml(wName("t"), [["xml:space", "preserve"]], [escapeXml(line)])
84
- );
85
- }
86
- });
87
- const key = paraId === null ? [] : [xmlnsAttr("w14"), [qualify("w14", "paraId"), paraId]];
88
- return elementXml(wName("p"), key, [elementXml(wName("r"), [], pieces)]);
65
+ function isLayoutText(node) {
66
+ return node.nodeType === TEXT_NODE && /^[ \t\r\n]*$/.test(node.nodeValue ?? "");
67
+ }
68
+ function holdsElementsAndLayout(el) {
69
+ return Array.from(el.childNodes).every(
70
+ (node) => node.nodeType === ELEMENT_NODE || isLayoutText(node)
71
+ );
89
72
  }
90
73
  var PARAGRAPH_OR_SKIPPED = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<([^\s<>/:="']+:)?p(?=[\s/>])[^>]*>/g;
91
74
  function lastBodyParagraph(comment) {
@@ -132,25 +115,6 @@ function withThreadKey(commentXml, paraId, arrived) {
132
115
  const keyed = opening[0].slice(0, selfClosing ? -2 : -1) + ` ${attrsText([[qualify("w14", "paraId"), paraId]])}` + (selfClosing ? "/>" : ">");
133
116
  return commentXml.slice(0, opening.index) + keyed + commentXml.slice(opening.index + opening[0].length);
134
117
  }
135
- function readRunText(run) {
136
- if (!holdsElementsOnly(run)) return null;
137
- const pieces = [];
138
- for (const child of Array.from(run.children)) {
139
- if (isNamed(child, W_NS, "br")) {
140
- if (!attributesWithin(child, NO_ATTRIBUTES) || child.childNodes.length > 0) {
141
- return null;
142
- }
143
- pieces.push("\n");
144
- continue;
145
- }
146
- if (!isNamed(child, W_NS, "t")) return null;
147
- if (!attributesWithin(child, TEXT_ATTRIBUTES) || child.getAttributeNS(XML_NS, "space") !== "preserve" || !holdsTextOnly(child)) {
148
- return null;
149
- }
150
- pieces.push(child.textContent ?? "");
151
- }
152
- return pieces.join("");
153
- }
154
118
  function wellFormedCommentExtension(entry) {
155
119
  return attributesWithin(entry, COMMENT_EX_ATTRIBUTES) && entry.childNodes.length === 0 && threadKeyOrNone(entry, "paraId") && threadKeyOrNone(entry, "paraIdParent") && // The writer says a thread is settled or open, and nothing else `ST_OnOff` would take
156
120
  ["0", "1", null].includes(attributeByLocalName(entry, "done"));
@@ -167,21 +131,6 @@ function recordedIdentity(person) {
167
131
  const [presence] = Array.from(person.children);
168
132
  return presence === void 0 ? null : attributeByLocalName(presence, "userId");
169
133
  }
170
- function readStrictCommentBody(comment) {
171
- if (!holdsElementsOnly(comment)) return null;
172
- const paragraphs = Array.from(comment.children);
173
- if (paragraphs.length !== 1) return null;
174
- const [paragraph] = paragraphs;
175
- if (!isNamed(paragraph, W_NS, "p")) return null;
176
- if (!attributesWithin(paragraph, PARAGRAPH_ATTRIBUTES)) return null;
177
- if (!holdsElementsOnly(paragraph)) return null;
178
- const runs = Array.from(paragraph.children);
179
- if (runs.length !== 1) return null;
180
- const [run] = runs;
181
- if (!isNamed(run, W_NS, "r")) return null;
182
- if (!attributesWithin(run, NO_ATTRIBUTES)) return null;
183
- return readRunText(run);
184
- }
185
134
  function renderCommentExtension(comment) {
186
135
  if (comment.extensionXml !== null) return comment.extensionXml;
187
136
  const parent = "parentParaId" in comment ? [[qualify("w15", "paraIdParent"), comment.parentParaId]] : [];
@@ -211,10 +160,12 @@ export {
211
160
  arrivedEntries,
212
161
  attributesWithin,
213
162
  declarationWritten,
163
+ declarationsWritten,
164
+ holdsElementsAndLayout,
165
+ holdsElementsOnly,
166
+ isLayoutText,
214
167
  lastBodyParagraph,
215
- readStrictCommentBody,
216
168
  recordedIdentity,
217
- renderCommentBody,
218
169
  renderCommentExtension,
219
170
  renderPerson,
220
171
  wellFormedCommentExtension,
@@ -3,15 +3,17 @@
3
3
  */
4
4
  import type { Node as PMNode } from "prosemirror-model";
5
5
  import type { ImportedComments } from "./reading";
6
+ /**
7
+ * A comment as the reference node in the story carries it: who wrote it, where its thread state
8
+ * hangs, and the replies under it. What it says is not here - a body is a story of its own, held
9
+ * on the document node under `comment:<id>` (`docx/story`).
10
+ */
6
11
  export interface CommentReferenceData {
7
12
  id: string;
8
13
  author: string | null;
9
14
  authorId: string | null;
10
15
  initials: string | null;
11
16
  date: string | null;
12
- text: string;
13
- commentXml: string | null;
14
- imported: boolean;
15
17
  paraId: string;
16
18
  resolved: boolean;
17
19
  extensionXml: string | null;
@@ -24,9 +26,6 @@ export interface CommentReplyData {
24
26
  authorId: string | null;
25
27
  initials: string | null;
26
28
  date: string | null;
27
- text: string;
28
- commentXml: string | null;
29
- imported: boolean;
30
29
  paraId: string;
31
30
  parentParaId: string;
32
31
  extensionXml: string | null;
@@ -26,9 +26,6 @@ function importedCommentReplies(comments, rootId) {
26
26
  authorId: reply.authorId,
27
27
  initials: reply.initials,
28
28
  date: reply.date,
29
- text: reply.text,
30
- commentXml: reply.xml,
31
- imported: true,
32
29
  paraId,
33
30
  parentParaId,
34
31
  extensionXml: reply.extensionXml
@@ -59,9 +56,6 @@ function replyData(value) {
59
56
  authorId: nullableString(entry.authorId),
60
57
  initials: nullableString(entry.initials),
61
58
  date: nullableString(entry.date),
62
- text: nullableString(entry.text) ?? "",
63
- commentXml: nullableString(entry.commentXml),
64
- imported: entry.imported === true,
65
59
  paraId,
66
60
  parentParaId,
67
61
  extensionXml: nullableString(entry.extensionXml)
@@ -79,9 +73,6 @@ function referenceData(node) {
79
73
  authorId: nullableString(node.attrs.authorId),
80
74
  initials: nullableString(node.attrs.initials),
81
75
  date: nullableString(node.attrs.date),
82
- text: nullableString(node.attrs.text) ?? "",
83
- commentXml: nullableString(node.attrs.commentXml),
84
- imported: node.attrs.imported === true,
85
76
  paraId: nullableString(node.attrs.paraId) ?? "00000001",
86
77
  resolved: node.attrs.resolved === true,
87
78
  extensionXml: nullableString(node.attrs.extensionXml),
@@ -11,16 +11,17 @@
11
11
  * have made the change. Asking them as one would refuse a moderator rewriting somebody else's
12
12
  * comment under `editableComments: "all"`, which is a thing the editor lets them do.
13
13
  */
14
- import type { EditableComments } from "../../schema/protection";
14
+ import { type EditableComments } from "../../schema/protection";
15
15
  import type { StoryPartKind } from "../protectionPolicy";
16
16
  import { type ImportedPeople } from "./people";
17
17
  /**
18
18
  * Whether this editor's writer could have put the entry out, whoever it belongs to.
19
19
  *
20
- * Shape alone: which attributes it carries and what stands inside it. Who may have written it is
21
- * `entryAllowed`'s question. Each kind is read where it is written (`./grammar`).
20
+ * Shape alone: which attributes it carries and what stands inside it, judged against the entry the
21
+ * original file held under the same id. Who may have written it is `entryAllowed`'s question.
22
+ * Each kind is read where it is written (`./grammar`).
22
23
  */
23
- export declare function wellFormedEntry(entry: Element): boolean;
24
+ export declare function wellFormedEntry(entry: Element, original: Element | null): boolean;
24
25
  /**
25
26
  * Whether the only thing between the two is that a thread gained its key.
26
27
  *
@@ -34,13 +35,18 @@ export declare function threadKeyAlone(entry: Element, original: Element): boole
34
35
  * Whether `authorId` could have written or rewritten this entry.
35
36
  *
36
37
  * Permission alone: whether the shape is one this editor writes is `wellFormedEntry`'s question.
37
- * An entry that appeared has to be this author's. One that arrived keeps the identity it arrived
38
- * with; settling its thread or replying to it belong to everyone and leave what it says alone,
39
- * while rewriting what it says is its author's, a moderator's, or anyone's where no identity was
40
- * recorded for it. That last is the rule the editor holds to (`schema/protection`), and the two
41
- * have to answer alike or a file the editor wrote would be turned down here.
38
+ * An entry that appeared has to be one this author could have written, which
39
+ * `commentAdditionAllowed` decides here as it does over the story. One that arrived keeps the
40
+ * identity it arrived with; settling its thread or replying to it belong to everyone and leave
41
+ * what it says alone, while rewriting what it says is its author's, a moderator's, or anyone's
42
+ * where no identity was recorded for it. Both are the rules the editor holds to
43
+ * (`schema/protection`), and the two have to answer alike or a file the editor wrote would be
44
+ * turned down here.
45
+ *
46
+ * `unattributed` is read from the file that arrived, since it says what the submission is judged
47
+ * against; `people` is the submission's, since it says who the submission names.
42
48
  */
43
- export declare function entryAllowed(entry: Element, original: Element | null, authorId: string, editableComments: EditableComments, people: ImportedPeople): boolean;
49
+ export declare function entryAllowed(entry: Element, original: Element | null, authorId: string, editableComments: EditableComments, people: ImportedPeople, unattributed: ReadonlySet<string>): boolean;
44
50
  export declare const commentsPart: StoryPartKind;
45
51
  export declare const commentsExtendedPart: StoryPartKind;
46
52
  export declare const peoplePart: StoryPartKind;
@@ -7,7 +7,16 @@ import {
7
7
  serializeXml,
8
8
  W_NS
9
9
  } from "../../ooxml/xml.js";
10
+ import { isPreservedNode } from "../../schema/preservedFragments.js";
11
+ import {
12
+ commentAdditionAllowed
13
+ } from "../../schema/protection.js";
14
+ import { NO_FORMATTING } from "../formatting.js";
15
+ import { NO_IMPORT_SOURCES } from "../importParagraph.js";
10
16
  import { availablePartPath } from "../packageParts.js";
17
+ import { serializeBlock } from "../serializeBlock.js";
18
+ import { buildBlock } from "../story.js";
19
+ import { isModelledBlock } from "../storyProjection.js";
11
20
  import {
12
21
  COMMENTS_CONTENT_TYPE,
13
22
  COMMENTS_EXTENDED_CONTENT_TYPE,
@@ -21,9 +30,11 @@ import {
21
30
  import {
22
31
  attributesWithin,
23
32
  COMMENT_ATTRIBUTES,
33
+ declarationsWritten,
24
34
  declarationWritten,
35
+ holdsElementsAndLayout,
36
+ isLayoutText,
25
37
  lastBodyParagraph,
26
- readStrictCommentBody,
27
38
  recordedIdentity,
28
39
  wellFormedCommentExtension,
29
40
  wellFormedPerson
@@ -42,9 +53,46 @@ function sameAttributes(entry, original, names) {
42
53
  (name) => attributeByLocalName(entry, name) === attributeByLocalName(original, name)
43
54
  );
44
55
  }
45
- function wellFormedEntry(entry) {
56
+ function holdsPreserved(node) {
57
+ if (isPreservedNode(node)) return true;
58
+ let found = false;
59
+ node.descendants((child) => {
60
+ if (isPreservedNode(child)) found = true;
61
+ return !found;
62
+ });
63
+ return found;
64
+ }
65
+ var BLOCK_DECLARATIONS = Object.entries(NAMESPACES).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
66
+ function normalizedBlock(xml) {
67
+ try {
68
+ const root = parseXml(
69
+ `<b ${BLOCK_DECLARATIONS}>${xml}</b>`
70
+ ).documentElement;
71
+ const [block] = Array.from(root.children);
72
+ return block === void 0 || root.children.length !== 1 ? null : serializeXml(block);
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+ function writtenBlock(el) {
78
+ if (!declarationsWritten(el)) return false;
79
+ const node = buildBlock(el, "", NO_IMPORT_SOURCES, NO_FORMATTING);
80
+ if (!isModelledBlock(node) || holdsPreserved(node)) return false;
81
+ const written = normalizedBlock(serializeBlock(node));
82
+ return written !== null && written === normalizedBlock(serializeXml(el));
83
+ }
84
+ function wellFormedStoryBody(entry, original) {
85
+ if (!holdsElementsAndLayout(entry)) return false;
86
+ const kept = new Set(
87
+ original === null ? [] : Array.from(original.children, serializeXml)
88
+ );
89
+ return Array.from(entry.children).every(
90
+ (block) => kept.has(serializeXml(block)) || writtenBlock(block)
91
+ );
92
+ }
93
+ function wellFormedEntry(entry, original) {
46
94
  if (entry.namespaceURI === W_NS && entry.localName === "comment") {
47
- return attributesWithin(entry, COMMENT_ATTRIBUTES) && readStrictCommentBody(entry) !== null;
95
+ return attributesWithin(entry, COMMENT_ATTRIBUTES) && wellFormedStoryBody(entry, original);
48
96
  }
49
97
  if (entry.namespaceURI === W15_NS && entry.localName === "commentEx") {
50
98
  return wellFormedCommentExtension(entry);
@@ -67,11 +115,17 @@ function threadKeyAlone(entry, original) {
67
115
  const kept = before === null || after === before;
68
116
  return kept && withoutThreadKey(entry) === withoutThreadKey(original);
69
117
  }
70
- function entryAllowed(entry, original, authorId, editableComments, people) {
118
+ function entryAllowed(entry, original, authorId, editableComments, people, unattributed) {
71
119
  if (entry.namespaceURI === W_NS && entry.localName === "comment") {
72
120
  const author = attributeByLocalName(entry, "author");
73
121
  const recorded = author === null ? null : commentAuthorId(people, author);
74
- if (original === null) return recorded === authorId;
122
+ if (original === null) {
123
+ return commentAdditionAllowed(
124
+ { author, authorId: recorded },
125
+ authorId,
126
+ unattributed
127
+ );
128
+ }
75
129
  const paraId = lastParagraphId(original);
76
130
  if (!sameAttributes(entry, original, COMMENT_IDENTITY) || paraId !== null && lastParagraphId(entry) !== paraId) {
77
131
  return false;
@@ -83,23 +137,20 @@ function entryAllowed(entry, original, authorId, editableComments, people) {
83
137
  return sameAttributes(entry, original, ["paraId", "paraIdParent"]);
84
138
  }
85
139
  if (entry.namespaceURI === W15_NS && entry.localName === "person") {
86
- return original === null && recordedIdentity(entry) === authorId;
140
+ const author = attributeByLocalName(entry, "author");
141
+ return original === null && recordedIdentity(entry) === authorId && author !== null && !unattributed.has(author);
87
142
  }
88
143
  return false;
89
144
  }
90
- var TEXT_NODE = 3;
91
145
  var COMPATIBILITY = /* @__PURE__ */ new Map([
92
146
  ["xmlns:w14", NAMESPACES.w14],
93
147
  ["xmlns:mc", NAMESPACES.mc]
94
148
  ]);
95
149
  var IGNORABLE = "mc:Ignorable";
96
150
  var IGNORED = "w14";
97
- function isLayout(node) {
98
- return node.nodeType === TEXT_NODE && /^[ \t\r\n]*$/.test(node.nodeValue ?? "");
99
- }
100
151
  function surroundingNodes(parent) {
101
152
  return parent === null ? [] : Array.from(parent.childNodes).filter(
102
- (node) => !isElement(node) && !isLayout(node)
153
+ (node) => !isElement(node) && !isLayoutText(node)
103
154
  );
104
155
  }
105
156
  function sameNodes(now, was) {
@@ -192,12 +243,13 @@ function storyPart(shape) {
192
243
  referents: shape.referents,
193
244
  wellFormed: wellFormedEntry,
194
245
  anyonesChange: threadKeyAlone,
195
- allowed: (entry, original, authorId, options, session) => entryAllowed(
246
+ allowed: (entry, original, authorId, options, session, unattributed) => entryAllowed(
196
247
  entry,
197
248
  original,
198
249
  authorId,
199
250
  options.editableComments,
200
- session.comments.people
251
+ session.comments.people,
252
+ unattributed
201
253
  )
202
254
  };
203
255
  }
@@ -8,6 +8,8 @@
8
8
  * part by, so a name stands for one identity per file: a name the part already records is read as
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
+ * A name the file's comments already write under carries the same weight, recorded or not: a
12
+ * person appended for it would claim those comments too.
11
13
  */
12
14
  import type { PartPlanContext } from "../partPlan";
13
15
  import type { StoryPartKind } from "../protectionPolicy";
@@ -33,10 +35,16 @@ export declare function readPeople(parts: Map<string, Uint8Array>, mainPartPath:
33
35
  */
34
36
  export declare function commentAuthorId(people: ImportedPeople, author: string): string | null;
35
37
  /**
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
+ * The authors to record a person for, each under the name they write as: the ones the comments
39
+ * name one identity for and the part does not record already. A name the part records is left as
40
+ * it stands, whichever provider recorded it and whatever it resolves to. So is a name the comments
41
+ * carry under more than one identity, one identity and none among them: recording it would hand
42
+ * the comments carrying no identity to whoever the name then resolved to, and a document saved by
43
+ * a Word that wrote no people part carries every one of its comments that way.
44
+ * `unattributed` also reserves names from the original Comments part, including preserved entries
45
+ * the current editable bodies do not carry. The planner and export invariants use this same set.
38
46
  */
39
- export declare function unrecordedAuthors(bodies: Iterable<CommentReferenceData | CommentReplyData>, people: ImportedPeople): Map<string, string>;
47
+ export declare function unrecordedAuthors(bodies: Iterable<CommentReferenceData | CommentReplyData>, people: ImportedPeople, unattributed: ReadonlySet<string>): Map<string, string>;
40
48
  /**
41
49
  * Plans the people part, its relationship and its content type for every identity the current
42
50
  * comments carry that the document has not recorded. Null when it has recorded them all, which
@@ -10,6 +10,7 @@ import {
10
10
  encodeUtf8,
11
11
  parseXml
12
12
  } from "../../ooxml/xml.js";
13
+ import { unattributedCommentAuthors } from "../../schema/protection.js";
13
14
  import { relatedPartPath } from "../packageParts.js";
14
15
  import { directoryOf } from "../relationships.js";
15
16
  import { COMMENT_AUTHOR_PROVIDER, PEOPLE_REL_TYPE, W15_NS } from "./constants.js";
@@ -51,12 +52,21 @@ function readPeople(parts, mainPartPath) {
51
52
  function commentAuthorId(people, author) {
52
53
  return people.byAuthor.get(author) ?? null;
53
54
  }
54
- function unrecordedAuthors(bodies, people) {
55
- const unrecorded = /* @__PURE__ */ new Map();
55
+ function unrecordedAuthors(bodies, people, unattributed) {
56
+ const identities = /* @__PURE__ */ new Map();
56
57
  for (const body of bodies) {
57
- if (body.author === null || body.authorId === null) continue;
58
- if (people.byAuthor.has(body.author)) continue;
59
- unrecorded.set(body.author, body.authorId);
58
+ if (body.author === null) continue;
59
+ if (people.byAuthor.has(body.author) || unattributed.has(body.author))
60
+ continue;
61
+ const ids = identities.get(body.author) ?? /* @__PURE__ */ new Set();
62
+ ids.add(body.authorId);
63
+ identities.set(body.author, ids);
64
+ }
65
+ const unrecorded = /* @__PURE__ */ new Map();
66
+ for (const [author, ids] of identities) {
67
+ if (ids.size !== 1) continue;
68
+ const only = Array.from(ids)[0] ?? null;
69
+ if (only !== null) unrecorded.set(author, only);
60
70
  }
61
71
  return unrecorded;
62
72
  }
@@ -94,7 +104,11 @@ function peopleXml(people, added) {
94
104
  }
95
105
  function planPeoplePart(part, bodies, session, context) {
96
106
  const people = session.comments.people;
97
- const added = unrecordedAuthors(bodies, people);
107
+ const added = unrecordedAuthors(
108
+ bodies,
109
+ people,
110
+ unattributedCommentAuthors(session.comments.ordered)
111
+ );
98
112
  if (added.size === 0) return null;
99
113
  const parts = /* @__PURE__ */ new Map();
100
114
  const addingPart = part.pathIn(session) === null;
@@ -3,6 +3,7 @@ import {
3
3
  commentAdditionsBy,
4
4
  commentEditsOwned,
5
5
  commentIdentitiesKept,
6
+ unattributedCommentAuthors,
6
7
  withoutComments
7
8
  } from "../../schema/protection.js";
8
9
  import {
@@ -18,7 +19,12 @@ function sameBody(before, after) {
18
19
  }
19
20
  function commentsStoryKept(before, after, authorId, { editableComments }) {
20
21
  if (!sameBody(before, after)) return refused("body-changed");
21
- if (!commentIdentitiesKept(before.doc, after.doc) || !commentAdditionsBy(before.doc, after.doc, authorId)) {
22
+ if (!commentIdentitiesKept(before.doc, after.doc) || !commentAdditionsBy(
23
+ before.doc,
24
+ after.doc,
25
+ authorId,
26
+ unattributedCommentAuthors(before.session.comments.ordered)
27
+ )) {
22
28
  return refused("comment-author-forged");
23
29
  }
24
30
  if (!commentEditsOwned(before.doc, after.doc, {
@@ -2,6 +2,10 @@
2
2
  * Reads comment and comment-extension package parts.
3
3
  */
4
4
  import { type ImportedPeople } from "./people";
5
+ /**
6
+ * One entry of the Comments part as it arrived, everything about it but what it says: that is a
7
+ * story of its own, sliced and read the way a body block is (`docx/story`).
8
+ */
5
9
  export interface ImportedComment {
6
10
  id: string;
7
11
  author: string | null;
@@ -13,8 +17,6 @@ export interface ImportedComment {
13
17
  authorId: string | null;
14
18
  initials: string | null;
15
19
  date: string | null;
16
- text: string;
17
- xml: string;
18
20
  paraId: string | null;
19
21
  parentParaId: string | null;
20
22
  resolved: boolean;
@@ -4,8 +4,7 @@ import {
4
4
  decodeUtf8,
5
5
  elementChildren,
6
6
  parseXml,
7
- serializeXml,
8
- W_NS
7
+ serializeXml
9
8
  } from "../../ooxml/xml.js";
10
9
  import { relatedPartPath } from "../packageParts.js";
11
10
  import { COMMENTS_EXTENDED_REL_TYPE, COMMENTS_REL_TYPE } from "./constants.js";
@@ -15,16 +14,6 @@ import {
15
14
  NO_PEOPLE,
16
15
  readPeople
17
16
  } from "./people.js";
18
- function inlineCommentText(node) {
19
- if (node.localName === "t") return node.textContent ?? "";
20
- if (node.localName === "tab") return " ";
21
- if (node.localName === "br" || node.localName === "cr") return "\n";
22
- return elementChildren(node).map(inlineCommentText).join("");
23
- }
24
- function commentText(el) {
25
- const paragraphs = Array.from(el.getElementsByTagNameNS(W_NS, "p"));
26
- return paragraphs.map(inlineCommentText).join("\n");
27
- }
28
17
  var NO_COMMENTS = {
29
18
  partPath: null,
30
19
  xml: null,
@@ -117,8 +106,6 @@ function readComments(parts, mainPartPath) {
117
106
  authorId: author === null ? null : commentAuthorId(people, author),
118
107
  initials: attributeByLocalName(el, "initials"),
119
108
  date: attributeByLocalName(el, "date"),
120
- text: commentText(el),
121
- xml: serializeXml(el),
122
109
  paraId,
123
110
  parentParaId: extension?.parentParaId ?? null,
124
111
  resolved: extension?.resolved ?? false,