@beyondwork/docx-react-component 1.0.19 → 1.0.21
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 +44 -25
- package/src/api/public-types.ts +336 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +14 -2
- package/src/core/search/search-text.ts +28 -0
- package/src/core/state/editor-state.ts +3 -0
- package/src/index.ts +21 -0
- package/src/io/docx-session.ts +363 -17
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +83 -3
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +82 -8
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +240 -2
- package/src/io/ooxml/parse-headers-footers.ts +431 -7
- package/src/io/ooxml/parse-inline-media.ts +15 -1
- package/src/io/ooxml/parse-main-document.ts +396 -14
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +117 -1
- package/src/model/snapshot.ts +85 -1
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-navigation.ts +52 -13
- package/src/runtime/document-runtime.ts +1521 -75
- package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
- package/src/runtime/session-capabilities.ts +33 -3
- package/src/runtime/surface-projection.ts +86 -25
- package/src/runtime/table-schema.ts +2 -2
- package/src/runtime/view-state.ts +24 -6
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +915 -1314
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1448 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +55 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +27 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +237 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +55 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +190 -48
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +130 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
- package/src/validation/compatibility-engine.ts +27 -4
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/docx-comment-proof.ts +220 -0
|
@@ -7,23 +7,27 @@ import React, {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
useRef,
|
|
9
9
|
useState,
|
|
10
|
-
useSyncExternalStore,
|
|
11
10
|
} from "react";
|
|
12
11
|
|
|
13
12
|
import type {
|
|
14
13
|
AutosaveState,
|
|
15
14
|
CompatibilityReport,
|
|
15
|
+
DocumentNavigationSnapshot,
|
|
16
|
+
EditorAnchorProjection,
|
|
16
17
|
EditorDatastoreAdapter,
|
|
17
18
|
EditorHostAdapter,
|
|
18
19
|
EditorSessionState,
|
|
19
20
|
ExportResult,
|
|
20
21
|
EditorError,
|
|
21
22
|
EditorStoryTarget,
|
|
23
|
+
EditorViewStateSnapshot,
|
|
22
24
|
EditorWarning,
|
|
23
25
|
ExportDocxOptions,
|
|
26
|
+
FieldSnapshot,
|
|
24
27
|
FormattingAlignment,
|
|
25
28
|
FormattingStateSnapshot,
|
|
26
29
|
HeaderFooterLinkPatch,
|
|
30
|
+
InteractionGuardSnapshot,
|
|
27
31
|
InsertImageOptions,
|
|
28
32
|
InsertTableOptions,
|
|
29
33
|
PageLayoutSnapshot,
|
|
@@ -37,14 +41,22 @@ import type {
|
|
|
37
41
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
38
42
|
StyleCatalogSnapshot,
|
|
39
43
|
SurfaceBlockSnapshot,
|
|
44
|
+
TocRefreshResult,
|
|
45
|
+
UpdateFieldsResult,
|
|
40
46
|
ViewMode as EditorViewMode,
|
|
47
|
+
WorkflowBlockedCommandReason,
|
|
48
|
+
WorkflowMarkupSnapshot,
|
|
49
|
+
WorkflowScopeSnapshot,
|
|
41
50
|
WordReviewEditorEvent,
|
|
42
51
|
WordReviewEditorProps,
|
|
43
52
|
WordReviewEditorRef,
|
|
44
53
|
WorkspaceMode,
|
|
45
54
|
ZoomLevel,
|
|
46
55
|
} from "../api/public-types";
|
|
47
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
editorSessionStateFromPersistedSnapshot,
|
|
58
|
+
persistedSnapshotFromEditorSessionState,
|
|
59
|
+
} from "../api/session-state.ts";
|
|
48
60
|
import {
|
|
49
61
|
createDetachedAnchor,
|
|
50
62
|
createNodeAnchor,
|
|
@@ -98,81 +110,67 @@ import {
|
|
|
98
110
|
insertTable as insertTableInDocument,
|
|
99
111
|
splitParagraph as splitParagraphInDocument,
|
|
100
112
|
} from "../core/commands/text-commands.ts";
|
|
101
|
-
import {
|
|
102
|
-
createCanonicalDocumentId,
|
|
103
|
-
type SelectionSnapshot as InternalSelectionSnapshot,
|
|
104
|
-
} from "../core/state/editor-state.ts";
|
|
105
|
-
import {
|
|
106
|
-
createDocumentRuntime,
|
|
107
|
-
type DocumentRuntime,
|
|
108
|
-
} from "../runtime/document-runtime.ts";
|
|
113
|
+
import { type SelectionSnapshot as InternalSelectionSnapshot } from "../core/state/editor-state.ts";
|
|
109
114
|
import {
|
|
110
115
|
getStoryBlocks,
|
|
111
116
|
replaceStoryBlocks,
|
|
112
117
|
} from "../runtime/story-targeting.ts";
|
|
113
|
-
import { loadDocxEditorSession } from "../io/docx-session.ts";
|
|
114
118
|
import {
|
|
115
119
|
decodePersistedSourcePackageBytes,
|
|
116
120
|
hasValidPersistedSourcePackageDigest,
|
|
117
121
|
} from "../io/source-package-provenance.ts";
|
|
122
|
+
import { readOpcPackage } from "../io/opc/package-reader.ts";
|
|
118
123
|
import { deriveCapabilities } from "../runtime/session-capabilities";
|
|
119
124
|
import { searchDocument } from "../runtime/document-search.ts";
|
|
120
125
|
import {
|
|
121
|
-
|
|
126
|
+
createEditorViewStateSnapshot,
|
|
127
|
+
createViewState,
|
|
128
|
+
} from "../runtime/view-state.ts";
|
|
129
|
+
import {
|
|
122
130
|
type TwProseMirrorSurfaceRef,
|
|
123
131
|
} from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
|
|
124
|
-
import {
|
|
132
|
+
import type { MediaPreviewDescriptor } from "../ui-tailwind/editor-surface/pm-state-from-snapshot";
|
|
133
|
+
import {
|
|
134
|
+
incrementInvalidationCounter,
|
|
135
|
+
recordPerfSample,
|
|
136
|
+
} from "../ui-tailwind/editor-surface/perf-probe.ts";
|
|
125
137
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail";
|
|
138
|
+
import {
|
|
139
|
+
selectMetaSlice,
|
|
140
|
+
selectReviewSlice,
|
|
141
|
+
selectStatusSlice,
|
|
142
|
+
selectSurfaceSlice,
|
|
143
|
+
selectToolbarSlice,
|
|
144
|
+
selectViewSlice,
|
|
145
|
+
shallowEqualRecord,
|
|
146
|
+
useRuntimeSnapshotSlice,
|
|
147
|
+
useRuntimeValue,
|
|
148
|
+
} from "./runtime-snapshot-selectors.ts";
|
|
126
149
|
import type { MarkupDisplay } from "./headless/comment-decoration-model";
|
|
127
150
|
import type {
|
|
128
151
|
SelectionToolbarAnchor,
|
|
129
152
|
SelectionToolbarModel,
|
|
130
153
|
} from "./headless/selection-toolbar-model";
|
|
154
|
+
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
155
|
+
import { deriveVisibleWorkflowBlockedRails } from "./workflow-surface-blocked-rails.ts";
|
|
156
|
+
import {
|
|
157
|
+
type WordReviewEditorRuntime,
|
|
158
|
+
persistAndExport as persistAndExportFromBoundary,
|
|
159
|
+
persistSession as persistSessionFromBoundary,
|
|
160
|
+
rejectExportWhileLoading as rejectExportWhileLoadingFromBoundary,
|
|
161
|
+
useEditorRuntimeBoundary,
|
|
162
|
+
} from "./editor-runtime-boundary.ts";
|
|
131
163
|
import {
|
|
132
164
|
downloadExportResult,
|
|
133
165
|
withExportDelivery,
|
|
134
166
|
} from "./browser-export";
|
|
167
|
+
import { EditorShellView } from "./editor-shell-view.tsx";
|
|
168
|
+
import { EditorSurfaceController } from "./editor-surface-controller.tsx";
|
|
135
169
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
initialSessionState?: EditorSessionState;
|
|
141
|
-
initialSnapshot?: PersistedEditorSnapshot;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
interface CreateRuntimeArgs {
|
|
145
|
-
documentId: string;
|
|
146
|
-
readOnly: boolean;
|
|
147
|
-
source: ResolvedSource;
|
|
148
|
-
initialViewState?: {
|
|
149
|
-
workspaceMode?: WorkspaceMode;
|
|
150
|
-
zoomLevel?: ZoomLevel;
|
|
151
|
-
};
|
|
152
|
-
hostAdapter?: EditorHostAdapter;
|
|
153
|
-
datastore?: EditorDatastoreAdapter;
|
|
154
|
-
currentUserId?: string;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
interface RuntimeLifecycleHandlers {
|
|
158
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
159
|
-
onWarning?: (warning: EditorWarning) => void;
|
|
160
|
-
onError?: (error: EditorError) => void;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
interface WordReviewEditorRuntime extends DocumentRuntime {
|
|
164
|
-
getFatalError?(): EditorError | undefined;
|
|
165
|
-
dispose?(): void;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
type PackageBackedDocxSession = ReturnType<typeof loadDocxEditorSession>;
|
|
169
|
-
|
|
170
|
-
interface SnapshotExportBarrier {
|
|
171
|
-
reason:
|
|
172
|
-
| "missing_source_package_provenance"
|
|
173
|
-
| "invalid_source_package_provenance";
|
|
174
|
-
message: string;
|
|
175
|
-
}
|
|
170
|
+
export {
|
|
171
|
+
__createFallbackRuntime,
|
|
172
|
+
__resolveWordReviewEditorSource,
|
|
173
|
+
} from "./editor-runtime-boundary.ts";
|
|
176
174
|
|
|
177
175
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
178
176
|
position: "absolute",
|
|
@@ -186,6 +184,15 @@ const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
|
186
184
|
border: 0,
|
|
187
185
|
};
|
|
188
186
|
|
|
187
|
+
const BROWSER_SAFE_PREVIEW_TYPES = new Set([
|
|
188
|
+
"image/png",
|
|
189
|
+
"image/jpeg",
|
|
190
|
+
"image/jpg",
|
|
191
|
+
"image/gif",
|
|
192
|
+
"image/webp",
|
|
193
|
+
"image/bmp",
|
|
194
|
+
]);
|
|
195
|
+
|
|
189
196
|
const ACCESSIBLE_REGION_ORDER = [
|
|
190
197
|
"toolbar",
|
|
191
198
|
"document",
|
|
@@ -392,13 +399,28 @@ export function __createWordReviewEditorRefBridge(
|
|
|
392
399
|
runtime.closeStory();
|
|
393
400
|
},
|
|
394
401
|
getPageLayoutSnapshot: () => {
|
|
395
|
-
return runtime.getPageLayoutSnapshot();
|
|
402
|
+
return clonePublicValue(runtime.getPageLayoutSnapshot());
|
|
396
403
|
},
|
|
397
404
|
getDocumentNavigationSnapshot: () => {
|
|
398
|
-
return runtime.getDocumentNavigationSnapshot();
|
|
405
|
+
return clonePublicValue(runtime.getDocumentNavigationSnapshot());
|
|
406
|
+
},
|
|
407
|
+
getFieldSnapshot: () => {
|
|
408
|
+
return clonePublicValue(runtime.getFieldSnapshot());
|
|
409
|
+
},
|
|
410
|
+
updateFields: (options) => {
|
|
411
|
+
return runtime.updateFields(options);
|
|
412
|
+
},
|
|
413
|
+
updateTableOfContents: (options) => {
|
|
414
|
+
return runtime.updateTableOfContents(options);
|
|
399
415
|
},
|
|
400
416
|
getViewState: () => {
|
|
401
|
-
return runtime.getViewState();
|
|
417
|
+
return clonePublicValue(runtime.getViewState());
|
|
418
|
+
},
|
|
419
|
+
setDocumentMode: (mode) => {
|
|
420
|
+
runtime.setDocumentMode(mode);
|
|
421
|
+
},
|
|
422
|
+
getProtectionSnapshot: () => {
|
|
423
|
+
return clonePublicValue(runtime.getProtectionSnapshot());
|
|
402
424
|
},
|
|
403
425
|
setWorkspaceMode: (mode) => {
|
|
404
426
|
runtime.setWorkspaceMode(mode);
|
|
@@ -427,195 +449,28 @@ export function __createWordReviewEditorRefBridge(
|
|
|
427
449
|
setImageFrame: (mediaId, offsets) => {
|
|
428
450
|
applyRuntimeImageReposition(runtime, mediaId, offsets);
|
|
429
451
|
},
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
export async function __resolveWordReviewEditorSource(
|
|
434
|
-
props: Pick<
|
|
435
|
-
WordReviewEditorProps,
|
|
436
|
-
| "documentId"
|
|
437
|
-
| "hostAdapter"
|
|
438
|
-
| "datastore"
|
|
439
|
-
| "externalDocSource"
|
|
440
|
-
| "initialDocx"
|
|
441
|
-
| "initialSessionState"
|
|
442
|
-
| "initialSnapshot"
|
|
443
|
-
| "initialSourceLabel"
|
|
444
|
-
| "loadRevision"
|
|
445
|
-
| "loadSourcePolicy"
|
|
446
|
-
>,
|
|
447
|
-
): Promise<ResolvedSource> {
|
|
448
|
-
const explicitInitialCount =
|
|
449
|
-
Number(Boolean(props.initialDocx)) +
|
|
450
|
-
Number(Boolean(props.initialSessionState)) +
|
|
451
|
-
Number(Boolean(props.initialSnapshot));
|
|
452
|
-
if (explicitInitialCount > 1) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
"Provide exactly one of initialDocx, initialSessionState, or initialSnapshot.",
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (props.externalDocSource) {
|
|
459
|
-
if (props.externalDocSource.kind === "docx") {
|
|
460
|
-
return {
|
|
461
|
-
source: "docx",
|
|
462
|
-
initialDocx: props.externalDocSource.bytes,
|
|
463
|
-
sourceLabel: props.externalDocSource.sourceLabel,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (props.externalDocSource.kind === "session") {
|
|
468
|
-
return {
|
|
469
|
-
source: "session",
|
|
470
|
-
initialSessionState: props.externalDocSource.sessionState,
|
|
471
|
-
sourceLabel:
|
|
472
|
-
props.externalDocSource.sourceLabel ??
|
|
473
|
-
props.externalDocSource.sessionState.sourcePackage?.sourceLabel,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return {
|
|
478
|
-
source: "snapshot",
|
|
479
|
-
initialSnapshot: props.externalDocSource.snapshot,
|
|
480
|
-
sourceLabel: props.externalDocSource.sourceLabel,
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (props.initialSessionState) {
|
|
485
|
-
return {
|
|
486
|
-
source: "session",
|
|
487
|
-
initialSessionState: props.initialSessionState,
|
|
488
|
-
sourceLabel:
|
|
489
|
-
props.initialSourceLabel ??
|
|
490
|
-
props.initialSessionState.sourcePackage?.sourceLabel,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (props.initialSnapshot) {
|
|
495
|
-
return {
|
|
496
|
-
source: "snapshot",
|
|
497
|
-
initialSnapshot: props.initialSnapshot,
|
|
498
|
-
sourceLabel: props.initialSourceLabel,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (props.initialDocx) {
|
|
503
|
-
return {
|
|
504
|
-
source: "docx",
|
|
505
|
-
initialDocx: props.initialDocx,
|
|
506
|
-
sourceLabel: props.initialSourceLabel,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const loader = props.hostAdapter?.load ?? props.datastore?.load;
|
|
511
|
-
if (!loader) {
|
|
512
|
-
throw new Error(
|
|
513
|
-
`WordReviewEditor ${props.documentId} needs initialDocx, initialSessionState, initialSnapshot, or a host/datastore load source.`,
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const loadResult = await loader({
|
|
518
|
-
documentId: props.documentId,
|
|
519
|
-
loadRevision: props.loadRevision,
|
|
520
|
-
loadSourcePolicy: props.loadSourcePolicy,
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
if (!loadResult.source) {
|
|
524
|
-
throw new Error(
|
|
525
|
-
`Host or datastore loader did not return a loadable source for ${props.documentId}.`,
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (loadResult.source.kind === "docx") {
|
|
530
|
-
return {
|
|
531
|
-
source: "docx",
|
|
532
|
-
initialDocx: loadResult.source.bytes,
|
|
533
|
-
sourceLabel: loadResult.source.sourceLabel,
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (loadResult.source.kind === "session") {
|
|
538
|
-
return {
|
|
539
|
-
source: "session",
|
|
540
|
-
initialSessionState: loadResult.source.sessionState,
|
|
541
|
-
sourceLabel:
|
|
542
|
-
loadResult.source.sourceLabel ??
|
|
543
|
-
loadResult.source.sessionState.sourcePackage?.sourceLabel,
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return {
|
|
548
|
-
source: "snapshot",
|
|
549
|
-
initialSnapshot: loadResult.source.snapshot,
|
|
550
|
-
sourceLabel: loadResult.source.sourceLabel,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
export function __createFallbackRuntime(args: CreateRuntimeArgs): WordReviewEditorRuntime {
|
|
555
|
-
return createRuntime(args);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function createRuntime(
|
|
559
|
-
args: CreateRuntimeArgs,
|
|
560
|
-
handlers: RuntimeLifecycleHandlers = {},
|
|
561
|
-
): WordReviewEditorRuntime {
|
|
562
|
-
const docxSession = args.source.initialDocx
|
|
563
|
-
? loadDocxEditorSession({
|
|
564
|
-
documentId: args.documentId,
|
|
565
|
-
sourceLabel: args.source.sourceLabel,
|
|
566
|
-
bytes: args.source.initialDocx,
|
|
567
|
-
editorBuild: "dev",
|
|
568
|
-
})
|
|
569
|
-
: undefined;
|
|
570
|
-
const snapshotExportResolution = !args.source.initialDocx
|
|
571
|
-
? resolvePackageBackedExportSession(args)
|
|
572
|
-
: undefined;
|
|
573
|
-
const initialSessionState =
|
|
574
|
-
args.source.initialSessionState ??
|
|
575
|
-
docxSession?.initialSessionState ??
|
|
576
|
-
(args.source.initialSnapshot
|
|
577
|
-
? editorSessionStateFromPersistedSnapshot(args.source.initialSnapshot)
|
|
578
|
-
: editorSessionStateFromPersistedSnapshot(
|
|
579
|
-
createFallbackPersistedSnapshot(
|
|
580
|
-
args.documentId,
|
|
581
|
-
args.source.sourceLabel ?? "Generated shell snapshot",
|
|
582
|
-
),
|
|
583
|
-
));
|
|
584
|
-
const runtimeSessionState = snapshotExportResolution?.barrier
|
|
585
|
-
? applySessionExportBarrier(initialSessionState, snapshotExportResolution.barrier)
|
|
586
|
-
: initialSessionState;
|
|
587
|
-
|
|
588
|
-
return createDocumentRuntime({
|
|
589
|
-
documentId: args.documentId,
|
|
590
|
-
initialSessionState: runtimeSessionState,
|
|
591
|
-
sourceKind: args.source.source,
|
|
592
|
-
sourceLabel: args.source.sourceLabel,
|
|
593
|
-
initialViewState: args.initialViewState,
|
|
594
|
-
readOnly: args.readOnly || docxSession?.readOnly,
|
|
595
|
-
editorBuild: runtimeSessionState.editorBuild,
|
|
596
|
-
fatalError: docxSession?.fatalError,
|
|
597
|
-
exportDocx: async (sessionState, options) => {
|
|
598
|
-
if (docxSession) {
|
|
599
|
-
return docxSession.exportDocx(sessionState, options);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (snapshotExportResolution?.session) {
|
|
603
|
-
return snapshotExportResolution.session.exportDocx(sessionState, options);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
throw createSnapshotExportBlockedError(
|
|
607
|
-
args.documentId,
|
|
608
|
-
snapshotExportResolution?.barrier ?? {
|
|
609
|
-
reason: "missing_source_package_provenance",
|
|
610
|
-
message:
|
|
611
|
-
"DOCX export is blocked because this session does not carry embedded source package provenance.",
|
|
612
|
-
},
|
|
613
|
-
);
|
|
452
|
+
setWorkflowOverlay: (overlay) => {
|
|
453
|
+
runtime.setWorkflowOverlay(clonePublicValue(overlay));
|
|
614
454
|
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
455
|
+
clearWorkflowOverlay: () => {
|
|
456
|
+
runtime.clearWorkflowOverlay();
|
|
457
|
+
},
|
|
458
|
+
getWorkflowScopeSnapshot: () => {
|
|
459
|
+
return clonePublicValue(runtime.getWorkflowScopeSnapshot());
|
|
460
|
+
},
|
|
461
|
+
getInteractionGuardSnapshot: () => {
|
|
462
|
+
return clonePublicValue(runtime.getInteractionGuardSnapshot());
|
|
463
|
+
},
|
|
464
|
+
getWorkflowMarkupSnapshot: () => {
|
|
465
|
+
return clonePublicValue(runtime.getWorkflowMarkupSnapshot());
|
|
466
|
+
},
|
|
467
|
+
getWorkflowCandidateRanges: (options) => {
|
|
468
|
+
return clonePublicValue(runtime.getWorkflowCandidateRanges(options));
|
|
469
|
+
},
|
|
470
|
+
replaceWorkflowMarkupText: (markupId, text) => {
|
|
471
|
+
runtime.replaceWorkflowMarkupText(markupId, text);
|
|
472
|
+
},
|
|
473
|
+
};
|
|
619
474
|
}
|
|
620
475
|
|
|
621
476
|
export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditorProps>(
|
|
@@ -633,7 +488,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
633
488
|
initialSessionState,
|
|
634
489
|
initialSnapshot,
|
|
635
490
|
initialSourceLabel,
|
|
636
|
-
markupDisplay
|
|
491
|
+
markupDisplay,
|
|
637
492
|
onError,
|
|
638
493
|
onEvent,
|
|
639
494
|
onWarning,
|
|
@@ -642,261 +497,169 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
642
497
|
showReviewPanel = true,
|
|
643
498
|
} = props;
|
|
644
499
|
|
|
645
|
-
const [runtime, setRuntime] = useState<WordReviewEditorRuntime | null>(null);
|
|
646
|
-
const [loadError, setLoadError] = useState<EditorError | null>(null);
|
|
647
500
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
648
501
|
const [showTrackedChanges, setShowTrackedChanges] = useState(false);
|
|
649
502
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
650
503
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
651
504
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
652
505
|
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
653
|
-
const runtimeRef = useRef<WordReviewEditorRuntime | null>(null);
|
|
654
506
|
const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
|
|
655
507
|
const selectionToolbarElementRef = useRef<HTMLDivElement | null>(null);
|
|
656
508
|
const shellRef = useRef<HTMLDivElement | null>(null);
|
|
657
509
|
const lastSelectionToolbarKeyRef = useRef<string | null>(null);
|
|
658
510
|
const lastAnnouncedErrorIdRef = useRef<string | null>(null);
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (!initialSourceRef.current || initialSourceRef.current.documentId !== documentId) {
|
|
682
|
-
initialSourceRef.current = {
|
|
683
|
-
documentId,
|
|
684
|
-
initialDocx,
|
|
685
|
-
initialSessionState,
|
|
686
|
-
initialSnapshot,
|
|
687
|
-
initialSourceLabel,
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
const stableInitialSource = initialSourceRef.current;
|
|
692
|
-
const sourceReloadKey = externalDocSource
|
|
693
|
-
? `external:${externalDocSource.kind}:${externalDocumentRevision ?? "static"}`
|
|
694
|
-
: stableInitialSource?.initialSessionState
|
|
695
|
-
? "initial-session"
|
|
696
|
-
: stableInitialSource?.initialSnapshot
|
|
697
|
-
? "initial-snapshot"
|
|
698
|
-
: stableInitialSource?.initialDocx
|
|
699
|
-
? "initial-docx"
|
|
700
|
-
: hostAdapter
|
|
701
|
-
? `host-adapter:${loadRevision ?? "static"}`
|
|
702
|
-
: `datastore:${loadRevision ?? "static"}`;
|
|
703
|
-
|
|
704
|
-
useEffect(() => {
|
|
705
|
-
hostAdapterRef.current = hostAdapter;
|
|
706
|
-
datastoreRef.current = datastore;
|
|
707
|
-
onEventRef.current = onEvent;
|
|
708
|
-
onWarningRef.current = onWarning;
|
|
709
|
-
onErrorRef.current = onError;
|
|
710
|
-
}, [datastore, hostAdapter, onError, onEvent, onWarning]);
|
|
711
|
-
|
|
712
|
-
useEffect(() => {
|
|
713
|
-
let cancelled = false;
|
|
714
|
-
|
|
715
|
-
async function loadRuntime(): Promise<void> {
|
|
716
|
-
setLoadError(null);
|
|
717
|
-
|
|
718
|
-
try {
|
|
719
|
-
const source = await __resolveWordReviewEditorSource({
|
|
720
|
-
documentId,
|
|
721
|
-
hostAdapter: hostAdapterRef.current,
|
|
722
|
-
datastore: datastoreRef.current,
|
|
723
|
-
externalDocSource,
|
|
724
|
-
initialDocx: stableInitialSource?.initialDocx,
|
|
725
|
-
initialSessionState: stableInitialSource?.initialSessionState,
|
|
726
|
-
initialSnapshot: stableInitialSource?.initialSnapshot,
|
|
727
|
-
initialSourceLabel: stableInitialSource?.initialSourceLabel,
|
|
728
|
-
loadRevision,
|
|
729
|
-
loadSourcePolicy,
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
if (cancelled) {
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
runtimeRef.current?.dispose?.();
|
|
737
|
-
const nextRuntime = createRuntime(
|
|
738
|
-
{
|
|
739
|
-
documentId,
|
|
740
|
-
readOnly,
|
|
741
|
-
source,
|
|
742
|
-
initialViewState: runtimeViewStateSeedRef.current,
|
|
743
|
-
hostAdapter: hostAdapterRef.current,
|
|
744
|
-
datastore: datastoreRef.current,
|
|
745
|
-
currentUserId: currentUser.userId,
|
|
746
|
-
},
|
|
747
|
-
{
|
|
748
|
-
onWarning: onWarningRef.current,
|
|
749
|
-
onError: onErrorRef.current,
|
|
750
|
-
},
|
|
751
|
-
);
|
|
752
|
-
emitEditorEvent({
|
|
753
|
-
hostAdapter: hostAdapterRef.current,
|
|
754
|
-
datastore: datastoreRef.current,
|
|
755
|
-
onEvent: onEventRef.current,
|
|
756
|
-
event: createReadyEvent(nextRuntime, source.source),
|
|
757
|
-
});
|
|
758
|
-
runtimeRef.current = nextRuntime;
|
|
759
|
-
setRuntime(nextRuntime);
|
|
760
|
-
} catch (error) {
|
|
761
|
-
if (cancelled) {
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
const normalized = normalizeEditorError(error);
|
|
766
|
-
setLoadError(normalized);
|
|
767
|
-
onErrorRef.current?.(normalized);
|
|
768
|
-
emitEditorEvent({
|
|
769
|
-
hostAdapter: hostAdapterRef.current,
|
|
770
|
-
datastore: datastoreRef.current,
|
|
771
|
-
onEvent: onEventRef.current,
|
|
772
|
-
event: {
|
|
773
|
-
type: "error",
|
|
774
|
-
documentId,
|
|
775
|
-
error: normalized,
|
|
776
|
-
},
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
void loadRuntime();
|
|
782
|
-
|
|
783
|
-
return () => {
|
|
784
|
-
cancelled = true;
|
|
785
|
-
};
|
|
786
|
-
}, [
|
|
787
|
-
documentId,
|
|
788
|
-
readOnly,
|
|
789
|
-
sourceReloadKey,
|
|
790
|
-
]);
|
|
791
|
-
|
|
792
|
-
useEffect(() => {
|
|
793
|
-
if (!runtime?.subscribeToEvents) {
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return runtime.subscribeToEvents((event) => {
|
|
798
|
-
if (event.type === "export_completed" || event.type === "ready") {
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
emitEditorEvent({
|
|
802
|
-
hostAdapter: hostAdapterRef.current,
|
|
803
|
-
datastore: datastoreRef.current,
|
|
804
|
-
onEvent: onEventRef.current,
|
|
805
|
-
event,
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
}, [runtime]);
|
|
809
|
-
|
|
810
|
-
useEffect(() => {
|
|
811
|
-
return () => {
|
|
812
|
-
if (autosaveTimerRef.current) {
|
|
813
|
-
clearTimeout(autosaveTimerRef.current);
|
|
814
|
-
autosaveTimerRef.current = null;
|
|
815
|
-
}
|
|
816
|
-
runtimeRef.current?.dispose?.();
|
|
817
|
-
runtimeRef.current = null;
|
|
818
|
-
};
|
|
819
|
-
}, []);
|
|
820
|
-
|
|
821
|
-
const optimisticRuntime = useMemo(
|
|
822
|
-
() =>
|
|
823
|
-
__createFallbackRuntime({
|
|
824
|
-
documentId,
|
|
825
|
-
readOnly,
|
|
826
|
-
currentUserId: currentUser.userId,
|
|
827
|
-
initialViewState: runtimeViewStateSeedRef.current,
|
|
828
|
-
source: {
|
|
829
|
-
source:
|
|
830
|
-
initialSessionState
|
|
831
|
-
? "session"
|
|
832
|
-
: "snapshot",
|
|
833
|
-
initialSessionState:
|
|
834
|
-
initialSessionState ??
|
|
835
|
-
(initialSnapshot
|
|
836
|
-
? editorSessionStateFromPersistedSnapshot(initialSnapshot)
|
|
837
|
-
: undefined),
|
|
838
|
-
initialSnapshot:
|
|
839
|
-
initialSnapshot ?? createFallbackPersistedSnapshot(documentId, initialSourceLabel),
|
|
840
|
-
sourceLabel: guessSourceLabel(
|
|
841
|
-
initialSourceLabel,
|
|
842
|
-
initialSessionState,
|
|
843
|
-
initialSnapshot,
|
|
844
|
-
externalDocSource,
|
|
845
|
-
),
|
|
846
|
-
},
|
|
847
|
-
hostAdapter: hostAdapterRef.current,
|
|
848
|
-
datastore: datastoreRef.current,
|
|
849
|
-
}),
|
|
850
|
-
[
|
|
851
|
-
currentUser.userId,
|
|
852
|
-
documentId,
|
|
853
|
-
initialSessionState,
|
|
854
|
-
initialSnapshot,
|
|
855
|
-
initialSourceLabel,
|
|
856
|
-
readOnly,
|
|
857
|
-
hostAdapter,
|
|
858
|
-
externalDocSource?.kind,
|
|
859
|
-
externalDocSource?.sourceLabel,
|
|
860
|
-
],
|
|
511
|
+
const {
|
|
512
|
+
runtime,
|
|
513
|
+
loadError,
|
|
514
|
+
activeRuntime,
|
|
515
|
+
fallbackSnapshot,
|
|
516
|
+
loadingSessionState,
|
|
517
|
+
loadingViewState,
|
|
518
|
+
loadingNavigation,
|
|
519
|
+
hostAdapterRef,
|
|
520
|
+
datastoreRef,
|
|
521
|
+
onEventRef,
|
|
522
|
+
onWarningRef,
|
|
523
|
+
onErrorRef,
|
|
524
|
+
autosaveTimerRef,
|
|
525
|
+
lastSavedRevisionTokenRef,
|
|
526
|
+
runtimeViewStateSeedRef,
|
|
527
|
+
} = useEditorRuntimeBoundary(props);
|
|
528
|
+
const metaSlice = useRuntimeSnapshotSlice(
|
|
529
|
+
runtime,
|
|
530
|
+
fallbackSnapshot,
|
|
531
|
+
selectMetaSlice,
|
|
532
|
+
shallowEqualRecord,
|
|
861
533
|
);
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
534
|
+
const toolbarSlice = useRuntimeSnapshotSlice(
|
|
535
|
+
runtime,
|
|
536
|
+
fallbackSnapshot,
|
|
537
|
+
selectToolbarSlice,
|
|
538
|
+
shallowEqualRecord,
|
|
539
|
+
);
|
|
540
|
+
const surfaceSlice = useRuntimeSnapshotSlice(
|
|
541
|
+
runtime,
|
|
542
|
+
fallbackSnapshot,
|
|
543
|
+
selectSurfaceSlice,
|
|
544
|
+
shallowEqualRecord,
|
|
545
|
+
);
|
|
546
|
+
const reviewSlice = useRuntimeSnapshotSlice(
|
|
547
|
+
runtime,
|
|
548
|
+
fallbackSnapshot,
|
|
549
|
+
selectReviewSlice,
|
|
550
|
+
shallowEqualRecord,
|
|
551
|
+
);
|
|
552
|
+
const viewSlice = useRuntimeSnapshotSlice(
|
|
553
|
+
runtime,
|
|
554
|
+
fallbackSnapshot,
|
|
555
|
+
selectViewSlice,
|
|
556
|
+
shallowEqualRecord,
|
|
557
|
+
);
|
|
558
|
+
const statusSlice = useRuntimeSnapshotSlice(
|
|
559
|
+
runtime,
|
|
560
|
+
fallbackSnapshot,
|
|
561
|
+
selectStatusSlice,
|
|
562
|
+
shallowEqualRecord,
|
|
563
|
+
);
|
|
564
|
+
const snapshot = useMemo(
|
|
565
|
+
() => ({
|
|
566
|
+
documentId: metaSlice.documentId,
|
|
567
|
+
sessionId: metaSlice.sessionId,
|
|
568
|
+
sourceLabel: metaSlice.sourceLabel,
|
|
569
|
+
revisionToken: surfaceSlice.revisionToken,
|
|
570
|
+
isReady: toolbarSlice.isReady,
|
|
571
|
+
isDirty: statusSlice.isDirty,
|
|
572
|
+
readOnly: toolbarSlice.readOnly,
|
|
573
|
+
documentMode: viewSlice.documentMode,
|
|
574
|
+
selection: surfaceSlice.selection,
|
|
575
|
+
activeStory: viewSlice.activeStory,
|
|
576
|
+
pageLayout: viewSlice.pageLayout,
|
|
577
|
+
documentStats: statusSlice.documentStats,
|
|
578
|
+
comments: reviewSlice.comments,
|
|
579
|
+
trackedChanges: reviewSlice.trackedChanges,
|
|
580
|
+
compatibility: reviewSlice.compatibility,
|
|
581
|
+
warnings: statusSlice.warnings,
|
|
582
|
+
fatalError: statusSlice.fatalError,
|
|
583
|
+
commandState: toolbarSlice.commandState,
|
|
584
|
+
surface: surfaceSlice.surface,
|
|
585
|
+
protectionSnapshot: statusSlice.protectionSnapshot,
|
|
586
|
+
}),
|
|
877
587
|
[
|
|
878
|
-
documentId,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
588
|
+
metaSlice.documentId,
|
|
589
|
+
metaSlice.sessionId,
|
|
590
|
+
metaSlice.sourceLabel,
|
|
591
|
+
surfaceSlice.revisionToken,
|
|
592
|
+
surfaceSlice.selection,
|
|
593
|
+
surfaceSlice.surface,
|
|
594
|
+
toolbarSlice.isReady,
|
|
595
|
+
toolbarSlice.readOnly,
|
|
596
|
+
toolbarSlice.commandState,
|
|
597
|
+
statusSlice.isDirty,
|
|
598
|
+
statusSlice.documentStats,
|
|
599
|
+
statusSlice.warnings,
|
|
600
|
+
statusSlice.fatalError,
|
|
601
|
+
statusSlice.protectionSnapshot,
|
|
602
|
+
reviewSlice.comments,
|
|
603
|
+
reviewSlice.trackedChanges,
|
|
604
|
+
reviewSlice.compatibility,
|
|
605
|
+
viewSlice.documentMode,
|
|
606
|
+
viewSlice.activeStory,
|
|
607
|
+
viewSlice.pageLayout,
|
|
885
608
|
],
|
|
886
609
|
);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
610
|
+
const viewState = useRuntimeValue(
|
|
611
|
+
runtime
|
|
612
|
+
? {
|
|
613
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
614
|
+
getValue: () => runtime.getViewState(),
|
|
615
|
+
}
|
|
616
|
+
: null,
|
|
617
|
+
loadingViewState,
|
|
892
618
|
);
|
|
893
|
-
|
|
894
|
-
const activeRuntime = runtime ?? optimisticRuntime;
|
|
895
|
-
const viewState = activeRuntime.getViewState();
|
|
896
619
|
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
897
|
-
const liveMarkupDisplay
|
|
898
|
-
const
|
|
899
|
-
|
|
620
|
+
const liveMarkupDisplay = __resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
621
|
+
const documentNavigation = useRuntimeValue(
|
|
622
|
+
runtime
|
|
623
|
+
? {
|
|
624
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
625
|
+
getValue: () => runtime.getDocumentNavigationSnapshot(),
|
|
626
|
+
}
|
|
627
|
+
: null,
|
|
628
|
+
loadingNavigation,
|
|
629
|
+
);
|
|
630
|
+
const workflowScopeSnapshot = useRuntimeValue(
|
|
631
|
+
runtime
|
|
632
|
+
? {
|
|
633
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
634
|
+
getValue: () => runtime.getWorkflowScopeSnapshot(),
|
|
635
|
+
}
|
|
636
|
+
: null,
|
|
637
|
+
null,
|
|
638
|
+
workflowScopeSnapshotsEqual,
|
|
639
|
+
);
|
|
640
|
+
const interactionGuardSnapshot = useRuntimeValue(
|
|
641
|
+
runtime
|
|
642
|
+
? {
|
|
643
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
644
|
+
getValue: () => runtime.getInteractionGuardSnapshot(),
|
|
645
|
+
}
|
|
646
|
+
: null,
|
|
647
|
+
{ blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
648
|
+
interactionGuardSnapshotsEqual,
|
|
649
|
+
);
|
|
650
|
+
const workflowMarkupSnapshot = useMemo(
|
|
651
|
+
() => (runtime ? runtime.getWorkflowMarkupSnapshot() : null),
|
|
652
|
+
[runtime, snapshot.revisionToken],
|
|
653
|
+
);
|
|
654
|
+
const workflowBlockedRails = useMemo(
|
|
655
|
+
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
656
|
+
[snapshot.surface, workflowMarkupSnapshot],
|
|
657
|
+
);
|
|
658
|
+
const sessionState = useMemo(
|
|
659
|
+
() => (runtime ? runtime.getSessionState() : loadingSessionState),
|
|
660
|
+
[loadingSessionState, runtime, snapshot.revisionToken],
|
|
661
|
+
);
|
|
662
|
+
const canonicalDocument = sessionState.canonicalDocument;
|
|
900
663
|
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
901
664
|
|
|
902
665
|
useEffect(() => {
|
|
@@ -910,6 +673,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
910
673
|
};
|
|
911
674
|
}, [viewState.workspaceMode, viewState.zoomLevel]);
|
|
912
675
|
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
recordPerfSample("shell.render");
|
|
678
|
+
incrementInvalidationCounter("shell.rerenders");
|
|
679
|
+
}, [snapshot.revisionToken, snapshot.selection, viewState, documentNavigation]);
|
|
680
|
+
|
|
913
681
|
useImperativeHandle(
|
|
914
682
|
ref,
|
|
915
683
|
() => ({
|
|
@@ -939,7 +707,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
939
707
|
rejectAllChanges: () => activeRuntime.rejectAllChanges(),
|
|
940
708
|
exportDocx: (options) =>
|
|
941
709
|
runtime
|
|
942
|
-
?
|
|
710
|
+
? persistAndExportFromBoundary({
|
|
943
711
|
hostAdapter: hostAdapterRef.current,
|
|
944
712
|
datastore: datastoreRef.current,
|
|
945
713
|
documentId,
|
|
@@ -950,7 +718,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
950
718
|
lastSavedRevisionTokenRef,
|
|
951
719
|
autosaveTimerRef,
|
|
952
720
|
})
|
|
953
|
-
:
|
|
721
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
954
722
|
documentId,
|
|
955
723
|
hostAdapter: hostAdapterRef.current,
|
|
956
724
|
datastore: datastoreRef.current,
|
|
@@ -1149,13 +917,28 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1149
917
|
activeRuntime.closeStory();
|
|
1150
918
|
},
|
|
1151
919
|
getPageLayoutSnapshot: () => {
|
|
1152
|
-
return activeRuntime.getPageLayoutSnapshot();
|
|
920
|
+
return clonePublicValue(activeRuntime.getPageLayoutSnapshot());
|
|
1153
921
|
},
|
|
1154
922
|
getDocumentNavigationSnapshot: () => {
|
|
1155
|
-
return activeRuntime.getDocumentNavigationSnapshot();
|
|
923
|
+
return clonePublicValue(activeRuntime.getDocumentNavigationSnapshot());
|
|
924
|
+
},
|
|
925
|
+
getFieldSnapshot: () => {
|
|
926
|
+
return clonePublicValue(activeRuntime.getFieldSnapshot());
|
|
927
|
+
},
|
|
928
|
+
updateFields: (options) => {
|
|
929
|
+
return activeRuntime.updateFields(options);
|
|
930
|
+
},
|
|
931
|
+
updateTableOfContents: (options) => {
|
|
932
|
+
return activeRuntime.updateTableOfContents(options);
|
|
1156
933
|
},
|
|
1157
934
|
getViewState: () => {
|
|
1158
|
-
return activeRuntime.getViewState();
|
|
935
|
+
return clonePublicValue(activeRuntime.getViewState());
|
|
936
|
+
},
|
|
937
|
+
setDocumentMode: (mode) => {
|
|
938
|
+
activeRuntime.setDocumentMode(mode);
|
|
939
|
+
},
|
|
940
|
+
getProtectionSnapshot: () => {
|
|
941
|
+
return clonePublicValue(activeRuntime.getProtectionSnapshot());
|
|
1159
942
|
},
|
|
1160
943
|
setWorkspaceMode: (mode) => {
|
|
1161
944
|
activeRuntime.setWorkspaceMode(mode);
|
|
@@ -1184,6 +967,27 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1184
967
|
setImageFrame: (mediaId, offsets) => {
|
|
1185
968
|
applyRuntimeImageReposition(activeRuntime, mediaId, offsets);
|
|
1186
969
|
},
|
|
970
|
+
setWorkflowOverlay: (overlay) => {
|
|
971
|
+
activeRuntime.setWorkflowOverlay(clonePublicValue(overlay));
|
|
972
|
+
},
|
|
973
|
+
clearWorkflowOverlay: () => {
|
|
974
|
+
activeRuntime.clearWorkflowOverlay();
|
|
975
|
+
},
|
|
976
|
+
getWorkflowScopeSnapshot: () => {
|
|
977
|
+
return clonePublicValue(activeRuntime.getWorkflowScopeSnapshot());
|
|
978
|
+
},
|
|
979
|
+
getInteractionGuardSnapshot: () => {
|
|
980
|
+
return clonePublicValue(activeRuntime.getInteractionGuardSnapshot());
|
|
981
|
+
},
|
|
982
|
+
getWorkflowMarkupSnapshot: () => {
|
|
983
|
+
return clonePublicValue(activeRuntime.getWorkflowMarkupSnapshot());
|
|
984
|
+
},
|
|
985
|
+
getWorkflowCandidateRanges: (options) => {
|
|
986
|
+
return clonePublicValue(activeRuntime.getWorkflowCandidateRanges(options));
|
|
987
|
+
},
|
|
988
|
+
replaceWorkflowMarkupText: (markupId, text) => {
|
|
989
|
+
activeRuntime.replaceWorkflowMarkupText(markupId, text);
|
|
990
|
+
},
|
|
1187
991
|
}),
|
|
1188
992
|
[activeRuntime, currentUser.userId, documentId, runtime],
|
|
1189
993
|
);
|
|
@@ -1213,7 +1017,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1213
1017
|
|
|
1214
1018
|
const debounceMs = props.autosave?.debounceMs ?? 800;
|
|
1215
1019
|
if (debounceMs <= 0) {
|
|
1216
|
-
void
|
|
1020
|
+
void persistSessionFromBoundary({
|
|
1217
1021
|
hostAdapter: hostAdapterRef.current,
|
|
1218
1022
|
datastore: datastoreRef.current,
|
|
1219
1023
|
documentId,
|
|
@@ -1227,7 +1031,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1227
1031
|
}
|
|
1228
1032
|
|
|
1229
1033
|
autosaveTimerRef.current = setTimeout(() => {
|
|
1230
|
-
void
|
|
1034
|
+
void persistSessionFromBoundary({
|
|
1231
1035
|
hostAdapter: hostAdapterRef.current,
|
|
1232
1036
|
datastore: datastoreRef.current,
|
|
1233
1037
|
documentId,
|
|
@@ -1301,7 +1105,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1301
1105
|
|
|
1302
1106
|
function exportCurrentDocument(): void {
|
|
1303
1107
|
void (runtime
|
|
1304
|
-
?
|
|
1108
|
+
? persistAndExportFromBoundary({
|
|
1305
1109
|
hostAdapter: hostAdapterRef.current,
|
|
1306
1110
|
datastore: datastoreRef.current,
|
|
1307
1111
|
documentId,
|
|
@@ -1311,7 +1115,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1311
1115
|
lastSavedRevisionTokenRef,
|
|
1312
1116
|
autosaveTimerRef,
|
|
1313
1117
|
})
|
|
1314
|
-
:
|
|
1118
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
1315
1119
|
documentId,
|
|
1316
1120
|
hostAdapter: hostAdapterRef.current,
|
|
1317
1121
|
datastore: datastoreRef.current,
|
|
@@ -1320,20 +1124,89 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1320
1124
|
}));
|
|
1321
1125
|
}
|
|
1322
1126
|
|
|
1323
|
-
const derivedCapabilities = deriveCapabilities(
|
|
1127
|
+
const derivedCapabilities = deriveCapabilities(
|
|
1128
|
+
snapshot,
|
|
1129
|
+
reviewMode,
|
|
1130
|
+
workflowScopeSnapshot,
|
|
1131
|
+
);
|
|
1324
1132
|
const capabilities = showReviewPanel
|
|
1325
1133
|
? derivedCapabilities
|
|
1326
1134
|
: { ...derivedCapabilities, reviewRailVisible: false };
|
|
1327
1135
|
const formattingState = getFormattingStateFromRenderSnapshot(snapshot);
|
|
1328
1136
|
const styleCatalog = useMemo(
|
|
1329
|
-
() => getRuntimeStyleCatalog(
|
|
1330
|
-
[
|
|
1137
|
+
() => getRuntimeStyleCatalog(canonicalDocument.styles),
|
|
1138
|
+
[canonicalDocument.styles],
|
|
1331
1139
|
);
|
|
1332
1140
|
const diagnosticsModeMessage = getDiagnosticsModeMessage(loadError ?? snapshot.fatalError);
|
|
1333
1141
|
const addCommentDisabledReason =
|
|
1334
1142
|
!capabilities.canAddComment && !snapshot.selection.isCollapsed
|
|
1335
1143
|
? "Select text within one paragraph to add a DOCX comment."
|
|
1336
1144
|
: undefined;
|
|
1145
|
+
const activeImageContext = useMemo(
|
|
1146
|
+
() =>
|
|
1147
|
+
buildActiveImageContext({
|
|
1148
|
+
canonicalDocument,
|
|
1149
|
+
selection: snapshot.selection,
|
|
1150
|
+
storyTarget: viewState.activeStory,
|
|
1151
|
+
surface: snapshot.surface,
|
|
1152
|
+
}),
|
|
1153
|
+
[canonicalDocument, snapshot.selection, snapshot.surface, viewState.activeStory],
|
|
1154
|
+
);
|
|
1155
|
+
const sourcePackage = sessionState.sourcePackage;
|
|
1156
|
+
const mediaPreviewCatalogKey = Object.values(canonicalDocument.media.items)
|
|
1157
|
+
.map((item) =>
|
|
1158
|
+
[
|
|
1159
|
+
item.mediaId,
|
|
1160
|
+
item.packagePartName,
|
|
1161
|
+
item.contentType ?? "",
|
|
1162
|
+
item.widthEmu ?? "",
|
|
1163
|
+
item.heightEmu ?? "",
|
|
1164
|
+
].join(":"),
|
|
1165
|
+
)
|
|
1166
|
+
.sort()
|
|
1167
|
+
.join("|");
|
|
1168
|
+
const mediaPreviews = useMemo(() => {
|
|
1169
|
+
if (!sourcePackage) {
|
|
1170
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
const bytes = decodePersistedSourcePackageBytes(sourcePackage);
|
|
1174
|
+
if (!hasValidPersistedSourcePackageDigest(sourcePackage, bytes)) {
|
|
1175
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1176
|
+
}
|
|
1177
|
+
const opc = readOpcPackage(bytes);
|
|
1178
|
+
const previews: Record<string, MediaPreviewDescriptor> = {};
|
|
1179
|
+
for (const item of Object.values(canonicalDocument.media.items)) {
|
|
1180
|
+
const contentType = item.contentType?.toLowerCase();
|
|
1181
|
+
const part = opc.parts.get(item.packagePartName);
|
|
1182
|
+
if (
|
|
1183
|
+
!part?.bytes ||
|
|
1184
|
+
!contentType ||
|
|
1185
|
+
!BROWSER_SAFE_PREVIEW_TYPES.has(contentType)
|
|
1186
|
+
) {
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
previews[item.mediaId] = {
|
|
1190
|
+
src: createImageDataUrl(contentType, part.bytes),
|
|
1191
|
+
...(item.widthEmu !== undefined ? { widthEmu: item.widthEmu } : {}),
|
|
1192
|
+
...(item.heightEmu !== undefined ? { heightEmu: item.heightEmu } : {}),
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
return previews;
|
|
1196
|
+
} catch {
|
|
1197
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1198
|
+
}
|
|
1199
|
+
}, [mediaPreviewCatalogKey, sourcePackage?.sha256Hex]);
|
|
1200
|
+
const activeObjectContext = useMemo(
|
|
1201
|
+
() =>
|
|
1202
|
+
viewState.activeObjectFrame && viewState.activeObjectFrame.kind !== "image"
|
|
1203
|
+
? {
|
|
1204
|
+
kind: viewState.activeObjectFrame.kind,
|
|
1205
|
+
display: viewState.activeObjectFrame.display,
|
|
1206
|
+
}
|
|
1207
|
+
: null,
|
|
1208
|
+
[viewState.activeObjectFrame],
|
|
1209
|
+
);
|
|
1337
1210
|
const selectionToolbar = buildSelectionToolbarModel({
|
|
1338
1211
|
snapshot,
|
|
1339
1212
|
viewState,
|
|
@@ -1609,131 +1482,204 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1609
1482
|
},
|
|
1610
1483
|
};
|
|
1611
1484
|
|
|
1485
|
+
const commands = useCommandBag<EditorCommandBag>({
|
|
1486
|
+
...reviewCallbacks,
|
|
1487
|
+
onWorkspaceModeChange: (mode) => activeRuntime.setWorkspaceMode(mode),
|
|
1488
|
+
onZoomChange: (level) => activeRuntime.setZoom(level),
|
|
1489
|
+
onActiveRailTabChange: setActiveRailTab,
|
|
1490
|
+
onShowTrackedChangesChange: setShowTrackedChanges,
|
|
1491
|
+
onToggleBold: () =>
|
|
1492
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" }),
|
|
1493
|
+
onToggleItalic: () =>
|
|
1494
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "italic" }),
|
|
1495
|
+
onToggleUnderline: () =>
|
|
1496
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "underline" }),
|
|
1497
|
+
onSetSelectionTextColor: (color) =>
|
|
1498
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-text-color", color }),
|
|
1499
|
+
onSetSelectionHighlightColor: (color) =>
|
|
1500
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-highlight-color", color }),
|
|
1501
|
+
onToggleStrikethrough: () =>
|
|
1502
|
+
applyRuntimeFormattingOperation(activeRuntime, {
|
|
1503
|
+
type: "toggle",
|
|
1504
|
+
mark: "strikethrough",
|
|
1505
|
+
}),
|
|
1506
|
+
onToggleSuperscript: () =>
|
|
1507
|
+
applyRuntimeFormattingOperation(activeRuntime, {
|
|
1508
|
+
type: "toggle",
|
|
1509
|
+
mark: "superscript",
|
|
1510
|
+
}),
|
|
1511
|
+
onToggleSubscript: () =>
|
|
1512
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "subscript" }),
|
|
1513
|
+
onSetFontFamily: (fontFamily) =>
|
|
1514
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-font-family", fontFamily }),
|
|
1515
|
+
onSetFontSize: (fontSize) =>
|
|
1516
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-font-size", size: fontSize }),
|
|
1517
|
+
onSetTextColor: (color) =>
|
|
1518
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-text-color", color }),
|
|
1519
|
+
onSetHighlightColor: (color) =>
|
|
1520
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-highlight-color", color }),
|
|
1521
|
+
onSetAlignment: (alignment) =>
|
|
1522
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-alignment", alignment }),
|
|
1523
|
+
onSetParagraphStyle: (styleId) => applyRuntimeParagraphStyle(activeRuntime, styleId),
|
|
1524
|
+
onOutdent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" }),
|
|
1525
|
+
onIndent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "indent" }),
|
|
1526
|
+
onInsertPageBreak: () => applyRuntimeInsertPageBreak(activeRuntime),
|
|
1527
|
+
onInsertTable: () => applyRuntimeInsertTable(activeRuntime, { rows: 3, columns: 3 }),
|
|
1528
|
+
onInsertSectionBreak: (type) => applyRuntimeInsertSectionBreak(activeRuntime, type),
|
|
1529
|
+
onInsertImage: (options) => applyRuntimeInsertImage(activeRuntime, options),
|
|
1530
|
+
onSetTableStyle: (styleId) => applyRuntimeTableStyle(activeRuntime, styleId),
|
|
1531
|
+
onAddRowBefore: () =>
|
|
1532
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1533
|
+
type: "add-row-before",
|
|
1534
|
+
}),
|
|
1535
|
+
onAddRowAfter: () =>
|
|
1536
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1537
|
+
type: "add-row-after",
|
|
1538
|
+
}),
|
|
1539
|
+
onAddColumnBefore: () =>
|
|
1540
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1541
|
+
type: "add-column-before",
|
|
1542
|
+
}),
|
|
1543
|
+
onAddColumnAfter: () =>
|
|
1544
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1545
|
+
type: "add-column-after",
|
|
1546
|
+
}),
|
|
1547
|
+
onDeleteRow: () =>
|
|
1548
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1549
|
+
type: "delete-row",
|
|
1550
|
+
}),
|
|
1551
|
+
onDeleteColumn: () =>
|
|
1552
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1553
|
+
type: "delete-column",
|
|
1554
|
+
}),
|
|
1555
|
+
onDeleteTable: () =>
|
|
1556
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1557
|
+
type: "delete-table",
|
|
1558
|
+
}),
|
|
1559
|
+
onMergeCells: () =>
|
|
1560
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1561
|
+
type: "merge-cells",
|
|
1562
|
+
}),
|
|
1563
|
+
onSplitCell: () =>
|
|
1564
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1565
|
+
type: "split-cell",
|
|
1566
|
+
}),
|
|
1567
|
+
onSetCellBackground: (color) =>
|
|
1568
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1569
|
+
type: "set-cell-background",
|
|
1570
|
+
color,
|
|
1571
|
+
}),
|
|
1572
|
+
onSetImageLayout: (mediaId, dimensions) =>
|
|
1573
|
+
applyRuntimeImageResize(activeRuntime, mediaId, dimensions),
|
|
1574
|
+
onSetImageFrame: (mediaId, offsets) =>
|
|
1575
|
+
applyRuntimeImageReposition(activeRuntime, mediaId, offsets),
|
|
1576
|
+
onOpenHeaderStory: () =>
|
|
1577
|
+
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "header"),
|
|
1578
|
+
onOpenFooterStory: () =>
|
|
1579
|
+
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "footer"),
|
|
1580
|
+
onDeleteSectionBreak: (sectionIndex) =>
|
|
1581
|
+
applyRuntimeDeleteSectionBreak(activeRuntime, sectionIndex),
|
|
1582
|
+
onUpdateSectionLayout: (sectionIndex, patch) =>
|
|
1583
|
+
applyRuntimeUpdateSectionLayout(activeRuntime, sectionIndex, patch),
|
|
1584
|
+
onSetSectionPageNumbering: (sectionIndex, patch) =>
|
|
1585
|
+
applyRuntimeSetSectionPageNumbering(activeRuntime, sectionIndex, patch),
|
|
1586
|
+
onSetHeaderFooterLink: (sectionIndex, patch) =>
|
|
1587
|
+
applyRuntimeSetHeaderFooterLink(activeRuntime, sectionIndex, patch),
|
|
1588
|
+
onSetParagraphIndentation: (indentation) =>
|
|
1589
|
+
applyRuntimeParagraphIndentation(activeRuntime, indentation),
|
|
1590
|
+
onSetParagraphTabStops: (tabStops) =>
|
|
1591
|
+
applyRuntimeParagraphTabStops(activeRuntime, tabStops),
|
|
1592
|
+
onRestartNumbering: () =>
|
|
1593
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "restart" }),
|
|
1594
|
+
onContinueNumbering: () =>
|
|
1595
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "continue" }),
|
|
1596
|
+
onNavigateHeading: (headingId) => {
|
|
1597
|
+
const heading = documentNavigation.headings.find(
|
|
1598
|
+
(entry) => entry.headingId === headingId,
|
|
1599
|
+
);
|
|
1600
|
+
if (!heading) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
applyRuntimeSelection(
|
|
1604
|
+
activeRuntime,
|
|
1605
|
+
createCollapsedPublicSelection(heading.offset),
|
|
1606
|
+
);
|
|
1607
|
+
},
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
const documentElement = (
|
|
1611
|
+
<EditorSurfaceController
|
|
1612
|
+
ref={surfaceRef}
|
|
1613
|
+
currentUser={currentUser}
|
|
1614
|
+
snapshot={snapshot}
|
|
1615
|
+
canonicalDocument={canonicalDocument}
|
|
1616
|
+
documentNavigation={documentNavigation}
|
|
1617
|
+
reviewMode={reviewMode}
|
|
1618
|
+
markupDisplay={liveMarkupDisplay}
|
|
1619
|
+
activeRevisionId={activeRevisionId}
|
|
1620
|
+
showTrackedChanges={showTrackedChanges}
|
|
1621
|
+
mediaPreviews={mediaPreviews}
|
|
1622
|
+
isPageWorkspace={isPageWorkspace}
|
|
1623
|
+
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
1624
|
+
workflowCandidates={workflowScopeSnapshot?.candidates}
|
|
1625
|
+
workflowBlockedReasons={workflowBlockedRails}
|
|
1626
|
+
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1627
|
+
{...editorCallbacks}
|
|
1628
|
+
onCommentActivated={(commentId) => {
|
|
1629
|
+
activeRuntime.openComment(commentId);
|
|
1630
|
+
setActiveRailTab("comments");
|
|
1631
|
+
}}
|
|
1632
|
+
onRevisionActivated={(revisionId) => {
|
|
1633
|
+
setActiveRevisionId(revisionId);
|
|
1634
|
+
setActiveRailTab("changes");
|
|
1635
|
+
}}
|
|
1636
|
+
/>
|
|
1637
|
+
);
|
|
1638
|
+
|
|
1612
1639
|
return (
|
|
1613
|
-
<
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
{
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
)
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
zoomLevel={viewState.zoomLevel}
|
|
1657
|
-
formattingState={formattingState}
|
|
1658
|
-
styleCatalog={styleCatalog}
|
|
1659
|
-
activeRailTab={activeRailTab}
|
|
1660
|
-
activeCommentId={snapshot.comments.activeCommentId}
|
|
1661
|
-
activeRevisionId={activeRevisionId}
|
|
1662
|
-
showTrackedChanges={showTrackedChanges}
|
|
1663
|
-
selectionToolbar={shouldRenderSelectionToolbar ? selectionToolbar : null}
|
|
1664
|
-
selectionToolbarAnchor={shouldRenderSelectionToolbar ? selectionToolbarAnchor : null}
|
|
1665
|
-
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1666
|
-
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1667
|
-
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1668
|
-
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
1669
|
-
selectionToolbarRef={selectionToolbarElementRef}
|
|
1670
|
-
onWorkspaceModeChange={(mode) => activeRuntime.setWorkspaceMode(mode)}
|
|
1671
|
-
onZoomChange={(level) => activeRuntime.setZoom(level)}
|
|
1672
|
-
onActiveRailTabChange={setActiveRailTab}
|
|
1673
|
-
onShowTrackedChangesChange={setShowTrackedChanges}
|
|
1674
|
-
onToggleBold={() =>
|
|
1675
|
-
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" })}
|
|
1676
|
-
onToggleItalic={() =>
|
|
1677
|
-
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "italic" })}
|
|
1678
|
-
onToggleUnderline={() =>
|
|
1679
|
-
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "underline" })}
|
|
1680
|
-
onSetParagraphStyle={(styleId) =>
|
|
1681
|
-
applyRuntimeParagraphStyle(activeRuntime, styleId)}
|
|
1682
|
-
onOutdent={() =>
|
|
1683
|
-
applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" })}
|
|
1684
|
-
onIndent={() =>
|
|
1685
|
-
applyRuntimeFormattingOperation(activeRuntime, { type: "indent" })}
|
|
1686
|
-
onOpenHeaderStory={() =>
|
|
1687
|
-
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "header")}
|
|
1688
|
-
onOpenFooterStory={() =>
|
|
1689
|
-
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "footer")}
|
|
1690
|
-
onSetParagraphIndentation={(indentation) =>
|
|
1691
|
-
applyRuntimeParagraphIndentation(activeRuntime, indentation)
|
|
1692
|
-
}
|
|
1693
|
-
onSetParagraphTabStops={(tabStops) =>
|
|
1694
|
-
applyRuntimeParagraphTabStops(activeRuntime, tabStops)
|
|
1695
|
-
}
|
|
1696
|
-
onRestartNumbering={() => applyRuntimeNumberingFlow(activeRuntime, { type: "restart" })}
|
|
1697
|
-
onContinueNumbering={() => applyRuntimeNumberingFlow(activeRuntime, { type: "continue" })}
|
|
1698
|
-
onNavigateHeading={(headingId) => {
|
|
1699
|
-
const heading = documentNavigation.headings.find(
|
|
1700
|
-
(entry) => entry.headingId === headingId,
|
|
1701
|
-
);
|
|
1702
|
-
if (!heading) {
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
applyRuntimeSelection(
|
|
1706
|
-
activeRuntime,
|
|
1707
|
-
createCollapsedPublicSelection(heading.offset),
|
|
1708
|
-
);
|
|
1709
|
-
}}
|
|
1710
|
-
{...reviewCallbacks}
|
|
1711
|
-
document={
|
|
1712
|
-
<TwProseMirrorSurface
|
|
1713
|
-
ref={surfaceRef}
|
|
1714
|
-
currentUser={currentUser}
|
|
1715
|
-
snapshot={snapshot}
|
|
1716
|
-
canonicalDocument={canonicalDocument}
|
|
1717
|
-
documentNavigation={documentNavigation}
|
|
1718
|
-
reviewMode={reviewMode}
|
|
1719
|
-
markupDisplay={liveMarkupDisplay}
|
|
1720
|
-
activeRevisionId={activeRevisionId}
|
|
1721
|
-
showTrackedChanges={showTrackedChanges}
|
|
1722
|
-
isPageWorkspace={isPageWorkspace}
|
|
1723
|
-
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1724
|
-
{...editorCallbacks}
|
|
1725
|
-
onCommentActivated={(commentId) => {
|
|
1726
|
-
activeRuntime.openComment(commentId);
|
|
1727
|
-
setActiveRailTab("comments");
|
|
1728
|
-
}}
|
|
1729
|
-
onRevisionActivated={(revisionId) => {
|
|
1730
|
-
setActiveRevisionId(revisionId);
|
|
1731
|
-
setActiveRailTab("changes");
|
|
1732
|
-
}}
|
|
1733
|
-
/>
|
|
1734
|
-
}
|
|
1735
|
-
/>
|
|
1736
|
-
</div>
|
|
1640
|
+
<EditorShellView
|
|
1641
|
+
shellRef={shellRef}
|
|
1642
|
+
documentId={documentId}
|
|
1643
|
+
snapshot={snapshot}
|
|
1644
|
+
loadError={loadError}
|
|
1645
|
+
diagnosticsModeMessage={diagnosticsModeMessage}
|
|
1646
|
+
accessibilityInstructionsId={accessibilityInstructionsId}
|
|
1647
|
+
accessibilityStatusId={accessibilityStatusId}
|
|
1648
|
+
accessibilityAlertId={accessibilityAlertId}
|
|
1649
|
+
accessibilityStatusMessage={buildAccessibilityStatusMessage(
|
|
1650
|
+
snapshot,
|
|
1651
|
+
loadError ?? undefined,
|
|
1652
|
+
)}
|
|
1653
|
+
visuallyHiddenStyles={VISUALLY_HIDDEN_STYLES}
|
|
1654
|
+
onShellKeyDownCapture={handleShellKeyDownCapture}
|
|
1655
|
+
viewState={viewState}
|
|
1656
|
+
markupDisplay={liveMarkupDisplay}
|
|
1657
|
+
currentUserId={currentUser.userId}
|
|
1658
|
+
capabilities={capabilities}
|
|
1659
|
+
documentNavigation={documentNavigation}
|
|
1660
|
+
reviewMode={reviewMode}
|
|
1661
|
+
workspaceMode={viewState.workspaceMode}
|
|
1662
|
+
zoomLevel={viewState.zoomLevel}
|
|
1663
|
+
formattingState={formattingState}
|
|
1664
|
+
styleCatalog={styleCatalog}
|
|
1665
|
+
activeRailTab={activeRailTab}
|
|
1666
|
+
activeCommentId={snapshot.comments.activeCommentId}
|
|
1667
|
+
activeRevisionId={activeRevisionId}
|
|
1668
|
+
showTrackedChanges={showTrackedChanges}
|
|
1669
|
+
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
1670
|
+
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
1671
|
+
selectionToolbar={shouldRenderSelectionToolbar ? selectionToolbar : null}
|
|
1672
|
+
selectionToolbarAnchor={shouldRenderSelectionToolbar ? selectionToolbarAnchor : null}
|
|
1673
|
+
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1674
|
+
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1675
|
+
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1676
|
+
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
1677
|
+
selectionToolbarRef={selectionToolbarElementRef}
|
|
1678
|
+
activeImageContext={activeImageContext}
|
|
1679
|
+
activeObjectContext={activeObjectContext}
|
|
1680
|
+
commands={commands}
|
|
1681
|
+
document={documentElement}
|
|
1682
|
+
/>
|
|
1737
1683
|
);
|
|
1738
1684
|
},
|
|
1739
1685
|
);
|
|
@@ -1772,9 +1718,14 @@ function applyRuntimeFormattingOperation(
|
|
|
1772
1718
|
}
|
|
1773
1719
|
|
|
1774
1720
|
function getRuntimeStyleCatalog(
|
|
1775
|
-
|
|
1721
|
+
input:
|
|
1722
|
+
| WordReviewEditorRuntime
|
|
1723
|
+
| EditorSessionState["canonicalDocument"]["styles"],
|
|
1776
1724
|
): StyleCatalogSnapshot {
|
|
1777
|
-
const styles =
|
|
1725
|
+
const styles =
|
|
1726
|
+
"getSessionState" in input
|
|
1727
|
+
? input.getSessionState().canonicalDocument.styles
|
|
1728
|
+
: input;
|
|
1778
1729
|
const mapRecord = <
|
|
1779
1730
|
T extends {
|
|
1780
1731
|
styleId: string;
|
|
@@ -1965,6 +1916,13 @@ function applyRuntimeInsertSectionBreak(
|
|
|
1965
1916
|
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1966
1917
|
return;
|
|
1967
1918
|
}
|
|
1919
|
+
if (snapshot.documentMode === "suggesting") {
|
|
1920
|
+
runtime.emitBlockedCommand("insertSectionBreak", [{
|
|
1921
|
+
code: "unsupported_surface",
|
|
1922
|
+
message: "Section break insertion is not supported in suggesting mode.",
|
|
1923
|
+
}]);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1968
1926
|
|
|
1969
1927
|
const sessionState = runtime.getSessionState();
|
|
1970
1928
|
const timestamp = new Date().toISOString();
|
|
@@ -2136,6 +2094,14 @@ function applyRuntimeSetHeaderFooterLink(
|
|
|
2136
2094
|
}
|
|
2137
2095
|
|
|
2138
2096
|
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
2097
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2098
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2099
|
+
runtime.emitBlockedCommand("insertPageBreak", [{
|
|
2100
|
+
code: "unsupported_surface",
|
|
2101
|
+
message: "Page break insertion is not supported in suggesting mode.",
|
|
2102
|
+
}]);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2139
2105
|
const context = getStoryMutationContext(runtime);
|
|
2140
2106
|
if (!context) {
|
|
2141
2107
|
return;
|
|
@@ -2153,6 +2119,14 @@ function applyRuntimeInsertTable(
|
|
|
2153
2119
|
runtime: WordReviewEditorRuntime,
|
|
2154
2120
|
options: InsertTableOptions,
|
|
2155
2121
|
): void {
|
|
2122
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2123
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2124
|
+
runtime.emitBlockedCommand("insertTable", [{
|
|
2125
|
+
code: "unsupported_surface",
|
|
2126
|
+
message: "Table insertion is not supported in suggesting mode.",
|
|
2127
|
+
}]);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2156
2130
|
const context = getStoryMutationContext(runtime);
|
|
2157
2131
|
if (!context) {
|
|
2158
2132
|
return;
|
|
@@ -2171,6 +2145,14 @@ function applyRuntimeInsertImage(
|
|
|
2171
2145
|
runtime: WordReviewEditorRuntime,
|
|
2172
2146
|
options: InsertImageOptions,
|
|
2173
2147
|
): void {
|
|
2148
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2149
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2150
|
+
runtime.emitBlockedCommand("insertImage", [{
|
|
2151
|
+
code: "unsupported_surface",
|
|
2152
|
+
message: "Image insertion is not supported in suggesting mode.",
|
|
2153
|
+
}]);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2174
2156
|
const context = getStoryMutationContext(runtime);
|
|
2175
2157
|
if (!context) {
|
|
2176
2158
|
return;
|
|
@@ -2209,6 +2191,13 @@ function applyRuntimeImageResize(
|
|
|
2209
2191
|
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2210
2192
|
return;
|
|
2211
2193
|
}
|
|
2194
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2195
|
+
runtime.emitBlockedCommand("setImageLayout", [{
|
|
2196
|
+
code: "unsupported_surface",
|
|
2197
|
+
message: "Image resize is not supported in suggesting mode.",
|
|
2198
|
+
}]);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2212
2201
|
|
|
2213
2202
|
try {
|
|
2214
2203
|
const sessionState = runtime.getSessionState();
|
|
@@ -2234,23 +2223,35 @@ function applyRuntimeImageReposition(
|
|
|
2234
2223
|
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2235
2224
|
): void {
|
|
2236
2225
|
const snapshot = runtime.getRenderSnapshot();
|
|
2237
|
-
if (
|
|
2226
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2227
|
+
runtime.emitBlockedCommand("setImageFrame", [{
|
|
2228
|
+
code: "unsupported_surface",
|
|
2229
|
+
message: "Image reposition is not supported in suggesting mode.",
|
|
2230
|
+
}]);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
const context = getStoryMutationContext(runtime);
|
|
2234
|
+
if (!context) {
|
|
2238
2235
|
return;
|
|
2239
2236
|
}
|
|
2240
2237
|
|
|
2241
2238
|
try {
|
|
2242
|
-
const sessionState = runtime.getSessionState();
|
|
2243
2239
|
const result = repositionFloatingImageInDocument(
|
|
2244
|
-
|
|
2240
|
+
context.localDocument,
|
|
2245
2241
|
mediaId,
|
|
2246
2242
|
offsets,
|
|
2243
|
+
context.timestamp,
|
|
2244
|
+
);
|
|
2245
|
+
dispatchStoryMutationResult(
|
|
2246
|
+
runtime,
|
|
2247
|
+
context,
|
|
2248
|
+
{
|
|
2249
|
+
changed: true,
|
|
2250
|
+
document: result.document,
|
|
2251
|
+
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
2252
|
+
},
|
|
2253
|
+
context.timestamp,
|
|
2247
2254
|
);
|
|
2248
|
-
runtime.dispatch({
|
|
2249
|
-
type: "document.replace",
|
|
2250
|
-
document: result.document,
|
|
2251
|
-
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
2252
|
-
origin: { source: "api", timestamp: new Date().toISOString() },
|
|
2253
|
-
});
|
|
2254
2255
|
} catch {
|
|
2255
2256
|
return;
|
|
2256
2257
|
}
|
|
@@ -2274,6 +2275,14 @@ function applyRuntimeTableStructureOperation(
|
|
|
2274
2275
|
| { type: "split-cell" }
|
|
2275
2276
|
| { type: "set-cell-background"; color: string },
|
|
2276
2277
|
): void {
|
|
2278
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2279
|
+
if (snapshot.documentMode === "suggesting") {
|
|
2280
|
+
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
2281
|
+
code: "unsupported_surface",
|
|
2282
|
+
message: `Table operation "${operation.type}" is not supported in suggesting mode.`,
|
|
2283
|
+
}]);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2277
2286
|
const context = getStoryMutationContext(runtime);
|
|
2278
2287
|
if (!context) {
|
|
2279
2288
|
return;
|
|
@@ -2758,6 +2767,13 @@ function normalizeRequestedSelection(
|
|
|
2758
2767
|
);
|
|
2759
2768
|
}
|
|
2760
2769
|
|
|
2770
|
+
export function __resolveLiveMarkupDisplay(
|
|
2771
|
+
requested: MarkupDisplay | undefined,
|
|
2772
|
+
isPageWorkspace: boolean,
|
|
2773
|
+
): MarkupDisplay {
|
|
2774
|
+
return requested ?? (isPageWorkspace ? "all" : "clean");
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2761
2777
|
function createCollapsedPublicSelection(
|
|
2762
2778
|
position: number,
|
|
2763
2779
|
storyTarget?: EditorStoryTarget,
|
|
@@ -3008,60 +3024,6 @@ function guessSourceLabel(
|
|
|
3008
3024
|
);
|
|
3009
3025
|
}
|
|
3010
3026
|
|
|
3011
|
-
function createLoadingSnapshot(
|
|
3012
|
-
documentId: string,
|
|
3013
|
-
readOnly: boolean,
|
|
3014
|
-
sourceLabel?: string,
|
|
3015
|
-
): RuntimeRenderSnapshot {
|
|
3016
|
-
return {
|
|
3017
|
-
documentId,
|
|
3018
|
-
sessionId: `${documentId}-loading`,
|
|
3019
|
-
sourceLabel,
|
|
3020
|
-
revisionToken: `${documentId}:loading`,
|
|
3021
|
-
isReady: false,
|
|
3022
|
-
isDirty: false,
|
|
3023
|
-
readOnly,
|
|
3024
|
-
selection: collapsedSelection(),
|
|
3025
|
-
activeStory: { kind: "main" },
|
|
3026
|
-
documentStats: {
|
|
3027
|
-
storyLength: 0,
|
|
3028
|
-
commentCount: 0,
|
|
3029
|
-
revisionCount: 0,
|
|
3030
|
-
opaqueFragmentCount: 0,
|
|
3031
|
-
},
|
|
3032
|
-
comments: {
|
|
3033
|
-
openCommentIds: [],
|
|
3034
|
-
resolvedCommentIds: [],
|
|
3035
|
-
detachedCommentIds: [],
|
|
3036
|
-
totalCount: 0,
|
|
3037
|
-
threads: [],
|
|
3038
|
-
},
|
|
3039
|
-
trackedChanges: {
|
|
3040
|
-
pendingChangeIds: [],
|
|
3041
|
-
acceptedChangeIds: [],
|
|
3042
|
-
rejectedChangeIds: [],
|
|
3043
|
-
detachedChangeIds: [],
|
|
3044
|
-
actionableChangeIds: [],
|
|
3045
|
-
preserveOnlyChangeIds: [],
|
|
3046
|
-
totalCount: 0,
|
|
3047
|
-
revisions: [],
|
|
3048
|
-
},
|
|
3049
|
-
compatibility: {
|
|
3050
|
-
blockExport: false,
|
|
3051
|
-
blockExportReasons: [],
|
|
3052
|
-
warningCount: 0,
|
|
3053
|
-
errorCount: 0,
|
|
3054
|
-
featureEntries: [],
|
|
3055
|
-
},
|
|
3056
|
-
warnings: [],
|
|
3057
|
-
commandState: {
|
|
3058
|
-
canUndo: false,
|
|
3059
|
-
canRedo: false,
|
|
3060
|
-
readOnly,
|
|
3061
|
-
},
|
|
3062
|
-
};
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
3027
|
function deriveEditorViewMode(
|
|
3066
3028
|
readOnly: boolean,
|
|
3067
3029
|
reviewMode: WordReviewEditorProps["reviewMode"] = "review",
|
|
@@ -3072,636 +3034,6 @@ function deriveEditorViewMode(
|
|
|
3072
3034
|
return reviewMode === "editing" ? "editing" : "review";
|
|
3073
3035
|
}
|
|
3074
3036
|
|
|
3075
|
-
function createErrorSnapshot(documentId: string, error: EditorError): RuntimeRenderSnapshot {
|
|
3076
|
-
return {
|
|
3077
|
-
...createLoadingSnapshot(documentId, true),
|
|
3078
|
-
isReady: true,
|
|
3079
|
-
sessionId: `${documentId}-error`,
|
|
3080
|
-
revisionToken: `${documentId}:error`,
|
|
3081
|
-
compatibility: {
|
|
3082
|
-
blockExport: true,
|
|
3083
|
-
blockExportReasons: [error.message],
|
|
3084
|
-
warningCount: 0,
|
|
3085
|
-
errorCount: 1,
|
|
3086
|
-
featureEntries: [],
|
|
3087
|
-
},
|
|
3088
|
-
fatalError: error,
|
|
3089
|
-
};
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
async function persistAndExport(input: {
|
|
3093
|
-
hostAdapter?: EditorHostAdapter;
|
|
3094
|
-
datastore?: EditorDatastoreAdapter;
|
|
3095
|
-
documentId: string;
|
|
3096
|
-
runtime: WordReviewEditorRuntime;
|
|
3097
|
-
onError?: (error: EditorError) => void;
|
|
3098
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
3099
|
-
options?: ExportDocxOptions;
|
|
3100
|
-
lastSavedRevisionTokenRef: React.MutableRefObject<string | null>;
|
|
3101
|
-
autosaveTimerRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>;
|
|
3102
|
-
}): Promise<ExportResult> {
|
|
3103
|
-
if (input.autosaveTimerRef.current) {
|
|
3104
|
-
clearTimeout(input.autosaveTimerRef.current);
|
|
3105
|
-
input.autosaveTimerRef.current = null;
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
await persistSession({
|
|
3109
|
-
hostAdapter: input.hostAdapter,
|
|
3110
|
-
datastore: input.datastore,
|
|
3111
|
-
documentId: input.documentId,
|
|
3112
|
-
runtime: input.runtime,
|
|
3113
|
-
isAutosave: false,
|
|
3114
|
-
onError: input.onError,
|
|
3115
|
-
onEvent: input.onEvent,
|
|
3116
|
-
lastSavedRevisionTokenRef: input.lastSavedRevisionTokenRef,
|
|
3117
|
-
});
|
|
3118
|
-
|
|
3119
|
-
let result: ExportResult;
|
|
3120
|
-
try {
|
|
3121
|
-
result = await input.runtime.exportDocx(input.options);
|
|
3122
|
-
} catch (error) {
|
|
3123
|
-
const normalized = normalizeExportError(error, input.documentId, input.options);
|
|
3124
|
-
input.onError?.(normalized);
|
|
3125
|
-
emitEditorEvent({
|
|
3126
|
-
hostAdapter: input.hostAdapter,
|
|
3127
|
-
datastore: input.datastore,
|
|
3128
|
-
onEvent: input.onEvent,
|
|
3129
|
-
event: {
|
|
3130
|
-
type: "error",
|
|
3131
|
-
documentId: input.documentId,
|
|
3132
|
-
error: normalized,
|
|
3133
|
-
},
|
|
3134
|
-
});
|
|
3135
|
-
throw normalized;
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
const saveExport = input.hostAdapter?.saveExport ?? input.datastore?.saveExport;
|
|
3139
|
-
const saveExportSource = input.hostAdapter?.saveExport ? "host" : "datastore";
|
|
3140
|
-
if (!saveExport) {
|
|
3141
|
-
result = downloadExportResult(result);
|
|
3142
|
-
emitEditorEvent({
|
|
3143
|
-
hostAdapter: input.hostAdapter,
|
|
3144
|
-
datastore: input.datastore,
|
|
3145
|
-
onEvent: input.onEvent,
|
|
3146
|
-
event: {
|
|
3147
|
-
type: "export_completed",
|
|
3148
|
-
documentId: input.documentId,
|
|
3149
|
-
result,
|
|
3150
|
-
},
|
|
3151
|
-
});
|
|
3152
|
-
return result;
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
|
-
try {
|
|
3156
|
-
const saveResult = await saveExport({
|
|
3157
|
-
documentId: input.documentId,
|
|
3158
|
-
result,
|
|
3159
|
-
});
|
|
3160
|
-
result = withExportDelivery(result, {
|
|
3161
|
-
mode: "persisted-by-host",
|
|
3162
|
-
savedAt: saveResult.savedAt,
|
|
3163
|
-
});
|
|
3164
|
-
} catch (error) {
|
|
3165
|
-
const normalized = normalizeStorageError(error, {
|
|
3166
|
-
message: "Export persisted bytes could not be stored.",
|
|
3167
|
-
source: saveExportSource,
|
|
3168
|
-
details: {
|
|
3169
|
-
operation: "saveExport",
|
|
3170
|
-
},
|
|
3171
|
-
});
|
|
3172
|
-
input.onError?.(normalized);
|
|
3173
|
-
emitEditorEvent({
|
|
3174
|
-
hostAdapter: input.hostAdapter,
|
|
3175
|
-
datastore: input.datastore,
|
|
3176
|
-
onEvent: input.onEvent,
|
|
3177
|
-
event: {
|
|
3178
|
-
type: "error",
|
|
3179
|
-
documentId: input.documentId,
|
|
3180
|
-
error: normalized,
|
|
3181
|
-
},
|
|
3182
|
-
});
|
|
3183
|
-
result = withExportDelivery(result, {
|
|
3184
|
-
mode: "exported-bytes-only",
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
|
|
3188
|
-
emitEditorEvent({
|
|
3189
|
-
hostAdapter: input.hostAdapter,
|
|
3190
|
-
datastore: input.datastore,
|
|
3191
|
-
onEvent: input.onEvent,
|
|
3192
|
-
event: {
|
|
3193
|
-
type: "export_completed",
|
|
3194
|
-
documentId: input.documentId,
|
|
3195
|
-
result,
|
|
3196
|
-
},
|
|
3197
|
-
});
|
|
3198
|
-
|
|
3199
|
-
return result;
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
function rejectExportWhileLoading(input: {
|
|
3203
|
-
documentId: string;
|
|
3204
|
-
hostAdapter?: EditorHostAdapter;
|
|
3205
|
-
datastore?: EditorDatastoreAdapter;
|
|
3206
|
-
onError?: (error: EditorError) => void;
|
|
3207
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
3208
|
-
}): Promise<never> {
|
|
3209
|
-
const error: EditorError = {
|
|
3210
|
-
errorId: "word-review-editor-loading-export",
|
|
3211
|
-
code: "internal_invariant",
|
|
3212
|
-
message: "WordReviewEditor is still loading and cannot export yet.",
|
|
3213
|
-
isFatal: false,
|
|
3214
|
-
source: "runtime",
|
|
3215
|
-
};
|
|
3216
|
-
input.onError?.(error);
|
|
3217
|
-
emitEditorEvent({
|
|
3218
|
-
hostAdapter: input.hostAdapter,
|
|
3219
|
-
datastore: input.datastore,
|
|
3220
|
-
onEvent: input.onEvent,
|
|
3221
|
-
event: {
|
|
3222
|
-
type: "error",
|
|
3223
|
-
documentId: input.documentId,
|
|
3224
|
-
error,
|
|
3225
|
-
},
|
|
3226
|
-
});
|
|
3227
|
-
return Promise.reject(error);
|
|
3228
|
-
}
|
|
3229
|
-
|
|
3230
|
-
async function persistSession(input: {
|
|
3231
|
-
hostAdapter?: EditorHostAdapter;
|
|
3232
|
-
datastore?: EditorDatastoreAdapter;
|
|
3233
|
-
documentId: string;
|
|
3234
|
-
runtime: WordReviewEditorRuntime;
|
|
3235
|
-
isAutosave: boolean;
|
|
3236
|
-
onError?: (error: EditorError) => void;
|
|
3237
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
3238
|
-
lastSavedRevisionTokenRef: React.MutableRefObject<string | null>;
|
|
3239
|
-
}): Promise<void> {
|
|
3240
|
-
const saveSession = input.hostAdapter?.saveSession;
|
|
3241
|
-
const saveSnapshot = input.datastore?.saveSnapshot;
|
|
3242
|
-
if (!saveSession && !saveSnapshot) {
|
|
3243
|
-
return;
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
const sessionState = input.runtime.getSessionState();
|
|
3247
|
-
const snapshot = input.runtime.getPersistedSnapshot();
|
|
3248
|
-
const revisionToken = input.runtime.getRenderSnapshot().revisionToken;
|
|
3249
|
-
|
|
3250
|
-
if (input.isAutosave) {
|
|
3251
|
-
emitEditorEvent({
|
|
3252
|
-
hostAdapter: input.hostAdapter,
|
|
3253
|
-
datastore: input.datastore,
|
|
3254
|
-
onEvent: input.onEvent,
|
|
3255
|
-
event: {
|
|
3256
|
-
type: "autosave_state",
|
|
3257
|
-
documentId: input.documentId,
|
|
3258
|
-
state: {
|
|
3259
|
-
status: "saving",
|
|
3260
|
-
} satisfies AutosaveState,
|
|
3261
|
-
},
|
|
3262
|
-
});
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
try {
|
|
3266
|
-
const result = saveSession
|
|
3267
|
-
? await saveSession({
|
|
3268
|
-
documentId: input.documentId,
|
|
3269
|
-
sessionState,
|
|
3270
|
-
isAutosave: input.isAutosave,
|
|
3271
|
-
})
|
|
3272
|
-
: await saveSnapshot!({
|
|
3273
|
-
documentId: input.documentId,
|
|
3274
|
-
snapshot,
|
|
3275
|
-
isAutosave: input.isAutosave,
|
|
3276
|
-
});
|
|
3277
|
-
input.lastSavedRevisionTokenRef.current = revisionToken;
|
|
3278
|
-
emitEditorEvent({
|
|
3279
|
-
hostAdapter: input.hostAdapter,
|
|
3280
|
-
datastore: input.datastore,
|
|
3281
|
-
onEvent: input.onEvent,
|
|
3282
|
-
event: saveSession
|
|
3283
|
-
? {
|
|
3284
|
-
type: "session_saved",
|
|
3285
|
-
documentId: input.documentId,
|
|
3286
|
-
sessionState: input.runtime.getSessionState(),
|
|
3287
|
-
savedAt: result.savedAt,
|
|
3288
|
-
isAutosave: input.isAutosave,
|
|
3289
|
-
}
|
|
3290
|
-
: {
|
|
3291
|
-
type: "snapshot_saved",
|
|
3292
|
-
documentId: input.documentId,
|
|
3293
|
-
snapshot: {
|
|
3294
|
-
...snapshot,
|
|
3295
|
-
savedAt: result.savedAt,
|
|
3296
|
-
},
|
|
3297
|
-
isAutosave: input.isAutosave,
|
|
3298
|
-
},
|
|
3299
|
-
});
|
|
3300
|
-
if (input.isAutosave) {
|
|
3301
|
-
emitEditorEvent({
|
|
3302
|
-
hostAdapter: input.hostAdapter,
|
|
3303
|
-
datastore: input.datastore,
|
|
3304
|
-
onEvent: input.onEvent,
|
|
3305
|
-
event: {
|
|
3306
|
-
type: "autosave_state",
|
|
3307
|
-
documentId: input.documentId,
|
|
3308
|
-
state: {
|
|
3309
|
-
status: "saved",
|
|
3310
|
-
savedAt: result.savedAt,
|
|
3311
|
-
} satisfies AutosaveState,
|
|
3312
|
-
},
|
|
3313
|
-
});
|
|
3314
|
-
}
|
|
3315
|
-
} catch (error) {
|
|
3316
|
-
const normalized = normalizeStorageError(error, {
|
|
3317
|
-
message: input.isAutosave
|
|
3318
|
-
? saveSession
|
|
3319
|
-
? "Autosave failed while storing the editor session."
|
|
3320
|
-
: "Autosave failed while storing the editor snapshot."
|
|
3321
|
-
: saveSession
|
|
3322
|
-
? "Session save failed while preparing the export checkpoint."
|
|
3323
|
-
: "Snapshot save failed while preparing the export checkpoint.",
|
|
3324
|
-
source: saveSession ? "host" : "datastore",
|
|
3325
|
-
details: {
|
|
3326
|
-
operation: saveSession ? "saveSession" : "saveSnapshot",
|
|
3327
|
-
isAutosave: input.isAutosave,
|
|
3328
|
-
},
|
|
3329
|
-
});
|
|
3330
|
-
input.onError?.(normalized);
|
|
3331
|
-
emitEditorEvent({
|
|
3332
|
-
hostAdapter: input.hostAdapter,
|
|
3333
|
-
datastore: input.datastore,
|
|
3334
|
-
onEvent: input.onEvent,
|
|
3335
|
-
event: {
|
|
3336
|
-
type: "error",
|
|
3337
|
-
documentId: input.documentId,
|
|
3338
|
-
error: normalized,
|
|
3339
|
-
},
|
|
3340
|
-
});
|
|
3341
|
-
if (input.isAutosave) {
|
|
3342
|
-
emitEditorEvent({
|
|
3343
|
-
hostAdapter: input.hostAdapter,
|
|
3344
|
-
datastore: input.datastore,
|
|
3345
|
-
onEvent: input.onEvent,
|
|
3346
|
-
event: {
|
|
3347
|
-
type: "autosave_state",
|
|
3348
|
-
documentId: input.documentId,
|
|
3349
|
-
state: {
|
|
3350
|
-
status: "error",
|
|
3351
|
-
error: normalized,
|
|
3352
|
-
} satisfies AutosaveState,
|
|
3353
|
-
},
|
|
3354
|
-
});
|
|
3355
|
-
}
|
|
3356
|
-
if (!input.isAutosave) {
|
|
3357
|
-
throw normalized;
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
function emitEditorEvent(input: {
|
|
3363
|
-
hostAdapter?: EditorHostAdapter;
|
|
3364
|
-
datastore?: EditorDatastoreAdapter;
|
|
3365
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
3366
|
-
event: WordReviewEditorEvent;
|
|
3367
|
-
}): void {
|
|
3368
|
-
input.onEvent?.(input.event);
|
|
3369
|
-
const logEvent = input.hostAdapter?.logEvent ?? input.datastore?.logEvent;
|
|
3370
|
-
logEvent?.({
|
|
3371
|
-
type: input.event.type,
|
|
3372
|
-
documentId: input.event.documentId,
|
|
3373
|
-
detail: summarizeEventDetail(input.event),
|
|
3374
|
-
});
|
|
3375
|
-
}
|
|
3376
|
-
|
|
3377
|
-
function summarizeEventDetail(
|
|
3378
|
-
event: WordReviewEditorEvent,
|
|
3379
|
-
): Record<string, unknown> | undefined {
|
|
3380
|
-
switch (event.type) {
|
|
3381
|
-
case "dirty_changed":
|
|
3382
|
-
return { isDirty: event.isDirty };
|
|
3383
|
-
case "comment_added":
|
|
3384
|
-
return { commentId: event.commentId };
|
|
3385
|
-
case "comment_resolved":
|
|
3386
|
-
return { commentId: event.commentId };
|
|
3387
|
-
case "change_accepted":
|
|
3388
|
-
case "change_rejected":
|
|
3389
|
-
return { changeId: event.changeId };
|
|
3390
|
-
case "warning_added":
|
|
3391
|
-
return { warningId: event.warning.warningId, code: event.warning.code };
|
|
3392
|
-
case "warning_cleared":
|
|
3393
|
-
return { warningId: event.warningId, code: event.code };
|
|
3394
|
-
case "error":
|
|
3395
|
-
return { errorId: event.error.errorId, code: event.error.code };
|
|
3396
|
-
case "autosave_state":
|
|
3397
|
-
return { status: event.state.status };
|
|
3398
|
-
case "snapshot_saved":
|
|
3399
|
-
return { isAutosave: event.isAutosave, savedAt: event.snapshot.savedAt };
|
|
3400
|
-
case "session_saved":
|
|
3401
|
-
return { isAutosave: event.isAutosave, savedAt: event.savedAt };
|
|
3402
|
-
case "export_completed":
|
|
3403
|
-
return {
|
|
3404
|
-
fileName: event.result.fileName,
|
|
3405
|
-
deliveryMode: event.result.delivery?.mode,
|
|
3406
|
-
savedAt: event.result.delivery?.savedAt,
|
|
3407
|
-
};
|
|
3408
|
-
case "story_changed":
|
|
3409
|
-
return { activeStory: event.activeStory };
|
|
3410
|
-
case "selection_changed":
|
|
3411
|
-
return {
|
|
3412
|
-
anchor: event.selection.anchor,
|
|
3413
|
-
head: event.selection.head,
|
|
3414
|
-
};
|
|
3415
|
-
case "ready":
|
|
3416
|
-
return {
|
|
3417
|
-
source: event.source,
|
|
3418
|
-
blockExport: event.compatibility.blockExport,
|
|
3419
|
-
};
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
function createReadyEvent(
|
|
3424
|
-
runtime: Pick<WordReviewEditorRuntime, "getCompatibilityReport" | "getRenderSnapshot">,
|
|
3425
|
-
source: "docx" | "session" | "snapshot",
|
|
3426
|
-
): Extract<WordReviewEditorEvent, { type: "ready" }> {
|
|
3427
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
3428
|
-
return {
|
|
3429
|
-
type: "ready",
|
|
3430
|
-
documentId: snapshot.documentId,
|
|
3431
|
-
sessionId: snapshot.sessionId,
|
|
3432
|
-
source,
|
|
3433
|
-
stats: snapshot.documentStats,
|
|
3434
|
-
compatibility: runtime.getCompatibilityReport(),
|
|
3435
|
-
comments: snapshot.comments,
|
|
3436
|
-
trackedChanges: snapshot.trackedChanges,
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
|
-
function normalizeStorageError(
|
|
3441
|
-
error: unknown,
|
|
3442
|
-
fallback: {
|
|
3443
|
-
message: string;
|
|
3444
|
-
source: "host" | "datastore";
|
|
3445
|
-
details?: Record<string, unknown>;
|
|
3446
|
-
},
|
|
3447
|
-
): EditorError {
|
|
3448
|
-
if (
|
|
3449
|
-
typeof error === "object" &&
|
|
3450
|
-
error !== null &&
|
|
3451
|
-
"errorId" in error &&
|
|
3452
|
-
"code" in error &&
|
|
3453
|
-
"message" in error
|
|
3454
|
-
) {
|
|
3455
|
-
return error as EditorError;
|
|
3456
|
-
}
|
|
3457
|
-
|
|
3458
|
-
return {
|
|
3459
|
-
errorId:
|
|
3460
|
-
fallback.source === "host"
|
|
3461
|
-
? "word-review-editor-host"
|
|
3462
|
-
: "word-review-editor-datastore",
|
|
3463
|
-
code: "datastore_failed",
|
|
3464
|
-
message: error instanceof Error ? error.message : fallback.message,
|
|
3465
|
-
isFatal: false,
|
|
3466
|
-
source: fallback.source === "host" ? "host" : "datastore",
|
|
3467
|
-
details: fallback.details,
|
|
3468
|
-
};
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
function createFallbackSnapshot(args: CreateRuntimeArgs): RuntimeRenderSnapshot {
|
|
3472
|
-
const initialSessionState =
|
|
3473
|
-
args.source.initialSessionState ??
|
|
3474
|
-
(args.source.initialSnapshot
|
|
3475
|
-
? editorSessionStateFromPersistedSnapshot(args.source.initialSnapshot)
|
|
3476
|
-
: undefined);
|
|
3477
|
-
const warnings = initialSessionState?.warningLog ?? [];
|
|
3478
|
-
const compatibility = initialSessionState?.compatibility ?? emptyCompatibilityReport();
|
|
3479
|
-
|
|
3480
|
-
return {
|
|
3481
|
-
...createLoadingSnapshot(args.documentId, args.readOnly, args.source.sourceLabel),
|
|
3482
|
-
sessionId: `${args.documentId}-session`,
|
|
3483
|
-
revisionToken: `${args.documentId}:0`,
|
|
3484
|
-
isReady: true,
|
|
3485
|
-
documentStats: {
|
|
3486
|
-
storyLength: estimateStoryLength(initialSessionState ?? args.source.initialSnapshot),
|
|
3487
|
-
commentCount: 0,
|
|
3488
|
-
revisionCount: 0,
|
|
3489
|
-
opaqueFragmentCount: 0,
|
|
3490
|
-
},
|
|
3491
|
-
compatibility: {
|
|
3492
|
-
blockExport: compatibility.blockExport,
|
|
3493
|
-
blockExportReasons: [],
|
|
3494
|
-
warningCount: compatibility.warnings.length,
|
|
3495
|
-
errorCount: compatibility.errors.length,
|
|
3496
|
-
featureEntries: compatibility.featureEntries,
|
|
3497
|
-
},
|
|
3498
|
-
warnings,
|
|
3499
|
-
};
|
|
3500
|
-
}
|
|
3501
|
-
|
|
3502
|
-
function createFallbackPersistedSnapshot(
|
|
3503
|
-
documentId: string,
|
|
3504
|
-
label = "Generated shell snapshot",
|
|
3505
|
-
): PersistedEditorSnapshot {
|
|
3506
|
-
const docId = createCanonicalDocumentId(documentId);
|
|
3507
|
-
return {
|
|
3508
|
-
snapshotVersion: "persisted-editor-snapshot/2",
|
|
3509
|
-
schemaVersion: "cds/1.0.0",
|
|
3510
|
-
documentId,
|
|
3511
|
-
docId,
|
|
3512
|
-
createdAt: "1970-01-01T00:00:00.000Z",
|
|
3513
|
-
updatedAt: "1970-01-01T00:00:00.000Z",
|
|
3514
|
-
savedAt: "1970-01-01T00:00:00.000Z",
|
|
3515
|
-
editorBuild: label,
|
|
3516
|
-
canonicalDocument: {
|
|
3517
|
-
schemaVersion: "cds/1.0.0",
|
|
3518
|
-
docId,
|
|
3519
|
-
createdAt: "1970-01-01T00:00:00.000Z",
|
|
3520
|
-
updatedAt: "1970-01-01T00:00:00.000Z",
|
|
3521
|
-
metadata: {
|
|
3522
|
-
customProperties: {},
|
|
3523
|
-
},
|
|
3524
|
-
styles: {
|
|
3525
|
-
paragraphs: {},
|
|
3526
|
-
characters: {},
|
|
3527
|
-
tables: {},
|
|
3528
|
-
},
|
|
3529
|
-
numbering: {
|
|
3530
|
-
abstractDefinitions: {},
|
|
3531
|
-
instances: {},
|
|
3532
|
-
},
|
|
3533
|
-
media: {
|
|
3534
|
-
items: {},
|
|
3535
|
-
},
|
|
3536
|
-
content: {
|
|
3537
|
-
type: "doc",
|
|
3538
|
-
children: [{ type: "paragraph", children: [] }],
|
|
3539
|
-
},
|
|
3540
|
-
review: {
|
|
3541
|
-
comments: {},
|
|
3542
|
-
revisions: {},
|
|
3543
|
-
},
|
|
3544
|
-
preservation: {
|
|
3545
|
-
opaqueFragments: {},
|
|
3546
|
-
packageParts: {},
|
|
3547
|
-
},
|
|
3548
|
-
diagnostics: {
|
|
3549
|
-
warnings: [],
|
|
3550
|
-
errors: [],
|
|
3551
|
-
},
|
|
3552
|
-
},
|
|
3553
|
-
compatibility: emptyCompatibilityReport(),
|
|
3554
|
-
warningLog: [],
|
|
3555
|
-
};
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
function emptyCompatibilityReport(): CompatibilityReport {
|
|
3559
|
-
return {
|
|
3560
|
-
reportVersion: "compatibility-report/1",
|
|
3561
|
-
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
3562
|
-
blockExport: false,
|
|
3563
|
-
featureEntries: [],
|
|
3564
|
-
warnings: [],
|
|
3565
|
-
errors: [],
|
|
3566
|
-
};
|
|
3567
|
-
}
|
|
3568
|
-
|
|
3569
|
-
function resolvePackageBackedExportSession(args: CreateRuntimeArgs): {
|
|
3570
|
-
session?: PackageBackedDocxSession;
|
|
3571
|
-
barrier?: SnapshotExportBarrier;
|
|
3572
|
-
} {
|
|
3573
|
-
const sourcePackage =
|
|
3574
|
-
args.source.initialSessionState?.sourcePackage ??
|
|
3575
|
-
args.source.initialSnapshot?.sourcePackage;
|
|
3576
|
-
if (!sourcePackage) {
|
|
3577
|
-
return {
|
|
3578
|
-
barrier: {
|
|
3579
|
-
reason: "missing_source_package_provenance",
|
|
3580
|
-
message:
|
|
3581
|
-
"DOCX export is blocked because this session was loaded without embedded source package provenance.",
|
|
3582
|
-
},
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
|
-
try {
|
|
3587
|
-
const bytes = decodePersistedSourcePackageBytes(sourcePackage);
|
|
3588
|
-
if (!hasValidPersistedSourcePackageDigest(sourcePackage, bytes)) {
|
|
3589
|
-
return {
|
|
3590
|
-
barrier: {
|
|
3591
|
-
reason: "invalid_source_package_provenance",
|
|
3592
|
-
message:
|
|
3593
|
-
"DOCX export is blocked because the embedded source package provenance failed its integrity check.",
|
|
3594
|
-
},
|
|
3595
|
-
};
|
|
3596
|
-
}
|
|
3597
|
-
|
|
3598
|
-
const session = loadDocxEditorSession({
|
|
3599
|
-
documentId: args.documentId,
|
|
3600
|
-
sourceLabel: sourcePackage.sourceLabel ?? args.source.sourceLabel,
|
|
3601
|
-
bytes,
|
|
3602
|
-
editorBuild:
|
|
3603
|
-
args.source.initialSessionState?.editorBuild ??
|
|
3604
|
-
args.source.initialSnapshot?.editorBuild ??
|
|
3605
|
-
"dev",
|
|
3606
|
-
});
|
|
3607
|
-
if (session.readOnly || session.fatalError) {
|
|
3608
|
-
return {
|
|
3609
|
-
barrier: {
|
|
3610
|
-
reason: "invalid_source_package_provenance",
|
|
3611
|
-
message:
|
|
3612
|
-
"DOCX export is blocked because the embedded source package provenance is no longer loadable as a valid package-backed session.",
|
|
3613
|
-
},
|
|
3614
|
-
};
|
|
3615
|
-
}
|
|
3616
|
-
|
|
3617
|
-
return { session };
|
|
3618
|
-
} catch {
|
|
3619
|
-
return {
|
|
3620
|
-
barrier: {
|
|
3621
|
-
reason: "invalid_source_package_provenance",
|
|
3622
|
-
message:
|
|
3623
|
-
"DOCX export is blocked because the embedded source package provenance could not be decoded into a package-backed session.",
|
|
3624
|
-
},
|
|
3625
|
-
};
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
|
|
3629
|
-
function applySessionExportBarrier(
|
|
3630
|
-
sessionState: EditorSessionState,
|
|
3631
|
-
barrier: SnapshotExportBarrier,
|
|
3632
|
-
): EditorSessionState {
|
|
3633
|
-
const featureEntryId = `feature:source-package-provenance:${barrier.reason}`;
|
|
3634
|
-
const featureEntries = sessionState.compatibility.featureEntries.some(
|
|
3635
|
-
(entry) => entry.featureEntryId === featureEntryId,
|
|
3636
|
-
)
|
|
3637
|
-
? sessionState.compatibility.featureEntries
|
|
3638
|
-
: [
|
|
3639
|
-
...sessionState.compatibility.featureEntries,
|
|
3640
|
-
{
|
|
3641
|
-
featureEntryId,
|
|
3642
|
-
featureKey: "source-package-provenance",
|
|
3643
|
-
featureClass: "unsupported-fatal" as const,
|
|
3644
|
-
message: barrier.message,
|
|
3645
|
-
details: {
|
|
3646
|
-
reason: barrier.reason,
|
|
3647
|
-
},
|
|
3648
|
-
},
|
|
3649
|
-
];
|
|
3650
|
-
|
|
3651
|
-
return {
|
|
3652
|
-
...sessionState,
|
|
3653
|
-
compatibility: {
|
|
3654
|
-
...sessionState.compatibility,
|
|
3655
|
-
blockExport: true,
|
|
3656
|
-
featureEntries,
|
|
3657
|
-
},
|
|
3658
|
-
};
|
|
3659
|
-
}
|
|
3660
|
-
|
|
3661
|
-
function createSnapshotExportBlockedError(
|
|
3662
|
-
documentId: string,
|
|
3663
|
-
barrier: SnapshotExportBarrier,
|
|
3664
|
-
): EditorError {
|
|
3665
|
-
return {
|
|
3666
|
-
errorId: `${documentId}:export:${barrier.reason}`,
|
|
3667
|
-
code: "export_failed",
|
|
3668
|
-
message: barrier.message,
|
|
3669
|
-
isFatal: false,
|
|
3670
|
-
source: "export",
|
|
3671
|
-
details: {
|
|
3672
|
-
reason: barrier.reason,
|
|
3673
|
-
},
|
|
3674
|
-
};
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
function normalizeExportError(
|
|
3678
|
-
error: unknown,
|
|
3679
|
-
documentId: string,
|
|
3680
|
-
options?: ExportDocxOptions,
|
|
3681
|
-
): EditorError {
|
|
3682
|
-
if (
|
|
3683
|
-
typeof error === "object" &&
|
|
3684
|
-
error !== null &&
|
|
3685
|
-
"errorId" in error &&
|
|
3686
|
-
"code" in error &&
|
|
3687
|
-
"message" in error
|
|
3688
|
-
) {
|
|
3689
|
-
return error as EditorError;
|
|
3690
|
-
}
|
|
3691
|
-
|
|
3692
|
-
return {
|
|
3693
|
-
errorId: `${documentId}:export:failed`,
|
|
3694
|
-
code: "export_failed",
|
|
3695
|
-
message:
|
|
3696
|
-
error instanceof Error ? error.message : "DOCX export failed for an unknown reason.",
|
|
3697
|
-
isFatal: false,
|
|
3698
|
-
source: "export",
|
|
3699
|
-
details: {
|
|
3700
|
-
requestedOptions: options ?? {},
|
|
3701
|
-
},
|
|
3702
|
-
};
|
|
3703
|
-
}
|
|
3704
|
-
|
|
3705
3037
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
3706
3038
|
return {
|
|
3707
3039
|
anchor: selection.anchor,
|
|
@@ -3825,6 +3157,102 @@ function selectionToolbarAnchorsEqual(
|
|
|
3825
3157
|
);
|
|
3826
3158
|
}
|
|
3827
3159
|
|
|
3160
|
+
function workflowScopeSnapshotsEqual(
|
|
3161
|
+
left: WorkflowScopeSnapshot | null,
|
|
3162
|
+
right: WorkflowScopeSnapshot | null,
|
|
3163
|
+
): boolean {
|
|
3164
|
+
if (left === right) {
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
if (!left || !right) {
|
|
3168
|
+
return false;
|
|
3169
|
+
}
|
|
3170
|
+
return (
|
|
3171
|
+
left.overlayPresent === right.overlayPresent &&
|
|
3172
|
+
left.activeWorkItemId === right.activeWorkItemId &&
|
|
3173
|
+
left.activeWorkItem === right.activeWorkItem &&
|
|
3174
|
+
left.scopes === right.scopes &&
|
|
3175
|
+
left.candidates === right.candidates &&
|
|
3176
|
+
workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons)
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
function interactionGuardSnapshotsEqual(
|
|
3181
|
+
left: InteractionGuardSnapshot,
|
|
3182
|
+
right: InteractionGuardSnapshot,
|
|
3183
|
+
): boolean {
|
|
3184
|
+
if (left === right) {
|
|
3185
|
+
return true;
|
|
3186
|
+
}
|
|
3187
|
+
return workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons);
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
function workflowBlockedReasonsEqual(
|
|
3191
|
+
left: readonly WorkflowBlockedCommandReason[],
|
|
3192
|
+
right: readonly WorkflowBlockedCommandReason[],
|
|
3193
|
+
): boolean {
|
|
3194
|
+
if (left === right) {
|
|
3195
|
+
return true;
|
|
3196
|
+
}
|
|
3197
|
+
if (left.length !== right.length) {
|
|
3198
|
+
return false;
|
|
3199
|
+
}
|
|
3200
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
3201
|
+
if (!workflowBlockedReasonEqual(left[index]!, right[index]!)) {
|
|
3202
|
+
return false;
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
return true;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
function workflowBlockedReasonEqual(
|
|
3209
|
+
left: WorkflowBlockedCommandReason,
|
|
3210
|
+
right: WorkflowBlockedCommandReason,
|
|
3211
|
+
): boolean {
|
|
3212
|
+
return (
|
|
3213
|
+
left.code === right.code &&
|
|
3214
|
+
left.message === right.message &&
|
|
3215
|
+
left.scopeId === right.scopeId &&
|
|
3216
|
+
left.workItemId === right.workItemId &&
|
|
3217
|
+
editorAnchorProjectionEqual(left.anchor, right.anchor) &&
|
|
3218
|
+
storyTargetsEqual(left.storyTarget, right.storyTarget)
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
function editorAnchorProjectionEqual(
|
|
3223
|
+
left: EditorAnchorProjection | undefined,
|
|
3224
|
+
right: EditorAnchorProjection | undefined,
|
|
3225
|
+
): boolean {
|
|
3226
|
+
if (left === right) {
|
|
3227
|
+
return true;
|
|
3228
|
+
}
|
|
3229
|
+
if (!left || !right || left.kind !== right.kind) {
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
switch (left.kind) {
|
|
3234
|
+
case "range":
|
|
3235
|
+
return (
|
|
3236
|
+
right.kind === "range" &&
|
|
3237
|
+
left.from === right.from &&
|
|
3238
|
+
left.to === right.to &&
|
|
3239
|
+
left.assoc.start === right.assoc.start &&
|
|
3240
|
+
left.assoc.end === right.assoc.end
|
|
3241
|
+
);
|
|
3242
|
+
case "node":
|
|
3243
|
+
return right.kind === "node" && left.at === right.at && left.assoc === right.assoc;
|
|
3244
|
+
case "detached":
|
|
3245
|
+
return (
|
|
3246
|
+
right.kind === "detached" &&
|
|
3247
|
+
left.lastKnownRange.from === right.lastKnownRange.from &&
|
|
3248
|
+
left.lastKnownRange.to === right.lastKnownRange.to &&
|
|
3249
|
+
left.reason === right.reason
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
return false;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3828
3256
|
function createSelectionToolbarSelectionKey(
|
|
3829
3257
|
selection: RuntimeRenderSnapshot["selection"],
|
|
3830
3258
|
activeStory: EditorStoryTarget,
|
|
@@ -3948,3 +3376,176 @@ function createSelectionToolbarListBadge(
|
|
|
3948
3376
|
label: viewState.activeListContext.isOrdered ? "Numbered list" : "Bulleted list",
|
|
3949
3377
|
};
|
|
3950
3378
|
}
|
|
3379
|
+
|
|
3380
|
+
function buildActiveImageContext(args: {
|
|
3381
|
+
canonicalDocument: PersistedEditorSnapshot["canonicalDocument"];
|
|
3382
|
+
selection: RuntimeRenderSnapshot["selection"];
|
|
3383
|
+
storyTarget: EditorStoryTarget;
|
|
3384
|
+
surface?: RuntimeRenderSnapshot["surface"];
|
|
3385
|
+
}): {
|
|
3386
|
+
mediaId: string;
|
|
3387
|
+
display: "inline" | "floating";
|
|
3388
|
+
widthEmu?: number;
|
|
3389
|
+
heightEmu?: number;
|
|
3390
|
+
horizontalOffsetEmu?: number;
|
|
3391
|
+
verticalOffsetEmu?: number;
|
|
3392
|
+
} | null {
|
|
3393
|
+
const imageSegment = findSelectedImageSegment(args.surface, args.selection);
|
|
3394
|
+
if (!imageSegment) {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
const storyBlocks = getStoryBlocks(args.canonicalDocument, args.storyTarget);
|
|
3399
|
+
const imageNode = findImageNodeByMediaId(storyBlocks, imageSegment.mediaId);
|
|
3400
|
+
const mediaItem = args.canonicalDocument.media.items[imageSegment.mediaId];
|
|
3401
|
+
|
|
3402
|
+
return {
|
|
3403
|
+
mediaId: imageSegment.mediaId,
|
|
3404
|
+
display: imageSegment.display === "floating" ? "floating" : "inline",
|
|
3405
|
+
widthEmu: mediaItem?.widthEmu,
|
|
3406
|
+
heightEmu: mediaItem?.heightEmu,
|
|
3407
|
+
horizontalOffsetEmu: imageNode?.floating?.horizontalPosition?.offset,
|
|
3408
|
+
verticalOffsetEmu: imageNode?.floating?.verticalPosition?.offset,
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
function findSelectedImageSegment(
|
|
3413
|
+
surface: RuntimeRenderSnapshot["surface"] | undefined,
|
|
3414
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
3415
|
+
): Extract<NonNullable<RuntimeRenderSnapshot["surface"]>["blocks"][number], { kind: "paragraph" }>["segments"][number] & {
|
|
3416
|
+
kind: "image";
|
|
3417
|
+
} | null {
|
|
3418
|
+
if (!surface) {
|
|
3419
|
+
return null;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
const position =
|
|
3423
|
+
selection.activeRange.kind === "node" ? selection.activeRange.at : selection.head;
|
|
3424
|
+
|
|
3425
|
+
return findSelectedImageSegmentInBlocks(surface.blocks, position);
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
function findSelectedImageSegmentInBlocks(
|
|
3429
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
3430
|
+
position: number,
|
|
3431
|
+
): Extract<NonNullable<RuntimeRenderSnapshot["surface"]>["blocks"][number], { kind: "paragraph" }>["segments"][number] & {
|
|
3432
|
+
kind: "image";
|
|
3433
|
+
} | null {
|
|
3434
|
+
for (const block of blocks) {
|
|
3435
|
+
if (position < block.from || position > block.to) {
|
|
3436
|
+
continue;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
if (block.kind === "paragraph") {
|
|
3440
|
+
const imageSegment = block.segments.find(
|
|
3441
|
+
(segment) => segment.kind === "image" && position >= segment.from && position <= segment.to,
|
|
3442
|
+
);
|
|
3443
|
+
if (imageSegment && imageSegment.kind === "image") {
|
|
3444
|
+
return imageSegment;
|
|
3445
|
+
}
|
|
3446
|
+
continue;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
if (block.kind === "table") {
|
|
3450
|
+
for (const row of block.rows) {
|
|
3451
|
+
for (const cell of row.cells) {
|
|
3452
|
+
const match = findSelectedImageSegmentInBlocks(cell.content, position);
|
|
3453
|
+
if (match) {
|
|
3454
|
+
return match;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
if (block.kind === "sdt_block") {
|
|
3462
|
+
const match = findSelectedImageSegmentInBlocks(block.children, position);
|
|
3463
|
+
if (match) {
|
|
3464
|
+
return match;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
function findImageNodeByMediaId(blocks: readonly unknown[], mediaId: string): {
|
|
3473
|
+
floating?: {
|
|
3474
|
+
horizontalPosition?: { offset?: number };
|
|
3475
|
+
verticalPosition?: { offset?: number };
|
|
3476
|
+
};
|
|
3477
|
+
} | null {
|
|
3478
|
+
for (const block of blocks) {
|
|
3479
|
+
const match = findImageNodeInValue(block, mediaId);
|
|
3480
|
+
if (match) {
|
|
3481
|
+
return match;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
return null;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
function findImageNodeInValue(
|
|
3488
|
+
value: unknown,
|
|
3489
|
+
mediaId: string,
|
|
3490
|
+
): {
|
|
3491
|
+
floating?: {
|
|
3492
|
+
horizontalPosition?: { offset?: number };
|
|
3493
|
+
verticalPosition?: { offset?: number };
|
|
3494
|
+
};
|
|
3495
|
+
} | null {
|
|
3496
|
+
if (!value || typeof value !== "object") {
|
|
3497
|
+
return null;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
const record = value as {
|
|
3501
|
+
type?: string;
|
|
3502
|
+
mediaId?: string;
|
|
3503
|
+
children?: unknown[];
|
|
3504
|
+
rows?: Array<{ cells?: Array<{ children?: unknown[] }> }>;
|
|
3505
|
+
floating?: {
|
|
3506
|
+
horizontalPosition?: { offset?: number };
|
|
3507
|
+
verticalPosition?: { offset?: number };
|
|
3508
|
+
};
|
|
3509
|
+
};
|
|
3510
|
+
|
|
3511
|
+
if (record.type === "image" && record.mediaId === mediaId) {
|
|
3512
|
+
return record;
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
if (Array.isArray(record.children)) {
|
|
3516
|
+
for (const child of record.children) {
|
|
3517
|
+
const match = findImageNodeInValue(child, mediaId);
|
|
3518
|
+
if (match) {
|
|
3519
|
+
return match;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
if (Array.isArray(record.rows)) {
|
|
3525
|
+
for (const row of record.rows) {
|
|
3526
|
+
for (const cell of row.cells ?? []) {
|
|
3527
|
+
for (const child of cell.children ?? []) {
|
|
3528
|
+
const match = findImageNodeInValue(child, mediaId);
|
|
3529
|
+
if (match) {
|
|
3530
|
+
return match;
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
return null;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
function createImageDataUrl(contentType: string, bytes: Uint8Array): string {
|
|
3541
|
+
const base64 = bytesToBase64(bytes);
|
|
3542
|
+
return `data:${contentType};base64,${base64}`;
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
3546
|
+
let binary = "";
|
|
3547
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
3548
|
+
binary += String.fromCharCode(bytes[index] ?? 0);
|
|
3549
|
+
}
|
|
3550
|
+
return btoa(binary);
|
|
3551
|
+
}
|