@beyondwork/docx-react-component 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -27,11 +27,15 @@ import type {
|
|
|
27
27
|
FormattingAlignment,
|
|
28
28
|
FormattingStateSnapshot,
|
|
29
29
|
HeaderFooterLinkPatch,
|
|
30
|
+
HostAnnotationItem,
|
|
31
|
+
HostAnnotationOverlay,
|
|
30
32
|
InteractionGuardSnapshot,
|
|
31
33
|
InsertImageOptions,
|
|
32
34
|
InsertTableOptions,
|
|
33
35
|
PageLayoutSnapshot,
|
|
34
36
|
PersistedEditorSnapshot,
|
|
37
|
+
ReviewQueueItem,
|
|
38
|
+
ReviewQueueSnapshot,
|
|
35
39
|
RuntimeRenderSnapshot,
|
|
36
40
|
SectionBreakType,
|
|
37
41
|
SectionLayoutPatch,
|
|
@@ -39,8 +43,11 @@ import type {
|
|
|
39
43
|
SearchOptions,
|
|
40
44
|
SearchResultSnapshot,
|
|
41
45
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
46
|
+
SuggestionEntrySnapshot,
|
|
47
|
+
SuggestionsSnapshot,
|
|
42
48
|
StyleCatalogSnapshot,
|
|
43
49
|
SurfaceBlockSnapshot,
|
|
50
|
+
SurfaceInlineSegment,
|
|
44
51
|
TrackedChangeEntrySnapshot,
|
|
45
52
|
TocRefreshResult,
|
|
46
53
|
UpdateFieldsResult,
|
|
@@ -48,6 +55,8 @@ import type {
|
|
|
48
55
|
WorkflowBlockedCommandReason,
|
|
49
56
|
WorkflowMarkupSnapshot,
|
|
50
57
|
WorkflowScopeSnapshot,
|
|
58
|
+
WordReviewEditorChromeOptions,
|
|
59
|
+
WordReviewEditorChromePreset,
|
|
51
60
|
WordReviewEditorChromeVisibility,
|
|
52
61
|
WordReviewEditorEvent,
|
|
53
62
|
WordReviewEditorProps,
|
|
@@ -81,6 +90,8 @@ import {
|
|
|
81
90
|
outdentListItems,
|
|
82
91
|
restartNumbering as restartListNumbering,
|
|
83
92
|
splitListParagraph,
|
|
93
|
+
toggleBulletedList,
|
|
94
|
+
toggleNumberedList,
|
|
84
95
|
} from "../core/commands/list-commands.ts";
|
|
85
96
|
import {
|
|
86
97
|
resolveActiveParagraphIndex,
|
|
@@ -101,6 +112,7 @@ import {
|
|
|
101
112
|
} from "../core/commands/image-commands.ts";
|
|
102
113
|
import {
|
|
103
114
|
applyTableStructureOperation,
|
|
115
|
+
getTableStructureContext,
|
|
104
116
|
} from "../core/commands/table-structure-commands.ts";
|
|
105
117
|
import {
|
|
106
118
|
deleteSelectionOrBackward,
|
|
@@ -124,6 +136,10 @@ import {
|
|
|
124
136
|
import { readOpcPackage } from "../io/opc/package-reader.ts";
|
|
125
137
|
import { deriveCapabilities } from "../runtime/session-capabilities";
|
|
126
138
|
import { searchDocument } from "../runtime/document-search.ts";
|
|
139
|
+
import {
|
|
140
|
+
resolveCurrentContextAnalyticsQuery,
|
|
141
|
+
runtimeContextAnalyticsSnapshotsEqual,
|
|
142
|
+
} from "../runtime/context-analytics.ts";
|
|
127
143
|
import {
|
|
128
144
|
createEditorViewStateSnapshot,
|
|
129
145
|
createViewState,
|
|
@@ -154,7 +170,12 @@ import type {
|
|
|
154
170
|
SelectionToolbarModel,
|
|
155
171
|
SuggestionCardModel,
|
|
156
172
|
} from "./headless/selection-toolbar-model";
|
|
173
|
+
import { resolveActiveSelectionTool } from "./headless/selection-tool-resolver";
|
|
157
174
|
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
175
|
+
import {
|
|
176
|
+
resolveHeadingShortcutStyleId,
|
|
177
|
+
resolveShellShortcut,
|
|
178
|
+
} from "./runtime-shortcut-dispatch";
|
|
158
179
|
import { deriveVisibleWorkflowBlockedRails } from "./workflow-surface-blocked-rails.ts";
|
|
159
180
|
import {
|
|
160
181
|
type WordReviewEditorRuntime,
|
|
@@ -169,6 +190,10 @@ import {
|
|
|
169
190
|
} from "./browser-export";
|
|
170
191
|
import { EditorShellView } from "./editor-shell-view.tsx";
|
|
171
192
|
import { EditorSurfaceController } from "./editor-surface-controller.tsx";
|
|
193
|
+
import {
|
|
194
|
+
resolveChromePreset,
|
|
195
|
+
resolveChromeVisibilityForPreset,
|
|
196
|
+
} from "../ui-tailwind/chrome/chrome-preset-model.ts";
|
|
172
197
|
|
|
173
198
|
export {
|
|
174
199
|
__createFallbackRuntime,
|
|
@@ -244,12 +269,20 @@ export function __createWordReviewEditorRefBridge(
|
|
|
244
269
|
clonePublicValue(runtime.getRenderSnapshot().comments),
|
|
245
270
|
getTrackedChangesSnapshot: () =>
|
|
246
271
|
clonePublicValue(runtime.getRenderSnapshot().trackedChanges),
|
|
272
|
+
getSuggestionsSnapshot: () =>
|
|
273
|
+
clonePublicValue(runtime.getSuggestionsSnapshot()),
|
|
247
274
|
getComments: () => clonePublicValue(runtime.getRenderSnapshot().comments),
|
|
248
275
|
getTrackedChanges: () =>
|
|
249
276
|
clonePublicValue(runtime.getRenderSnapshot().trackedChanges),
|
|
250
277
|
isDirty: () => runtime.getRenderSnapshot().isDirty,
|
|
251
278
|
getFormattingState: () => getFormattingStateFromRenderSnapshot(runtime.getRenderSnapshot()),
|
|
252
279
|
getStyleCatalog: () => getRuntimeStyleCatalog(runtime),
|
|
280
|
+
toggleBulletedList: () => {
|
|
281
|
+
applyRuntimeListToggle(runtime, "bulleted");
|
|
282
|
+
},
|
|
283
|
+
toggleNumberedList: () => {
|
|
284
|
+
applyRuntimeListToggle(runtime, "numbered");
|
|
285
|
+
},
|
|
253
286
|
toggleBold: () => {
|
|
254
287
|
applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "bold" });
|
|
255
288
|
},
|
|
@@ -310,6 +343,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
310
343
|
outdent: () => {
|
|
311
344
|
applyRuntimeFormattingOperation(runtime, { type: "outdent" });
|
|
312
345
|
},
|
|
346
|
+
restartNumbering: () => {
|
|
347
|
+
applyRuntimeNumberingFlow(runtime, { type: "restart" });
|
|
348
|
+
},
|
|
349
|
+
continueNumbering: () => {
|
|
350
|
+
applyRuntimeNumberingFlow(runtime, { type: "continue" });
|
|
351
|
+
},
|
|
313
352
|
insertPageBreak: () => {
|
|
314
353
|
applyRuntimeInsertPageBreak(runtime);
|
|
315
354
|
},
|
|
@@ -407,6 +446,36 @@ export function __createWordReviewEditorRefBridge(
|
|
|
407
446
|
getDocumentNavigationSnapshot: () => {
|
|
408
447
|
return clonePublicValue(runtime.getDocumentNavigationSnapshot());
|
|
409
448
|
},
|
|
449
|
+
getCurrentLocation: () => {
|
|
450
|
+
return clonePublicValue(runtime.getCurrentLocation());
|
|
451
|
+
},
|
|
452
|
+
getLocationForSelection: (selection) => {
|
|
453
|
+
return clonePublicValue(runtime.getLocationForSelection(selection));
|
|
454
|
+
},
|
|
455
|
+
getLocationForAnchor: (anchor, storyTarget) => {
|
|
456
|
+
return clonePublicValue(runtime.getLocationForAnchor(anchor, storyTarget));
|
|
457
|
+
},
|
|
458
|
+
captureRestorePoint: (input) => {
|
|
459
|
+
return clonePublicValue(runtime.captureRestorePoint(input));
|
|
460
|
+
},
|
|
461
|
+
restoreToPoint: (point, options) => {
|
|
462
|
+
return clonePublicValue(runtime.restoreToPoint(point, options));
|
|
463
|
+
},
|
|
464
|
+
getOutlineSnapshot: () => {
|
|
465
|
+
return clonePublicValue(runtime.getOutlineSnapshot());
|
|
466
|
+
},
|
|
467
|
+
getTocSnapshot: () => {
|
|
468
|
+
return clonePublicValue(runtime.getTocSnapshot());
|
|
469
|
+
},
|
|
470
|
+
getSections: () => {
|
|
471
|
+
return clonePublicValue(runtime.getSections());
|
|
472
|
+
},
|
|
473
|
+
getSectionSnapshot: (input) => {
|
|
474
|
+
return clonePublicValue(runtime.getSectionSnapshot(input));
|
|
475
|
+
},
|
|
476
|
+
describeEventImpact: (event) => {
|
|
477
|
+
return clonePublicValue(runtime.describeEventImpact(event));
|
|
478
|
+
},
|
|
410
479
|
getFieldSnapshot: () => {
|
|
411
480
|
return clonePublicValue(runtime.getFieldSnapshot());
|
|
412
481
|
},
|
|
@@ -467,6 +536,21 @@ export function __createWordReviewEditorRefBridge(
|
|
|
467
536
|
getWorkflowMarkupSnapshot: () => {
|
|
468
537
|
return clonePublicValue(runtime.getWorkflowMarkupSnapshot());
|
|
469
538
|
},
|
|
539
|
+
setWorkflowMetadataDefinitions: (definitions) => {
|
|
540
|
+
runtime.setWorkflowMetadataDefinitions(clonePublicValue(definitions));
|
|
541
|
+
},
|
|
542
|
+
clearWorkflowMetadataDefinitions: () => {
|
|
543
|
+
runtime.clearWorkflowMetadataDefinitions();
|
|
544
|
+
},
|
|
545
|
+
setWorkflowMetadataEntries: (entries) => {
|
|
546
|
+
runtime.setWorkflowMetadataEntries(clonePublicValue(entries));
|
|
547
|
+
},
|
|
548
|
+
clearWorkflowMetadataEntries: () => {
|
|
549
|
+
runtime.clearWorkflowMetadataEntries();
|
|
550
|
+
},
|
|
551
|
+
getWorkflowMetadataSnapshot: () => {
|
|
552
|
+
return clonePublicValue(runtime.getWorkflowMetadataSnapshot());
|
|
553
|
+
},
|
|
470
554
|
setHostAnnotationOverlay: (overlay) => {
|
|
471
555
|
runtime.setHostAnnotationOverlay(clonePublicValue(overlay));
|
|
472
556
|
},
|
|
@@ -476,6 +560,31 @@ export function __createWordReviewEditorRefBridge(
|
|
|
476
560
|
getHostAnnotationSnapshot: () => {
|
|
477
561
|
return clonePublicValue(runtime.getHostAnnotationSnapshot());
|
|
478
562
|
},
|
|
563
|
+
getReviewQueueSnapshot: () => {
|
|
564
|
+
return clonePublicValue(
|
|
565
|
+
deriveReviewQueueSnapshot({
|
|
566
|
+
sections: runtime.getSections(),
|
|
567
|
+
comments: runtime.getRenderSnapshot().comments.threads,
|
|
568
|
+
trackedChanges: runtime.getRenderSnapshot().trackedChanges.revisions,
|
|
569
|
+
hostAnnotations: runtime.getHostAnnotationSnapshot().annotations,
|
|
570
|
+
}),
|
|
571
|
+
);
|
|
572
|
+
},
|
|
573
|
+
getRuntimeContextAnalytics: (query) => {
|
|
574
|
+
return clonePublicValue(runtime.getRuntimeContextAnalytics(query));
|
|
575
|
+
},
|
|
576
|
+
goToNextReviewItem: () => {
|
|
577
|
+
return clonePublicValue(navigateReviewQueue(runtime, "next"));
|
|
578
|
+
},
|
|
579
|
+
goToPreviousReviewItem: () => {
|
|
580
|
+
return clonePublicValue(navigateReviewQueue(runtime, "previous"));
|
|
581
|
+
},
|
|
582
|
+
markSectionForReview: (input) => {
|
|
583
|
+
return markSectionForReview(runtime, input);
|
|
584
|
+
},
|
|
585
|
+
clearSectionReviewMark: (annotationId) => {
|
|
586
|
+
clearSectionReviewMark(runtime, annotationId);
|
|
587
|
+
},
|
|
479
588
|
getWorkflowCandidateRanges: (options) => {
|
|
480
589
|
return clonePublicValue(runtime.getWorkflowCandidateRanges(options));
|
|
481
590
|
},
|
|
@@ -514,7 +623,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
514
623
|
initialSessionState,
|
|
515
624
|
initialSnapshot,
|
|
516
625
|
initialSourceLabel,
|
|
626
|
+
chromePreset,
|
|
627
|
+
chromeOptions,
|
|
517
628
|
markupDisplay,
|
|
629
|
+
showUnsupportedObjectPreviews = false,
|
|
518
630
|
onError,
|
|
519
631
|
onEvent,
|
|
520
632
|
onWarning,
|
|
@@ -532,6 +644,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
532
644
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
533
645
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
534
646
|
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
647
|
+
const [hostAnnotationOverlayState, setHostAnnotationOverlayState] =
|
|
648
|
+
useState<HostAnnotationOverlay | null>(null);
|
|
649
|
+
const [activeReviewQueueItemId, setActiveReviewQueueItemId] = useState<string | null>(null);
|
|
650
|
+
const [reviewSectionMarks, setReviewSectionMarks] = useState<Array<{
|
|
651
|
+
annotationId: string;
|
|
652
|
+
sectionIndex: number;
|
|
653
|
+
label: string;
|
|
654
|
+
}>>([]);
|
|
535
655
|
const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
|
|
536
656
|
const selectionToolbarElementRef = useRef<HTMLDivElement | null>(null);
|
|
537
657
|
const shellRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -676,10 +796,75 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
676
796
|
{ effectiveMode: "edit", blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
677
797
|
interactionGuardSnapshotsEqual,
|
|
678
798
|
);
|
|
799
|
+
const documentContextAnalytics = useRuntimeValue(
|
|
800
|
+
runtime
|
|
801
|
+
? {
|
|
802
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
803
|
+
getValue: () =>
|
|
804
|
+
runtime.getRuntimeContextAnalytics({
|
|
805
|
+
scopeKind: "document",
|
|
806
|
+
}),
|
|
807
|
+
}
|
|
808
|
+
: null,
|
|
809
|
+
null,
|
|
810
|
+
runtimeContextAnalyticsSnapshotsEqual,
|
|
811
|
+
);
|
|
812
|
+
const selectionContextAnalytics = useRuntimeValue(
|
|
813
|
+
runtime
|
|
814
|
+
? {
|
|
815
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
816
|
+
getValue: () => runtime.getRuntimeContextAnalytics(),
|
|
817
|
+
}
|
|
818
|
+
: null,
|
|
819
|
+
null,
|
|
820
|
+
runtimeContextAnalyticsSnapshotsEqual,
|
|
821
|
+
);
|
|
822
|
+
const currentScopeContextAnalytics = useRuntimeValue(
|
|
823
|
+
runtime
|
|
824
|
+
? {
|
|
825
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
826
|
+
getValue: () =>
|
|
827
|
+
runtime.getRuntimeContextAnalytics(
|
|
828
|
+
resolveCurrentContextAnalyticsQuery({
|
|
829
|
+
workflowScopeSnapshot: runtime.getWorkflowScopeSnapshot(),
|
|
830
|
+
interactionGuardSnapshot: runtime.getInteractionGuardSnapshot(),
|
|
831
|
+
}),
|
|
832
|
+
),
|
|
833
|
+
}
|
|
834
|
+
: null,
|
|
835
|
+
null,
|
|
836
|
+
runtimeContextAnalyticsSnapshotsEqual,
|
|
837
|
+
);
|
|
679
838
|
const workflowMarkupSnapshot = useMemo(
|
|
680
839
|
() => (runtime ? runtime.getWorkflowMarkupSnapshot() : null),
|
|
681
840
|
[runtime, snapshot.revisionToken],
|
|
682
841
|
);
|
|
842
|
+
const sections = useMemo(
|
|
843
|
+
() => (runtime ? runtime.getSections() : []),
|
|
844
|
+
[runtime, snapshot.revisionToken],
|
|
845
|
+
);
|
|
846
|
+
const reviewQueueSnapshot = useMemo(
|
|
847
|
+
() =>
|
|
848
|
+
deriveReviewQueueSnapshot({
|
|
849
|
+
sections,
|
|
850
|
+
comments: snapshot.comments.threads,
|
|
851
|
+
trackedChanges: snapshot.trackedChanges.revisions,
|
|
852
|
+
hostAnnotations: mergeHostAndReviewSectionAnnotations(
|
|
853
|
+
hostAnnotationOverlayState?.annotations ?? [],
|
|
854
|
+
reviewSectionMarks,
|
|
855
|
+
sections,
|
|
856
|
+
),
|
|
857
|
+
activeItemId: activeReviewQueueItemId,
|
|
858
|
+
}),
|
|
859
|
+
[
|
|
860
|
+
activeReviewQueueItemId,
|
|
861
|
+
hostAnnotationOverlayState,
|
|
862
|
+
reviewSectionMarks,
|
|
863
|
+
sections,
|
|
864
|
+
snapshot.comments.threads,
|
|
865
|
+
snapshot.trackedChanges.revisions,
|
|
866
|
+
],
|
|
867
|
+
);
|
|
683
868
|
const workflowBlockedRails = useMemo(
|
|
684
869
|
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
685
870
|
[snapshot.surface, workflowMarkupSnapshot],
|
|
@@ -688,12 +873,104 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
688
873
|
() => (runtime ? runtime.getCanonicalDocument() : loadingSessionState.canonicalDocument),
|
|
689
874
|
[loadingSessionState.canonicalDocument, runtime, snapshot.revisionToken],
|
|
690
875
|
);
|
|
876
|
+
const loadingSnapshot = useMemo(
|
|
877
|
+
() =>
|
|
878
|
+
persistedSnapshotFromEditorSessionState(loadingSessionState, {
|
|
879
|
+
savedAt: loadingSessionState.updatedAt,
|
|
880
|
+
}),
|
|
881
|
+
[loadingSessionState],
|
|
882
|
+
);
|
|
691
883
|
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
692
884
|
|
|
885
|
+
useEffect(() => {
|
|
886
|
+
const mergedAnnotations = mergeHostAndReviewSectionAnnotations(
|
|
887
|
+
hostAnnotationOverlayState?.annotations ?? [],
|
|
888
|
+
reviewSectionMarks,
|
|
889
|
+
sections,
|
|
890
|
+
);
|
|
891
|
+
if (mergedAnnotations.length === 0) {
|
|
892
|
+
activeRuntime.clearHostAnnotationOverlay();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
activeRuntime.setHostAnnotationOverlay({
|
|
896
|
+
overlayVersion: "host-annotation-overlay/1",
|
|
897
|
+
annotations: mergedAnnotations,
|
|
898
|
+
});
|
|
899
|
+
}, [activeRuntime, hostAnnotationOverlayState, reviewSectionMarks, sections]);
|
|
900
|
+
|
|
693
901
|
useEffect(() => {
|
|
694
902
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
695
903
|
}, [activeRuntime, effectiveViewMode]);
|
|
696
904
|
|
|
905
|
+
const markCurrentSectionForReview = useCallback((input?: {
|
|
906
|
+
sectionIndex?: number;
|
|
907
|
+
label?: string;
|
|
908
|
+
}): string | null => {
|
|
909
|
+
const sectionIndex = input?.sectionIndex ?? documentNavigation.activeSectionIndex;
|
|
910
|
+
const targetSection = sections.find((section) => section.sectionIndex === sectionIndex);
|
|
911
|
+
if (!targetSection) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
const annotationId = createReviewSectionMarkId(targetSection.sectionIndex);
|
|
915
|
+
const label =
|
|
916
|
+
input?.label ??
|
|
917
|
+
targetSection.primaryHeadingText ??
|
|
918
|
+
`Section ${targetSection.sectionIndex + 1}`;
|
|
919
|
+
setReviewSectionMarks((current) => [
|
|
920
|
+
...current.filter((entry) => entry.annotationId !== annotationId),
|
|
921
|
+
{
|
|
922
|
+
annotationId,
|
|
923
|
+
sectionIndex: targetSection.sectionIndex,
|
|
924
|
+
label,
|
|
925
|
+
},
|
|
926
|
+
]);
|
|
927
|
+
setActiveReviewQueueItemId(`section:${annotationId}`);
|
|
928
|
+
return annotationId;
|
|
929
|
+
}, [documentNavigation.activeSectionIndex, sections]);
|
|
930
|
+
|
|
931
|
+
const clearReviewSectionMarkById = useCallback((annotationId: string) => {
|
|
932
|
+
setActiveReviewQueueItemId((current) =>
|
|
933
|
+
current === `section:${annotationId}` ? null : current);
|
|
934
|
+
setReviewSectionMarks((current) =>
|
|
935
|
+
current.filter((entry) => entry.annotationId !== annotationId));
|
|
936
|
+
}, []);
|
|
937
|
+
|
|
938
|
+
useEffect(() => {
|
|
939
|
+
if (snapshot.comments.activeCommentId) {
|
|
940
|
+
setActiveReviewQueueItemId(`comment:${snapshot.comments.activeCommentId}`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (activeRevisionId) {
|
|
944
|
+
setActiveReviewQueueItemId(`change:${activeRevisionId}`);
|
|
945
|
+
}
|
|
946
|
+
}, [activeRevisionId, snapshot.comments.activeCommentId]);
|
|
947
|
+
|
|
948
|
+
useEffect(() => {
|
|
949
|
+
if (
|
|
950
|
+
activeReviewQueueItemId &&
|
|
951
|
+
!reviewQueueSnapshot.items.some((item) => item.itemId === activeReviewQueueItemId)
|
|
952
|
+
) {
|
|
953
|
+
setActiveReviewQueueItemId(null);
|
|
954
|
+
}
|
|
955
|
+
}, [activeReviewQueueItemId, reviewQueueSnapshot.items]);
|
|
956
|
+
|
|
957
|
+
const navigateMountedReviewQueue = useCallback(
|
|
958
|
+
(direction: "next" | "previous"): ReviewQueueItem | null => {
|
|
959
|
+
const nextItem = getAdjacentReviewQueueItem(
|
|
960
|
+
reviewQueueSnapshot,
|
|
961
|
+
activeReviewQueueItemId,
|
|
962
|
+
direction,
|
|
963
|
+
);
|
|
964
|
+
if (!nextItem) {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
focusReviewQueueItem(activeRuntime, nextItem);
|
|
968
|
+
setActiveReviewQueueItemId(nextItem.itemId);
|
|
969
|
+
return nextItem;
|
|
970
|
+
},
|
|
971
|
+
[activeReviewQueueItemId, activeRuntime, reviewQueueSnapshot],
|
|
972
|
+
);
|
|
973
|
+
|
|
697
974
|
useEffect(() => {
|
|
698
975
|
activeRuntime.setDocumentMode(suggestionsEnabled ? "suggesting" : "editing");
|
|
699
976
|
}, [activeRuntime, suggestionsEnabled]);
|
|
@@ -750,6 +1027,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
750
1027
|
lastSavedRevisionTokenRef,
|
|
751
1028
|
autosaveTimerRef,
|
|
752
1029
|
})
|
|
1030
|
+
: loadError
|
|
1031
|
+
? Promise.reject(loadError)
|
|
753
1032
|
: rejectExportWhileLoadingFromBoundary({
|
|
754
1033
|
documentId,
|
|
755
1034
|
hostAdapter: hostAdapterRef.current,
|
|
@@ -757,8 +1036,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
757
1036
|
onError: onErrorRef.current,
|
|
758
1037
|
onEvent: onEventRef.current,
|
|
759
1038
|
}),
|
|
760
|
-
getSessionState: () =>
|
|
761
|
-
|
|
1039
|
+
getSessionState: () =>
|
|
1040
|
+
clonePublicValue(runtime ? runtime.getSessionState() : loadingSessionState),
|
|
1041
|
+
getSnapshot: () =>
|
|
1042
|
+
clonePublicValue(runtime ? runtime.getPersistedSnapshot() : loadingSnapshot),
|
|
762
1043
|
getRenderSnapshot: () => clonePublicValue(activeRuntime.getRenderSnapshot()),
|
|
763
1044
|
getCompatibilityReport: () => activeRuntime.getCompatibilityReport(),
|
|
764
1045
|
getWarnings: () => activeRuntime.getWarnings(),
|
|
@@ -766,6 +1047,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
766
1047
|
clonePublicValue(activeRuntime.getRenderSnapshot().comments),
|
|
767
1048
|
getTrackedChangesSnapshot: () =>
|
|
768
1049
|
clonePublicValue(activeRuntime.getRenderSnapshot().trackedChanges),
|
|
1050
|
+
getSuggestionsSnapshot: () =>
|
|
1051
|
+
clonePublicValue(activeRuntime.getSuggestionsSnapshot()),
|
|
769
1052
|
getComments: () =>
|
|
770
1053
|
clonePublicValue(activeRuntime.getRenderSnapshot().comments),
|
|
771
1054
|
getTrackedChanges: () =>
|
|
@@ -774,6 +1057,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
774
1057
|
getFormattingState: () =>
|
|
775
1058
|
getFormattingStateFromRenderSnapshot(activeRuntime.getRenderSnapshot()),
|
|
776
1059
|
getStyleCatalog: () => getRuntimeStyleCatalog(activeRuntime),
|
|
1060
|
+
toggleBulletedList: () => {
|
|
1061
|
+
applyRuntimeListToggle(activeRuntime, "bulleted");
|
|
1062
|
+
},
|
|
1063
|
+
toggleNumberedList: () => {
|
|
1064
|
+
applyRuntimeListToggle(activeRuntime, "numbered");
|
|
1065
|
+
},
|
|
777
1066
|
toggleBold: () => {
|
|
778
1067
|
applyRuntimeFormattingOperation(activeRuntime, {
|
|
779
1068
|
type: "toggle",
|
|
@@ -852,6 +1141,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
852
1141
|
outdent: () => {
|
|
853
1142
|
applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" });
|
|
854
1143
|
},
|
|
1144
|
+
restartNumbering: () => {
|
|
1145
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "restart" });
|
|
1146
|
+
},
|
|
1147
|
+
continueNumbering: () => {
|
|
1148
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "continue" });
|
|
1149
|
+
},
|
|
855
1150
|
insertPageBreak: () => {
|
|
856
1151
|
applyRuntimeInsertPageBreak(activeRuntime);
|
|
857
1152
|
},
|
|
@@ -960,6 +1255,36 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
960
1255
|
getDocumentNavigationSnapshot: () => {
|
|
961
1256
|
return clonePublicValue(activeRuntime.getDocumentNavigationSnapshot());
|
|
962
1257
|
},
|
|
1258
|
+
getCurrentLocation: () => {
|
|
1259
|
+
return clonePublicValue(activeRuntime.getCurrentLocation());
|
|
1260
|
+
},
|
|
1261
|
+
getLocationForSelection: (selection) => {
|
|
1262
|
+
return clonePublicValue(activeRuntime.getLocationForSelection(selection));
|
|
1263
|
+
},
|
|
1264
|
+
getLocationForAnchor: (anchor, storyTarget) => {
|
|
1265
|
+
return clonePublicValue(activeRuntime.getLocationForAnchor(anchor, storyTarget));
|
|
1266
|
+
},
|
|
1267
|
+
captureRestorePoint: (input) => {
|
|
1268
|
+
return clonePublicValue(activeRuntime.captureRestorePoint(input));
|
|
1269
|
+
},
|
|
1270
|
+
restoreToPoint: (point, options) => {
|
|
1271
|
+
return clonePublicValue(activeRuntime.restoreToPoint(point, options));
|
|
1272
|
+
},
|
|
1273
|
+
getOutlineSnapshot: () => {
|
|
1274
|
+
return clonePublicValue(activeRuntime.getOutlineSnapshot());
|
|
1275
|
+
},
|
|
1276
|
+
getTocSnapshot: () => {
|
|
1277
|
+
return clonePublicValue(activeRuntime.getTocSnapshot());
|
|
1278
|
+
},
|
|
1279
|
+
getSections: () => {
|
|
1280
|
+
return clonePublicValue(activeRuntime.getSections());
|
|
1281
|
+
},
|
|
1282
|
+
getSectionSnapshot: (input) => {
|
|
1283
|
+
return clonePublicValue(activeRuntime.getSectionSnapshot(input));
|
|
1284
|
+
},
|
|
1285
|
+
describeEventImpact: (event) => {
|
|
1286
|
+
return clonePublicValue(activeRuntime.describeEventImpact(event));
|
|
1287
|
+
},
|
|
963
1288
|
getFieldSnapshot: () => {
|
|
964
1289
|
return clonePublicValue(activeRuntime.getFieldSnapshot());
|
|
965
1290
|
},
|
|
@@ -1020,15 +1345,48 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1020
1345
|
getWorkflowMarkupSnapshot: () => {
|
|
1021
1346
|
return clonePublicValue(activeRuntime.getWorkflowMarkupSnapshot());
|
|
1022
1347
|
},
|
|
1348
|
+
setWorkflowMetadataDefinitions: (definitions) => {
|
|
1349
|
+
activeRuntime.setWorkflowMetadataDefinitions(clonePublicValue(definitions));
|
|
1350
|
+
},
|
|
1351
|
+
clearWorkflowMetadataDefinitions: () => {
|
|
1352
|
+
activeRuntime.clearWorkflowMetadataDefinitions();
|
|
1353
|
+
},
|
|
1354
|
+
setWorkflowMetadataEntries: (entries) => {
|
|
1355
|
+
activeRuntime.setWorkflowMetadataEntries(clonePublicValue(entries));
|
|
1356
|
+
},
|
|
1357
|
+
clearWorkflowMetadataEntries: () => {
|
|
1358
|
+
activeRuntime.clearWorkflowMetadataEntries();
|
|
1359
|
+
},
|
|
1360
|
+
getWorkflowMetadataSnapshot: () => {
|
|
1361
|
+
return clonePublicValue(activeRuntime.getWorkflowMetadataSnapshot());
|
|
1362
|
+
},
|
|
1023
1363
|
setHostAnnotationOverlay: (overlay) => {
|
|
1024
|
-
|
|
1364
|
+
setHostAnnotationOverlayState(clonePublicValue(overlay));
|
|
1025
1365
|
},
|
|
1026
1366
|
clearHostAnnotationOverlay: () => {
|
|
1027
|
-
|
|
1367
|
+
setHostAnnotationOverlayState(null);
|
|
1028
1368
|
},
|
|
1029
1369
|
getHostAnnotationSnapshot: () => {
|
|
1030
1370
|
return clonePublicValue(activeRuntime.getHostAnnotationSnapshot());
|
|
1031
1371
|
},
|
|
1372
|
+
getReviewQueueSnapshot: () => {
|
|
1373
|
+
return clonePublicValue(reviewQueueSnapshot);
|
|
1374
|
+
},
|
|
1375
|
+
getRuntimeContextAnalytics: (query) => {
|
|
1376
|
+
return clonePublicValue(activeRuntime.getRuntimeContextAnalytics(query));
|
|
1377
|
+
},
|
|
1378
|
+
goToNextReviewItem: () => {
|
|
1379
|
+
return clonePublicValue(navigateMountedReviewQueue("next"));
|
|
1380
|
+
},
|
|
1381
|
+
goToPreviousReviewItem: () => {
|
|
1382
|
+
return clonePublicValue(navigateMountedReviewQueue("previous"));
|
|
1383
|
+
},
|
|
1384
|
+
markSectionForReview: (input) => {
|
|
1385
|
+
return markCurrentSectionForReview(input);
|
|
1386
|
+
},
|
|
1387
|
+
clearSectionReviewMark: (annotationId) => {
|
|
1388
|
+
clearReviewSectionMarkById(annotationId);
|
|
1389
|
+
},
|
|
1032
1390
|
getWorkflowCandidateRanges: (options) => {
|
|
1033
1391
|
return clonePublicValue(activeRuntime.getWorkflowCandidateRanges(options));
|
|
1034
1392
|
},
|
|
@@ -1036,7 +1394,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1036
1394
|
activeRuntime.replaceWorkflowMarkupText(markupId, text);
|
|
1037
1395
|
},
|
|
1038
1396
|
}),
|
|
1039
|
-
[
|
|
1397
|
+
[
|
|
1398
|
+
activeRuntime,
|
|
1399
|
+
clearReviewSectionMarkById,
|
|
1400
|
+
currentUser.userId,
|
|
1401
|
+
documentId,
|
|
1402
|
+
markCurrentSectionForReview,
|
|
1403
|
+
navigateMountedReviewQueue,
|
|
1404
|
+
reviewQueueSnapshot,
|
|
1405
|
+
runtime,
|
|
1406
|
+
],
|
|
1040
1407
|
);
|
|
1041
1408
|
|
|
1042
1409
|
useEffect(() => {
|
|
@@ -1154,6 +1521,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1154
1521
|
}
|
|
1155
1522
|
|
|
1156
1523
|
function exportCurrentDocument(): void {
|
|
1524
|
+
const fileName =
|
|
1525
|
+
typeof snapshot.sourceLabel === "string" && snapshot.sourceLabel.trim().length > 0
|
|
1526
|
+
? snapshot.sourceLabel
|
|
1527
|
+
: undefined;
|
|
1157
1528
|
void (runtime
|
|
1158
1529
|
? persistAndExportFromBoundary({
|
|
1159
1530
|
hostAdapter: hostAdapterRef.current,
|
|
@@ -1162,9 +1533,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1162
1533
|
runtime,
|
|
1163
1534
|
onError: onErrorRef.current,
|
|
1164
1535
|
onEvent: onEventRef.current,
|
|
1536
|
+
...(fileName ? { options: { fileName } } : {}),
|
|
1165
1537
|
lastSavedRevisionTokenRef,
|
|
1166
1538
|
autosaveTimerRef,
|
|
1167
1539
|
})
|
|
1540
|
+
: loadError
|
|
1541
|
+
? Promise.reject(loadError)
|
|
1168
1542
|
: rejectExportWhileLoadingFromBoundary({
|
|
1169
1543
|
documentId,
|
|
1170
1544
|
hostAdapter: hostAdapterRef.current,
|
|
@@ -1179,7 +1553,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1179
1553
|
reviewMode,
|
|
1180
1554
|
workflowScopeSnapshot,
|
|
1181
1555
|
);
|
|
1556
|
+
const effectiveChromePreset = resolveChromePreset(chromePreset, reviewMode);
|
|
1182
1557
|
const resolvedChromeVisibility = resolveWordReviewEditorChromeVisibility(
|
|
1558
|
+
effectiveChromePreset,
|
|
1559
|
+
chromeOptions,
|
|
1183
1560
|
chromeVisibility,
|
|
1184
1561
|
showReviewPanel,
|
|
1185
1562
|
);
|
|
@@ -1263,6 +1640,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1263
1640
|
: null,
|
|
1264
1641
|
[viewState.activeObjectFrame],
|
|
1265
1642
|
);
|
|
1643
|
+
const activeTableContext = useMemo(
|
|
1644
|
+
() =>
|
|
1645
|
+
getTableStructureContext(
|
|
1646
|
+
canonicalDocument,
|
|
1647
|
+
snapshot,
|
|
1648
|
+
surfaceRef.current?.getTableSelection() ?? null,
|
|
1649
|
+
),
|
|
1650
|
+
[canonicalDocument, snapshot],
|
|
1651
|
+
);
|
|
1652
|
+
const suggestionsSnapshot = activeRuntime.getSuggestionsSnapshot();
|
|
1653
|
+
const activeCommentThread =
|
|
1654
|
+
snapshot.comments.activeCommentId
|
|
1655
|
+
? snapshot.comments.threads.find((thread) => thread.commentId === snapshot.comments.activeCommentId)
|
|
1656
|
+
: undefined;
|
|
1266
1657
|
const selectionToolbar = buildSelectionToolbarModel({
|
|
1267
1658
|
snapshot,
|
|
1268
1659
|
viewState,
|
|
@@ -1276,6 +1667,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1276
1667
|
});
|
|
1277
1668
|
const suggestionCard = buildSuggestionCardModel({
|
|
1278
1669
|
snapshot,
|
|
1670
|
+
suggestionsSnapshot,
|
|
1279
1671
|
viewState,
|
|
1280
1672
|
capabilities,
|
|
1281
1673
|
workflowScopeSnapshot,
|
|
@@ -1284,15 +1676,70 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1284
1676
|
suppressedSuggestionRevisionId,
|
|
1285
1677
|
addCommentDisabledReason,
|
|
1286
1678
|
});
|
|
1679
|
+
useEffect(() => {
|
|
1680
|
+
if (!activeRevisionId) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (snapshot.comments.activeCommentId) {
|
|
1684
|
+
setActiveRevisionId(undefined);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
if (!isActiveRevisionStillFocused(
|
|
1688
|
+
activeRevisionId,
|
|
1689
|
+
suggestionsSnapshot,
|
|
1690
|
+
snapshot.selection,
|
|
1691
|
+
viewState.activeStory,
|
|
1692
|
+
)) {
|
|
1693
|
+
setActiveRevisionId(undefined);
|
|
1694
|
+
}
|
|
1695
|
+
}, [
|
|
1696
|
+
activeRevisionId,
|
|
1697
|
+
snapshot.comments.activeCommentId,
|
|
1698
|
+
snapshot.selection,
|
|
1699
|
+
suggestionsSnapshot,
|
|
1700
|
+
viewState.activeStory,
|
|
1701
|
+
]);
|
|
1702
|
+
const activeSelectionTool = resolveActiveSelectionTool({
|
|
1703
|
+
snapshot,
|
|
1704
|
+
viewState,
|
|
1705
|
+
capabilities,
|
|
1706
|
+
documentNavigation,
|
|
1707
|
+
styleCatalog,
|
|
1708
|
+
formattingState,
|
|
1709
|
+
workflowScopeSnapshot,
|
|
1710
|
+
interactionGuardSnapshot,
|
|
1711
|
+
workflowMarkupSnapshot: workflowMarkupSnapshot ?? undefined,
|
|
1712
|
+
suggestionsSnapshot,
|
|
1713
|
+
activeRevisionId,
|
|
1714
|
+
activeCommentId: snapshot.comments.activeCommentId,
|
|
1715
|
+
activeCommentThread,
|
|
1716
|
+
activeTableContext,
|
|
1717
|
+
activeImageContext,
|
|
1718
|
+
activeObjectContext,
|
|
1719
|
+
activeListContext: viewState.activeListContext,
|
|
1720
|
+
preferListStructureContext: viewState.workspaceMode === "page",
|
|
1721
|
+
addCommentDisabledReason,
|
|
1722
|
+
suppressedSuggestionRevisionId,
|
|
1723
|
+
});
|
|
1287
1724
|
const selectionToolbarSelectionKey = useMemo(
|
|
1288
|
-
() =>
|
|
1289
|
-
|
|
1725
|
+
() =>
|
|
1726
|
+
createSelectionToolbarSelectionKey(
|
|
1727
|
+
snapshot.selection,
|
|
1728
|
+
viewState.activeStory,
|
|
1729
|
+
activeRevisionId,
|
|
1730
|
+
snapshot.comments.activeCommentId,
|
|
1731
|
+
),
|
|
1732
|
+
[activeRevisionId, snapshot.comments.activeCommentId, snapshot.selection, viewState.activeStory],
|
|
1290
1733
|
);
|
|
1291
1734
|
const shouldRenderSelectionChrome = Boolean(
|
|
1292
|
-
|
|
1735
|
+
activeSelectionTool &&
|
|
1293
1736
|
selectionToolbarSelectionKey &&
|
|
1294
1737
|
selectionToolbarDismissedKey !== selectionToolbarSelectionKey &&
|
|
1295
|
-
(
|
|
1738
|
+
(
|
|
1739
|
+
viewState.isFocused ||
|
|
1740
|
+
selectionToolbarFocusWithin ||
|
|
1741
|
+
activeSelectionTool.kind === "structure-context"
|
|
1742
|
+
),
|
|
1296
1743
|
);
|
|
1297
1744
|
const accessibilityInstructionsId = `${documentId}-accessibility-instructions`;
|
|
1298
1745
|
const accessibilityStatusId = `${documentId}-accessibility-status`;
|
|
@@ -1426,11 +1873,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1426
1873
|
}, [selectionToolbarSelectionKey]);
|
|
1427
1874
|
|
|
1428
1875
|
useEffect(() => {
|
|
1429
|
-
if (!
|
|
1876
|
+
if (!activeSelectionTool) {
|
|
1430
1877
|
setSelectionToolbarAnchor(null);
|
|
1431
1878
|
setSelectionToolbarFocusWithin(false);
|
|
1432
1879
|
}
|
|
1433
|
-
}, [
|
|
1880
|
+
}, [activeSelectionTool]);
|
|
1434
1881
|
|
|
1435
1882
|
useEffect(() => {
|
|
1436
1883
|
const shell = shellRef.current;
|
|
@@ -1460,50 +1907,85 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1460
1907
|
|
|
1461
1908
|
function handleShellKeyDownCapture(event: React.KeyboardEvent<HTMLDivElement>): void {
|
|
1462
1909
|
const targetWithinDocument = isTargetWithinDocumentSurface(event.target);
|
|
1463
|
-
const
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
shouldRenderSelectionChrome &&
|
|
1482
|
-
(isTargetWithinDocumentSurface(event.target) || isTargetWithinSelectionToolbar(event.target))
|
|
1483
|
-
) {
|
|
1484
|
-
event.preventDefault();
|
|
1485
|
-
event.stopPropagation();
|
|
1486
|
-
const restoreSurfaceFocus = isTargetWithinSelectionToolbar(event.target);
|
|
1487
|
-
dismissSelectionToolbar("escape");
|
|
1488
|
-
if (restoreSurfaceFocus) {
|
|
1489
|
-
queueMicrotask(() => {
|
|
1490
|
-
focusDocumentSurface();
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
if (event.key !== "F6") {
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1910
|
+
const targetWithinToolbar = isTargetWithinSelectionToolbar(event.target);
|
|
1911
|
+
const shortcut = resolveShellShortcut(
|
|
1912
|
+
{
|
|
1913
|
+
key: event.key,
|
|
1914
|
+
ctrlKey: event.ctrlKey,
|
|
1915
|
+
metaKey: event.metaKey,
|
|
1916
|
+
altKey: event.altKey,
|
|
1917
|
+
shiftKey: event.shiftKey,
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
target: targetWithinDocument
|
|
1921
|
+
? "document"
|
|
1922
|
+
: targetWithinToolbar
|
|
1923
|
+
? "selection-toolbar"
|
|
1924
|
+
: "shell",
|
|
1925
|
+
selectionToolbarVisible: shouldRenderSelectionChrome,
|
|
1926
|
+
},
|
|
1927
|
+
);
|
|
1499
1928
|
|
|
1500
|
-
|
|
1501
|
-
if (!shell) {
|
|
1929
|
+
if (shortcut.kind === "none" || shortcut.kind === "delegate") {
|
|
1502
1930
|
return;
|
|
1503
1931
|
}
|
|
1504
1932
|
|
|
1505
1933
|
event.preventDefault();
|
|
1506
|
-
|
|
1934
|
+
event.stopPropagation();
|
|
1935
|
+
|
|
1936
|
+
switch (shortcut.kind) {
|
|
1937
|
+
case "history":
|
|
1938
|
+
if (shortcut.history === "undo") {
|
|
1939
|
+
commands.onUndo();
|
|
1940
|
+
} else {
|
|
1941
|
+
commands.onRedo();
|
|
1942
|
+
}
|
|
1943
|
+
return;
|
|
1944
|
+
case "dismiss-selection-toolbar": {
|
|
1945
|
+
dismissSelectionToolbar("escape");
|
|
1946
|
+
if (targetWithinToolbar) {
|
|
1947
|
+
queueMicrotask(() => {
|
|
1948
|
+
focusDocumentSurface();
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
case "focus-region": {
|
|
1954
|
+
const shell = shellRef.current;
|
|
1955
|
+
if (!shell) {
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
focusRelativeRegion(shell, shortcut.direction);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
case "toggle-formatting":
|
|
1962
|
+
if (shortcut.mark === "bold") {
|
|
1963
|
+
commands.onToggleBold?.();
|
|
1964
|
+
} else if (shortcut.mark === "italic") {
|
|
1965
|
+
commands.onToggleItalic?.();
|
|
1966
|
+
} else {
|
|
1967
|
+
commands.onToggleUnderline?.();
|
|
1968
|
+
}
|
|
1969
|
+
return;
|
|
1970
|
+
case "add-comment":
|
|
1971
|
+
commands.onAddComment();
|
|
1972
|
+
return;
|
|
1973
|
+
case "set-heading-level": {
|
|
1974
|
+
const styleId = resolveHeadingShortcutStyleId(styleCatalog, shortcut.level);
|
|
1975
|
+
if (!styleId) {
|
|
1976
|
+
activeRuntime.emitBlockedCommand("setParagraphStyle", [{
|
|
1977
|
+
code: "unsupported_surface",
|
|
1978
|
+
message: `Heading ${shortcut.level} is not available in this document's style catalog.`,
|
|
1979
|
+
}]);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
commands.onSetParagraphStyle?.(styleId);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
case "block":
|
|
1986
|
+
activeRuntime.emitBlockedCommand(shortcut.command, [shortcut.reason]);
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1507
1989
|
}
|
|
1508
1990
|
|
|
1509
1991
|
const editorCallbacks = {
|
|
@@ -1614,6 +2096,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1614
2096
|
applyRuntimeFormattingOperation(activeRuntime, { type: "set-highlight-color", color }),
|
|
1615
2097
|
onSetAlignment: (alignment) =>
|
|
1616
2098
|
applyRuntimeFormattingOperation(activeRuntime, { type: "set-alignment", alignment }),
|
|
2099
|
+
onToggleBulletedList: () => applyRuntimeListToggle(activeRuntime, "bulleted"),
|
|
2100
|
+
onToggleNumberedList: () => applyRuntimeListToggle(activeRuntime, "numbered"),
|
|
1617
2101
|
onSetParagraphStyle: (styleId) => applyRuntimeParagraphStyle(activeRuntime, styleId),
|
|
1618
2102
|
onOutdent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" }),
|
|
1619
2103
|
onIndent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "indent" }),
|
|
@@ -1687,6 +2171,19 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1687
2171
|
applyRuntimeNumberingFlow(activeRuntime, { type: "restart" }),
|
|
1688
2172
|
onContinueNumbering: () =>
|
|
1689
2173
|
applyRuntimeNumberingFlow(activeRuntime, { type: "continue" }),
|
|
2174
|
+
onUpdateFields: () => {
|
|
2175
|
+
activeRuntime.updateFields();
|
|
2176
|
+
},
|
|
2177
|
+
onUpdateTableOfContents: () => {
|
|
2178
|
+
activeRuntime.updateTableOfContents();
|
|
2179
|
+
},
|
|
2180
|
+
onGoToPreviousReviewItem: () => {
|
|
2181
|
+
navigateMountedReviewQueue("previous");
|
|
2182
|
+
},
|
|
2183
|
+
onGoToNextReviewItem: () => {
|
|
2184
|
+
navigateMountedReviewQueue("next");
|
|
2185
|
+
},
|
|
2186
|
+
onMarkSectionForReview: () => markCurrentSectionForReview(),
|
|
1690
2187
|
onNavigateHeading: (headingId) => {
|
|
1691
2188
|
const heading = documentNavigation.headings.find(
|
|
1692
2189
|
(entry) => entry.headingId === headingId,
|
|
@@ -1710,7 +2207,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1710
2207
|
documentNavigation={documentNavigation}
|
|
1711
2208
|
reviewMode={reviewMode}
|
|
1712
2209
|
markupDisplay={liveMarkupDisplay}
|
|
2210
|
+
showUnsupportedObjectPreviews={showUnsupportedObjectPreviews}
|
|
1713
2211
|
activeRevisionId={activeRevisionId}
|
|
2212
|
+
activeSelectionToolKind={activeSelectionTool?.kind ?? null}
|
|
1714
2213
|
showTrackedChanges={showTrackedChanges}
|
|
1715
2214
|
suggestionsEnabled={suggestionsEnabled}
|
|
1716
2215
|
mediaPreviews={mediaPreviews}
|
|
@@ -1720,6 +2219,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1720
2219
|
workflowBlockedReasons={workflowBlockedRails}
|
|
1721
2220
|
activeWorkflowWorkItemId={workflowScopeSnapshot?.activeWorkItemId ?? null}
|
|
1722
2221
|
activeWorkflowScopeIds={workflowScopeSnapshot?.activeWorkItem?.scopeIds ?? []}
|
|
2222
|
+
workflowMetadata={workflowMarkupSnapshot?.metadata}
|
|
1723
2223
|
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1724
2224
|
{...editorCallbacks}
|
|
1725
2225
|
onCommentActivated={(commentId) => {
|
|
@@ -1759,6 +2259,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1759
2259
|
workspaceMode={viewState.workspaceMode}
|
|
1760
2260
|
zoomLevel={viewState.zoomLevel}
|
|
1761
2261
|
formattingState={formattingState}
|
|
2262
|
+
activeListContext={viewState.activeListContext}
|
|
1762
2263
|
styleCatalog={styleCatalog}
|
|
1763
2264
|
activeRailTab={activeRailTab}
|
|
1764
2265
|
activeCommentId={snapshot.comments.activeCommentId}
|
|
@@ -1766,34 +2267,51 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1766
2267
|
showTrackedChanges={showTrackedChanges}
|
|
1767
2268
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
1768
2269
|
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2270
|
+
chromePreset={effectiveChromePreset}
|
|
2271
|
+
chromeOptions={chromeOptions}
|
|
2272
|
+
reviewQueue={reviewQueueSnapshot}
|
|
2273
|
+
documentContextAnalytics={documentContextAnalytics}
|
|
2274
|
+
selectionContextAnalytics={selectionContextAnalytics}
|
|
2275
|
+
currentScopeContextAnalytics={currentScopeContextAnalytics}
|
|
2276
|
+
activeSelectionTool={shouldRenderSelectionChrome ? activeSelectionTool : null}
|
|
2277
|
+
selectionToolAnchor={shouldRenderSelectionChrome ? selectionToolbarAnchor : null}
|
|
1772
2278
|
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1773
2279
|
onAddCommentFromSuggestion={addSelectionToolbarComment}
|
|
1774
|
-
onAcceptSuggestion={
|
|
2280
|
+
onAcceptSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
1775
2281
|
? () => {
|
|
1776
|
-
|
|
2282
|
+
for (const changeId of activeSelectionTool.changeIds) {
|
|
2283
|
+
activeRuntime.acceptChange(changeId);
|
|
2284
|
+
}
|
|
1777
2285
|
dismissSelectionToolbar("chrome-action");
|
|
1778
2286
|
}
|
|
1779
2287
|
: undefined}
|
|
1780
|
-
onRejectSuggestion={
|
|
2288
|
+
onRejectSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
1781
2289
|
? () => {
|
|
1782
|
-
|
|
2290
|
+
for (const changeId of activeSelectionTool.changeIds) {
|
|
2291
|
+
activeRuntime.rejectChange(changeId);
|
|
2292
|
+
}
|
|
1783
2293
|
dismissSelectionToolbar("chrome-action");
|
|
1784
2294
|
}
|
|
1785
2295
|
: undefined}
|
|
1786
|
-
onEditSuggestion={
|
|
2296
|
+
onEditSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
1787
2297
|
? () => {
|
|
1788
|
-
setSuppressedSuggestionRevisionId(
|
|
2298
|
+
setSuppressedSuggestionRevisionId(activeSelectionTool.suggestionId);
|
|
2299
|
+
const activeSuggestion = suggestionsSnapshot.suggestions.find(
|
|
2300
|
+
(suggestion) => suggestion.suggestionId === activeSelectionTool.suggestionId,
|
|
2301
|
+
);
|
|
2302
|
+
if (activeSuggestion) {
|
|
2303
|
+
applyRuntimeSelection(
|
|
2304
|
+
activeRuntime,
|
|
2305
|
+
createSelectionFromAnchor(activeSuggestion.anchor, activeSuggestion.storyTarget),
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
setSelectionToolbarFocusWithin(true);
|
|
1789
2309
|
}
|
|
1790
2310
|
: undefined}
|
|
1791
2311
|
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1792
2312
|
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1793
2313
|
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
1794
2314
|
selectionToolbarRef={selectionToolbarElementRef}
|
|
1795
|
-
activeImageContext={activeImageContext}
|
|
1796
|
-
activeObjectContext={activeObjectContext}
|
|
1797
2315
|
commands={commands}
|
|
1798
2316
|
document={documentElement}
|
|
1799
2317
|
/>
|
|
@@ -1813,6 +2331,11 @@ function applyRuntimeFormattingOperation(
|
|
|
1813
2331
|
| { type: "indent" }
|
|
1814
2332
|
| { type: "outdent" },
|
|
1815
2333
|
): void {
|
|
2334
|
+
if (isSelectionSuggesting(runtime)) {
|
|
2335
|
+
if (applySuggestingFormattingOperation(runtime, operation)) {
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
1816
2339
|
if (emitSuggestingUnsupportedMutation(runtime, getFormattingOperationCommandName(operation))) {
|
|
1817
2340
|
return;
|
|
1818
2341
|
}
|
|
@@ -1837,6 +2360,159 @@ function applyRuntimeFormattingOperation(
|
|
|
1837
2360
|
);
|
|
1838
2361
|
}
|
|
1839
2362
|
|
|
2363
|
+
function applySuggestingFormattingOperation(
|
|
2364
|
+
runtime: WordReviewEditorRuntime,
|
|
2365
|
+
operation:
|
|
2366
|
+
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
2367
|
+
| { type: "set-font-family"; fontFamily: string | null }
|
|
2368
|
+
| { type: "set-font-size"; size: number | null }
|
|
2369
|
+
| { type: "set-text-color"; color: string | null }
|
|
2370
|
+
| { type: "set-highlight-color"; color: string | null }
|
|
2371
|
+
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
2372
|
+
| { type: "indent" }
|
|
2373
|
+
| { type: "outdent" },
|
|
2374
|
+
): boolean {
|
|
2375
|
+
const commandName = getFormattingOperationCommandName(operation);
|
|
2376
|
+
const context = getStoryMutationContext(runtime, commandName);
|
|
2377
|
+
if (!context) {
|
|
2378
|
+
return true;
|
|
2379
|
+
}
|
|
2380
|
+
if (context.activeStory.kind !== "main") {
|
|
2381
|
+
runtime.emitBlockedCommand(commandName, [{
|
|
2382
|
+
code: "suggesting_unsupported",
|
|
2383
|
+
message: `"${commandName}" is not supported in suggesting mode for this story.`,
|
|
2384
|
+
}]);
|
|
2385
|
+
return true;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
if (operation.type === "set-alignment" || operation.type === "indent" || operation.type === "outdent") {
|
|
2389
|
+
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
2390
|
+
if (!paragraphContext) {
|
|
2391
|
+
return true;
|
|
2392
|
+
}
|
|
2393
|
+
const beforeXml = buildParagraphPropertyBeforeXml(paragraphContext.paragraph);
|
|
2394
|
+
const result = applyFormattingOperationToDocument(
|
|
2395
|
+
context.localDocument,
|
|
2396
|
+
context.localSnapshot,
|
|
2397
|
+
operation,
|
|
2398
|
+
);
|
|
2399
|
+
if (!result.changed) {
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
const nextDocument = appendPropertyChangeSuggestion(
|
|
2403
|
+
result.document,
|
|
2404
|
+
{
|
|
2405
|
+
from: paragraphContext.paragraph.from,
|
|
2406
|
+
to: paragraphContext.paragraph.to,
|
|
2407
|
+
},
|
|
2408
|
+
{
|
|
2409
|
+
originalRevisionType: "pPrChange",
|
|
2410
|
+
xmlTag: "pPrChange",
|
|
2411
|
+
beforeXml,
|
|
2412
|
+
semanticKind: "paragraph-property-change",
|
|
2413
|
+
storyTarget: context.activeStory,
|
|
2414
|
+
authorId: runtime.getDefaultAuthorId?.(),
|
|
2415
|
+
},
|
|
2416
|
+
context.timestamp,
|
|
2417
|
+
);
|
|
2418
|
+
dispatchStoryMutationResult(
|
|
2419
|
+
runtime,
|
|
2420
|
+
context,
|
|
2421
|
+
{
|
|
2422
|
+
changed: true,
|
|
2423
|
+
document: nextDocument,
|
|
2424
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
2425
|
+
},
|
|
2426
|
+
context.timestamp,
|
|
2427
|
+
);
|
|
2428
|
+
return true;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const segment = findSingleSelectedTextSegment(context.localSnapshot);
|
|
2432
|
+
if (!segment) {
|
|
2433
|
+
runtime.emitBlockedCommand(commandName, [{
|
|
2434
|
+
code: "suggesting_unsupported",
|
|
2435
|
+
message: `"${commandName}" requires one bounded text segment in suggesting mode.`,
|
|
2436
|
+
}]);
|
|
2437
|
+
return true;
|
|
2438
|
+
}
|
|
2439
|
+
const beforeXml = buildRunPropertyBeforeXml(segment);
|
|
2440
|
+
const result = applyFormattingOperationToDocument(
|
|
2441
|
+
context.localDocument,
|
|
2442
|
+
context.localSnapshot,
|
|
2443
|
+
operation,
|
|
2444
|
+
);
|
|
2445
|
+
if (!result.changed) {
|
|
2446
|
+
return true;
|
|
2447
|
+
}
|
|
2448
|
+
const nextDocument = appendPropertyChangeSuggestion(
|
|
2449
|
+
result.document,
|
|
2450
|
+
{
|
|
2451
|
+
from: segment.from,
|
|
2452
|
+
to: segment.to,
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
originalRevisionType: "rPrChange",
|
|
2456
|
+
xmlTag: "rPrChange",
|
|
2457
|
+
beforeXml,
|
|
2458
|
+
semanticKind: "formatting-change",
|
|
2459
|
+
storyTarget: context.activeStory,
|
|
2460
|
+
authorId: runtime.getDefaultAuthorId?.(),
|
|
2461
|
+
},
|
|
2462
|
+
context.timestamp,
|
|
2463
|
+
);
|
|
2464
|
+
dispatchStoryMutationResult(
|
|
2465
|
+
runtime,
|
|
2466
|
+
context,
|
|
2467
|
+
{
|
|
2468
|
+
changed: true,
|
|
2469
|
+
document: nextDocument,
|
|
2470
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
2471
|
+
},
|
|
2472
|
+
context.timestamp,
|
|
2473
|
+
);
|
|
2474
|
+
return true;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
function applyRuntimeListToggle(
|
|
2478
|
+
runtime: WordReviewEditorRuntime,
|
|
2479
|
+
kind: "bulleted" | "numbered",
|
|
2480
|
+
): void {
|
|
2481
|
+
const commandName =
|
|
2482
|
+
kind === "bulleted" ? "toggleBulletedList" : "toggleNumberedList";
|
|
2483
|
+
if (emitSuggestingUnsupportedMutation(runtime, commandName)) {
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const context = getStoryMutationContext(runtime, commandName);
|
|
2487
|
+
if (!context) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
2492
|
+
if (!paragraphContext) {
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
const result =
|
|
2497
|
+
kind === "bulleted"
|
|
2498
|
+
? toggleBulletedList(
|
|
2499
|
+
context.localDocument,
|
|
2500
|
+
[paragraphContext.paragraphIndex],
|
|
2501
|
+
{ timestamp: context.timestamp },
|
|
2502
|
+
)
|
|
2503
|
+
: toggleNumberedList(
|
|
2504
|
+
context.localDocument,
|
|
2505
|
+
[paragraphContext.paragraphIndex],
|
|
2506
|
+
{ timestamp: context.timestamp },
|
|
2507
|
+
);
|
|
2508
|
+
dispatchStoryMutationResult(
|
|
2509
|
+
runtime,
|
|
2510
|
+
context,
|
|
2511
|
+
createListMutationResult(result, context.localSnapshot.selection),
|
|
2512
|
+
context.timestamp,
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
|
|
1840
2516
|
function getRuntimeStyleCatalog(
|
|
1841
2517
|
input:
|
|
1842
2518
|
| WordReviewEditorRuntime
|
|
@@ -2113,6 +2789,183 @@ function emitSuggestingUnsupportedMutation(
|
|
|
2113
2789
|
return true;
|
|
2114
2790
|
}
|
|
2115
2791
|
|
|
2792
|
+
function appendPropertyChangeSuggestion(
|
|
2793
|
+
document: EditorSessionState["canonicalDocument"],
|
|
2794
|
+
anchor: { from: number; to: number },
|
|
2795
|
+
input: {
|
|
2796
|
+
originalRevisionType: "rPrChange" | "pPrChange";
|
|
2797
|
+
xmlTag: "rPrChange" | "pPrChange";
|
|
2798
|
+
beforeXml: string;
|
|
2799
|
+
semanticKind: "formatting-change" | "paragraph-property-change";
|
|
2800
|
+
storyTarget: EditorStoryTarget;
|
|
2801
|
+
authorId?: string;
|
|
2802
|
+
},
|
|
2803
|
+
timestamp: string,
|
|
2804
|
+
): EditorSessionState["canonicalDocument"] {
|
|
2805
|
+
const existing = document.review.revisions;
|
|
2806
|
+
const changeId = createRuntimeSuggestionChangeId(existing, timestamp);
|
|
2807
|
+
const resolvedAuthorId = input.authorId ?? "unknown";
|
|
2808
|
+
return {
|
|
2809
|
+
...document,
|
|
2810
|
+
review: {
|
|
2811
|
+
...document.review,
|
|
2812
|
+
revisions: {
|
|
2813
|
+
...existing,
|
|
2814
|
+
[changeId]: {
|
|
2815
|
+
changeId,
|
|
2816
|
+
kind: "property-change",
|
|
2817
|
+
anchor: createRangeAnchor(anchor.from, anchor.to, { start: 1, end: -1 }),
|
|
2818
|
+
authorId: resolvedAuthorId,
|
|
2819
|
+
createdAt: timestamp,
|
|
2820
|
+
warningIds: [],
|
|
2821
|
+
metadata: {
|
|
2822
|
+
source: "runtime",
|
|
2823
|
+
storyTarget: input.storyTarget,
|
|
2824
|
+
suggestionId: changeId,
|
|
2825
|
+
semanticKind: input.semanticKind,
|
|
2826
|
+
originalRevisionType: input.originalRevisionType,
|
|
2827
|
+
propertyChangeData: {
|
|
2828
|
+
xmlTag: input.xmlTag,
|
|
2829
|
+
beforeXml: input.beforeXml,
|
|
2830
|
+
},
|
|
2831
|
+
},
|
|
2832
|
+
status: "open",
|
|
2833
|
+
},
|
|
2834
|
+
},
|
|
2835
|
+
},
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
function createRuntimeSuggestionChangeId(
|
|
2840
|
+
existing: EditorSessionState["canonicalDocument"]["review"]["revisions"],
|
|
2841
|
+
timestamp: string,
|
|
2842
|
+
): string {
|
|
2843
|
+
const base = `change-${timestamp.replace(/[^0-9]/gu, "")}`;
|
|
2844
|
+
let counter = Object.keys(existing).length + 1;
|
|
2845
|
+
let candidate = `${base}-p${counter}`;
|
|
2846
|
+
while (existing[candidate]) {
|
|
2847
|
+
counter += 1;
|
|
2848
|
+
candidate = `${base}-p${counter}`;
|
|
2849
|
+
}
|
|
2850
|
+
return candidate;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function findSingleSelectedTextSegment(
|
|
2854
|
+
snapshot: Pick<RuntimeRenderSnapshot, "surface" | "selection">,
|
|
2855
|
+
): Extract<SurfaceInlineSegment, { kind: "text" }> | null {
|
|
2856
|
+
if (!snapshot.surface || snapshot.selection.activeRange.kind !== "range" || snapshot.selection.isCollapsed) {
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
const selectionFrom = Math.min(snapshot.selection.anchor, snapshot.selection.head);
|
|
2860
|
+
const selectionTo = Math.max(snapshot.selection.anchor, snapshot.selection.head);
|
|
2861
|
+
const segments = collectSelectedTextSegments(snapshot.surface.blocks, selectionFrom, selectionTo);
|
|
2862
|
+
if (segments.length !== 1) {
|
|
2863
|
+
return null;
|
|
2864
|
+
}
|
|
2865
|
+
const [segment] = segments;
|
|
2866
|
+
if (!segment || segment.from !== selectionFrom || segment.to !== selectionTo) {
|
|
2867
|
+
return null;
|
|
2868
|
+
}
|
|
2869
|
+
return segment;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function collectSelectedTextSegments(
|
|
2873
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
2874
|
+
selectionFrom: number,
|
|
2875
|
+
selectionTo: number,
|
|
2876
|
+
output: Array<Extract<SurfaceInlineSegment, { kind: "text" }>> = [],
|
|
2877
|
+
): Array<Extract<SurfaceInlineSegment, { kind: "text" }>> {
|
|
2878
|
+
for (const block of blocks) {
|
|
2879
|
+
if (block.kind === "paragraph") {
|
|
2880
|
+
for (const segment of block.segments) {
|
|
2881
|
+
if (
|
|
2882
|
+
segment.kind === "text" &&
|
|
2883
|
+
rangesOverlap(selectionFrom, selectionTo, segment.from, segment.to)
|
|
2884
|
+
) {
|
|
2885
|
+
output.push(segment);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
continue;
|
|
2889
|
+
}
|
|
2890
|
+
if (block.kind === "table") {
|
|
2891
|
+
for (const row of block.rows) {
|
|
2892
|
+
for (const cell of row.cells) {
|
|
2893
|
+
collectSelectedTextSegments(cell.content, selectionFrom, selectionTo, output);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
if (block.kind === "sdt_block") {
|
|
2899
|
+
collectSelectedTextSegments(block.children, selectionFrom, selectionTo, output);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
return output;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
function buildRunPropertyBeforeXml(
|
|
2906
|
+
segment: Extract<SurfaceInlineSegment, { kind: "text" }>,
|
|
2907
|
+
): string {
|
|
2908
|
+
const parts: string[] = [];
|
|
2909
|
+
const marks = new Set(segment.marks ?? []);
|
|
2910
|
+
if (marks.has("bold")) parts.push("<w:b/>");
|
|
2911
|
+
if (marks.has("italic")) parts.push("<w:i/>");
|
|
2912
|
+
if (marks.has("underline")) parts.push("<w:u w:val=\"single\"/>");
|
|
2913
|
+
if (marks.has("strikethrough")) parts.push("<w:strike/>");
|
|
2914
|
+
if (marks.has("superscript")) parts.push("<w:vertAlign w:val=\"superscript\"/>");
|
|
2915
|
+
if (marks.has("subscript")) parts.push("<w:vertAlign w:val=\"subscript\"/>");
|
|
2916
|
+
if (segment.markAttrs?.fontFamily) {
|
|
2917
|
+
parts.push(`<w:rFonts w:ascii="${escapeAttributeXml(segment.markAttrs.fontFamily)}" w:hAnsi="${escapeAttributeXml(segment.markAttrs.fontFamily)}"/>`);
|
|
2918
|
+
}
|
|
2919
|
+
if (segment.markAttrs?.fontSize !== undefined) {
|
|
2920
|
+
parts.push(`<w:sz w:val="${segment.markAttrs.fontSize}"/>`);
|
|
2921
|
+
}
|
|
2922
|
+
if (segment.markAttrs?.textColor) {
|
|
2923
|
+
parts.push(`<w:color w:val="${escapeAttributeXml(segment.markAttrs.textColor)}"/>`);
|
|
2924
|
+
}
|
|
2925
|
+
if (segment.markAttrs?.backgroundColor) {
|
|
2926
|
+
parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${escapeAttributeXml(segment.markAttrs.backgroundColor)}"/>`);
|
|
2927
|
+
}
|
|
2928
|
+
return `<w:rPr>${parts.join("")}</w:rPr>`;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
function buildParagraphPropertyBeforeXml(
|
|
2932
|
+
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
2933
|
+
): string {
|
|
2934
|
+
const parts: string[] = [];
|
|
2935
|
+
if (paragraph.styleId) {
|
|
2936
|
+
parts.push(`<w:pStyle w:val="${escapeAttributeXml(paragraph.styleId)}"/>`);
|
|
2937
|
+
}
|
|
2938
|
+
if (paragraph.numbering) {
|
|
2939
|
+
parts.push(
|
|
2940
|
+
`<w:numPr><w:ilvl w:val="${paragraph.numbering.level}"/><w:numId w:val="${escapeAttributeXml(
|
|
2941
|
+
paragraph.numbering.numberingInstanceId.replace(/^num:/u, ""),
|
|
2942
|
+
)}"/></w:numPr>`,
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
if (paragraph.alignment) {
|
|
2946
|
+
parts.push(`<w:jc w:val="${escapeAttributeXml(paragraph.alignment)}"/>`);
|
|
2947
|
+
}
|
|
2948
|
+
if (paragraph.indentation) {
|
|
2949
|
+
const attrs: string[] = [];
|
|
2950
|
+
if (paragraph.indentation.left !== undefined) attrs.push(`w:left="${paragraph.indentation.left}"`);
|
|
2951
|
+
if (paragraph.indentation.right !== undefined) attrs.push(`w:right="${paragraph.indentation.right}"`);
|
|
2952
|
+
if (paragraph.indentation.firstLine !== undefined) attrs.push(`w:firstLine="${paragraph.indentation.firstLine}"`);
|
|
2953
|
+
if (paragraph.indentation.hanging !== undefined) attrs.push(`w:hanging="${paragraph.indentation.hanging}"`);
|
|
2954
|
+
if (attrs.length > 0) {
|
|
2955
|
+
parts.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
function escapeAttributeXml(value: string): string {
|
|
2962
|
+
return value
|
|
2963
|
+
.replace(/&/g, "&")
|
|
2964
|
+
.replace(/</g, "<")
|
|
2965
|
+
.replace(/>/g, ">")
|
|
2966
|
+
.replace(/"/g, """);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2116
2969
|
function isSelectionSuggesting(runtime: WordReviewEditorRuntime): boolean {
|
|
2117
2970
|
return runtime.getInteractionGuardSnapshot().effectiveMode === "suggest";
|
|
2118
2971
|
}
|
|
@@ -3262,6 +4115,8 @@ function deriveEditorViewMode(
|
|
|
3262
4115
|
}
|
|
3263
4116
|
|
|
3264
4117
|
function resolveWordReviewEditorChromeVisibility(
|
|
4118
|
+
chromePreset: WordReviewEditorChromePreset,
|
|
4119
|
+
chromeOptions: Partial<WordReviewEditorChromeOptions> | undefined,
|
|
3265
4120
|
chromeVisibility: WordReviewEditorProps["chromeVisibility"],
|
|
3266
4121
|
showReviewPanel: boolean,
|
|
3267
4122
|
): Partial<WordReviewEditorChromeVisibility> {
|
|
@@ -3269,10 +4124,14 @@ function resolveWordReviewEditorChromeVisibility(
|
|
|
3269
4124
|
showReviewPanel
|
|
3270
4125
|
? {}
|
|
3271
4126
|
: { reviewRail: false } satisfies Partial<WordReviewEditorChromeVisibility>;
|
|
3272
|
-
return {
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
4127
|
+
return resolveChromeVisibilityForPreset({
|
|
4128
|
+
chromePreset,
|
|
4129
|
+
chromeOptions,
|
|
4130
|
+
chromeVisibility: {
|
|
4131
|
+
...legacyVisibility,
|
|
4132
|
+
...chromeVisibility,
|
|
4133
|
+
},
|
|
4134
|
+
});
|
|
3276
4135
|
}
|
|
3277
4136
|
|
|
3278
4137
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
@@ -3508,6 +4367,7 @@ function createSelectionToolbarSelectionKey(
|
|
|
3508
4367
|
selection: RuntimeRenderSnapshot["selection"],
|
|
3509
4368
|
activeStory: EditorStoryTarget,
|
|
3510
4369
|
activeRevisionId?: string,
|
|
4370
|
+
activeCommentId?: string | null,
|
|
3511
4371
|
): string | null {
|
|
3512
4372
|
if (activeRevisionId) {
|
|
3513
4373
|
return JSON.stringify({
|
|
@@ -3515,7 +4375,19 @@ function createSelectionToolbarSelectionKey(
|
|
|
3515
4375
|
revisionId: activeRevisionId,
|
|
3516
4376
|
});
|
|
3517
4377
|
}
|
|
3518
|
-
if (
|
|
4378
|
+
if (activeCommentId && selection.isCollapsed) {
|
|
4379
|
+
return JSON.stringify({
|
|
4380
|
+
story: activeStory,
|
|
4381
|
+
commentId: activeCommentId,
|
|
4382
|
+
});
|
|
4383
|
+
}
|
|
4384
|
+
if (selection.activeRange.kind !== "range") {
|
|
4385
|
+
if (selection.activeRange.kind === "node") {
|
|
4386
|
+
return JSON.stringify({
|
|
4387
|
+
story: activeStory,
|
|
4388
|
+
nodeAt: selection.activeRange.at,
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
3519
4391
|
return null;
|
|
3520
4392
|
}
|
|
3521
4393
|
|
|
@@ -3523,9 +4395,38 @@ function createSelectionToolbarSelectionKey(
|
|
|
3523
4395
|
story: activeStory,
|
|
3524
4396
|
from: selection.activeRange.from,
|
|
3525
4397
|
to: selection.activeRange.to,
|
|
4398
|
+
collapsed: selection.isCollapsed,
|
|
3526
4399
|
});
|
|
3527
4400
|
}
|
|
3528
4401
|
|
|
4402
|
+
function isActiveRevisionStillFocused(
|
|
4403
|
+
activeRevisionId: string,
|
|
4404
|
+
suggestionsSnapshot: SuggestionsSnapshot | null,
|
|
4405
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
4406
|
+
activeStory: EditorStoryTarget,
|
|
4407
|
+
): boolean {
|
|
4408
|
+
const suggestion = suggestionsSnapshot?.suggestions.find((entry) =>
|
|
4409
|
+
entry.status === "active" &&
|
|
4410
|
+
entry.actionability === "actionable" &&
|
|
4411
|
+
entry.anchor.kind === "range" &&
|
|
4412
|
+
storyTargetsEqual(entry.storyTarget, activeStory) &&
|
|
4413
|
+
entry.changeIds.includes(activeRevisionId)
|
|
4414
|
+
);
|
|
4415
|
+
if (!suggestion || suggestion.anchor.kind !== "range") {
|
|
4416
|
+
return false;
|
|
4417
|
+
}
|
|
4418
|
+
if (selection.activeRange.kind !== "range") {
|
|
4419
|
+
return false;
|
|
4420
|
+
}
|
|
4421
|
+
if (selection.isCollapsed) {
|
|
4422
|
+
const point = selection.activeRange.from;
|
|
4423
|
+
return point >= suggestion.anchor.from && point <= suggestion.anchor.to;
|
|
4424
|
+
}
|
|
4425
|
+
const selectionFrom = Math.min(selection.activeRange.from, selection.activeRange.to);
|
|
4426
|
+
const selectionTo = Math.max(selection.activeRange.from, selection.activeRange.to);
|
|
4427
|
+
return selectionFrom < suggestion.anchor.to && suggestion.anchor.from < selectionTo;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
3529
4430
|
function buildSelectionToolbarModel(args: {
|
|
3530
4431
|
snapshot: RuntimeRenderSnapshot;
|
|
3531
4432
|
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>;
|
|
@@ -3596,6 +4497,7 @@ function buildSelectionToolbarModel(args: {
|
|
|
3596
4497
|
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3597
4498
|
|
|
3598
4499
|
return {
|
|
4500
|
+
kind: "formatting-inline",
|
|
3599
4501
|
previewText,
|
|
3600
4502
|
badges,
|
|
3601
4503
|
canToggleFormatting,
|
|
@@ -3609,6 +4511,7 @@ function buildSelectionToolbarModel(args: {
|
|
|
3609
4511
|
|
|
3610
4512
|
function buildSuggestionCardModel(args: {
|
|
3611
4513
|
snapshot: RuntimeRenderSnapshot;
|
|
4514
|
+
suggestionsSnapshot: SuggestionsSnapshot;
|
|
3612
4515
|
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>;
|
|
3613
4516
|
capabilities: ReturnType<typeof deriveCapabilities>;
|
|
3614
4517
|
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
@@ -3619,6 +4522,7 @@ function buildSuggestionCardModel(args: {
|
|
|
3619
4522
|
}): SuggestionCardModel | null {
|
|
3620
4523
|
const {
|
|
3621
4524
|
snapshot,
|
|
4525
|
+
suggestionsSnapshot,
|
|
3622
4526
|
viewState,
|
|
3623
4527
|
capabilities,
|
|
3624
4528
|
workflowScopeSnapshot,
|
|
@@ -3646,32 +4550,32 @@ function buildSuggestionCardModel(args: {
|
|
|
3646
4550
|
const selectionTo = activeRange
|
|
3647
4551
|
? Math.max(activeRange.from, activeRange.to)
|
|
3648
4552
|
: null;
|
|
3649
|
-
const
|
|
3650
|
-
storyTargetsEqual(
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
4553
|
+
const candidateSuggestions = suggestionsSnapshot.suggestions.filter((suggestion) =>
|
|
4554
|
+
storyTargetsEqual(suggestion.storyTarget, viewState.activeStory) &&
|
|
4555
|
+
suggestion.status === "active" &&
|
|
4556
|
+
suggestion.actionability === "actionable" &&
|
|
4557
|
+
suggestion.anchor.kind === "range" &&
|
|
3654
4558
|
(
|
|
3655
|
-
activeRevisionId
|
|
4559
|
+
(activeRevisionId !== undefined && suggestion.changeIds.includes(activeRevisionId)) ||
|
|
3656
4560
|
(
|
|
3657
4561
|
selectionFrom !== null &&
|
|
3658
4562
|
selectionTo !== null &&
|
|
3659
4563
|
rangesOverlap(
|
|
3660
4564
|
selectionFrom,
|
|
3661
4565
|
selectionTo,
|
|
3662
|
-
|
|
3663
|
-
|
|
4566
|
+
suggestion.anchor.from,
|
|
4567
|
+
suggestion.anchor.to,
|
|
3664
4568
|
)
|
|
3665
4569
|
)
|
|
3666
4570
|
)
|
|
3667
4571
|
);
|
|
3668
|
-
const
|
|
4572
|
+
const focusedSuggestion = (
|
|
3669
4573
|
activeRevisionId
|
|
3670
|
-
?
|
|
4574
|
+
? candidateSuggestions.find((suggestion) => suggestion.changeIds.includes(activeRevisionId))
|
|
3671
4575
|
: null
|
|
3672
|
-
) ??
|
|
4576
|
+
) ?? candidateSuggestions[0];
|
|
3673
4577
|
|
|
3674
|
-
if (!
|
|
4578
|
+
if (!focusedSuggestion || focusedSuggestion.suggestionId === suppressedSuggestionRevisionId) {
|
|
3675
4579
|
return null;
|
|
3676
4580
|
}
|
|
3677
4581
|
|
|
@@ -3699,17 +4603,18 @@ function buildSuggestionCardModel(args: {
|
|
|
3699
4603
|
(canAddComment ? undefined : addCommentDisabledReason);
|
|
3700
4604
|
|
|
3701
4605
|
return {
|
|
3702
|
-
|
|
3703
|
-
|
|
4606
|
+
kind: "suggestion-review",
|
|
4607
|
+
suggestionId: focusedSuggestion.suggestionId,
|
|
4608
|
+
changeIds: focusedSuggestion.changeIds,
|
|
4609
|
+
kindLabel: getSuggestionKindLabel(focusedSuggestion.kind),
|
|
3704
4610
|
previewText:
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
focusedRevision.label ??
|
|
4611
|
+
focusedSuggestion.excerpt ??
|
|
4612
|
+
focusedSuggestion.detail ??
|
|
3708
4613
|
"Suggested change",
|
|
3709
4614
|
badges,
|
|
3710
|
-
canAccept: canReviewSuggestion && capabilities.canAcceptChange &&
|
|
3711
|
-
canReject: canReviewSuggestion && capabilities.canRejectChange &&
|
|
3712
|
-
canEditSuggestion: canReviewSuggestion,
|
|
4615
|
+
canAccept: canReviewSuggestion && capabilities.canAcceptChange && focusedSuggestion.canAccept,
|
|
4616
|
+
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedSuggestion.canReject,
|
|
4617
|
+
canEditSuggestion: canReviewSuggestion && focusedSuggestion.editable,
|
|
3713
4618
|
canAddComment,
|
|
3714
4619
|
...(disabledReason ? { disabledReason } : {}),
|
|
3715
4620
|
};
|
|
@@ -3897,18 +4802,28 @@ function rangesOverlap(
|
|
|
3897
4802
|
return leftFrom < rightTo && rightFrom < leftTo;
|
|
3898
4803
|
}
|
|
3899
4804
|
|
|
3900
|
-
function getSuggestionKindLabel(
|
|
4805
|
+
function getSuggestionKindLabel(
|
|
4806
|
+
kind: TrackedChangeEntrySnapshot["kind"] | SuggestionEntrySnapshot["kind"],
|
|
4807
|
+
): string {
|
|
3901
4808
|
switch (kind) {
|
|
3902
4809
|
case "insertion":
|
|
3903
4810
|
return "Suggested insertion";
|
|
3904
4811
|
case "deletion":
|
|
3905
4812
|
return "Suggested deletion";
|
|
4813
|
+
case "replacement":
|
|
4814
|
+
return "Suggested replacement";
|
|
3906
4815
|
case "formatting":
|
|
4816
|
+
case "formatting-change":
|
|
3907
4817
|
return "Suggested formatting change";
|
|
3908
4818
|
case "property-change":
|
|
4819
|
+
case "paragraph-property-change":
|
|
3909
4820
|
return "Suggested property change";
|
|
3910
4821
|
case "move":
|
|
3911
4822
|
return "Suggested move";
|
|
4823
|
+
case "structural-change":
|
|
4824
|
+
return "Suggested structural change";
|
|
4825
|
+
case "object-change":
|
|
4826
|
+
return "Suggested object change";
|
|
3912
4827
|
}
|
|
3913
4828
|
}
|
|
3914
4829
|
|
|
@@ -4084,3 +4999,270 @@ function bytesToBase64(bytes: Uint8Array): string {
|
|
|
4084
4999
|
}
|
|
4085
5000
|
return btoa(binary);
|
|
4086
5001
|
}
|
|
5002
|
+
|
|
5003
|
+
function deriveReviewQueueSnapshot(input: {
|
|
5004
|
+
sections: Array<{
|
|
5005
|
+
sectionIndex: number;
|
|
5006
|
+
pageRange: { start: number; end: number };
|
|
5007
|
+
anchor: EditorAnchorProjection;
|
|
5008
|
+
}>;
|
|
5009
|
+
comments: RuntimeRenderSnapshot["comments"]["threads"];
|
|
5010
|
+
trackedChanges: RuntimeRenderSnapshot["trackedChanges"]["revisions"];
|
|
5011
|
+
hostAnnotations: HostAnnotationItem[];
|
|
5012
|
+
activeItemId?: string | null;
|
|
5013
|
+
}): ReviewQueueSnapshot {
|
|
5014
|
+
const items: ReviewQueueItem[] = [
|
|
5015
|
+
...input.comments
|
|
5016
|
+
.filter((thread) => thread.status !== "detached")
|
|
5017
|
+
.map((thread) => {
|
|
5018
|
+
const section = findSectionForAnchor(input.sections, thread.anchor);
|
|
5019
|
+
const status: ReviewQueueItem["status"] =
|
|
5020
|
+
thread.status === "resolved" ? "resolved" : "open";
|
|
5021
|
+
return {
|
|
5022
|
+
itemId: `comment:${thread.commentId}`,
|
|
5023
|
+
kind: "comment" as const,
|
|
5024
|
+
label: thread.excerpt || thread.anchorLabel || "Comment",
|
|
5025
|
+
anchor: thread.anchor,
|
|
5026
|
+
actionable: thread.status === "open",
|
|
5027
|
+
status,
|
|
5028
|
+
sectionIndex: section?.sectionIndex,
|
|
5029
|
+
};
|
|
5030
|
+
}),
|
|
5031
|
+
...input.trackedChanges
|
|
5032
|
+
.filter((revision) => revision.status !== "detached")
|
|
5033
|
+
.map((revision) => {
|
|
5034
|
+
const section = findSectionForAnchor(input.sections, revision.anchor);
|
|
5035
|
+
const status: ReviewQueueItem["status"] =
|
|
5036
|
+
revision.status === "active" ? "open" : "resolved";
|
|
5037
|
+
return {
|
|
5038
|
+
itemId: `change:${revision.revisionId}`,
|
|
5039
|
+
kind: "change" as const,
|
|
5040
|
+
label: revision.label,
|
|
5041
|
+
anchor: revision.anchor,
|
|
5042
|
+
storyTarget: revision.storyTarget,
|
|
5043
|
+
actionable:
|
|
5044
|
+
revision.status === "active" &&
|
|
5045
|
+
revision.actionability === "actionable" &&
|
|
5046
|
+
(revision.canAccept || revision.canReject),
|
|
5047
|
+
status,
|
|
5048
|
+
sectionIndex: section?.sectionIndex,
|
|
5049
|
+
};
|
|
5050
|
+
}),
|
|
5051
|
+
...input.hostAnnotations.filter(isSectionReviewAnnotation).map((annotation) => {
|
|
5052
|
+
const section = findSectionForAnchor(input.sections, annotation.anchor);
|
|
5053
|
+
return {
|
|
5054
|
+
itemId: `section:${annotation.annotationId}`,
|
|
5055
|
+
kind: "section_mark" as const,
|
|
5056
|
+
label: annotation.label,
|
|
5057
|
+
anchor: annotation.anchor,
|
|
5058
|
+
storyTarget: annotation.storyTarget,
|
|
5059
|
+
actionable: true,
|
|
5060
|
+
status: "open" as const,
|
|
5061
|
+
sectionIndex: section?.sectionIndex,
|
|
5062
|
+
};
|
|
5063
|
+
}),
|
|
5064
|
+
].sort(compareReviewQueueItems);
|
|
5065
|
+
|
|
5066
|
+
const activeIndex = items.length === 0
|
|
5067
|
+
? -1
|
|
5068
|
+
: input.activeItemId
|
|
5069
|
+
? items.findIndex((item) => item.itemId === input.activeItemId)
|
|
5070
|
+
: items.findIndex((item) => item.actionable);
|
|
5071
|
+
const openCount = items.filter((item) => item.status === "open").length;
|
|
5072
|
+
|
|
5073
|
+
return {
|
|
5074
|
+
totalCount: items.length,
|
|
5075
|
+
openCount,
|
|
5076
|
+
activeIndex: activeIndex >= 0 ? activeIndex : items.length === 0 ? -1 : 0,
|
|
5077
|
+
items,
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
function navigateReviewQueue(
|
|
5082
|
+
runtime: WordReviewEditorRuntime,
|
|
5083
|
+
direction: "next" | "previous",
|
|
5084
|
+
): ReviewQueueItem | null {
|
|
5085
|
+
const queue = deriveReviewQueueSnapshot({
|
|
5086
|
+
sections: runtime.getSections(),
|
|
5087
|
+
comments: runtime.getRenderSnapshot().comments.threads,
|
|
5088
|
+
trackedChanges: runtime.getRenderSnapshot().trackedChanges.revisions,
|
|
5089
|
+
hostAnnotations: runtime.getHostAnnotationSnapshot().annotations,
|
|
5090
|
+
});
|
|
5091
|
+
const nextItem = getAdjacentReviewQueueItem(queue, null, direction);
|
|
5092
|
+
if (!nextItem) {
|
|
5093
|
+
return null;
|
|
5094
|
+
}
|
|
5095
|
+
focusReviewQueueItem(runtime, nextItem);
|
|
5096
|
+
return nextItem;
|
|
5097
|
+
}
|
|
5098
|
+
|
|
5099
|
+
function getAdjacentReviewQueueItem(
|
|
5100
|
+
queue: ReviewQueueSnapshot,
|
|
5101
|
+
activeItemId: string | null,
|
|
5102
|
+
direction: "next" | "previous",
|
|
5103
|
+
): ReviewQueueItem | null {
|
|
5104
|
+
if (queue.items.length === 0) {
|
|
5105
|
+
return null;
|
|
5106
|
+
}
|
|
5107
|
+
const currentIndex = activeItemId
|
|
5108
|
+
? queue.items.findIndex((item) => item.itemId === activeItemId)
|
|
5109
|
+
: queue.activeIndex;
|
|
5110
|
+
const safeIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
5111
|
+
const nextIndex =
|
|
5112
|
+
direction === "next"
|
|
5113
|
+
? (safeIndex + 1) % queue.items.length
|
|
5114
|
+
: (safeIndex - 1 + queue.items.length) % queue.items.length;
|
|
5115
|
+
return queue.items[nextIndex] ?? queue.items[0] ?? null;
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
function focusReviewQueueItem(
|
|
5119
|
+
runtime: WordReviewEditorRuntime,
|
|
5120
|
+
item: ReviewQueueItem,
|
|
5121
|
+
): void {
|
|
5122
|
+
if (item.kind === "comment") {
|
|
5123
|
+
runtime.openComment(item.itemId.replace(/^comment:/u, ""));
|
|
5124
|
+
}
|
|
5125
|
+
if (item.kind === "change") {
|
|
5126
|
+
const revisionId = item.itemId.replace(/^change:/u, "");
|
|
5127
|
+
const revision = runtime.getRenderSnapshot().trackedChanges.revisions.find(
|
|
5128
|
+
(entry) => entry.revisionId === revisionId,
|
|
5129
|
+
);
|
|
5130
|
+
if (revision?.storyTarget) {
|
|
5131
|
+
runtime.openStory(revision.storyTarget);
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
applyRuntimeSelection(
|
|
5135
|
+
runtime,
|
|
5136
|
+
createSelectionFromAnchor(item.anchor, item.storyTarget),
|
|
5137
|
+
);
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
function markSectionForReview(
|
|
5141
|
+
runtime: WordReviewEditorRuntime,
|
|
5142
|
+
input?: { sectionIndex?: number; label?: string },
|
|
5143
|
+
): string | null {
|
|
5144
|
+
const targetIndex =
|
|
5145
|
+
input?.sectionIndex ?? runtime.getDocumentNavigationSnapshot().activeSectionIndex;
|
|
5146
|
+
const section = runtime.getSectionSnapshot({ sectionIndex: targetIndex });
|
|
5147
|
+
if (!section) {
|
|
5148
|
+
return null;
|
|
5149
|
+
}
|
|
5150
|
+
const annotationId = createReviewSectionMarkId(section.sectionIndex);
|
|
5151
|
+
runtime.setHostAnnotationOverlay({
|
|
5152
|
+
overlayVersion: "host-annotation-overlay/1",
|
|
5153
|
+
annotations: [
|
|
5154
|
+
...runtime.getHostAnnotationSnapshot().annotations.filter(
|
|
5155
|
+
(annotation) => annotation.annotationId !== annotationId,
|
|
5156
|
+
),
|
|
5157
|
+
{
|
|
5158
|
+
annotationId,
|
|
5159
|
+
kind: "note",
|
|
5160
|
+
label:
|
|
5161
|
+
input?.label ??
|
|
5162
|
+
section.primaryHeadingText ??
|
|
5163
|
+
`Section ${section.sectionIndex + 1}`,
|
|
5164
|
+
anchor: section.anchor,
|
|
5165
|
+
provenance: "host",
|
|
5166
|
+
detail: "Marked for review",
|
|
5167
|
+
},
|
|
5168
|
+
],
|
|
5169
|
+
});
|
|
5170
|
+
return annotationId;
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
function clearSectionReviewMark(
|
|
5174
|
+
runtime: WordReviewEditorRuntime,
|
|
5175
|
+
annotationId: string,
|
|
5176
|
+
): void {
|
|
5177
|
+
const nextAnnotations = runtime
|
|
5178
|
+
.getHostAnnotationSnapshot()
|
|
5179
|
+
.annotations.filter((annotation) => annotation.annotationId !== annotationId);
|
|
5180
|
+
if (nextAnnotations.length === 0) {
|
|
5181
|
+
runtime.clearHostAnnotationOverlay();
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
runtime.setHostAnnotationOverlay({
|
|
5185
|
+
overlayVersion: "host-annotation-overlay/1",
|
|
5186
|
+
annotations: nextAnnotations,
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
function mergeHostAndReviewSectionAnnotations(
|
|
5191
|
+
hostAnnotations: HostAnnotationItem[],
|
|
5192
|
+
reviewSectionMarks: Array<{ annotationId: string; sectionIndex: number; label: string }>,
|
|
5193
|
+
sections: Array<{
|
|
5194
|
+
sectionIndex: number;
|
|
5195
|
+
anchor: EditorAnchorProjection;
|
|
5196
|
+
}>,
|
|
5197
|
+
): HostAnnotationItem[] {
|
|
5198
|
+
const reviewAnnotations = reviewSectionMarks.flatMap((entry) => {
|
|
5199
|
+
const section = sections.find((candidate) => candidate.sectionIndex === entry.sectionIndex);
|
|
5200
|
+
if (!section) {
|
|
5201
|
+
return [];
|
|
5202
|
+
}
|
|
5203
|
+
return [{
|
|
5204
|
+
annotationId: entry.annotationId,
|
|
5205
|
+
kind: "note" as const,
|
|
5206
|
+
label: entry.label,
|
|
5207
|
+
anchor: section.anchor,
|
|
5208
|
+
provenance: "host" as const,
|
|
5209
|
+
detail: "Marked for review",
|
|
5210
|
+
}];
|
|
5211
|
+
});
|
|
5212
|
+
return [...hostAnnotations, ...reviewAnnotations];
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
function isSectionReviewAnnotation(annotation: HostAnnotationItem): boolean {
|
|
5216
|
+
return annotation.kind === "note" && annotation.detail === "Marked for review";
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
function createReviewSectionMarkId(sectionIndex: number): string {
|
|
5220
|
+
return `review-section:${sectionIndex}`;
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
function findSectionForAnchor(
|
|
5224
|
+
sections: Array<{
|
|
5225
|
+
sectionIndex: number;
|
|
5226
|
+
pageRange: { start: number; end: number };
|
|
5227
|
+
anchor: EditorAnchorProjection;
|
|
5228
|
+
}>,
|
|
5229
|
+
anchor: EditorAnchorProjection,
|
|
5230
|
+
) {
|
|
5231
|
+
const anchorStart = getAnchorStart(anchor);
|
|
5232
|
+
return sections.find((section) => {
|
|
5233
|
+
const sectionStart = getAnchorStart(section.anchor);
|
|
5234
|
+
const sectionEnd = getAnchorEnd(section.anchor);
|
|
5235
|
+
return anchorStart >= sectionStart && anchorStart <= sectionEnd;
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
5238
|
+
|
|
5239
|
+
function compareReviewQueueItems(left: ReviewQueueItem, right: ReviewQueueItem): number {
|
|
5240
|
+
const leftSection = left.sectionIndex ?? Number.MAX_SAFE_INTEGER;
|
|
5241
|
+
const rightSection = right.sectionIndex ?? Number.MAX_SAFE_INTEGER;
|
|
5242
|
+
if (leftSection !== rightSection) {
|
|
5243
|
+
return leftSection - rightSection;
|
|
5244
|
+
}
|
|
5245
|
+
return getAnchorStart(left.anchor) - getAnchorStart(right.anchor);
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
function getAnchorStart(anchor: EditorAnchorProjection): number {
|
|
5249
|
+
switch (anchor.kind) {
|
|
5250
|
+
case "range":
|
|
5251
|
+
return anchor.from;
|
|
5252
|
+
case "node":
|
|
5253
|
+
return anchor.at;
|
|
5254
|
+
case "detached":
|
|
5255
|
+
return anchor.lastKnownRange?.from ?? Number.MAX_SAFE_INTEGER;
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
|
|
5259
|
+
function getAnchorEnd(anchor: EditorAnchorProjection): number {
|
|
5260
|
+
switch (anchor.kind) {
|
|
5261
|
+
case "range":
|
|
5262
|
+
return anchor.to;
|
|
5263
|
+
case "node":
|
|
5264
|
+
return anchor.at;
|
|
5265
|
+
case "detached":
|
|
5266
|
+
return anchor.lastKnownRange?.to ?? Number.MAX_SAFE_INTEGER;
|
|
5267
|
+
}
|
|
5268
|
+
}
|