@beyondwork/docx-react-component 1.0.22 → 1.0.24-rc
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 +81 -38
- package/package.json +1 -1
- package/src/api/public-types.ts +67 -1
- package/src/core/commands/index.ts +625 -5
- package/src/index.ts +5 -0
- package/src/io/docx-session.ts +181 -2
- package/src/io/export/serialize-main-document.ts +21 -1
- package/src/io/normalize/normalize-text.ts +4 -0
- package/src/io/ooxml/parse-main-document.ts +88 -7
- package/src/model/canonical-document.ts +22 -0
- package/src/review/store/revision-store.ts +1 -0
- package/src/review/store/revision-types.ts +2 -0
- package/src/runtime/document-runtime.ts +503 -51
- package/src/runtime/session-capabilities.ts +6 -5
- package/src/runtime/surface-projection.ts +2 -0
- package/src/runtime/table-schema.ts +2 -0
- package/src/runtime/workflow-markup.ts +5 -1
- package/src/ui/WordReviewEditor.tsx +667 -132
- package/src/ui/editor-runtime-boundary.ts +10 -1
- package/src/ui/editor-shell-view.tsx +8 -0
- package/src/ui/editor-surface-controller.tsx +6 -0
- package/src/ui/headless/selection-toolbar-model.ts +12 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +6 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +96 -28
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +2 -0
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +6 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +82 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +4 -1
- package/src/ui-tailwind/theme/editor-theme.css +10 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +21 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +110 -32
|
@@ -41,12 +41,14 @@ import type {
|
|
|
41
41
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
42
42
|
StyleCatalogSnapshot,
|
|
43
43
|
SurfaceBlockSnapshot,
|
|
44
|
+
TrackedChangeEntrySnapshot,
|
|
44
45
|
TocRefreshResult,
|
|
45
46
|
UpdateFieldsResult,
|
|
46
47
|
ViewMode as EditorViewMode,
|
|
47
48
|
WorkflowBlockedCommandReason,
|
|
48
49
|
WorkflowMarkupSnapshot,
|
|
49
50
|
WorkflowScopeSnapshot,
|
|
51
|
+
WordReviewEditorChromeVisibility,
|
|
50
52
|
WordReviewEditorEvent,
|
|
51
53
|
WordReviewEditorProps,
|
|
52
54
|
WordReviewEditorRef,
|
|
@@ -150,6 +152,7 @@ import type { MarkupDisplay } from "./headless/comment-decoration-model";
|
|
|
150
152
|
import type {
|
|
151
153
|
SelectionToolbarAnchor,
|
|
152
154
|
SelectionToolbarModel,
|
|
155
|
+
SuggestionCardModel,
|
|
153
156
|
} from "./headless/selection-toolbar-model";
|
|
154
157
|
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
155
158
|
import { deriveVisibleWorkflowBlockedRails } from "./workflow-surface-blocked-rails.ts";
|
|
@@ -464,6 +467,15 @@ export function __createWordReviewEditorRefBridge(
|
|
|
464
467
|
getWorkflowMarkupSnapshot: () => {
|
|
465
468
|
return clonePublicValue(runtime.getWorkflowMarkupSnapshot());
|
|
466
469
|
},
|
|
470
|
+
setHostAnnotationOverlay: (overlay) => {
|
|
471
|
+
runtime.setHostAnnotationOverlay(clonePublicValue(overlay));
|
|
472
|
+
},
|
|
473
|
+
clearHostAnnotationOverlay: () => {
|
|
474
|
+
runtime.clearHostAnnotationOverlay();
|
|
475
|
+
},
|
|
476
|
+
getHostAnnotationSnapshot: () => {
|
|
477
|
+
return clonePublicValue(runtime.getHostAnnotationSnapshot());
|
|
478
|
+
},
|
|
467
479
|
getWorkflowCandidateRanges: (options) => {
|
|
468
480
|
return clonePublicValue(runtime.getWorkflowCandidateRanges(options));
|
|
469
481
|
},
|
|
@@ -473,6 +485,20 @@ export function __createWordReviewEditorRefBridge(
|
|
|
473
485
|
};
|
|
474
486
|
}
|
|
475
487
|
|
|
488
|
+
export function __applyRuntimeTextCommand(
|
|
489
|
+
runtime: WordReviewEditorRuntime,
|
|
490
|
+
command:
|
|
491
|
+
| { type: "insert-text"; text: string }
|
|
492
|
+
| { type: "delete-backward" }
|
|
493
|
+
| { type: "delete-forward" }
|
|
494
|
+
| { type: "insert-tab" }
|
|
495
|
+
| { type: "outdent-tab" }
|
|
496
|
+
| { type: "insert-hard-break" }
|
|
497
|
+
| { type: "split-paragraph" },
|
|
498
|
+
): void {
|
|
499
|
+
applyRuntimeTextCommand(runtime, command);
|
|
500
|
+
}
|
|
501
|
+
|
|
476
502
|
export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditorProps>(
|
|
477
503
|
function WordReviewEditor(props, ref) {
|
|
478
504
|
const {
|
|
@@ -494,12 +520,15 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
494
520
|
onWarning,
|
|
495
521
|
readOnly = false,
|
|
496
522
|
reviewMode = "review",
|
|
523
|
+
suggestionsEnabled = false,
|
|
497
524
|
showReviewPanel = true,
|
|
525
|
+
chromeVisibility,
|
|
498
526
|
} = props;
|
|
499
527
|
|
|
500
528
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
501
529
|
const [showTrackedChanges, setShowTrackedChanges] = useState(false);
|
|
502
530
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
531
|
+
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
503
532
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
504
533
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
505
534
|
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
@@ -644,7 +673,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
644
673
|
getValue: () => runtime.getInteractionGuardSnapshot(),
|
|
645
674
|
}
|
|
646
675
|
: null,
|
|
647
|
-
{ blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
676
|
+
{ effectiveMode: "edit", blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
648
677
|
interactionGuardSnapshotsEqual,
|
|
649
678
|
);
|
|
650
679
|
const workflowMarkupSnapshot = useMemo(
|
|
@@ -655,17 +684,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
655
684
|
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
656
685
|
[snapshot.surface, workflowMarkupSnapshot],
|
|
657
686
|
);
|
|
658
|
-
const
|
|
659
|
-
() => (runtime ? runtime.
|
|
660
|
-
[loadingSessionState, runtime, snapshot.revisionToken],
|
|
687
|
+
const canonicalDocument = useMemo(
|
|
688
|
+
() => (runtime ? runtime.getCanonicalDocument() : loadingSessionState.canonicalDocument),
|
|
689
|
+
[loadingSessionState.canonicalDocument, runtime, snapshot.revisionToken],
|
|
661
690
|
);
|
|
662
|
-
const canonicalDocument = sessionState.canonicalDocument;
|
|
663
691
|
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
664
692
|
|
|
665
693
|
useEffect(() => {
|
|
666
694
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
667
695
|
}, [activeRuntime, effectiveViewMode]);
|
|
668
696
|
|
|
697
|
+
useEffect(() => {
|
|
698
|
+
activeRuntime.setDocumentMode(suggestionsEnabled ? "suggesting" : "editing");
|
|
699
|
+
}, [activeRuntime, suggestionsEnabled]);
|
|
700
|
+
|
|
669
701
|
useEffect(() => {
|
|
670
702
|
runtimeViewStateSeedRef.current = {
|
|
671
703
|
workspaceMode: viewState.workspaceMode,
|
|
@@ -901,14 +933,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
901
933
|
(r) => r.revisionId === revisionId,
|
|
902
934
|
);
|
|
903
935
|
if (!revision || revision.anchor.kind === "detached") return;
|
|
904
|
-
applyRuntimeSelection(
|
|
936
|
+
applyRuntimeSelection(
|
|
937
|
+
activeRuntime,
|
|
938
|
+
createSelectionFromAnchor(revision.anchor, revision.storyTarget),
|
|
939
|
+
);
|
|
905
940
|
},
|
|
906
941
|
scrollToComment: (commentId: string) => {
|
|
907
942
|
const comment = activeRuntime.getRenderSnapshot().comments.threads.find(
|
|
908
943
|
(t) => t.commentId === commentId,
|
|
909
944
|
);
|
|
910
945
|
if (!comment || comment.anchor.kind === "detached") return;
|
|
911
|
-
applyRuntimeSelection(
|
|
946
|
+
applyRuntimeSelection(
|
|
947
|
+
activeRuntime,
|
|
948
|
+
createSelectionFromAnchor(comment.anchor),
|
|
949
|
+
);
|
|
912
950
|
},
|
|
913
951
|
openStory: (target: EditorStoryTarget) => {
|
|
914
952
|
activeRuntime.openStory(target);
|
|
@@ -982,6 +1020,15 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
982
1020
|
getWorkflowMarkupSnapshot: () => {
|
|
983
1021
|
return clonePublicValue(activeRuntime.getWorkflowMarkupSnapshot());
|
|
984
1022
|
},
|
|
1023
|
+
setHostAnnotationOverlay: (overlay) => {
|
|
1024
|
+
activeRuntime.setHostAnnotationOverlay(clonePublicValue(overlay));
|
|
1025
|
+
},
|
|
1026
|
+
clearHostAnnotationOverlay: () => {
|
|
1027
|
+
activeRuntime.clearHostAnnotationOverlay();
|
|
1028
|
+
},
|
|
1029
|
+
getHostAnnotationSnapshot: () => {
|
|
1030
|
+
return clonePublicValue(activeRuntime.getHostAnnotationSnapshot());
|
|
1031
|
+
},
|
|
985
1032
|
getWorkflowCandidateRanges: (options) => {
|
|
986
1033
|
return clonePublicValue(activeRuntime.getWorkflowCandidateRanges(options));
|
|
987
1034
|
},
|
|
@@ -1080,11 +1127,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1080
1127
|
snapshot.trackedChanges.totalCount,
|
|
1081
1128
|
]);
|
|
1082
1129
|
|
|
1083
|
-
function focusAnchor(
|
|
1130
|
+
function focusAnchor(
|
|
1131
|
+
anchor: PublicSelectionSnapshot["activeRange"],
|
|
1132
|
+
storyTarget?: EditorStoryTarget,
|
|
1133
|
+
): void {
|
|
1084
1134
|
if (anchor.kind === "detached") {
|
|
1085
1135
|
return;
|
|
1086
1136
|
}
|
|
1087
|
-
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(anchor));
|
|
1137
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(anchor, storyTarget));
|
|
1088
1138
|
}
|
|
1089
1139
|
|
|
1090
1140
|
function addReviewComment(): string | null {
|
|
@@ -1129,7 +1179,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1129
1179
|
reviewMode,
|
|
1130
1180
|
workflowScopeSnapshot,
|
|
1131
1181
|
);
|
|
1132
|
-
const
|
|
1182
|
+
const resolvedChromeVisibility = resolveWordReviewEditorChromeVisibility(
|
|
1183
|
+
chromeVisibility,
|
|
1184
|
+
showReviewPanel,
|
|
1185
|
+
);
|
|
1186
|
+
const capabilities = resolvedChromeVisibility.reviewRail
|
|
1133
1187
|
? derivedCapabilities
|
|
1134
1188
|
: { ...derivedCapabilities, reviewRailVisible: false };
|
|
1135
1189
|
const formattingState = getFormattingStateFromRenderSnapshot(snapshot);
|
|
@@ -1152,7 +1206,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1152
1206
|
}),
|
|
1153
1207
|
[canonicalDocument, snapshot.selection, snapshot.surface, viewState.activeStory],
|
|
1154
1208
|
);
|
|
1155
|
-
const sourcePackage =
|
|
1209
|
+
const sourcePackage = runtime
|
|
1210
|
+
? runtime.getSourcePackage()
|
|
1211
|
+
: loadingSessionState.sourcePackage;
|
|
1156
1212
|
const mediaPreviewCatalogKey = Object.values(canonicalDocument.media.items)
|
|
1157
1213
|
.map((item) =>
|
|
1158
1214
|
[
|
|
@@ -1214,14 +1270,26 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1214
1270
|
documentNavigation,
|
|
1215
1271
|
styleCatalog,
|
|
1216
1272
|
formattingState,
|
|
1273
|
+
workflowScopeSnapshot,
|
|
1274
|
+
interactionGuardSnapshot,
|
|
1275
|
+
addCommentDisabledReason,
|
|
1276
|
+
});
|
|
1277
|
+
const suggestionCard = buildSuggestionCardModel({
|
|
1278
|
+
snapshot,
|
|
1279
|
+
viewState,
|
|
1280
|
+
capabilities,
|
|
1281
|
+
workflowScopeSnapshot,
|
|
1282
|
+
interactionGuardSnapshot,
|
|
1283
|
+
activeRevisionId,
|
|
1284
|
+
suppressedSuggestionRevisionId,
|
|
1217
1285
|
addCommentDisabledReason,
|
|
1218
1286
|
});
|
|
1219
1287
|
const selectionToolbarSelectionKey = useMemo(
|
|
1220
|
-
() => createSelectionToolbarSelectionKey(snapshot.selection, viewState.activeStory),
|
|
1221
|
-
[snapshot.selection, viewState.activeStory],
|
|
1288
|
+
() => createSelectionToolbarSelectionKey(snapshot.selection, viewState.activeStory, activeRevisionId),
|
|
1289
|
+
[activeRevisionId, snapshot.selection, viewState.activeStory],
|
|
1222
1290
|
);
|
|
1223
|
-
const
|
|
1224
|
-
selectionToolbar &&
|
|
1291
|
+
const shouldRenderSelectionChrome = Boolean(
|
|
1292
|
+
(selectionToolbar || suggestionCard) &&
|
|
1225
1293
|
selectionToolbarSelectionKey &&
|
|
1226
1294
|
selectionToolbarDismissedKey !== selectionToolbarSelectionKey &&
|
|
1227
1295
|
(viewState.isFocused || selectionToolbarFocusWithin),
|
|
@@ -1341,6 +1409,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1341
1409
|
|
|
1342
1410
|
useEffect(() => {
|
|
1343
1411
|
if (!selectionToolbarSelectionKey) {
|
|
1412
|
+
setSuppressedSuggestionRevisionId(null);
|
|
1344
1413
|
setSelectionToolbarDismissedKey(null);
|
|
1345
1414
|
setSelectionToolbarFocusWithin(false);
|
|
1346
1415
|
setSelectionToolbarAnchor(null);
|
|
@@ -1350,17 +1419,18 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1350
1419
|
|
|
1351
1420
|
if (lastSelectionToolbarKeyRef.current !== selectionToolbarSelectionKey) {
|
|
1352
1421
|
lastSelectionToolbarKeyRef.current = selectionToolbarSelectionKey;
|
|
1422
|
+
setSuppressedSuggestionRevisionId(null);
|
|
1353
1423
|
setSelectionToolbarDismissedKey(null);
|
|
1354
1424
|
setSelectionToolbarFocusWithin(false);
|
|
1355
1425
|
}
|
|
1356
1426
|
}, [selectionToolbarSelectionKey]);
|
|
1357
1427
|
|
|
1358
1428
|
useEffect(() => {
|
|
1359
|
-
if (!selectionToolbar) {
|
|
1429
|
+
if (!selectionToolbar && !suggestionCard) {
|
|
1360
1430
|
setSelectionToolbarAnchor(null);
|
|
1361
1431
|
setSelectionToolbarFocusWithin(false);
|
|
1362
1432
|
}
|
|
1363
|
-
}, [selectionToolbar]);
|
|
1433
|
+
}, [selectionToolbar, suggestionCard]);
|
|
1364
1434
|
|
|
1365
1435
|
useEffect(() => {
|
|
1366
1436
|
const shell = shellRef.current;
|
|
@@ -1389,9 +1459,26 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1389
1459
|
}, [loadError, snapshot.fatalError]);
|
|
1390
1460
|
|
|
1391
1461
|
function handleShellKeyDownCapture(event: React.KeyboardEvent<HTMLDivElement>): void {
|
|
1462
|
+
const targetWithinDocument = isTargetWithinDocumentSurface(event.target);
|
|
1463
|
+
const isUndoShortcut = (event.ctrlKey || event.metaKey) && !event.shiftKey && event.key.toLowerCase() === "z";
|
|
1464
|
+
const isRedoShortcut =
|
|
1465
|
+
((event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === "z") ||
|
|
1466
|
+
(event.ctrlKey && event.key.toLowerCase() === "y");
|
|
1467
|
+
|
|
1468
|
+
if ((isUndoShortcut || isRedoShortcut) && targetWithinDocument) {
|
|
1469
|
+
event.preventDefault();
|
|
1470
|
+
event.stopPropagation();
|
|
1471
|
+
if (isUndoShortcut) {
|
|
1472
|
+
activeRuntime.undo();
|
|
1473
|
+
} else {
|
|
1474
|
+
activeRuntime.redo();
|
|
1475
|
+
}
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1392
1479
|
if (
|
|
1393
1480
|
event.key === "Escape" &&
|
|
1394
|
-
|
|
1481
|
+
shouldRenderSelectionChrome &&
|
|
1395
1482
|
(isTargetWithinDocumentSurface(event.target) || isTargetWithinSelectionToolbar(event.target))
|
|
1396
1483
|
) {
|
|
1397
1484
|
event.preventDefault();
|
|
@@ -1430,6 +1517,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1430
1517
|
onOutdentTab: () => applyRuntimeTextCommand(activeRuntime, { type: "outdent-tab" }),
|
|
1431
1518
|
onInsertHardBreak: () => applyRuntimeTextCommand(activeRuntime, { type: "insert-hard-break" }),
|
|
1432
1519
|
onSplitParagraph: () => applyRuntimeTextCommand(activeRuntime, { type: "split-paragraph" }),
|
|
1520
|
+
onUndo: () => activeRuntime.undo(),
|
|
1521
|
+
onRedo: () => activeRuntime.redo(),
|
|
1522
|
+
onBlockedInput: (command: "paste" | "drop", message: string) =>
|
|
1523
|
+
activeRuntime.emitBlockedCommand(command, [{
|
|
1524
|
+
code: "unsupported_surface",
|
|
1525
|
+
message,
|
|
1526
|
+
}]),
|
|
1433
1527
|
};
|
|
1434
1528
|
|
|
1435
1529
|
const reviewCallbacks = {
|
|
@@ -1458,7 +1552,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1458
1552
|
},
|
|
1459
1553
|
onOpenRevision: (revision: typeof snapshot.trackedChanges.revisions[number]) => {
|
|
1460
1554
|
setActiveRevisionId(revision.revisionId);
|
|
1461
|
-
focusAnchor(revision.anchor);
|
|
1555
|
+
focusAnchor(revision.anchor, revision.storyTarget);
|
|
1462
1556
|
setActiveRailTab("changes");
|
|
1463
1557
|
},
|
|
1464
1558
|
onAcceptRevision: (revisionId: string) => {
|
|
@@ -1618,11 +1712,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1618
1712
|
markupDisplay={liveMarkupDisplay}
|
|
1619
1713
|
activeRevisionId={activeRevisionId}
|
|
1620
1714
|
showTrackedChanges={showTrackedChanges}
|
|
1715
|
+
suggestionsEnabled={suggestionsEnabled}
|
|
1621
1716
|
mediaPreviews={mediaPreviews}
|
|
1622
1717
|
isPageWorkspace={isPageWorkspace}
|
|
1623
1718
|
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
1624
1719
|
workflowCandidates={workflowScopeSnapshot?.candidates}
|
|
1625
1720
|
workflowBlockedReasons={workflowBlockedRails}
|
|
1721
|
+
activeWorkflowWorkItemId={workflowScopeSnapshot?.activeWorkItemId ?? null}
|
|
1722
|
+
activeWorkflowScopeIds={workflowScopeSnapshot?.activeWorkItem?.scopeIds ?? []}
|
|
1626
1723
|
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1627
1724
|
{...editorCallbacks}
|
|
1628
1725
|
onCommentActivated={(commentId) => {
|
|
@@ -1656,6 +1753,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1656
1753
|
markupDisplay={liveMarkupDisplay}
|
|
1657
1754
|
currentUserId={currentUser.userId}
|
|
1658
1755
|
capabilities={capabilities}
|
|
1756
|
+
chromeVisibility={resolvedChromeVisibility}
|
|
1659
1757
|
documentNavigation={documentNavigation}
|
|
1660
1758
|
reviewMode={reviewMode}
|
|
1661
1759
|
workspaceMode={viewState.workspaceMode}
|
|
@@ -1668,9 +1766,28 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1668
1766
|
showTrackedChanges={showTrackedChanges}
|
|
1669
1767
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
1670
1768
|
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
1671
|
-
selectionToolbar={
|
|
1672
|
-
|
|
1769
|
+
selectionToolbar={shouldRenderSelectionChrome ? selectionToolbar : null}
|
|
1770
|
+
suggestionCard={shouldRenderSelectionChrome ? suggestionCard : null}
|
|
1771
|
+
selectionToolbarAnchor={shouldRenderSelectionChrome ? selectionToolbarAnchor : null}
|
|
1673
1772
|
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1773
|
+
onAddCommentFromSuggestion={addSelectionToolbarComment}
|
|
1774
|
+
onAcceptSuggestion={suggestionCard
|
|
1775
|
+
? () => {
|
|
1776
|
+
activeRuntime.acceptChange(suggestionCard.revisionId);
|
|
1777
|
+
dismissSelectionToolbar("chrome-action");
|
|
1778
|
+
}
|
|
1779
|
+
: undefined}
|
|
1780
|
+
onRejectSuggestion={suggestionCard
|
|
1781
|
+
? () => {
|
|
1782
|
+
activeRuntime.rejectChange(suggestionCard.revisionId);
|
|
1783
|
+
dismissSelectionToolbar("chrome-action");
|
|
1784
|
+
}
|
|
1785
|
+
: undefined}
|
|
1786
|
+
onEditSuggestion={suggestionCard
|
|
1787
|
+
? () => {
|
|
1788
|
+
setSuppressedSuggestionRevisionId(suggestionCard.revisionId);
|
|
1789
|
+
}
|
|
1790
|
+
: undefined}
|
|
1674
1791
|
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1675
1792
|
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1676
1793
|
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
@@ -1696,7 +1813,10 @@ function applyRuntimeFormattingOperation(
|
|
|
1696
1813
|
| { type: "indent" }
|
|
1697
1814
|
| { type: "outdent" },
|
|
1698
1815
|
): void {
|
|
1699
|
-
|
|
1816
|
+
if (emitSuggestingUnsupportedMutation(runtime, getFormattingOperationCommandName(operation))) {
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
const context = getStoryMutationContext(runtime, getFormattingOperationCommandName(operation));
|
|
1700
1820
|
if (!context) {
|
|
1701
1821
|
return;
|
|
1702
1822
|
}
|
|
@@ -1764,7 +1884,10 @@ function applyRuntimeParagraphStyle(
|
|
|
1764
1884
|
runtime: WordReviewEditorRuntime,
|
|
1765
1885
|
styleId: string | null,
|
|
1766
1886
|
): void {
|
|
1767
|
-
|
|
1887
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphStyle")) {
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const context = getStoryMutationContext(runtime, "setParagraphStyle");
|
|
1768
1891
|
if (!context) {
|
|
1769
1892
|
return;
|
|
1770
1893
|
}
|
|
@@ -1789,7 +1912,10 @@ function applyRuntimeTableStyle(
|
|
|
1789
1912
|
runtime: WordReviewEditorRuntime,
|
|
1790
1913
|
styleId: string | null,
|
|
1791
1914
|
): void {
|
|
1792
|
-
|
|
1915
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setTableStyle")) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const context = getStoryMutationContext(runtime, "setTableStyle");
|
|
1793
1919
|
if (!context) {
|
|
1794
1920
|
return;
|
|
1795
1921
|
}
|
|
@@ -1819,7 +1945,10 @@ function applyRuntimeParagraphIndentation(
|
|
|
1819
1945
|
hanging?: number;
|
|
1820
1946
|
},
|
|
1821
1947
|
): void {
|
|
1822
|
-
|
|
1948
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphIndentation")) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
const context = getStoryMutationContext(runtime, "setParagraphIndentation");
|
|
1823
1952
|
if (!context) {
|
|
1824
1953
|
return;
|
|
1825
1954
|
}
|
|
@@ -1845,7 +1974,10 @@ function applyRuntimeParagraphTabStops(
|
|
|
1845
1974
|
runtime: WordReviewEditorRuntime,
|
|
1846
1975
|
tabStops: Array<{ pos: number; val?: string; leader?: string }>,
|
|
1847
1976
|
): void {
|
|
1848
|
-
|
|
1977
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphTabStops")) {
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const context = getStoryMutationContext(runtime, "setParagraphTabStops");
|
|
1849
1981
|
if (!context) {
|
|
1850
1982
|
return;
|
|
1851
1983
|
}
|
|
@@ -1871,7 +2003,18 @@ function applyRuntimeNumberingFlow(
|
|
|
1871
2003
|
runtime: WordReviewEditorRuntime,
|
|
1872
2004
|
operation: { type: "restart"; startAt?: number } | { type: "continue" },
|
|
1873
2005
|
): void {
|
|
1874
|
-
|
|
2006
|
+
if (
|
|
2007
|
+
emitSuggestingUnsupportedMutation(
|
|
2008
|
+
runtime,
|
|
2009
|
+
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
2010
|
+
)
|
|
2011
|
+
) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
const context = getStoryMutationContext(
|
|
2015
|
+
runtime,
|
|
2016
|
+
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
2017
|
+
);
|
|
1875
2018
|
if (!context) {
|
|
1876
2019
|
return;
|
|
1877
2020
|
}
|
|
@@ -1916,7 +2059,10 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1916
2059
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1917
2060
|
return;
|
|
1918
2061
|
}
|
|
1919
|
-
if (
|
|
2062
|
+
if (emitWorkflowBlockedMutation(runtime, "insertSectionBreak")) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
if (isSelectionSuggesting(runtime)) {
|
|
1920
2066
|
runtime.emitBlockedCommand("insertSectionBreak", [{
|
|
1921
2067
|
code: "unsupported_surface",
|
|
1922
2068
|
message: "Section break insertion is not supported in suggesting mode.",
|
|
@@ -1952,6 +2098,56 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1952
2098
|
);
|
|
1953
2099
|
}
|
|
1954
2100
|
|
|
2101
|
+
function emitSuggestingUnsupportedMutation(
|
|
2102
|
+
runtime: WordReviewEditorRuntime,
|
|
2103
|
+
command: string,
|
|
2104
|
+
): boolean {
|
|
2105
|
+
if (!isSelectionSuggesting(runtime)) {
|
|
2106
|
+
return false;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
runtime.emitBlockedCommand(command, [{
|
|
2110
|
+
code: "suggesting_unsupported",
|
|
2111
|
+
message: `"${command}" is not supported in suggesting mode.`,
|
|
2112
|
+
}]);
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
function isSelectionSuggesting(runtime: WordReviewEditorRuntime): boolean {
|
|
2117
|
+
return runtime.getInteractionGuardSnapshot().effectiveMode === "suggest";
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function getFormattingOperationCommandName(
|
|
2121
|
+
operation:
|
|
2122
|
+
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
2123
|
+
| { type: "set-font-family"; fontFamily: string | null }
|
|
2124
|
+
| { type: "set-font-size"; size: number | null }
|
|
2125
|
+
| { type: "set-text-color"; color: string | null }
|
|
2126
|
+
| { type: "set-highlight-color"; color: string | null }
|
|
2127
|
+
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
2128
|
+
| { type: "indent" }
|
|
2129
|
+
| { type: "outdent" },
|
|
2130
|
+
): string {
|
|
2131
|
+
switch (operation.type) {
|
|
2132
|
+
case "toggle":
|
|
2133
|
+
return `toggle${operation.mark.charAt(0).toUpperCase()}${operation.mark.slice(1)}`;
|
|
2134
|
+
case "set-font-family":
|
|
2135
|
+
return "setFontFamily";
|
|
2136
|
+
case "set-font-size":
|
|
2137
|
+
return "setFontSize";
|
|
2138
|
+
case "set-text-color":
|
|
2139
|
+
return "setTextColor";
|
|
2140
|
+
case "set-highlight-color":
|
|
2141
|
+
return "setHighlightColor";
|
|
2142
|
+
case "set-alignment":
|
|
2143
|
+
return "setAlignment";
|
|
2144
|
+
case "indent":
|
|
2145
|
+
return "indent";
|
|
2146
|
+
case "outdent":
|
|
2147
|
+
return "outdent";
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
1955
2151
|
function applyRuntimeDeleteSectionBreak(
|
|
1956
2152
|
runtime: WordReviewEditorRuntime,
|
|
1957
2153
|
sectionIndex: number,
|
|
@@ -1960,6 +2156,16 @@ function applyRuntimeDeleteSectionBreak(
|
|
|
1960
2156
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1961
2157
|
return;
|
|
1962
2158
|
}
|
|
2159
|
+
if (emitWorkflowBlockedMutation(runtime, "deleteSectionBreak")) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2163
|
+
runtime.emitBlockedCommand("deleteSectionBreak", [{
|
|
2164
|
+
code: "unsupported_surface",
|
|
2165
|
+
message: "Section break deletion is not supported in suggesting mode.",
|
|
2166
|
+
}]);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
1963
2169
|
|
|
1964
2170
|
const sessionState = runtime.getSessionState();
|
|
1965
2171
|
const timestamp = new Date().toISOString();
|
|
@@ -1989,6 +2195,16 @@ function applyRuntimeUpdateSectionLayout(
|
|
|
1989
2195
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1990
2196
|
return;
|
|
1991
2197
|
}
|
|
2198
|
+
if (emitWorkflowBlockedMutation(runtime, "updateSectionLayout")) {
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2202
|
+
runtime.emitBlockedCommand("updateSectionLayout", [{
|
|
2203
|
+
code: "unsupported_surface",
|
|
2204
|
+
message: "Section layout updates are not supported in suggesting mode.",
|
|
2205
|
+
}]);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
1992
2208
|
|
|
1993
2209
|
const sessionState = runtime.getSessionState();
|
|
1994
2210
|
const timestamp = new Date().toISOString();
|
|
@@ -2025,6 +2241,16 @@ function applyRuntimeSetSectionPageNumbering(
|
|
|
2025
2241
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
2026
2242
|
return;
|
|
2027
2243
|
}
|
|
2244
|
+
if (emitWorkflowBlockedMutation(runtime, "setSectionPageNumbering")) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2248
|
+
runtime.emitBlockedCommand("setSectionPageNumbering", [{
|
|
2249
|
+
code: "unsupported_surface",
|
|
2250
|
+
message: "Section page numbering updates are not supported in suggesting mode.",
|
|
2251
|
+
}]);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2028
2254
|
|
|
2029
2255
|
const sessionState = runtime.getSessionState();
|
|
2030
2256
|
const timestamp = new Date().toISOString();
|
|
@@ -2072,6 +2298,16 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2072
2298
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
2073
2299
|
return;
|
|
2074
2300
|
}
|
|
2301
|
+
if (emitWorkflowBlockedMutation(runtime, "setHeaderFooterLink")) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2305
|
+
runtime.emitBlockedCommand("setHeaderFooterLink", [{
|
|
2306
|
+
code: "unsupported_surface",
|
|
2307
|
+
message: "Header and footer linkage updates are not supported in suggesting mode.",
|
|
2308
|
+
}]);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2075
2311
|
|
|
2076
2312
|
const sessionState = runtime.getSessionState();
|
|
2077
2313
|
const timestamp = new Date().toISOString();
|
|
@@ -2094,15 +2330,14 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2094
2330
|
}
|
|
2095
2331
|
|
|
2096
2332
|
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
2097
|
-
|
|
2098
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2333
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2099
2334
|
runtime.emitBlockedCommand("insertPageBreak", [{
|
|
2100
2335
|
code: "unsupported_surface",
|
|
2101
2336
|
message: "Page break insertion is not supported in suggesting mode.",
|
|
2102
2337
|
}]);
|
|
2103
2338
|
return;
|
|
2104
2339
|
}
|
|
2105
|
-
const context = getStoryMutationContext(runtime);
|
|
2340
|
+
const context = getStoryMutationContext(runtime, "insertPageBreak");
|
|
2106
2341
|
if (!context) {
|
|
2107
2342
|
return;
|
|
2108
2343
|
}
|
|
@@ -2119,15 +2354,14 @@ function applyRuntimeInsertTable(
|
|
|
2119
2354
|
runtime: WordReviewEditorRuntime,
|
|
2120
2355
|
options: InsertTableOptions,
|
|
2121
2356
|
): void {
|
|
2122
|
-
|
|
2123
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2357
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2124
2358
|
runtime.emitBlockedCommand("insertTable", [{
|
|
2125
2359
|
code: "unsupported_surface",
|
|
2126
2360
|
message: "Table insertion is not supported in suggesting mode.",
|
|
2127
2361
|
}]);
|
|
2128
2362
|
return;
|
|
2129
2363
|
}
|
|
2130
|
-
const context = getStoryMutationContext(runtime);
|
|
2364
|
+
const context = getStoryMutationContext(runtime, "insertTable");
|
|
2131
2365
|
if (!context) {
|
|
2132
2366
|
return;
|
|
2133
2367
|
}
|
|
@@ -2145,15 +2379,14 @@ function applyRuntimeInsertImage(
|
|
|
2145
2379
|
runtime: WordReviewEditorRuntime,
|
|
2146
2380
|
options: InsertImageOptions,
|
|
2147
2381
|
): void {
|
|
2148
|
-
|
|
2149
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2382
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2150
2383
|
runtime.emitBlockedCommand("insertImage", [{
|
|
2151
2384
|
code: "unsupported_surface",
|
|
2152
2385
|
message: "Image insertion is not supported in suggesting mode.",
|
|
2153
2386
|
}]);
|
|
2154
2387
|
return;
|
|
2155
2388
|
}
|
|
2156
|
-
const context = getStoryMutationContext(runtime);
|
|
2389
|
+
const context = getStoryMutationContext(runtime, "insertImage");
|
|
2157
2390
|
if (!context) {
|
|
2158
2391
|
return;
|
|
2159
2392
|
}
|
|
@@ -2191,7 +2424,10 @@ function applyRuntimeImageResize(
|
|
|
2191
2424
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2192
2425
|
return;
|
|
2193
2426
|
}
|
|
2194
|
-
if (
|
|
2427
|
+
if (emitWorkflowBlockedMutation(runtime, "setImageLayout")) {
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2195
2431
|
runtime.emitBlockedCommand("setImageLayout", [{
|
|
2196
2432
|
code: "unsupported_surface",
|
|
2197
2433
|
message: "Image resize is not supported in suggesting mode.",
|
|
@@ -2222,15 +2458,17 @@ function applyRuntimeImageReposition(
|
|
|
2222
2458
|
mediaId: string,
|
|
2223
2459
|
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2224
2460
|
): void {
|
|
2225
|
-
|
|
2226
|
-
|
|
2461
|
+
if (emitWorkflowBlockedMutation(runtime, "setImageFrame")) {
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2227
2465
|
runtime.emitBlockedCommand("setImageFrame", [{
|
|
2228
2466
|
code: "unsupported_surface",
|
|
2229
2467
|
message: "Image reposition is not supported in suggesting mode.",
|
|
2230
2468
|
}]);
|
|
2231
2469
|
return;
|
|
2232
2470
|
}
|
|
2233
|
-
const context = getStoryMutationContext(runtime);
|
|
2471
|
+
const context = getStoryMutationContext(runtime, "setImageFrame");
|
|
2234
2472
|
if (!context) {
|
|
2235
2473
|
return;
|
|
2236
2474
|
}
|
|
@@ -2275,15 +2513,14 @@ function applyRuntimeTableStructureOperation(
|
|
|
2275
2513
|
| { type: "split-cell" }
|
|
2276
2514
|
| { type: "set-cell-background"; color: string },
|
|
2277
2515
|
): void {
|
|
2278
|
-
|
|
2279
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2516
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2280
2517
|
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
2281
2518
|
code: "unsupported_surface",
|
|
2282
2519
|
message: `Table operation "${operation.type}" is not supported in suggesting mode.`,
|
|
2283
2520
|
}]);
|
|
2284
2521
|
return;
|
|
2285
2522
|
}
|
|
2286
|
-
const context = getStoryMutationContext(runtime);
|
|
2523
|
+
const context = getStoryMutationContext(runtime, `table.${operation.type}`);
|
|
2287
2524
|
if (!context) {
|
|
2288
2525
|
return;
|
|
2289
2526
|
}
|
|
@@ -2308,102 +2545,76 @@ function applyRuntimeTextCommand(
|
|
|
2308
2545
|
| { type: "insert-hard-break" }
|
|
2309
2546
|
| { type: "split-paragraph" },
|
|
2310
2547
|
): void {
|
|
2311
|
-
const
|
|
2548
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2549
|
+
const context = getStoryMutationContext(runtime, getMountedTextCommandName(command));
|
|
2312
2550
|
if (!context) {
|
|
2313
2551
|
return;
|
|
2314
2552
|
}
|
|
2315
2553
|
|
|
2554
|
+
const effectiveSelectionMode = runtime.getInteractionGuardSnapshot().effectiveMode;
|
|
2316
2555
|
const listAwareResult = applyListAwareTextCommand(context, command);
|
|
2556
|
+
if (effectiveSelectionMode === "suggest" && listAwareResult) {
|
|
2557
|
+
runtime.emitBlockedCommand(getMountedTextCommandName(command), [{
|
|
2558
|
+
code: "suggesting_unsupported",
|
|
2559
|
+
message: "List structure changes are not supported in suggesting mode.",
|
|
2560
|
+
}]);
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2317
2564
|
if (listAwareResult) {
|
|
2318
2565
|
dispatchStoryMutationResult(runtime, context, listAwareResult, context.timestamp);
|
|
2319
2566
|
return;
|
|
2320
2567
|
}
|
|
2321
2568
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
return;
|
|
2344
|
-
}
|
|
2569
|
+
switch (command.type) {
|
|
2570
|
+
case "insert-text":
|
|
2571
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert", text: command.text });
|
|
2572
|
+
return;
|
|
2573
|
+
case "delete-backward":
|
|
2574
|
+
runtime.applyActiveStoryTextCommand({ type: "text.delete-backward" });
|
|
2575
|
+
return;
|
|
2576
|
+
case "delete-forward":
|
|
2577
|
+
runtime.applyActiveStoryTextCommand({ type: "text.delete-forward" });
|
|
2578
|
+
return;
|
|
2579
|
+
case "insert-tab":
|
|
2580
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert-tab" });
|
|
2581
|
+
return;
|
|
2582
|
+
case "outdent-tab":
|
|
2583
|
+
return;
|
|
2584
|
+
case "insert-hard-break":
|
|
2585
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert-hard-break" });
|
|
2586
|
+
return;
|
|
2587
|
+
case "split-paragraph":
|
|
2588
|
+
runtime.applyActiveStoryTextCommand({ type: "paragraph.split" });
|
|
2589
|
+
return;
|
|
2345
2590
|
}
|
|
2591
|
+
}
|
|
2346
2592
|
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
selection,
|
|
2373
|
-
{ timestamp: context.timestamp },
|
|
2374
|
-
);
|
|
2375
|
-
case "outdent-tab":
|
|
2376
|
-
return {
|
|
2377
|
-
changed: false,
|
|
2378
|
-
document: context.localDocument,
|
|
2379
|
-
selection,
|
|
2380
|
-
};
|
|
2381
|
-
case "insert-hard-break":
|
|
2382
|
-
return insertHardBreakInDocument(
|
|
2383
|
-
context.localDocument,
|
|
2384
|
-
selection,
|
|
2385
|
-
{ timestamp: context.timestamp },
|
|
2386
|
-
);
|
|
2387
|
-
case "split-paragraph":
|
|
2388
|
-
return splitParagraphInDocument(
|
|
2389
|
-
context.localDocument,
|
|
2390
|
-
selection,
|
|
2391
|
-
{ timestamp: context.timestamp },
|
|
2392
|
-
);
|
|
2393
|
-
}
|
|
2394
|
-
})();
|
|
2395
|
-
|
|
2396
|
-
dispatchStoryMutationResult(
|
|
2397
|
-
runtime,
|
|
2398
|
-
context,
|
|
2399
|
-
{
|
|
2400
|
-
changed: "changed" in localResult ? localResult.changed : true,
|
|
2401
|
-
document: localResult.document,
|
|
2402
|
-
selection: localResult.selection,
|
|
2403
|
-
mapping: "mapping" in localResult ? localResult.mapping : undefined,
|
|
2404
|
-
},
|
|
2405
|
-
context.timestamp,
|
|
2406
|
-
);
|
|
2593
|
+
function getMountedTextCommandName(
|
|
2594
|
+
command:
|
|
2595
|
+
| { type: "insert-text"; text: string }
|
|
2596
|
+
| { type: "delete-backward" }
|
|
2597
|
+
| { type: "delete-forward" }
|
|
2598
|
+
| { type: "insert-tab" }
|
|
2599
|
+
| { type: "outdent-tab" }
|
|
2600
|
+
| { type: "insert-hard-break" }
|
|
2601
|
+
| { type: "split-paragraph" },
|
|
2602
|
+
): string {
|
|
2603
|
+
switch (command.type) {
|
|
2604
|
+
case "insert-text":
|
|
2605
|
+
return "text.insert";
|
|
2606
|
+
case "delete-backward":
|
|
2607
|
+
return "text.delete-backward";
|
|
2608
|
+
case "delete-forward":
|
|
2609
|
+
return "text.delete-forward";
|
|
2610
|
+
case "insert-tab":
|
|
2611
|
+
case "outdent-tab":
|
|
2612
|
+
return "text.insert-tab";
|
|
2613
|
+
case "insert-hard-break":
|
|
2614
|
+
return "text.insert-hard-break";
|
|
2615
|
+
case "split-paragraph":
|
|
2616
|
+
return "paragraph.split";
|
|
2617
|
+
}
|
|
2407
2618
|
}
|
|
2408
2619
|
|
|
2409
2620
|
function applyListAwareTextCommand(
|
|
@@ -2598,8 +2809,21 @@ function canApplyRuntimeMutation(snapshot: RuntimeRenderSnapshot): boolean {
|
|
|
2598
2809
|
return snapshot.isReady && !snapshot.readOnly && !snapshot.fatalError;
|
|
2599
2810
|
}
|
|
2600
2811
|
|
|
2812
|
+
function emitWorkflowBlockedMutation(
|
|
2813
|
+
runtime: WordReviewEditorRuntime,
|
|
2814
|
+
command: string,
|
|
2815
|
+
): boolean {
|
|
2816
|
+
const interactionGuardSnapshot = runtime.getInteractionGuardSnapshot();
|
|
2817
|
+
if (interactionGuardSnapshot.blockedReasons.length === 0) {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
runtime.emitBlockedCommand(command, interactionGuardSnapshot.blockedReasons);
|
|
2821
|
+
return true;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2601
2824
|
function getStoryMutationContext(
|
|
2602
2825
|
runtime: WordReviewEditorRuntime,
|
|
2826
|
+
command?: string,
|
|
2603
2827
|
): {
|
|
2604
2828
|
timestamp: string;
|
|
2605
2829
|
activeStory: EditorStoryTarget;
|
|
@@ -2611,6 +2835,9 @@ function getStoryMutationContext(
|
|
|
2611
2835
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2612
2836
|
return null;
|
|
2613
2837
|
}
|
|
2838
|
+
if (command && emitWorkflowBlockedMutation(runtime, command)) {
|
|
2839
|
+
return null;
|
|
2840
|
+
}
|
|
2614
2841
|
|
|
2615
2842
|
const persistedDocument = runtime.getSessionState().canonicalDocument;
|
|
2616
2843
|
const activeStory = snapshot.activeStory;
|
|
@@ -3034,6 +3261,20 @@ function deriveEditorViewMode(
|
|
|
3034
3261
|
return reviewMode === "editing" ? "editing" : "review";
|
|
3035
3262
|
}
|
|
3036
3263
|
|
|
3264
|
+
function resolveWordReviewEditorChromeVisibility(
|
|
3265
|
+
chromeVisibility: WordReviewEditorProps["chromeVisibility"],
|
|
3266
|
+
showReviewPanel: boolean,
|
|
3267
|
+
): Partial<WordReviewEditorChromeVisibility> {
|
|
3268
|
+
const legacyVisibility =
|
|
3269
|
+
showReviewPanel
|
|
3270
|
+
? {}
|
|
3271
|
+
: { reviewRail: false } satisfies Partial<WordReviewEditorChromeVisibility>;
|
|
3272
|
+
return {
|
|
3273
|
+
...legacyVisibility,
|
|
3274
|
+
...chromeVisibility,
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3037
3278
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
3038
3279
|
return {
|
|
3039
3280
|
anchor: selection.anchor,
|
|
@@ -3057,6 +3298,7 @@ function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
|
3057
3298
|
|
|
3058
3299
|
function createSelectionFromAnchor(
|
|
3059
3300
|
anchor: PublicSelectionSnapshot["activeRange"],
|
|
3301
|
+
storyTarget?: EditorStoryTarget,
|
|
3060
3302
|
): PublicSelectionSnapshot {
|
|
3061
3303
|
switch (anchor.kind) {
|
|
3062
3304
|
case "range":
|
|
@@ -3065,6 +3307,7 @@ function createSelectionFromAnchor(
|
|
|
3065
3307
|
head: anchor.to,
|
|
3066
3308
|
isCollapsed: anchor.from === anchor.to,
|
|
3067
3309
|
activeRange: anchor,
|
|
3310
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3068
3311
|
};
|
|
3069
3312
|
case "node":
|
|
3070
3313
|
return {
|
|
@@ -3072,6 +3315,7 @@ function createSelectionFromAnchor(
|
|
|
3072
3315
|
head: anchor.at,
|
|
3073
3316
|
isCollapsed: true,
|
|
3074
3317
|
activeRange: anchor,
|
|
3318
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3075
3319
|
};
|
|
3076
3320
|
case "detached":
|
|
3077
3321
|
return {
|
|
@@ -3079,6 +3323,7 @@ function createSelectionFromAnchor(
|
|
|
3079
3323
|
head: anchor.lastKnownRange.to,
|
|
3080
3324
|
isCollapsed: anchor.lastKnownRange.from === anchor.lastKnownRange.to,
|
|
3081
3325
|
activeRange: anchor,
|
|
3326
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3082
3327
|
};
|
|
3083
3328
|
}
|
|
3084
3329
|
}
|
|
@@ -3184,7 +3429,13 @@ function interactionGuardSnapshotsEqual(
|
|
|
3184
3429
|
if (left === right) {
|
|
3185
3430
|
return true;
|
|
3186
3431
|
}
|
|
3187
|
-
return
|
|
3432
|
+
return (
|
|
3433
|
+
left.effectiveMode === right.effectiveMode &&
|
|
3434
|
+
left.matchedScopeId === right.matchedScopeId &&
|
|
3435
|
+
left.matchedScopeMode === right.matchedScopeMode &&
|
|
3436
|
+
left.disabledReason === right.disabledReason &&
|
|
3437
|
+
workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons)
|
|
3438
|
+
);
|
|
3188
3439
|
}
|
|
3189
3440
|
|
|
3190
3441
|
function workflowBlockedReasonsEqual(
|
|
@@ -3256,7 +3507,14 @@ function editorAnchorProjectionEqual(
|
|
|
3256
3507
|
function createSelectionToolbarSelectionKey(
|
|
3257
3508
|
selection: RuntimeRenderSnapshot["selection"],
|
|
3258
3509
|
activeStory: EditorStoryTarget,
|
|
3510
|
+
activeRevisionId?: string,
|
|
3259
3511
|
): string | null {
|
|
3512
|
+
if (activeRevisionId) {
|
|
3513
|
+
return JSON.stringify({
|
|
3514
|
+
story: activeStory,
|
|
3515
|
+
revisionId: activeRevisionId,
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3260
3518
|
if (selection.isCollapsed || selection.activeRange.kind !== "range") {
|
|
3261
3519
|
return null;
|
|
3262
3520
|
}
|
|
@@ -3275,6 +3533,8 @@ function buildSelectionToolbarModel(args: {
|
|
|
3275
3533
|
documentNavigation: ReturnType<WordReviewEditorRuntime["getDocumentNavigationSnapshot"]>;
|
|
3276
3534
|
styleCatalog: StyleCatalogSnapshot;
|
|
3277
3535
|
formattingState: FormattingStateSnapshot;
|
|
3536
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
3537
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
3278
3538
|
addCommentDisabledReason?: string;
|
|
3279
3539
|
}): SelectionToolbarModel | null {
|
|
3280
3540
|
const {
|
|
@@ -3284,6 +3544,8 @@ function buildSelectionToolbarModel(args: {
|
|
|
3284
3544
|
documentNavigation,
|
|
3285
3545
|
styleCatalog,
|
|
3286
3546
|
formattingState,
|
|
3547
|
+
workflowScopeSnapshot,
|
|
3548
|
+
interactionGuardSnapshot,
|
|
3287
3549
|
addCommentDisabledReason,
|
|
3288
3550
|
} = args;
|
|
3289
3551
|
|
|
@@ -3304,6 +3566,14 @@ function buildSelectionToolbarModel(args: {
|
|
|
3304
3566
|
|
|
3305
3567
|
const badges = [
|
|
3306
3568
|
createSelectionToolbarStoryBadge(viewState.activeStory),
|
|
3569
|
+
createSelectionToolbarWorkflowBadge(
|
|
3570
|
+
resolveSelectionWorkflowPosture(
|
|
3571
|
+
snapshot,
|
|
3572
|
+
viewState,
|
|
3573
|
+
workflowScopeSnapshot,
|
|
3574
|
+
interactionGuardSnapshot,
|
|
3575
|
+
),
|
|
3576
|
+
),
|
|
3307
3577
|
viewState.workspaceMode === "page" && documentNavigation.pageCount > 0
|
|
3308
3578
|
? { label: `Page ${documentNavigation.activePageIndex + 1}` as const }
|
|
3309
3579
|
: null,
|
|
@@ -3311,15 +3581,137 @@ function buildSelectionToolbarModel(args: {
|
|
|
3311
3581
|
createSelectionToolbarListBadge(viewState),
|
|
3312
3582
|
].filter((badge): badge is SelectionToolbarModel["badges"][number] => Boolean(badge));
|
|
3313
3583
|
|
|
3584
|
+
const workflowPosture = resolveSelectionWorkflowPosture(
|
|
3585
|
+
snapshot,
|
|
3586
|
+
viewState,
|
|
3587
|
+
workflowScopeSnapshot,
|
|
3588
|
+
interactionGuardSnapshot,
|
|
3589
|
+
);
|
|
3590
|
+
const canToggleFormatting = workflowPosture.mode === "edit";
|
|
3591
|
+
const canAddComment = workflowPosture.mode === "view" || workflowPosture.mode === "blocked"
|
|
3592
|
+
? false
|
|
3593
|
+
: capabilities.canAddComment;
|
|
3594
|
+
const disabledReason =
|
|
3595
|
+
workflowPosture.disabledReason ??
|
|
3596
|
+
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3597
|
+
|
|
3314
3598
|
return {
|
|
3315
3599
|
previewText,
|
|
3316
3600
|
badges,
|
|
3317
|
-
canToggleFormatting
|
|
3601
|
+
canToggleFormatting,
|
|
3318
3602
|
boldActive: formattingState.bold,
|
|
3319
3603
|
italicActive: formattingState.italic,
|
|
3320
3604
|
underlineActive: formattingState.underline,
|
|
3321
|
-
canAddComment
|
|
3322
|
-
...(
|
|
3605
|
+
canAddComment,
|
|
3606
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
function buildSuggestionCardModel(args: {
|
|
3611
|
+
snapshot: RuntimeRenderSnapshot;
|
|
3612
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>;
|
|
3613
|
+
capabilities: ReturnType<typeof deriveCapabilities>;
|
|
3614
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
3615
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
3616
|
+
activeRevisionId?: string;
|
|
3617
|
+
suppressedSuggestionRevisionId?: string | null;
|
|
3618
|
+
addCommentDisabledReason?: string;
|
|
3619
|
+
}): SuggestionCardModel | null {
|
|
3620
|
+
const {
|
|
3621
|
+
snapshot,
|
|
3622
|
+
viewState,
|
|
3623
|
+
capabilities,
|
|
3624
|
+
workflowScopeSnapshot,
|
|
3625
|
+
interactionGuardSnapshot,
|
|
3626
|
+
activeRevisionId,
|
|
3627
|
+
suppressedSuggestionRevisionId,
|
|
3628
|
+
addCommentDisabledReason,
|
|
3629
|
+
} = args;
|
|
3630
|
+
|
|
3631
|
+
if (
|
|
3632
|
+
!snapshot.surface ||
|
|
3633
|
+
!capabilities.canEdit ||
|
|
3634
|
+
viewState.viewMode === "view"
|
|
3635
|
+
) {
|
|
3636
|
+
return null;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
const activeRange =
|
|
3640
|
+
!snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
|
|
3641
|
+
? snapshot.selection.activeRange
|
|
3642
|
+
: null;
|
|
3643
|
+
const selectionFrom = activeRange
|
|
3644
|
+
? Math.min(activeRange.from, activeRange.to)
|
|
3645
|
+
: null;
|
|
3646
|
+
const selectionTo = activeRange
|
|
3647
|
+
? Math.max(activeRange.from, activeRange.to)
|
|
3648
|
+
: null;
|
|
3649
|
+
const candidateRevisions = snapshot.trackedChanges.revisions.filter((revision) =>
|
|
3650
|
+
storyTargetsEqual(revision.storyTarget ?? { kind: "main" }, viewState.activeStory) &&
|
|
3651
|
+
revision.status === "active" &&
|
|
3652
|
+
revision.actionability === "actionable" &&
|
|
3653
|
+
revision.anchor.kind === "range" &&
|
|
3654
|
+
(
|
|
3655
|
+
activeRevisionId === revision.revisionId ||
|
|
3656
|
+
(
|
|
3657
|
+
selectionFrom !== null &&
|
|
3658
|
+
selectionTo !== null &&
|
|
3659
|
+
rangesOverlap(
|
|
3660
|
+
selectionFrom,
|
|
3661
|
+
selectionTo,
|
|
3662
|
+
revision.anchor.from,
|
|
3663
|
+
revision.anchor.to,
|
|
3664
|
+
)
|
|
3665
|
+
)
|
|
3666
|
+
)
|
|
3667
|
+
);
|
|
3668
|
+
const focusedRevision = (
|
|
3669
|
+
activeRevisionId
|
|
3670
|
+
? candidateRevisions.find((revision) => revision.revisionId === activeRevisionId)
|
|
3671
|
+
: null
|
|
3672
|
+
) ?? candidateRevisions[0];
|
|
3673
|
+
|
|
3674
|
+
if (!focusedRevision || focusedRevision.revisionId === suppressedSuggestionRevisionId) {
|
|
3675
|
+
return null;
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
const badges = [
|
|
3679
|
+
createSelectionToolbarStoryBadge(viewState.activeStory),
|
|
3680
|
+
workflowScopeSnapshot?.activeWorkItem?.title
|
|
3681
|
+
? {
|
|
3682
|
+
label: workflowScopeSnapshot.activeWorkItem.title,
|
|
3683
|
+
tone: "accent" as const,
|
|
3684
|
+
}
|
|
3685
|
+
: null,
|
|
3686
|
+
].filter((badge): badge is SuggestionCardModel["badges"][number] => Boolean(badge));
|
|
3687
|
+
const workflowPosture = resolveSelectionWorkflowPosture(
|
|
3688
|
+
snapshot,
|
|
3689
|
+
viewState,
|
|
3690
|
+
workflowScopeSnapshot,
|
|
3691
|
+
interactionGuardSnapshot,
|
|
3692
|
+
);
|
|
3693
|
+
const canReviewSuggestion = workflowPosture.mode === "edit" || workflowPosture.mode === "suggest";
|
|
3694
|
+
const canAddComment = workflowPosture.mode === "view" || workflowPosture.mode === "blocked"
|
|
3695
|
+
? false
|
|
3696
|
+
: capabilities.canAddComment;
|
|
3697
|
+
const disabledReason =
|
|
3698
|
+
workflowPosture.disabledReason ??
|
|
3699
|
+
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3700
|
+
|
|
3701
|
+
return {
|
|
3702
|
+
revisionId: focusedRevision.revisionId,
|
|
3703
|
+
kindLabel: getSuggestionKindLabel(focusedRevision.kind),
|
|
3704
|
+
previewText:
|
|
3705
|
+
focusedRevision.excerpt ??
|
|
3706
|
+
focusedRevision.detail ??
|
|
3707
|
+
focusedRevision.label ??
|
|
3708
|
+
"Suggested change",
|
|
3709
|
+
badges,
|
|
3710
|
+
canAccept: canReviewSuggestion && capabilities.canAcceptChange && focusedRevision.canAccept,
|
|
3711
|
+
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedRevision.canReject,
|
|
3712
|
+
canEditSuggestion: canReviewSuggestion,
|
|
3713
|
+
canAddComment,
|
|
3714
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
3323
3715
|
};
|
|
3324
3716
|
}
|
|
3325
3717
|
|
|
@@ -3377,6 +3769,149 @@ function createSelectionToolbarListBadge(
|
|
|
3377
3769
|
};
|
|
3378
3770
|
}
|
|
3379
3771
|
|
|
3772
|
+
function createSelectionToolbarWorkflowBadge(
|
|
3773
|
+
posture: ReturnType<typeof resolveSelectionWorkflowPosture>,
|
|
3774
|
+
): SelectionToolbarModel["badges"][number] | null {
|
|
3775
|
+
switch (posture.mode) {
|
|
3776
|
+
case "suggest":
|
|
3777
|
+
return { label: "Suggest", tone: "accent" };
|
|
3778
|
+
case "comment":
|
|
3779
|
+
return { label: "Comment only", tone: "accent" };
|
|
3780
|
+
case "view":
|
|
3781
|
+
return { label: "View only" };
|
|
3782
|
+
case "blocked":
|
|
3783
|
+
return { label: "Blocked" };
|
|
3784
|
+
default:
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
function resolveSelectionWorkflowPosture(
|
|
3790
|
+
snapshot: RuntimeRenderSnapshot,
|
|
3791
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>,
|
|
3792
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null,
|
|
3793
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot,
|
|
3794
|
+
): {
|
|
3795
|
+
mode: "edit" | "suggest" | "comment" | "view" | "blocked";
|
|
3796
|
+
disabledReason?: string;
|
|
3797
|
+
} {
|
|
3798
|
+
const blockedReasons =
|
|
3799
|
+
interactionGuardSnapshot?.blockedReasons ??
|
|
3800
|
+
workflowScopeSnapshot?.blockedReasons ??
|
|
3801
|
+
[];
|
|
3802
|
+
const blockingReason = blockedReasons[0];
|
|
3803
|
+
if (blockingReason) {
|
|
3804
|
+
if (blockingReason.code === "workflow_comment_only") {
|
|
3805
|
+
return {
|
|
3806
|
+
mode: "comment",
|
|
3807
|
+
disabledReason: blockingReason.message,
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
if (blockingReason.code === "workflow_view_only") {
|
|
3811
|
+
return {
|
|
3812
|
+
mode: "view",
|
|
3813
|
+
disabledReason: blockingReason.message,
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
return {
|
|
3817
|
+
mode: "blocked",
|
|
3818
|
+
disabledReason: blockingReason.message,
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
if (interactionGuardSnapshot) {
|
|
3823
|
+
if (interactionGuardSnapshot.effectiveMode === "suggest") {
|
|
3824
|
+
return {
|
|
3825
|
+
mode: "suggest",
|
|
3826
|
+
disabledReason:
|
|
3827
|
+
interactionGuardSnapshot.disabledReason ??
|
|
3828
|
+
"Suggestion authoring is active here; direct formatting changes are blocked.",
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
if (interactionGuardSnapshot.effectiveMode === "comment") {
|
|
3832
|
+
return {
|
|
3833
|
+
mode: "comment",
|
|
3834
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
if (interactionGuardSnapshot.effectiveMode === "view") {
|
|
3838
|
+
return {
|
|
3839
|
+
mode: "view",
|
|
3840
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
if (interactionGuardSnapshot.effectiveMode === "blocked") {
|
|
3844
|
+
return {
|
|
3845
|
+
mode: "blocked",
|
|
3846
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
const activeRange =
|
|
3852
|
+
!snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
|
|
3853
|
+
? snapshot.selection.activeRange
|
|
3854
|
+
: null;
|
|
3855
|
+
const matchingScope = activeRange && workflowScopeSnapshot
|
|
3856
|
+
? workflowScopeSnapshot.scopes.find((scope) => {
|
|
3857
|
+
const scopeStoryTarget = scope.storyTarget ?? { kind: "main" as const };
|
|
3858
|
+
if (!storyTargetsEqual(scopeStoryTarget, viewState.activeStory)) {
|
|
3859
|
+
return false;
|
|
3860
|
+
}
|
|
3861
|
+
if (scope.anchor.kind === "detached") {
|
|
3862
|
+
return false;
|
|
3863
|
+
}
|
|
3864
|
+
const scopeFrom = scope.anchor.kind === "range" ? scope.anchor.from : scope.anchor.at;
|
|
3865
|
+
const scopeTo = scope.anchor.kind === "range" ? scope.anchor.to : scope.anchor.at;
|
|
3866
|
+
return activeRange.from >= scopeFrom && activeRange.to <= scopeTo;
|
|
3867
|
+
})
|
|
3868
|
+
: null;
|
|
3869
|
+
|
|
3870
|
+
if (matchingScope?.mode === "suggest") {
|
|
3871
|
+
return {
|
|
3872
|
+
mode: "suggest",
|
|
3873
|
+
disabledReason: "Suggestion authoring is active here; direct formatting changes are blocked.",
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
if (matchingScope?.mode === "comment") {
|
|
3877
|
+
return {
|
|
3878
|
+
mode: "comment",
|
|
3879
|
+
disabledReason: `Scope "${matchingScope.label ?? matchingScope.scopeId}" allows comments only.`,
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
if (matchingScope?.mode === "view") {
|
|
3883
|
+
return {
|
|
3884
|
+
mode: "view",
|
|
3885
|
+
disabledReason: `Scope "${matchingScope.label ?? matchingScope.scopeId}" is view-only.`,
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
return { mode: "edit" };
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
function rangesOverlap(
|
|
3892
|
+
leftFrom: number,
|
|
3893
|
+
leftTo: number,
|
|
3894
|
+
rightFrom: number,
|
|
3895
|
+
rightTo: number,
|
|
3896
|
+
): boolean {
|
|
3897
|
+
return leftFrom < rightTo && rightFrom < leftTo;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
function getSuggestionKindLabel(kind: TrackedChangeEntrySnapshot["kind"]): string {
|
|
3901
|
+
switch (kind) {
|
|
3902
|
+
case "insertion":
|
|
3903
|
+
return "Suggested insertion";
|
|
3904
|
+
case "deletion":
|
|
3905
|
+
return "Suggested deletion";
|
|
3906
|
+
case "formatting":
|
|
3907
|
+
return "Suggested formatting change";
|
|
3908
|
+
case "property-change":
|
|
3909
|
+
return "Suggested property change";
|
|
3910
|
+
case "move":
|
|
3911
|
+
return "Suggested move";
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3380
3915
|
function buildActiveImageContext(args: {
|
|
3381
3916
|
canonicalDocument: PersistedEditorSnapshot["canonicalDocument"];
|
|
3382
3917
|
selection: RuntimeRenderSnapshot["selection"];
|