@beyondwork/docx-react-component 1.0.38 → 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 +183 -6
- package/src/core/commands/table-structure-commands.ts +31 -2
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-numbering.ts +42 -8
- 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/ooxml/parse-fields.ts +10 -3
- 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/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +83 -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-runtime.ts +134 -18
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +2 -0
- package/src/runtime/layout/layout-engine-instance.ts +69 -2
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/page-graph.ts +36 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +342 -28
- package/src/runtime/layout/project-block-fragments.ts +154 -20
- package/src/runtime/layout/public-facet.ts +40 -1
- 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 +21 -1
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/render-kernel.ts +5 -1
- 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/ui/WordReviewEditor.tsx +285 -5
- 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 +4 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +34 -5
- package/src/ui/headless/scoped-chrome-policy.ts +29 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +14 -8
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +7 -10
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +101 -21
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +5 -1
- package/src/ui-tailwind/chrome-overlay/index.ts +0 -6
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +27 -17
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +28 -3
- 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 -78
- 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 +1 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +122 -1
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +36 -3
- package/src/ui-tailwind/tw-review-workspace.tsx +132 -54
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +0 -95
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -50,6 +50,11 @@ import type {
|
|
|
50
50
|
SurfaceInlineSegment,
|
|
51
51
|
TableOp,
|
|
52
52
|
TableOpResult,
|
|
53
|
+
PublicTableEvent,
|
|
54
|
+
PublicTableRenderPlan,
|
|
55
|
+
PublicTableRowHeight,
|
|
56
|
+
PublicTableStyle,
|
|
57
|
+
PublicTableSummary,
|
|
53
58
|
TrackedChangeEntrySnapshot,
|
|
54
59
|
TocRefreshResult,
|
|
55
60
|
UpdateFieldsResult,
|
|
@@ -201,7 +206,12 @@ import {
|
|
|
201
206
|
resolveChromePreset,
|
|
202
207
|
resolveChromeVisibilityForPreset,
|
|
203
208
|
} from "../ui-tailwind/chrome/chrome-preset-model.ts";
|
|
204
|
-
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";
|
|
205
215
|
|
|
206
216
|
export {
|
|
207
217
|
__createFallbackRuntime,
|
|
@@ -508,6 +518,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
508
518
|
setZoom: (level) => {
|
|
509
519
|
runtime.setZoom(level);
|
|
510
520
|
},
|
|
521
|
+
setEditorRole: (role) => {
|
|
522
|
+
runtime.setEditorRole(role);
|
|
523
|
+
},
|
|
524
|
+
setChromePin: (surface, pin) => {
|
|
525
|
+
runtime.setChromePin(surface, pin);
|
|
526
|
+
},
|
|
511
527
|
insertSectionBreak: (type, options) => {
|
|
512
528
|
applyRuntimeInsertSectionBreak(runtime, type, options);
|
|
513
529
|
},
|
|
@@ -642,6 +658,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
642
658
|
onError,
|
|
643
659
|
onEvent,
|
|
644
660
|
onWarning,
|
|
661
|
+
onReviewSidebarTrackedChanges,
|
|
662
|
+
onReviewSidebarComments,
|
|
645
663
|
readOnly = false,
|
|
646
664
|
reviewMode = "review",
|
|
647
665
|
suggestionsEnabled = false,
|
|
@@ -673,6 +691,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
673
691
|
runtime,
|
|
674
692
|
loadError,
|
|
675
693
|
activeRuntime,
|
|
694
|
+
commandAppliedBridge,
|
|
676
695
|
fallbackSnapshot,
|
|
677
696
|
loadingSessionState,
|
|
678
697
|
loadingViewState,
|
|
@@ -993,9 +1012,48 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
993
1012
|
|
|
994
1013
|
useEffect(() => {
|
|
995
1014
|
if (!ydoc || !runtime) return;
|
|
996
|
-
const handle =
|
|
1015
|
+
const handle = createRuntimeCollabSync({
|
|
1016
|
+
ydoc,
|
|
1017
|
+
runtime,
|
|
1018
|
+
authorId: currentUser.userId,
|
|
1019
|
+
commandAppliedBridge,
|
|
1020
|
+
});
|
|
997
1021
|
return () => handle.destroy();
|
|
998
|
-
}, [
|
|
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
|
+
]);
|
|
999
1057
|
|
|
1000
1058
|
useEffect(() => {
|
|
1001
1059
|
runtimeViewStateSeedRef.current = {
|
|
@@ -1331,6 +1389,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1331
1389
|
setZoom: (level) => {
|
|
1332
1390
|
activeRuntime.setZoom(level);
|
|
1333
1391
|
},
|
|
1392
|
+
setEditorRole: (role) => {
|
|
1393
|
+
activeRuntime.setEditorRole(role);
|
|
1394
|
+
},
|
|
1395
|
+
setChromePin: (surface, pin) => {
|
|
1396
|
+
activeRuntime.setChromePin(surface, pin);
|
|
1397
|
+
},
|
|
1334
1398
|
insertSectionBreak: (type, options) => {
|
|
1335
1399
|
applyRuntimeInsertSectionBreak(activeRuntime, type, options);
|
|
1336
1400
|
},
|
|
@@ -2188,6 +2252,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2188
2252
|
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "header"),
|
|
2189
2253
|
onOpenFooterStory: () =>
|
|
2190
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"),
|
|
2191
2259
|
onDeleteSectionBreak: (sectionIndex) =>
|
|
2192
2260
|
applyRuntimeDeleteSectionBreak(activeRuntime, sectionIndex),
|
|
2193
2261
|
onUpdateSectionLayout: (sectionIndex, patch) =>
|
|
@@ -2235,7 +2303,6 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2235
2303
|
<EditorSurfaceController
|
|
2236
2304
|
ref={surfaceRef}
|
|
2237
2305
|
currentUser={currentUser}
|
|
2238
|
-
ydoc={ydoc}
|
|
2239
2306
|
awareness={awareness}
|
|
2240
2307
|
snapshot={snapshot}
|
|
2241
2308
|
canonicalDocument={canonicalDocument}
|
|
@@ -2261,6 +2328,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2261
2328
|
dispatchRuntimeCommand={(command) =>
|
|
2262
2329
|
activeRuntime.applyActiveStoryTextCommand(command as never)
|
|
2263
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
|
+
}
|
|
2264
2341
|
onCommentActivated={(commentId) => {
|
|
2265
2342
|
activeRuntime.openComment(commentId);
|
|
2266
2343
|
setActiveRailTab("comments");
|
|
@@ -2354,6 +2431,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2354
2431
|
selectionToolbarRef={selectionToolbarElementRef}
|
|
2355
2432
|
commands={commands}
|
|
2356
2433
|
document={documentElement}
|
|
2434
|
+
onReviewSidebarTrackedChanges={onReviewSidebarTrackedChanges}
|
|
2435
|
+
onReviewSidebarComments={onReviewSidebarComments}
|
|
2357
2436
|
/>
|
|
2358
2437
|
);
|
|
2359
2438
|
},
|
|
@@ -3453,7 +3532,7 @@ function buildTablesFacet(
|
|
|
3453
3532
|
) {
|
|
3454
3533
|
const getCapabilities = () => {
|
|
3455
3534
|
const snapshot = runtime.getRenderSnapshot();
|
|
3456
|
-
const document = runtime.
|
|
3535
|
+
const document = runtime.getCanonicalDocument();
|
|
3457
3536
|
return (
|
|
3458
3537
|
clonePublicValue(
|
|
3459
3538
|
getTableStructureContext(
|
|
@@ -3464,6 +3543,178 @@ function buildTablesFacet(
|
|
|
3464
3543
|
) ?? null
|
|
3465
3544
|
);
|
|
3466
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
|
+
|
|
3467
3718
|
return {
|
|
3468
3719
|
apply(op: TableOp): TableOpResult {
|
|
3469
3720
|
if (op.kind === "insert") {
|
|
@@ -3487,9 +3738,19 @@ function buildTablesFacet(
|
|
|
3487
3738
|
};
|
|
3488
3739
|
},
|
|
3489
3740
|
getCapabilities,
|
|
3741
|
+
getTables,
|
|
3742
|
+
getTable,
|
|
3743
|
+
getTableForSelection,
|
|
3744
|
+
getRenderPlan,
|
|
3745
|
+
getColumnWidths,
|
|
3746
|
+
getRowHeights,
|
|
3747
|
+
getStyleCatalog,
|
|
3748
|
+
subscribe,
|
|
3490
3749
|
};
|
|
3491
3750
|
}
|
|
3492
3751
|
|
|
3752
|
+
export { buildTablesFacet as __buildTablesFacet };
|
|
3753
|
+
|
|
3493
3754
|
function applyRuntimeTextCommand(
|
|
3494
3755
|
runtime: WordReviewEditorRuntime,
|
|
3495
3756
|
command:
|
|
@@ -3982,6 +4243,25 @@ function clonePublicValue<T>(value: T): T {
|
|
|
3982
4243
|
return structuredClone(value);
|
|
3983
4244
|
}
|
|
3984
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
|
+
|
|
3985
4265
|
function openDefaultStoryVariant(
|
|
3986
4266
|
runtime: WordReviewEditorRuntime,
|
|
3987
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,
|
|
@@ -83,6 +83,10 @@ export interface EditorShellViewProps {
|
|
|
83
83
|
onSelectionToolbarBlurCapture?: React.FocusEventHandler<HTMLDivElement>;
|
|
84
84
|
selectionToolbarRef?: React.Ref<HTMLDivElement>;
|
|
85
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;
|
|
86
90
|
}
|
|
87
91
|
|
|
88
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<
|
|
@@ -31,6 +31,8 @@ export type ToolbarChromeItemId =
|
|
|
31
31
|
| "export"
|
|
32
32
|
// R1: role-scoped action region entries
|
|
33
33
|
| "editor-scope-posture-menu"
|
|
34
|
+
| "review-sidebar-tracked-changes"
|
|
35
|
+
| "review-sidebar-comments"
|
|
34
36
|
| "review-queue-prev"
|
|
35
37
|
| "review-queue-next"
|
|
36
38
|
| "review-queue-counts"
|
|
@@ -72,7 +74,7 @@ export interface ToolbarChromeRegistryEntry extends ChromeRegistryEntryBase {
|
|
|
72
74
|
roles: ReadonlyArray<EditorRole>;
|
|
73
75
|
fullPlacement: Exclude<ToolbarChromePlacement, "hidden">;
|
|
74
76
|
compactPlacement: ToolbarChromePlacement;
|
|
75
|
-
runtimeBehavior: "always" | "formatting" | "structure" | "comment";
|
|
77
|
+
runtimeBehavior: "always" | "formatting" | "structure" | "comment" | "sidebar-panel";
|
|
76
78
|
scopeBehavior?: "default" | "scoped-only" | "hidden-when-scoped";
|
|
77
79
|
/**
|
|
78
80
|
* Optional per-role placement override. When a role overrides the
|
|
@@ -265,6 +267,9 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
265
267
|
surfaces: ["top-toolbar"],
|
|
266
268
|
group: "review",
|
|
267
269
|
presets: ["simple", "advanced", "review", "workflow"],
|
|
270
|
+
// Visible in every role, but editor/review roles render it inside the
|
|
271
|
+
// role action region (see ROLE_ACTION_SETS) and the right cluster
|
|
272
|
+
// suppresses it via `isChromeItemOwnedByRoleRegion` to avoid duplication.
|
|
268
273
|
roles: ALL_ROLES,
|
|
269
274
|
fullPlacement: "inline",
|
|
270
275
|
compactPlacement: "inline",
|
|
@@ -274,9 +279,8 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
274
279
|
id: "tracked-changes-toggle",
|
|
275
280
|
surfaces: ["top-toolbar"],
|
|
276
281
|
group: "review",
|
|
277
|
-
// R1 promotes this toggle to every preset that shows a toolbar so the
|
|
278
|
-
// editor role can see inline tracked changes without switching preset.
|
|
279
282
|
presets: ["simple", "advanced", "review", "workflow"],
|
|
283
|
+
// Same ownership rule as `comment` — editor/review role regions own it.
|
|
280
284
|
roles: ALL_ROLES,
|
|
281
285
|
fullPlacement: "inline",
|
|
282
286
|
compactPlacement: "inline",
|
|
@@ -323,18 +327,43 @@ export const TOOLBAR_CHROME_REGISTRY: ReadonlyArray<ToolbarChromeRegistryEntry>
|
|
|
323
327
|
runtimeBehavior: "always",
|
|
324
328
|
},
|
|
325
329
|
|
|
326
|
-
// ───── R1:
|
|
330
|
+
// ───── R1: Workflow-role scope posture menu ───────────────────────────
|
|
327
331
|
{
|
|
328
332
|
id: "editor-scope-posture-menu",
|
|
329
333
|
surfaces: ["top-toolbar"],
|
|
330
334
|
group: "scope",
|
|
331
335
|
presets: ["advanced", "review", "workflow"],
|
|
332
|
-
|
|
336
|
+
// Scoping/tagging is a workflow-primary action; moved from editor role.
|
|
337
|
+
roles: WORKFLOW_ONLY,
|
|
333
338
|
fullPlacement: "inline",
|
|
334
339
|
compactPlacement: "overflow",
|
|
335
340
|
runtimeBehavior: "always",
|
|
336
341
|
},
|
|
337
342
|
|
|
343
|
+
// ───── R1: Review-role sidebar panel toggles (optional, host-provided) ─
|
|
344
|
+
{
|
|
345
|
+
id: "review-sidebar-tracked-changes",
|
|
346
|
+
surfaces: ["top-toolbar"],
|
|
347
|
+
group: "review-sidebar",
|
|
348
|
+
presets: ["review"],
|
|
349
|
+
roles: REVIEW_ONLY,
|
|
350
|
+
fullPlacement: "inline",
|
|
351
|
+
compactPlacement: "inline",
|
|
352
|
+
// Hidden unless the host provides the sidebar panel callback via
|
|
353
|
+
// hasSidebarPanelAccess in ResolveScopedChromePolicyInput.
|
|
354
|
+
runtimeBehavior: "sidebar-panel",
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: "review-sidebar-comments",
|
|
358
|
+
surfaces: ["top-toolbar"],
|
|
359
|
+
group: "review-sidebar",
|
|
360
|
+
presets: ["review"],
|
|
361
|
+
roles: REVIEW_ONLY,
|
|
362
|
+
fullPlacement: "inline",
|
|
363
|
+
compactPlacement: "inline",
|
|
364
|
+
runtimeBehavior: "sidebar-panel",
|
|
365
|
+
},
|
|
366
|
+
|
|
338
367
|
// ───── R1: Review-role primaries ──────────────────────────────────────
|
|
339
368
|
{
|
|
340
369
|
id: "review-queue-prev",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type ToolbarChromeItemId,
|
|
12
12
|
type ToolbarChromePlacement,
|
|
13
13
|
} from "./chrome-registry";
|
|
14
|
+
import { ROLE_ACTION_SETS } from "../../ui-tailwind/chrome/role-action-sets";
|
|
14
15
|
import type { SelectionToolKind } from "./selection-tool-types";
|
|
15
16
|
|
|
16
17
|
export interface ToolbarChromeItemPolicy {
|
|
@@ -44,6 +45,13 @@ export interface ResolveScopedChromePolicyInput {
|
|
|
44
45
|
* callers that haven't adopted the role model yet).
|
|
45
46
|
*/
|
|
46
47
|
role?: EditorRole;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the host has wired sidebar-panel callbacks
|
|
50
|
+
* (`onReviewSidebarTrackedChanges` / `onReviewSidebarComments`).
|
|
51
|
+
* Defaults to `false` — sidebar panel buttons are hidden unless the host
|
|
52
|
+
* explicitly opts in (typically the harness).
|
|
53
|
+
*/
|
|
54
|
+
hasSidebarPanelAccess?: boolean;
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
export function resolveScopedChromePolicy(
|
|
@@ -88,6 +96,9 @@ export function resolveScopedChromePolicy(
|
|
|
88
96
|
case "comment":
|
|
89
97
|
visible = canAddComment;
|
|
90
98
|
break;
|
|
99
|
+
case "sidebar-panel":
|
|
100
|
+
visible = Boolean(input.hasSidebarPanelAccess);
|
|
101
|
+
break;
|
|
91
102
|
default:
|
|
92
103
|
visible = true;
|
|
93
104
|
break;
|
|
@@ -152,6 +163,24 @@ export function isToolbarChromeItemVisible(
|
|
|
152
163
|
return policy.toolbar[itemId]?.visible ?? false;
|
|
153
164
|
}
|
|
154
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Returns true when the given chrome item is owned by the active role's
|
|
168
|
+
* inline role-action region (i.e. it appears in `ROLE_ACTION_SETS[role]`).
|
|
169
|
+
* The right-cluster render path uses this to suppress its own copy so we
|
|
170
|
+
* don't render the same button twice.
|
|
171
|
+
*
|
|
172
|
+
* When `role` is undefined (legacy host, no role model adopted) this
|
|
173
|
+
* returns `false` — the right cluster keeps ownership per the pre-R1
|
|
174
|
+
* layout.
|
|
175
|
+
*/
|
|
176
|
+
export function isChromeItemOwnedByRoleRegion(
|
|
177
|
+
itemId: ToolbarChromeItemId,
|
|
178
|
+
role: EditorRole | undefined,
|
|
179
|
+
): boolean {
|
|
180
|
+
if (!role) return false;
|
|
181
|
+
return ROLE_ACTION_SETS[role].includes(itemId);
|
|
182
|
+
}
|
|
183
|
+
|
|
155
184
|
export function getToolbarChromePlacement(
|
|
156
185
|
policy: ScopedChromePolicy,
|
|
157
186
|
itemId: ToolbarChromeItemId,
|