@beyondwork/docx-react-component 1.0.21 → 1.0.23
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 +763 -38
- package/package.json +25 -36
- package/src/api/public-types.ts +66 -1
- package/src/core/commands/index.ts +574 -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 +661 -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 +5 -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 +44 -16
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +2 -0
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +127 -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 {
|
|
@@ -495,11 +521,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
495
521
|
readOnly = false,
|
|
496
522
|
reviewMode = "review",
|
|
497
523
|
showReviewPanel = true,
|
|
524
|
+
chromeVisibility,
|
|
498
525
|
} = props;
|
|
499
526
|
|
|
500
527
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
501
528
|
const [showTrackedChanges, setShowTrackedChanges] = useState(false);
|
|
502
529
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
530
|
+
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
503
531
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
504
532
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
505
533
|
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
@@ -644,7 +672,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
644
672
|
getValue: () => runtime.getInteractionGuardSnapshot(),
|
|
645
673
|
}
|
|
646
674
|
: null,
|
|
647
|
-
{ blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
675
|
+
{ effectiveMode: "edit", blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
648
676
|
interactionGuardSnapshotsEqual,
|
|
649
677
|
);
|
|
650
678
|
const workflowMarkupSnapshot = useMemo(
|
|
@@ -655,11 +683,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
655
683
|
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
656
684
|
[snapshot.surface, workflowMarkupSnapshot],
|
|
657
685
|
);
|
|
658
|
-
const
|
|
659
|
-
() => (runtime ? runtime.
|
|
660
|
-
[loadingSessionState, runtime, snapshot.revisionToken],
|
|
686
|
+
const canonicalDocument = useMemo(
|
|
687
|
+
() => (runtime ? runtime.getCanonicalDocument() : loadingSessionState.canonicalDocument),
|
|
688
|
+
[loadingSessionState.canonicalDocument, runtime, snapshot.revisionToken],
|
|
661
689
|
);
|
|
662
|
-
const canonicalDocument = sessionState.canonicalDocument;
|
|
663
690
|
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
664
691
|
|
|
665
692
|
useEffect(() => {
|
|
@@ -901,14 +928,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
901
928
|
(r) => r.revisionId === revisionId,
|
|
902
929
|
);
|
|
903
930
|
if (!revision || revision.anchor.kind === "detached") return;
|
|
904
|
-
applyRuntimeSelection(
|
|
931
|
+
applyRuntimeSelection(
|
|
932
|
+
activeRuntime,
|
|
933
|
+
createSelectionFromAnchor(revision.anchor, revision.storyTarget),
|
|
934
|
+
);
|
|
905
935
|
},
|
|
906
936
|
scrollToComment: (commentId: string) => {
|
|
907
937
|
const comment = activeRuntime.getRenderSnapshot().comments.threads.find(
|
|
908
938
|
(t) => t.commentId === commentId,
|
|
909
939
|
);
|
|
910
940
|
if (!comment || comment.anchor.kind === "detached") return;
|
|
911
|
-
applyRuntimeSelection(
|
|
941
|
+
applyRuntimeSelection(
|
|
942
|
+
activeRuntime,
|
|
943
|
+
createSelectionFromAnchor(comment.anchor),
|
|
944
|
+
);
|
|
912
945
|
},
|
|
913
946
|
openStory: (target: EditorStoryTarget) => {
|
|
914
947
|
activeRuntime.openStory(target);
|
|
@@ -982,6 +1015,15 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
982
1015
|
getWorkflowMarkupSnapshot: () => {
|
|
983
1016
|
return clonePublicValue(activeRuntime.getWorkflowMarkupSnapshot());
|
|
984
1017
|
},
|
|
1018
|
+
setHostAnnotationOverlay: (overlay) => {
|
|
1019
|
+
activeRuntime.setHostAnnotationOverlay(clonePublicValue(overlay));
|
|
1020
|
+
},
|
|
1021
|
+
clearHostAnnotationOverlay: () => {
|
|
1022
|
+
activeRuntime.clearHostAnnotationOverlay();
|
|
1023
|
+
},
|
|
1024
|
+
getHostAnnotationSnapshot: () => {
|
|
1025
|
+
return clonePublicValue(activeRuntime.getHostAnnotationSnapshot());
|
|
1026
|
+
},
|
|
985
1027
|
getWorkflowCandidateRanges: (options) => {
|
|
986
1028
|
return clonePublicValue(activeRuntime.getWorkflowCandidateRanges(options));
|
|
987
1029
|
},
|
|
@@ -1080,11 +1122,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1080
1122
|
snapshot.trackedChanges.totalCount,
|
|
1081
1123
|
]);
|
|
1082
1124
|
|
|
1083
|
-
function focusAnchor(
|
|
1125
|
+
function focusAnchor(
|
|
1126
|
+
anchor: PublicSelectionSnapshot["activeRange"],
|
|
1127
|
+
storyTarget?: EditorStoryTarget,
|
|
1128
|
+
): void {
|
|
1084
1129
|
if (anchor.kind === "detached") {
|
|
1085
1130
|
return;
|
|
1086
1131
|
}
|
|
1087
|
-
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(anchor));
|
|
1132
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(anchor, storyTarget));
|
|
1088
1133
|
}
|
|
1089
1134
|
|
|
1090
1135
|
function addReviewComment(): string | null {
|
|
@@ -1129,7 +1174,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1129
1174
|
reviewMode,
|
|
1130
1175
|
workflowScopeSnapshot,
|
|
1131
1176
|
);
|
|
1132
|
-
const
|
|
1177
|
+
const resolvedChromeVisibility = resolveWordReviewEditorChromeVisibility(
|
|
1178
|
+
chromeVisibility,
|
|
1179
|
+
showReviewPanel,
|
|
1180
|
+
);
|
|
1181
|
+
const capabilities = resolvedChromeVisibility.reviewRail
|
|
1133
1182
|
? derivedCapabilities
|
|
1134
1183
|
: { ...derivedCapabilities, reviewRailVisible: false };
|
|
1135
1184
|
const formattingState = getFormattingStateFromRenderSnapshot(snapshot);
|
|
@@ -1152,7 +1201,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1152
1201
|
}),
|
|
1153
1202
|
[canonicalDocument, snapshot.selection, snapshot.surface, viewState.activeStory],
|
|
1154
1203
|
);
|
|
1155
|
-
const sourcePackage =
|
|
1204
|
+
const sourcePackage = runtime
|
|
1205
|
+
? runtime.getSourcePackage()
|
|
1206
|
+
: loadingSessionState.sourcePackage;
|
|
1156
1207
|
const mediaPreviewCatalogKey = Object.values(canonicalDocument.media.items)
|
|
1157
1208
|
.map((item) =>
|
|
1158
1209
|
[
|
|
@@ -1214,14 +1265,26 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1214
1265
|
documentNavigation,
|
|
1215
1266
|
styleCatalog,
|
|
1216
1267
|
formattingState,
|
|
1268
|
+
workflowScopeSnapshot,
|
|
1269
|
+
interactionGuardSnapshot,
|
|
1270
|
+
addCommentDisabledReason,
|
|
1271
|
+
});
|
|
1272
|
+
const suggestionCard = buildSuggestionCardModel({
|
|
1273
|
+
snapshot,
|
|
1274
|
+
viewState,
|
|
1275
|
+
capabilities,
|
|
1276
|
+
workflowScopeSnapshot,
|
|
1277
|
+
interactionGuardSnapshot,
|
|
1278
|
+
activeRevisionId,
|
|
1279
|
+
suppressedSuggestionRevisionId,
|
|
1217
1280
|
addCommentDisabledReason,
|
|
1218
1281
|
});
|
|
1219
1282
|
const selectionToolbarSelectionKey = useMemo(
|
|
1220
|
-
() => createSelectionToolbarSelectionKey(snapshot.selection, viewState.activeStory),
|
|
1221
|
-
[snapshot.selection, viewState.activeStory],
|
|
1283
|
+
() => createSelectionToolbarSelectionKey(snapshot.selection, viewState.activeStory, activeRevisionId),
|
|
1284
|
+
[activeRevisionId, snapshot.selection, viewState.activeStory],
|
|
1222
1285
|
);
|
|
1223
|
-
const
|
|
1224
|
-
selectionToolbar &&
|
|
1286
|
+
const shouldRenderSelectionChrome = Boolean(
|
|
1287
|
+
(selectionToolbar || suggestionCard) &&
|
|
1225
1288
|
selectionToolbarSelectionKey &&
|
|
1226
1289
|
selectionToolbarDismissedKey !== selectionToolbarSelectionKey &&
|
|
1227
1290
|
(viewState.isFocused || selectionToolbarFocusWithin),
|
|
@@ -1341,6 +1404,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1341
1404
|
|
|
1342
1405
|
useEffect(() => {
|
|
1343
1406
|
if (!selectionToolbarSelectionKey) {
|
|
1407
|
+
setSuppressedSuggestionRevisionId(null);
|
|
1344
1408
|
setSelectionToolbarDismissedKey(null);
|
|
1345
1409
|
setSelectionToolbarFocusWithin(false);
|
|
1346
1410
|
setSelectionToolbarAnchor(null);
|
|
@@ -1350,17 +1414,18 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1350
1414
|
|
|
1351
1415
|
if (lastSelectionToolbarKeyRef.current !== selectionToolbarSelectionKey) {
|
|
1352
1416
|
lastSelectionToolbarKeyRef.current = selectionToolbarSelectionKey;
|
|
1417
|
+
setSuppressedSuggestionRevisionId(null);
|
|
1353
1418
|
setSelectionToolbarDismissedKey(null);
|
|
1354
1419
|
setSelectionToolbarFocusWithin(false);
|
|
1355
1420
|
}
|
|
1356
1421
|
}, [selectionToolbarSelectionKey]);
|
|
1357
1422
|
|
|
1358
1423
|
useEffect(() => {
|
|
1359
|
-
if (!selectionToolbar) {
|
|
1424
|
+
if (!selectionToolbar && !suggestionCard) {
|
|
1360
1425
|
setSelectionToolbarAnchor(null);
|
|
1361
1426
|
setSelectionToolbarFocusWithin(false);
|
|
1362
1427
|
}
|
|
1363
|
-
}, [selectionToolbar]);
|
|
1428
|
+
}, [selectionToolbar, suggestionCard]);
|
|
1364
1429
|
|
|
1365
1430
|
useEffect(() => {
|
|
1366
1431
|
const shell = shellRef.current;
|
|
@@ -1389,9 +1454,26 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1389
1454
|
}, [loadError, snapshot.fatalError]);
|
|
1390
1455
|
|
|
1391
1456
|
function handleShellKeyDownCapture(event: React.KeyboardEvent<HTMLDivElement>): void {
|
|
1457
|
+
const targetWithinDocument = isTargetWithinDocumentSurface(event.target);
|
|
1458
|
+
const isUndoShortcut = (event.ctrlKey || event.metaKey) && !event.shiftKey && event.key.toLowerCase() === "z";
|
|
1459
|
+
const isRedoShortcut =
|
|
1460
|
+
((event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === "z") ||
|
|
1461
|
+
(event.ctrlKey && event.key.toLowerCase() === "y");
|
|
1462
|
+
|
|
1463
|
+
if ((isUndoShortcut || isRedoShortcut) && targetWithinDocument) {
|
|
1464
|
+
event.preventDefault();
|
|
1465
|
+
event.stopPropagation();
|
|
1466
|
+
if (isUndoShortcut) {
|
|
1467
|
+
activeRuntime.undo();
|
|
1468
|
+
} else {
|
|
1469
|
+
activeRuntime.redo();
|
|
1470
|
+
}
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1392
1474
|
if (
|
|
1393
1475
|
event.key === "Escape" &&
|
|
1394
|
-
|
|
1476
|
+
shouldRenderSelectionChrome &&
|
|
1395
1477
|
(isTargetWithinDocumentSurface(event.target) || isTargetWithinSelectionToolbar(event.target))
|
|
1396
1478
|
) {
|
|
1397
1479
|
event.preventDefault();
|
|
@@ -1430,6 +1512,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1430
1512
|
onOutdentTab: () => applyRuntimeTextCommand(activeRuntime, { type: "outdent-tab" }),
|
|
1431
1513
|
onInsertHardBreak: () => applyRuntimeTextCommand(activeRuntime, { type: "insert-hard-break" }),
|
|
1432
1514
|
onSplitParagraph: () => applyRuntimeTextCommand(activeRuntime, { type: "split-paragraph" }),
|
|
1515
|
+
onUndo: () => activeRuntime.undo(),
|
|
1516
|
+
onRedo: () => activeRuntime.redo(),
|
|
1517
|
+
onBlockedInput: (command: "paste" | "drop", message: string) =>
|
|
1518
|
+
activeRuntime.emitBlockedCommand(command, [{
|
|
1519
|
+
code: "unsupported_surface",
|
|
1520
|
+
message,
|
|
1521
|
+
}]),
|
|
1433
1522
|
};
|
|
1434
1523
|
|
|
1435
1524
|
const reviewCallbacks = {
|
|
@@ -1458,7 +1547,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1458
1547
|
},
|
|
1459
1548
|
onOpenRevision: (revision: typeof snapshot.trackedChanges.revisions[number]) => {
|
|
1460
1549
|
setActiveRevisionId(revision.revisionId);
|
|
1461
|
-
focusAnchor(revision.anchor);
|
|
1550
|
+
focusAnchor(revision.anchor, revision.storyTarget);
|
|
1462
1551
|
setActiveRailTab("changes");
|
|
1463
1552
|
},
|
|
1464
1553
|
onAcceptRevision: (revisionId: string) => {
|
|
@@ -1623,6 +1712,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1623
1712
|
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
1624
1713
|
workflowCandidates={workflowScopeSnapshot?.candidates}
|
|
1625
1714
|
workflowBlockedReasons={workflowBlockedRails}
|
|
1715
|
+
activeWorkflowWorkItemId={workflowScopeSnapshot?.activeWorkItemId ?? null}
|
|
1716
|
+
activeWorkflowScopeIds={workflowScopeSnapshot?.activeWorkItem?.scopeIds ?? []}
|
|
1626
1717
|
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1627
1718
|
{...editorCallbacks}
|
|
1628
1719
|
onCommentActivated={(commentId) => {
|
|
@@ -1656,6 +1747,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1656
1747
|
markupDisplay={liveMarkupDisplay}
|
|
1657
1748
|
currentUserId={currentUser.userId}
|
|
1658
1749
|
capabilities={capabilities}
|
|
1750
|
+
chromeVisibility={resolvedChromeVisibility}
|
|
1659
1751
|
documentNavigation={documentNavigation}
|
|
1660
1752
|
reviewMode={reviewMode}
|
|
1661
1753
|
workspaceMode={viewState.workspaceMode}
|
|
@@ -1668,9 +1760,28 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1668
1760
|
showTrackedChanges={showTrackedChanges}
|
|
1669
1761
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
1670
1762
|
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
1671
|
-
selectionToolbar={
|
|
1672
|
-
|
|
1763
|
+
selectionToolbar={shouldRenderSelectionChrome ? selectionToolbar : null}
|
|
1764
|
+
suggestionCard={shouldRenderSelectionChrome ? suggestionCard : null}
|
|
1765
|
+
selectionToolbarAnchor={shouldRenderSelectionChrome ? selectionToolbarAnchor : null}
|
|
1673
1766
|
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1767
|
+
onAddCommentFromSuggestion={addSelectionToolbarComment}
|
|
1768
|
+
onAcceptSuggestion={suggestionCard
|
|
1769
|
+
? () => {
|
|
1770
|
+
activeRuntime.acceptChange(suggestionCard.revisionId);
|
|
1771
|
+
dismissSelectionToolbar("chrome-action");
|
|
1772
|
+
}
|
|
1773
|
+
: undefined}
|
|
1774
|
+
onRejectSuggestion={suggestionCard
|
|
1775
|
+
? () => {
|
|
1776
|
+
activeRuntime.rejectChange(suggestionCard.revisionId);
|
|
1777
|
+
dismissSelectionToolbar("chrome-action");
|
|
1778
|
+
}
|
|
1779
|
+
: undefined}
|
|
1780
|
+
onEditSuggestion={suggestionCard
|
|
1781
|
+
? () => {
|
|
1782
|
+
setSuppressedSuggestionRevisionId(suggestionCard.revisionId);
|
|
1783
|
+
}
|
|
1784
|
+
: undefined}
|
|
1674
1785
|
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1675
1786
|
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1676
1787
|
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
@@ -1696,7 +1807,10 @@ function applyRuntimeFormattingOperation(
|
|
|
1696
1807
|
| { type: "indent" }
|
|
1697
1808
|
| { type: "outdent" },
|
|
1698
1809
|
): void {
|
|
1699
|
-
|
|
1810
|
+
if (emitSuggestingUnsupportedMutation(runtime, getFormattingOperationCommandName(operation))) {
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
const context = getStoryMutationContext(runtime, getFormattingOperationCommandName(operation));
|
|
1700
1814
|
if (!context) {
|
|
1701
1815
|
return;
|
|
1702
1816
|
}
|
|
@@ -1764,7 +1878,10 @@ function applyRuntimeParagraphStyle(
|
|
|
1764
1878
|
runtime: WordReviewEditorRuntime,
|
|
1765
1879
|
styleId: string | null,
|
|
1766
1880
|
): void {
|
|
1767
|
-
|
|
1881
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphStyle")) {
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
const context = getStoryMutationContext(runtime, "setParagraphStyle");
|
|
1768
1885
|
if (!context) {
|
|
1769
1886
|
return;
|
|
1770
1887
|
}
|
|
@@ -1789,7 +1906,10 @@ function applyRuntimeTableStyle(
|
|
|
1789
1906
|
runtime: WordReviewEditorRuntime,
|
|
1790
1907
|
styleId: string | null,
|
|
1791
1908
|
): void {
|
|
1792
|
-
|
|
1909
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setTableStyle")) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const context = getStoryMutationContext(runtime, "setTableStyle");
|
|
1793
1913
|
if (!context) {
|
|
1794
1914
|
return;
|
|
1795
1915
|
}
|
|
@@ -1819,7 +1939,10 @@ function applyRuntimeParagraphIndentation(
|
|
|
1819
1939
|
hanging?: number;
|
|
1820
1940
|
},
|
|
1821
1941
|
): void {
|
|
1822
|
-
|
|
1942
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphIndentation")) {
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
const context = getStoryMutationContext(runtime, "setParagraphIndentation");
|
|
1823
1946
|
if (!context) {
|
|
1824
1947
|
return;
|
|
1825
1948
|
}
|
|
@@ -1845,7 +1968,10 @@ function applyRuntimeParagraphTabStops(
|
|
|
1845
1968
|
runtime: WordReviewEditorRuntime,
|
|
1846
1969
|
tabStops: Array<{ pos: number; val?: string; leader?: string }>,
|
|
1847
1970
|
): void {
|
|
1848
|
-
|
|
1971
|
+
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphTabStops")) {
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
const context = getStoryMutationContext(runtime, "setParagraphTabStops");
|
|
1849
1975
|
if (!context) {
|
|
1850
1976
|
return;
|
|
1851
1977
|
}
|
|
@@ -1871,7 +1997,18 @@ function applyRuntimeNumberingFlow(
|
|
|
1871
1997
|
runtime: WordReviewEditorRuntime,
|
|
1872
1998
|
operation: { type: "restart"; startAt?: number } | { type: "continue" },
|
|
1873
1999
|
): void {
|
|
1874
|
-
|
|
2000
|
+
if (
|
|
2001
|
+
emitSuggestingUnsupportedMutation(
|
|
2002
|
+
runtime,
|
|
2003
|
+
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
2004
|
+
)
|
|
2005
|
+
) {
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const context = getStoryMutationContext(
|
|
2009
|
+
runtime,
|
|
2010
|
+
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
2011
|
+
);
|
|
1875
2012
|
if (!context) {
|
|
1876
2013
|
return;
|
|
1877
2014
|
}
|
|
@@ -1916,7 +2053,10 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1916
2053
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1917
2054
|
return;
|
|
1918
2055
|
}
|
|
1919
|
-
if (
|
|
2056
|
+
if (emitWorkflowBlockedMutation(runtime, "insertSectionBreak")) {
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
if (isSelectionSuggesting(runtime)) {
|
|
1920
2060
|
runtime.emitBlockedCommand("insertSectionBreak", [{
|
|
1921
2061
|
code: "unsupported_surface",
|
|
1922
2062
|
message: "Section break insertion is not supported in suggesting mode.",
|
|
@@ -1952,6 +2092,56 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1952
2092
|
);
|
|
1953
2093
|
}
|
|
1954
2094
|
|
|
2095
|
+
function emitSuggestingUnsupportedMutation(
|
|
2096
|
+
runtime: WordReviewEditorRuntime,
|
|
2097
|
+
command: string,
|
|
2098
|
+
): boolean {
|
|
2099
|
+
if (!isSelectionSuggesting(runtime)) {
|
|
2100
|
+
return false;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
runtime.emitBlockedCommand(command, [{
|
|
2104
|
+
code: "suggesting_unsupported",
|
|
2105
|
+
message: `"${command}" is not supported in suggesting mode.`,
|
|
2106
|
+
}]);
|
|
2107
|
+
return true;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
function isSelectionSuggesting(runtime: WordReviewEditorRuntime): boolean {
|
|
2111
|
+
return runtime.getInteractionGuardSnapshot().effectiveMode === "suggest";
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
function getFormattingOperationCommandName(
|
|
2115
|
+
operation:
|
|
2116
|
+
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
2117
|
+
| { type: "set-font-family"; fontFamily: string | null }
|
|
2118
|
+
| { type: "set-font-size"; size: number | null }
|
|
2119
|
+
| { type: "set-text-color"; color: string | null }
|
|
2120
|
+
| { type: "set-highlight-color"; color: string | null }
|
|
2121
|
+
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
2122
|
+
| { type: "indent" }
|
|
2123
|
+
| { type: "outdent" },
|
|
2124
|
+
): string {
|
|
2125
|
+
switch (operation.type) {
|
|
2126
|
+
case "toggle":
|
|
2127
|
+
return `toggle${operation.mark.charAt(0).toUpperCase()}${operation.mark.slice(1)}`;
|
|
2128
|
+
case "set-font-family":
|
|
2129
|
+
return "setFontFamily";
|
|
2130
|
+
case "set-font-size":
|
|
2131
|
+
return "setFontSize";
|
|
2132
|
+
case "set-text-color":
|
|
2133
|
+
return "setTextColor";
|
|
2134
|
+
case "set-highlight-color":
|
|
2135
|
+
return "setHighlightColor";
|
|
2136
|
+
case "set-alignment":
|
|
2137
|
+
return "setAlignment";
|
|
2138
|
+
case "indent":
|
|
2139
|
+
return "indent";
|
|
2140
|
+
case "outdent":
|
|
2141
|
+
return "outdent";
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
1955
2145
|
function applyRuntimeDeleteSectionBreak(
|
|
1956
2146
|
runtime: WordReviewEditorRuntime,
|
|
1957
2147
|
sectionIndex: number,
|
|
@@ -1960,6 +2150,16 @@ function applyRuntimeDeleteSectionBreak(
|
|
|
1960
2150
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1961
2151
|
return;
|
|
1962
2152
|
}
|
|
2153
|
+
if (emitWorkflowBlockedMutation(runtime, "deleteSectionBreak")) {
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2157
|
+
runtime.emitBlockedCommand("deleteSectionBreak", [{
|
|
2158
|
+
code: "unsupported_surface",
|
|
2159
|
+
message: "Section break deletion is not supported in suggesting mode.",
|
|
2160
|
+
}]);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
1963
2163
|
|
|
1964
2164
|
const sessionState = runtime.getSessionState();
|
|
1965
2165
|
const timestamp = new Date().toISOString();
|
|
@@ -1989,6 +2189,16 @@ function applyRuntimeUpdateSectionLayout(
|
|
|
1989
2189
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1990
2190
|
return;
|
|
1991
2191
|
}
|
|
2192
|
+
if (emitWorkflowBlockedMutation(runtime, "updateSectionLayout")) {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2196
|
+
runtime.emitBlockedCommand("updateSectionLayout", [{
|
|
2197
|
+
code: "unsupported_surface",
|
|
2198
|
+
message: "Section layout updates are not supported in suggesting mode.",
|
|
2199
|
+
}]);
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
1992
2202
|
|
|
1993
2203
|
const sessionState = runtime.getSessionState();
|
|
1994
2204
|
const timestamp = new Date().toISOString();
|
|
@@ -2025,6 +2235,16 @@ function applyRuntimeSetSectionPageNumbering(
|
|
|
2025
2235
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
2026
2236
|
return;
|
|
2027
2237
|
}
|
|
2238
|
+
if (emitWorkflowBlockedMutation(runtime, "setSectionPageNumbering")) {
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2242
|
+
runtime.emitBlockedCommand("setSectionPageNumbering", [{
|
|
2243
|
+
code: "unsupported_surface",
|
|
2244
|
+
message: "Section page numbering updates are not supported in suggesting mode.",
|
|
2245
|
+
}]);
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2028
2248
|
|
|
2029
2249
|
const sessionState = runtime.getSessionState();
|
|
2030
2250
|
const timestamp = new Date().toISOString();
|
|
@@ -2072,6 +2292,16 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2072
2292
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
2073
2293
|
return;
|
|
2074
2294
|
}
|
|
2295
|
+
if (emitWorkflowBlockedMutation(runtime, "setHeaderFooterLink")) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2299
|
+
runtime.emitBlockedCommand("setHeaderFooterLink", [{
|
|
2300
|
+
code: "unsupported_surface",
|
|
2301
|
+
message: "Header and footer linkage updates are not supported in suggesting mode.",
|
|
2302
|
+
}]);
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2075
2305
|
|
|
2076
2306
|
const sessionState = runtime.getSessionState();
|
|
2077
2307
|
const timestamp = new Date().toISOString();
|
|
@@ -2094,15 +2324,14 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2094
2324
|
}
|
|
2095
2325
|
|
|
2096
2326
|
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
2097
|
-
|
|
2098
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2327
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2099
2328
|
runtime.emitBlockedCommand("insertPageBreak", [{
|
|
2100
2329
|
code: "unsupported_surface",
|
|
2101
2330
|
message: "Page break insertion is not supported in suggesting mode.",
|
|
2102
2331
|
}]);
|
|
2103
2332
|
return;
|
|
2104
2333
|
}
|
|
2105
|
-
const context = getStoryMutationContext(runtime);
|
|
2334
|
+
const context = getStoryMutationContext(runtime, "insertPageBreak");
|
|
2106
2335
|
if (!context) {
|
|
2107
2336
|
return;
|
|
2108
2337
|
}
|
|
@@ -2119,15 +2348,14 @@ function applyRuntimeInsertTable(
|
|
|
2119
2348
|
runtime: WordReviewEditorRuntime,
|
|
2120
2349
|
options: InsertTableOptions,
|
|
2121
2350
|
): void {
|
|
2122
|
-
|
|
2123
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2351
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2124
2352
|
runtime.emitBlockedCommand("insertTable", [{
|
|
2125
2353
|
code: "unsupported_surface",
|
|
2126
2354
|
message: "Table insertion is not supported in suggesting mode.",
|
|
2127
2355
|
}]);
|
|
2128
2356
|
return;
|
|
2129
2357
|
}
|
|
2130
|
-
const context = getStoryMutationContext(runtime);
|
|
2358
|
+
const context = getStoryMutationContext(runtime, "insertTable");
|
|
2131
2359
|
if (!context) {
|
|
2132
2360
|
return;
|
|
2133
2361
|
}
|
|
@@ -2145,15 +2373,14 @@ function applyRuntimeInsertImage(
|
|
|
2145
2373
|
runtime: WordReviewEditorRuntime,
|
|
2146
2374
|
options: InsertImageOptions,
|
|
2147
2375
|
): void {
|
|
2148
|
-
|
|
2149
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2376
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2150
2377
|
runtime.emitBlockedCommand("insertImage", [{
|
|
2151
2378
|
code: "unsupported_surface",
|
|
2152
2379
|
message: "Image insertion is not supported in suggesting mode.",
|
|
2153
2380
|
}]);
|
|
2154
2381
|
return;
|
|
2155
2382
|
}
|
|
2156
|
-
const context = getStoryMutationContext(runtime);
|
|
2383
|
+
const context = getStoryMutationContext(runtime, "insertImage");
|
|
2157
2384
|
if (!context) {
|
|
2158
2385
|
return;
|
|
2159
2386
|
}
|
|
@@ -2191,7 +2418,10 @@ function applyRuntimeImageResize(
|
|
|
2191
2418
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2192
2419
|
return;
|
|
2193
2420
|
}
|
|
2194
|
-
if (
|
|
2421
|
+
if (emitWorkflowBlockedMutation(runtime, "setImageLayout")) {
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2195
2425
|
runtime.emitBlockedCommand("setImageLayout", [{
|
|
2196
2426
|
code: "unsupported_surface",
|
|
2197
2427
|
message: "Image resize is not supported in suggesting mode.",
|
|
@@ -2222,15 +2452,17 @@ function applyRuntimeImageReposition(
|
|
|
2222
2452
|
mediaId: string,
|
|
2223
2453
|
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2224
2454
|
): void {
|
|
2225
|
-
|
|
2226
|
-
|
|
2455
|
+
if (emitWorkflowBlockedMutation(runtime, "setImageFrame")) {
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2227
2459
|
runtime.emitBlockedCommand("setImageFrame", [{
|
|
2228
2460
|
code: "unsupported_surface",
|
|
2229
2461
|
message: "Image reposition is not supported in suggesting mode.",
|
|
2230
2462
|
}]);
|
|
2231
2463
|
return;
|
|
2232
2464
|
}
|
|
2233
|
-
const context = getStoryMutationContext(runtime);
|
|
2465
|
+
const context = getStoryMutationContext(runtime, "setImageFrame");
|
|
2234
2466
|
if (!context) {
|
|
2235
2467
|
return;
|
|
2236
2468
|
}
|
|
@@ -2275,15 +2507,14 @@ function applyRuntimeTableStructureOperation(
|
|
|
2275
2507
|
| { type: "split-cell" }
|
|
2276
2508
|
| { type: "set-cell-background"; color: string },
|
|
2277
2509
|
): void {
|
|
2278
|
-
|
|
2279
|
-
if (snapshot.documentMode === "suggesting") {
|
|
2510
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2280
2511
|
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
2281
2512
|
code: "unsupported_surface",
|
|
2282
2513
|
message: `Table operation "${operation.type}" is not supported in suggesting mode.`,
|
|
2283
2514
|
}]);
|
|
2284
2515
|
return;
|
|
2285
2516
|
}
|
|
2286
|
-
const context = getStoryMutationContext(runtime);
|
|
2517
|
+
const context = getStoryMutationContext(runtime, `table.${operation.type}`);
|
|
2287
2518
|
if (!context) {
|
|
2288
2519
|
return;
|
|
2289
2520
|
}
|
|
@@ -2308,102 +2539,76 @@ function applyRuntimeTextCommand(
|
|
|
2308
2539
|
| { type: "insert-hard-break" }
|
|
2309
2540
|
| { type: "split-paragraph" },
|
|
2310
2541
|
): void {
|
|
2311
|
-
const
|
|
2542
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2543
|
+
const context = getStoryMutationContext(runtime, getMountedTextCommandName(command));
|
|
2312
2544
|
if (!context) {
|
|
2313
2545
|
return;
|
|
2314
2546
|
}
|
|
2315
2547
|
|
|
2548
|
+
const effectiveSelectionMode = runtime.getInteractionGuardSnapshot().effectiveMode;
|
|
2316
2549
|
const listAwareResult = applyListAwareTextCommand(context, command);
|
|
2550
|
+
if (effectiveSelectionMode === "suggest" && listAwareResult) {
|
|
2551
|
+
runtime.emitBlockedCommand(getMountedTextCommandName(command), [{
|
|
2552
|
+
code: "suggesting_unsupported",
|
|
2553
|
+
message: "List structure changes are not supported in suggesting mode.",
|
|
2554
|
+
}]);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2317
2558
|
if (listAwareResult) {
|
|
2318
2559
|
dispatchStoryMutationResult(runtime, context, listAwareResult, context.timestamp);
|
|
2319
2560
|
return;
|
|
2320
2561
|
}
|
|
2321
2562
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
return;
|
|
2344
|
-
}
|
|
2563
|
+
switch (command.type) {
|
|
2564
|
+
case "insert-text":
|
|
2565
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert", text: command.text });
|
|
2566
|
+
return;
|
|
2567
|
+
case "delete-backward":
|
|
2568
|
+
runtime.applyActiveStoryTextCommand({ type: "text.delete-backward" });
|
|
2569
|
+
return;
|
|
2570
|
+
case "delete-forward":
|
|
2571
|
+
runtime.applyActiveStoryTextCommand({ type: "text.delete-forward" });
|
|
2572
|
+
return;
|
|
2573
|
+
case "insert-tab":
|
|
2574
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert-tab" });
|
|
2575
|
+
return;
|
|
2576
|
+
case "outdent-tab":
|
|
2577
|
+
return;
|
|
2578
|
+
case "insert-hard-break":
|
|
2579
|
+
runtime.applyActiveStoryTextCommand({ type: "text.insert-hard-break" });
|
|
2580
|
+
return;
|
|
2581
|
+
case "split-paragraph":
|
|
2582
|
+
runtime.applyActiveStoryTextCommand({ type: "paragraph.split" });
|
|
2583
|
+
return;
|
|
2345
2584
|
}
|
|
2585
|
+
}
|
|
2346
2586
|
|
|
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
|
-
);
|
|
2587
|
+
function getMountedTextCommandName(
|
|
2588
|
+
command:
|
|
2589
|
+
| { type: "insert-text"; text: string }
|
|
2590
|
+
| { type: "delete-backward" }
|
|
2591
|
+
| { type: "delete-forward" }
|
|
2592
|
+
| { type: "insert-tab" }
|
|
2593
|
+
| { type: "outdent-tab" }
|
|
2594
|
+
| { type: "insert-hard-break" }
|
|
2595
|
+
| { type: "split-paragraph" },
|
|
2596
|
+
): string {
|
|
2597
|
+
switch (command.type) {
|
|
2598
|
+
case "insert-text":
|
|
2599
|
+
return "text.insert";
|
|
2600
|
+
case "delete-backward":
|
|
2601
|
+
return "text.delete-backward";
|
|
2602
|
+
case "delete-forward":
|
|
2603
|
+
return "text.delete-forward";
|
|
2604
|
+
case "insert-tab":
|
|
2605
|
+
case "outdent-tab":
|
|
2606
|
+
return "text.insert-tab";
|
|
2607
|
+
case "insert-hard-break":
|
|
2608
|
+
return "text.insert-hard-break";
|
|
2609
|
+
case "split-paragraph":
|
|
2610
|
+
return "paragraph.split";
|
|
2611
|
+
}
|
|
2407
2612
|
}
|
|
2408
2613
|
|
|
2409
2614
|
function applyListAwareTextCommand(
|
|
@@ -2598,8 +2803,21 @@ function canApplyRuntimeMutation(snapshot: RuntimeRenderSnapshot): boolean {
|
|
|
2598
2803
|
return snapshot.isReady && !snapshot.readOnly && !snapshot.fatalError;
|
|
2599
2804
|
}
|
|
2600
2805
|
|
|
2806
|
+
function emitWorkflowBlockedMutation(
|
|
2807
|
+
runtime: WordReviewEditorRuntime,
|
|
2808
|
+
command: string,
|
|
2809
|
+
): boolean {
|
|
2810
|
+
const interactionGuardSnapshot = runtime.getInteractionGuardSnapshot();
|
|
2811
|
+
if (interactionGuardSnapshot.blockedReasons.length === 0) {
|
|
2812
|
+
return false;
|
|
2813
|
+
}
|
|
2814
|
+
runtime.emitBlockedCommand(command, interactionGuardSnapshot.blockedReasons);
|
|
2815
|
+
return true;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2601
2818
|
function getStoryMutationContext(
|
|
2602
2819
|
runtime: WordReviewEditorRuntime,
|
|
2820
|
+
command?: string,
|
|
2603
2821
|
): {
|
|
2604
2822
|
timestamp: string;
|
|
2605
2823
|
activeStory: EditorStoryTarget;
|
|
@@ -2611,6 +2829,9 @@ function getStoryMutationContext(
|
|
|
2611
2829
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2612
2830
|
return null;
|
|
2613
2831
|
}
|
|
2832
|
+
if (command && emitWorkflowBlockedMutation(runtime, command)) {
|
|
2833
|
+
return null;
|
|
2834
|
+
}
|
|
2614
2835
|
|
|
2615
2836
|
const persistedDocument = runtime.getSessionState().canonicalDocument;
|
|
2616
2837
|
const activeStory = snapshot.activeStory;
|
|
@@ -3034,6 +3255,20 @@ function deriveEditorViewMode(
|
|
|
3034
3255
|
return reviewMode === "editing" ? "editing" : "review";
|
|
3035
3256
|
}
|
|
3036
3257
|
|
|
3258
|
+
function resolveWordReviewEditorChromeVisibility(
|
|
3259
|
+
chromeVisibility: WordReviewEditorProps["chromeVisibility"],
|
|
3260
|
+
showReviewPanel: boolean,
|
|
3261
|
+
): Partial<WordReviewEditorChromeVisibility> {
|
|
3262
|
+
const legacyVisibility =
|
|
3263
|
+
showReviewPanel
|
|
3264
|
+
? {}
|
|
3265
|
+
: { reviewRail: false } satisfies Partial<WordReviewEditorChromeVisibility>;
|
|
3266
|
+
return {
|
|
3267
|
+
...legacyVisibility,
|
|
3268
|
+
...chromeVisibility,
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3037
3272
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
3038
3273
|
return {
|
|
3039
3274
|
anchor: selection.anchor,
|
|
@@ -3057,6 +3292,7 @@ function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
|
3057
3292
|
|
|
3058
3293
|
function createSelectionFromAnchor(
|
|
3059
3294
|
anchor: PublicSelectionSnapshot["activeRange"],
|
|
3295
|
+
storyTarget?: EditorStoryTarget,
|
|
3060
3296
|
): PublicSelectionSnapshot {
|
|
3061
3297
|
switch (anchor.kind) {
|
|
3062
3298
|
case "range":
|
|
@@ -3065,6 +3301,7 @@ function createSelectionFromAnchor(
|
|
|
3065
3301
|
head: anchor.to,
|
|
3066
3302
|
isCollapsed: anchor.from === anchor.to,
|
|
3067
3303
|
activeRange: anchor,
|
|
3304
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3068
3305
|
};
|
|
3069
3306
|
case "node":
|
|
3070
3307
|
return {
|
|
@@ -3072,6 +3309,7 @@ function createSelectionFromAnchor(
|
|
|
3072
3309
|
head: anchor.at,
|
|
3073
3310
|
isCollapsed: true,
|
|
3074
3311
|
activeRange: anchor,
|
|
3312
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3075
3313
|
};
|
|
3076
3314
|
case "detached":
|
|
3077
3315
|
return {
|
|
@@ -3079,6 +3317,7 @@ function createSelectionFromAnchor(
|
|
|
3079
3317
|
head: anchor.lastKnownRange.to,
|
|
3080
3318
|
isCollapsed: anchor.lastKnownRange.from === anchor.lastKnownRange.to,
|
|
3081
3319
|
activeRange: anchor,
|
|
3320
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
3082
3321
|
};
|
|
3083
3322
|
}
|
|
3084
3323
|
}
|
|
@@ -3184,7 +3423,13 @@ function interactionGuardSnapshotsEqual(
|
|
|
3184
3423
|
if (left === right) {
|
|
3185
3424
|
return true;
|
|
3186
3425
|
}
|
|
3187
|
-
return
|
|
3426
|
+
return (
|
|
3427
|
+
left.effectiveMode === right.effectiveMode &&
|
|
3428
|
+
left.matchedScopeId === right.matchedScopeId &&
|
|
3429
|
+
left.matchedScopeMode === right.matchedScopeMode &&
|
|
3430
|
+
left.disabledReason === right.disabledReason &&
|
|
3431
|
+
workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons)
|
|
3432
|
+
);
|
|
3188
3433
|
}
|
|
3189
3434
|
|
|
3190
3435
|
function workflowBlockedReasonsEqual(
|
|
@@ -3256,7 +3501,14 @@ function editorAnchorProjectionEqual(
|
|
|
3256
3501
|
function createSelectionToolbarSelectionKey(
|
|
3257
3502
|
selection: RuntimeRenderSnapshot["selection"],
|
|
3258
3503
|
activeStory: EditorStoryTarget,
|
|
3504
|
+
activeRevisionId?: string,
|
|
3259
3505
|
): string | null {
|
|
3506
|
+
if (activeRevisionId) {
|
|
3507
|
+
return JSON.stringify({
|
|
3508
|
+
story: activeStory,
|
|
3509
|
+
revisionId: activeRevisionId,
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3260
3512
|
if (selection.isCollapsed || selection.activeRange.kind !== "range") {
|
|
3261
3513
|
return null;
|
|
3262
3514
|
}
|
|
@@ -3275,6 +3527,8 @@ function buildSelectionToolbarModel(args: {
|
|
|
3275
3527
|
documentNavigation: ReturnType<WordReviewEditorRuntime["getDocumentNavigationSnapshot"]>;
|
|
3276
3528
|
styleCatalog: StyleCatalogSnapshot;
|
|
3277
3529
|
formattingState: FormattingStateSnapshot;
|
|
3530
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
3531
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
3278
3532
|
addCommentDisabledReason?: string;
|
|
3279
3533
|
}): SelectionToolbarModel | null {
|
|
3280
3534
|
const {
|
|
@@ -3284,6 +3538,8 @@ function buildSelectionToolbarModel(args: {
|
|
|
3284
3538
|
documentNavigation,
|
|
3285
3539
|
styleCatalog,
|
|
3286
3540
|
formattingState,
|
|
3541
|
+
workflowScopeSnapshot,
|
|
3542
|
+
interactionGuardSnapshot,
|
|
3287
3543
|
addCommentDisabledReason,
|
|
3288
3544
|
} = args;
|
|
3289
3545
|
|
|
@@ -3304,6 +3560,14 @@ function buildSelectionToolbarModel(args: {
|
|
|
3304
3560
|
|
|
3305
3561
|
const badges = [
|
|
3306
3562
|
createSelectionToolbarStoryBadge(viewState.activeStory),
|
|
3563
|
+
createSelectionToolbarWorkflowBadge(
|
|
3564
|
+
resolveSelectionWorkflowPosture(
|
|
3565
|
+
snapshot,
|
|
3566
|
+
viewState,
|
|
3567
|
+
workflowScopeSnapshot,
|
|
3568
|
+
interactionGuardSnapshot,
|
|
3569
|
+
),
|
|
3570
|
+
),
|
|
3307
3571
|
viewState.workspaceMode === "page" && documentNavigation.pageCount > 0
|
|
3308
3572
|
? { label: `Page ${documentNavigation.activePageIndex + 1}` as const }
|
|
3309
3573
|
: null,
|
|
@@ -3311,15 +3575,137 @@ function buildSelectionToolbarModel(args: {
|
|
|
3311
3575
|
createSelectionToolbarListBadge(viewState),
|
|
3312
3576
|
].filter((badge): badge is SelectionToolbarModel["badges"][number] => Boolean(badge));
|
|
3313
3577
|
|
|
3578
|
+
const workflowPosture = resolveSelectionWorkflowPosture(
|
|
3579
|
+
snapshot,
|
|
3580
|
+
viewState,
|
|
3581
|
+
workflowScopeSnapshot,
|
|
3582
|
+
interactionGuardSnapshot,
|
|
3583
|
+
);
|
|
3584
|
+
const canToggleFormatting = workflowPosture.mode === "edit";
|
|
3585
|
+
const canAddComment = workflowPosture.mode === "view" || workflowPosture.mode === "blocked"
|
|
3586
|
+
? false
|
|
3587
|
+
: capabilities.canAddComment;
|
|
3588
|
+
const disabledReason =
|
|
3589
|
+
workflowPosture.disabledReason ??
|
|
3590
|
+
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3591
|
+
|
|
3314
3592
|
return {
|
|
3315
3593
|
previewText,
|
|
3316
3594
|
badges,
|
|
3317
|
-
canToggleFormatting
|
|
3595
|
+
canToggleFormatting,
|
|
3318
3596
|
boldActive: formattingState.bold,
|
|
3319
3597
|
italicActive: formattingState.italic,
|
|
3320
3598
|
underlineActive: formattingState.underline,
|
|
3321
|
-
canAddComment
|
|
3322
|
-
...(
|
|
3599
|
+
canAddComment,
|
|
3600
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
function buildSuggestionCardModel(args: {
|
|
3605
|
+
snapshot: RuntimeRenderSnapshot;
|
|
3606
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>;
|
|
3607
|
+
capabilities: ReturnType<typeof deriveCapabilities>;
|
|
3608
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
3609
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
3610
|
+
activeRevisionId?: string;
|
|
3611
|
+
suppressedSuggestionRevisionId?: string | null;
|
|
3612
|
+
addCommentDisabledReason?: string;
|
|
3613
|
+
}): SuggestionCardModel | null {
|
|
3614
|
+
const {
|
|
3615
|
+
snapshot,
|
|
3616
|
+
viewState,
|
|
3617
|
+
capabilities,
|
|
3618
|
+
workflowScopeSnapshot,
|
|
3619
|
+
interactionGuardSnapshot,
|
|
3620
|
+
activeRevisionId,
|
|
3621
|
+
suppressedSuggestionRevisionId,
|
|
3622
|
+
addCommentDisabledReason,
|
|
3623
|
+
} = args;
|
|
3624
|
+
|
|
3625
|
+
if (
|
|
3626
|
+
!snapshot.surface ||
|
|
3627
|
+
!capabilities.canEdit ||
|
|
3628
|
+
viewState.viewMode === "view"
|
|
3629
|
+
) {
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
const activeRange =
|
|
3634
|
+
!snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
|
|
3635
|
+
? snapshot.selection.activeRange
|
|
3636
|
+
: null;
|
|
3637
|
+
const selectionFrom = activeRange
|
|
3638
|
+
? Math.min(activeRange.from, activeRange.to)
|
|
3639
|
+
: null;
|
|
3640
|
+
const selectionTo = activeRange
|
|
3641
|
+
? Math.max(activeRange.from, activeRange.to)
|
|
3642
|
+
: null;
|
|
3643
|
+
const candidateRevisions = snapshot.trackedChanges.revisions.filter((revision) =>
|
|
3644
|
+
storyTargetsEqual(revision.storyTarget ?? { kind: "main" }, viewState.activeStory) &&
|
|
3645
|
+
revision.status === "active" &&
|
|
3646
|
+
revision.actionability === "actionable" &&
|
|
3647
|
+
revision.anchor.kind === "range" &&
|
|
3648
|
+
(
|
|
3649
|
+
activeRevisionId === revision.revisionId ||
|
|
3650
|
+
(
|
|
3651
|
+
selectionFrom !== null &&
|
|
3652
|
+
selectionTo !== null &&
|
|
3653
|
+
rangesOverlap(
|
|
3654
|
+
selectionFrom,
|
|
3655
|
+
selectionTo,
|
|
3656
|
+
revision.anchor.from,
|
|
3657
|
+
revision.anchor.to,
|
|
3658
|
+
)
|
|
3659
|
+
)
|
|
3660
|
+
)
|
|
3661
|
+
);
|
|
3662
|
+
const focusedRevision = (
|
|
3663
|
+
activeRevisionId
|
|
3664
|
+
? candidateRevisions.find((revision) => revision.revisionId === activeRevisionId)
|
|
3665
|
+
: null
|
|
3666
|
+
) ?? candidateRevisions[0];
|
|
3667
|
+
|
|
3668
|
+
if (!focusedRevision || focusedRevision.revisionId === suppressedSuggestionRevisionId) {
|
|
3669
|
+
return null;
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
const badges = [
|
|
3673
|
+
createSelectionToolbarStoryBadge(viewState.activeStory),
|
|
3674
|
+
workflowScopeSnapshot?.activeWorkItem?.title
|
|
3675
|
+
? {
|
|
3676
|
+
label: workflowScopeSnapshot.activeWorkItem.title,
|
|
3677
|
+
tone: "accent" as const,
|
|
3678
|
+
}
|
|
3679
|
+
: null,
|
|
3680
|
+
].filter((badge): badge is SuggestionCardModel["badges"][number] => Boolean(badge));
|
|
3681
|
+
const workflowPosture = resolveSelectionWorkflowPosture(
|
|
3682
|
+
snapshot,
|
|
3683
|
+
viewState,
|
|
3684
|
+
workflowScopeSnapshot,
|
|
3685
|
+
interactionGuardSnapshot,
|
|
3686
|
+
);
|
|
3687
|
+
const canReviewSuggestion = workflowPosture.mode === "edit" || workflowPosture.mode === "suggest";
|
|
3688
|
+
const canAddComment = workflowPosture.mode === "view" || workflowPosture.mode === "blocked"
|
|
3689
|
+
? false
|
|
3690
|
+
: capabilities.canAddComment;
|
|
3691
|
+
const disabledReason =
|
|
3692
|
+
workflowPosture.disabledReason ??
|
|
3693
|
+
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3694
|
+
|
|
3695
|
+
return {
|
|
3696
|
+
revisionId: focusedRevision.revisionId,
|
|
3697
|
+
kindLabel: getSuggestionKindLabel(focusedRevision.kind),
|
|
3698
|
+
previewText:
|
|
3699
|
+
focusedRevision.excerpt ??
|
|
3700
|
+
focusedRevision.detail ??
|
|
3701
|
+
focusedRevision.label ??
|
|
3702
|
+
"Suggested change",
|
|
3703
|
+
badges,
|
|
3704
|
+
canAccept: canReviewSuggestion && capabilities.canAcceptChange && focusedRevision.canAccept,
|
|
3705
|
+
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedRevision.canReject,
|
|
3706
|
+
canEditSuggestion: canReviewSuggestion,
|
|
3707
|
+
canAddComment,
|
|
3708
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
3323
3709
|
};
|
|
3324
3710
|
}
|
|
3325
3711
|
|
|
@@ -3377,6 +3763,149 @@ function createSelectionToolbarListBadge(
|
|
|
3377
3763
|
};
|
|
3378
3764
|
}
|
|
3379
3765
|
|
|
3766
|
+
function createSelectionToolbarWorkflowBadge(
|
|
3767
|
+
posture: ReturnType<typeof resolveSelectionWorkflowPosture>,
|
|
3768
|
+
): SelectionToolbarModel["badges"][number] | null {
|
|
3769
|
+
switch (posture.mode) {
|
|
3770
|
+
case "suggest":
|
|
3771
|
+
return { label: "Suggest", tone: "accent" };
|
|
3772
|
+
case "comment":
|
|
3773
|
+
return { label: "Comment only", tone: "accent" };
|
|
3774
|
+
case "view":
|
|
3775
|
+
return { label: "View only" };
|
|
3776
|
+
case "blocked":
|
|
3777
|
+
return { label: "Blocked" };
|
|
3778
|
+
default:
|
|
3779
|
+
return null;
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
function resolveSelectionWorkflowPosture(
|
|
3784
|
+
snapshot: RuntimeRenderSnapshot,
|
|
3785
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>,
|
|
3786
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null,
|
|
3787
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot,
|
|
3788
|
+
): {
|
|
3789
|
+
mode: "edit" | "suggest" | "comment" | "view" | "blocked";
|
|
3790
|
+
disabledReason?: string;
|
|
3791
|
+
} {
|
|
3792
|
+
const blockedReasons =
|
|
3793
|
+
interactionGuardSnapshot?.blockedReasons ??
|
|
3794
|
+
workflowScopeSnapshot?.blockedReasons ??
|
|
3795
|
+
[];
|
|
3796
|
+
const blockingReason = blockedReasons[0];
|
|
3797
|
+
if (blockingReason) {
|
|
3798
|
+
if (blockingReason.code === "workflow_comment_only") {
|
|
3799
|
+
return {
|
|
3800
|
+
mode: "comment",
|
|
3801
|
+
disabledReason: blockingReason.message,
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
if (blockingReason.code === "workflow_view_only") {
|
|
3805
|
+
return {
|
|
3806
|
+
mode: "view",
|
|
3807
|
+
disabledReason: blockingReason.message,
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
return {
|
|
3811
|
+
mode: "blocked",
|
|
3812
|
+
disabledReason: blockingReason.message,
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
if (interactionGuardSnapshot) {
|
|
3817
|
+
if (interactionGuardSnapshot.effectiveMode === "suggest") {
|
|
3818
|
+
return {
|
|
3819
|
+
mode: "suggest",
|
|
3820
|
+
disabledReason:
|
|
3821
|
+
interactionGuardSnapshot.disabledReason ??
|
|
3822
|
+
"Suggestion authoring is active here; direct formatting changes are blocked.",
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
if (interactionGuardSnapshot.effectiveMode === "comment") {
|
|
3826
|
+
return {
|
|
3827
|
+
mode: "comment",
|
|
3828
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
if (interactionGuardSnapshot.effectiveMode === "view") {
|
|
3832
|
+
return {
|
|
3833
|
+
mode: "view",
|
|
3834
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
if (interactionGuardSnapshot.effectiveMode === "blocked") {
|
|
3838
|
+
return {
|
|
3839
|
+
mode: "blocked",
|
|
3840
|
+
disabledReason: interactionGuardSnapshot.disabledReason,
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
const activeRange =
|
|
3846
|
+
!snapshot.selection.isCollapsed && snapshot.selection.activeRange.kind === "range"
|
|
3847
|
+
? snapshot.selection.activeRange
|
|
3848
|
+
: null;
|
|
3849
|
+
const matchingScope = activeRange && workflowScopeSnapshot
|
|
3850
|
+
? workflowScopeSnapshot.scopes.find((scope) => {
|
|
3851
|
+
const scopeStoryTarget = scope.storyTarget ?? { kind: "main" as const };
|
|
3852
|
+
if (!storyTargetsEqual(scopeStoryTarget, viewState.activeStory)) {
|
|
3853
|
+
return false;
|
|
3854
|
+
}
|
|
3855
|
+
if (scope.anchor.kind === "detached") {
|
|
3856
|
+
return false;
|
|
3857
|
+
}
|
|
3858
|
+
const scopeFrom = scope.anchor.kind === "range" ? scope.anchor.from : scope.anchor.at;
|
|
3859
|
+
const scopeTo = scope.anchor.kind === "range" ? scope.anchor.to : scope.anchor.at;
|
|
3860
|
+
return activeRange.from >= scopeFrom && activeRange.to <= scopeTo;
|
|
3861
|
+
})
|
|
3862
|
+
: null;
|
|
3863
|
+
|
|
3864
|
+
if (matchingScope?.mode === "suggest") {
|
|
3865
|
+
return {
|
|
3866
|
+
mode: "suggest",
|
|
3867
|
+
disabledReason: "Suggestion authoring is active here; direct formatting changes are blocked.",
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
if (matchingScope?.mode === "comment") {
|
|
3871
|
+
return {
|
|
3872
|
+
mode: "comment",
|
|
3873
|
+
disabledReason: `Scope "${matchingScope.label ?? matchingScope.scopeId}" allows comments only.`,
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
if (matchingScope?.mode === "view") {
|
|
3877
|
+
return {
|
|
3878
|
+
mode: "view",
|
|
3879
|
+
disabledReason: `Scope "${matchingScope.label ?? matchingScope.scopeId}" is view-only.`,
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
return { mode: "edit" };
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
function rangesOverlap(
|
|
3886
|
+
leftFrom: number,
|
|
3887
|
+
leftTo: number,
|
|
3888
|
+
rightFrom: number,
|
|
3889
|
+
rightTo: number,
|
|
3890
|
+
): boolean {
|
|
3891
|
+
return leftFrom < rightTo && rightFrom < leftTo;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
function getSuggestionKindLabel(kind: TrackedChangeEntrySnapshot["kind"]): string {
|
|
3895
|
+
switch (kind) {
|
|
3896
|
+
case "insertion":
|
|
3897
|
+
return "Suggested insertion";
|
|
3898
|
+
case "deletion":
|
|
3899
|
+
return "Suggested deletion";
|
|
3900
|
+
case "formatting":
|
|
3901
|
+
return "Suggested formatting change";
|
|
3902
|
+
case "property-change":
|
|
3903
|
+
return "Suggested property change";
|
|
3904
|
+
case "move":
|
|
3905
|
+
return "Suggested move";
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3380
3909
|
function buildActiveImageContext(args: {
|
|
3381
3910
|
canonicalDocument: PersistedEditorSnapshot["canonicalDocument"];
|
|
3382
3911
|
selection: RuntimeRenderSnapshot["selection"];
|