@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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
interface TableWidthLike {
|
|
2
|
+
value: number;
|
|
3
|
+
type: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface BorderSpecLike {
|
|
7
|
+
value?: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
space?: number;
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface TableBordersLike {
|
|
14
|
+
top?: BorderSpecLike;
|
|
15
|
+
left?: BorderSpecLike;
|
|
16
|
+
bottom?: BorderSpecLike;
|
|
17
|
+
right?: BorderSpecLike;
|
|
18
|
+
insideH?: BorderSpecLike;
|
|
19
|
+
insideV?: BorderSpecLike;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TableCellMarginsLike {
|
|
23
|
+
top?: number;
|
|
24
|
+
left?: number;
|
|
25
|
+
bottom?: number;
|
|
26
|
+
right?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TableLookLike {
|
|
30
|
+
val?: string;
|
|
31
|
+
firstRow?: boolean;
|
|
32
|
+
lastRow?: boolean;
|
|
33
|
+
firstColumn?: boolean;
|
|
34
|
+
lastColumn?: boolean;
|
|
35
|
+
noHBand?: boolean;
|
|
36
|
+
noVBand?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CellShadingLike {
|
|
40
|
+
val?: string;
|
|
41
|
+
color?: string;
|
|
42
|
+
fill?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface TablePropertiesLike {
|
|
46
|
+
propertiesXml?: string;
|
|
47
|
+
styleId?: string;
|
|
48
|
+
width?: TableWidthLike;
|
|
49
|
+
alignment?: string;
|
|
50
|
+
borders?: TableBordersLike;
|
|
51
|
+
cellMargins?: TableCellMarginsLike;
|
|
52
|
+
tblLook?: TableLookLike;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface TableRowPropertiesLike {
|
|
56
|
+
propertiesXml?: string;
|
|
57
|
+
gridBefore?: number;
|
|
58
|
+
widthBefore?: TableWidthLike;
|
|
59
|
+
gridAfter?: number;
|
|
60
|
+
widthAfter?: TableWidthLike;
|
|
61
|
+
height?: number;
|
|
62
|
+
heightRule?: string;
|
|
63
|
+
isHeader?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface TableCellPropertiesLike {
|
|
67
|
+
propertiesXml?: string;
|
|
68
|
+
width?: TableWidthLike;
|
|
69
|
+
gridSpan?: number;
|
|
70
|
+
verticalMerge?: "restart" | "continue";
|
|
71
|
+
borders?: TableBordersLike;
|
|
72
|
+
shading?: CellShadingLike;
|
|
73
|
+
verticalAlign?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface PropertyStripSpec {
|
|
77
|
+
pairedTags?: string[];
|
|
78
|
+
selfClosingTags?: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const TABLE_PROPERTY_STRIP_SPEC: PropertyStripSpec = {
|
|
82
|
+
pairedTags: ["w:tblBorders", "w:tblCellMar"],
|
|
83
|
+
selfClosingTags: ["w:tblStyle", "w:tblW", "w:jc", "w:tblLook"],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ROW_PROPERTY_STRIP_SPEC: PropertyStripSpec = {
|
|
87
|
+
selfClosingTags: ["w:gridBefore", "w:wBefore", "w:gridAfter", "w:wAfter", "w:trHeight", "w:tblHeader"],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const CELL_PROPERTY_STRIP_SPEC: PropertyStripSpec = {
|
|
91
|
+
pairedTags: ["w:tcBorders"],
|
|
92
|
+
selfClosingTags: ["w:tcW", "w:gridSpan", "w:vMerge", "w:shd", "w:vAlign"],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export function serializeTablePropertiesXml(table: TablePropertiesLike): string {
|
|
96
|
+
return mergePropertiesXml(
|
|
97
|
+
"w:tblPr",
|
|
98
|
+
table.propertiesXml,
|
|
99
|
+
buildTablePropertiesInnerXml(table),
|
|
100
|
+
TABLE_PROPERTY_STRIP_SPEC,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function serializeTableRowPropertiesXml(row: TableRowPropertiesLike): string {
|
|
105
|
+
return mergePropertiesXml(
|
|
106
|
+
"w:trPr",
|
|
107
|
+
row.propertiesXml,
|
|
108
|
+
buildTableRowPropertiesInnerXml(row),
|
|
109
|
+
ROW_PROPERTY_STRIP_SPEC,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function serializeTableCellPropertiesXml(cell: TableCellPropertiesLike): string {
|
|
114
|
+
return mergePropertiesXml(
|
|
115
|
+
"w:tcPr",
|
|
116
|
+
cell.propertiesXml,
|
|
117
|
+
buildTableCellPropertiesInnerXml(cell),
|
|
118
|
+
CELL_PROPERTY_STRIP_SPEC,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mergePropertiesXml(
|
|
123
|
+
tagName: "w:tblPr" | "w:trPr" | "w:tcPr",
|
|
124
|
+
existingXml: string | undefined,
|
|
125
|
+
supportedInnerXml: string,
|
|
126
|
+
stripSpec: PropertyStripSpec,
|
|
127
|
+
): string {
|
|
128
|
+
const preservedInnerXml = stripKnownProperties(
|
|
129
|
+
extractWrappedChildren(tagName, existingXml),
|
|
130
|
+
stripSpec,
|
|
131
|
+
);
|
|
132
|
+
const mergedInnerXml = [supportedInnerXml, preservedInnerXml]
|
|
133
|
+
.filter((part) => part.length > 0)
|
|
134
|
+
.join("");
|
|
135
|
+
return mergedInnerXml.length > 0 ? `<${tagName}>${mergedInnerXml}</${tagName}>` : "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function extractWrappedChildren(
|
|
139
|
+
tagName: "w:tblPr" | "w:trPr" | "w:tcPr",
|
|
140
|
+
xml: string | undefined,
|
|
141
|
+
): string {
|
|
142
|
+
if (!xml) {
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
const trimmed = xml.trim();
|
|
146
|
+
const wrapped = new RegExp(`^<${tagName}\\b[^>]*>([\\s\\S]*)</${tagName}>$`, "u").exec(trimmed);
|
|
147
|
+
return wrapped?.[1] ?? trimmed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function stripKnownProperties(xml: string, stripSpec: PropertyStripSpec): string {
|
|
151
|
+
let nextXml = xml;
|
|
152
|
+
for (const tagName of stripSpec.pairedTags ?? []) {
|
|
153
|
+
nextXml = nextXml.replace(
|
|
154
|
+
new RegExp(`<${tagName}\\b[^>]*>[\\s\\S]*?</${tagName}>`, "gu"),
|
|
155
|
+
"",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
for (const tagName of stripSpec.selfClosingTags ?? []) {
|
|
159
|
+
nextXml = nextXml.replace(new RegExp(`<${tagName}\\b[^>]*/>`, "gu"), "");
|
|
160
|
+
}
|
|
161
|
+
return nextXml.trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function buildTablePropertiesInnerXml(table: TablePropertiesLike): string {
|
|
165
|
+
const children: string[] = [];
|
|
166
|
+
if (table.styleId) {
|
|
167
|
+
children.push(`<w:tblStyle w:val="${escapeAttribute(table.styleId)}"/>`);
|
|
168
|
+
}
|
|
169
|
+
if (table.width) {
|
|
170
|
+
children.push(serializeWidth("tblW", table.width));
|
|
171
|
+
}
|
|
172
|
+
if (table.alignment) {
|
|
173
|
+
children.push(`<w:jc w:val="${escapeAttribute(table.alignment)}"/>`);
|
|
174
|
+
}
|
|
175
|
+
if (table.borders) {
|
|
176
|
+
const bordersXml = serializeBorders(table.borders);
|
|
177
|
+
if (bordersXml) {
|
|
178
|
+
children.push(`<w:tblBorders>${bordersXml}</w:tblBorders>`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (table.cellMargins) {
|
|
182
|
+
const marginsXml = serializeTableCellMargins(table.cellMargins);
|
|
183
|
+
if (marginsXml) {
|
|
184
|
+
children.push(`<w:tblCellMar>${marginsXml}</w:tblCellMar>`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (table.tblLook) {
|
|
188
|
+
const tblLookXml = serializeTableLook(table.tblLook);
|
|
189
|
+
if (tblLookXml) {
|
|
190
|
+
children.push(tblLookXml);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return children.join("");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildTableRowPropertiesInnerXml(row: TableRowPropertiesLike): string {
|
|
197
|
+
const children: string[] = [];
|
|
198
|
+
if (row.gridBefore !== undefined) {
|
|
199
|
+
children.push(`<w:gridBefore w:val="${row.gridBefore}"/>`);
|
|
200
|
+
}
|
|
201
|
+
if (row.widthBefore) {
|
|
202
|
+
children.push(`<w:wBefore w:w="${row.widthBefore.value}" w:type="${row.widthBefore.type}"/>`);
|
|
203
|
+
}
|
|
204
|
+
if (row.gridAfter !== undefined) {
|
|
205
|
+
children.push(`<w:gridAfter w:val="${row.gridAfter}"/>`);
|
|
206
|
+
}
|
|
207
|
+
if (row.widthAfter) {
|
|
208
|
+
children.push(`<w:wAfter w:w="${row.widthAfter.value}" w:type="${row.widthAfter.type}"/>`);
|
|
209
|
+
}
|
|
210
|
+
if (row.height !== undefined) {
|
|
211
|
+
const hRuleAttr = row.heightRule ? ` w:hRule="${escapeAttribute(row.heightRule)}"` : "";
|
|
212
|
+
children.push(`<w:trHeight w:val="${row.height}"${hRuleAttr}/>`);
|
|
213
|
+
}
|
|
214
|
+
if (row.isHeader !== undefined) {
|
|
215
|
+
children.push(row.isHeader ? `<w:tblHeader/>` : `<w:tblHeader w:val="0"/>`);
|
|
216
|
+
}
|
|
217
|
+
return children.join("");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function buildTableCellPropertiesInnerXml(cell: TableCellPropertiesLike): string {
|
|
221
|
+
const children: string[] = [];
|
|
222
|
+
if (cell.width) {
|
|
223
|
+
children.push(serializeWidth("tcW", cell.width));
|
|
224
|
+
}
|
|
225
|
+
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
226
|
+
children.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
227
|
+
}
|
|
228
|
+
if (cell.verticalMerge) {
|
|
229
|
+
children.push(
|
|
230
|
+
cell.verticalMerge === "restart"
|
|
231
|
+
? `<w:vMerge w:val="restart"/>`
|
|
232
|
+
: `<w:vMerge/>`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (cell.borders) {
|
|
236
|
+
const bordersXml = serializeBorders(cell.borders);
|
|
237
|
+
if (bordersXml) {
|
|
238
|
+
children.push(`<w:tcBorders>${bordersXml}</w:tcBorders>`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (cell.shading) {
|
|
242
|
+
const shadingXml = serializeCellShading(cell.shading);
|
|
243
|
+
if (shadingXml) {
|
|
244
|
+
children.push(shadingXml);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (cell.verticalAlign) {
|
|
248
|
+
children.push(`<w:vAlign w:val="${escapeAttribute(cell.verticalAlign)}"/>`);
|
|
249
|
+
}
|
|
250
|
+
return children.join("");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function serializeWidth(elementName: "tblW" | "tcW", width: TableWidthLike): string {
|
|
254
|
+
return `<w:${elementName} w:w="${width.value}" w:type="${escapeAttribute(width.type)}"/>`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function serializeBorders(borders: TableBordersLike): string {
|
|
258
|
+
const sides = ["top", "left", "bottom", "right", "insideH", "insideV"] as const;
|
|
259
|
+
return sides
|
|
260
|
+
.filter((side) => borders[side] !== undefined)
|
|
261
|
+
.map((side) => serializeBorderSpec(side, borders[side]!))
|
|
262
|
+
.join("");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function serializeBorderSpec(elementName: string, border: BorderSpecLike): string {
|
|
266
|
+
const attrs: string[] = [];
|
|
267
|
+
if (border.value) attrs.push(`w:val="${escapeAttribute(border.value)}"`);
|
|
268
|
+
if (border.size !== undefined) attrs.push(`w:sz="${border.size}"`);
|
|
269
|
+
if (border.space !== undefined) attrs.push(`w:space="${border.space}"`);
|
|
270
|
+
if (border.color) attrs.push(`w:color="${escapeAttribute(border.color)}"`);
|
|
271
|
+
return attrs.length > 0 ? `<w:${elementName} ${attrs.join(" ")}/>` : "";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function serializeTableCellMargins(margins: TableCellMarginsLike): string {
|
|
275
|
+
const parts: string[] = [];
|
|
276
|
+
if (margins.top !== undefined) parts.push(`<w:top w:w="${margins.top}" w:type="dxa"/>`);
|
|
277
|
+
if (margins.left !== undefined) parts.push(`<w:left w:w="${margins.left}" w:type="dxa"/>`);
|
|
278
|
+
if (margins.bottom !== undefined) parts.push(`<w:bottom w:w="${margins.bottom}" w:type="dxa"/>`);
|
|
279
|
+
if (margins.right !== undefined) parts.push(`<w:right w:w="${margins.right}" w:type="dxa"/>`);
|
|
280
|
+
return parts.join("");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function serializeTableLook(tblLook: TableLookLike): string {
|
|
284
|
+
const attrs: string[] = [];
|
|
285
|
+
if (tblLook.val) {
|
|
286
|
+
attrs.push(`w:val="${escapeAttribute(tblLook.val)}"`);
|
|
287
|
+
}
|
|
288
|
+
for (const [key, attr] of [
|
|
289
|
+
["firstRow", "w:firstRow"],
|
|
290
|
+
["lastRow", "w:lastRow"],
|
|
291
|
+
["firstColumn", "w:firstColumn"],
|
|
292
|
+
["lastColumn", "w:lastColumn"],
|
|
293
|
+
["noHBand", "w:noHBand"],
|
|
294
|
+
["noVBand", "w:noVBand"],
|
|
295
|
+
] as const) {
|
|
296
|
+
const value = tblLook[key];
|
|
297
|
+
if (value !== undefined) {
|
|
298
|
+
attrs.push(`${attr}="${value ? "1" : "0"}"`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return attrs.length > 0 ? `<w:tblLook ${attrs.join(" ")}/>` : "";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function serializeCellShading(shading: CellShadingLike): string {
|
|
305
|
+
const attrs: string[] = [];
|
|
306
|
+
if (shading.val) attrs.push(`w:val="${escapeAttribute(shading.val)}"`);
|
|
307
|
+
if (shading.color) attrs.push(`w:color="${escapeAttribute(shading.color)}"`);
|
|
308
|
+
if (shading.fill) attrs.push(`w:fill="${escapeAttribute(shading.fill)}"`);
|
|
309
|
+
return attrs.length > 0 ? `<w:shd ${attrs.join(" ")}/>` : "";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function escapeAttribute(value: string): string {
|
|
313
|
+
return value
|
|
314
|
+
.replace(/&/gu, "&")
|
|
315
|
+
.replace(/"/gu, """)
|
|
316
|
+
.replace(/</gu, "<")
|
|
317
|
+
.replace(/>/gu, ">");
|
|
318
|
+
}
|
|
@@ -199,6 +199,11 @@ function normalizeTable(
|
|
|
199
199
|
...(table.propertiesXml ? { propertiesXml: table.propertiesXml } : {}),
|
|
200
200
|
gridColumns: table.gridColumns,
|
|
201
201
|
rows,
|
|
202
|
+
...(table.width ? { width: table.width } : {}),
|
|
203
|
+
...(table.alignment ? { alignment: table.alignment } : {}),
|
|
204
|
+
...(table.borders ? { borders: table.borders } : {}),
|
|
205
|
+
...(table.cellMargins ? { cellMargins: table.cellMargins } : {}),
|
|
206
|
+
...(table.tblLook ? { tblLook: table.tblLook } : {}),
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
|
|
@@ -215,6 +220,9 @@ function normalizeTableRow(
|
|
|
215
220
|
...(row.widthBefore ? { widthBefore: row.widthBefore } : {}),
|
|
216
221
|
...(row.gridAfter !== undefined ? { gridAfter: row.gridAfter } : {}),
|
|
217
222
|
...(row.widthAfter ? { widthAfter: row.widthAfter } : {}),
|
|
223
|
+
...(row.height !== undefined ? { height: row.height } : {}),
|
|
224
|
+
...(row.heightRule ? { heightRule: row.heightRule } : {}),
|
|
225
|
+
...(row.isHeader !== undefined ? { isHeader: row.isHeader } : {}),
|
|
218
226
|
cells,
|
|
219
227
|
};
|
|
220
228
|
}
|
|
@@ -237,6 +245,10 @@ function normalizeTableCell(
|
|
|
237
245
|
...(cell.propertiesXml ? { propertiesXml: cell.propertiesXml } : {}),
|
|
238
246
|
...(cell.gridSpan ? { gridSpan: cell.gridSpan } : {}),
|
|
239
247
|
...(cell.verticalMerge ? { verticalMerge: cell.verticalMerge } : {}),
|
|
248
|
+
...(cell.width ? { width: cell.width } : {}),
|
|
249
|
+
...(cell.borders ? { borders: cell.borders } : {}),
|
|
250
|
+
...(cell.shading ? { shading: cell.shading } : {}),
|
|
251
|
+
...(cell.verticalAlign ? { verticalAlign: cell.verticalAlign } : {}),
|
|
240
252
|
children,
|
|
241
253
|
};
|
|
242
254
|
}
|
|
@@ -262,6 +274,7 @@ function normalizeCustomXml(
|
|
|
262
274
|
type: "custom_xml",
|
|
263
275
|
...(block.uri ? { uri: block.uri } : {}),
|
|
264
276
|
...(block.element ? { element: block.element } : {}),
|
|
277
|
+
...(block.rawXml ? { rawXml: block.rawXml } : {}),
|
|
265
278
|
children: block.children.flatMap((child) => normalizeBlocks(child, state, packagePartName)),
|
|
266
279
|
};
|
|
267
280
|
}
|
|
@@ -321,7 +334,7 @@ function normalizeInlineChildren(
|
|
|
321
334
|
...(node.marks && node.marks.length > 0 ? { marks: node.marks } : {}),
|
|
322
335
|
});
|
|
323
336
|
}
|
|
324
|
-
state.cursor += node.text.length;
|
|
337
|
+
state.cursor += Array.from(node.text).length;
|
|
325
338
|
break;
|
|
326
339
|
}
|
|
327
340
|
case "tab":
|
|
@@ -431,6 +444,7 @@ function normalizeInlineChildren(
|
|
|
431
444
|
? normalizeInlineChildren(node.children, state, packagePartName)
|
|
432
445
|
: normalizeFieldContentXml(node.contentXml ?? "");
|
|
433
446
|
state.cursor = cursorBeforeField;
|
|
447
|
+
const renderedLength = measureNormalizedInlineDisplayLength(fieldChildren);
|
|
434
448
|
normalized.push({
|
|
435
449
|
type: "field",
|
|
436
450
|
fieldType: node.fieldType,
|
|
@@ -440,7 +454,7 @@ function normalizeInlineChildren(
|
|
|
440
454
|
...(classification.target ? { fieldTarget: classification.target } : {}),
|
|
441
455
|
refreshStatus: classification.supported ? "stale" : "preserve-only",
|
|
442
456
|
});
|
|
443
|
-
state.cursor +=
|
|
457
|
+
state.cursor += renderedLength > 0 ? renderedLength : 1;
|
|
444
458
|
break;
|
|
445
459
|
}
|
|
446
460
|
}
|
|
@@ -538,7 +552,7 @@ function normalizeHyperlink(node: ParsedHyperlinkNode): {
|
|
|
538
552
|
function measureHyperlink(node: ParsedHyperlinkNode): number {
|
|
539
553
|
return node.children.reduce<number>((size, child) => {
|
|
540
554
|
if (child.type === "text") {
|
|
541
|
-
return size + child.text.length;
|
|
555
|
+
return size + Array.from(child.text).length;
|
|
542
556
|
}
|
|
543
557
|
return size + 1;
|
|
544
558
|
}, 0);
|
|
@@ -637,3 +651,20 @@ function normalizeFieldContentXml(contentXml: string | undefined): InlineNode[]
|
|
|
637
651
|
|
|
638
652
|
return children;
|
|
639
653
|
}
|
|
654
|
+
|
|
655
|
+
function measureNormalizedInlineDisplayLength(nodes: InlineNode[]): number {
|
|
656
|
+
return nodes.reduce((total, node) => {
|
|
657
|
+
switch (node.type) {
|
|
658
|
+
case "text":
|
|
659
|
+
return total + Array.from(node.text).length;
|
|
660
|
+
case "hyperlink":
|
|
661
|
+
case "field":
|
|
662
|
+
return total + measureNormalizedInlineDisplayLength(node.children);
|
|
663
|
+
case "bookmark_start":
|
|
664
|
+
case "bookmark_end":
|
|
665
|
+
return total;
|
|
666
|
+
default:
|
|
667
|
+
return total + 1;
|
|
668
|
+
}
|
|
669
|
+
}, 0);
|
|
670
|
+
}
|
|
@@ -164,6 +164,9 @@ export function parseCommentsFromOoxml(
|
|
|
164
164
|
source: "import",
|
|
165
165
|
rootOoxmlCommentId: rootDefinition.commentId,
|
|
166
166
|
rootParaId: rootDefinition.paraId,
|
|
167
|
+
detachedReason: "incomplete-markers",
|
|
168
|
+
actionabilityNote:
|
|
169
|
+
"Re-anchoring requires the host to supply a valid range. The comment body and thread are preserved for display.",
|
|
167
170
|
},
|
|
168
171
|
}),
|
|
169
172
|
);
|
|
@@ -194,6 +197,9 @@ export function parseCommentsFromOoxml(
|
|
|
194
197
|
source: "import",
|
|
195
198
|
rootOoxmlCommentId: rootDefinition.commentId,
|
|
196
199
|
rootParaId: rootDefinition.paraId,
|
|
200
|
+
detachedReason: "multi-paragraph",
|
|
201
|
+
actionabilityNote:
|
|
202
|
+
"The comment thread and body are preserved. Operators see the thread in the sidebar but cannot navigate to an inline highlight.",
|
|
197
203
|
},
|
|
198
204
|
}),
|
|
199
205
|
);
|
|
@@ -11,6 +11,22 @@ import type {
|
|
|
11
11
|
TableRowNode,
|
|
12
12
|
TextMark,
|
|
13
13
|
} from "../../model/canonical-document.ts";
|
|
14
|
+
import {
|
|
15
|
+
readCellBorders,
|
|
16
|
+
readCellShading,
|
|
17
|
+
readCellVerticalAlign,
|
|
18
|
+
readCellWidth,
|
|
19
|
+
readGridColumns as readSharedGridColumns,
|
|
20
|
+
readRowHeight,
|
|
21
|
+
readRowHeightRule,
|
|
22
|
+
readRowIsHeader,
|
|
23
|
+
readTableAlignment,
|
|
24
|
+
readTableBorders,
|
|
25
|
+
readTableCellMargins,
|
|
26
|
+
readTableLook,
|
|
27
|
+
readTableStyleId,
|
|
28
|
+
readTableWidth,
|
|
29
|
+
} from "./parse-tables.ts";
|
|
14
30
|
|
|
15
31
|
// ---- XML node types (inline, no external dep) ----
|
|
16
32
|
|
|
@@ -19,11 +35,15 @@ interface XmlElementNode {
|
|
|
19
35
|
name: string;
|
|
20
36
|
attributes: Record<string, string>;
|
|
21
37
|
children: XmlNode[];
|
|
38
|
+
start: number;
|
|
39
|
+
end: number;
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
interface XmlTextNode {
|
|
25
43
|
type: "text";
|
|
26
44
|
text: string;
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
type XmlNode = XmlElementNode | XmlTextNode;
|
|
@@ -656,6 +676,11 @@ function parseSimpleTableElement(tblElement: XmlElementNode): TableNode {
|
|
|
656
676
|
const rows: TableRowNode[] = [];
|
|
657
677
|
let propertiesXml: string | undefined;
|
|
658
678
|
let styleId: string | undefined;
|
|
679
|
+
let width: TableNode["width"];
|
|
680
|
+
let alignment: TableNode["alignment"];
|
|
681
|
+
let borders: TableNode["borders"];
|
|
682
|
+
let cellMargins: TableNode["cellMargins"];
|
|
683
|
+
let tblLook: TableNode["tblLook"];
|
|
659
684
|
|
|
660
685
|
for (const child of tblElement.children) {
|
|
661
686
|
if (child.type !== "element") continue;
|
|
@@ -663,8 +688,12 @@ function parseSimpleTableElement(tblElement: XmlElementNode): TableNode {
|
|
|
663
688
|
|
|
664
689
|
if (name === "tblPr") {
|
|
665
690
|
propertiesXml = serializeElementToXml(child);
|
|
666
|
-
|
|
667
|
-
|
|
691
|
+
styleId = readTableStyleId(child);
|
|
692
|
+
width = readTableWidth(child);
|
|
693
|
+
alignment = readTableAlignment(child);
|
|
694
|
+
borders = readTableBorders(child);
|
|
695
|
+
cellMargins = readTableCellMargins(child);
|
|
696
|
+
tblLook = readTableLook(child);
|
|
668
697
|
} else if (name === "tblGrid") {
|
|
669
698
|
gridColumns = readGridColumns(child);
|
|
670
699
|
} else if (name === "tr") {
|
|
@@ -678,24 +707,24 @@ function parseSimpleTableElement(tblElement: XmlElementNode): TableNode {
|
|
|
678
707
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
679
708
|
gridColumns,
|
|
680
709
|
rows,
|
|
710
|
+
...(width ? { width } : {}),
|
|
711
|
+
...(alignment ? { alignment } : {}),
|
|
712
|
+
...(borders ? { borders } : {}),
|
|
713
|
+
...(cellMargins ? { cellMargins } : {}),
|
|
714
|
+
...(tblLook ? { tblLook } : {}),
|
|
681
715
|
};
|
|
682
716
|
}
|
|
683
717
|
|
|
684
718
|
function readGridColumns(tblGrid: XmlElementNode): number[] {
|
|
685
|
-
|
|
686
|
-
for (const child of tblGrid.children) {
|
|
687
|
-
if (child.type !== "element") continue;
|
|
688
|
-
if (localName(child.name) === "gridCol") {
|
|
689
|
-
const w = child.attributes["w:w"] ?? child.attributes.w ?? "0";
|
|
690
|
-
columns.push(Number.parseInt(w, 10) || 0);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return columns;
|
|
719
|
+
return readSharedGridColumns(tblGrid);
|
|
694
720
|
}
|
|
695
721
|
|
|
696
722
|
function parseSimpleTableRow(trElement: XmlElementNode): TableRowNode {
|
|
697
723
|
const cells: TableCellNode[] = [];
|
|
698
724
|
let propertiesXml: string | undefined;
|
|
725
|
+
let height: TableRowNode["height"];
|
|
726
|
+
let heightRule: TableRowNode["heightRule"];
|
|
727
|
+
let isHeader: TableRowNode["isHeader"];
|
|
699
728
|
|
|
700
729
|
for (const child of trElement.children) {
|
|
701
730
|
if (child.type !== "element") continue;
|
|
@@ -703,6 +732,9 @@ function parseSimpleTableRow(trElement: XmlElementNode): TableRowNode {
|
|
|
703
732
|
|
|
704
733
|
if (name === "trPr") {
|
|
705
734
|
propertiesXml = serializeElementToXml(child);
|
|
735
|
+
height = readRowHeight(child);
|
|
736
|
+
heightRule = readRowHeightRule(child);
|
|
737
|
+
isHeader = readRowIsHeader(child);
|
|
706
738
|
} else if (name === "tc") {
|
|
707
739
|
cells.push(parseSimpleTableCell(child));
|
|
708
740
|
}
|
|
@@ -711,6 +743,9 @@ function parseSimpleTableRow(trElement: XmlElementNode): TableRowNode {
|
|
|
711
743
|
return {
|
|
712
744
|
type: "table_row",
|
|
713
745
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
746
|
+
...(height !== undefined ? { height } : {}),
|
|
747
|
+
...(heightRule ? { heightRule } : {}),
|
|
748
|
+
...(isHeader !== undefined ? { isHeader } : {}),
|
|
714
749
|
cells,
|
|
715
750
|
};
|
|
716
751
|
}
|
|
@@ -720,6 +755,10 @@ function parseSimpleTableCell(tcElement: XmlElementNode): TableCellNode {
|
|
|
720
755
|
let propertiesXml: string | undefined;
|
|
721
756
|
let gridSpan: number | undefined;
|
|
722
757
|
let verticalMerge: "restart" | "continue" | undefined;
|
|
758
|
+
let width: TableCellNode["width"];
|
|
759
|
+
let borders: TableCellNode["borders"];
|
|
760
|
+
let shading: TableCellNode["shading"];
|
|
761
|
+
let verticalAlign: TableCellNode["verticalAlign"];
|
|
723
762
|
|
|
724
763
|
for (const child of tcElement.children) {
|
|
725
764
|
if (child.type !== "element") continue;
|
|
@@ -736,6 +775,10 @@ function parseSimpleTableCell(tcElement: XmlElementNode): TableCellNode {
|
|
|
736
775
|
const vmVal = vmEl.attributes["w:val"] ?? vmEl.attributes.val ?? "continue";
|
|
737
776
|
verticalMerge = vmVal === "restart" ? "restart" : "continue";
|
|
738
777
|
}
|
|
778
|
+
width = readCellWidth(child);
|
|
779
|
+
borders = readCellBorders(child);
|
|
780
|
+
shading = readCellShading(child);
|
|
781
|
+
verticalAlign = readCellVerticalAlign(child);
|
|
739
782
|
} else if (name === "p") {
|
|
740
783
|
children.push(parseParagraphElement(child));
|
|
741
784
|
}
|
|
@@ -746,6 +789,10 @@ function parseSimpleTableCell(tcElement: XmlElementNode): TableCellNode {
|
|
|
746
789
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
747
790
|
...(gridSpan ? { gridSpan } : {}),
|
|
748
791
|
...(verticalMerge ? { verticalMerge } : {}),
|
|
792
|
+
...(width ? { width } : {}),
|
|
793
|
+
...(borders ? { borders } : {}),
|
|
794
|
+
...(shading ? { shading } : {}),
|
|
795
|
+
...(verticalAlign ? { verticalAlign } : {}),
|
|
749
796
|
children: children.length > 0 ? children : [{ type: "paragraph", children: [] }],
|
|
750
797
|
};
|
|
751
798
|
}
|
|
@@ -791,6 +838,8 @@ function parseXml(xml: string): XmlElementNode {
|
|
|
791
838
|
name: "__root__",
|
|
792
839
|
attributes: {},
|
|
793
840
|
children: [],
|
|
841
|
+
start: 0,
|
|
842
|
+
end: xml.length,
|
|
794
843
|
};
|
|
795
844
|
const stack: XmlElementNode[] = [root];
|
|
796
845
|
let cursor = 0;
|
|
@@ -814,6 +863,8 @@ function parseXml(xml: string): XmlElementNode {
|
|
|
814
863
|
stack[stack.length - 1]?.children.push({
|
|
815
864
|
type: "text",
|
|
816
865
|
text: xml.slice(cursor + 9, textEnd),
|
|
866
|
+
start: cursor,
|
|
867
|
+
end: end >= 0 ? end + 3 : xml.length,
|
|
817
868
|
});
|
|
818
869
|
cursor = end >= 0 ? end + 3 : xml.length;
|
|
819
870
|
continue;
|
|
@@ -824,7 +875,7 @@ function parseXml(xml: string): XmlElementNode {
|
|
|
824
875
|
const end = nextTag >= 0 ? nextTag : xml.length;
|
|
825
876
|
const text = decodeXmlEntities(xml.slice(cursor, end));
|
|
826
877
|
if (text.trim().length > 0 || (text.length > 0 && stack.length > 1)) {
|
|
827
|
-
stack[stack.length - 1]?.children.push({ type: "text", text });
|
|
878
|
+
stack[stack.length - 1]?.children.push({ type: "text", text, start: cursor, end });
|
|
828
879
|
}
|
|
829
880
|
cursor = end;
|
|
830
881
|
continue;
|
|
@@ -833,7 +884,10 @@ function parseXml(xml: string): XmlElementNode {
|
|
|
833
884
|
if (xml[cursor + 1] === "/") {
|
|
834
885
|
const end = xml.indexOf(">", cursor);
|
|
835
886
|
if (end < 0) break;
|
|
836
|
-
stack.pop();
|
|
887
|
+
const current = stack.pop();
|
|
888
|
+
if (current) {
|
|
889
|
+
current.end = end + 1;
|
|
890
|
+
}
|
|
837
891
|
cursor = end + 1;
|
|
838
892
|
continue;
|
|
839
893
|
}
|
|
@@ -855,6 +909,8 @@ function parseXml(xml: string): XmlElementNode {
|
|
|
855
909
|
name: tagName,
|
|
856
910
|
attributes,
|
|
857
911
|
children: [],
|
|
912
|
+
start: cursor,
|
|
913
|
+
end: tagEnd + 1,
|
|
858
914
|
};
|
|
859
915
|
|
|
860
916
|
stack[stack.length - 1]?.children.push(element);
|