@beyondwork/docx-react-component 1.0.42 → 1.0.45
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 +17 -0
- package/package.json +5 -4
- package/src/api/editor-state-types.ts +110 -0
- package/src/api/public-types.ts +333 -4
- package/src/core/commands/formatting-commands.ts +7 -1
- package/src/core/commands/index.ts +60 -10
- package/src/core/commands/text-commands.ts +59 -0
- package/src/core/search/search-text.ts +15 -2
- package/src/core/selection/review-anchors.ts +131 -21
- package/src/index.ts +29 -1
- package/src/io/chart-preview-resolver.ts +281 -0
- package/src/io/docx-session.ts +692 -2
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +38 -9
- package/src/io/export/twip.ts +1 -1
- package/src/io/load-scheduler.ts +230 -0
- package/src/io/normalize/normalize-text.ts +116 -0
- package/src/io/ooxml/parse-comments.ts +0 -33
- package/src/io/ooxml/parse-complex-content.ts +14 -0
- package/src/io/ooxml/parse-main-document.ts +4 -0
- package/src/io/ooxml/workflow-payload-validator.ts +97 -1
- package/src/io/ooxml/workflow-payload.ts +172 -1
- package/src/preservation/opaque-region.ts +5 -0
- package/src/review/store/comment-remapping.ts +2 -2
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/document-runtime.ts +661 -42
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
- package/src/runtime/edit-dispatch/index.ts +2 -0
- package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
- package/src/runtime/editor-state-channel.ts +544 -0
- package/src/runtime/editor-state-integration.ts +217 -0
- package/src/runtime/editor-surface/capabilities.ts +411 -0
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +4 -0
- package/src/runtime/layout/layout-engine-instance.ts +63 -2
- package/src/runtime/layout/layout-engine-version.ts +41 -0
- package/src/runtime/layout/paginated-layout-engine.ts +211 -14
- package/src/runtime/layout/public-facet.ts +430 -1
- package/src/runtime/perf-counters.ts +28 -0
- package/src/runtime/prerender/cache-envelope.ts +29 -0
- package/src/runtime/prerender/cache-key.ts +66 -0
- package/src/runtime/prerender/font-fingerprint.ts +17 -0
- package/src/runtime/prerender/graph-canonicalize.ts +121 -0
- package/src/runtime/prerender/indexeddb-cache.ts +184 -0
- package/src/runtime/prerender/prerender-document.ts +145 -0
- package/src/runtime/render/block-fragment-projection.ts +2 -0
- package/src/runtime/render/render-frame-types.ts +17 -0
- package/src/runtime/render/render-kernel.ts +172 -29
- package/src/runtime/selection/post-edit-validator.ts +77 -0
- package/src/runtime/surface-projection.ts +45 -7
- package/src/runtime/workflow-markup.ts +71 -16
- package/src/ui/WordReviewEditor.tsx +142 -237
- package/src/ui/editor-command-bag.ts +14 -0
- package/src/ui/editor-runtime-boundary.ts +115 -12
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-helpers.ts +10 -0
- package/src/ui/runtime-shortcut-dispatch.ts +28 -68
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
- package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
- package/src/ui-tailwind/editor-surface/pm-schema.ts +170 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +58 -7
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +47 -0
- package/src/ui-tailwind/index.ts +5 -1
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
- package/src/ui-tailwind/theme/editor-theme.css +47 -14
- package/src/ui-tailwind/tw-review-workspace.tsx +303 -123
|
@@ -34,12 +34,20 @@ import {
|
|
|
34
34
|
type DocumentRuntimeEvent,
|
|
35
35
|
type DocumentRuntime,
|
|
36
36
|
} from "../runtime/document-runtime.ts";
|
|
37
|
+
import { createEditorStateChannel } from "../runtime/editor-state-channel.ts";
|
|
37
38
|
import {
|
|
38
39
|
createRuntimeCommandAppliedBridge,
|
|
39
40
|
type RuntimeCommandAppliedBridge,
|
|
40
41
|
} from "../runtime/collab/runtime-collab-sync.ts";
|
|
41
42
|
import { createInertLayoutFacet } from "../runtime/layout/index.ts";
|
|
42
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
loadDocxEditorSession,
|
|
45
|
+
loadDocxEditorSessionAsync,
|
|
46
|
+
} from "../io/docx-session.ts";
|
|
47
|
+
import {
|
|
48
|
+
createLoadScheduler,
|
|
49
|
+
type LoadScheduler,
|
|
50
|
+
} from "../io/load-scheduler.ts";
|
|
43
51
|
import {
|
|
44
52
|
decodePersistedSourcePackageBytes,
|
|
45
53
|
hasValidPersistedSourcePackageDigest,
|
|
@@ -48,6 +56,7 @@ import {
|
|
|
48
56
|
createEditorViewStateSnapshot,
|
|
49
57
|
createViewState,
|
|
50
58
|
} from "../runtime/view-state.ts";
|
|
59
|
+
import { hydrateEditorStateFromEnvelope } from "../runtime/editor-state-integration.ts";
|
|
51
60
|
import {
|
|
52
61
|
recordPerfSample,
|
|
53
62
|
} from "../ui-tailwind/editor-surface/perf-probe.ts";
|
|
@@ -62,6 +71,14 @@ export interface ResolvedSource {
|
|
|
62
71
|
initialDocx?: Uint8Array | ArrayBuffer;
|
|
63
72
|
initialSessionState?: EditorSessionState;
|
|
64
73
|
initialSnapshot?: PersistedEditorSnapshot;
|
|
74
|
+
/**
|
|
75
|
+
* Fastload P6: when the boundary hook pre-loads the docx via the
|
|
76
|
+
* async loader (DOM environments only), it stashes the session here
|
|
77
|
+
* so `createRuntime` reuses it instead of running another synchronous
|
|
78
|
+
* load. Undefined in SSR / Node test fallback — `createRuntime` then
|
|
79
|
+
* does the classic synchronous load.
|
|
80
|
+
*/
|
|
81
|
+
preloadedDocxSession?: ReturnType<typeof loadDocxEditorSession>;
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
export interface CreateRuntimeArgs {
|
|
@@ -280,6 +297,9 @@ export function useEditorRuntimeBoundary(
|
|
|
280
297
|
const pendingReadySourceRef = useRef<"docx" | "session" | "snapshot" | null>(null);
|
|
281
298
|
const autosaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
282
299
|
const lastSavedRevisionTokenRef = useRef<string | null>(null);
|
|
300
|
+
// Fastload P6: scheduler used by the DOM-side async docx loader. Held
|
|
301
|
+
// here so it can be disposed on unmount / source change.
|
|
302
|
+
const loadSchedulerRef = useRef<LoadScheduler | null>(null);
|
|
283
303
|
const hostAdapterRef = useRef(hostAdapter);
|
|
284
304
|
const datastoreRef = useRef(datastore);
|
|
285
305
|
const onEventRef = useRef(onEvent);
|
|
@@ -355,6 +375,10 @@ export function useEditorRuntimeBoundary(
|
|
|
355
375
|
lastSavedRevisionTokenRef.current = null;
|
|
356
376
|
runtimeRef.current?.dispose?.();
|
|
357
377
|
runtimeRef.current = null;
|
|
378
|
+
// Fastload P6: dispose any scheduler held from a previous mount /
|
|
379
|
+
// source cycle before allocating a fresh one for this load.
|
|
380
|
+
loadSchedulerRef.current?.dispose();
|
|
381
|
+
loadSchedulerRef.current = null;
|
|
358
382
|
setRuntime(null);
|
|
359
383
|
|
|
360
384
|
try {
|
|
@@ -375,6 +399,33 @@ export function useEditorRuntimeBoundary(
|
|
|
375
399
|
return;
|
|
376
400
|
}
|
|
377
401
|
|
|
402
|
+
// Fastload P6: in a DOM environment, preload the docx via the
|
|
403
|
+
// async loader so normalize-body yields mid-walk and the browser
|
|
404
|
+
// can paint the skeleton while the rest of the parse finishes.
|
|
405
|
+
// SSR / Node tests fall through to the synchronous load inside
|
|
406
|
+
// `createRuntime`.
|
|
407
|
+
if (
|
|
408
|
+
source.initialDocx !== undefined &&
|
|
409
|
+
source.preloadedDocxSession === undefined &&
|
|
410
|
+
typeof document !== "undefined"
|
|
411
|
+
) {
|
|
412
|
+
const scheduler = createLoadScheduler();
|
|
413
|
+
loadSchedulerRef.current = scheduler;
|
|
414
|
+
const preloaded = await loadDocxEditorSessionAsync({
|
|
415
|
+
documentId,
|
|
416
|
+
sourceLabel: source.sourceLabel,
|
|
417
|
+
bytes: source.initialDocx,
|
|
418
|
+
editorBuild: "dev",
|
|
419
|
+
scheduler,
|
|
420
|
+
});
|
|
421
|
+
if (cancelled) {
|
|
422
|
+
scheduler.dispose();
|
|
423
|
+
loadSchedulerRef.current = null;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
source.preloadedDocxSession = preloaded;
|
|
427
|
+
}
|
|
428
|
+
|
|
378
429
|
const nextRuntime = createRuntime(
|
|
379
430
|
{
|
|
380
431
|
documentId,
|
|
@@ -480,6 +531,11 @@ export function useEditorRuntimeBoundary(
|
|
|
480
531
|
}
|
|
481
532
|
runtimeRef.current?.dispose?.();
|
|
482
533
|
runtimeRef.current = null;
|
|
534
|
+
// Fastload P6: release any pending idle callbacks the load scheduler
|
|
535
|
+
// registered so React unmount doesn't leak setTimeout/IdleCallback
|
|
536
|
+
// handles.
|
|
537
|
+
loadSchedulerRef.current?.dispose();
|
|
538
|
+
loadSchedulerRef.current = null;
|
|
483
539
|
};
|
|
484
540
|
}, []);
|
|
485
541
|
|
|
@@ -568,14 +624,16 @@ function createRuntime(
|
|
|
568
624
|
handlers: RuntimeLifecycleHandlers = {},
|
|
569
625
|
): WordReviewEditorRuntime {
|
|
570
626
|
const bootstrapEvents: DocumentRuntimeEvent[] = [];
|
|
571
|
-
const docxSession =
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
627
|
+
const docxSession =
|
|
628
|
+
args.source.preloadedDocxSession ??
|
|
629
|
+
(args.source.initialDocx
|
|
630
|
+
? loadDocxEditorSession({
|
|
631
|
+
documentId: args.documentId,
|
|
632
|
+
sourceLabel: args.source.sourceLabel,
|
|
633
|
+
bytes: args.source.initialDocx,
|
|
634
|
+
editorBuild: "dev",
|
|
635
|
+
})
|
|
636
|
+
: undefined);
|
|
579
637
|
const snapshotExportResolution = !args.source.initialDocx
|
|
580
638
|
? resolvePackageBackedExportSession(args)
|
|
581
639
|
: undefined;
|
|
@@ -594,7 +652,7 @@ function createRuntime(
|
|
|
594
652
|
? applySessionExportBarrier(initialSessionState, snapshotExportResolution.barrier)
|
|
595
653
|
: initialSessionState;
|
|
596
654
|
|
|
597
|
-
const
|
|
655
|
+
const baseRuntime = createDocumentRuntime({
|
|
598
656
|
documentId: args.documentId,
|
|
599
657
|
initialSessionState: runtimeSessionState,
|
|
600
658
|
sourceKind: args.source.source,
|
|
@@ -629,7 +687,39 @@ function createRuntime(
|
|
|
629
687
|
},
|
|
630
688
|
defaultAuthorId: args.currentUserId,
|
|
631
689
|
onCommandApplied: args.commandAppliedBridge?.onCommandApplied,
|
|
632
|
-
})
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// Schema 1.2: drive load-path hydration from the parsed envelope.
|
|
693
|
+
if (docxSession?.initialEditorStatePayload) {
|
|
694
|
+
void hydrateEditorStateFromEnvelope({
|
|
695
|
+
editorState: docxSession.initialEditorStatePayload,
|
|
696
|
+
channel: baseRuntime.editorStateChannel,
|
|
697
|
+
applyBlob: (ns, data) => {
|
|
698
|
+
switch (ns) {
|
|
699
|
+
case "hostAnnotations":
|
|
700
|
+
baseRuntime.setHostAnnotationOverlay(data as import("../api/public-types.ts").HostAnnotationOverlay);
|
|
701
|
+
break;
|
|
702
|
+
case "workflowOverlay":
|
|
703
|
+
baseRuntime.setWorkflowOverlay(data as import("../api/public-types.ts").WorkflowOverlay);
|
|
704
|
+
break;
|
|
705
|
+
case "workflowMetadata":
|
|
706
|
+
// Metadata is split across definitions + entries in the runtime;
|
|
707
|
+
// inline payload carries the full snapshot so we apply entries.
|
|
708
|
+
if (data && typeof data === "object" && "entries" in (data as object)) {
|
|
709
|
+
baseRuntime.setWorkflowMetadataEntries(
|
|
710
|
+
(data as { entries: import("../api/public-types.ts").WorkflowMetadataEntry[] }).entries,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
case "workItems":
|
|
715
|
+
// workItems namespace is reserved; no runtime setter yet.
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const runtime: WordReviewEditorRuntime = Object.assign(baseRuntime, {
|
|
633
723
|
drainBootstrapEvents: () => bootstrapEvents.splice(0, bootstrapEvents.length),
|
|
634
724
|
emitBlockedCommand: (
|
|
635
725
|
command: string,
|
|
@@ -806,7 +896,9 @@ function createLoadingRuntimeBridge(input: {
|
|
|
806
896
|
openComment: () => undefined,
|
|
807
897
|
resolveComment: () => undefined,
|
|
808
898
|
reopenComment: () => undefined,
|
|
809
|
-
addCommentReply: () =>
|
|
899
|
+
addCommentReply: () => {
|
|
900
|
+
throw createLoadingBoundaryError(input.snapshot.documentId, "comment");
|
|
901
|
+
},
|
|
810
902
|
editCommentBody: () => undefined,
|
|
811
903
|
acceptChange: () => undefined,
|
|
812
904
|
rejectChange: () => undefined,
|
|
@@ -908,6 +1000,17 @@ function createLoadingRuntimeBridge(input: {
|
|
|
908
1000
|
items: [],
|
|
909
1001
|
}),
|
|
910
1002
|
getRuntimeContextAnalytics: () => null,
|
|
1003
|
+
// Schema 1.2 — no-op stubs for loading boundary (SSR / headless path).
|
|
1004
|
+
configureEditorStatePolicy: () => undefined,
|
|
1005
|
+
registerEditorStateResolver: () => undefined,
|
|
1006
|
+
registerEditorStatePersister: () => undefined,
|
|
1007
|
+
getEditorStateKey: () => undefined,
|
|
1008
|
+
retryPendingPersist: async () => undefined,
|
|
1009
|
+
editorStateChannel: createEditorStateChannel(),
|
|
1010
|
+
getPerfCountersSnapshot: () => ({}),
|
|
1011
|
+
resetPerfCounters: () => undefined,
|
|
1012
|
+
setVisibleBlockRange: () => undefined,
|
|
1013
|
+
requestViewportRefresh: () => undefined,
|
|
911
1014
|
};
|
|
912
1015
|
}
|
|
913
1016
|
|
|
@@ -64,6 +64,16 @@ export interface EditorShellViewProps {
|
|
|
64
64
|
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
65
65
|
chromePreset?: WordReviewEditorChromePreset;
|
|
66
66
|
chromeOptions?: Partial<WordReviewEditorChromeOptions>;
|
|
67
|
+
/** P9g — live collab session for the `"collab"` chrome preset's top nav. */
|
|
68
|
+
collabSession?: import("../runtime/collab-session.ts").CollabSession;
|
|
69
|
+
collabTransportStatus?: import("../api/awareness-identity-types.ts").TransportStatus;
|
|
70
|
+
collabActorId?: string;
|
|
71
|
+
collabSendBaseline?: {
|
|
72
|
+
originDocumentId: string;
|
|
73
|
+
originPayloadId: string;
|
|
74
|
+
originContentHash: string;
|
|
75
|
+
payloadXml: string;
|
|
76
|
+
};
|
|
67
77
|
reviewQueue?: ReviewQueueSnapshot;
|
|
68
78
|
documentContextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
69
79
|
selectionContextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
@@ -52,6 +52,11 @@ export interface EditorSurfaceControllerProps {
|
|
|
52
52
|
onUndo?: () => void;
|
|
53
53
|
onRedo?: () => void;
|
|
54
54
|
onBlockedInput?: (command: "paste" | "drop", message: string) => void;
|
|
55
|
+
onPasteApplied?: (meta: {
|
|
56
|
+
segmentCount: number;
|
|
57
|
+
charCount: number;
|
|
58
|
+
source: "paste" | "drop";
|
|
59
|
+
}) => void;
|
|
55
60
|
onCommentActivated?: (commentId: string) => void;
|
|
56
61
|
onRevisionActivated?: (revisionId: string) => void;
|
|
57
62
|
workflowScopes?: readonly WorkflowScope[];
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { SelectionSnapshot } from "../../api/public-types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Headless-UI-side `createSelectionSnapshot` that produces the **public**
|
|
5
|
+
* `EditorAnchorProjection` shape (top-level `from`/`to`). The runtime-facing
|
|
6
|
+
* twin at `src/core/state/editor-state.ts` produces the internal
|
|
7
|
+
* `RangeAnchor` shape (`range: { from, to }`). The two are *not*
|
|
8
|
+
* interchangeable — they serve different type contracts. See the
|
|
9
|
+
* `EditorAnchorProjection` definitions in `src/api/public-types.ts` vs
|
|
10
|
+
* `src/core/selection/mapping.ts`. Do not merge without first unifying
|
|
11
|
+
* those two definitions.
|
|
12
|
+
*/
|
|
3
13
|
export function createSelectionSnapshot(anchor: number, head = anchor): SelectionSnapshot {
|
|
4
14
|
const from = Math.min(anchor, head);
|
|
5
15
|
const to = Math.max(anchor, head);
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
StyleCatalogSnapshot,
|
|
3
3
|
WorkflowBlockedCommandReason,
|
|
4
4
|
} from "../api/public-types.ts";
|
|
5
|
+
import { CAPABILITY_BY_ID } from "../runtime/editor-surface/capabilities.ts";
|
|
5
6
|
|
|
6
7
|
export interface ShortcutKeyInput {
|
|
7
8
|
key: string;
|
|
@@ -113,87 +114,35 @@ export function resolveShellShortcut(
|
|
|
113
114
|
return { kind: "delegate", shortcut: "zoom-reset" };
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
if (isPasteTextOnlyShortcut(input, key)) {
|
|
117
|
-
return {
|
|
118
|
-
kind: "block",
|
|
119
|
-
command: "pasteTextOnly",
|
|
120
|
-
reason: createUnsupportedShortcutReason(
|
|
121
|
-
"Plain-text paste is not supported in the mounted editor yet.",
|
|
122
|
-
),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
117
|
if (isReplaceShortcut(input, key)) {
|
|
127
|
-
return
|
|
128
|
-
kind: "block",
|
|
129
|
-
command: "replaceText",
|
|
130
|
-
reason: createUnsupportedShortcutReason(
|
|
131
|
-
"Replace shortcuts are not supported in the mounted editor yet.",
|
|
132
|
-
),
|
|
133
|
-
};
|
|
118
|
+
return resolveBlockedCapability("replaceText");
|
|
134
119
|
}
|
|
135
120
|
|
|
136
121
|
if (
|
|
137
122
|
isGoToShortcut(input, key) ||
|
|
138
123
|
(key === "f5" && !input.shiftKey)
|
|
139
124
|
) {
|
|
140
|
-
return
|
|
141
|
-
kind: "block",
|
|
142
|
-
command: "goTo",
|
|
143
|
-
reason: createUnsupportedShortcutReason(
|
|
144
|
-
"Go To shortcuts are not supported in the mounted editor yet.",
|
|
145
|
-
),
|
|
146
|
-
};
|
|
125
|
+
return resolveBlockedCapability("goTo");
|
|
147
126
|
}
|
|
148
127
|
|
|
149
128
|
if (isModShiftShortcut(input, key, "e")) {
|
|
150
|
-
return
|
|
151
|
-
kind: "block",
|
|
152
|
-
command: "toggleTrackChanges",
|
|
153
|
-
reason: createUnsupportedShortcutReason(
|
|
154
|
-
"Track changes authoring shortcuts are not supported in the mounted editor.",
|
|
155
|
-
),
|
|
156
|
-
};
|
|
129
|
+
return resolveBlockedCapability("toggleTrackChanges");
|
|
157
130
|
}
|
|
158
131
|
|
|
159
132
|
if (key === "f7" && !input.shiftKey) {
|
|
160
|
-
return
|
|
161
|
-
kind: "block",
|
|
162
|
-
command: "checkSpelling",
|
|
163
|
-
reason: createUnsupportedShortcutReason(
|
|
164
|
-
"Spelling shortcuts are not supported in the mounted editor.",
|
|
165
|
-
),
|
|
166
|
-
};
|
|
133
|
+
return resolveBlockedCapability("checkSpelling");
|
|
167
134
|
}
|
|
168
135
|
|
|
169
136
|
if (key === "f7" && input.shiftKey) {
|
|
170
|
-
return
|
|
171
|
-
kind: "block",
|
|
172
|
-
command: "openThesaurus",
|
|
173
|
-
reason: createUnsupportedShortcutReason(
|
|
174
|
-
"Thesaurus shortcuts are not supported in the mounted editor.",
|
|
175
|
-
),
|
|
176
|
-
};
|
|
137
|
+
return resolveBlockedCapability("openThesaurus");
|
|
177
138
|
}
|
|
178
139
|
|
|
179
140
|
if (key === "f8") {
|
|
180
|
-
return
|
|
181
|
-
kind: "block",
|
|
182
|
-
command: "extendSelection",
|
|
183
|
-
reason: createUnsupportedShortcutReason(
|
|
184
|
-
"Extend-selection shortcuts are not supported in the mounted editor.",
|
|
185
|
-
),
|
|
186
|
-
};
|
|
141
|
+
return resolveBlockedCapability("extendSelection");
|
|
187
142
|
}
|
|
188
143
|
|
|
189
144
|
if (key === "f5" && input.shiftKey) {
|
|
190
|
-
return
|
|
191
|
-
kind: "block",
|
|
192
|
-
command: "lastEdit",
|
|
193
|
-
reason: createUnsupportedShortcutReason(
|
|
194
|
-
"Last-edit shortcuts are not supported in the mounted editor.",
|
|
195
|
-
),
|
|
196
|
-
};
|
|
145
|
+
return resolveBlockedCapability("lastEdit");
|
|
197
146
|
}
|
|
198
147
|
|
|
199
148
|
return { kind: "none" };
|
|
@@ -262,10 +211,27 @@ export function resolveHeadingShortcutStyleId(
|
|
|
262
211
|
return null;
|
|
263
212
|
}
|
|
264
213
|
|
|
265
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Look up a `blocked` capability by id and produce the matching
|
|
216
|
+
* `ShellShortcutResolution`. The capability's `blockReason` becomes
|
|
217
|
+
* the dispatcher's returned reason, so editing the user-facing
|
|
218
|
+
* message in `capabilities.ts` is enough to update every shell-layer
|
|
219
|
+
* block path — no dispatcher change needed. Throws if the id is not
|
|
220
|
+
* registered as a blocked capability; the C.1 anchor-check test
|
|
221
|
+
* guarantees every id this file references is present.
|
|
222
|
+
*/
|
|
223
|
+
function resolveBlockedCapability(id: string): ShellShortcutResolution {
|
|
224
|
+
const cap = CAPABILITY_BY_ID.get(id);
|
|
225
|
+
if (!cap || cap.kind !== "blocked" || !cap.blockReason) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`resolveShellShortcut: capability ${id} is not registered as a blocked entry. ` +
|
|
228
|
+
"See src/runtime/editor-surface/capabilities.ts.",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
266
231
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
232
|
+
kind: "block",
|
|
233
|
+
command: id,
|
|
234
|
+
reason: cap.blockReason as WorkflowBlockedCommandReason,
|
|
269
235
|
};
|
|
270
236
|
}
|
|
271
237
|
|
|
@@ -357,9 +323,3 @@ function isZoomOutShortcut(input: ShortcutKeyInput, key: string): boolean {
|
|
|
357
323
|
(key === "-" || key === "_" || key === "subtract");
|
|
358
324
|
}
|
|
359
325
|
|
|
360
|
-
function isPasteTextOnlyShortcut(input: ShortcutKeyInput, key: string): boolean {
|
|
361
|
-
return key === "v" &&
|
|
362
|
-
Boolean(input.ctrlKey || input.metaKey) &&
|
|
363
|
-
!input.altKey &&
|
|
364
|
-
Boolean(input.shiftKey);
|
|
365
|
-
}
|
|
@@ -1,15 +1,75 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
WordReviewEditorChromeOptions,
|
|
5
|
+
WordReviewEditorChromePreset,
|
|
6
|
+
} from "../../api/public-types";
|
|
7
|
+
import type { CollabSession } from "../../runtime/collab-session.ts";
|
|
8
|
+
import type { TransportStatus } from "../../api/awareness-identity-types.ts";
|
|
4
9
|
import {
|
|
5
10
|
TwToolbar,
|
|
6
11
|
type TwToolbarProps,
|
|
7
12
|
} from "../toolbar/tw-toolbar";
|
|
13
|
+
import { CollabTopNavContainer } from "./collab-top-nav-container";
|
|
8
14
|
|
|
9
15
|
export interface ChromePresetToolbarProps extends TwToolbarProps {
|
|
10
16
|
chromePreset: WordReviewEditorChromePreset;
|
|
17
|
+
/** P9g — optional collab session. Rendered above the toolbar when the `"collab"` preset is active. */
|
|
18
|
+
collabSession?: CollabSession;
|
|
19
|
+
collabTransportStatus?: TransportStatus;
|
|
20
|
+
activeCommentId?: string;
|
|
21
|
+
collabActorId?: string;
|
|
22
|
+
collabSendBaseline?: {
|
|
23
|
+
originDocumentId: string;
|
|
24
|
+
originPayloadId: string;
|
|
25
|
+
originContentHash: string;
|
|
26
|
+
payloadXml: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Sub-surface visibility toggles from `resolveChromePresetOptions`.
|
|
30
|
+
* When omitted the container defaults match the `"collab"` preset.
|
|
31
|
+
*/
|
|
32
|
+
chromeOptionsResolved?: Pick<
|
|
33
|
+
WordReviewEditorChromeOptions,
|
|
34
|
+
| "showCollabTopNav"
|
|
35
|
+
| "showCollabPresenceStrip"
|
|
36
|
+
| "showCollabRoleChip"
|
|
37
|
+
| "showCollabAudienceChip"
|
|
38
|
+
| "showCollabTamperBanner"
|
|
39
|
+
| "showCollabNegotiationActionBar"
|
|
40
|
+
| "showCollabSendToSupplier"
|
|
41
|
+
>;
|
|
11
42
|
}
|
|
12
43
|
|
|
13
44
|
export function ChromePresetToolbar(props: ChromePresetToolbarProps) {
|
|
14
|
-
|
|
45
|
+
const {
|
|
46
|
+
collabSession,
|
|
47
|
+
collabTransportStatus,
|
|
48
|
+
activeCommentId,
|
|
49
|
+
collabActorId,
|
|
50
|
+
collabSendBaseline,
|
|
51
|
+
chromeOptionsResolved,
|
|
52
|
+
...toolbarProps
|
|
53
|
+
} = props;
|
|
54
|
+
|
|
55
|
+
const collabActive =
|
|
56
|
+
props.chromePreset === "collab" && collabSession !== undefined;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
{collabActive ? (
|
|
61
|
+
<CollabTopNavContainer
|
|
62
|
+
session={collabSession}
|
|
63
|
+
{...(activeCommentId !== undefined ? { activeCommentId } : {})}
|
|
64
|
+
actorId={collabActorId ?? "local"}
|
|
65
|
+
{...(collabTransportStatus
|
|
66
|
+
? { transportStatus: collabTransportStatus }
|
|
67
|
+
: {})}
|
|
68
|
+
{...(chromeOptionsResolved ? { visibility: chromeOptionsResolved } : {})}
|
|
69
|
+
{...(collabSendBaseline ? { sendBaseline: collabSendBaseline } : {})}
|
|
70
|
+
/>
|
|
71
|
+
) : null}
|
|
72
|
+
<TwToolbar {...toolbarProps} preset={props.chromePreset} />
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
15
75
|
}
|