@beyondwork/docx-react-component 1.0.19 → 1.0.21
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.
- package/package.json +44 -25
- package/src/api/public-types.ts +336 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +14 -2
- package/src/core/search/search-text.ts +28 -0
- package/src/core/state/editor-state.ts +3 -0
- package/src/index.ts +21 -0
- package/src/io/docx-session.ts +363 -17
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +83 -3
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +82 -8
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +240 -2
- package/src/io/ooxml/parse-headers-footers.ts +431 -7
- package/src/io/ooxml/parse-inline-media.ts +15 -1
- package/src/io/ooxml/parse-main-document.ts +396 -14
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +117 -1
- package/src/model/snapshot.ts +85 -1
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-navigation.ts +52 -13
- package/src/runtime/document-runtime.ts +1521 -75
- package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
- package/src/runtime/session-capabilities.ts +33 -3
- package/src/runtime/surface-projection.ts +86 -25
- package/src/runtime/table-schema.ts +2 -2
- package/src/runtime/view-state.ts +24 -6
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +915 -1314
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1448 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +55 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +27 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +237 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +55 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +190 -48
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +130 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
- package/src/validation/compatibility-engine.ts +27 -4
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/docx-comment-proof.ts +220 -0
|
@@ -3,7 +3,9 @@ import type {
|
|
|
3
3
|
FootnoteCollection,
|
|
4
4
|
FootnoteDefinition,
|
|
5
5
|
InlineNode,
|
|
6
|
+
ParagraphIndentation,
|
|
6
7
|
ParagraphNode,
|
|
8
|
+
ParagraphSpacing,
|
|
7
9
|
TableCellNode,
|
|
8
10
|
TableNode,
|
|
9
11
|
TableRowNode,
|
|
@@ -149,6 +151,7 @@ function parseNoteElement(
|
|
|
149
151
|
type: "opaque_block",
|
|
150
152
|
fragmentId: `fragment:note-tbl-${noteId}`,
|
|
151
153
|
warningId: `warning:note-opaque-table`,
|
|
154
|
+
rawXml: serializeElementToXml(child),
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
157
|
} else {
|
|
@@ -156,6 +159,7 @@ function parseNoteElement(
|
|
|
156
159
|
type: "opaque_block",
|
|
157
160
|
fragmentId: `fragment:note-opaque-${noteId}`,
|
|
158
161
|
warningId: `warning:note-opaque-block`,
|
|
162
|
+
rawXml: serializeElementToXml(child),
|
|
159
163
|
});
|
|
160
164
|
}
|
|
161
165
|
}
|
|
@@ -165,7 +169,15 @@ function parseNoteElement(
|
|
|
165
169
|
|
|
166
170
|
function parseParagraphElement(pElement: XmlElementNode): ParagraphNode {
|
|
167
171
|
let styleId: string | undefined;
|
|
172
|
+
let alignment: ParagraphNode["alignment"];
|
|
173
|
+
let spacing: ParagraphNode["spacing"];
|
|
174
|
+
let indentation: ParagraphNode["indentation"];
|
|
168
175
|
const children: InlineNode[] = [];
|
|
176
|
+
let activeComplexField: {
|
|
177
|
+
instruction: string;
|
|
178
|
+
children: InlineNode[];
|
|
179
|
+
mode: "instruction" | "result";
|
|
180
|
+
} | null = null;
|
|
169
181
|
|
|
170
182
|
for (const child of pElement.children) {
|
|
171
183
|
if (child.type !== "element") {
|
|
@@ -177,24 +189,141 @@ function parseParagraphElement(pElement: XmlElementNode): ParagraphNode {
|
|
|
177
189
|
if (name === "pPr") {
|
|
178
190
|
const pStyle = findChildElementOptional(child, "pStyle");
|
|
179
191
|
styleId = pStyle?.attributes["w:val"] ?? pStyle?.attributes.val;
|
|
192
|
+
const jc = findChildElementOptional(child, "jc");
|
|
193
|
+
const jcVal = jc?.attributes["w:val"] ?? jc?.attributes.val;
|
|
194
|
+
if (jcVal === "left" || jcVal === "center" || jcVal === "right" || jcVal === "both" || jcVal === "distribute") {
|
|
195
|
+
alignment = jcVal;
|
|
196
|
+
}
|
|
197
|
+
spacing = readParagraphSpacing(child);
|
|
198
|
+
indentation = readParagraphIndentation(child);
|
|
180
199
|
} else if (name === "r") {
|
|
181
|
-
|
|
200
|
+
activeComplexField = appendRunNodes(child, children, activeComplexField);
|
|
182
201
|
} else if (name === "hyperlink") {
|
|
202
|
+
if (activeComplexField && activeComplexField.instruction.trim().length > 0) {
|
|
203
|
+
children.push({
|
|
204
|
+
type: "field",
|
|
205
|
+
fieldType: "complex",
|
|
206
|
+
instruction: activeComplexField.instruction,
|
|
207
|
+
children: activeComplexField.children,
|
|
208
|
+
});
|
|
209
|
+
activeComplexField = null;
|
|
210
|
+
}
|
|
183
211
|
children.push(parseHyperlinkElement(child));
|
|
184
212
|
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
185
|
-
|
|
213
|
+
const bookmarkNode = parseBookmarkElement(child);
|
|
214
|
+
if (activeComplexField?.mode === "result") {
|
|
215
|
+
activeComplexField.children.push(bookmarkNode);
|
|
216
|
+
} else {
|
|
217
|
+
if (activeComplexField && activeComplexField.instruction.trim().length > 0) {
|
|
218
|
+
children.push({
|
|
219
|
+
type: "field",
|
|
220
|
+
fieldType: "complex",
|
|
221
|
+
instruction: activeComplexField.instruction,
|
|
222
|
+
children: activeComplexField.children,
|
|
223
|
+
});
|
|
224
|
+
activeComplexField = null;
|
|
225
|
+
}
|
|
226
|
+
children.push(bookmarkNode);
|
|
227
|
+
}
|
|
186
228
|
} else if (name === "fldSimple") {
|
|
229
|
+
if (activeComplexField && activeComplexField.instruction.trim().length > 0) {
|
|
230
|
+
children.push({
|
|
231
|
+
type: "field",
|
|
232
|
+
fieldType: "complex",
|
|
233
|
+
instruction: activeComplexField.instruction,
|
|
234
|
+
children: activeComplexField.children,
|
|
235
|
+
});
|
|
236
|
+
activeComplexField = null;
|
|
237
|
+
}
|
|
187
238
|
pushFieldNode(children, child, "simple");
|
|
188
239
|
}
|
|
189
240
|
}
|
|
190
241
|
|
|
242
|
+
if (activeComplexField && activeComplexField.instruction.trim().length > 0) {
|
|
243
|
+
children.push({
|
|
244
|
+
type: "field",
|
|
245
|
+
fieldType: "complex",
|
|
246
|
+
instruction: activeComplexField.instruction,
|
|
247
|
+
children: activeComplexField.children,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
191
251
|
return {
|
|
192
252
|
type: "paragraph",
|
|
193
253
|
...(styleId ? { styleId } : {}),
|
|
254
|
+
...(alignment ? { alignment } : {}),
|
|
255
|
+
...(spacing ? { spacing } : {}),
|
|
256
|
+
...(indentation ? { indentation } : {}),
|
|
194
257
|
children,
|
|
195
258
|
};
|
|
196
259
|
}
|
|
197
260
|
|
|
261
|
+
function appendRunNodes(
|
|
262
|
+
rElement: XmlElementNode,
|
|
263
|
+
nodes: InlineNode[],
|
|
264
|
+
activeComplexField: {
|
|
265
|
+
instruction: string;
|
|
266
|
+
children: InlineNode[];
|
|
267
|
+
mode: "instruction" | "result";
|
|
268
|
+
} | null,
|
|
269
|
+
): {
|
|
270
|
+
instruction: string;
|
|
271
|
+
children: InlineNode[];
|
|
272
|
+
mode: "instruction" | "result";
|
|
273
|
+
} | null {
|
|
274
|
+
const marks: TextMark[] = parseRunProperties(rElement);
|
|
275
|
+
|
|
276
|
+
for (const child of rElement.children) {
|
|
277
|
+
if (child.type !== "element") {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const name = localName(child.name);
|
|
282
|
+
if (name === "fldChar") {
|
|
283
|
+
const fldType = child.attributes["w:fldCharType"] ?? child.attributes.fldCharType;
|
|
284
|
+
if (fldType === "begin") {
|
|
285
|
+
activeComplexField = { instruction: "", children: [], mode: "instruction" };
|
|
286
|
+
} else if (fldType === "separate" && activeComplexField) {
|
|
287
|
+
activeComplexField.mode = "result";
|
|
288
|
+
} else if (fldType === "end" && activeComplexField) {
|
|
289
|
+
if (activeComplexField.instruction.trim().length > 0) {
|
|
290
|
+
nodes.push({
|
|
291
|
+
type: "field",
|
|
292
|
+
fieldType: "complex",
|
|
293
|
+
instruction: activeComplexField.instruction,
|
|
294
|
+
children: activeComplexField.children,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
activeComplexField = null;
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (name === "instrText") {
|
|
303
|
+
if (activeComplexField) {
|
|
304
|
+
activeComplexField.instruction += extractTextContent(child);
|
|
305
|
+
} else {
|
|
306
|
+
pushFieldNode(nodes, child, "complex");
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const inlineNode = parseRunChildNode(child, marks);
|
|
312
|
+
if (!inlineNode) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (activeComplexField?.mode === "result") {
|
|
317
|
+
activeComplexField.children.push(inlineNode);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
nodes.push(inlineNode);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return activeComplexField;
|
|
325
|
+
}
|
|
326
|
+
|
|
198
327
|
function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
199
328
|
const nodes: InlineNode[] = [];
|
|
200
329
|
const marks: TextMark[] = parseRunProperties(rElement);
|
|
@@ -243,6 +372,52 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
243
372
|
return nodes;
|
|
244
373
|
}
|
|
245
374
|
|
|
375
|
+
function parseRunChildNode(
|
|
376
|
+
child: XmlElementNode,
|
|
377
|
+
marks: TextMark[],
|
|
378
|
+
): InlineNode | null {
|
|
379
|
+
const name = localName(child.name);
|
|
380
|
+
|
|
381
|
+
if (name === "t") {
|
|
382
|
+
const text = extractTextContent(child);
|
|
383
|
+
if (text.length > 0) {
|
|
384
|
+
return {
|
|
385
|
+
type: "text",
|
|
386
|
+
text,
|
|
387
|
+
...(marks.length > 0 ? { marks } : {}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
if (name === "br") {
|
|
393
|
+
return { type: "hard_break" };
|
|
394
|
+
}
|
|
395
|
+
if (name === "tab") {
|
|
396
|
+
return { type: "tab" };
|
|
397
|
+
}
|
|
398
|
+
if (name === "footnoteReference") {
|
|
399
|
+
const noteId =
|
|
400
|
+
child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
401
|
+
if (noteId) {
|
|
402
|
+
return { type: "footnote_ref", noteId, noteKind: "footnote" };
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
if (name === "endnoteReference") {
|
|
407
|
+
const noteId =
|
|
408
|
+
child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
409
|
+
if (noteId) {
|
|
410
|
+
return { type: "footnote_ref", noteId, noteKind: "endnote" };
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
415
|
+
return parseBookmarkElement(child);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
246
421
|
function parseHyperlinkElement(element: XmlElementNode): Extract<InlineNode, { type: "hyperlink" }> {
|
|
247
422
|
const href = element.attributes["w:anchor"]
|
|
248
423
|
? `#${element.attributes["w:anchor"]}`
|
|
@@ -339,12 +514,75 @@ function parseRunProperties(rElement: XmlElementNode): TextMark[] {
|
|
|
339
514
|
case "strike":
|
|
340
515
|
if (val !== "0" && val !== "false") marks.push({ type: "strikethrough" });
|
|
341
516
|
break;
|
|
517
|
+
case "dstrike":
|
|
518
|
+
if (val !== "0" && val !== "false") marks.push({ type: "doubleStrikethrough" });
|
|
519
|
+
break;
|
|
520
|
+
case "rFonts": {
|
|
521
|
+
const family =
|
|
522
|
+
child.attributes["w:ascii"] ??
|
|
523
|
+
child.attributes["w:hAnsi"] ??
|
|
524
|
+
child.attributes.ascii ??
|
|
525
|
+
child.attributes.hAnsi;
|
|
526
|
+
if (family) marks.push({ type: "fontFamily", val: family });
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "sz": {
|
|
530
|
+
const szVal = child.attributes["w:val"] ?? child.attributes.val;
|
|
531
|
+
if (szVal) {
|
|
532
|
+
const size = Number.parseInt(szVal, 10);
|
|
533
|
+
if (Number.isFinite(size) && size > 0) marks.push({ type: "fontSize", val: size });
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
case "color": {
|
|
538
|
+
const colorVal = child.attributes["w:val"] ?? child.attributes.val;
|
|
539
|
+
if (colorVal && colorVal !== "auto") marks.push({ type: "textColor", color: colorVal });
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case "smallCaps":
|
|
543
|
+
if (val !== "0" && val !== "false") marks.push({ type: "smallCaps" });
|
|
544
|
+
break;
|
|
545
|
+
case "caps":
|
|
546
|
+
if (val !== "0" && val !== "false") marks.push({ type: "allCaps" });
|
|
547
|
+
break;
|
|
342
548
|
}
|
|
343
549
|
}
|
|
344
550
|
|
|
345
551
|
return marks;
|
|
346
552
|
}
|
|
347
553
|
|
|
554
|
+
function readParagraphSpacing(pPr: XmlElementNode): ParagraphSpacing | undefined {
|
|
555
|
+
const spacingNode = findChildElementOptional(pPr, "spacing");
|
|
556
|
+
if (!spacingNode) return undefined;
|
|
557
|
+
const result: ParagraphSpacing = {};
|
|
558
|
+
const before = spacingNode.attributes["w:before"] ?? spacingNode.attributes.before;
|
|
559
|
+
if (before) result.before = Number.parseInt(before, 10);
|
|
560
|
+
const after = spacingNode.attributes["w:after"] ?? spacingNode.attributes.after;
|
|
561
|
+
if (after) result.after = Number.parseInt(after, 10);
|
|
562
|
+
const line = spacingNode.attributes["w:line"] ?? spacingNode.attributes.line;
|
|
563
|
+
if (line) result.line = Number.parseInt(line, 10);
|
|
564
|
+
const lineRule = spacingNode.attributes["w:lineRule"] ?? spacingNode.attributes.lineRule;
|
|
565
|
+
if (lineRule === "auto" || lineRule === "exact" || lineRule === "atLeast") {
|
|
566
|
+
result.lineRule = lineRule;
|
|
567
|
+
}
|
|
568
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function readParagraphIndentation(pPr: XmlElementNode): ParagraphIndentation | undefined {
|
|
572
|
+
const indNode = findChildElementOptional(pPr, "ind");
|
|
573
|
+
if (!indNode) return undefined;
|
|
574
|
+
const result: ParagraphIndentation = {};
|
|
575
|
+
const left = indNode.attributes["w:left"] ?? indNode.attributes.left;
|
|
576
|
+
if (left) result.left = Number.parseInt(left, 10);
|
|
577
|
+
const right = indNode.attributes["w:right"] ?? indNode.attributes.right;
|
|
578
|
+
if (right) result.right = Number.parseInt(right, 10);
|
|
579
|
+
const firstLine = indNode.attributes["w:firstLine"] ?? indNode.attributes.firstLine;
|
|
580
|
+
if (firstLine) result.firstLine = Number.parseInt(firstLine, 10);
|
|
581
|
+
const hanging = indNode.attributes["w:hanging"] ?? indNode.attributes.hanging;
|
|
582
|
+
if (hanging) result.hanging = Number.parseInt(hanging, 10);
|
|
583
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
584
|
+
}
|
|
585
|
+
|
|
348
586
|
function extractTextContent(tElement: XmlElementNode): string {
|
|
349
587
|
let text = "";
|
|
350
588
|
for (const child of tElement.children) {
|