@beyondwork/docx-react-component 1.0.18 → 1.0.19
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/README.md +8 -2
- package/package.json +24 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +374 -4
- package/src/api/session-state.ts +58 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +5 -1
- package/src/core/commands/list-commands.ts +231 -36
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/section-layout-commands.ts +680 -0
- package/src/core/commands/style-commands.ts +262 -0
- package/src/core/search/search-text.ts +329 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +1 -1
- package/src/index.ts +30 -0
- package/src/io/docx-session.ts +260 -39
- package/src/io/export/serialize-main-document.ts +202 -5
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/normalize/normalize-text.ts +63 -25
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-footnotes.ts +212 -20
- package/src/io/ooxml/parse-headers-footers.ts +229 -25
- package/src/io/ooxml/parse-inline-media.ts +16 -0
- package/src/io/ooxml/parse-main-document.ts +411 -6
- package/src/io/ooxml/parse-numbering.ts +7 -0
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +25 -0
- package/src/io/ooxml/parse-styles.ts +463 -0
- package/src/io/ooxml/parse-theme.ts +32 -0
- package/src/model/canonical-document.ts +133 -3
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +2 -1
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +564 -0
- package/src/runtime/document-runtime.ts +265 -35
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/numbering-prefix.ts +47 -26
- package/src/runtime/page-layout-estimation.ts +212 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +1 -0
- package/src/runtime/session-capabilities.ts +2 -0
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +239 -12
- package/src/runtime/table-schema.ts +87 -5
- package/src/runtime/view-state.ts +459 -0
- package/src/ui/WordReviewEditor.tsx +1902 -312
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/selection-helpers.ts +20 -0
- package/src/ui/headless/selection-toolbar-model.ts +22 -0
- package/src/ui/headless/use-editor-keyboard.ts +6 -1
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +125 -14
- package/src/ui-tailwind/editor-surface/perf-probe.ts +107 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +45 -6
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +47 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +95 -22
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +394 -77
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
- package/src/ui-tailwind/index.ts +2 -1
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-review-rail.tsx +6 -6
- package/src/ui-tailwind/theme/editor-theme.css +123 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +291 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +926 -27
- package/src/validation/compatibility-engine.ts +92 -20
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +487 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RuntimeRenderSnapshot,
|
|
3
|
+
SurfaceBlockSnapshot,
|
|
4
|
+
} from "../../api/public-types";
|
|
5
|
+
import type {
|
|
6
|
+
BlockNode,
|
|
7
|
+
CanonicalDocument as CanonicalDocumentEnvelope,
|
|
8
|
+
ParagraphIndentation,
|
|
9
|
+
ParagraphNode,
|
|
10
|
+
TabStop,
|
|
11
|
+
TableCellNode,
|
|
12
|
+
TableNode,
|
|
13
|
+
TableRowNode,
|
|
14
|
+
} from "../../model/canonical-document.ts";
|
|
15
|
+
|
|
16
|
+
export interface ParagraphLayoutCommandContext {
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ParagraphLayoutMutationResult {
|
|
21
|
+
document: CanonicalDocumentEnvelope;
|
|
22
|
+
selection: RuntimeRenderSnapshot["selection"];
|
|
23
|
+
changed: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setActiveParagraphIndentation(
|
|
27
|
+
document: CanonicalDocumentEnvelope,
|
|
28
|
+
snapshot: RuntimeRenderSnapshot,
|
|
29
|
+
indentation: ParagraphIndentation,
|
|
30
|
+
_context: ParagraphLayoutCommandContext,
|
|
31
|
+
): ParagraphLayoutMutationResult {
|
|
32
|
+
return mutateActiveParagraph(document, snapshot, (paragraph) => {
|
|
33
|
+
const nextIndentation = mergeIndentationPatch(paragraph.indentation, indentation);
|
|
34
|
+
if (indentationEquals(paragraph.indentation, nextIndentation)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (nextIndentation) {
|
|
38
|
+
paragraph.indentation = nextIndentation;
|
|
39
|
+
} else {
|
|
40
|
+
delete paragraph.indentation;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function setActiveParagraphTabStops(
|
|
47
|
+
document: CanonicalDocumentEnvelope,
|
|
48
|
+
snapshot: RuntimeRenderSnapshot,
|
|
49
|
+
tabStops: ReadonlyArray<{ pos: number; val?: string; leader?: string }>,
|
|
50
|
+
_context: ParagraphLayoutCommandContext,
|
|
51
|
+
): ParagraphLayoutMutationResult {
|
|
52
|
+
return mutateActiveParagraph(document, snapshot, (paragraph) => {
|
|
53
|
+
const nextTabStops = normalizeTabStops(tabStops);
|
|
54
|
+
if (tabStopsEqual(paragraph.tabStops, nextTabStops)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (nextTabStops.length > 0) {
|
|
58
|
+
paragraph.tabStops = nextTabStops;
|
|
59
|
+
} else {
|
|
60
|
+
delete paragraph.tabStops;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mutateActiveParagraph(
|
|
67
|
+
document: CanonicalDocumentEnvelope,
|
|
68
|
+
snapshot: RuntimeRenderSnapshot,
|
|
69
|
+
mutate: (paragraph: ParagraphNode) => boolean,
|
|
70
|
+
): ParagraphLayoutMutationResult {
|
|
71
|
+
const surface = snapshot.surface;
|
|
72
|
+
if (!surface) {
|
|
73
|
+
return {
|
|
74
|
+
document,
|
|
75
|
+
selection: snapshot.selection,
|
|
76
|
+
changed: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const paragraphIndex = resolveActiveParagraphIndex(surface.blocks, snapshot.selection);
|
|
81
|
+
if (paragraphIndex === null) {
|
|
82
|
+
return {
|
|
83
|
+
document,
|
|
84
|
+
selection: snapshot.selection,
|
|
85
|
+
changed: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const working = structuredClone(document) as CanonicalDocumentEnvelope;
|
|
90
|
+
const paragraphs = collectCanonicalParagraphs(working.content.children);
|
|
91
|
+
const target = paragraphs[paragraphIndex];
|
|
92
|
+
if (!target) {
|
|
93
|
+
return {
|
|
94
|
+
document,
|
|
95
|
+
selection: snapshot.selection,
|
|
96
|
+
changed: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const changed = mutate(target);
|
|
101
|
+
return {
|
|
102
|
+
document: changed ? working : document,
|
|
103
|
+
selection: snapshot.selection,
|
|
104
|
+
changed,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function resolveActiveParagraphIndex(
|
|
109
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
110
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
111
|
+
): number | null {
|
|
112
|
+
const paragraphs = collectSurfaceParagraphs(blocks);
|
|
113
|
+
if (paragraphs.length === 0) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (selection.activeRange.kind === "node") {
|
|
118
|
+
const nodePosition = selection.activeRange.at;
|
|
119
|
+
const nodeIndex = paragraphs.findIndex(
|
|
120
|
+
(paragraph) => nodePosition >= paragraph.from && nodePosition <= paragraph.to,
|
|
121
|
+
);
|
|
122
|
+
if (nodeIndex >= 0) {
|
|
123
|
+
return nodeIndex;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const selectionFrom = Math.min(selection.anchor, selection.head);
|
|
128
|
+
const selectionTo = Math.max(selection.anchor, selection.head);
|
|
129
|
+
|
|
130
|
+
const exactIndex = paragraphs.findIndex((paragraph) =>
|
|
131
|
+
selection.isCollapsed
|
|
132
|
+
? selection.head >= paragraph.from && selection.head <= paragraph.to
|
|
133
|
+
: selectionFrom < paragraph.to && paragraph.from < selectionTo,
|
|
134
|
+
);
|
|
135
|
+
if (exactIndex >= 0) {
|
|
136
|
+
return exactIndex;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function collectSurfaceParagraphs(
|
|
143
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
144
|
+
output: Array<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>> = [],
|
|
145
|
+
): Array<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>> {
|
|
146
|
+
for (const block of blocks) {
|
|
147
|
+
if (block.kind === "paragraph") {
|
|
148
|
+
output.push(block);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (block.kind === "table") {
|
|
152
|
+
for (const row of block.rows) {
|
|
153
|
+
for (const cell of row.cells) {
|
|
154
|
+
collectSurfaceParagraphs(cell.content, output);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (block.kind === "sdt_block") {
|
|
160
|
+
collectSurfaceParagraphs(block.children, output);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return output;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function collectCanonicalParagraphs(
|
|
167
|
+
blocks: readonly BlockNode[],
|
|
168
|
+
output: ParagraphNode[] = [],
|
|
169
|
+
): ParagraphNode[] {
|
|
170
|
+
for (const block of blocks) {
|
|
171
|
+
switch (block.type) {
|
|
172
|
+
case "paragraph":
|
|
173
|
+
output.push(block);
|
|
174
|
+
break;
|
|
175
|
+
case "table":
|
|
176
|
+
collectParagraphsFromTable(block, output);
|
|
177
|
+
break;
|
|
178
|
+
case "sdt":
|
|
179
|
+
collectCanonicalParagraphs(block.children, output);
|
|
180
|
+
break;
|
|
181
|
+
case "custom_xml":
|
|
182
|
+
// `custom_xml` wrappers are projected as preserve-only opaque blocks on
|
|
183
|
+
// the live surface, so their hidden descendants must stay out of the
|
|
184
|
+
// paragraph mutation index or surface/canonical ordinals drift.
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return output;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function collectParagraphsFromTable(table: TableNode, output: ParagraphNode[]): void {
|
|
194
|
+
for (const row of table.rows) {
|
|
195
|
+
collectParagraphsFromRow(row, output);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function collectParagraphsFromRow(row: TableRowNode, output: ParagraphNode[]): void {
|
|
200
|
+
for (const cell of row.cells) {
|
|
201
|
+
collectParagraphsFromCell(cell, output);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function collectParagraphsFromCell(cell: TableCellNode, output: ParagraphNode[]): void {
|
|
206
|
+
collectCanonicalParagraphs(cell.children, output);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function mergeIndentationPatch(
|
|
210
|
+
current: ParagraphIndentation | undefined,
|
|
211
|
+
patch: ParagraphIndentation,
|
|
212
|
+
): ParagraphIndentation | undefined {
|
|
213
|
+
const merged: ParagraphIndentation = {
|
|
214
|
+
...(current ?? {}),
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if ("left" in patch) {
|
|
218
|
+
merged.left = patch.left;
|
|
219
|
+
}
|
|
220
|
+
if ("right" in patch) {
|
|
221
|
+
merged.right = patch.right;
|
|
222
|
+
}
|
|
223
|
+
if ("firstLine" in patch) {
|
|
224
|
+
merged.firstLine = patch.firstLine;
|
|
225
|
+
if (!("hanging" in patch)) {
|
|
226
|
+
delete merged.hanging;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if ("hanging" in patch) {
|
|
230
|
+
merged.hanging = patch.hanging;
|
|
231
|
+
if (!("firstLine" in patch)) {
|
|
232
|
+
delete merged.firstLine;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return normalizeIndentation(merged);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeIndentation(
|
|
240
|
+
indentation: ParagraphIndentation,
|
|
241
|
+
): ParagraphIndentation | undefined {
|
|
242
|
+
const normalized: ParagraphIndentation = {};
|
|
243
|
+
if (indentation.left !== undefined && indentation.left > 0) {
|
|
244
|
+
normalized.left = Math.round(indentation.left);
|
|
245
|
+
}
|
|
246
|
+
if (indentation.right !== undefined && indentation.right > 0) {
|
|
247
|
+
normalized.right = Math.round(indentation.right);
|
|
248
|
+
}
|
|
249
|
+
if (indentation.firstLine !== undefined && indentation.firstLine > 0) {
|
|
250
|
+
normalized.firstLine = Math.round(indentation.firstLine);
|
|
251
|
+
}
|
|
252
|
+
if (indentation.hanging !== undefined && indentation.hanging > 0) {
|
|
253
|
+
normalized.hanging = Math.round(indentation.hanging);
|
|
254
|
+
delete normalized.firstLine;
|
|
255
|
+
}
|
|
256
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function indentationEquals(
|
|
260
|
+
left: ParagraphIndentation | undefined,
|
|
261
|
+
right: ParagraphIndentation | undefined,
|
|
262
|
+
): boolean {
|
|
263
|
+
return (
|
|
264
|
+
(left?.left ?? 0) === (right?.left ?? 0) &&
|
|
265
|
+
(left?.right ?? 0) === (right?.right ?? 0) &&
|
|
266
|
+
(left?.firstLine ?? 0) === (right?.firstLine ?? 0) &&
|
|
267
|
+
(left?.hanging ?? 0) === (right?.hanging ?? 0)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizeTabStops(
|
|
272
|
+
tabStops: ReadonlyArray<{ pos: number; val?: string; leader?: string }>,
|
|
273
|
+
): TabStop[] {
|
|
274
|
+
const seen = new Set<number>();
|
|
275
|
+
const normalized = [...tabStops]
|
|
276
|
+
.map((tabStop) => ({
|
|
277
|
+
position: Math.max(0, Math.round(tabStop.pos)),
|
|
278
|
+
align: normalizeTabAlign(tabStop.val),
|
|
279
|
+
...(normalizeTabLeader(tabStop.leader)
|
|
280
|
+
? { leader: normalizeTabLeader(tabStop.leader) }
|
|
281
|
+
: {}),
|
|
282
|
+
}))
|
|
283
|
+
.filter((tabStop) => {
|
|
284
|
+
if (seen.has(tabStop.position)) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
seen.add(tabStop.position);
|
|
288
|
+
return true;
|
|
289
|
+
})
|
|
290
|
+
.sort((left, right) => left.position - right.position);
|
|
291
|
+
|
|
292
|
+
return normalized;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function normalizeTabAlign(value: string | undefined): TabStop["align"] {
|
|
296
|
+
switch (value) {
|
|
297
|
+
case "center":
|
|
298
|
+
case "right":
|
|
299
|
+
case "decimal":
|
|
300
|
+
case "bar":
|
|
301
|
+
case "clear":
|
|
302
|
+
return value;
|
|
303
|
+
default:
|
|
304
|
+
return "left";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeTabLeader(value: string | undefined): TabStop["leader"] | undefined {
|
|
309
|
+
switch (value) {
|
|
310
|
+
case "none":
|
|
311
|
+
case "dot":
|
|
312
|
+
case "hyphen":
|
|
313
|
+
case "underscore":
|
|
314
|
+
case "heavy":
|
|
315
|
+
case "middleDot":
|
|
316
|
+
return value;
|
|
317
|
+
default:
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function tabStopsEqual(
|
|
323
|
+
left: readonly TabStop[] | undefined,
|
|
324
|
+
right: readonly TabStop[] | undefined,
|
|
325
|
+
): boolean {
|
|
326
|
+
const normalizedLeft = left ?? [];
|
|
327
|
+
const normalizedRight = right ?? [];
|
|
328
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return normalizedLeft.every((tabStop, index) => {
|
|
332
|
+
const other = normalizedRight[index];
|
|
333
|
+
return (
|
|
334
|
+
tabStop.position === other?.position &&
|
|
335
|
+
(tabStop.align ?? "left") === (other?.align ?? "left") &&
|
|
336
|
+
(tabStop.leader ?? "") === (other?.leader ?? "")
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
}
|