web-doc 0.6.2 → 0.8.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 (201) hide show
  1. package/THIRD_PARTY_NOTICES.md +33 -1
  2. package/dist/adapters/docx-images.d.ts +13 -4
  3. package/dist/adapters/docx-images.js +3 -0
  4. package/dist/adapters/docx-paragraphs.d.ts +65 -0
  5. package/dist/adapters/docx-paragraphs.js +165 -0
  6. package/dist/adapters/docx-prepass.d.ts +17 -0
  7. package/dist/adapters/docx-prepass.js +230 -0
  8. package/dist/adapters/office.d.ts +31 -5
  9. package/dist/adapters/office.js +60 -29
  10. package/dist/adapters/pdf.d.ts +9 -0
  11. package/dist/adapters/pdf.js +10 -0
  12. package/dist/assets/pdfium/pdfium.wasm +0 -0
  13. package/dist/contracts.d.ts +53 -2
  14. package/dist/edit/ai/outline.d.ts +47 -0
  15. package/dist/edit/ai/outline.js +338 -0
  16. package/dist/edit/ai/targets.d.ts +16 -0
  17. package/dist/edit/ai/targets.js +309 -0
  18. package/dist/edit/ai/tools.d.ts +28 -0
  19. package/dist/edit/ai/tools.js +605 -0
  20. package/dist/edit/ai/types.d.ts +175 -0
  21. package/dist/edit/ai/types.js +1 -0
  22. package/dist/edit/assets.d.ts +23 -0
  23. package/dist/edit/assets.js +75 -0
  24. package/dist/edit/docx/elements.d.ts +6 -0
  25. package/dist/edit/docx/elements.js +90 -0
  26. package/dist/edit/docx/engine.d.ts +57 -0
  27. package/dist/edit/docx/engine.js +547 -0
  28. package/dist/edit/docx/handlers.d.ts +3 -0
  29. package/dist/edit/docx/handlers.js +15 -0
  30. package/dist/edit/docx/ids.d.ts +45 -0
  31. package/dist/edit/docx/ids.js +101 -0
  32. package/dist/edit/docx/model.d.ts +94 -0
  33. package/dist/edit/docx/model.js +350 -0
  34. package/dist/edit/docx/operations.d.ts +47 -0
  35. package/dist/edit/docx/operations.js +3 -0
  36. package/dist/edit/docx/provider.d.ts +16 -0
  37. package/dist/edit/docx/provider.js +37 -0
  38. package/dist/edit/docx/schemas.d.ts +6 -0
  39. package/dist/edit/docx/schemas.js +192 -0
  40. package/dist/edit/docx/session.d.ts +48 -0
  41. package/dist/edit/docx/session.js +467 -0
  42. package/dist/edit/docx/structure-ops.d.ts +6 -0
  43. package/dist/edit/docx/structure-ops.js +529 -0
  44. package/dist/edit/docx/style.d.ts +45 -0
  45. package/dist/edit/docx/style.js +375 -0
  46. package/dist/edit/docx/table-ops.d.ts +4 -0
  47. package/dist/edit/docx/table-ops.js +241 -0
  48. package/dist/edit/docx/text-ops.d.ts +40 -0
  49. package/dist/edit/docx/text-ops.js +491 -0
  50. package/dist/edit/docx/text.d.ts +34 -0
  51. package/dist/edit/docx/text.js +284 -0
  52. package/dist/edit/docx/tracked.d.ts +52 -0
  53. package/dist/edit/docx/tracked.js +347 -0
  54. package/dist/edit/docx/types.d.ts +206 -0
  55. package/dist/edit/docx/types.js +1 -0
  56. package/dist/edit/docx/write.d.ts +64 -0
  57. package/dist/edit/docx/write.js +375 -0
  58. package/dist/edit/engine.d.ts +128 -0
  59. package/dist/edit/engine.js +1 -0
  60. package/dist/edit/history.d.ts +77 -0
  61. package/dist/edit/history.js +123 -0
  62. package/dist/edit/ooxml/names.d.ts +20 -0
  63. package/dist/edit/ooxml/names.js +61 -0
  64. package/dist/edit/ooxml/opc.d.ts +50 -0
  65. package/dist/edit/ooxml/opc.js +150 -0
  66. package/dist/edit/ooxml/package.d.ts +82 -0
  67. package/dist/edit/ooxml/package.js +233 -0
  68. package/dist/edit/ooxml/patch.d.ts +51 -0
  69. package/dist/edit/ooxml/patch.js +250 -0
  70. package/dist/edit/ooxml/transaction.d.ts +39 -0
  71. package/dist/edit/ooxml/transaction.js +316 -0
  72. package/dist/edit/ooxml/worker.d.ts +8 -0
  73. package/dist/edit/ooxml/worker.js +12 -0
  74. package/dist/edit/ooxml/writer.d.ts +21 -0
  75. package/dist/edit/ooxml/writer.js +187 -0
  76. package/dist/edit/ooxml/xml.d.ts +74 -0
  77. package/dist/edit/ooxml/xml.js +451 -0
  78. package/dist/edit/ooxml/zip.d.ts +54 -0
  79. package/dist/edit/ooxml/zip.js +280 -0
  80. package/dist/edit/operations.d.ts +19 -0
  81. package/dist/edit/operations.js +137 -0
  82. package/dist/edit/pdf/engine/compact.d.ts +6 -0
  83. package/dist/edit/pdf/engine/compact.js +442 -0
  84. package/dist/edit/pdf/engine/document.d.ts +95 -0
  85. package/dist/edit/pdf/engine/document.js +868 -0
  86. package/dist/edit/pdf/engine/elements.d.ts +45 -0
  87. package/dist/edit/pdf/engine/elements.js +313 -0
  88. package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
  89. package/dist/edit/pdf/engine/existing-text.js +424 -0
  90. package/dist/edit/pdf/engine/fonts.d.ts +78 -0
  91. package/dist/edit/pdf/engine/fonts.js +466 -0
  92. package/dist/edit/pdf/engine/geometry.d.ts +36 -0
  93. package/dist/edit/pdf/engine/geometry.js +97 -0
  94. package/dist/edit/pdf/engine/handler.d.ts +21 -0
  95. package/dist/edit/pdf/engine/handler.js +108 -0
  96. package/dist/edit/pdf/engine/images.d.ts +33 -0
  97. package/dist/edit/pdf/engine/images.js +188 -0
  98. package/dist/edit/pdf/engine/layout.d.ts +23 -0
  99. package/dist/edit/pdf/engine/layout.js +177 -0
  100. package/dist/edit/pdf/engine/operations.d.ts +67 -0
  101. package/dist/edit/pdf/engine/operations.js +3 -0
  102. package/dist/edit/pdf/engine/pages.d.ts +6 -0
  103. package/dist/edit/pdf/engine/pages.js +98 -0
  104. package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
  105. package/dist/edit/pdf/engine/pdfium.js +249 -0
  106. package/dist/edit/pdf/engine/shapes.d.ts +4 -0
  107. package/dist/edit/pdf/engine/shapes.js +183 -0
  108. package/dist/edit/pdf/engine/tables.d.ts +44 -0
  109. package/dist/edit/pdf/engine/tables.js +315 -0
  110. package/dist/edit/pdf/engine/text-box.d.ts +71 -0
  111. package/dist/edit/pdf/engine/text-box.js +317 -0
  112. package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
  113. package/dist/edit/pdf/engine/text-layout.js +67 -0
  114. package/dist/edit/pdf/engine/transform.d.ts +5 -0
  115. package/dist/edit/pdf/engine/transform.js +137 -0
  116. package/dist/edit/pdf/provider.d.ts +35 -0
  117. package/dist/edit/pdf/provider.js +92 -0
  118. package/dist/edit/pdf/range-map.d.ts +16 -0
  119. package/dist/edit/pdf/range-map.js +107 -0
  120. package/dist/edit/pdf/schemas.d.ts +11 -0
  121. package/dist/edit/pdf/schemas.js +291 -0
  122. package/dist/edit/pdf/selection.d.ts +5 -0
  123. package/dist/edit/pdf/selection.js +152 -0
  124. package/dist/edit/pdf/session.d.ts +63 -0
  125. package/dist/edit/pdf/session.js +312 -0
  126. package/dist/edit/pdf/types.d.ts +336 -0
  127. package/dist/edit/pdf/types.js +1 -0
  128. package/dist/edit/pptx/elements.d.ts +74 -0
  129. package/dist/edit/pptx/elements.js +301 -0
  130. package/dist/edit/pptx/engine.d.ts +37 -0
  131. package/dist/edit/pptx/engine.js +466 -0
  132. package/dist/edit/pptx/geometry.d.ts +72 -0
  133. package/dist/edit/pptx/geometry.js +193 -0
  134. package/dist/edit/pptx/handler.d.ts +7 -0
  135. package/dist/edit/pptx/handler.js +99 -0
  136. package/dist/edit/pptx/handlers.d.ts +3 -0
  137. package/dist/edit/pptx/handlers.js +21 -0
  138. package/dist/edit/pptx/image-table-ops.d.ts +5 -0
  139. package/dist/edit/pptx/image-table-ops.js +233 -0
  140. package/dist/edit/pptx/model.d.ts +69 -0
  141. package/dist/edit/pptx/model.js +170 -0
  142. package/dist/edit/pptx/operations.d.ts +50 -0
  143. package/dist/edit/pptx/operations.js +11 -0
  144. package/dist/edit/pptx/provider.d.ts +17 -0
  145. package/dist/edit/pptx/provider.js +40 -0
  146. package/dist/edit/pptx/schemas.d.ts +6 -0
  147. package/dist/edit/pptx/schemas.js +220 -0
  148. package/dist/edit/pptx/session.d.ts +54 -0
  149. package/dist/edit/pptx/session.js +145 -0
  150. package/dist/edit/pptx/shape-ops.d.ts +10 -0
  151. package/dist/edit/pptx/shape-ops.js +486 -0
  152. package/dist/edit/pptx/slide-ops.d.ts +6 -0
  153. package/dist/edit/pptx/slide-ops.js +353 -0
  154. package/dist/edit/pptx/style.d.ts +31 -0
  155. package/dist/edit/pptx/style.js +181 -0
  156. package/dist/edit/pptx/text-ops.d.ts +13 -0
  157. package/dist/edit/pptx/text-ops.js +267 -0
  158. package/dist/edit/pptx/text-write.d.ts +67 -0
  159. package/dist/edit/pptx/text-write.js +293 -0
  160. package/dist/edit/pptx/text.d.ts +41 -0
  161. package/dist/edit/pptx/text.js +99 -0
  162. package/dist/edit/pptx/types.d.ts +232 -0
  163. package/dist/edit/pptx/types.js +1 -0
  164. package/dist/edit/schema.d.ts +11 -0
  165. package/dist/edit/schema.js +275 -0
  166. package/dist/edit/session.d.ts +77 -0
  167. package/dist/edit/session.js +943 -0
  168. package/dist/edit/sessions.d.ts +8 -0
  169. package/dist/edit/sessions.js +1 -0
  170. package/dist/edit/types.d.ts +290 -0
  171. package/dist/edit/types.js +1 -0
  172. package/dist/edit/worker-engine.d.ts +31 -0
  173. package/dist/edit/worker-engine.js +91 -0
  174. package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
  175. package/dist/fonts/manifest.json +6 -1
  176. package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
  177. package/dist/fonts.d.ts +2 -0
  178. package/dist/fonts.js +4 -0
  179. package/dist/fuzzy-alignment.d.ts +11 -4
  180. package/dist/fuzzy-alignment.js +3 -9
  181. package/dist/headless.d.ts +5 -1
  182. package/dist/headless.js +8 -1
  183. package/dist/index.d.ts +10 -2
  184. package/dist/index.js +16 -2
  185. package/dist/limits.js +6 -0
  186. package/dist/ooxml-edit-worker.d.ts +1 -0
  187. package/dist/ooxml-edit-worker.js +8 -0
  188. package/dist/pdf-edit-worker.d.ts +1 -0
  189. package/dist/pdf-edit-worker.js +35 -0
  190. package/dist/spreadsheet-viewport.d.ts +5 -0
  191. package/dist/spreadsheet-viewport.js +11 -0
  192. package/dist/ui.js +7 -0
  193. package/dist/viewer.d.ts +6 -0
  194. package/dist/viewer.js +293 -19
  195. package/dist/viewport.d.ts +13 -0
  196. package/dist/viewport.js +126 -1
  197. package/dist/worker-protocol.d.ts +39 -2
  198. package/dist/workers/fuzzy-search-worker.js +1 -1
  199. package/dist/workers/ooxml-edit-worker.js +9212 -0
  200. package/dist/workers/pdf-edit-worker.js +10847 -0
  201. package/package.json +3 -3
@@ -0,0 +1,284 @@
1
+ var _a;
2
+ import { W_NS } from "./ids.js";
3
+ /*
4
+ * The text model of a paragraph: `w:t` text, a tab, a line break (`\v`), a
5
+ * page or column break (`\f`), an inline picture or an embedded object as
6
+ * one object character, a field (`w:fldSimple`, or a complex field from
7
+ * `fldChar begin` to `end`) as its cached result text, hyperlinks and
8
+ * inline `w:sdt` as their runs, hidden runs as text, deleted text (`w:del`)
9
+ * left out. Offsets are UTF-16 code units of the paragraph's text. Every
10
+ * item remembers the XML it came from, so a later patch can keep the bytes
11
+ * of what an edit does not touch.
12
+ */
13
+ export const TAB = "\t";
14
+ export const LINE_BREAK = "\u000b";
15
+ export const PAGE_BREAK = "\f";
16
+ /** The object replacement character: an inline picture or an embedded object. */
17
+ export const OBJECT = "";
18
+ /** Wrappers whose children are read as the paragraph's own runs. */
19
+ const TRANSPARENT = new Set([
20
+ "hyperlink",
21
+ "smartTag",
22
+ "customXml",
23
+ "dir",
24
+ "bdo",
25
+ "ins",
26
+ "moveTo",
27
+ "sdtContent",
28
+ "fldSimple",
29
+ ]);
30
+ /** Wrappers whose content is deleted text: left out, the paragraph is tracked. */
31
+ const DELETED = new Set(["del", "moveFrom"]);
32
+ class Reader {
33
+ part;
34
+ items = [];
35
+ text = "";
36
+ tracked = false;
37
+ #field;
38
+ constructor(part) {
39
+ this.part = part;
40
+ }
41
+ paragraph(paragraph) {
42
+ for (const child of paragraph.children)
43
+ this.#child(child, undefined);
44
+ if (this.#field)
45
+ this.#closeField();
46
+ // A paragraph mark inserted, deleted or moved under revision marks the
47
+ // paragraph as well.
48
+ const pPr = paragraph.children.find((child) => child.local === "pPr" && child.namespace === W_NS);
49
+ if (pPr && paragraphMarkTracked(pPr))
50
+ this.#tracked();
51
+ }
52
+ #child(node, wrapper) {
53
+ if (node.namespace !== W_NS) {
54
+ // DrawingML, math and markup compatibility live outside w:; a math
55
+ // zone or an alternate-content block counts as one object.
56
+ if (node.local === "oMath" ||
57
+ node.local === "oMathPara" ||
58
+ node.local === "AlternateContent")
59
+ this.#push("object", OBJECT, node, node, wrapper);
60
+ return;
61
+ }
62
+ switch (node.local) {
63
+ case "r":
64
+ this.#run(node, wrapper);
65
+ return;
66
+ case "sdt": {
67
+ const content = node.children.find((child) => child.local === "sdtContent" && child.namespace === W_NS);
68
+ if (content)
69
+ for (const child of content.children)
70
+ this.#child(child, node);
71
+ return;
72
+ }
73
+ case "ins":
74
+ case "moveTo":
75
+ this.#tracked();
76
+ for (const child of node.children)
77
+ this.#child(child, node);
78
+ return;
79
+ default:
80
+ break;
81
+ }
82
+ if (DELETED.has(node.local)) {
83
+ this.#tracked();
84
+ return;
85
+ }
86
+ if (TRANSPARENT.has(node.local)) {
87
+ if (node.local === "fldSimple") {
88
+ this.#simpleField(node, wrapper);
89
+ return;
90
+ }
91
+ for (const child of node.children)
92
+ this.#child(child, node);
93
+ }
94
+ }
95
+ #tracked() {
96
+ this.tracked = true;
97
+ }
98
+ /** A simple field: one item carrying the text of its result runs. */
99
+ #simpleField(node, wrapper) {
100
+ const inner = new _a(this.part);
101
+ for (const child of node.children)
102
+ inner.#child(child, node);
103
+ this.tracked ||= inner.tracked;
104
+ this.#push("field", inner.text, node, node, wrapper);
105
+ }
106
+ #run(run, wrapper) {
107
+ const rPr = run.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
108
+ if (rPr) {
109
+ const marked = rPr.children.some((child) => child.namespace === W_NS &&
110
+ (child.local === "ins" || child.local === "del"));
111
+ if (marked)
112
+ this.#tracked();
113
+ }
114
+ if (this.#field)
115
+ this.#field.runs.push(run);
116
+ for (const child of run.children) {
117
+ if (child.namespace !== W_NS) {
118
+ if (child.local === "AlternateContent")
119
+ this.#emit("object", OBJECT, run, child, rPr, wrapper);
120
+ continue;
121
+ }
122
+ switch (child.local) {
123
+ case "rPr":
124
+ case "lastRenderedPageBreak":
125
+ case "instrText":
126
+ case "delInstrText":
127
+ case "delText":
128
+ break;
129
+ case "fldChar":
130
+ this.#fieldChar(child, run);
131
+ break;
132
+ case "t":
133
+ this.#emit("text", this.part.textOf(child), run, child, rPr, wrapper);
134
+ break;
135
+ case "tab":
136
+ case "ptab":
137
+ this.#emit("tab", TAB, run, child, rPr, wrapper);
138
+ break;
139
+ case "br": {
140
+ const type = this.part.attribute(child, "w:type");
141
+ if (type === "page" || type === "column")
142
+ this.#emit("pageBreak", PAGE_BREAK, run, child, rPr, wrapper);
143
+ else
144
+ this.#emit("break", LINE_BREAK, run, child, rPr, wrapper);
145
+ break;
146
+ }
147
+ case "cr":
148
+ this.#emit("break", LINE_BREAK, run, child, rPr, wrapper);
149
+ break;
150
+ case "sym": {
151
+ const code = Number.parseInt(this.part.attribute(child, "w:char") ?? "", 16);
152
+ this.#emit("text", Number.isFinite(code) && code > 0 && code <= 0x10ffff
153
+ ? String.fromCodePoint(code)
154
+ : "", run, child, rPr, wrapper);
155
+ break;
156
+ }
157
+ case "noBreakHyphen":
158
+ this.#emit("text", "‑", run, child, rPr, wrapper);
159
+ break;
160
+ case "softHyphen":
161
+ this.#emit("text", "­", run, child, rPr, wrapper);
162
+ break;
163
+ case "drawing": {
164
+ const inline = child.children.some((node) => node.local === "inline");
165
+ if (inline)
166
+ this.#emit("picture", OBJECT, run, child, rPr, wrapper);
167
+ else
168
+ this.#emit("anchor", "", run, child, rPr, wrapper);
169
+ break;
170
+ }
171
+ case "object":
172
+ case "pict":
173
+ this.#emit("object", OBJECT, run, child, rPr, wrapper);
174
+ break;
175
+ case "footnoteReference":
176
+ case "endnoteReference":
177
+ this.#emit("note", "", run, child, rPr, wrapper);
178
+ break;
179
+ case "ruby": {
180
+ const base = child.children.find((node) => node.local === "rubyBase");
181
+ if (base)
182
+ for (const node of base.children)
183
+ this.#child(node, wrapper);
184
+ break;
185
+ }
186
+ default:
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ #fieldChar(node, run) {
192
+ const type = this.part.attribute(node, "w:fldCharType");
193
+ if (type === "begin") {
194
+ if (this.#field)
195
+ this.#field.inResult.push(false);
196
+ else
197
+ this.#field = { begin: run, runs: [run], text: "", inResult: [false] };
198
+ return;
199
+ }
200
+ if (!this.#field)
201
+ return;
202
+ const levels = this.#field.inResult;
203
+ if (type === "separate") {
204
+ levels[levels.length - 1] = true;
205
+ return;
206
+ }
207
+ if (type === "end") {
208
+ if (levels.length > 1)
209
+ levels.pop();
210
+ else
211
+ this.#closeField();
212
+ }
213
+ }
214
+ #closeField() {
215
+ const field = this.#field;
216
+ this.#field = undefined;
217
+ const rPr = field.begin.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
218
+ this.#append({
219
+ kind: "field",
220
+ start: this.text.length,
221
+ end: this.text.length + field.text.length,
222
+ text: field.text,
223
+ run: field.begin,
224
+ child: field.begin,
225
+ ...(rPr ? { rPr } : {}),
226
+ fieldRuns: field.runs,
227
+ });
228
+ }
229
+ /** Text inside an open field goes to its result; outside it is an item. */
230
+ #emit(kind, text, run, child, rPr, wrapper) {
231
+ const field = this.#field;
232
+ if (field) {
233
+ // What Word shows: the result of the outer field, which holds the
234
+ // results of the fields nested in it and none of their instructions.
235
+ if (field.inResult.every(Boolean))
236
+ field.text += text;
237
+ return;
238
+ }
239
+ this.#push(kind, text, run, child, wrapper, rPr);
240
+ }
241
+ #push(kind, text, run, child, wrapper, rPr) {
242
+ this.#append({
243
+ kind,
244
+ start: this.text.length,
245
+ end: this.text.length + text.length,
246
+ text,
247
+ run,
248
+ child,
249
+ ...(rPr ? { rPr } : {}),
250
+ ...(wrapper ? { wrapper } : {}),
251
+ });
252
+ }
253
+ #append(item) {
254
+ this.items.push(item);
255
+ this.text += item.text;
256
+ }
257
+ }
258
+ _a = Reader;
259
+ const MARK_REVISIONS = new Set(["ins", "del", "moveFrom", "moveTo"]);
260
+ /**
261
+ * Whether the paragraph mark's `w:rPr` carries an insertion, deletion or
262
+ * move. Changed properties alone (`w:rPrChange`, `w:pPrChange`) leave a
263
+ * paragraph editable, as module 06 decided.
264
+ */
265
+ function paragraphMarkTracked(pPr) {
266
+ for (const child of pPr.children) {
267
+ if (child.namespace !== W_NS)
268
+ continue;
269
+ if (child.local === "rPr" &&
270
+ child.children.some((mark) => mark.namespace === W_NS && MARK_REVISIONS.has(mark.local)))
271
+ return true;
272
+ }
273
+ return false;
274
+ }
275
+ /** Reads the text of a `w:p`. */
276
+ export function readParagraphText(part, paragraph) {
277
+ const reader = new Reader(part);
278
+ reader.paragraph(paragraph);
279
+ return { text: reader.text, items: reader.items, tracked: reader.tracked };
280
+ }
281
+ /** The first item that draws text, for the resolved style of a paragraph. */
282
+ export function firstTextItem(model) {
283
+ return model.items.find((item) => (item.kind === "text" || item.kind === "field") && item.text.length > 0);
284
+ }
@@ -0,0 +1,52 @@
1
+ import type { XmlElement, XmlPart } from "../ooxml/xml.js";
2
+ import type { ParagraphRecord } from "./model.js";
3
+ import type { DocxOperationContext, Issue } from "./operations.js";
4
+ import type { DocxRevision } from "./types.js";
5
+ /** The attributes of one revision element, with a fresh `w:id`. */
6
+ export declare function revisionAttributes(context: DocxOperationContext): string;
7
+ /** A refusal for an operation, or a part of one, that has no tracked form. */
8
+ export declare function unsupportedTracked(issue: Issue, path: string, what: string): void;
9
+ /**
10
+ * Whether a tracked change of `[start, end)` would touch a hyperlink, a
11
+ * content control, a field or a run already under revision, which the
12
+ * tracked rebuild cannot express; reports it as an issue at `path`.
13
+ */
14
+ export declare function trackedRangeProblem(record: ParagraphRecord, start: number, end: number, issue: Issue, path: string): boolean;
15
+ /** A deleted run: its properties and its content, text as `w:delText`. */
16
+ export declare function deletedRunXml(rPr: string, content: string): string;
17
+ export interface TrackedSplit {
18
+ /** Everything before the change, bytes kept. */
19
+ readonly before: string;
20
+ /** The removed runs inside one `w:del`, or "". */
21
+ readonly deleted: string;
22
+ /** The `w:rPr` bytes new text takes. */
23
+ readonly rPr: string;
24
+ /** Everything after the change, bytes kept. */
25
+ readonly after: string;
26
+ }
27
+ /**
28
+ * The paragraph's content around `[start, end)`, unit by unit as the direct
29
+ * edit walks it: the runs the range covers re-serialized as deleted, a cut
30
+ * run split into a kept head, a deleted middle and a kept tail, everything
31
+ * else as its bytes. New text takes the style of the first replaced run,
32
+ * else of the run before a caret, else of the run after it. The caller
33
+ * places the insertion between `deleted` and `after`. A wrapper or a field
34
+ * inside the range never reaches here: validation refuses it.
35
+ */
36
+ export declare function trackedSplit(context: DocxOperationContext, record: ParagraphRecord, start: number, end: number, fallbackRPr: string): TrackedSplit;
37
+ /** `text` as an inserted run under revision; nothing for empty text. */
38
+ export declare function insertedRunXml(context: DocxOperationContext, rPr: string, text: string): string;
39
+ /** `w:pPr` bytes with the paragraph mark under revision (`w:ins` or `w:del` first in its `w:rPr`), section properties left out. */
40
+ export declare function markedParagraphProperties(context: DocxOperationContext, pPr: XmlElement | undefined, mark: "ins" | "del"): string;
41
+ /**
42
+ * A whole paragraph marked deleted: its mark under `w:del`, every run
43
+ * inside `w:del` with `w:delText`, other children kept. Word removes the
44
+ * paragraph when the deletion is accepted.
45
+ */
46
+ export declare function deletedParagraphXml(context: DocxOperationContext, record: ParagraphRecord): string;
47
+ /**
48
+ * The revisions a paragraph holds, in document order: run-level
49
+ * insertions, deletions and moves with their text, the paragraph mark's
50
+ * own revision, and changed run or paragraph properties.
51
+ */
52
+ export declare function revisionsOf(part: XmlPart, paragraph: XmlElement): DocxRevision[];
@@ -0,0 +1,347 @@
1
+ import { W_NS } from "./ids.js";
2
+ import { LINE_BREAK, TAB } from "./text.js";
3
+ import { contentOf, rPrOf, unitsOf } from "./text-ops.js";
4
+ import { escapeAttributeValue, innerPropertiesXml, mergedProperties, paragraphXml, PPR_ORDER, runContentXml, runXml, sliceOf, } from "./write.js";
5
+ /*
6
+ * Tracked changes: the same paragraph rebuild as a direct edit, but what a
7
+ * direct edit drops is kept inside `w:del` (text as `w:delText`) and what
8
+ * it adds goes inside `w:ins`, each with an id, the author and the date of
9
+ * the batch, so Word and Pages show the edit as a suggestion to accept or
10
+ * reject. A paragraph rebuilt this way holds revisions and is read-only
11
+ * for the next edit (decision 6 of the ai-edit module). Hyperlinks,
12
+ * content controls and fields have no tracked form here: a change that
13
+ * touches one is refused.
14
+ */
15
+ /** The attributes of one revision element, with a fresh `w:id`. */
16
+ export function revisionAttributes(context) {
17
+ const tracked = context.tracked;
18
+ return ` w:id="${context.nextRevisionId()}" w:author="${escapeAttributeValue(tracked.author)}"${tracked.date === undefined
19
+ ? ""
20
+ : ` w:date="${escapeAttributeValue(tracked.date)}"`}`;
21
+ }
22
+ /** A refusal for an operation, or a part of one, that has no tracked form. */
23
+ export function unsupportedTracked(issue, path, what) {
24
+ issue(path, "unsupported-change-mode", `${what} has no tracked form; apply it directly and review with checkpoints`);
25
+ }
26
+ /** The child of `container` an item sits under. */
27
+ function topOf(node, container) {
28
+ let current = node;
29
+ while (current.parent && current.parent !== container)
30
+ current = current.parent;
31
+ return current;
32
+ }
33
+ /**
34
+ * Whether a tracked change of `[start, end)` would touch a hyperlink, a
35
+ * content control, a field or a run already under revision, which the
36
+ * tracked rebuild cannot express; reports it as an issue at `path`.
37
+ */
38
+ export function trackedRangeProblem(record, start, end, issue, path) {
39
+ for (const item of record.text.items) {
40
+ const overlaps = item.start === item.end
41
+ ? start < item.start && item.start < end
42
+ : item.start < end && start < item.end;
43
+ if (!overlaps)
44
+ continue;
45
+ const top = topOf(item.run, record.node);
46
+ // An equation or an alternate-content block sits in the paragraph
47
+ // without a run; a revision cannot wrap it either.
48
+ const plainRun = item.run.local === "r" && item.run.namespace === W_NS;
49
+ if (!plainRun ||
50
+ item.fieldRuns ||
51
+ item.kind === "field" ||
52
+ top !== item.run ||
53
+ item.wrapper !== undefined) {
54
+ unsupportedTracked(issue, path, "A change touching a hyperlink, content control, field or equation");
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ /** Run content with `w:t` as `w:delText`, for a deleted run. */
61
+ function deletedContent(content) {
62
+ return content
63
+ .replaceAll("<w:t>", "<w:delText>")
64
+ .replaceAll("<w:t ", "<w:delText ")
65
+ .replaceAll("</w:t>", "</w:delText>")
66
+ .replaceAll("<w:instrText", "<w:delInstrText")
67
+ .replaceAll("</w:instrText>", "</w:delInstrText>");
68
+ }
69
+ /** A deleted run: its properties and its content, text as `w:delText`. */
70
+ export function deletedRunXml(rPr, content) {
71
+ return content.length === 0
72
+ ? ""
73
+ : `<w:r>${rPr}${deletedContent(content)}</w:r>`;
74
+ }
75
+ /** The whole content of a run (every child but `w:rPr`) as bytes. */
76
+ function runContentOf(part, run) {
77
+ return run.children
78
+ .filter((child) => !(child.local === "rPr" && child.namespace === W_NS))
79
+ .map((child) => sliceOf(part, child))
80
+ .join("");
81
+ }
82
+ /** `w:rPr` bytes of a run element. */
83
+ function runPropertiesOf(part, run) {
84
+ const rPr = run.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
85
+ return sliceOf(part, rPr);
86
+ }
87
+ /**
88
+ * The deleted part of a cut run: like `contentOf`, but a zero-width item
89
+ * (a note reference, an anchored drawing) belongs to the deletion only
90
+ * strictly inside `(from, to)`, so the kept head or tail keeps it instead
91
+ * and nothing is written twice.
92
+ */
93
+ function deletedContentOf(part, items, from, to) {
94
+ let out = "";
95
+ for (const item of items) {
96
+ if (item.start === item.end) {
97
+ if (from < item.start && item.start < to)
98
+ out += sliceOf(part, item.child);
99
+ continue;
100
+ }
101
+ const begin = Math.max(item.start, from);
102
+ const stop = Math.min(item.end, to);
103
+ if (stop <= begin)
104
+ continue;
105
+ if (item.kind === "text" && item.child.local === "t")
106
+ out += runContentXml(item.text.slice(begin - item.start, stop - item.start));
107
+ else if (begin === item.start && stop === item.end)
108
+ out += sliceOf(part, item.child);
109
+ }
110
+ return out;
111
+ }
112
+ /**
113
+ * The paragraph's content around `[start, end)`, unit by unit as the direct
114
+ * edit walks it: the runs the range covers re-serialized as deleted, a cut
115
+ * run split into a kept head, a deleted middle and a kept tail, everything
116
+ * else as its bytes. New text takes the style of the first replaced run,
117
+ * else of the run before a caret, else of the run after it. The caller
118
+ * places the insertion between `deleted` and `after`. A wrapper or a field
119
+ * inside the range never reaches here: validation refuses it.
120
+ */
121
+ export function trackedSplit(context, record, start, end, fallbackRPr) {
122
+ const part = context.model.document;
123
+ let before = "";
124
+ let after = "";
125
+ const deletedRuns = [];
126
+ let rPr;
127
+ let lastRPr;
128
+ /** The change has been passed: zero-width children now follow it. */
129
+ let passed = false;
130
+ const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
131
+ for (const unit of unitsOf(record.node, record.text.items)) {
132
+ if (unit.kind === "zero") {
133
+ if (passed)
134
+ after += raw(unit);
135
+ else
136
+ before += raw(unit);
137
+ continue;
138
+ }
139
+ if (unit.end <= start) {
140
+ before += raw(unit);
141
+ lastRPr = rPrOf(part, unit.items.at(-1));
142
+ // A caret right after this unit: the new text follows its style.
143
+ if (start === end && unit.end === start) {
144
+ rPr ??= lastRPr;
145
+ passed = true;
146
+ }
147
+ continue;
148
+ }
149
+ if (unit.start >= end) {
150
+ rPr ??= lastRPr ?? rPrOf(part, unit.items[0]);
151
+ passed = true;
152
+ after += raw(unit);
153
+ continue;
154
+ }
155
+ if (unit.kind !== "run")
156
+ throw new Error("A tracked change cannot cut a field or a wrapper");
157
+ const run = unit.nodes[0];
158
+ const style = runPropertiesOf(part, run);
159
+ if (unit.start >= start && unit.end <= end) {
160
+ // Covered whole: the run goes into the deletion as it is.
161
+ deletedRuns.push(deletedRunXml(style, runContentOf(part, run)));
162
+ }
163
+ else {
164
+ const head = contentOf(part, unit.items, unit.start, start, true);
165
+ const middle = deletedContentOf(part, unit.items, Math.max(unit.start, start), Math.min(unit.end, end));
166
+ const tail = contentOf(part, unit.items, end, unit.end, start < end);
167
+ if (head)
168
+ before += runXml(style, head);
169
+ if (middle)
170
+ deletedRuns.push(deletedRunXml(style, middle));
171
+ if (tail)
172
+ after += runXml(style, tail);
173
+ }
174
+ rPr ??= style;
175
+ if (unit.end >= end)
176
+ passed = true;
177
+ }
178
+ const deleted = deletedRuns.length > 0
179
+ ? `<w:del${revisionAttributes(context)}>${deletedRuns.join("")}</w:del>`
180
+ : "";
181
+ return { before, deleted, rPr: rPr ?? lastRPr ?? fallbackRPr, after };
182
+ }
183
+ /** `text` as an inserted run under revision; nothing for empty text. */
184
+ export function insertedRunXml(context, rPr, text) {
185
+ const content = runContentXml(text);
186
+ return content.length === 0
187
+ ? ""
188
+ : `<w:ins${revisionAttributes(context)}>${runXml(rPr, content)}</w:ins>`;
189
+ }
190
+ /** `w:pPr` bytes with the paragraph mark under revision (`w:ins` or `w:del` first in its `w:rPr`), section properties left out. */
191
+ export function markedParagraphProperties(context, pPr, mark) {
192
+ const part = context.model.document;
193
+ const rPr = pPr?.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
194
+ const inner = innerPropertiesXml(part, rPr, ["ins", "del"]);
195
+ const marked = `<w:rPr><w:${mark}${revisionAttributes(context)}/>${inner}</w:rPr>`;
196
+ return mergedProperties(part, pPr, "w:pPr", PPR_ORDER, new Map([
197
+ ["rPr", marked],
198
+ ["sectPr", null],
199
+ ]));
200
+ }
201
+ /**
202
+ * A whole paragraph marked deleted: its mark under `w:del`, every run
203
+ * inside `w:del` with `w:delText`, other children kept. Word removes the
204
+ * paragraph when the deletion is accepted.
205
+ */
206
+ export function deletedParagraphXml(context, record) {
207
+ const part = context.model.document;
208
+ const pPr = markedParagraphProperties(context, record.pPr, "del");
209
+ let content = "";
210
+ let open = [];
211
+ const flush = () => {
212
+ if (open.length > 0)
213
+ content += `<w:del${revisionAttributes(context)}>${open.join("")}</w:del>`;
214
+ open = [];
215
+ };
216
+ for (const child of record.node.children) {
217
+ if (child.local === "pPr" && child.namespace === W_NS)
218
+ continue;
219
+ if (child.local === "r" && child.namespace === W_NS) {
220
+ const run = deletedRunXml(runPropertiesOf(part, child), runContentOf(part, child));
221
+ if (run)
222
+ open.push(run);
223
+ continue;
224
+ }
225
+ flush();
226
+ content += sliceOf(part, child);
227
+ }
228
+ flush();
229
+ return paragraphXml(record.node, record.id, pPr, content);
230
+ }
231
+ const RUN_REVISIONS = new Set(["ins", "del", "moveFrom", "moveTo"]);
232
+ /**
233
+ * The revisions a paragraph holds, in document order: run-level
234
+ * insertions, deletions and moves with their text, the paragraph mark's
235
+ * own revision, and changed run or paragraph properties.
236
+ */
237
+ export function revisionsOf(part, paragraph) {
238
+ const out = [];
239
+ const attributes = (node) => {
240
+ const author = part.attribute(node, "w:author");
241
+ const date = part.attribute(node, "w:date");
242
+ const raw = part.attribute(node, "w:id");
243
+ return {
244
+ id: raw !== undefined && /^\d+$/.test(raw) ? Number(raw) : -1,
245
+ ...(author === undefined ? {} : { author }),
246
+ ...(date === undefined ? {} : { date }),
247
+ };
248
+ };
249
+ const visit = (node, inRPr, inPPr) => {
250
+ for (const child of node.children) {
251
+ if (child.namespace !== W_NS) {
252
+ visit(child, inRPr, inPPr);
253
+ continue;
254
+ }
255
+ if (child.local === "pPr") {
256
+ visit(child, false, true);
257
+ continue;
258
+ }
259
+ if (child.local === "rPr") {
260
+ visit(child, true, inPPr);
261
+ continue;
262
+ }
263
+ if (inRPr && RUN_REVISIONS.has(child.local)) {
264
+ out.push({
265
+ start: child.start,
266
+ revision: {
267
+ kind: child.local,
268
+ scope: inPPr ? "mark" : "runs",
269
+ ...attributes(child),
270
+ ...(inPPr ? { text: "\n" } : {}),
271
+ },
272
+ });
273
+ continue;
274
+ }
275
+ if (inRPr && child.local === "rPrChange") {
276
+ out.push({
277
+ start: child.start,
278
+ revision: {
279
+ kind: "rPrChange",
280
+ scope: inPPr ? "mark" : "runs",
281
+ ...attributes(child),
282
+ ...(inPPr || !node.parent
283
+ ? {}
284
+ : { text: textOf(part, node.parent) }),
285
+ },
286
+ });
287
+ continue;
288
+ }
289
+ if (inPPr && child.local === "pPrChange") {
290
+ out.push({
291
+ start: child.start,
292
+ revision: {
293
+ kind: "pPrChange",
294
+ scope: "paragraph",
295
+ ...attributes(child),
296
+ },
297
+ });
298
+ continue;
299
+ }
300
+ if (!inRPr && !inPPr && RUN_REVISIONS.has(child.local)) {
301
+ out.push({
302
+ start: child.start,
303
+ revision: {
304
+ kind: child.local,
305
+ scope: "runs",
306
+ ...attributes(child),
307
+ text: textOf(part, child),
308
+ },
309
+ });
310
+ visit(child, false, false);
311
+ continue;
312
+ }
313
+ visit(child, inRPr, inPPr);
314
+ }
315
+ };
316
+ visit(paragraph, false, false);
317
+ return out
318
+ .sort((a, b) => a.start - b.start)
319
+ .map((entry) => Object.freeze(entry.revision));
320
+ }
321
+ /** The text of every run under `node`: `w:t` and `w:delText`, tabs and breaks. */
322
+ function textOf(part, node) {
323
+ let text = "";
324
+ const visit = (current) => {
325
+ for (const child of current.children) {
326
+ if (child.namespace === W_NS) {
327
+ if (child.local === "t" || child.local === "delText") {
328
+ text += part.textOf(child);
329
+ continue;
330
+ }
331
+ if (child.local === "tab") {
332
+ text += TAB;
333
+ continue;
334
+ }
335
+ if (child.local === "br" || child.local === "cr") {
336
+ text += LINE_BREAK;
337
+ continue;
338
+ }
339
+ if (child.local === "rPr" || child.local === "pPr")
340
+ continue;
341
+ }
342
+ visit(child);
343
+ }
344
+ };
345
+ visit(node);
346
+ return text;
347
+ }