@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,5 +1,5 @@
1
1
  // src/docx/comments/writing.ts
2
- import { elementXml, xmlnsAttr } from "../../ooxml/element.js";
2
+ import { attrsText } from "../../ooxml/element.js";
3
3
  import { NAMESPACES, wName, xmlnsDecl } from "../../ooxml/names.js";
4
4
  import {
5
5
  ensureRootDeclarations,
@@ -7,10 +7,18 @@ import {
7
7
  splicePart
8
8
  } from "../../ooxml/partSplice.js";
9
9
  import { encodeUtf8 } from "../../ooxml/xml.js";
10
+ import { sameSource } from "../../schema/sourceEquality.js";
11
+ import { NO_EXPORT_REFS } from "../exportRefs.js";
10
12
  import { directoryOf } from "../relationships.js";
13
+ import { serializeStory } from "../serializeStory.js";
14
+ import {
15
+ storyFromText,
16
+ storyKey,
17
+ storyOf,
18
+ withThreadKeyOn
19
+ } from "../story.js";
11
20
  import {
12
21
  arrivedEntries,
13
- renderCommentBody,
14
22
  renderCommentExtension,
15
23
  withThreadKey
16
24
  } from "./grammar.js";
@@ -20,7 +28,26 @@ import {
20
28
  import { commentsExtendedPart, commentsPart, peoplePart } from "./parts.js";
21
29
  import { planPeoplePart } from "./people.js";
22
30
  function needsThreadKey(id, comment, session) {
23
- return comment.imported && carriesThreadMetadata(comment) && (session.comments.byId.get(id)?.paraId ?? null) === null;
31
+ return session.comments.byId.has(id) && carriesThreadMetadata(comment) && (session.comments.byId.get(id)?.paraId ?? null) === null;
32
+ }
33
+ function bodyStory(doc, id) {
34
+ return storyOf(doc, storyKey("comment", id));
35
+ }
36
+ function importedBody(session, id) {
37
+ return session.stories.get(storyKey("comment", id)) ?? null;
38
+ }
39
+ function importedEntry(comment, session) {
40
+ const arrived = session.comments.byId.get(comment.id);
41
+ if (arrived === void 0 || arrived.author !== comment.author || arrived.date !== comment.date || arrived.initials !== comment.initials) {
42
+ return null;
43
+ }
44
+ return importedBody(session, comment.id);
45
+ }
46
+ function bodyKept(doc, comment, session) {
47
+ const imported = importedEntry(comment, session);
48
+ if (imported === null) return null;
49
+ const current = bodyStory(doc, comment.id);
50
+ return current === null || sameSource(current, imported.doc) ? imported : null;
24
51
  }
25
52
  function commentsChanged(doc, session) {
26
53
  const current = commentReferencesIn(doc);
@@ -34,12 +61,8 @@ function commentsChanged(doc, session) {
34
61
  for (const id of originalBodies) {
35
62
  if (!currentBodies.has(id)) return true;
36
63
  }
37
- for (const id of session.commentReferenceIds) {
38
- const comment = current.get(id);
39
- if (!comment?.imported) return true;
40
- }
41
- for (const comment of current.values()) {
42
- if (comment.replies.some((reply) => !reply.imported)) return true;
64
+ for (const comment of currentBodies.values()) {
65
+ if (bodyKept(doc, comment, session) === null) return true;
43
66
  }
44
67
  for (const [id, comment] of current) {
45
68
  if (needsThreadKey(id, comment, session)) return true;
@@ -74,10 +97,13 @@ function extensionsXml(references, comments, originallyReferenced) {
74
97
  if (comments.extendedXml === null) {
75
98
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx ${xmlnsDecl("w15")}>${pieces.join("")}</w15:commentsEx>`;
76
99
  }
77
- return splicePart(comments.extendedXml, {
78
- root: EXTENSIONS_ROOT,
79
- replaceChildren: pieces.join("")
80
- });
100
+ return ensureRootDeclarations(
101
+ splicePart(comments.extendedXml, {
102
+ root: EXTENSIONS_ROOT,
103
+ replaceChildren: pieces.join("")
104
+ }),
105
+ { namespaces: { w15: NAMESPACES.w15 } }
106
+ );
81
107
  }
82
108
  var COMMENTS_ROOT = "comments";
83
109
  var EXTENSIONS_ROOT = "commentsEx";
@@ -108,31 +134,38 @@ function carriesThreadMetadata(comment) {
108
134
  function keyedEntry(comment, arrivedKeyed) {
109
135
  return carriesThreadMetadata(comment) || arrivedKeyed.has(comment.id);
110
136
  }
111
- function renderedComment(comment, arrivedKeyed, arrived) {
112
- if (comment.imported && comment.commentXml !== null) {
113
- return carriesThreadMetadata(comment) ? withThreadKey(
114
- comment.commentXml,
115
- comment.paraId,
116
- arrived.get(comment.id) ?? null
117
- ) : comment.commentXml;
118
- }
137
+ function writtenContainer(comment) {
119
138
  const attrs = [
120
- xmlnsAttr("w"),
121
139
  [wName("id"), comment.id],
122
140
  comment.author === null ? null : [wName("author"), comment.author],
123
141
  comment.date === null ? null : [wName("date"), comment.date],
124
142
  comment.initials === null ? null : [wName("initials"), comment.initials]
125
143
  ];
126
- const paraId = keyedEntry(comment, arrivedKeyed) ? comment.paraId : null;
127
- const body = renderCommentBody(comment.text, paraId);
128
- return elementXml(
129
- wName("comment"),
130
- attrs.filter((attr) => attr !== null),
131
- [body]
144
+ const text = attrsText(
145
+ attrs.filter((attr) => attr !== null)
132
146
  );
147
+ return {
148
+ open: text === "" ? `<${wName("comment")}>` : `<${wName("comment")} ${text}>`,
149
+ close: `</${wName("comment")}>`
150
+ };
151
+ }
152
+ function renderedComment(comment, arrivedKeyed, arrived, doc, session) {
153
+ const kept = bodyKept(doc, comment, session);
154
+ if (kept !== null) {
155
+ return carriesThreadMetadata(comment) ? withThreadKey(kept.xml, comment.paraId, arrived.get(comment.id) ?? null) : kept.xml;
156
+ }
157
+ const paraId = keyedEntry(comment, arrivedKeyed) ? comment.paraId : null;
158
+ const entry = importedEntry(comment, session);
159
+ const said = bodyStory(doc, comment.id) ?? storyFromText("");
160
+ const body = paraId === null ? said : withThreadKeyOn(said, paraId);
161
+ return serializeStory(body, null, entry ?? writtenContainer(comment), {
162
+ ...NO_EXPORT_REFS,
163
+ session
164
+ });
133
165
  }
166
+ var COMMENT_MARKUP = { namespaces: { w: NAMESPACES.w } };
134
167
  var THREAD_MARKUP = {
135
- namespaces: { w14: NAMESPACES.w14, mc: NAMESPACES.mc },
168
+ namespaces: { w: NAMESPACES.w, w14: NAMESPACES.w14, mc: NAMESPACES.mc },
136
169
  ignorable: ["w14"]
137
170
  };
138
171
  function currentCommentBodies(references) {
@@ -157,7 +190,8 @@ function originalThreadIds(comments, rootIds) {
157
190
  }
158
191
  return ids;
159
192
  }
160
- function commentsXml(references, comments, originallyReferenced) {
193
+ function commentsXml(doc, session, references, originallyReferenced) {
194
+ const comments = session.comments;
161
195
  const currentBodies = currentCommentBodies(references);
162
196
  const arrivedKeyed = new Set(
163
197
  Array.from(comments.byId.values()).flatMap(
@@ -172,20 +206,26 @@ function commentsXml(references, comments, originallyReferenced) {
172
206
  const pieces = [];
173
207
  const written = /* @__PURE__ */ new Set();
174
208
  for (const original of comments.ordered) {
209
+ if (written.has(original.id)) continue;
175
210
  const current = currentBodies.get(original.id);
176
211
  if (current) {
177
- pieces.push(renderedComment(current, arrivedKeyed, arrived));
212
+ pieces.push(
213
+ renderedComment(current, arrivedKeyed, arrived, doc, session)
214
+ );
178
215
  written.add(original.id);
179
216
  continue;
180
217
  }
181
218
  if (!originalThreads.has(original.id)) {
182
- pieces.push(original.xml);
219
+ const story = importedBody(session, original.id);
220
+ if (story !== null) pieces.push(story.xml);
183
221
  written.add(original.id);
184
222
  }
185
223
  }
186
224
  for (const [id, comment] of currentBodies) {
187
225
  if (!written.has(id)) {
188
- pieces.push(renderedComment(comment, arrivedKeyed, arrived));
226
+ pieces.push(
227
+ renderedComment(comment, arrivedKeyed, arrived, doc, session)
228
+ );
189
229
  }
190
230
  }
191
231
  if (comments.xml === null) {
@@ -196,7 +236,10 @@ function commentsXml(references, comments, originallyReferenced) {
196
236
  root: COMMENTS_ROOT,
197
237
  replaceChildren: pieces.join("")
198
238
  });
199
- return hasThreadMetadata ? ensureRootDeclarations(rewritten, THREAD_MARKUP) : rewritten;
239
+ return ensureRootDeclarations(
240
+ rewritten,
241
+ hasThreadMetadata ? THREAD_MARKUP : COMMENT_MARKUP
242
+ );
200
243
  }
201
244
  function planCommentParts(doc, session, context) {
202
245
  const bodyChanged = commentsChanged(doc, session);
@@ -215,7 +258,7 @@ function planCommentParts(doc, session, context) {
215
258
  parts.set(
216
259
  partPath,
217
260
  encodeUtf8(
218
- commentsXml(references, session.comments, session.commentReferenceIds),
261
+ commentsXml(doc, session, references, session.commentReferenceIds),
219
262
  session.comments.hadBom
220
263
  )
221
264
  );
@@ -1,17 +1,21 @@
1
1
  // src/docx/exportDocx.ts
2
2
  import { DocxExportError } from "../ooxml/errors.js";
3
+ import {
4
+ ensureRootDeclarations
5
+ } from "../ooxml/partSplice.js";
3
6
  import {
4
7
  encodeUtf8,
5
8
  parseXml,
9
+ R_NS,
6
10
  W_NS,
7
11
  withXmlParser
8
12
  } from "../ooxml/xml.js";
9
- import { sameSource } from "../schema/sourceEquality.js";
10
13
  import { commentsPlanner } from "./comments.js";
11
14
  import { repackParts } from "./container.js";
12
15
  import {
13
16
  fidelityNotesOf
14
17
  } from "./fidelity.js";
18
+ import { headerFooterPlanner } from "./headersFooters.js";
15
19
  import { hyperlinkRefs } from "./hyperlink.js";
16
20
  import { withUniqueIdentities } from "./identities.js";
17
21
  import { problemsOf } from "./invariants.js";
@@ -27,28 +31,22 @@ import {
27
31
  relationshipWriter,
28
32
  relsPathOf
29
33
  } from "./relationships.js";
30
- import { serializeBlock } from "./serializeBlock.js";
31
- import {
32
- originalBlock,
33
- sessionOf
34
- } from "./session.js";
35
- function blockXml(node, session, refs) {
36
- const imported = originalBlock(node, session);
37
- if (imported && sameSource(node, imported.node)) return imported.xml;
38
- return serializeBlock(node, session, refs);
39
- }
34
+ import { blockXml } from "./serializeBlock.js";
35
+ import { sessionOf } from "./session.js";
40
36
  function buildDocumentXml(doc, session, refs) {
41
37
  const pieces = [session.documentPrefix];
42
38
  withUniqueIdentities(doc).forEach((child) => {
43
- pieces.push(blockXml(child, session, refs));
39
+ pieces.push(blockXml(child, refs, doc.childCount === 1));
44
40
  });
41
+ const sectPr = doc.attrs.sectPr;
42
+ if (typeof sectPr === "string") pieces.push(sectPr);
45
43
  pieces.push(session.documentSuffix);
46
44
  return pieces.join("");
47
45
  }
48
46
  function localAttribute(el, name) {
49
47
  return Array.from(el.attributes).find((attribute) => attribute.localName === name)?.value ?? null;
50
48
  }
51
- function assertBookmarkPairs(documentXml) {
49
+ function assertMainPart(documentXml, wroteLinks) {
52
50
  let root;
53
51
  try {
54
52
  root = parseXml(documentXml).documentElement;
@@ -63,6 +61,15 @@ function assertBookmarkPairs(documentXml) {
63
61
  const used = /* @__PURE__ */ new Set();
64
62
  for (const element of root.getElementsByTagName("*")) {
65
63
  if (element.namespaceURI !== W_NS) continue;
64
+ if (wroteLinks && element.localName === "hyperlink") {
65
+ const id2 = element.getAttributeNode("r:id");
66
+ if (id2 && id2.namespaceURI !== R_NS) {
67
+ throw new DocxExportError(
68
+ "unsupported-content",
69
+ "a hyperlink's r:id is shadowed by a different relationship namespace"
70
+ );
71
+ }
72
+ }
66
73
  if (element.localName !== "bookmarkStart" && element.localName !== "bookmarkEnd") {
67
74
  continue;
68
75
  }
@@ -97,9 +104,11 @@ function assertBookmarkPairs(documentXml) {
97
104
  );
98
105
  }
99
106
  }
107
+ var LINK_MARKUP = { namespaces: { r: R_NS } };
100
108
  var PART_PLANNERS = [
101
109
  numberingPlanner,
102
- commentsPlanner
110
+ commentsPlanner,
111
+ headerFooterPlanner
103
112
  ];
104
113
  function exportDocx(doc, session, options) {
105
114
  return exportDocxReport(doc, session, options).bytes;
@@ -135,12 +144,15 @@ function writeDocx(doc, store, notes, planners) {
135
144
  contentTypes: contentTypeWriter(store.parts)
136
145
  };
137
146
  const media = planImageMedia(doc, store, context);
138
- const documentXml = buildDocumentXml(doc, store, {
147
+ const links = hyperlinkRefs(context.relationships);
148
+ const body = buildDocumentXml(doc, store, {
139
149
  images: media?.refs ?? NO_IMAGE_REFS,
140
- links: hyperlinkRefs(context.relationships),
141
- notes
150
+ links,
151
+ notes,
152
+ session: store
142
153
  });
143
- assertBookmarkPairs(documentXml);
154
+ const documentXml = links.addedRelId() ? ensureRootDeclarations(body, LINK_MARKUP) : body;
155
+ assertMainPart(documentXml, links.addedRelId());
144
156
  const parts = runPartPlanners(planners, doc, store, context, media?.parts);
145
157
  const rels = context.relationships.part(store.parts.get(relsPath));
146
158
  if (rels) parts.set(relsPath, rels);
@@ -1,19 +1,24 @@
1
1
  /**
2
2
  * What the serializers need from the rest of the package while a body is being written: which
3
3
  * relationship an inserted image ended up on (`docx/media`), which one each link's address lives
4
- * on (`docx/hyperlink`), and where to record an approximation a writer had to make
5
- * (`docx/fidelity`).
4
+ * on (`docx/hyperlink`), where to record an approximation a writer had to make (`docx/fidelity`),
5
+ * and the fragments the document was opened from (`docx/session`).
6
6
  *
7
7
  * They travel as one value so that a serializer taking a body apart passes on everything the
8
- * pieces of it may need, whatever a later kind of content turns out to require.
8
+ * pieces of it may need, whatever a later kind of content turns out to require. The session is
9
+ * among them because a preserved block names the fragment it stands for wherever it ends up: an
10
+ * edit may move one into a table cell, and the cell writer has no other way to reach the original.
9
11
  */
10
12
  import { type FidelityCollector } from "./fidelity";
11
13
  import { type LinkRefs } from "./hyperlink";
12
14
  import { type ImageRefs } from "./media";
15
+ import type { SessionStore } from "./session";
13
16
  export interface ExportRefs {
14
17
  images: ImageRefs;
15
18
  links: LinkRefs;
16
19
  notes: FidelityCollector;
20
+ /** null for a serializer called on a document nobody opened */
21
+ session: SessionStore | null;
17
22
  }
18
23
  /** What a serializer called on its own, outside an export, has to work with */
19
24
  export declare const NO_EXPORT_REFS: ExportRefs;
@@ -5,7 +5,8 @@ import { NO_IMAGE_REFS } from "./media.js";
5
5
  var NO_EXPORT_REFS = {
6
6
  images: NO_IMAGE_REFS,
7
7
  links: NO_LINK_REFS,
8
- notes: NO_FIDELITY_COLLECTOR
8
+ notes: NO_FIDELITY_COLLECTOR,
9
+ session: null
9
10
  };
10
11
  export {
11
12
  NO_EXPORT_REFS
@@ -13,31 +13,45 @@ function elementOf(node) {
13
13
  return stringAttr(node.attrs.name) ?? elementNameOf(stringAttr(node.attrs.xml) ?? "");
14
14
  }
15
15
  function demotedBlock(element) {
16
- const name = element === null ? null : localPart(element);
16
+ const name = localNameOf(element);
17
17
  if (name === "tbl") return { severity: "placeholder", code: "table-demoted" };
18
18
  if (name === "p") {
19
19
  return { severity: "placeholder", code: "paragraph-demoted" };
20
20
  }
21
21
  return { severity: "placeholder", code: "preserved-block" };
22
22
  }
23
+ function severityOf(display) {
24
+ return display === "chip" ? "placeholder" : "hidden";
25
+ }
23
26
  function preservedBy(node, element) {
24
27
  switch (node.type.name) {
25
- case "docxRaw":
26
- return demotedBlock(element);
27
28
  case "rawBlock":
28
- return { severity: "placeholder", code: "preserved-block" };
29
- case "bookmarkBlock":
30
- return { severity: "hidden", code: "range-marker" };
29
+ return preservedBlockNote(node, element);
30
+ case "rawRunContent":
31
+ return {
32
+ severity: severityOf(node.attrs.display),
33
+ code: "preserved-run-content"
34
+ };
31
35
  case "rawInline":
32
- return preservedInline(element);
36
+ return preservedInline(node, element);
33
37
  default:
34
38
  return null;
35
39
  }
36
40
  }
37
- function preservedInline(element) {
38
- const name = element === null ? null : localPart(element);
41
+ function preservedBlockNote(node, element) {
42
+ if (severityOf(node.attrs.display) === "placeholder") {
43
+ return demotedBlock(element);
44
+ }
45
+ return node.attrs.guarded === true ? { severity: "hidden", code: "range-marker" } : { severity: "hidden", code: "preserved-block" };
46
+ }
47
+ function preservedInline(node, element) {
48
+ const name = stringAttr(node.attrs.element) ?? localNameOf(element);
39
49
  if (name === "r") return null;
40
- return name === "bookmarkStart" || name === "bookmarkEnd" ? { severity: "hidden", code: "range-marker" } : { severity: "hidden", code: "preserved-inline" };
50
+ const severity = severityOf(node.attrs.display);
51
+ return severity === "hidden" && node.attrs.guarded === true ? { severity, code: "range-marker" } : { severity, code: "preserved-inline" };
52
+ }
53
+ function localNameOf(element) {
54
+ return element === null ? null : localPart(element);
41
55
  }
42
56
  function blockNumberOf(node) {
43
57
  const srcId = stringAttr(node.attrs.srcId);
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Where the fields of a paragraph stand, read back off the fragments the editor keeps as they came.
3
+ *
4
+ * A complex field is not one element. It is a begin character, the instruction text, an optional
5
+ * separate character, the result its producer cached, and an end character, all standing loose
6
+ * among the runs of the paragraph in that order (§17.16.18). Nothing in the model pairs them up, so
7
+ * anything that wants to know what a field was told to print has to read the characters back. A
8
+ * simple field says the same in one element and carries its instruction as an attribute (§17.16.19).
9
+ *
10
+ * What a field means, and how to work it out again, belongs to the fields feature. This answers the
11
+ * one question a projection has: where each field stands and what its instruction says.
12
+ */
13
+ import type { Node as PMNode } from "prosemirror-model";
14
+ export interface FieldSpan {
15
+ /** The instruction text between begin and separate (or end), whitespace-trimmed */
16
+ instr: string;
17
+ /** Where the begin character stands, and the simple field itself for one written as a single element */
18
+ begin: number;
19
+ /** Where the separate character stands, and null for a field that cached no result */
20
+ separate: number | null;
21
+ /** Where the end character stands, which is `begin` again for a simple field */
22
+ end: number;
23
+ /** The half-open range of positions the cached result covers, and null for a field carrying none */
24
+ result: [number, number] | null;
25
+ }
26
+ /**
27
+ * Whether the node is one of the pieces a complex field is written out of rather than something
28
+ * the field prints. A projection drops these whether or not they pair up into a field.
29
+ */
30
+ export declare function isFieldCharacter(node: PMNode): boolean;
31
+ /**
32
+ * The fields this paragraph holds, in the order they begin, each position offset by `offset`.
33
+ *
34
+ * Positions are counted inside the paragraph's content, so `offset` is where that content starts:
35
+ * a caller reading a paragraph standing on its own passes 0, and one reading a paragraph of a
36
+ * document passes the position just inside it.
37
+ *
38
+ * A begin character with no end is no field: the file says a field starts there and never says
39
+ * what it prints, so there is nothing to pair it with and it is passed over. `documentFidelity`
40
+ * still reports the characters themselves as content kept rather than modelled.
41
+ */
42
+ export declare function fieldSpans(paragraph: PMNode, offset: number): FieldSpan[];
@@ -0,0 +1,76 @@
1
+ // src/docx/fields.ts
2
+ import { parsePropsXml } from "../ooxml/props.js";
3
+ import { attributeByLocalName } from "../ooxml/xml.js";
4
+ var FIELD_CHARACTER = "fldChar";
5
+ var INSTRUCTION = "instrText";
6
+ var SIMPLE_FIELD = "fldSimple";
7
+ function attributeOf(node, name) {
8
+ const xml = node.attrs.xml;
9
+ if (typeof xml !== "string") return null;
10
+ const el = parsePropsXml(xml);
11
+ return el === null ? null : attributeByLocalName(el, name);
12
+ }
13
+ function chipText(node) {
14
+ const text = node.attrs.text;
15
+ return typeof text === "string" ? text : "";
16
+ }
17
+ var DELETED_INSTRUCTION = "delInstrText";
18
+ var FIELD_PLUMBING = /* @__PURE__ */ new Set([
19
+ FIELD_CHARACTER,
20
+ INSTRUCTION,
21
+ DELETED_INSTRUCTION
22
+ ]);
23
+ function isFieldCharacter(node) {
24
+ const element = node.attrs.element;
25
+ return node.type.name === "rawRunContent" && typeof element === "string" && FIELD_PLUMBING.has(element);
26
+ }
27
+ function fieldSpans(paragraph, offset) {
28
+ const spans = [];
29
+ const open = [];
30
+ paragraph.forEach((child, at) => {
31
+ const pos = offset + at;
32
+ const element = child.attrs.element;
33
+ if (child.type.name === "rawInline" && element === SIMPLE_FIELD) {
34
+ spans.push({
35
+ instr: (attributeOf(child, "instr") ?? "").trim(),
36
+ begin: pos,
37
+ separate: null,
38
+ end: pos,
39
+ result: null
40
+ });
41
+ return;
42
+ }
43
+ if (child.type.name !== "rawRunContent") return;
44
+ if (element === INSTRUCTION) {
45
+ const active2 = open.at(-1);
46
+ if (active2 && active2.separate === null) active2.instr += chipText(child);
47
+ return;
48
+ }
49
+ if (element !== FIELD_CHARACTER) return;
50
+ const kind = attributeOf(child, "fldCharType");
51
+ if (kind === "begin") {
52
+ open.push({ instr: "", begin: pos, separate: null });
53
+ return;
54
+ }
55
+ const active = open.at(-1);
56
+ if (active === void 0) return;
57
+ if (kind === "separate") {
58
+ if (active.separate === null) active.separate = pos;
59
+ return;
60
+ }
61
+ if (kind !== "end") return;
62
+ open.pop();
63
+ spans.push({
64
+ instr: active.instr.trim(),
65
+ begin: active.begin,
66
+ separate: active.separate,
67
+ end: pos,
68
+ result: active.separate === null ? null : [active.separate + 1, pos]
69
+ });
70
+ });
71
+ return spans.sort((a, b) => a.begin - b.begin);
72
+ }
73
+ export {
74
+ fieldSpans,
75
+ isFieldCharacter
76
+ };
@@ -4,8 +4,8 @@
4
4
  * and the clipboard reader all take them from here, so a paragraph is drawn the same whichever of
5
5
  * them last touched it.
6
6
  */
7
- import type { Mark } from "prosemirror-model";
8
- import type { ParagraphFormat, RunFormat } from "../../model/format";
7
+ import { type Mark, type Node as PMNode } from "prosemirror-model";
8
+ import { type ParagraphFormat, type RunFormat } from "../../model/format";
9
9
  import type { FormattingContext } from "./context";
10
10
  import { type ParagraphPlacement, type ResolvedParagraph } from "./resolve";
11
11
  /** The two derived paragraph attrs (`format` and `styleRun` in `schema`) */
@@ -25,3 +25,21 @@ export declare function paragraphAttrsFor(pPr: string | null, context: Formattin
25
25
  * An inline that is not text and carries no mark is left as it is.
26
26
  */
27
27
  export declare function runMarkUnder(mark: Mark | null, isText: boolean, paragraph: ResolvedParagraph, context: FormattingContext): Mark | null;
28
+ /**
29
+ * The paragraph with the values the hierarchy gives it, and the text inside it wearing the run
30
+ * mark that goes with them.
31
+ *
32
+ * The style's run values are also laid on the paragraph itself, so that text carrying no run of
33
+ * its own - typed in the editor - is drawn in them (`styleRun` in `schema`).
34
+ * Where the paragraph stands in a table cell, `placement` is the part of the table it belongs to,
35
+ * which the table style dresses on top of everything else it lays down.
36
+ */
37
+ export declare function styledParagraph(node: PMNode, context: FormattingContext, placement?: ParagraphPlacement | null): PMNode;
38
+ /** Table display inputs, refreshed from the same style context as the paragraphs inside it. */
39
+ export declare function tableStyleAttrs(tblPr: Element | null, context: FormattingContext): {
40
+ format: import("../..").TableFormat | null;
41
+ styleInside: import("../tableFormatting").InsideBorders | null;
42
+ styleCellMargins: import("../tableFormatting").CellMargins | null;
43
+ styleConditions: Readonly<Partial<Record<"band1Horz" | "band1Vert" | "band2Horz" | "band2Vert" | "firstCol" | "firstRow" | "lastCol" | "lastRow" | "neCell" | "nwCell" | "seCell" | "swCell" | "wholeTable", import("../tableFormatting").CellStyleFormat>>> | null;
44
+ styleBands: import("../tableFormatting").BandSizes | null;
45
+ };
@@ -1,9 +1,26 @@
1
1
  // src/docx/formatting/attrs.ts
2
+ import { Fragment } from "prosemirror-model";
3
+ import {
4
+ toBandSizes,
5
+ toCellMargins,
6
+ toInsideBorders,
7
+ toTableStyleConditions
8
+ } from "../../model/format.js";
2
9
  import { docxSchema } from "../../schema/index.js";
10
+ import {
11
+ cellConditionsOf,
12
+ layerBandSizes,
13
+ NO_BAND_SIZES,
14
+ readBandSizes,
15
+ readTableFormat,
16
+ tblStyleIdOf
17
+ } from "../tableFormatting.js";
3
18
  import {
4
19
  resolveParagraph,
5
- resolveRun
20
+ resolveRun,
21
+ tableStyleFor
6
22
  } from "./resolve.js";
23
+ import { layerTableFormat } from "./styles.js";
7
24
  function paragraphAttrsOf(paragraph) {
8
25
  return { format: paragraph.format, styleRun: paragraph.styleRun };
9
26
  }
@@ -24,8 +41,46 @@ function runMarkUnder(mark, isText, paragraph, context) {
24
41
  const format = resolveRun(null, paragraph, context);
25
42
  return format === null ? null : docxSchema.marks.run.create({ rPr: null, rAttrs: null, format });
26
43
  }
44
+ function styledParagraph(node, context, placement = null) {
45
+ const pPr = node.attrs.pPr;
46
+ const paragraph = resolveParagraph(
47
+ typeof pPr === "string" ? pPr : null,
48
+ context,
49
+ placement
50
+ );
51
+ const inline = node.children.map((child) => {
52
+ const mark = runMarkUnder(
53
+ child.marks.find((entry) => entry.type === docxSchema.marks.run) ?? null,
54
+ child.isText,
55
+ paragraph,
56
+ context
57
+ );
58
+ return mark ? child.mark(mark.addToSet(child.marks)) : child;
59
+ });
60
+ return node.type.create(
61
+ { ...node.attrs, ...paragraphAttrsOf(paragraph) },
62
+ Fragment.fromArray(inline),
63
+ node.marks
64
+ );
65
+ }
66
+ function tableStyleAttrs(tblPr, context) {
67
+ const style = tableStyleFor(tblStyleIdOf(tblPr), context);
68
+ return {
69
+ format: layerTableFormat(style?.table ?? {}, readTableFormat(tblPr)),
70
+ styleInside: toInsideBorders(style?.tableInside),
71
+ styleCellMargins: toCellMargins(style?.tableCellMargins),
72
+ styleConditions: toTableStyleConditions(
73
+ cellConditionsOf(style?.tableConditions ?? {})
74
+ ),
75
+ styleBands: toBandSizes(
76
+ layerBandSizes(style?.tableBands ?? NO_BAND_SIZES, readBandSizes(tblPr))
77
+ )
78
+ };
79
+ }
27
80
  export {
28
81
  paragraphAttrsFor,
29
82
  paragraphAttrsOf,
30
- runMarkUnder
83
+ runMarkUnder,
84
+ styledParagraph,
85
+ tableStyleAttrs
31
86
  };
@@ -1,5 +1,5 @@
1
1
  import type { RunFormat } from "../../model/format";
2
- import { type Numbering } from "../../numbering/parseNumbering";
2
+ import { type Numbering, type NumberingOptions } from "../../numbering/parseNumbering";
3
3
  import { type CompatSettings } from "../documentSettings";
4
4
  import { type ThemeFonts } from "../theme";
5
5
  import { type StyleTable } from "./styles";
@@ -27,5 +27,13 @@ export interface FormattingContext {
27
27
  compat: CompatSettings;
28
28
  }
29
29
  export declare const NO_FORMATTING: FormattingContext;
30
+ /**
31
+ * How a document's numbering part is read against the rest of what the document lays down.
32
+ *
33
+ * The numbering folder cannot reach styles.xml, so the links a definition follows are handed to it
34
+ * from here, and the two callers that read a numbering part - opening a document and asking an
35
+ * open one for its lists - resolve the same links.
36
+ */
37
+ export declare function numberingOptionsFor(styles: StyleTable, themeFonts: ThemeFonts): NumberingOptions;
30
38
  /** The context an opened document resolves against. A document without a styles part lays down nothing */
31
- export declare function formattingContextOf(styles: Document | null, numbering: Numbering, themeFonts: ThemeFonts, compat?: CompatSettings): FormattingContext;
39
+ export declare function formattingContextOf(styles: Document | null, numberingXml: string | null, themeFonts: ThemeFonts, compat?: CompatSettings): FormattingContext;