@beyondwork/docx-react-component 1.0.19 → 1.0.20
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 +1 -1
- 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 +850 -1315
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1422 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +51 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -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 +35 -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 +51 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +174 -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 +4 -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,21 @@ 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
|
+
WorkflowScopeSnapshot,
|
|
41
49
|
WordReviewEditorEvent,
|
|
42
50
|
WordReviewEditorProps,
|
|
43
51
|
WordReviewEditorRef,
|
|
44
52
|
WorkspaceMode,
|
|
45
53
|
ZoomLevel,
|
|
46
54
|
} from "../api/public-types";
|
|
47
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
editorSessionStateFromPersistedSnapshot,
|
|
57
|
+
persistedSnapshotFromEditorSessionState,
|
|
58
|
+
} from "../api/session-state.ts";
|
|
48
59
|
import {
|
|
49
60
|
createDetachedAnchor,
|
|
50
61
|
createNodeAnchor,
|
|
@@ -98,81 +109,66 @@ import {
|
|
|
98
109
|
insertTable as insertTableInDocument,
|
|
99
110
|
splitParagraph as splitParagraphInDocument,
|
|
100
111
|
} 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";
|
|
112
|
+
import { type SelectionSnapshot as InternalSelectionSnapshot } from "../core/state/editor-state.ts";
|
|
109
113
|
import {
|
|
110
114
|
getStoryBlocks,
|
|
111
115
|
replaceStoryBlocks,
|
|
112
116
|
} from "../runtime/story-targeting.ts";
|
|
113
|
-
import { loadDocxEditorSession } from "../io/docx-session.ts";
|
|
114
117
|
import {
|
|
115
118
|
decodePersistedSourcePackageBytes,
|
|
116
119
|
hasValidPersistedSourcePackageDigest,
|
|
117
120
|
} from "../io/source-package-provenance.ts";
|
|
121
|
+
import { readOpcPackage } from "../io/opc/package-reader.ts";
|
|
118
122
|
import { deriveCapabilities } from "../runtime/session-capabilities";
|
|
119
123
|
import { searchDocument } from "../runtime/document-search.ts";
|
|
120
124
|
import {
|
|
121
|
-
|
|
125
|
+
createEditorViewStateSnapshot,
|
|
126
|
+
createViewState,
|
|
127
|
+
} from "../runtime/view-state.ts";
|
|
128
|
+
import {
|
|
122
129
|
type TwProseMirrorSurfaceRef,
|
|
123
130
|
} from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
|
|
124
|
-
import {
|
|
131
|
+
import type { MediaPreviewDescriptor } from "../ui-tailwind/editor-surface/pm-state-from-snapshot";
|
|
132
|
+
import {
|
|
133
|
+
incrementInvalidationCounter,
|
|
134
|
+
recordPerfSample,
|
|
135
|
+
} from "../ui-tailwind/editor-surface/perf-probe.ts";
|
|
125
136
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail";
|
|
137
|
+
import {
|
|
138
|
+
selectMetaSlice,
|
|
139
|
+
selectReviewSlice,
|
|
140
|
+
selectStatusSlice,
|
|
141
|
+
selectSurfaceSlice,
|
|
142
|
+
selectToolbarSlice,
|
|
143
|
+
selectViewSlice,
|
|
144
|
+
shallowEqualRecord,
|
|
145
|
+
useRuntimeSnapshotSlice,
|
|
146
|
+
useRuntimeValue,
|
|
147
|
+
} from "./runtime-snapshot-selectors.ts";
|
|
126
148
|
import type { MarkupDisplay } from "./headless/comment-decoration-model";
|
|
127
149
|
import type {
|
|
128
150
|
SelectionToolbarAnchor,
|
|
129
151
|
SelectionToolbarModel,
|
|
130
152
|
} from "./headless/selection-toolbar-model";
|
|
153
|
+
import { type EditorCommandBag, useCommandBag } from "./editor-command-bag.ts";
|
|
154
|
+
import {
|
|
155
|
+
type WordReviewEditorRuntime,
|
|
156
|
+
persistAndExport as persistAndExportFromBoundary,
|
|
157
|
+
persistSession as persistSessionFromBoundary,
|
|
158
|
+
rejectExportWhileLoading as rejectExportWhileLoadingFromBoundary,
|
|
159
|
+
useEditorRuntimeBoundary,
|
|
160
|
+
} from "./editor-runtime-boundary.ts";
|
|
131
161
|
import {
|
|
132
162
|
downloadExportResult,
|
|
133
163
|
withExportDelivery,
|
|
134
164
|
} from "./browser-export";
|
|
165
|
+
import { EditorShellView } from "./editor-shell-view.tsx";
|
|
166
|
+
import { EditorSurfaceController } from "./editor-surface-controller.tsx";
|
|
135
167
|
|
|
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
|
-
}
|
|
168
|
+
export {
|
|
169
|
+
__createFallbackRuntime,
|
|
170
|
+
__resolveWordReviewEditorSource,
|
|
171
|
+
} from "./editor-runtime-boundary.ts";
|
|
176
172
|
|
|
177
173
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
178
174
|
position: "absolute",
|
|
@@ -186,6 +182,15 @@ const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
|
186
182
|
border: 0,
|
|
187
183
|
};
|
|
188
184
|
|
|
185
|
+
const BROWSER_SAFE_PREVIEW_TYPES = new Set([
|
|
186
|
+
"image/png",
|
|
187
|
+
"image/jpeg",
|
|
188
|
+
"image/jpg",
|
|
189
|
+
"image/gif",
|
|
190
|
+
"image/webp",
|
|
191
|
+
"image/bmp",
|
|
192
|
+
]);
|
|
193
|
+
|
|
189
194
|
const ACCESSIBLE_REGION_ORDER = [
|
|
190
195
|
"toolbar",
|
|
191
196
|
"document",
|
|
@@ -392,13 +397,28 @@ export function __createWordReviewEditorRefBridge(
|
|
|
392
397
|
runtime.closeStory();
|
|
393
398
|
},
|
|
394
399
|
getPageLayoutSnapshot: () => {
|
|
395
|
-
return runtime.getPageLayoutSnapshot();
|
|
400
|
+
return clonePublicValue(runtime.getPageLayoutSnapshot());
|
|
396
401
|
},
|
|
397
402
|
getDocumentNavigationSnapshot: () => {
|
|
398
|
-
return runtime.getDocumentNavigationSnapshot();
|
|
403
|
+
return clonePublicValue(runtime.getDocumentNavigationSnapshot());
|
|
404
|
+
},
|
|
405
|
+
getFieldSnapshot: () => {
|
|
406
|
+
return clonePublicValue(runtime.getFieldSnapshot());
|
|
407
|
+
},
|
|
408
|
+
updateFields: (options) => {
|
|
409
|
+
return runtime.updateFields(options);
|
|
410
|
+
},
|
|
411
|
+
updateTableOfContents: (options) => {
|
|
412
|
+
return runtime.updateTableOfContents(options);
|
|
399
413
|
},
|
|
400
414
|
getViewState: () => {
|
|
401
|
-
return runtime.getViewState();
|
|
415
|
+
return clonePublicValue(runtime.getViewState());
|
|
416
|
+
},
|
|
417
|
+
setDocumentMode: (mode) => {
|
|
418
|
+
runtime.setDocumentMode(mode);
|
|
419
|
+
},
|
|
420
|
+
getProtectionSnapshot: () => {
|
|
421
|
+
return clonePublicValue(runtime.getProtectionSnapshot());
|
|
402
422
|
},
|
|
403
423
|
setWorkspaceMode: (mode) => {
|
|
404
424
|
runtime.setWorkspaceMode(mode);
|
|
@@ -427,195 +447,28 @@ export function __createWordReviewEditorRefBridge(
|
|
|
427
447
|
setImageFrame: (mediaId, offsets) => {
|
|
428
448
|
applyRuntimeImageReposition(runtime, mediaId, offsets);
|
|
429
449
|
},
|
|
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
|
-
);
|
|
450
|
+
setWorkflowOverlay: (overlay) => {
|
|
451
|
+
runtime.setWorkflowOverlay(clonePublicValue(overlay));
|
|
614
452
|
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
453
|
+
clearWorkflowOverlay: () => {
|
|
454
|
+
runtime.clearWorkflowOverlay();
|
|
455
|
+
},
|
|
456
|
+
getWorkflowScopeSnapshot: () => {
|
|
457
|
+
return clonePublicValue(runtime.getWorkflowScopeSnapshot());
|
|
458
|
+
},
|
|
459
|
+
getInteractionGuardSnapshot: () => {
|
|
460
|
+
return clonePublicValue(runtime.getInteractionGuardSnapshot());
|
|
461
|
+
},
|
|
462
|
+
getWorkflowMarkupSnapshot: () => {
|
|
463
|
+
return clonePublicValue(runtime.getWorkflowMarkupSnapshot());
|
|
464
|
+
},
|
|
465
|
+
getWorkflowCandidateRanges: (options) => {
|
|
466
|
+
return clonePublicValue(runtime.getWorkflowCandidateRanges(options));
|
|
467
|
+
},
|
|
468
|
+
replaceWorkflowMarkupText: (markupId, text) => {
|
|
469
|
+
runtime.replaceWorkflowMarkupText(markupId, text);
|
|
470
|
+
},
|
|
471
|
+
};
|
|
619
472
|
}
|
|
620
473
|
|
|
621
474
|
export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditorProps>(
|
|
@@ -633,7 +486,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
633
486
|
initialSessionState,
|
|
634
487
|
initialSnapshot,
|
|
635
488
|
initialSourceLabel,
|
|
636
|
-
markupDisplay
|
|
489
|
+
markupDisplay,
|
|
637
490
|
onError,
|
|
638
491
|
onEvent,
|
|
639
492
|
onWarning,
|
|
@@ -642,261 +495,161 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
642
495
|
showReviewPanel = true,
|
|
643
496
|
} = props;
|
|
644
497
|
|
|
645
|
-
const [runtime, setRuntime] = useState<WordReviewEditorRuntime | null>(null);
|
|
646
|
-
const [loadError, setLoadError] = useState<EditorError | null>(null);
|
|
647
498
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
648
499
|
const [showTrackedChanges, setShowTrackedChanges] = useState(false);
|
|
649
500
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
650
501
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
651
502
|
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
652
503
|
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
653
|
-
const runtimeRef = useRef<WordReviewEditorRuntime | null>(null);
|
|
654
504
|
const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
|
|
655
505
|
const selectionToolbarElementRef = useRef<HTMLDivElement | null>(null);
|
|
656
506
|
const shellRef = useRef<HTMLDivElement | null>(null);
|
|
657
507
|
const lastSelectionToolbarKeyRef = useRef<string | null>(null);
|
|
658
508
|
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
|
-
],
|
|
509
|
+
const {
|
|
510
|
+
runtime,
|
|
511
|
+
loadError,
|
|
512
|
+
activeRuntime,
|
|
513
|
+
fallbackSnapshot,
|
|
514
|
+
loadingSessionState,
|
|
515
|
+
loadingViewState,
|
|
516
|
+
loadingNavigation,
|
|
517
|
+
hostAdapterRef,
|
|
518
|
+
datastoreRef,
|
|
519
|
+
onEventRef,
|
|
520
|
+
onWarningRef,
|
|
521
|
+
onErrorRef,
|
|
522
|
+
autosaveTimerRef,
|
|
523
|
+
lastSavedRevisionTokenRef,
|
|
524
|
+
runtimeViewStateSeedRef,
|
|
525
|
+
} = useEditorRuntimeBoundary(props);
|
|
526
|
+
const metaSlice = useRuntimeSnapshotSlice(
|
|
527
|
+
runtime,
|
|
528
|
+
fallbackSnapshot,
|
|
529
|
+
selectMetaSlice,
|
|
530
|
+
shallowEqualRecord,
|
|
861
531
|
);
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
532
|
+
const toolbarSlice = useRuntimeSnapshotSlice(
|
|
533
|
+
runtime,
|
|
534
|
+
fallbackSnapshot,
|
|
535
|
+
selectToolbarSlice,
|
|
536
|
+
shallowEqualRecord,
|
|
537
|
+
);
|
|
538
|
+
const surfaceSlice = useRuntimeSnapshotSlice(
|
|
539
|
+
runtime,
|
|
540
|
+
fallbackSnapshot,
|
|
541
|
+
selectSurfaceSlice,
|
|
542
|
+
shallowEqualRecord,
|
|
543
|
+
);
|
|
544
|
+
const reviewSlice = useRuntimeSnapshotSlice(
|
|
545
|
+
runtime,
|
|
546
|
+
fallbackSnapshot,
|
|
547
|
+
selectReviewSlice,
|
|
548
|
+
shallowEqualRecord,
|
|
549
|
+
);
|
|
550
|
+
const viewSlice = useRuntimeSnapshotSlice(
|
|
551
|
+
runtime,
|
|
552
|
+
fallbackSnapshot,
|
|
553
|
+
selectViewSlice,
|
|
554
|
+
shallowEqualRecord,
|
|
555
|
+
);
|
|
556
|
+
const statusSlice = useRuntimeSnapshotSlice(
|
|
557
|
+
runtime,
|
|
558
|
+
fallbackSnapshot,
|
|
559
|
+
selectStatusSlice,
|
|
560
|
+
shallowEqualRecord,
|
|
561
|
+
);
|
|
562
|
+
const snapshot = useMemo(
|
|
563
|
+
() => ({
|
|
564
|
+
documentId: metaSlice.documentId,
|
|
565
|
+
sessionId: metaSlice.sessionId,
|
|
566
|
+
sourceLabel: metaSlice.sourceLabel,
|
|
567
|
+
revisionToken: surfaceSlice.revisionToken,
|
|
568
|
+
isReady: toolbarSlice.isReady,
|
|
569
|
+
isDirty: statusSlice.isDirty,
|
|
570
|
+
readOnly: toolbarSlice.readOnly,
|
|
571
|
+
documentMode: viewSlice.documentMode,
|
|
572
|
+
selection: surfaceSlice.selection,
|
|
573
|
+
activeStory: viewSlice.activeStory,
|
|
574
|
+
pageLayout: viewSlice.pageLayout,
|
|
575
|
+
documentStats: statusSlice.documentStats,
|
|
576
|
+
comments: reviewSlice.comments,
|
|
577
|
+
trackedChanges: reviewSlice.trackedChanges,
|
|
578
|
+
compatibility: reviewSlice.compatibility,
|
|
579
|
+
warnings: statusSlice.warnings,
|
|
580
|
+
fatalError: statusSlice.fatalError,
|
|
581
|
+
commandState: toolbarSlice.commandState,
|
|
582
|
+
surface: surfaceSlice.surface,
|
|
583
|
+
protectionSnapshot: statusSlice.protectionSnapshot,
|
|
584
|
+
}),
|
|
877
585
|
[
|
|
878
|
-
documentId,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
586
|
+
metaSlice.documentId,
|
|
587
|
+
metaSlice.sessionId,
|
|
588
|
+
metaSlice.sourceLabel,
|
|
589
|
+
surfaceSlice.revisionToken,
|
|
590
|
+
surfaceSlice.selection,
|
|
591
|
+
surfaceSlice.surface,
|
|
592
|
+
toolbarSlice.isReady,
|
|
593
|
+
toolbarSlice.readOnly,
|
|
594
|
+
toolbarSlice.commandState,
|
|
595
|
+
statusSlice.isDirty,
|
|
596
|
+
statusSlice.documentStats,
|
|
597
|
+
statusSlice.warnings,
|
|
598
|
+
statusSlice.fatalError,
|
|
599
|
+
statusSlice.protectionSnapshot,
|
|
600
|
+
reviewSlice.comments,
|
|
601
|
+
reviewSlice.trackedChanges,
|
|
602
|
+
reviewSlice.compatibility,
|
|
603
|
+
viewSlice.documentMode,
|
|
604
|
+
viewSlice.activeStory,
|
|
605
|
+
viewSlice.pageLayout,
|
|
885
606
|
],
|
|
886
607
|
);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
608
|
+
const viewState = useRuntimeValue(
|
|
609
|
+
runtime
|
|
610
|
+
? {
|
|
611
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
612
|
+
getValue: () => runtime.getViewState(),
|
|
613
|
+
}
|
|
614
|
+
: null,
|
|
615
|
+
loadingViewState,
|
|
892
616
|
);
|
|
893
|
-
|
|
894
|
-
const activeRuntime = runtime ?? optimisticRuntime;
|
|
895
|
-
const viewState = activeRuntime.getViewState();
|
|
896
617
|
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
897
|
-
const liveMarkupDisplay
|
|
898
|
-
const
|
|
899
|
-
|
|
618
|
+
const liveMarkupDisplay = __resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
619
|
+
const documentNavigation = useRuntimeValue(
|
|
620
|
+
runtime
|
|
621
|
+
? {
|
|
622
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
623
|
+
getValue: () => runtime.getDocumentNavigationSnapshot(),
|
|
624
|
+
}
|
|
625
|
+
: null,
|
|
626
|
+
loadingNavigation,
|
|
627
|
+
);
|
|
628
|
+
const workflowScopeSnapshot = useRuntimeValue(
|
|
629
|
+
runtime
|
|
630
|
+
? {
|
|
631
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
632
|
+
getValue: () => runtime.getWorkflowScopeSnapshot(),
|
|
633
|
+
}
|
|
634
|
+
: null,
|
|
635
|
+
null,
|
|
636
|
+
workflowScopeSnapshotsEqual,
|
|
637
|
+
);
|
|
638
|
+
const interactionGuardSnapshot = useRuntimeValue(
|
|
639
|
+
runtime
|
|
640
|
+
? {
|
|
641
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
642
|
+
getValue: () => runtime.getInteractionGuardSnapshot(),
|
|
643
|
+
}
|
|
644
|
+
: null,
|
|
645
|
+
{ blockedReasons: [] } satisfies InteractionGuardSnapshot,
|
|
646
|
+
interactionGuardSnapshotsEqual,
|
|
647
|
+
);
|
|
648
|
+
const sessionState = useMemo(
|
|
649
|
+
() => (runtime ? runtime.getSessionState() : loadingSessionState),
|
|
650
|
+
[loadingSessionState, runtime, snapshot.revisionToken],
|
|
651
|
+
);
|
|
652
|
+
const canonicalDocument = sessionState.canonicalDocument;
|
|
900
653
|
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
901
654
|
|
|
902
655
|
useEffect(() => {
|
|
@@ -910,6 +663,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
910
663
|
};
|
|
911
664
|
}, [viewState.workspaceMode, viewState.zoomLevel]);
|
|
912
665
|
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
recordPerfSample("shell.render");
|
|
668
|
+
incrementInvalidationCounter("shell.rerenders");
|
|
669
|
+
}, [snapshot.revisionToken, snapshot.selection, viewState, documentNavigation]);
|
|
670
|
+
|
|
913
671
|
useImperativeHandle(
|
|
914
672
|
ref,
|
|
915
673
|
() => ({
|
|
@@ -939,7 +697,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
939
697
|
rejectAllChanges: () => activeRuntime.rejectAllChanges(),
|
|
940
698
|
exportDocx: (options) =>
|
|
941
699
|
runtime
|
|
942
|
-
?
|
|
700
|
+
? persistAndExportFromBoundary({
|
|
943
701
|
hostAdapter: hostAdapterRef.current,
|
|
944
702
|
datastore: datastoreRef.current,
|
|
945
703
|
documentId,
|
|
@@ -950,7 +708,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
950
708
|
lastSavedRevisionTokenRef,
|
|
951
709
|
autosaveTimerRef,
|
|
952
710
|
})
|
|
953
|
-
:
|
|
711
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
954
712
|
documentId,
|
|
955
713
|
hostAdapter: hostAdapterRef.current,
|
|
956
714
|
datastore: datastoreRef.current,
|
|
@@ -1149,13 +907,28 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1149
907
|
activeRuntime.closeStory();
|
|
1150
908
|
},
|
|
1151
909
|
getPageLayoutSnapshot: () => {
|
|
1152
|
-
return activeRuntime.getPageLayoutSnapshot();
|
|
910
|
+
return clonePublicValue(activeRuntime.getPageLayoutSnapshot());
|
|
1153
911
|
},
|
|
1154
912
|
getDocumentNavigationSnapshot: () => {
|
|
1155
|
-
return activeRuntime.getDocumentNavigationSnapshot();
|
|
913
|
+
return clonePublicValue(activeRuntime.getDocumentNavigationSnapshot());
|
|
914
|
+
},
|
|
915
|
+
getFieldSnapshot: () => {
|
|
916
|
+
return clonePublicValue(activeRuntime.getFieldSnapshot());
|
|
917
|
+
},
|
|
918
|
+
updateFields: (options) => {
|
|
919
|
+
return activeRuntime.updateFields(options);
|
|
920
|
+
},
|
|
921
|
+
updateTableOfContents: (options) => {
|
|
922
|
+
return activeRuntime.updateTableOfContents(options);
|
|
1156
923
|
},
|
|
1157
924
|
getViewState: () => {
|
|
1158
|
-
return activeRuntime.getViewState();
|
|
925
|
+
return clonePublicValue(activeRuntime.getViewState());
|
|
926
|
+
},
|
|
927
|
+
setDocumentMode: (mode) => {
|
|
928
|
+
activeRuntime.setDocumentMode(mode);
|
|
929
|
+
},
|
|
930
|
+
getProtectionSnapshot: () => {
|
|
931
|
+
return clonePublicValue(activeRuntime.getProtectionSnapshot());
|
|
1159
932
|
},
|
|
1160
933
|
setWorkspaceMode: (mode) => {
|
|
1161
934
|
activeRuntime.setWorkspaceMode(mode);
|
|
@@ -1184,6 +957,27 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1184
957
|
setImageFrame: (mediaId, offsets) => {
|
|
1185
958
|
applyRuntimeImageReposition(activeRuntime, mediaId, offsets);
|
|
1186
959
|
},
|
|
960
|
+
setWorkflowOverlay: (overlay) => {
|
|
961
|
+
activeRuntime.setWorkflowOverlay(clonePublicValue(overlay));
|
|
962
|
+
},
|
|
963
|
+
clearWorkflowOverlay: () => {
|
|
964
|
+
activeRuntime.clearWorkflowOverlay();
|
|
965
|
+
},
|
|
966
|
+
getWorkflowScopeSnapshot: () => {
|
|
967
|
+
return clonePublicValue(activeRuntime.getWorkflowScopeSnapshot());
|
|
968
|
+
},
|
|
969
|
+
getInteractionGuardSnapshot: () => {
|
|
970
|
+
return clonePublicValue(activeRuntime.getInteractionGuardSnapshot());
|
|
971
|
+
},
|
|
972
|
+
getWorkflowMarkupSnapshot: () => {
|
|
973
|
+
return clonePublicValue(activeRuntime.getWorkflowMarkupSnapshot());
|
|
974
|
+
},
|
|
975
|
+
getWorkflowCandidateRanges: (options) => {
|
|
976
|
+
return clonePublicValue(activeRuntime.getWorkflowCandidateRanges(options));
|
|
977
|
+
},
|
|
978
|
+
replaceWorkflowMarkupText: (markupId, text) => {
|
|
979
|
+
activeRuntime.replaceWorkflowMarkupText(markupId, text);
|
|
980
|
+
},
|
|
1187
981
|
}),
|
|
1188
982
|
[activeRuntime, currentUser.userId, documentId, runtime],
|
|
1189
983
|
);
|
|
@@ -1213,7 +1007,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1213
1007
|
|
|
1214
1008
|
const debounceMs = props.autosave?.debounceMs ?? 800;
|
|
1215
1009
|
if (debounceMs <= 0) {
|
|
1216
|
-
void
|
|
1010
|
+
void persistSessionFromBoundary({
|
|
1217
1011
|
hostAdapter: hostAdapterRef.current,
|
|
1218
1012
|
datastore: datastoreRef.current,
|
|
1219
1013
|
documentId,
|
|
@@ -1227,7 +1021,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1227
1021
|
}
|
|
1228
1022
|
|
|
1229
1023
|
autosaveTimerRef.current = setTimeout(() => {
|
|
1230
|
-
void
|
|
1024
|
+
void persistSessionFromBoundary({
|
|
1231
1025
|
hostAdapter: hostAdapterRef.current,
|
|
1232
1026
|
datastore: datastoreRef.current,
|
|
1233
1027
|
documentId,
|
|
@@ -1301,7 +1095,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1301
1095
|
|
|
1302
1096
|
function exportCurrentDocument(): void {
|
|
1303
1097
|
void (runtime
|
|
1304
|
-
?
|
|
1098
|
+
? persistAndExportFromBoundary({
|
|
1305
1099
|
hostAdapter: hostAdapterRef.current,
|
|
1306
1100
|
datastore: datastoreRef.current,
|
|
1307
1101
|
documentId,
|
|
@@ -1311,7 +1105,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1311
1105
|
lastSavedRevisionTokenRef,
|
|
1312
1106
|
autosaveTimerRef,
|
|
1313
1107
|
})
|
|
1314
|
-
:
|
|
1108
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
1315
1109
|
documentId,
|
|
1316
1110
|
hostAdapter: hostAdapterRef.current,
|
|
1317
1111
|
datastore: datastoreRef.current,
|
|
@@ -1320,20 +1114,89 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1320
1114
|
}));
|
|
1321
1115
|
}
|
|
1322
1116
|
|
|
1323
|
-
const derivedCapabilities = deriveCapabilities(
|
|
1117
|
+
const derivedCapabilities = deriveCapabilities(
|
|
1118
|
+
snapshot,
|
|
1119
|
+
reviewMode,
|
|
1120
|
+
workflowScopeSnapshot,
|
|
1121
|
+
);
|
|
1324
1122
|
const capabilities = showReviewPanel
|
|
1325
1123
|
? derivedCapabilities
|
|
1326
1124
|
: { ...derivedCapabilities, reviewRailVisible: false };
|
|
1327
1125
|
const formattingState = getFormattingStateFromRenderSnapshot(snapshot);
|
|
1328
1126
|
const styleCatalog = useMemo(
|
|
1329
|
-
() => getRuntimeStyleCatalog(
|
|
1330
|
-
[
|
|
1127
|
+
() => getRuntimeStyleCatalog(canonicalDocument.styles),
|
|
1128
|
+
[canonicalDocument.styles],
|
|
1331
1129
|
);
|
|
1332
1130
|
const diagnosticsModeMessage = getDiagnosticsModeMessage(loadError ?? snapshot.fatalError);
|
|
1333
1131
|
const addCommentDisabledReason =
|
|
1334
1132
|
!capabilities.canAddComment && !snapshot.selection.isCollapsed
|
|
1335
1133
|
? "Select text within one paragraph to add a DOCX comment."
|
|
1336
1134
|
: undefined;
|
|
1135
|
+
const activeImageContext = useMemo(
|
|
1136
|
+
() =>
|
|
1137
|
+
buildActiveImageContext({
|
|
1138
|
+
canonicalDocument,
|
|
1139
|
+
selection: snapshot.selection,
|
|
1140
|
+
storyTarget: viewState.activeStory,
|
|
1141
|
+
surface: snapshot.surface,
|
|
1142
|
+
}),
|
|
1143
|
+
[canonicalDocument, snapshot.selection, snapshot.surface, viewState.activeStory],
|
|
1144
|
+
);
|
|
1145
|
+
const sourcePackage = sessionState.sourcePackage;
|
|
1146
|
+
const mediaPreviewCatalogKey = Object.values(canonicalDocument.media.items)
|
|
1147
|
+
.map((item) =>
|
|
1148
|
+
[
|
|
1149
|
+
item.mediaId,
|
|
1150
|
+
item.packagePartName,
|
|
1151
|
+
item.contentType ?? "",
|
|
1152
|
+
item.widthEmu ?? "",
|
|
1153
|
+
item.heightEmu ?? "",
|
|
1154
|
+
].join(":"),
|
|
1155
|
+
)
|
|
1156
|
+
.sort()
|
|
1157
|
+
.join("|");
|
|
1158
|
+
const mediaPreviews = useMemo(() => {
|
|
1159
|
+
if (!sourcePackage) {
|
|
1160
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
const bytes = decodePersistedSourcePackageBytes(sourcePackage);
|
|
1164
|
+
if (!hasValidPersistedSourcePackageDigest(sourcePackage, bytes)) {
|
|
1165
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1166
|
+
}
|
|
1167
|
+
const opc = readOpcPackage(bytes);
|
|
1168
|
+
const previews: Record<string, MediaPreviewDescriptor> = {};
|
|
1169
|
+
for (const item of Object.values(canonicalDocument.media.items)) {
|
|
1170
|
+
const contentType = item.contentType?.toLowerCase();
|
|
1171
|
+
const part = opc.parts.get(item.packagePartName);
|
|
1172
|
+
if (
|
|
1173
|
+
!part?.bytes ||
|
|
1174
|
+
!contentType ||
|
|
1175
|
+
!BROWSER_SAFE_PREVIEW_TYPES.has(contentType)
|
|
1176
|
+
) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
previews[item.mediaId] = {
|
|
1180
|
+
src: createImageDataUrl(contentType, part.bytes),
|
|
1181
|
+
...(item.widthEmu !== undefined ? { widthEmu: item.widthEmu } : {}),
|
|
1182
|
+
...(item.heightEmu !== undefined ? { heightEmu: item.heightEmu } : {}),
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return previews;
|
|
1186
|
+
} catch {
|
|
1187
|
+
return {} as Record<string, MediaPreviewDescriptor>;
|
|
1188
|
+
}
|
|
1189
|
+
}, [mediaPreviewCatalogKey, sourcePackage?.sha256Hex]);
|
|
1190
|
+
const activeObjectContext = useMemo(
|
|
1191
|
+
() =>
|
|
1192
|
+
viewState.activeObjectFrame && viewState.activeObjectFrame.kind !== "image"
|
|
1193
|
+
? {
|
|
1194
|
+
kind: viewState.activeObjectFrame.kind,
|
|
1195
|
+
display: viewState.activeObjectFrame.display,
|
|
1196
|
+
}
|
|
1197
|
+
: null,
|
|
1198
|
+
[viewState.activeObjectFrame],
|
|
1199
|
+
);
|
|
1337
1200
|
const selectionToolbar = buildSelectionToolbarModel({
|
|
1338
1201
|
snapshot,
|
|
1339
1202
|
viewState,
|
|
@@ -1609,131 +1472,202 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1609
1472
|
},
|
|
1610
1473
|
};
|
|
1611
1474
|
|
|
1475
|
+
const commands = useCommandBag<EditorCommandBag>({
|
|
1476
|
+
...reviewCallbacks,
|
|
1477
|
+
onWorkspaceModeChange: (mode) => activeRuntime.setWorkspaceMode(mode),
|
|
1478
|
+
onZoomChange: (level) => activeRuntime.setZoom(level),
|
|
1479
|
+
onActiveRailTabChange: setActiveRailTab,
|
|
1480
|
+
onShowTrackedChangesChange: setShowTrackedChanges,
|
|
1481
|
+
onToggleBold: () =>
|
|
1482
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" }),
|
|
1483
|
+
onToggleItalic: () =>
|
|
1484
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "italic" }),
|
|
1485
|
+
onToggleUnderline: () =>
|
|
1486
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "underline" }),
|
|
1487
|
+
onSetSelectionTextColor: (color) =>
|
|
1488
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-text-color", color }),
|
|
1489
|
+
onSetSelectionHighlightColor: (color) =>
|
|
1490
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-highlight-color", color }),
|
|
1491
|
+
onToggleStrikethrough: () =>
|
|
1492
|
+
applyRuntimeFormattingOperation(activeRuntime, {
|
|
1493
|
+
type: "toggle",
|
|
1494
|
+
mark: "strikethrough",
|
|
1495
|
+
}),
|
|
1496
|
+
onToggleSuperscript: () =>
|
|
1497
|
+
applyRuntimeFormattingOperation(activeRuntime, {
|
|
1498
|
+
type: "toggle",
|
|
1499
|
+
mark: "superscript",
|
|
1500
|
+
}),
|
|
1501
|
+
onToggleSubscript: () =>
|
|
1502
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "subscript" }),
|
|
1503
|
+
onSetFontFamily: (fontFamily) =>
|
|
1504
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-font-family", fontFamily }),
|
|
1505
|
+
onSetFontSize: (fontSize) =>
|
|
1506
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-font-size", size: fontSize }),
|
|
1507
|
+
onSetTextColor: (color) =>
|
|
1508
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-text-color", color }),
|
|
1509
|
+
onSetHighlightColor: (color) =>
|
|
1510
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-highlight-color", color }),
|
|
1511
|
+
onSetAlignment: (alignment) =>
|
|
1512
|
+
applyRuntimeFormattingOperation(activeRuntime, { type: "set-alignment", alignment }),
|
|
1513
|
+
onSetParagraphStyle: (styleId) => applyRuntimeParagraphStyle(activeRuntime, styleId),
|
|
1514
|
+
onOutdent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" }),
|
|
1515
|
+
onIndent: () => applyRuntimeFormattingOperation(activeRuntime, { type: "indent" }),
|
|
1516
|
+
onInsertPageBreak: () => applyRuntimeInsertPageBreak(activeRuntime),
|
|
1517
|
+
onInsertTable: () => applyRuntimeInsertTable(activeRuntime, { rows: 3, columns: 3 }),
|
|
1518
|
+
onInsertSectionBreak: (type) => applyRuntimeInsertSectionBreak(activeRuntime, type),
|
|
1519
|
+
onInsertImage: (options) => applyRuntimeInsertImage(activeRuntime, options),
|
|
1520
|
+
onSetTableStyle: (styleId) => applyRuntimeTableStyle(activeRuntime, styleId),
|
|
1521
|
+
onAddRowBefore: () =>
|
|
1522
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1523
|
+
type: "add-row-before",
|
|
1524
|
+
}),
|
|
1525
|
+
onAddRowAfter: () =>
|
|
1526
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1527
|
+
type: "add-row-after",
|
|
1528
|
+
}),
|
|
1529
|
+
onAddColumnBefore: () =>
|
|
1530
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1531
|
+
type: "add-column-before",
|
|
1532
|
+
}),
|
|
1533
|
+
onAddColumnAfter: () =>
|
|
1534
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1535
|
+
type: "add-column-after",
|
|
1536
|
+
}),
|
|
1537
|
+
onDeleteRow: () =>
|
|
1538
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1539
|
+
type: "delete-row",
|
|
1540
|
+
}),
|
|
1541
|
+
onDeleteColumn: () =>
|
|
1542
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1543
|
+
type: "delete-column",
|
|
1544
|
+
}),
|
|
1545
|
+
onDeleteTable: () =>
|
|
1546
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1547
|
+
type: "delete-table",
|
|
1548
|
+
}),
|
|
1549
|
+
onMergeCells: () =>
|
|
1550
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1551
|
+
type: "merge-cells",
|
|
1552
|
+
}),
|
|
1553
|
+
onSplitCell: () =>
|
|
1554
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1555
|
+
type: "split-cell",
|
|
1556
|
+
}),
|
|
1557
|
+
onSetCellBackground: (color) =>
|
|
1558
|
+
applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current ?? null, {
|
|
1559
|
+
type: "set-cell-background",
|
|
1560
|
+
color,
|
|
1561
|
+
}),
|
|
1562
|
+
onSetImageLayout: (mediaId, dimensions) =>
|
|
1563
|
+
applyRuntimeImageResize(activeRuntime, mediaId, dimensions),
|
|
1564
|
+
onSetImageFrame: (mediaId, offsets) =>
|
|
1565
|
+
applyRuntimeImageReposition(activeRuntime, mediaId, offsets),
|
|
1566
|
+
onOpenHeaderStory: () =>
|
|
1567
|
+
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "header"),
|
|
1568
|
+
onOpenFooterStory: () =>
|
|
1569
|
+
openDefaultStoryVariant(activeRuntime, snapshot.pageLayout, documentNavigation, "footer"),
|
|
1570
|
+
onDeleteSectionBreak: (sectionIndex) =>
|
|
1571
|
+
applyRuntimeDeleteSectionBreak(activeRuntime, sectionIndex),
|
|
1572
|
+
onUpdateSectionLayout: (sectionIndex, patch) =>
|
|
1573
|
+
applyRuntimeUpdateSectionLayout(activeRuntime, sectionIndex, patch),
|
|
1574
|
+
onSetSectionPageNumbering: (sectionIndex, patch) =>
|
|
1575
|
+
applyRuntimeSetSectionPageNumbering(activeRuntime, sectionIndex, patch),
|
|
1576
|
+
onSetHeaderFooterLink: (sectionIndex, patch) =>
|
|
1577
|
+
applyRuntimeSetHeaderFooterLink(activeRuntime, sectionIndex, patch),
|
|
1578
|
+
onSetParagraphIndentation: (indentation) =>
|
|
1579
|
+
applyRuntimeParagraphIndentation(activeRuntime, indentation),
|
|
1580
|
+
onSetParagraphTabStops: (tabStops) =>
|
|
1581
|
+
applyRuntimeParagraphTabStops(activeRuntime, tabStops),
|
|
1582
|
+
onRestartNumbering: () =>
|
|
1583
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "restart" }),
|
|
1584
|
+
onContinueNumbering: () =>
|
|
1585
|
+
applyRuntimeNumberingFlow(activeRuntime, { type: "continue" }),
|
|
1586
|
+
onNavigateHeading: (headingId) => {
|
|
1587
|
+
const heading = documentNavigation.headings.find(
|
|
1588
|
+
(entry) => entry.headingId === headingId,
|
|
1589
|
+
);
|
|
1590
|
+
if (!heading) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
applyRuntimeSelection(
|
|
1594
|
+
activeRuntime,
|
|
1595
|
+
createCollapsedPublicSelection(heading.offset),
|
|
1596
|
+
);
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
const documentElement = (
|
|
1601
|
+
<EditorSurfaceController
|
|
1602
|
+
ref={surfaceRef}
|
|
1603
|
+
currentUser={currentUser}
|
|
1604
|
+
snapshot={snapshot}
|
|
1605
|
+
canonicalDocument={canonicalDocument}
|
|
1606
|
+
documentNavigation={documentNavigation}
|
|
1607
|
+
reviewMode={reviewMode}
|
|
1608
|
+
markupDisplay={liveMarkupDisplay}
|
|
1609
|
+
activeRevisionId={activeRevisionId}
|
|
1610
|
+
showTrackedChanges={showTrackedChanges}
|
|
1611
|
+
mediaPreviews={mediaPreviews}
|
|
1612
|
+
isPageWorkspace={isPageWorkspace}
|
|
1613
|
+
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
1614
|
+
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
1615
|
+
{...editorCallbacks}
|
|
1616
|
+
onCommentActivated={(commentId) => {
|
|
1617
|
+
activeRuntime.openComment(commentId);
|
|
1618
|
+
setActiveRailTab("comments");
|
|
1619
|
+
}}
|
|
1620
|
+
onRevisionActivated={(revisionId) => {
|
|
1621
|
+
setActiveRevisionId(revisionId);
|
|
1622
|
+
setActiveRailTab("changes");
|
|
1623
|
+
}}
|
|
1624
|
+
/>
|
|
1625
|
+
);
|
|
1626
|
+
|
|
1612
1627
|
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>
|
|
1628
|
+
<EditorShellView
|
|
1629
|
+
shellRef={shellRef}
|
|
1630
|
+
documentId={documentId}
|
|
1631
|
+
snapshot={snapshot}
|
|
1632
|
+
loadError={loadError}
|
|
1633
|
+
diagnosticsModeMessage={diagnosticsModeMessage}
|
|
1634
|
+
accessibilityInstructionsId={accessibilityInstructionsId}
|
|
1635
|
+
accessibilityStatusId={accessibilityStatusId}
|
|
1636
|
+
accessibilityAlertId={accessibilityAlertId}
|
|
1637
|
+
accessibilityStatusMessage={buildAccessibilityStatusMessage(
|
|
1638
|
+
snapshot,
|
|
1639
|
+
loadError ?? undefined,
|
|
1640
|
+
)}
|
|
1641
|
+
visuallyHiddenStyles={VISUALLY_HIDDEN_STYLES}
|
|
1642
|
+
onShellKeyDownCapture={handleShellKeyDownCapture}
|
|
1643
|
+
viewState={viewState}
|
|
1644
|
+
markupDisplay={liveMarkupDisplay}
|
|
1645
|
+
currentUserId={currentUser.userId}
|
|
1646
|
+
capabilities={capabilities}
|
|
1647
|
+
documentNavigation={documentNavigation}
|
|
1648
|
+
reviewMode={reviewMode}
|
|
1649
|
+
workspaceMode={viewState.workspaceMode}
|
|
1650
|
+
zoomLevel={viewState.zoomLevel}
|
|
1651
|
+
formattingState={formattingState}
|
|
1652
|
+
styleCatalog={styleCatalog}
|
|
1653
|
+
activeRailTab={activeRailTab}
|
|
1654
|
+
activeCommentId={snapshot.comments.activeCommentId}
|
|
1655
|
+
activeRevisionId={activeRevisionId}
|
|
1656
|
+
showTrackedChanges={showTrackedChanges}
|
|
1657
|
+
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
1658
|
+
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
1659
|
+
selectionToolbar={shouldRenderSelectionToolbar ? selectionToolbar : null}
|
|
1660
|
+
selectionToolbarAnchor={shouldRenderSelectionToolbar ? selectionToolbarAnchor : null}
|
|
1661
|
+
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
1662
|
+
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
1663
|
+
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
1664
|
+
onSelectionToolbarBlurCapture={handleSelectionToolbarBlurCapture}
|
|
1665
|
+
selectionToolbarRef={selectionToolbarElementRef}
|
|
1666
|
+
activeImageContext={activeImageContext}
|
|
1667
|
+
activeObjectContext={activeObjectContext}
|
|
1668
|
+
commands={commands}
|
|
1669
|
+
document={documentElement}
|
|
1670
|
+
/>
|
|
1737
1671
|
);
|
|
1738
1672
|
},
|
|
1739
1673
|
);
|
|
@@ -1772,9 +1706,14 @@ function applyRuntimeFormattingOperation(
|
|
|
1772
1706
|
}
|
|
1773
1707
|
|
|
1774
1708
|
function getRuntimeStyleCatalog(
|
|
1775
|
-
|
|
1709
|
+
input:
|
|
1710
|
+
| WordReviewEditorRuntime
|
|
1711
|
+
| EditorSessionState["canonicalDocument"]["styles"],
|
|
1776
1712
|
): StyleCatalogSnapshot {
|
|
1777
|
-
const styles =
|
|
1713
|
+
const styles =
|
|
1714
|
+
"getSessionState" in input
|
|
1715
|
+
? input.getSessionState().canonicalDocument.styles
|
|
1716
|
+
: input;
|
|
1778
1717
|
const mapRecord = <
|
|
1779
1718
|
T extends {
|
|
1780
1719
|
styleId: string;
|
|
@@ -2233,24 +2172,28 @@ function applyRuntimeImageReposition(
|
|
|
2233
2172
|
mediaId: string,
|
|
2234
2173
|
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2235
2174
|
): void {
|
|
2236
|
-
const
|
|
2237
|
-
if (!
|
|
2175
|
+
const context = getStoryMutationContext(runtime);
|
|
2176
|
+
if (!context) {
|
|
2238
2177
|
return;
|
|
2239
2178
|
}
|
|
2240
2179
|
|
|
2241
2180
|
try {
|
|
2242
|
-
const sessionState = runtime.getSessionState();
|
|
2243
2181
|
const result = repositionFloatingImageInDocument(
|
|
2244
|
-
|
|
2182
|
+
context.localDocument,
|
|
2245
2183
|
mediaId,
|
|
2246
2184
|
offsets,
|
|
2185
|
+
context.timestamp,
|
|
2186
|
+
);
|
|
2187
|
+
dispatchStoryMutationResult(
|
|
2188
|
+
runtime,
|
|
2189
|
+
context,
|
|
2190
|
+
{
|
|
2191
|
+
changed: true,
|
|
2192
|
+
document: result.document,
|
|
2193
|
+
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
2194
|
+
},
|
|
2195
|
+
context.timestamp,
|
|
2247
2196
|
);
|
|
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
2197
|
} catch {
|
|
2255
2198
|
return;
|
|
2256
2199
|
}
|
|
@@ -2758,6 +2701,13 @@ function normalizeRequestedSelection(
|
|
|
2758
2701
|
);
|
|
2759
2702
|
}
|
|
2760
2703
|
|
|
2704
|
+
export function __resolveLiveMarkupDisplay(
|
|
2705
|
+
requested: MarkupDisplay | undefined,
|
|
2706
|
+
isPageWorkspace: boolean,
|
|
2707
|
+
): MarkupDisplay {
|
|
2708
|
+
return requested ?? (isPageWorkspace ? "all" : "clean");
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2761
2711
|
function createCollapsedPublicSelection(
|
|
2762
2712
|
position: number,
|
|
2763
2713
|
storyTarget?: EditorStoryTarget,
|
|
@@ -3008,60 +2958,6 @@ function guessSourceLabel(
|
|
|
3008
2958
|
);
|
|
3009
2959
|
}
|
|
3010
2960
|
|
|
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
2961
|
function deriveEditorViewMode(
|
|
3066
2962
|
readOnly: boolean,
|
|
3067
2963
|
reviewMode: WordReviewEditorProps["reviewMode"] = "review",
|
|
@@ -3072,636 +2968,6 @@ function deriveEditorViewMode(
|
|
|
3072
2968
|
return reviewMode === "editing" ? "editing" : "review";
|
|
3073
2969
|
}
|
|
3074
2970
|
|
|
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
2971
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
3706
2972
|
return {
|
|
3707
2973
|
anchor: selection.anchor,
|
|
@@ -3825,6 +3091,102 @@ function selectionToolbarAnchorsEqual(
|
|
|
3825
3091
|
);
|
|
3826
3092
|
}
|
|
3827
3093
|
|
|
3094
|
+
function workflowScopeSnapshotsEqual(
|
|
3095
|
+
left: WorkflowScopeSnapshot | null,
|
|
3096
|
+
right: WorkflowScopeSnapshot | null,
|
|
3097
|
+
): boolean {
|
|
3098
|
+
if (left === right) {
|
|
3099
|
+
return true;
|
|
3100
|
+
}
|
|
3101
|
+
if (!left || !right) {
|
|
3102
|
+
return false;
|
|
3103
|
+
}
|
|
3104
|
+
return (
|
|
3105
|
+
left.overlayPresent === right.overlayPresent &&
|
|
3106
|
+
left.activeWorkItemId === right.activeWorkItemId &&
|
|
3107
|
+
left.activeWorkItem === right.activeWorkItem &&
|
|
3108
|
+
left.scopes === right.scopes &&
|
|
3109
|
+
left.candidates === right.candidates &&
|
|
3110
|
+
workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons)
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
function interactionGuardSnapshotsEqual(
|
|
3115
|
+
left: InteractionGuardSnapshot,
|
|
3116
|
+
right: InteractionGuardSnapshot,
|
|
3117
|
+
): boolean {
|
|
3118
|
+
if (left === right) {
|
|
3119
|
+
return true;
|
|
3120
|
+
}
|
|
3121
|
+
return workflowBlockedReasonsEqual(left.blockedReasons, right.blockedReasons);
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
function workflowBlockedReasonsEqual(
|
|
3125
|
+
left: readonly WorkflowBlockedCommandReason[],
|
|
3126
|
+
right: readonly WorkflowBlockedCommandReason[],
|
|
3127
|
+
): boolean {
|
|
3128
|
+
if (left === right) {
|
|
3129
|
+
return true;
|
|
3130
|
+
}
|
|
3131
|
+
if (left.length !== right.length) {
|
|
3132
|
+
return false;
|
|
3133
|
+
}
|
|
3134
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
3135
|
+
if (!workflowBlockedReasonEqual(left[index]!, right[index]!)) {
|
|
3136
|
+
return false;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return true;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
function workflowBlockedReasonEqual(
|
|
3143
|
+
left: WorkflowBlockedCommandReason,
|
|
3144
|
+
right: WorkflowBlockedCommandReason,
|
|
3145
|
+
): boolean {
|
|
3146
|
+
return (
|
|
3147
|
+
left.code === right.code &&
|
|
3148
|
+
left.message === right.message &&
|
|
3149
|
+
left.scopeId === right.scopeId &&
|
|
3150
|
+
left.workItemId === right.workItemId &&
|
|
3151
|
+
editorAnchorProjectionEqual(left.anchor, right.anchor) &&
|
|
3152
|
+
storyTargetsEqual(left.storyTarget, right.storyTarget)
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
function editorAnchorProjectionEqual(
|
|
3157
|
+
left: EditorAnchorProjection | undefined,
|
|
3158
|
+
right: EditorAnchorProjection | undefined,
|
|
3159
|
+
): boolean {
|
|
3160
|
+
if (left === right) {
|
|
3161
|
+
return true;
|
|
3162
|
+
}
|
|
3163
|
+
if (!left || !right || left.kind !== right.kind) {
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
switch (left.kind) {
|
|
3168
|
+
case "range":
|
|
3169
|
+
return (
|
|
3170
|
+
right.kind === "range" &&
|
|
3171
|
+
left.from === right.from &&
|
|
3172
|
+
left.to === right.to &&
|
|
3173
|
+
left.assoc.start === right.assoc.start &&
|
|
3174
|
+
left.assoc.end === right.assoc.end
|
|
3175
|
+
);
|
|
3176
|
+
case "node":
|
|
3177
|
+
return right.kind === "node" && left.at === right.at && left.assoc === right.assoc;
|
|
3178
|
+
case "detached":
|
|
3179
|
+
return (
|
|
3180
|
+
right.kind === "detached" &&
|
|
3181
|
+
left.lastKnownRange.from === right.lastKnownRange.from &&
|
|
3182
|
+
left.lastKnownRange.to === right.lastKnownRange.to &&
|
|
3183
|
+
left.reason === right.reason
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3828
3190
|
function createSelectionToolbarSelectionKey(
|
|
3829
3191
|
selection: RuntimeRenderSnapshot["selection"],
|
|
3830
3192
|
activeStory: EditorStoryTarget,
|
|
@@ -3948,3 +3310,176 @@ function createSelectionToolbarListBadge(
|
|
|
3948
3310
|
label: viewState.activeListContext.isOrdered ? "Numbered list" : "Bulleted list",
|
|
3949
3311
|
};
|
|
3950
3312
|
}
|
|
3313
|
+
|
|
3314
|
+
function buildActiveImageContext(args: {
|
|
3315
|
+
canonicalDocument: PersistedEditorSnapshot["canonicalDocument"];
|
|
3316
|
+
selection: RuntimeRenderSnapshot["selection"];
|
|
3317
|
+
storyTarget: EditorStoryTarget;
|
|
3318
|
+
surface?: RuntimeRenderSnapshot["surface"];
|
|
3319
|
+
}): {
|
|
3320
|
+
mediaId: string;
|
|
3321
|
+
display: "inline" | "floating";
|
|
3322
|
+
widthEmu?: number;
|
|
3323
|
+
heightEmu?: number;
|
|
3324
|
+
horizontalOffsetEmu?: number;
|
|
3325
|
+
verticalOffsetEmu?: number;
|
|
3326
|
+
} | null {
|
|
3327
|
+
const imageSegment = findSelectedImageSegment(args.surface, args.selection);
|
|
3328
|
+
if (!imageSegment) {
|
|
3329
|
+
return null;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
const storyBlocks = getStoryBlocks(args.canonicalDocument, args.storyTarget);
|
|
3333
|
+
const imageNode = findImageNodeByMediaId(storyBlocks, imageSegment.mediaId);
|
|
3334
|
+
const mediaItem = args.canonicalDocument.media.items[imageSegment.mediaId];
|
|
3335
|
+
|
|
3336
|
+
return {
|
|
3337
|
+
mediaId: imageSegment.mediaId,
|
|
3338
|
+
display: imageSegment.display === "floating" ? "floating" : "inline",
|
|
3339
|
+
widthEmu: mediaItem?.widthEmu,
|
|
3340
|
+
heightEmu: mediaItem?.heightEmu,
|
|
3341
|
+
horizontalOffsetEmu: imageNode?.floating?.horizontalPosition?.offset,
|
|
3342
|
+
verticalOffsetEmu: imageNode?.floating?.verticalPosition?.offset,
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function findSelectedImageSegment(
|
|
3347
|
+
surface: RuntimeRenderSnapshot["surface"] | undefined,
|
|
3348
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
3349
|
+
): Extract<NonNullable<RuntimeRenderSnapshot["surface"]>["blocks"][number], { kind: "paragraph" }>["segments"][number] & {
|
|
3350
|
+
kind: "image";
|
|
3351
|
+
} | null {
|
|
3352
|
+
if (!surface) {
|
|
3353
|
+
return null;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
const position =
|
|
3357
|
+
selection.activeRange.kind === "node" ? selection.activeRange.at : selection.head;
|
|
3358
|
+
|
|
3359
|
+
return findSelectedImageSegmentInBlocks(surface.blocks, position);
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
function findSelectedImageSegmentInBlocks(
|
|
3363
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
3364
|
+
position: number,
|
|
3365
|
+
): Extract<NonNullable<RuntimeRenderSnapshot["surface"]>["blocks"][number], { kind: "paragraph" }>["segments"][number] & {
|
|
3366
|
+
kind: "image";
|
|
3367
|
+
} | null {
|
|
3368
|
+
for (const block of blocks) {
|
|
3369
|
+
if (position < block.from || position > block.to) {
|
|
3370
|
+
continue;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
if (block.kind === "paragraph") {
|
|
3374
|
+
const imageSegment = block.segments.find(
|
|
3375
|
+
(segment) => segment.kind === "image" && position >= segment.from && position <= segment.to,
|
|
3376
|
+
);
|
|
3377
|
+
if (imageSegment && imageSegment.kind === "image") {
|
|
3378
|
+
return imageSegment;
|
|
3379
|
+
}
|
|
3380
|
+
continue;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
if (block.kind === "table") {
|
|
3384
|
+
for (const row of block.rows) {
|
|
3385
|
+
for (const cell of row.cells) {
|
|
3386
|
+
const match = findSelectedImageSegmentInBlocks(cell.content, position);
|
|
3387
|
+
if (match) {
|
|
3388
|
+
return match;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
if (block.kind === "sdt_block") {
|
|
3396
|
+
const match = findSelectedImageSegmentInBlocks(block.children, position);
|
|
3397
|
+
if (match) {
|
|
3398
|
+
return match;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
return null;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
function findImageNodeByMediaId(blocks: readonly unknown[], mediaId: string): {
|
|
3407
|
+
floating?: {
|
|
3408
|
+
horizontalPosition?: { offset?: number };
|
|
3409
|
+
verticalPosition?: { offset?: number };
|
|
3410
|
+
};
|
|
3411
|
+
} | null {
|
|
3412
|
+
for (const block of blocks) {
|
|
3413
|
+
const match = findImageNodeInValue(block, mediaId);
|
|
3414
|
+
if (match) {
|
|
3415
|
+
return match;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return null;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
function findImageNodeInValue(
|
|
3422
|
+
value: unknown,
|
|
3423
|
+
mediaId: string,
|
|
3424
|
+
): {
|
|
3425
|
+
floating?: {
|
|
3426
|
+
horizontalPosition?: { offset?: number };
|
|
3427
|
+
verticalPosition?: { offset?: number };
|
|
3428
|
+
};
|
|
3429
|
+
} | null {
|
|
3430
|
+
if (!value || typeof value !== "object") {
|
|
3431
|
+
return null;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
const record = value as {
|
|
3435
|
+
type?: string;
|
|
3436
|
+
mediaId?: string;
|
|
3437
|
+
children?: unknown[];
|
|
3438
|
+
rows?: Array<{ cells?: Array<{ children?: unknown[] }> }>;
|
|
3439
|
+
floating?: {
|
|
3440
|
+
horizontalPosition?: { offset?: number };
|
|
3441
|
+
verticalPosition?: { offset?: number };
|
|
3442
|
+
};
|
|
3443
|
+
};
|
|
3444
|
+
|
|
3445
|
+
if (record.type === "image" && record.mediaId === mediaId) {
|
|
3446
|
+
return record;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
if (Array.isArray(record.children)) {
|
|
3450
|
+
for (const child of record.children) {
|
|
3451
|
+
const match = findImageNodeInValue(child, mediaId);
|
|
3452
|
+
if (match) {
|
|
3453
|
+
return match;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
if (Array.isArray(record.rows)) {
|
|
3459
|
+
for (const row of record.rows) {
|
|
3460
|
+
for (const cell of row.cells ?? []) {
|
|
3461
|
+
for (const child of cell.children ?? []) {
|
|
3462
|
+
const match = findImageNodeInValue(child, mediaId);
|
|
3463
|
+
if (match) {
|
|
3464
|
+
return match;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
return null;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
function createImageDataUrl(contentType: string, bytes: Uint8Array): string {
|
|
3475
|
+
const base64 = bytesToBase64(bytes);
|
|
3476
|
+
return `data:${contentType};base64,${base64}`;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
3480
|
+
let binary = "";
|
|
3481
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
3482
|
+
binary += String.fromCharCode(bytes[index] ?? 0);
|
|
3483
|
+
}
|
|
3484
|
+
return btoa(binary);
|
|
3485
|
+
}
|