@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -48,6 +48,13 @@ import type {
|
|
|
48
48
|
StyleCatalogSnapshot,
|
|
49
49
|
SurfaceBlockSnapshot,
|
|
50
50
|
SurfaceInlineSegment,
|
|
51
|
+
TableOp,
|
|
52
|
+
TableOpResult,
|
|
53
|
+
PublicTableEvent,
|
|
54
|
+
PublicTableRenderPlan,
|
|
55
|
+
PublicTableRowHeight,
|
|
56
|
+
PublicTableStyle,
|
|
57
|
+
PublicTableSummary,
|
|
51
58
|
TrackedChangeEntrySnapshot,
|
|
52
59
|
TocRefreshResult,
|
|
53
60
|
UpdateFieldsResult,
|
|
@@ -113,6 +120,7 @@ import {
|
|
|
113
120
|
import {
|
|
114
121
|
applyTableStructureOperation,
|
|
115
122
|
getTableStructureContext,
|
|
123
|
+
type TableStructureOperation,
|
|
116
124
|
} from "../core/commands/table-structure-commands.ts";
|
|
117
125
|
import {
|
|
118
126
|
deleteSelectionOrBackward,
|
|
@@ -198,7 +206,12 @@ import {
|
|
|
198
206
|
resolveChromePreset,
|
|
199
207
|
resolveChromeVisibilityForPreset,
|
|
200
208
|
} from "../ui-tailwind/chrome/chrome-preset-model.ts";
|
|
201
|
-
import {
|
|
209
|
+
import { createRuntimeCollabSync } from "../runtime/collab/runtime-collab-sync.ts";
|
|
210
|
+
import {
|
|
211
|
+
clearLocalCursorState,
|
|
212
|
+
getCursorColorForUser,
|
|
213
|
+
setLocalCursorState,
|
|
214
|
+
} from "../runtime/collab/remote-cursor-awareness.ts";
|
|
202
215
|
|
|
203
216
|
export {
|
|
204
217
|
__createFallbackRuntime,
|
|
@@ -505,6 +518,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
505
518
|
setZoom: (level) => {
|
|
506
519
|
runtime.setZoom(level);
|
|
507
520
|
},
|
|
521
|
+
setEditorRole: (role) => {
|
|
522
|
+
runtime.setEditorRole(role);
|
|
523
|
+
},
|
|
524
|
+
setChromePin: (surface, pin) => {
|
|
525
|
+
runtime.setChromePin(surface, pin);
|
|
526
|
+
},
|
|
508
527
|
insertSectionBreak: (type, options) => {
|
|
509
528
|
applyRuntimeInsertSectionBreak(runtime, type, options);
|
|
510
529
|
},
|
|
@@ -579,6 +598,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
579
598
|
return clonePublicValue(runtime.getRuntimeContextAnalytics(query));
|
|
580
599
|
},
|
|
581
600
|
layout: runtime.layout,
|
|
601
|
+
tables: buildTablesFacet(runtime, mountedSurface ?? null),
|
|
582
602
|
goToNextReviewItem: () => {
|
|
583
603
|
return clonePublicValue(navigateReviewQueue(runtime, "next"));
|
|
584
604
|
},
|
|
@@ -638,6 +658,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
638
658
|
onError,
|
|
639
659
|
onEvent,
|
|
640
660
|
onWarning,
|
|
661
|
+
onReviewSidebarTrackedChanges,
|
|
662
|
+
onReviewSidebarComments,
|
|
641
663
|
readOnly = false,
|
|
642
664
|
reviewMode = "review",
|
|
643
665
|
suggestionsEnabled = false,
|
|
@@ -669,6 +691,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
669
691
|
runtime,
|
|
670
692
|
loadError,
|
|
671
693
|
activeRuntime,
|
|
694
|
+
commandAppliedBridge,
|
|
672
695
|
fallbackSnapshot,
|
|
673
696
|
loadingSessionState,
|
|
674
697
|
loadingViewState,
|
|
@@ -989,9 +1012,48 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
989
1012
|
|
|
990
1013
|
useEffect(() => {
|
|
991
1014
|
if (!ydoc || !runtime) return;
|
|
992
|
-
const handle =
|
|
1015
|
+
const handle = createRuntimeCollabSync({
|
|
1016
|
+
ydoc,
|
|
1017
|
+
runtime,
|
|
1018
|
+
authorId: currentUser.userId,
|
|
1019
|
+
commandAppliedBridge,
|
|
1020
|
+
});
|
|
993
1021
|
return () => handle.destroy();
|
|
994
|
-
}, [
|
|
1022
|
+
}, [commandAppliedBridge, currentUser.userId, runtime, ydoc]);
|
|
1023
|
+
|
|
1024
|
+
useEffect(() => {
|
|
1025
|
+
if (!awareness) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
return () => clearLocalCursorState(awareness);
|
|
1029
|
+
}, [awareness]);
|
|
1030
|
+
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
if (!awareness) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (!runtime) {
|
|
1036
|
+
clearLocalCursorState(awareness);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
setLocalCursorState(awareness, {
|
|
1041
|
+
userId: currentUser.userId,
|
|
1042
|
+
displayName: currentUser.displayName,
|
|
1043
|
+
color: getCursorColorForUser(currentUser.userId),
|
|
1044
|
+
anchor: snapshot.selection.anchor,
|
|
1045
|
+
head: snapshot.selection.head,
|
|
1046
|
+
storyTarget: snapshot.activeStory,
|
|
1047
|
+
});
|
|
1048
|
+
}, [
|
|
1049
|
+
awareness,
|
|
1050
|
+
currentUser.displayName,
|
|
1051
|
+
currentUser.userId,
|
|
1052
|
+
runtime,
|
|
1053
|
+
snapshot.activeStory,
|
|
1054
|
+
snapshot.selection.anchor,
|
|
1055
|
+
snapshot.selection.head,
|
|
1056
|
+
]);
|
|
995
1057
|
|
|
996
1058
|
useEffect(() => {
|
|
997
1059
|
runtimeViewStateSeedRef.current = {
|
|
@@ -1327,6 +1389,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1327
1389
|
setZoom: (level) => {
|
|
1328
1390
|
activeRuntime.setZoom(level);
|
|
1329
1391
|
},
|
|
1392
|
+
setEditorRole: (role) => {
|
|
1393
|
+
activeRuntime.setEditorRole(role);
|
|
1394
|
+
},
|
|
1395
|
+
setChromePin: (surface, pin) => {
|
|
1396
|
+
activeRuntime.setChromePin(surface, pin);
|
|
1397
|
+
},
|
|
1330
1398
|
insertSectionBreak: (type, options) => {
|
|
1331
1399
|
applyRuntimeInsertSectionBreak(activeRuntime, type, options);
|
|
1332
1400
|
},
|
|
@@ -1394,6 +1462,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1394
1462
|
return clonePublicValue(activeRuntime.getRuntimeContextAnalytics(query));
|
|
1395
1463
|
},
|
|
1396
1464
|
layout: activeRuntime.layout,
|
|
1465
|
+
tables: buildTablesFacet(activeRuntime, surfaceRef.current ?? null),
|
|
1397
1466
|
goToNextReviewItem: () => {
|
|
1398
1467
|
return clonePublicValue(navigateMountedReviewQueue("next"));
|
|
1399
1468
|
},
|
|
@@ -2183,6 +2252,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2183
2252
|
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "header"),
|
|
2184
2253
|
onOpenFooterStory: () =>
|
|
2185
2254
|
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "footer"),
|
|
2255
|
+
onOpenHeaderStoryForPage: (pageIndex: number) =>
|
|
2256
|
+
openStoryForPage(activeRuntime, pageIndex, "header"),
|
|
2257
|
+
onOpenFooterStoryForPage: (pageIndex: number) =>
|
|
2258
|
+
openStoryForPage(activeRuntime, pageIndex, "footer"),
|
|
2186
2259
|
onDeleteSectionBreak: (sectionIndex) =>
|
|
2187
2260
|
applyRuntimeDeleteSectionBreak(activeRuntime, sectionIndex),
|
|
2188
2261
|
onUpdateSectionLayout: (sectionIndex, patch) =>
|
|
@@ -2230,7 +2303,6 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2230
2303
|
<EditorSurfaceController
|
|
2231
2304
|
ref={surfaceRef}
|
|
2232
2305
|
currentUser={currentUser}
|
|
2233
|
-
ydoc={ydoc}
|
|
2234
2306
|
awareness={awareness}
|
|
2235
2307
|
snapshot={snapshot}
|
|
2236
2308
|
canonicalDocument={canonicalDocument}
|
|
@@ -2256,6 +2328,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2256
2328
|
dispatchRuntimeCommand={(command) =>
|
|
2257
2329
|
activeRuntime.applyActiveStoryTextCommand(command as never)
|
|
2258
2330
|
}
|
|
2331
|
+
layoutFacet={activeRuntime.layout}
|
|
2332
|
+
pageChromeHeaderBandPx={isPageWorkspace ? 32 : 0}
|
|
2333
|
+
pageChromeFooterBandPx={isPageWorkspace ? 32 : 0}
|
|
2334
|
+
pageChromeInterGapPx={isPageWorkspace ? 24 : 16}
|
|
2335
|
+
onOpenHeaderStoryForPage={(pageIndex: number) =>
|
|
2336
|
+
openStoryForPage(activeRuntime, pageIndex, "header")
|
|
2337
|
+
}
|
|
2338
|
+
onOpenFooterStoryForPage={(pageIndex: number) =>
|
|
2339
|
+
openStoryForPage(activeRuntime, pageIndex, "footer")
|
|
2340
|
+
}
|
|
2259
2341
|
onCommentActivated={(commentId) => {
|
|
2260
2342
|
activeRuntime.openComment(commentId);
|
|
2261
2343
|
setActiveRailTab("comments");
|
|
@@ -2300,6 +2382,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2300
2382
|
activeRevisionId={activeRevisionId}
|
|
2301
2383
|
showTrackedChanges={showTrackedChanges}
|
|
2302
2384
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
2385
|
+
layoutFacet={activeRuntime.layout}
|
|
2303
2386
|
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
2304
2387
|
chromePreset={effectiveChromePreset}
|
|
2305
2388
|
chromeOptions={chromeOptions}
|
|
@@ -2348,6 +2431,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2348
2431
|
selectionToolbarRef={selectionToolbarElementRef}
|
|
2349
2432
|
commands={commands}
|
|
2350
2433
|
document={documentElement}
|
|
2434
|
+
onReviewSidebarTrackedChanges={onReviewSidebarTrackedChanges}
|
|
2435
|
+
onReviewSidebarComments={onReviewSidebarComments}
|
|
2351
2436
|
/>
|
|
2352
2437
|
);
|
|
2353
2438
|
},
|
|
@@ -3388,28 +3473,19 @@ function applyRuntimeImageReposition(
|
|
|
3388
3473
|
function applyRuntimeTableStructureOperation(
|
|
3389
3474
|
runtime: WordReviewEditorRuntime,
|
|
3390
3475
|
mountedSurface: TwProseMirrorSurfaceRef | null | undefined,
|
|
3391
|
-
operation:
|
|
3392
|
-
|
|
3393
|
-
| { type: "add-row-after" }
|
|
3394
|
-
| { type: "add-column-before" }
|
|
3395
|
-
| { type: "add-column-after" }
|
|
3396
|
-
| { type: "delete-row" }
|
|
3397
|
-
| { type: "delete-column" }
|
|
3398
|
-
| { type: "delete-table" }
|
|
3399
|
-
| { type: "merge-cells" }
|
|
3400
|
-
| { type: "split-cell" }
|
|
3401
|
-
| { type: "set-cell-background"; color: string },
|
|
3402
|
-
): void {
|
|
3476
|
+
operation: TableStructureOperation,
|
|
3477
|
+
): { changed: boolean; coercedReason: string | null } {
|
|
3403
3478
|
if (isSelectionSuggesting(runtime)) {
|
|
3479
|
+
const coercedReason = `Table operation "${operation.type}" is not supported in suggesting mode.`;
|
|
3404
3480
|
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
3405
3481
|
code: "unsupported_surface",
|
|
3406
|
-
message:
|
|
3482
|
+
message: coercedReason,
|
|
3407
3483
|
}]);
|
|
3408
|
-
return;
|
|
3484
|
+
return { changed: false, coercedReason };
|
|
3409
3485
|
}
|
|
3410
3486
|
const context = getStoryMutationContext(runtime, `table.${operation.type}`);
|
|
3411
3487
|
if (!context) {
|
|
3412
|
-
return;
|
|
3488
|
+
return { changed: false, coercedReason: "No active mutation context." };
|
|
3413
3489
|
}
|
|
3414
3490
|
|
|
3415
3491
|
const result = applyTableStructureOperation(
|
|
@@ -3419,8 +3495,262 @@ function applyRuntimeTableStructureOperation(
|
|
|
3419
3495
|
operation,
|
|
3420
3496
|
);
|
|
3421
3497
|
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
3498
|
+
return {
|
|
3499
|
+
changed: result.changed,
|
|
3500
|
+
coercedReason: result.changed ? null : "Op was a no-op against the active selection.",
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
/**
|
|
3505
|
+
* Translate a public-API `TableOp` (kebab-case `kind` discriminator) to
|
|
3506
|
+
* the internal `TableStructureOperation` (`type` discriminator). The
|
|
3507
|
+
* shape values are identical aside from the discriminator name.
|
|
3508
|
+
*/
|
|
3509
|
+
export function __publicTableOpToInternal(op: TableOp): TableStructureOperation {
|
|
3510
|
+
return publicTableOpToInternal(op);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
function publicTableOpToInternal(op: TableOp): TableStructureOperation {
|
|
3514
|
+
const { kind, ...rest } = op as { kind: string } & Record<string, unknown>;
|
|
3515
|
+
if (kind === "insert") {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
"TableOp kind \"insert\" is not routed through ref.tables.apply; use ref.insertTable(...).",
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
return { type: kind, ...rest } as TableStructureOperation;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
/**
|
|
3524
|
+
* Build the `ref.tables` facet: a typed dispatch boundary + capability
|
|
3525
|
+
* read. Delegates every op through the same `applyRuntimeTableStructureOperation`
|
|
3526
|
+
* helper the flat ref verbs use, so there is exactly one server-side path
|
|
3527
|
+
* for every table mutation.
|
|
3528
|
+
*/
|
|
3529
|
+
function buildTablesFacet(
|
|
3530
|
+
runtime: WordReviewEditorRuntime,
|
|
3531
|
+
mountedSurface: TwProseMirrorSurfaceRef | null,
|
|
3532
|
+
) {
|
|
3533
|
+
const getCapabilities = () => {
|
|
3534
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
3535
|
+
const document = runtime.getCanonicalDocument();
|
|
3536
|
+
return (
|
|
3537
|
+
clonePublicValue(
|
|
3538
|
+
getTableStructureContext(
|
|
3539
|
+
document,
|
|
3540
|
+
snapshot,
|
|
3541
|
+
mountedSurface?.getTableSelection() ?? null,
|
|
3542
|
+
),
|
|
3543
|
+
) ?? null
|
|
3544
|
+
);
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3547
|
+
const buildSummary = (
|
|
3548
|
+
table: Extract<ReturnType<typeof runtime.getCanonicalDocument>["content"]["children"][number], { type: "table" }>,
|
|
3549
|
+
tableBlockIndex: number,
|
|
3550
|
+
): PublicTableSummary => {
|
|
3551
|
+
const blockId = `table-${tableBlockIndex}`;
|
|
3552
|
+
const hasVerticalMerges = table.rows.some((row) =>
|
|
3553
|
+
row.cells.some((cell) => cell.verticalMerge !== undefined),
|
|
3554
|
+
);
|
|
3555
|
+
const hasHorizontalSpans = table.rows.some((row) =>
|
|
3556
|
+
row.cells.some((cell) => (cell.gridSpan ?? 1) > 1),
|
|
3557
|
+
);
|
|
3558
|
+
return {
|
|
3559
|
+
tableBlockIndex,
|
|
3560
|
+
blockId,
|
|
3561
|
+
styleId: table.styleId ?? null,
|
|
3562
|
+
rowCount: table.rows.length,
|
|
3563
|
+
columnCount: table.gridColumns.length,
|
|
3564
|
+
gridColumnsTwips: table.gridColumns,
|
|
3565
|
+
alignment: table.alignment ?? null,
|
|
3566
|
+
hasVerticalMerges,
|
|
3567
|
+
hasHorizontalSpans,
|
|
3568
|
+
pageIndex: runtime.layout.getFirstPageIndexForBlock(blockId) ?? null,
|
|
3569
|
+
};
|
|
3570
|
+
};
|
|
3571
|
+
|
|
3572
|
+
const getTables = (options?: { sectionIndex?: number }): PublicTableSummary[] => {
|
|
3573
|
+
const document = runtime.getCanonicalDocument();
|
|
3574
|
+
const summaries: PublicTableSummary[] = [];
|
|
3575
|
+
let idx = 0;
|
|
3576
|
+
for (const block of document.content.children) {
|
|
3577
|
+
if (block.type === "table") {
|
|
3578
|
+
summaries.push(clonePublicValue(buildSummary(block, idx)));
|
|
3579
|
+
idx++;
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
if (options?.sectionIndex != null) {
|
|
3583
|
+
const section = runtime.layout.getSection(options.sectionIndex);
|
|
3584
|
+
if (!section) return [];
|
|
3585
|
+
return summaries.filter(
|
|
3586
|
+
(s) =>
|
|
3587
|
+
s.pageIndex != null &&
|
|
3588
|
+
s.pageIndex >= section.firstPageIndex &&
|
|
3589
|
+
s.pageIndex <= section.lastPageIndex,
|
|
3590
|
+
);
|
|
3591
|
+
}
|
|
3592
|
+
return summaries;
|
|
3593
|
+
};
|
|
3594
|
+
|
|
3595
|
+
const getTable = (tableBlockIndex: number): PublicTableSummary | null => {
|
|
3596
|
+
const document = runtime.getCanonicalDocument();
|
|
3597
|
+
let idx = 0;
|
|
3598
|
+
for (const block of document.content.children) {
|
|
3599
|
+
if (block.type === "table") {
|
|
3600
|
+
if (idx === tableBlockIndex) {
|
|
3601
|
+
return clonePublicValue(buildSummary(block, idx));
|
|
3602
|
+
}
|
|
3603
|
+
idx++;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return null;
|
|
3607
|
+
};
|
|
3608
|
+
|
|
3609
|
+
const getTableForSelection = (): PublicTableSummary | null => {
|
|
3610
|
+
const sel = mountedSurface?.getTableSelection() ?? null;
|
|
3611
|
+
if (!sel) return null;
|
|
3612
|
+
return getTable(sel.tableBlockIndex);
|
|
3613
|
+
};
|
|
3614
|
+
|
|
3615
|
+
const getRenderPlan = (
|
|
3616
|
+
tableBlockIndex: number,
|
|
3617
|
+
pageIndex?: number,
|
|
3618
|
+
): PublicTableRenderPlan | null => {
|
|
3619
|
+
const blockId = `table-${tableBlockIndex}`;
|
|
3620
|
+
const effectivePage =
|
|
3621
|
+
pageIndex ??
|
|
3622
|
+
runtime.layout.getFirstPageIndexForBlock(blockId) ??
|
|
3623
|
+
0;
|
|
3624
|
+
const plan = runtime.layout.getTableRenderPlan(blockId, effectivePage);
|
|
3625
|
+
if (!plan) return null;
|
|
3626
|
+
return clonePublicValue({
|
|
3627
|
+
blockId: plan.blockId,
|
|
3628
|
+
pageIndex: plan.pageIndex,
|
|
3629
|
+
columnsTwips: plan.columnsTwips,
|
|
3630
|
+
bandClasses: plan.bandClasses,
|
|
3631
|
+
verticalMerges: plan.verticalMerges,
|
|
3632
|
+
repeatedHeaderRows: plan.repeatedHeaderRows,
|
|
3633
|
+
columnResizeHandles: plan.columnResizeHandles,
|
|
3634
|
+
} satisfies PublicTableRenderPlan);
|
|
3635
|
+
};
|
|
3636
|
+
|
|
3637
|
+
const getColumnWidths = (tableBlockIndex: number): readonly number[] => {
|
|
3638
|
+
const document = runtime.getCanonicalDocument();
|
|
3639
|
+
let idx = 0;
|
|
3640
|
+
for (const block of document.content.children) {
|
|
3641
|
+
if (block.type === "table") {
|
|
3642
|
+
if (idx === tableBlockIndex) {
|
|
3643
|
+
return block.gridColumns;
|
|
3644
|
+
}
|
|
3645
|
+
idx++;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
return [];
|
|
3649
|
+
};
|
|
3650
|
+
|
|
3651
|
+
const getRowHeights = (tableBlockIndex: number): readonly PublicTableRowHeight[] => {
|
|
3652
|
+
const document = runtime.getCanonicalDocument();
|
|
3653
|
+
let idx = 0;
|
|
3654
|
+
for (const block of document.content.children) {
|
|
3655
|
+
if (block.type === "table") {
|
|
3656
|
+
if (idx === tableBlockIndex) {
|
|
3657
|
+
const blockId = `table-${tableBlockIndex}`;
|
|
3658
|
+
const measurement = runtime.layout.getMeasurement(blockId);
|
|
3659
|
+
const totalMeasured = measurement?.heightTwips ?? 0;
|
|
3660
|
+
const rows = block.rows;
|
|
3661
|
+
const perRowFallback = rows.length > 0 ? totalMeasured / rows.length : 0;
|
|
3662
|
+
return rows.map((row): PublicTableRowHeight => ({
|
|
3663
|
+
measured: perRowFallback,
|
|
3664
|
+
...(row.height != null ? { explicit: row.height } : {}),
|
|
3665
|
+
...(row.heightRule != null ? { rule: row.heightRule } : {}),
|
|
3666
|
+
isHeader: row.isHeader ?? false,
|
|
3667
|
+
}));
|
|
3668
|
+
}
|
|
3669
|
+
idx++;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
return [];
|
|
3673
|
+
};
|
|
3674
|
+
|
|
3675
|
+
const getStyleCatalog = (): readonly PublicTableStyle[] => {
|
|
3676
|
+
const document = runtime.getCanonicalDocument();
|
|
3677
|
+
return Object.values(document.styles.tables).map(
|
|
3678
|
+
(style): PublicTableStyle =>
|
|
3679
|
+
clonePublicValue({
|
|
3680
|
+
styleId: style.styleId,
|
|
3681
|
+
displayName: style.displayName,
|
|
3682
|
+
...(style.basedOn != null ? { basedOn: style.basedOn } : {}),
|
|
3683
|
+
isDefault: style.isDefault,
|
|
3684
|
+
}),
|
|
3685
|
+
);
|
|
3686
|
+
};
|
|
3687
|
+
|
|
3688
|
+
const subscribe = (
|
|
3689
|
+
listener: (event: PublicTableEvent) => void,
|
|
3690
|
+
): (() => void) => {
|
|
3691
|
+
const unsubRuntime = runtime.subscribeToEvents((event) => {
|
|
3692
|
+
if (event.type === "dirty_changed" && event.isDirty) {
|
|
3693
|
+
listener({
|
|
3694
|
+
kind: "table_structure_changed",
|
|
3695
|
+
revisionToken: runtime.getRenderSnapshot().revisionToken,
|
|
3696
|
+
});
|
|
3697
|
+
} else if (event.type === "selection_changed") {
|
|
3698
|
+
listener({ kind: "table_capabilities_changed" });
|
|
3699
|
+
}
|
|
3700
|
+
});
|
|
3701
|
+
const unsubLayout = runtime.layout.subscribe((event) => {
|
|
3702
|
+
if (
|
|
3703
|
+
event.kind === "layout_recomputed" ||
|
|
3704
|
+
event.kind === "incremental_relayout"
|
|
3705
|
+
) {
|
|
3706
|
+
listener({
|
|
3707
|
+
kind: "table_render_plan_ready",
|
|
3708
|
+
revision: event.revision,
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
});
|
|
3712
|
+
return () => {
|
|
3713
|
+
unsubRuntime();
|
|
3714
|
+
unsubLayout();
|
|
3715
|
+
};
|
|
3716
|
+
};
|
|
3717
|
+
|
|
3718
|
+
return {
|
|
3719
|
+
apply(op: TableOp): TableOpResult {
|
|
3720
|
+
if (op.kind === "insert") {
|
|
3721
|
+
applyRuntimeInsertTable(runtime, { rows: op.rows, columns: op.columns });
|
|
3722
|
+
return {
|
|
3723
|
+
changed: true,
|
|
3724
|
+
coercedReason: null,
|
|
3725
|
+
capabilities: getCapabilities(),
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
const internal = publicTableOpToInternal(op);
|
|
3729
|
+
const outcome = applyRuntimeTableStructureOperation(
|
|
3730
|
+
runtime,
|
|
3731
|
+
mountedSurface,
|
|
3732
|
+
internal,
|
|
3733
|
+
);
|
|
3734
|
+
return {
|
|
3735
|
+
changed: outcome.changed,
|
|
3736
|
+
coercedReason: outcome.coercedReason,
|
|
3737
|
+
capabilities: getCapabilities(),
|
|
3738
|
+
};
|
|
3739
|
+
},
|
|
3740
|
+
getCapabilities,
|
|
3741
|
+
getTables,
|
|
3742
|
+
getTable,
|
|
3743
|
+
getTableForSelection,
|
|
3744
|
+
getRenderPlan,
|
|
3745
|
+
getColumnWidths,
|
|
3746
|
+
getRowHeights,
|
|
3747
|
+
getStyleCatalog,
|
|
3748
|
+
subscribe,
|
|
3749
|
+
};
|
|
3422
3750
|
}
|
|
3423
3751
|
|
|
3752
|
+
export { buildTablesFacet as __buildTablesFacet };
|
|
3753
|
+
|
|
3424
3754
|
function applyRuntimeTextCommand(
|
|
3425
3755
|
runtime: WordReviewEditorRuntime,
|
|
3426
3756
|
command:
|
|
@@ -3913,6 +4243,25 @@ function clonePublicValue<T>(value: T): T {
|
|
|
3913
4243
|
return structuredClone(value);
|
|
3914
4244
|
}
|
|
3915
4245
|
|
|
4246
|
+
/**
|
|
4247
|
+
* Open the correct header/footer story for a specific page. The page's
|
|
4248
|
+
* resolved `stories.header` / `stories.footer` already carries the
|
|
4249
|
+
* right variant (default / first / even) for that page's section, so we
|
|
4250
|
+
* can hand it directly to `runtime.openStory()` without re-running the
|
|
4251
|
+
* variant-pick logic.
|
|
4252
|
+
*/
|
|
4253
|
+
function openStoryForPage(
|
|
4254
|
+
runtime: WordReviewEditorRuntime,
|
|
4255
|
+
pageIndex: number,
|
|
4256
|
+
kind: "header" | "footer",
|
|
4257
|
+
): void {
|
|
4258
|
+
const page = runtime.layout?.getPage(pageIndex);
|
|
4259
|
+
if (!page) return;
|
|
4260
|
+
const target = kind === "header" ? page.stories.header : page.stories.footer;
|
|
4261
|
+
if (!target) return;
|
|
4262
|
+
runtime.openStory(target);
|
|
4263
|
+
}
|
|
4264
|
+
|
|
3916
4265
|
function openDefaultStoryVariant(
|
|
3917
4266
|
runtime: WordReviewEditorRuntime,
|
|
3918
4267
|
pageLayout: PageLayoutSnapshot | undefined,
|
|
@@ -89,6 +89,10 @@ export interface EditorCommandBag {
|
|
|
89
89
|
onCloseStory?(): void;
|
|
90
90
|
onOpenHeaderStory?(): void;
|
|
91
91
|
onOpenFooterStory?(): void;
|
|
92
|
+
/** Open the header story for a specific page (double-click on its band). */
|
|
93
|
+
onOpenHeaderStoryForPage?(pageIndex: number): void;
|
|
94
|
+
/** Open the footer story for a specific page (double-click on its band). */
|
|
95
|
+
onOpenFooterStoryForPage?(pageIndex: number): void;
|
|
92
96
|
onSetParagraphIndentation?(indentation: {
|
|
93
97
|
left?: number;
|
|
94
98
|
right?: number;
|
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
type DocumentRuntimeEvent,
|
|
35
35
|
type DocumentRuntime,
|
|
36
36
|
} from "../runtime/document-runtime.ts";
|
|
37
|
+
import {
|
|
38
|
+
createRuntimeCommandAppliedBridge,
|
|
39
|
+
type RuntimeCommandAppliedBridge,
|
|
40
|
+
} from "../runtime/collab/runtime-collab-sync.ts";
|
|
37
41
|
import { createInertLayoutFacet } from "../runtime/layout/index.ts";
|
|
38
42
|
import { loadDocxEditorSession } from "../io/docx-session.ts";
|
|
39
43
|
import {
|
|
@@ -71,6 +75,7 @@ export interface CreateRuntimeArgs {
|
|
|
71
75
|
hostAdapter?: EditorHostAdapter;
|
|
72
76
|
datastore?: EditorDatastoreAdapter;
|
|
73
77
|
currentUserId?: string;
|
|
78
|
+
commandAppliedBridge?: RuntimeCommandAppliedBridge;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
interface RuntimeLifecycleHandlers {
|
|
@@ -103,6 +108,7 @@ export interface EditorRuntimeBoundaryState {
|
|
|
103
108
|
runtime: WordReviewEditorRuntime | null;
|
|
104
109
|
loadError: EditorError | null;
|
|
105
110
|
activeRuntime: WordReviewEditorRuntime;
|
|
111
|
+
commandAppliedBridge: RuntimeCommandAppliedBridge;
|
|
106
112
|
fallbackSnapshot: RuntimeRenderSnapshot;
|
|
107
113
|
loadingSessionState: EditorSessionState;
|
|
108
114
|
loadingViewState: EditorViewStateSnapshot;
|
|
@@ -280,6 +286,10 @@ export function useEditorRuntimeBoundary(
|
|
|
280
286
|
const onWarningRef = useRef(onWarning);
|
|
281
287
|
const onErrorRef = useRef(onError);
|
|
282
288
|
const currentUserIdRef = useRef(currentUser.userId);
|
|
289
|
+
const commandAppliedBridge = useMemo(
|
|
290
|
+
() => createRuntimeCommandAppliedBridge(),
|
|
291
|
+
[documentId],
|
|
292
|
+
);
|
|
283
293
|
const runtimeViewStateSeedRef = useRef<{
|
|
284
294
|
workspaceMode: WorkspaceMode;
|
|
285
295
|
zoomLevel: ZoomLevel;
|
|
@@ -374,6 +384,7 @@ export function useEditorRuntimeBoundary(
|
|
|
374
384
|
hostAdapter: hostAdapterRef.current,
|
|
375
385
|
datastore: datastoreRef.current,
|
|
376
386
|
currentUserId: currentUserIdRef.current,
|
|
387
|
+
commandAppliedBridge,
|
|
377
388
|
},
|
|
378
389
|
{
|
|
379
390
|
onWarning: onWarningRef.current,
|
|
@@ -536,6 +547,7 @@ export function useEditorRuntimeBoundary(
|
|
|
536
547
|
runtime,
|
|
537
548
|
loadError,
|
|
538
549
|
activeRuntime: runtime ?? loadingRuntimeBridge,
|
|
550
|
+
commandAppliedBridge,
|
|
539
551
|
fallbackSnapshot,
|
|
540
552
|
loadingSessionState,
|
|
541
553
|
loadingViewState,
|
|
@@ -616,6 +628,7 @@ function createRuntime(
|
|
|
616
628
|
bootstrapEvents.push(event);
|
|
617
629
|
},
|
|
618
630
|
defaultAuthorId: args.currentUserId,
|
|
631
|
+
onCommandApplied: args.commandAppliedBridge?.onCommandApplied,
|
|
619
632
|
}), {
|
|
620
633
|
drainBootstrapEvents: () => bootstrapEvents.splice(0, bootstrapEvents.length),
|
|
621
634
|
emitBlockedCommand: (
|
|
@@ -782,6 +795,7 @@ function createLoadingRuntimeBridge(input: {
|
|
|
782
795
|
],
|
|
783
796
|
}),
|
|
784
797
|
dispatch: () => undefined,
|
|
798
|
+
applyRemoteCommand: () => undefined,
|
|
785
799
|
undo: () => undefined,
|
|
786
800
|
redo: () => undefined,
|
|
787
801
|
focus: () => undefined,
|
|
@@ -807,6 +821,8 @@ function createLoadingRuntimeBridge(input: {
|
|
|
807
821
|
getProtectionSnapshot: () => input.snapshot.protectionSnapshot,
|
|
808
822
|
setWorkspaceMode: () => undefined,
|
|
809
823
|
setZoom: () => undefined,
|
|
824
|
+
setEditorRole: () => undefined,
|
|
825
|
+
setChromePin: () => undefined,
|
|
810
826
|
getPageLayoutSnapshot: () => null,
|
|
811
827
|
getDocumentNavigationSnapshot: () => input.navigation,
|
|
812
828
|
layout: inertLayoutFacet,
|
|
@@ -55,6 +55,12 @@ export interface EditorShellViewProps {
|
|
|
55
55
|
activeRevisionId?: string;
|
|
56
56
|
showTrackedChanges: boolean;
|
|
57
57
|
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
58
|
+
/**
|
|
59
|
+
* Runtime-owned layout facet passed through to the workspace so the
|
|
60
|
+
* ChromeOverlay (scope rail, workflow dock, etc.) can render over the
|
|
61
|
+
* document column and the review rail's Workflow tab can read segments.
|
|
62
|
+
*/
|
|
63
|
+
layoutFacet?: import("../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
58
64
|
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
59
65
|
chromePreset?: WordReviewEditorChromePreset;
|
|
60
66
|
chromeOptions?: Partial<WordReviewEditorChromeOptions>;
|
|
@@ -77,6 +83,10 @@ export interface EditorShellViewProps {
|
|
|
77
83
|
onSelectionToolbarBlurCapture?: React.FocusEventHandler<HTMLDivElement>;
|
|
78
84
|
selectionToolbarRef?: React.Ref<HTMLDivElement>;
|
|
79
85
|
chromeVisibility?: Partial<WordReviewEditorChromeVisibility>;
|
|
86
|
+
/** Review-role sidebar panel: open sidebar to tracked-changes panel. */
|
|
87
|
+
onReviewSidebarTrackedChanges?: () => void;
|
|
88
|
+
/** Review-role sidebar panel: open sidebar to comments panel. */
|
|
89
|
+
onReviewSidebarComments?: () => void;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
export function EditorShellView(props: EditorShellViewProps) {
|
|
@@ -23,7 +23,6 @@ import type { MediaPreviewDescriptor } from "../ui-tailwind/editor-surface/pm-st
|
|
|
23
23
|
|
|
24
24
|
export interface EditorSurfaceControllerProps {
|
|
25
25
|
currentUser: EditorUser;
|
|
26
|
-
ydoc?: import('yjs').Doc;
|
|
27
26
|
awareness?: import("y-protocols/awareness").Awareness;
|
|
28
27
|
snapshot: RuntimeRenderSnapshot;
|
|
29
28
|
canonicalDocument: CanonicalDocumentEnvelope;
|
|
@@ -46,6 +45,8 @@ export interface EditorSurfaceControllerProps {
|
|
|
46
45
|
onDeleteForward?: () => void;
|
|
47
46
|
onInsertTab?: () => void;
|
|
48
47
|
onOutdentTab?: () => void;
|
|
48
|
+
onListIndent?: () => void;
|
|
49
|
+
onListOutdent?: () => void;
|
|
49
50
|
onInsertHardBreak?: () => void;
|
|
50
51
|
onSplitParagraph?: () => void;
|
|
51
52
|
onUndo?: () => void;
|
|
@@ -63,6 +64,13 @@ export interface EditorSurfaceControllerProps {
|
|
|
63
64
|
dispatchRuntimeCommand?: (
|
|
64
65
|
command: import("../ui-tailwind/editor-surface/fast-text-edit-lane.ts").LaneRuntimeCommand,
|
|
65
66
|
) => import("../api/public-types.ts").TextCommandAck;
|
|
67
|
+
layoutFacet?: import("../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
68
|
+
pageChromeHeaderBandPx?: number;
|
|
69
|
+
pageChromeFooterBandPx?: number;
|
|
70
|
+
pageChromeInterGapPx?: number;
|
|
71
|
+
pageBreakRevision?: number;
|
|
72
|
+
onOpenHeaderStoryForPage?: (pageIndex: number) => void;
|
|
73
|
+
onOpenFooterStoryForPage?: (pageIndex: number) => void;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
export const EditorSurfaceController = forwardRef<
|