@beyondwork/docx-react-component 1.0.28 → 1.0.30
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 +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -8,6 +8,14 @@ import type {
|
|
|
8
8
|
TableRowNode,
|
|
9
9
|
TextMark,
|
|
10
10
|
} from "../../model/canonical-document.ts";
|
|
11
|
+
import type { RevisionRecord as ReviewRevisionRecord } from "../../review/store/revision-types.ts";
|
|
12
|
+
import { serializeRuntimeRevisionsIntoStoryXml } from "./serialize-runtime-revisions.ts";
|
|
13
|
+
import { splitStoryBlocksForRuntimeRevisions } from "./split-story-blocks-for-runtime-revisions.ts";
|
|
14
|
+
import {
|
|
15
|
+
serializeTableCellPropertiesXml,
|
|
16
|
+
serializeTablePropertiesXml,
|
|
17
|
+
serializeTableRowPropertiesXml,
|
|
18
|
+
} from "./table-properties-xml.ts";
|
|
11
19
|
|
|
12
20
|
export const WORD_FOOTNOTES_CONTENT_TYPE =
|
|
13
21
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
|
|
@@ -21,12 +29,16 @@ const R_NS = `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/rel
|
|
|
21
29
|
* Serialize the footnotes portion of a FootnoteCollection to footnotes.xml.
|
|
22
30
|
* Includes the required separator and continuation-separator stubs.
|
|
23
31
|
*/
|
|
24
|
-
export function serializeFootnotesXml(
|
|
32
|
+
export function serializeFootnotesXml(
|
|
33
|
+
collection: FootnoteCollection,
|
|
34
|
+
revisionsByNoteId: Record<string, readonly ReviewRevisionRecord[]> = {},
|
|
35
|
+
): string {
|
|
25
36
|
const entries = Object.values(collection.footnotes).sort(compareNoteIds);
|
|
26
37
|
const body = [
|
|
27
38
|
serializeSeparatorStub("footnote", "-1", "separator"),
|
|
28
39
|
serializeSeparatorStub("footnote", "0", "continuationSeparator"),
|
|
29
|
-
...entries.map((entry) =>
|
|
40
|
+
...entries.map((entry) =>
|
|
41
|
+
serializeNoteDefinition("footnote", entry, revisionsByNoteId[entry.noteId] ?? [])),
|
|
30
42
|
].join("");
|
|
31
43
|
|
|
32
44
|
return [
|
|
@@ -39,12 +51,16 @@ export function serializeFootnotesXml(collection: FootnoteCollection): string {
|
|
|
39
51
|
* Serialize the endnotes portion of a FootnoteCollection to endnotes.xml.
|
|
40
52
|
* Includes the required separator and continuation-separator stubs.
|
|
41
53
|
*/
|
|
42
|
-
export function serializeEndnotesXml(
|
|
54
|
+
export function serializeEndnotesXml(
|
|
55
|
+
collection: FootnoteCollection,
|
|
56
|
+
revisionsByNoteId: Record<string, readonly ReviewRevisionRecord[]> = {},
|
|
57
|
+
): string {
|
|
43
58
|
const entries = Object.values(collection.endnotes).sort(compareNoteIds);
|
|
44
59
|
const body = [
|
|
45
60
|
serializeSeparatorStub("endnote", "-1", "separator"),
|
|
46
61
|
serializeSeparatorStub("endnote", "0", "continuationSeparator"),
|
|
47
|
-
...entries.map((entry) =>
|
|
62
|
+
...entries.map((entry) =>
|
|
63
|
+
serializeNoteDefinition("endnote", entry, revisionsByNoteId[entry.noteId] ?? [])),
|
|
48
64
|
].join("");
|
|
49
65
|
|
|
50
66
|
return [
|
|
@@ -67,9 +83,11 @@ function serializeSeparatorStub(
|
|
|
67
83
|
function serializeNoteDefinition(
|
|
68
84
|
kind: "footnote" | "endnote",
|
|
69
85
|
definition: FootnoteDefinition,
|
|
86
|
+
revisions: readonly ReviewRevisionRecord[] = [],
|
|
70
87
|
): string {
|
|
71
88
|
const tag = kind === "footnote" ? "w:footnote" : "w:endnote";
|
|
72
|
-
const
|
|
89
|
+
const revisionReadyBlocks = splitStoryBlocksForRuntimeRevisions(definition.blocks, revisions);
|
|
90
|
+
const blocks = revisionReadyBlocks
|
|
73
91
|
.map((block) => {
|
|
74
92
|
if (block.type === "paragraph") {
|
|
75
93
|
return serializeParagraph(block);
|
|
@@ -85,7 +103,17 @@ function serializeNoteDefinition(
|
|
|
85
103
|
.join("");
|
|
86
104
|
|
|
87
105
|
const body = blocks || `<w:p><w:r><w:t></w:t></w:r></w:p>`;
|
|
88
|
-
|
|
106
|
+
const noteXml = `<${tag} w:id="${escapeAttribute(definition.noteId)}">${body}</${tag}>`;
|
|
107
|
+
if (revisions.length === 0) {
|
|
108
|
+
return noteXml;
|
|
109
|
+
}
|
|
110
|
+
const serialized = serializeRuntimeRevisionsIntoStoryXml(noteXml, revisions);
|
|
111
|
+
if (serialized.skippedRevisionIds.length > 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Cannot safely serialize ${serialized.skippedRevisionIds.length} revisions for ${kind} ${definition.noteId}.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return serialized.documentXml;
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
function serializeParagraph(paragraph: ParagraphNode): string {
|
|
@@ -107,8 +135,9 @@ function serializeParagraph(paragraph: ParagraphNode): string {
|
|
|
107
135
|
|
|
108
136
|
function serializeTable(table: TableNode): string {
|
|
109
137
|
let xml = "<w:tbl>";
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
const propertiesXml = serializeTablePropertiesXml(table);
|
|
139
|
+
if (propertiesXml) {
|
|
140
|
+
xml += propertiesXml;
|
|
112
141
|
}
|
|
113
142
|
if (table.gridColumns.length > 0) {
|
|
114
143
|
xml += "<w:tblGrid>";
|
|
@@ -126,8 +155,9 @@ function serializeTable(table: TableNode): string {
|
|
|
126
155
|
|
|
127
156
|
function serializeTableRow(row: TableRowNode): string {
|
|
128
157
|
let xml = "<w:tr>";
|
|
129
|
-
|
|
130
|
-
|
|
158
|
+
const propertiesXml = serializeTableRowPropertiesXml(row);
|
|
159
|
+
if (propertiesXml) {
|
|
160
|
+
xml += propertiesXml;
|
|
131
161
|
}
|
|
132
162
|
for (const cell of row.cells) {
|
|
133
163
|
xml += serializeTableCell(cell);
|
|
@@ -359,41 +389,6 @@ function serializeHyperlinkNode(node: Extract<InlineNode, { type: "hyperlink" }>
|
|
|
359
389
|
return `<w:hyperlink r:id="${escapeAttribute(node.href)}">${childrenXml}</w:hyperlink>`;
|
|
360
390
|
}
|
|
361
391
|
|
|
362
|
-
function wrapPropertiesXml(tagName: "w:trPr" | "w:tcPr", xml: string): string {
|
|
363
|
-
const trimmed = xml.trim();
|
|
364
|
-
if (trimmed.startsWith(`<${tagName}`)) {
|
|
365
|
-
return trimmed;
|
|
366
|
-
}
|
|
367
|
-
return `<${tagName}>${trimmed}</${tagName}>`;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function extractWrappedChildren(tagName: "w:tcPr", xml: string | undefined): string {
|
|
371
|
-
if (!xml) {
|
|
372
|
-
return "";
|
|
373
|
-
}
|
|
374
|
-
const trimmed = xml.trim();
|
|
375
|
-
const wrapped = new RegExp(`^<${tagName}\\b[^>]*>([\\s\\S]*)</${tagName}>$`, "u").exec(trimmed);
|
|
376
|
-
return wrapped?.[1] ?? trimmed;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
392
|
function buildTableCellPropertiesXml(cell: TableCellNode): string {
|
|
380
|
-
|
|
381
|
-
.replace(/<w:gridSpan\b[^>]*\/>/gu, "")
|
|
382
|
-
.replace(/<w:vMerge\b[^>]*\/>/gu, "")
|
|
383
|
-
.trim();
|
|
384
|
-
const parts: string[] = [];
|
|
385
|
-
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
386
|
-
parts.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
387
|
-
}
|
|
388
|
-
if (cell.verticalMerge) {
|
|
389
|
-
parts.push(
|
|
390
|
-
cell.verticalMerge === "restart"
|
|
391
|
-
? `<w:vMerge w:val="restart"/>`
|
|
392
|
-
: `<w:vMerge/>`,
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
if (innerXml.length > 0) {
|
|
396
|
-
parts.push(innerXml);
|
|
397
|
-
}
|
|
398
|
-
return parts.length > 0 ? `<w:tcPr>${parts.join("")}</w:tcPr>` : "";
|
|
393
|
+
return serializeTableCellPropertiesXml(cell);
|
|
399
394
|
}
|
|
@@ -9,6 +9,13 @@ import type {
|
|
|
9
9
|
TableRowNode,
|
|
10
10
|
TextMark,
|
|
11
11
|
} from "../../model/canonical-document.ts";
|
|
12
|
+
import type { RevisionRecord as ReviewRevisionRecord } from "../../review/store/revision-types.ts";
|
|
13
|
+
import { splitStoryBlocksForRuntimeRevisions } from "./split-story-blocks-for-runtime-revisions.ts";
|
|
14
|
+
import {
|
|
15
|
+
serializeTableCellPropertiesXml,
|
|
16
|
+
serializeTablePropertiesXml,
|
|
17
|
+
serializeTableRowPropertiesXml,
|
|
18
|
+
} from "./table-properties-xml.ts";
|
|
12
19
|
|
|
13
20
|
export const WORD_HEADER_CONTENT_TYPE =
|
|
14
21
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
|
|
@@ -40,6 +47,17 @@ export function serializeHeaderXml(header: HeaderDocument): string {
|
|
|
40
47
|
].join("\n");
|
|
41
48
|
}
|
|
42
49
|
|
|
50
|
+
export function serializeHeaderXmlWithRevisions(
|
|
51
|
+
header: HeaderDocument,
|
|
52
|
+
revisions: readonly ReviewRevisionRecord[] = [],
|
|
53
|
+
): string {
|
|
54
|
+
const body = serializeBlocks(splitStoryBlocksForRuntimeRevisions(header.blocks, revisions));
|
|
55
|
+
return [
|
|
56
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
57
|
+
`<w:hdr ${W_NS} ${R_NS} ${EXTENDED_ROOT_ATTRS}>${body}</w:hdr>`,
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
/**
|
|
44
62
|
* Serialize a FooterDocument into a footerN.xml string.
|
|
45
63
|
*/
|
|
@@ -51,6 +69,17 @@ export function serializeFooterXml(footer: FooterDocument): string {
|
|
|
51
69
|
].join("\n");
|
|
52
70
|
}
|
|
53
71
|
|
|
72
|
+
export function serializeFooterXmlWithRevisions(
|
|
73
|
+
footer: FooterDocument,
|
|
74
|
+
revisions: readonly ReviewRevisionRecord[] = [],
|
|
75
|
+
): string {
|
|
76
|
+
const body = serializeBlocks(splitStoryBlocksForRuntimeRevisions(footer.blocks, revisions));
|
|
77
|
+
return [
|
|
78
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
79
|
+
`<w:ftr ${W_NS} ${R_NS} ${EXTENDED_ROOT_ATTRS}>${body}</w:ftr>`,
|
|
80
|
+
].join("\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
// ---- Internal serialization ----
|
|
55
84
|
|
|
56
85
|
function serializeBlocks(
|
|
@@ -96,8 +125,9 @@ function serializeParagraph(paragraph: ParagraphNode): string {
|
|
|
96
125
|
|
|
97
126
|
function serializeTable(table: TableNode): string {
|
|
98
127
|
let xml = "<w:tbl>";
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
const propertiesXml = serializeTablePropertiesXml(table);
|
|
129
|
+
if (propertiesXml) {
|
|
130
|
+
xml += propertiesXml;
|
|
101
131
|
}
|
|
102
132
|
if (table.gridColumns.length > 0) {
|
|
103
133
|
xml += "<w:tblGrid>";
|
|
@@ -115,8 +145,9 @@ function serializeTable(table: TableNode): string {
|
|
|
115
145
|
|
|
116
146
|
function serializeTableRow(row: TableRowNode): string {
|
|
117
147
|
let xml = "<w:tr>";
|
|
118
|
-
|
|
119
|
-
|
|
148
|
+
const propertiesXml = serializeTableRowPropertiesXml(row);
|
|
149
|
+
if (propertiesXml) {
|
|
150
|
+
xml += propertiesXml;
|
|
120
151
|
}
|
|
121
152
|
for (const cell of row.cells) {
|
|
122
153
|
xml += serializeTableCell(cell);
|
|
@@ -332,41 +363,6 @@ function serializeHyperlinkNode(node: Extract<InlineNode, { type: "hyperlink" }>
|
|
|
332
363
|
return `<w:hyperlink r:id="${escapeAttribute(node.href)}">${childrenXml}</w:hyperlink>`;
|
|
333
364
|
}
|
|
334
365
|
|
|
335
|
-
function wrapPropertiesXml(tagName: "w:trPr" | "w:tcPr", xml: string): string {
|
|
336
|
-
const trimmed = xml.trim();
|
|
337
|
-
if (trimmed.startsWith(`<${tagName}`)) {
|
|
338
|
-
return trimmed;
|
|
339
|
-
}
|
|
340
|
-
return `<${tagName}>${trimmed}</${tagName}>`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function extractWrappedChildren(tagName: "w:tcPr", xml: string | undefined): string {
|
|
344
|
-
if (!xml) {
|
|
345
|
-
return "";
|
|
346
|
-
}
|
|
347
|
-
const trimmed = xml.trim();
|
|
348
|
-
const wrapped = new RegExp(`^<${tagName}\\b[^>]*>([\\s\\S]*)</${tagName}>$`, "u").exec(trimmed);
|
|
349
|
-
return wrapped?.[1] ?? trimmed;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
366
|
function buildTableCellPropertiesXml(cell: TableCellNode): string {
|
|
353
|
-
|
|
354
|
-
.replace(/<w:gridSpan\b[^>]*\/>/gu, "")
|
|
355
|
-
.replace(/<w:vMerge\b[^>]*\/>/gu, "")
|
|
356
|
-
.trim();
|
|
357
|
-
const parts: string[] = [];
|
|
358
|
-
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
359
|
-
parts.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
360
|
-
}
|
|
361
|
-
if (cell.verticalMerge) {
|
|
362
|
-
parts.push(
|
|
363
|
-
cell.verticalMerge === "restart"
|
|
364
|
-
? `<w:vMerge w:val="restart"/>`
|
|
365
|
-
: `<w:vMerge/>`,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
if (innerXml.length > 0) {
|
|
369
|
-
parts.push(innerXml);
|
|
370
|
-
}
|
|
371
|
-
return parts.length > 0 ? `<w:tcPr>${parts.join("")}</w:tcPr>` : "";
|
|
367
|
+
return serializeTableCellPropertiesXml(cell);
|
|
372
368
|
}
|
|
@@ -18,6 +18,11 @@ import type { RevisionParagraphBoundary } from "../ooxml/revision-boundaries.ts"
|
|
|
18
18
|
import { getOpaqueFragment } from "../../preservation/store.ts";
|
|
19
19
|
import { retainRelationshipsForFragment } from "../../preservation/relationship-retention.ts";
|
|
20
20
|
import { serializeParagraphNumberingProperties } from "./serialize-numbering.ts";
|
|
21
|
+
import {
|
|
22
|
+
serializeTableCellPropertiesXml,
|
|
23
|
+
serializeTablePropertiesXml,
|
|
24
|
+
serializeTableRowPropertiesXml,
|
|
25
|
+
} from "./table-properties-xml.ts";
|
|
21
26
|
|
|
22
27
|
const HYPERLINK_RELATIONSHIP_TYPE =
|
|
23
28
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
@@ -209,7 +214,7 @@ function serializeTableNode(
|
|
|
209
214
|
table: TableNode,
|
|
210
215
|
state: SerializationState,
|
|
211
216
|
): string {
|
|
212
|
-
const propertiesXml =
|
|
217
|
+
const propertiesXml = buildTablePropertiesXml(table);
|
|
213
218
|
const gridXml =
|
|
214
219
|
table.gridColumns.length > 0
|
|
215
220
|
? `<w:tblGrid>${table.gridColumns
|
|
@@ -218,7 +223,7 @@ function serializeTableNode(
|
|
|
218
223
|
: "";
|
|
219
224
|
const rowsXml = table.rows
|
|
220
225
|
.map((row) => {
|
|
221
|
-
const rowPropertiesXml =
|
|
226
|
+
const rowPropertiesXml = buildTableRowPropertiesXml(row);
|
|
222
227
|
const cellsXml = row.cells
|
|
223
228
|
.map((cell) => serializeTableCellNode(cell, state))
|
|
224
229
|
.join("");
|
|
@@ -232,7 +237,7 @@ function serializeTableCellNode(
|
|
|
232
237
|
cell: TableCellNode,
|
|
233
238
|
state: SerializationState,
|
|
234
239
|
): string {
|
|
235
|
-
const propertiesXml =
|
|
240
|
+
const propertiesXml = buildCellPropertiesXml(cell);
|
|
236
241
|
const blocksXml = cell.children
|
|
237
242
|
.map((child) => serializeBlockNode(child, state))
|
|
238
243
|
.join("");
|
|
@@ -257,47 +262,26 @@ function serializeBlockNode(
|
|
|
257
262
|
case "opaque_block":
|
|
258
263
|
return lookupOpaqueXml(block.fragmentId, state);
|
|
259
264
|
case "section_break":
|
|
260
|
-
|
|
261
|
-
return block.sectionPropertiesXml ?? block.propertiesXml!;
|
|
262
|
-
}
|
|
263
|
-
if (block.sectionProperties) return serializeSectionPropertiesXml(block.sectionProperties);
|
|
264
|
-
return "<w:sectPr/>";
|
|
265
|
+
return serializeNestedSectionBreak(block);
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
function serializeNestedSectionBreak(
|
|
270
|
+
block: Extract<DocumentRootNode["children"][number], { type: "section_break" }>,
|
|
271
|
+
): string {
|
|
272
|
+
const sectionPropertiesXml =
|
|
273
|
+
block.sectionPropertiesXml ??
|
|
274
|
+
block.propertiesXml ??
|
|
275
|
+
(block.sectionProperties ? serializeSectionPropertiesXml(block.sectionProperties) : "<w:sectPr/>");
|
|
276
|
+
return `<w:p><w:pPr>${sectionPropertiesXml}</w:pPr></w:p>`;
|
|
277
|
+
}
|
|
278
|
+
|
|
268
279
|
function buildCellPropertiesXml(cell: TableCellNode): string {
|
|
269
|
-
|
|
270
|
-
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
271
|
-
children.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
272
|
-
}
|
|
273
|
-
if (cell.verticalMerge) {
|
|
274
|
-
children.push(
|
|
275
|
-
cell.verticalMerge === "restart"
|
|
276
|
-
? `<w:vMerge w:val="restart"/>`
|
|
277
|
-
: `<w:vMerge/>`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
return children.length > 0 ? `<w:tcPr>${children.join("")}</w:tcPr>` : "";
|
|
280
|
+
return serializeTableCellPropertiesXml(cell);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
function buildTableRowPropertiesXml(row: TableNode["rows"][number]): string {
|
|
284
|
-
|
|
285
|
-
if (row.gridBefore !== undefined) {
|
|
286
|
-
children.push(`<w:gridBefore w:val="${row.gridBefore}"/>`);
|
|
287
|
-
}
|
|
288
|
-
if (row.widthBefore) {
|
|
289
|
-
children.push(`<w:wBefore w:w="${row.widthBefore.value}" w:type="${row.widthBefore.type}"/>`);
|
|
290
|
-
}
|
|
291
|
-
if (row.gridAfter !== undefined) {
|
|
292
|
-
children.push(`<w:gridAfter w:val="${row.gridAfter}"/>`);
|
|
293
|
-
}
|
|
294
|
-
if (row.widthAfter) {
|
|
295
|
-
children.push(`<w:wAfter w:w="${row.widthAfter.value}" w:type="${row.widthAfter.type}"/>`);
|
|
296
|
-
}
|
|
297
|
-
if (children.length === 0) {
|
|
298
|
-
return "";
|
|
299
|
-
}
|
|
300
|
-
return `<w:trPr>${children.join("")}</w:trPr>`;
|
|
284
|
+
return serializeTableRowPropertiesXml(row);
|
|
301
285
|
}
|
|
302
286
|
|
|
303
287
|
function serializeSdtNode(
|
|
@@ -313,6 +297,9 @@ function serializeCustomXmlNode(
|
|
|
313
297
|
block: CustomXmlNode,
|
|
314
298
|
state: SerializationState,
|
|
315
299
|
): string {
|
|
300
|
+
if (block.rawXml) {
|
|
301
|
+
return block.rawXml;
|
|
302
|
+
}
|
|
316
303
|
const attrs: string[] = [];
|
|
317
304
|
if (block.uri) {
|
|
318
305
|
attrs.push(`w:uri="${escapeAttribute(block.uri)}"`);
|
|
@@ -573,33 +560,7 @@ function buildParagraphPropertiesXml(paragraph: ParagraphNode): string {
|
|
|
573
560
|
}
|
|
574
561
|
|
|
575
562
|
function buildTablePropertiesXml(table: TableNode): string {
|
|
576
|
-
|
|
577
|
-
if (table.styleId) {
|
|
578
|
-
children.push(`<w:tblStyle w:val="${escapeAttribute(table.styleId)}"/>`);
|
|
579
|
-
}
|
|
580
|
-
if (table.tblLook) {
|
|
581
|
-
const attrs: string[] = [];
|
|
582
|
-
if (table.tblLook.val) {
|
|
583
|
-
attrs.push(`w:val="${escapeAttribute(table.tblLook.val)}"`);
|
|
584
|
-
}
|
|
585
|
-
for (const [key, attr] of [
|
|
586
|
-
["firstRow", "w:firstRow"],
|
|
587
|
-
["lastRow", "w:lastRow"],
|
|
588
|
-
["firstColumn", "w:firstColumn"],
|
|
589
|
-
["lastColumn", "w:lastColumn"],
|
|
590
|
-
["noHBand", "w:noHBand"],
|
|
591
|
-
["noVBand", "w:noVBand"],
|
|
592
|
-
] as const) {
|
|
593
|
-
const value = table.tblLook[key];
|
|
594
|
-
if (value !== undefined) {
|
|
595
|
-
attrs.push(`${attr}="${value ? "1" : "0"}"`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (attrs.length > 0) {
|
|
599
|
-
children.push(`<w:tblLook ${attrs.join(" ")}/>`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return children.length > 0 ? `<w:tblPr>${children.join("")}</w:tblPr>` : "";
|
|
563
|
+
return serializeTablePropertiesXml(table);
|
|
603
564
|
}
|
|
604
565
|
|
|
605
566
|
function serializeParagraphBorders(borders: ParagraphNode["borders"]): string {
|
|
@@ -1347,30 +1308,35 @@ function wrapInlineRawXml(rawXml: string): string {
|
|
|
1347
1308
|
}
|
|
1348
1309
|
|
|
1349
1310
|
function documentNeedsW14Namespace(content: DocumentRootNode): boolean {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1311
|
+
return content.children.some(blockNeedsW14Namespace);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function blockNeedsW14Namespace(block: DocumentRootNode["children"][number]): boolean {
|
|
1315
|
+
switch (block.type) {
|
|
1316
|
+
case "paragraph":
|
|
1317
|
+
return block.children.some(inlineNodeNeedsW14Namespace);
|
|
1318
|
+
case "table":
|
|
1319
|
+
return block.rows.some((row) =>
|
|
1320
|
+
row.cells.some((cell) => cell.children.some(blockNeedsW14Namespace)),
|
|
1321
|
+
);
|
|
1322
|
+
case "sdt":
|
|
1323
|
+
return Boolean(block.properties.checkbox) || block.children.some(blockNeedsW14Namespace);
|
|
1324
|
+
case "custom_xml":
|
|
1325
|
+
return block.children.some(blockNeedsW14Namespace);
|
|
1326
|
+
default:
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function inlineNodeNeedsW14Namespace(node: InlineNode): boolean {
|
|
1332
|
+
switch (node.type) {
|
|
1333
|
+
case "text":
|
|
1334
|
+
case "symbol":
|
|
1335
|
+
return Boolean(node.marks?.some((mark) => mark.type === "textFill"));
|
|
1336
|
+
case "hyperlink":
|
|
1337
|
+
case "field":
|
|
1338
|
+
return node.children.some(inlineNodeNeedsW14Namespace);
|
|
1339
|
+
default:
|
|
1340
|
+
return false;
|
|
1374
1341
|
}
|
|
1375
|
-
return false;
|
|
1376
1342
|
}
|
|
@@ -65,17 +65,56 @@ function serializeAbstractDefinition(definition: NumberingCatalog["abstractDefin
|
|
|
65
65
|
return `<w:abstractNum w:abstractNumId="${abstractNumId}">${levels}</w:abstractNum>`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function serializeLevel(
|
|
68
|
+
function serializeLevel(
|
|
69
|
+
level: NumberingCatalog["abstractDefinitions"][string]["levels"][number],
|
|
70
|
+
serializedLevel = level.level,
|
|
71
|
+
): string {
|
|
69
72
|
const start = level.startAt !== undefined ? `<w:start w:val="${level.startAt}"/>` : "";
|
|
70
73
|
const paragraphStyle = level.paragraphStyleId
|
|
71
74
|
? `<w:pStyle w:val="${escapeAttribute(level.paragraphStyleId)}"/>`
|
|
72
75
|
: "";
|
|
73
76
|
const isLegal = level.isLegalNumbering ? "<w:isLgl/>" : "";
|
|
74
77
|
const suffix = level.suffix ? `<w:suff w:val="${escapeAttribute(level.suffix)}"/>` : "";
|
|
78
|
+
const justification = level.paragraphGeometry?.justification
|
|
79
|
+
? `<w:lvlJc w:val="${escapeAttribute(level.paragraphGeometry.justification)}"/>`
|
|
80
|
+
: "";
|
|
81
|
+
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
75
82
|
|
|
76
|
-
return `<w:lvl w:ilvl="${
|
|
83
|
+
return `<w:lvl w:ilvl="${serializedLevel}">${start}<w:numFmt w:val="${escapeAttribute(
|
|
77
84
|
level.format,
|
|
78
|
-
)}"/><w:lvlText w:val="${escapeAttribute(level.text)}"/>${paragraphStyle}${isLegal}${suffix}</w:lvl>`;
|
|
85
|
+
)}"/><w:lvlText w:val="${escapeAttribute(level.text)}"/>${paragraphStyle}${isLegal}${suffix}${justification}${paragraphProperties}</w:lvl>`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function serializeLevelOverride(
|
|
89
|
+
level: NumberingCatalog["instances"][string]["overrides"][number]["levelDefinition"],
|
|
90
|
+
serializedLevel: number,
|
|
91
|
+
): string {
|
|
92
|
+
if (!level) {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const start = level.startAt !== undefined ? `<w:start w:val="${level.startAt}"/>` : "";
|
|
97
|
+
const format = level.format ? `<w:numFmt w:val="${escapeAttribute(level.format)}"/>` : "";
|
|
98
|
+
const text = level.text !== undefined
|
|
99
|
+
? `<w:lvlText w:val="${escapeAttribute(level.text)}"/>`
|
|
100
|
+
: "";
|
|
101
|
+
const paragraphStyle = level.paragraphStyleId
|
|
102
|
+
? `<w:pStyle w:val="${escapeAttribute(level.paragraphStyleId)}"/>`
|
|
103
|
+
: "";
|
|
104
|
+
const isLegal =
|
|
105
|
+
level.isLegalNumbering === undefined
|
|
106
|
+
? ""
|
|
107
|
+
: level.isLegalNumbering
|
|
108
|
+
? "<w:isLgl/>"
|
|
109
|
+
: `<w:isLgl w:val="0"/>`;
|
|
110
|
+
const suffix = level.suffix ? `<w:suff w:val="${escapeAttribute(level.suffix)}"/>` : "";
|
|
111
|
+
const justification = level.paragraphGeometry?.justification
|
|
112
|
+
? `<w:lvlJc w:val="${escapeAttribute(level.paragraphGeometry.justification)}"/>`
|
|
113
|
+
: "";
|
|
114
|
+
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
115
|
+
const body = `${start}${format}${text}${paragraphStyle}${isLegal}${suffix}${justification}${paragraphProperties}`;
|
|
116
|
+
|
|
117
|
+
return body.length > 0 ? `<w:lvl w:ilvl="${serializedLevel}">${body}</w:lvl>` : "";
|
|
79
118
|
}
|
|
80
119
|
|
|
81
120
|
function serializeInstance(instance: NumberingCatalog["instances"][string]): string {
|
|
@@ -94,7 +133,68 @@ function serializeInstance(instance: NumberingCatalog["instances"][string]): str
|
|
|
94
133
|
function serializeOverride(override: NumberingCatalog["instances"][string]["overrides"][number]): string {
|
|
95
134
|
const startOverride =
|
|
96
135
|
override.startAt !== undefined ? `<w:startOverride w:val="${override.startAt}"/>` : "";
|
|
97
|
-
|
|
136
|
+
const levelDefinition = override.levelDefinition
|
|
137
|
+
? serializeLevelOverride(override.levelDefinition, override.level)
|
|
138
|
+
: "";
|
|
139
|
+
return `<w:lvlOverride w:ilvl="${override.level}">${startOverride}${levelDefinition}</w:lvlOverride>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function serializeLevelParagraphGeometry(
|
|
143
|
+
paragraphGeometry: NumberingCatalog["abstractDefinitions"][string]["levels"][number]["paragraphGeometry"],
|
|
144
|
+
): string {
|
|
145
|
+
if (!paragraphGeometry) {
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const children: string[] = [];
|
|
150
|
+
if (paragraphGeometry.spacing) {
|
|
151
|
+
const attrs: string[] = [];
|
|
152
|
+
if (paragraphGeometry.spacing.before !== undefined) {
|
|
153
|
+
attrs.push(`w:before="${paragraphGeometry.spacing.before}"`);
|
|
154
|
+
}
|
|
155
|
+
if (paragraphGeometry.spacing.after !== undefined) {
|
|
156
|
+
attrs.push(`w:after="${paragraphGeometry.spacing.after}"`);
|
|
157
|
+
}
|
|
158
|
+
if (paragraphGeometry.spacing.line !== undefined) {
|
|
159
|
+
attrs.push(`w:line="${paragraphGeometry.spacing.line}"`);
|
|
160
|
+
}
|
|
161
|
+
if (paragraphGeometry.spacing.lineRule !== undefined) {
|
|
162
|
+
attrs.push(`w:lineRule="${paragraphGeometry.spacing.lineRule}"`);
|
|
163
|
+
}
|
|
164
|
+
if (attrs.length > 0) {
|
|
165
|
+
children.push(`<w:spacing ${attrs.join(" ")}/>`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (paragraphGeometry.indentation) {
|
|
170
|
+
const attrs: string[] = [];
|
|
171
|
+
if (paragraphGeometry.indentation.left !== undefined) {
|
|
172
|
+
attrs.push(`w:left="${paragraphGeometry.indentation.left}"`);
|
|
173
|
+
}
|
|
174
|
+
if (paragraphGeometry.indentation.right !== undefined) {
|
|
175
|
+
attrs.push(`w:right="${paragraphGeometry.indentation.right}"`);
|
|
176
|
+
}
|
|
177
|
+
if (paragraphGeometry.indentation.firstLine !== undefined) {
|
|
178
|
+
attrs.push(`w:firstLine="${paragraphGeometry.indentation.firstLine}"`);
|
|
179
|
+
}
|
|
180
|
+
if (paragraphGeometry.indentation.hanging !== undefined) {
|
|
181
|
+
attrs.push(`w:hanging="${paragraphGeometry.indentation.hanging}"`);
|
|
182
|
+
}
|
|
183
|
+
if (attrs.length > 0) {
|
|
184
|
+
children.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (paragraphGeometry.tabStops && paragraphGeometry.tabStops.length > 0) {
|
|
189
|
+
const tabsXml = paragraphGeometry.tabStops.map((tab) => {
|
|
190
|
+
const leader = tab.leader === "middleDot" ? "middledot" : tab.leader;
|
|
191
|
+
const leaderAttr = leader ? ` w:leader="${leader}"` : "";
|
|
192
|
+
return `<w:tab w:val="${tab.align}" w:pos="${tab.position}"${leaderAttr}/>`;
|
|
193
|
+
}).join("");
|
|
194
|
+
children.push(`<w:tabs>${tabsXml}</w:tabs>`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return children.length > 0 ? `<w:pPr>${children.join("")}</w:pPr>` : "";
|
|
98
198
|
}
|
|
99
199
|
|
|
100
200
|
function compareSerializedIds(left: string, right: string): number {
|