@beyondwork/docx-react-component 1.0.18 → 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/README.md +8 -2
- package/package.json +24 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +710 -4
- package/src/api/session-state.ts +60 -0
- package/src/core/commands/formatting-commands.ts +2 -1
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +19 -3
- package/src/core/commands/list-commands.ts +231 -36
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/section-layout-commands.ts +680 -0
- package/src/core/commands/style-commands.ts +262 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +4 -1
- package/src/index.ts +51 -0
- package/src/io/docx-session.ts +623 -56
- 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 +285 -8
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +144 -32
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -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 +452 -22
- package/src/io/ooxml/parse-headers-footers.ts +657 -29
- package/src/io/ooxml/parse-inline-media.ts +30 -0
- package/src/io/ooxml/parse-main-document.ts +807 -20
- package/src/io/ooxml/parse-numbering.ts +7 -0
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +25 -0
- package/src/io/ooxml/parse-styles.ts +463 -0
- package/src/io/ooxml/parse-theme.ts +32 -0
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +250 -4
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +87 -2
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +603 -0
- package/src/runtime/document-runtime.ts +1754 -78
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/numbering-prefix.ts +47 -26
- package/src/runtime/page-layout-estimation.ts +212 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +9 -0
- package/src/runtime/session-capabilities.ts +35 -3
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +324 -36
- package/src/runtime/table-schema.ts +89 -7
- package/src/runtime/view-state.ts +477 -0
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +2469 -1344
- package/src/ui/browser-export.ts +52 -0
- 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/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/headless/selection-helpers.ts +20 -0
- package/src/ui/headless/selection-toolbar-model.ts +22 -0
- package/src/ui/headless/use-editor-keyboard.ts +6 -1
- 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-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +150 -14
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +46 -7
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +3 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +186 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +191 -68
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +528 -85
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
- package/src/ui-tailwind/index.ts +2 -1
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +8 -8
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +127 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +829 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +1238 -42
- package/src/validation/compatibility-engine.ts +119 -24
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +707 -0
|
@@ -1,112 +1,174 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
forwardRef,
|
|
3
|
+
type FocusEvent,
|
|
4
|
+
useCallback,
|
|
3
5
|
useEffect,
|
|
4
6
|
useImperativeHandle,
|
|
5
7
|
useMemo,
|
|
6
8
|
useRef,
|
|
7
9
|
useState,
|
|
8
|
-
useSyncExternalStore,
|
|
9
10
|
} from "react";
|
|
10
11
|
|
|
11
12
|
import type {
|
|
12
13
|
AutosaveState,
|
|
13
|
-
EditorDatastoreAdapter,
|
|
14
14
|
CompatibilityReport,
|
|
15
|
+
DocumentNavigationSnapshot,
|
|
16
|
+
EditorAnchorProjection,
|
|
17
|
+
EditorDatastoreAdapter,
|
|
18
|
+
EditorHostAdapter,
|
|
19
|
+
EditorSessionState,
|
|
20
|
+
ExportResult,
|
|
15
21
|
EditorError,
|
|
22
|
+
EditorStoryTarget,
|
|
23
|
+
EditorViewStateSnapshot,
|
|
16
24
|
EditorWarning,
|
|
17
|
-
FormattingAlignment,
|
|
18
25
|
ExportDocxOptions,
|
|
26
|
+
FieldSnapshot,
|
|
27
|
+
FormattingAlignment,
|
|
28
|
+
FormattingStateSnapshot,
|
|
29
|
+
HeaderFooterLinkPatch,
|
|
30
|
+
InteractionGuardSnapshot,
|
|
19
31
|
InsertImageOptions,
|
|
20
32
|
InsertTableOptions,
|
|
33
|
+
PageLayoutSnapshot,
|
|
21
34
|
PersistedEditorSnapshot,
|
|
22
35
|
RuntimeRenderSnapshot,
|
|
36
|
+
SectionBreakType,
|
|
37
|
+
SectionLayoutPatch,
|
|
38
|
+
SectionPageNumberingPatch,
|
|
23
39
|
SearchOptions,
|
|
24
40
|
SearchResultSnapshot,
|
|
25
41
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
26
|
-
|
|
42
|
+
StyleCatalogSnapshot,
|
|
43
|
+
SurfaceBlockSnapshot,
|
|
44
|
+
TocRefreshResult,
|
|
45
|
+
UpdateFieldsResult,
|
|
46
|
+
ViewMode as EditorViewMode,
|
|
47
|
+
WorkflowBlockedCommandReason,
|
|
48
|
+
WorkflowScopeSnapshot,
|
|
27
49
|
WordReviewEditorEvent,
|
|
28
50
|
WordReviewEditorProps,
|
|
29
51
|
WordReviewEditorRef,
|
|
52
|
+
WorkspaceMode,
|
|
53
|
+
ZoomLevel,
|
|
30
54
|
} from "../api/public-types";
|
|
55
|
+
import {
|
|
56
|
+
editorSessionStateFromPersistedSnapshot,
|
|
57
|
+
persistedSnapshotFromEditorSessionState,
|
|
58
|
+
} from "../api/session-state.ts";
|
|
31
59
|
import {
|
|
32
60
|
createDetachedAnchor,
|
|
33
61
|
createNodeAnchor,
|
|
34
62
|
createRangeAnchor,
|
|
63
|
+
storyTargetsEqual,
|
|
35
64
|
type TransactionMapping,
|
|
36
65
|
} from "../core/selection/mapping.ts";
|
|
37
66
|
import {
|
|
38
67
|
applyFormattingOperationToDocument,
|
|
39
68
|
getFormattingStateFromRenderSnapshot,
|
|
40
69
|
} from "../core/commands/formatting-commands.ts";
|
|
41
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
applyParagraphStyleToDocument,
|
|
72
|
+
applyTableStyleToDocument,
|
|
73
|
+
} from "../core/commands/style-commands.ts";
|
|
74
|
+
import {
|
|
75
|
+
continueNumbering as continueListNumbering,
|
|
76
|
+
backspaceAtListStart,
|
|
77
|
+
indentListItems,
|
|
78
|
+
outdentListItems,
|
|
79
|
+
restartNumbering as restartListNumbering,
|
|
80
|
+
splitListParagraph,
|
|
81
|
+
} from "../core/commands/list-commands.ts";
|
|
82
|
+
import {
|
|
83
|
+
resolveActiveParagraphIndex,
|
|
84
|
+
setActiveParagraphIndentation,
|
|
85
|
+
setActiveParagraphTabStops,
|
|
86
|
+
} from "../core/commands/paragraph-layout-commands.ts";
|
|
87
|
+
import {
|
|
88
|
+
deleteSectionBreakAtSectionIndex,
|
|
89
|
+
insertSectionBreakAfterSectionIndex,
|
|
90
|
+
setHeaderFooterLinkAtSectionIndex,
|
|
91
|
+
setSectionPageNumberingAtSectionIndex,
|
|
92
|
+
updateSectionLayoutAtSectionIndex,
|
|
93
|
+
} from "../core/commands/section-layout-commands.ts";
|
|
94
|
+
import {
|
|
95
|
+
insertImage as insertImageInDocument,
|
|
96
|
+
resizeImage as resizeImageInCatalog,
|
|
97
|
+
repositionFloatingImage as repositionFloatingImageInDocument,
|
|
98
|
+
} from "../core/commands/image-commands.ts";
|
|
42
99
|
import {
|
|
43
100
|
applyTableStructureOperation,
|
|
44
101
|
} from "../core/commands/table-structure-commands.ts";
|
|
45
102
|
import {
|
|
103
|
+
deleteSelectionOrBackward,
|
|
104
|
+
deleteSelectionOrForward,
|
|
105
|
+
insertHardBreak as insertHardBreakInDocument,
|
|
46
106
|
insertPageBreak as insertPageBreakInDocument,
|
|
107
|
+
insertTab as insertTabInDocument,
|
|
108
|
+
insertText as insertTextInDocument,
|
|
47
109
|
insertTable as insertTableInDocument,
|
|
110
|
+
splitParagraph as splitParagraphInDocument,
|
|
48
111
|
} from "../core/commands/text-commands.ts";
|
|
112
|
+
import { type SelectionSnapshot as InternalSelectionSnapshot } from "../core/state/editor-state.ts";
|
|
49
113
|
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} from "../
|
|
53
|
-
import {
|
|
54
|
-
createDocumentRuntime,
|
|
55
|
-
type DocumentRuntime,
|
|
56
|
-
} from "../runtime/document-runtime.ts";
|
|
57
|
-
import { loadDocxEditorSession } from "../io/docx-session.ts";
|
|
114
|
+
getStoryBlocks,
|
|
115
|
+
replaceStoryBlocks,
|
|
116
|
+
} from "../runtime/story-targeting.ts";
|
|
58
117
|
import {
|
|
59
118
|
decodePersistedSourcePackageBytes,
|
|
60
119
|
hasValidPersistedSourcePackageDigest,
|
|
61
120
|
} from "../io/source-package-provenance.ts";
|
|
121
|
+
import { readOpcPackage } from "../io/opc/package-reader.ts";
|
|
62
122
|
import { deriveCapabilities } from "../runtime/session-capabilities";
|
|
123
|
+
import { searchDocument } from "../runtime/document-search.ts";
|
|
63
124
|
import {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} from "../
|
|
125
|
+
createEditorViewStateSnapshot,
|
|
126
|
+
createViewState,
|
|
127
|
+
} from "../runtime/view-state.ts";
|
|
67
128
|
import {
|
|
68
|
-
TwProseMirrorSurface,
|
|
69
129
|
type TwProseMirrorSurfaceRef,
|
|
70
130
|
} from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
|
|
71
|
-
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";
|
|
72
136
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail";
|
|
73
|
-
import
|
|
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";
|
|
74
148
|
import type { MarkupDisplay } from "./headless/comment-decoration-model";
|
|
149
|
+
import type {
|
|
150
|
+
SelectionToolbarAnchor,
|
|
151
|
+
SelectionToolbarModel,
|
|
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";
|
|
161
|
+
import {
|
|
162
|
+
downloadExportResult,
|
|
163
|
+
withExportDelivery,
|
|
164
|
+
} from "./browser-export";
|
|
165
|
+
import { EditorShellView } from "./editor-shell-view.tsx";
|
|
166
|
+
import { EditorSurfaceController } from "./editor-surface-controller.tsx";
|
|
75
167
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
initialSnapshot?: PersistedEditorSnapshot;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
interface CreateRuntimeArgs {
|
|
84
|
-
documentId: string;
|
|
85
|
-
readOnly: boolean;
|
|
86
|
-
source: ResolvedSource;
|
|
87
|
-
datastore?: EditorDatastoreAdapter;
|
|
88
|
-
currentUserId?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface RuntimeLifecycleHandlers {
|
|
92
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
93
|
-
onWarning?: (warning: EditorWarning) => void;
|
|
94
|
-
onError?: (error: EditorError) => void;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
interface WordReviewEditorRuntime extends DocumentRuntime {
|
|
98
|
-
getFatalError?(): EditorError | undefined;
|
|
99
|
-
dispose?(): void;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
type PackageBackedDocxSession = ReturnType<typeof loadDocxEditorSession>;
|
|
103
|
-
|
|
104
|
-
interface SnapshotExportBarrier {
|
|
105
|
-
reason:
|
|
106
|
-
| "missing_source_package_provenance"
|
|
107
|
-
| "invalid_source_package_provenance";
|
|
108
|
-
message: string;
|
|
109
|
-
}
|
|
168
|
+
export {
|
|
169
|
+
__createFallbackRuntime,
|
|
170
|
+
__resolveWordReviewEditorSource,
|
|
171
|
+
} from "./editor-runtime-boundary.ts";
|
|
110
172
|
|
|
111
173
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
112
174
|
position: "absolute",
|
|
@@ -120,6 +182,15 @@ const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
|
120
182
|
border: 0,
|
|
121
183
|
};
|
|
122
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
|
+
|
|
123
194
|
const ACCESSIBLE_REGION_ORDER = [
|
|
124
195
|
"toolbar",
|
|
125
196
|
"document",
|
|
@@ -129,6 +200,12 @@ const ACCESSIBLE_REGION_ORDER = [
|
|
|
129
200
|
|
|
130
201
|
type AccessibleRegionId = (typeof ACCESSIBLE_REGION_ORDER)[number];
|
|
131
202
|
|
|
203
|
+
type SelectionToolbarDismissReason =
|
|
204
|
+
| "blur"
|
|
205
|
+
| "chrome-action"
|
|
206
|
+
| "comment-action"
|
|
207
|
+
| "escape";
|
|
208
|
+
|
|
132
209
|
export function __createWordReviewEditorRefBridge(
|
|
133
210
|
runtime: WordReviewEditorRuntime,
|
|
134
211
|
mountedSurface?: TwProseMirrorSurfaceRef | null,
|
|
@@ -138,6 +215,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
138
215
|
blur: () => runtime.blur(),
|
|
139
216
|
undo: () => runtime.undo(),
|
|
140
217
|
redo: () => runtime.redo(),
|
|
218
|
+
replaceText: (text, target) => runtime.replaceText(text, target),
|
|
141
219
|
addComment: (params) => runtime.addComment(params),
|
|
142
220
|
openComment: (commentId) => runtime.openComment(commentId),
|
|
143
221
|
resolveComment: (commentId) => runtime.resolveComment(commentId),
|
|
@@ -152,6 +230,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
152
230
|
acceptAllChanges: () => runtime.acceptAllChanges(),
|
|
153
231
|
rejectAllChanges: () => runtime.rejectAllChanges(),
|
|
154
232
|
exportDocx: (options) => runtime.exportDocx(options),
|
|
233
|
+
getSessionState: () => runtime.getSessionState(),
|
|
155
234
|
getSnapshot: () => runtime.getPersistedSnapshot(),
|
|
156
235
|
getRenderSnapshot: () => clonePublicValue(runtime.getRenderSnapshot()),
|
|
157
236
|
getCompatibilityReport: () => runtime.getCompatibilityReport(),
|
|
@@ -165,7 +244,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
165
244
|
clonePublicValue(runtime.getRenderSnapshot().trackedChanges),
|
|
166
245
|
isDirty: () => runtime.getRenderSnapshot().isDirty,
|
|
167
246
|
getFormattingState: () => getFormattingStateFromRenderSnapshot(runtime.getRenderSnapshot()),
|
|
168
|
-
|
|
247
|
+
getStyleCatalog: () => getRuntimeStyleCatalog(runtime),
|
|
169
248
|
toggleBold: () => {
|
|
170
249
|
applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "bold" });
|
|
171
250
|
},
|
|
@@ -214,6 +293,12 @@ export function __createWordReviewEditorRefBridge(
|
|
|
214
293
|
alignment,
|
|
215
294
|
});
|
|
216
295
|
},
|
|
296
|
+
setParagraphStyle: (styleId) => {
|
|
297
|
+
applyRuntimeParagraphStyle(runtime, styleId);
|
|
298
|
+
},
|
|
299
|
+
setTableStyle: (styleId) => {
|
|
300
|
+
applyRuntimeTableStyle(runtime, styleId);
|
|
301
|
+
},
|
|
217
302
|
indent: () => {
|
|
218
303
|
applyRuntimeFormattingOperation(runtime, { type: "indent" });
|
|
219
304
|
},
|
|
@@ -281,190 +366,127 @@ export function __createWordReviewEditorRefBridge(
|
|
|
281
366
|
});
|
|
282
367
|
},
|
|
283
368
|
search: (query, options) =>
|
|
284
|
-
mountedSurface
|
|
285
|
-
searchSnapshotSurface(runtime.getRenderSnapshot(), query, options),
|
|
369
|
+
searchRuntimeDocument(runtime, mountedSurface ?? null, query, options),
|
|
286
370
|
clearSearch: () => {
|
|
287
371
|
mountedSurface?.clearSearch();
|
|
288
372
|
},
|
|
289
373
|
setSelection: (selection) => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
selection
|
|
293
|
-
|
|
294
|
-
),
|
|
295
|
-
});
|
|
374
|
+
applyRuntimeSelection(
|
|
375
|
+
runtime,
|
|
376
|
+
normalizeRequestedSelection(runtime.getRenderSnapshot(), selection),
|
|
377
|
+
);
|
|
296
378
|
},
|
|
297
379
|
scrollToRevision: (revisionId: string) => {
|
|
298
380
|
const revision = runtime.getRenderSnapshot().trackedChanges.revisions.find(
|
|
299
381
|
(r) => r.revisionId === revisionId,
|
|
300
382
|
);
|
|
301
383
|
if (!revision || revision.anchor.kind === "detached") return;
|
|
302
|
-
runtime.
|
|
303
|
-
type: "selection.set",
|
|
304
|
-
selection: toRuntimeSelectionSnapshot(createSelectionFromAnchor(revision.anchor)),
|
|
305
|
-
});
|
|
384
|
+
applyRuntimeSelection(runtime, createSelectionFromAnchor(revision.anchor));
|
|
306
385
|
},
|
|
307
386
|
scrollToComment: (commentId: string) => {
|
|
308
387
|
const comment = runtime.getRenderSnapshot().comments.threads.find(
|
|
309
388
|
(t) => t.commentId === commentId,
|
|
310
389
|
);
|
|
311
390
|
if (!comment || comment.anchor.kind === "detached") return;
|
|
312
|
-
runtime.
|
|
313
|
-
type: "selection.set",
|
|
314
|
-
selection: toRuntimeSelectionSnapshot(createSelectionFromAnchor(comment.anchor)),
|
|
315
|
-
});
|
|
391
|
+
applyRuntimeSelection(runtime, createSelectionFromAnchor(comment.anchor));
|
|
316
392
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
export async function __resolveWordReviewEditorSource(
|
|
321
|
-
props: Pick<
|
|
322
|
-
WordReviewEditorProps,
|
|
323
|
-
| "documentId"
|
|
324
|
-
| "datastore"
|
|
325
|
-
| "externalDocSource"
|
|
326
|
-
| "initialDocx"
|
|
327
|
-
| "initialSnapshot"
|
|
328
|
-
| "initialSourceLabel"
|
|
329
|
-
>,
|
|
330
|
-
): Promise<ResolvedSource> {
|
|
331
|
-
const explicitInitialCount =
|
|
332
|
-
Number(Boolean(props.initialDocx)) + Number(Boolean(props.initialSnapshot));
|
|
333
|
-
if (explicitInitialCount > 1) {
|
|
334
|
-
throw new Error("Provide exactly one of initialDocx or initialSnapshot.");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (props.externalDocSource) {
|
|
338
|
-
return props.externalDocSource.kind === "docx"
|
|
339
|
-
? {
|
|
340
|
-
source: "docx",
|
|
341
|
-
initialDocx: props.externalDocSource.bytes,
|
|
342
|
-
sourceLabel: props.externalDocSource.sourceLabel,
|
|
343
|
-
}
|
|
344
|
-
: {
|
|
345
|
-
source: "snapshot",
|
|
346
|
-
initialSnapshot: props.externalDocSource.snapshot,
|
|
347
|
-
sourceLabel: props.externalDocSource.sourceLabel,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (props.initialSnapshot) {
|
|
352
|
-
return {
|
|
353
|
-
source: "snapshot",
|
|
354
|
-
initialSnapshot: props.initialSnapshot,
|
|
355
|
-
sourceLabel: props.initialSourceLabel,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (props.initialDocx) {
|
|
360
|
-
return {
|
|
361
|
-
source: "docx",
|
|
362
|
-
initialDocx: props.initialDocx,
|
|
363
|
-
sourceLabel: props.initialSourceLabel,
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (!props.datastore) {
|
|
368
|
-
throw new Error(
|
|
369
|
-
`WordReviewEditor ${props.documentId} needs initialDocx, initialSnapshot, or datastore.load().`,
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const loadResult = await props.datastore.load({
|
|
374
|
-
documentId: props.documentId,
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
if (!loadResult.source) {
|
|
378
|
-
throw new Error(`Datastore did not return a loadable source for ${props.documentId}.`);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return loadResult.source.kind === "docx"
|
|
382
|
-
? {
|
|
383
|
-
source: "datastore",
|
|
384
|
-
initialDocx: loadResult.source.bytes,
|
|
385
|
-
sourceLabel: loadResult.source.sourceLabel,
|
|
386
|
-
}
|
|
387
|
-
: {
|
|
388
|
-
source: "datastore",
|
|
389
|
-
initialSnapshot: loadResult.source.snapshot,
|
|
390
|
-
sourceLabel: loadResult.source.sourceLabel,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export function __createFallbackRuntime(args: CreateRuntimeArgs): WordReviewEditorRuntime {
|
|
395
|
-
return createRuntime(args);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function createRuntime(
|
|
399
|
-
args: CreateRuntimeArgs,
|
|
400
|
-
handlers: RuntimeLifecycleHandlers = {},
|
|
401
|
-
): WordReviewEditorRuntime {
|
|
402
|
-
const docxSession = args.source.initialDocx
|
|
403
|
-
? loadDocxEditorSession({
|
|
404
|
-
documentId: args.documentId,
|
|
405
|
-
sourceLabel: args.source.sourceLabel,
|
|
406
|
-
bytes: args.source.initialDocx,
|
|
407
|
-
editorBuild: "dev",
|
|
408
|
-
})
|
|
409
|
-
: undefined;
|
|
410
|
-
const snapshotExportResolution = !args.source.initialDocx
|
|
411
|
-
? resolveSnapshotExportSession(args)
|
|
412
|
-
: undefined;
|
|
413
|
-
const initialSnapshot =
|
|
414
|
-
args.source.initialSnapshot ??
|
|
415
|
-
docxSession?.initialSnapshot ??
|
|
416
|
-
createFallbackPersistedSnapshot(
|
|
417
|
-
args.documentId,
|
|
418
|
-
args.source.sourceLabel ?? "Generated shell snapshot",
|
|
419
|
-
);
|
|
420
|
-
const runtimeSnapshot = snapshotExportResolution?.barrier
|
|
421
|
-
? applySnapshotExportBarrier(initialSnapshot, snapshotExportResolution.barrier)
|
|
422
|
-
: initialSnapshot;
|
|
423
|
-
|
|
424
|
-
return createDocumentRuntime({
|
|
425
|
-
documentId: args.documentId,
|
|
426
|
-
initialSnapshot: runtimeSnapshot,
|
|
427
|
-
sourceKind: args.source.source,
|
|
428
|
-
sourceLabel: args.source.sourceLabel,
|
|
429
|
-
readOnly: args.readOnly || docxSession?.readOnly,
|
|
430
|
-
editorBuild: runtimeSnapshot.editorBuild,
|
|
431
|
-
fatalError: docxSession?.fatalError,
|
|
432
|
-
exportDocx: async (snapshot, options) => {
|
|
433
|
-
if (docxSession) {
|
|
434
|
-
return docxSession.exportDocx(snapshot, options);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (snapshotExportResolution?.session) {
|
|
438
|
-
return snapshotExportResolution.session.exportDocx(snapshot, options);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
throw createSnapshotExportBlockedError(
|
|
442
|
-
args.documentId,
|
|
443
|
-
snapshotExportResolution?.barrier ?? {
|
|
444
|
-
reason: "missing_source_package_provenance",
|
|
445
|
-
message:
|
|
446
|
-
"DOCX export is blocked because this snapshot does not carry embedded source package provenance.",
|
|
447
|
-
},
|
|
448
|
-
);
|
|
393
|
+
openStory: (target: EditorStoryTarget) => {
|
|
394
|
+
runtime.openStory(target);
|
|
449
395
|
},
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
396
|
+
closeStory: () => {
|
|
397
|
+
runtime.closeStory();
|
|
398
|
+
},
|
|
399
|
+
getPageLayoutSnapshot: () => {
|
|
400
|
+
return clonePublicValue(runtime.getPageLayoutSnapshot());
|
|
401
|
+
},
|
|
402
|
+
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);
|
|
413
|
+
},
|
|
414
|
+
getViewState: () => {
|
|
415
|
+
return clonePublicValue(runtime.getViewState());
|
|
416
|
+
},
|
|
417
|
+
setDocumentMode: (mode) => {
|
|
418
|
+
runtime.setDocumentMode(mode);
|
|
419
|
+
},
|
|
420
|
+
getProtectionSnapshot: () => {
|
|
421
|
+
return clonePublicValue(runtime.getProtectionSnapshot());
|
|
422
|
+
},
|
|
423
|
+
setWorkspaceMode: (mode) => {
|
|
424
|
+
runtime.setWorkspaceMode(mode);
|
|
425
|
+
},
|
|
426
|
+
setZoom: (level) => {
|
|
427
|
+
runtime.setZoom(level);
|
|
428
|
+
},
|
|
429
|
+
insertSectionBreak: (type, options) => {
|
|
430
|
+
applyRuntimeInsertSectionBreak(runtime, type, options);
|
|
431
|
+
},
|
|
432
|
+
deleteSectionBreak: (sectionIndex) => {
|
|
433
|
+
applyRuntimeDeleteSectionBreak(runtime, sectionIndex);
|
|
434
|
+
},
|
|
435
|
+
updateSectionLayout: (sectionIndex, patch) => {
|
|
436
|
+
applyRuntimeUpdateSectionLayout(runtime, sectionIndex, patch);
|
|
437
|
+
},
|
|
438
|
+
setSectionPageNumbering: (sectionIndex, patch) => {
|
|
439
|
+
applyRuntimeSetSectionPageNumbering(runtime, sectionIndex, patch);
|
|
440
|
+
},
|
|
441
|
+
setHeaderFooterLink: (sectionIndex, params) => {
|
|
442
|
+
applyRuntimeSetHeaderFooterLink(runtime, sectionIndex, params);
|
|
443
|
+
},
|
|
444
|
+
setImageLayout: (mediaId, dimensions) => {
|
|
445
|
+
applyRuntimeImageResize(runtime, mediaId, dimensions);
|
|
446
|
+
},
|
|
447
|
+
setImageFrame: (mediaId, offsets) => {
|
|
448
|
+
applyRuntimeImageReposition(runtime, mediaId, offsets);
|
|
449
|
+
},
|
|
450
|
+
setWorkflowOverlay: (overlay) => {
|
|
451
|
+
runtime.setWorkflowOverlay(clonePublicValue(overlay));
|
|
452
|
+
},
|
|
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
|
+
};
|
|
454
472
|
}
|
|
455
473
|
|
|
456
474
|
export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditorProps>(
|
|
457
475
|
function WordReviewEditor(props, ref) {
|
|
458
476
|
const {
|
|
459
477
|
currentUser,
|
|
478
|
+
hostAdapter,
|
|
460
479
|
datastore,
|
|
461
480
|
documentId,
|
|
462
481
|
externalDocSource,
|
|
463
482
|
externalDocumentRevision,
|
|
464
483
|
initialDocx,
|
|
484
|
+
loadRevision,
|
|
485
|
+
loadSourcePolicy = "prefer-saved-state",
|
|
486
|
+
initialSessionState,
|
|
465
487
|
initialSnapshot,
|
|
466
488
|
initialSourceLabel,
|
|
467
|
-
markupDisplay
|
|
489
|
+
markupDisplay,
|
|
468
490
|
onError,
|
|
469
491
|
onEvent,
|
|
470
492
|
onWarning,
|
|
@@ -473,203 +495,178 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
473
495
|
showReviewPanel = true,
|
|
474
496
|
} = props;
|
|
475
497
|
|
|
476
|
-
const [runtime, setRuntime] = useState<WordReviewEditorRuntime | null>(null);
|
|
477
|
-
const [loadError, setLoadError] = useState<EditorError | null>(null);
|
|
478
|
-
const [viewMode, setViewMode] = useState<ViewMode>("canvas");
|
|
479
|
-
const liveMarkupDisplay: MarkupDisplay = viewMode === "document" ? "all" : "clean";
|
|
480
498
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
481
499
|
const [showTrackedChanges, setShowTrackedChanges] = useState(false);
|
|
482
500
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
483
|
-
const
|
|
501
|
+
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
502
|
+
const [selectionToolbarDismissedKey, setSelectionToolbarDismissedKey] = useState<string | null>(null);
|
|
503
|
+
const [selectionToolbarFocusWithin, setSelectionToolbarFocusWithin] = useState(false);
|
|
484
504
|
const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
|
|
505
|
+
const selectionToolbarElementRef = useRef<HTMLDivElement | null>(null);
|
|
485
506
|
const shellRef = useRef<HTMLDivElement | null>(null);
|
|
507
|
+
const lastSelectionToolbarKeyRef = useRef<string | null>(null);
|
|
486
508
|
const lastAnnouncedErrorIdRef = useRef<string | null>(null);
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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,
|
|
531
|
+
);
|
|
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
|
+
}),
|
|
585
|
+
[
|
|
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,
|
|
606
|
+
],
|
|
607
|
+
);
|
|
608
|
+
const viewState = useRuntimeValue(
|
|
609
|
+
runtime
|
|
610
|
+
? {
|
|
611
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
612
|
+
getValue: () => runtime.getViewState(),
|
|
543
613
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
{
|
|
555
|
-
onWarning: onWarningRef.current,
|
|
556
|
-
onError: onErrorRef.current,
|
|
557
|
-
},
|
|
558
|
-
);
|
|
559
|
-
emitEditorEvent({
|
|
560
|
-
datastore: datastoreRef.current,
|
|
561
|
-
onEvent: onEventRef.current,
|
|
562
|
-
event: createReadyEvent(nextRuntime, source.source),
|
|
563
|
-
});
|
|
564
|
-
runtimeRef.current = nextRuntime;
|
|
565
|
-
setRuntime(nextRuntime);
|
|
566
|
-
} catch (error) {
|
|
567
|
-
if (cancelled) {
|
|
568
|
-
return;
|
|
614
|
+
: null,
|
|
615
|
+
loadingViewState,
|
|
616
|
+
);
|
|
617
|
+
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
618
|
+
const liveMarkupDisplay = __resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
619
|
+
const documentNavigation = useRuntimeValue(
|
|
620
|
+
runtime
|
|
621
|
+
? {
|
|
622
|
+
subscribe: (listener) => runtime.subscribe(listener),
|
|
623
|
+
getValue: () => runtime.getDocumentNavigationSnapshot(),
|
|
569
624
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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;
|
|
653
|
+
const effectiveViewMode = deriveEditorViewMode(snapshot.readOnly, reviewMode);
|
|
596
654
|
|
|
597
655
|
useEffect(() => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return runtime.subscribeToEvents((event) => {
|
|
603
|
-
emitEditorEvent({
|
|
604
|
-
datastore: datastoreRef.current,
|
|
605
|
-
onEvent: onEventRef.current,
|
|
606
|
-
event,
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
}, [runtime]);
|
|
656
|
+
activeRuntime.setViewMode(effectiveViewMode);
|
|
657
|
+
}, [activeRuntime, effectiveViewMode]);
|
|
610
658
|
|
|
611
659
|
useEffect(() => {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
autosaveTimerRef.current = null;
|
|
616
|
-
}
|
|
617
|
-
runtimeRef.current?.dispose?.();
|
|
618
|
-
runtimeRef.current = null;
|
|
660
|
+
runtimeViewStateSeedRef.current = {
|
|
661
|
+
workspaceMode: viewState.workspaceMode,
|
|
662
|
+
zoomLevel: viewState.zoomLevel,
|
|
619
663
|
};
|
|
620
|
-
}, []);
|
|
621
|
-
|
|
622
|
-
const optimisticRuntime = useMemo(
|
|
623
|
-
() =>
|
|
624
|
-
__createFallbackRuntime({
|
|
625
|
-
documentId,
|
|
626
|
-
readOnly,
|
|
627
|
-
currentUserId: currentUser.userId,
|
|
628
|
-
source: {
|
|
629
|
-
source: "snapshot",
|
|
630
|
-
initialSnapshot:
|
|
631
|
-
initialSnapshot ?? createFallbackPersistedSnapshot(documentId, initialSourceLabel),
|
|
632
|
-
sourceLabel: guessSourceLabel(initialSourceLabel, initialSnapshot, externalDocSource),
|
|
633
|
-
},
|
|
634
|
-
datastore: datastoreRef.current,
|
|
635
|
-
}),
|
|
636
|
-
[
|
|
637
|
-
currentUser.userId,
|
|
638
|
-
documentId,
|
|
639
|
-
initialSnapshot,
|
|
640
|
-
initialSourceLabel,
|
|
641
|
-
readOnly,
|
|
642
|
-
externalDocSource?.kind,
|
|
643
|
-
externalDocSource?.sourceLabel,
|
|
644
|
-
],
|
|
645
|
-
);
|
|
646
|
-
|
|
647
|
-
const fallbackSnapshot = useMemo(
|
|
648
|
-
() =>
|
|
649
|
-
loadError
|
|
650
|
-
? createErrorSnapshot(documentId, loadError)
|
|
651
|
-
: createLoadingSnapshot(
|
|
652
|
-
documentId,
|
|
653
|
-
readOnly,
|
|
654
|
-
guessSourceLabel(initialSourceLabel, initialSnapshot, externalDocSource),
|
|
655
|
-
),
|
|
656
|
-
[
|
|
657
|
-
documentId,
|
|
658
|
-
externalDocSource,
|
|
659
|
-
initialSnapshot,
|
|
660
|
-
initialSourceLabel,
|
|
661
|
-
loadError,
|
|
662
|
-
readOnly,
|
|
663
|
-
],
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
const snapshot = useSyncExternalStore(
|
|
667
|
-
(listener) => runtime?.subscribe(listener) ?? (() => undefined),
|
|
668
|
-
() => runtime?.getRenderSnapshot() ?? fallbackSnapshot,
|
|
669
|
-
() => runtime?.getRenderSnapshot() ?? fallbackSnapshot,
|
|
670
|
-
);
|
|
664
|
+
}, [viewState.workspaceMode, viewState.zoomLevel]);
|
|
671
665
|
|
|
672
|
-
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
recordPerfSample("shell.render");
|
|
668
|
+
incrementInvalidationCounter("shell.rerenders");
|
|
669
|
+
}, [snapshot.revisionToken, snapshot.selection, viewState, documentNavigation]);
|
|
673
670
|
|
|
674
671
|
useImperativeHandle(
|
|
675
672
|
ref,
|
|
@@ -678,6 +675,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
678
675
|
blur: () => activeRuntime.blur(),
|
|
679
676
|
undo: () => activeRuntime.undo(),
|
|
680
677
|
redo: () => activeRuntime.redo(),
|
|
678
|
+
replaceText: (text, target) => activeRuntime.replaceText(text, target),
|
|
681
679
|
addComment: (params) =>
|
|
682
680
|
activeRuntime.addComment({
|
|
683
681
|
...params,
|
|
@@ -699,7 +697,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
699
697
|
rejectAllChanges: () => activeRuntime.rejectAllChanges(),
|
|
700
698
|
exportDocx: (options) =>
|
|
701
699
|
runtime
|
|
702
|
-
?
|
|
700
|
+
? persistAndExportFromBoundary({
|
|
701
|
+
hostAdapter: hostAdapterRef.current,
|
|
703
702
|
datastore: datastoreRef.current,
|
|
704
703
|
documentId,
|
|
705
704
|
runtime,
|
|
@@ -709,12 +708,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
709
708
|
lastSavedRevisionTokenRef,
|
|
710
709
|
autosaveTimerRef,
|
|
711
710
|
})
|
|
712
|
-
:
|
|
711
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
713
712
|
documentId,
|
|
713
|
+
hostAdapter: hostAdapterRef.current,
|
|
714
714
|
datastore: datastoreRef.current,
|
|
715
715
|
onError: onErrorRef.current,
|
|
716
716
|
onEvent: onEventRef.current,
|
|
717
717
|
}),
|
|
718
|
+
getSessionState: () => activeRuntime.getSessionState(),
|
|
718
719
|
getSnapshot: () => activeRuntime.getPersistedSnapshot(),
|
|
719
720
|
getRenderSnapshot: () => clonePublicValue(activeRuntime.getRenderSnapshot()),
|
|
720
721
|
getCompatibilityReport: () => activeRuntime.getCompatibilityReport(),
|
|
@@ -730,7 +731,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
730
731
|
isDirty: () => activeRuntime.getRenderSnapshot().isDirty,
|
|
731
732
|
getFormattingState: () =>
|
|
732
733
|
getFormattingStateFromRenderSnapshot(activeRuntime.getRenderSnapshot()),
|
|
733
|
-
|
|
734
|
+
getStyleCatalog: () => getRuntimeStyleCatalog(activeRuntime),
|
|
734
735
|
toggleBold: () => {
|
|
735
736
|
applyRuntimeFormattingOperation(activeRuntime, {
|
|
736
737
|
type: "toggle",
|
|
@@ -797,6 +798,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
797
798
|
alignment,
|
|
798
799
|
});
|
|
799
800
|
},
|
|
801
|
+
setParagraphStyle: (styleId) => {
|
|
802
|
+
applyRuntimeParagraphStyle(activeRuntime, styleId);
|
|
803
|
+
},
|
|
804
|
+
setTableStyle: (styleId) => {
|
|
805
|
+
applyRuntimeTableStyle(activeRuntime, styleId);
|
|
806
|
+
},
|
|
800
807
|
indent: () => {
|
|
801
808
|
applyRuntimeFormattingOperation(activeRuntime, { type: "indent" });
|
|
802
809
|
},
|
|
@@ -864,45 +871,121 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
864
871
|
});
|
|
865
872
|
},
|
|
866
873
|
search: (query, options) =>
|
|
867
|
-
|
|
868
|
-
|
|
874
|
+
searchRuntimeDocument(
|
|
875
|
+
activeRuntime,
|
|
876
|
+
surfaceRef.current,
|
|
877
|
+
query,
|
|
878
|
+
options,
|
|
879
|
+
),
|
|
869
880
|
clearSearch: () => {
|
|
870
881
|
surfaceRef.current?.clearSearch();
|
|
871
882
|
},
|
|
872
883
|
setSelection: (selection) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
selection
|
|
876
|
-
|
|
877
|
-
),
|
|
878
|
-
});
|
|
884
|
+
applyRuntimeSelection(
|
|
885
|
+
activeRuntime,
|
|
886
|
+
normalizeRequestedSelection(activeRuntime.getRenderSnapshot(), selection),
|
|
887
|
+
);
|
|
879
888
|
},
|
|
880
889
|
scrollToRevision: (revisionId: string) => {
|
|
881
890
|
const revision = activeRuntime.getRenderSnapshot().trackedChanges.revisions.find(
|
|
882
891
|
(r) => r.revisionId === revisionId,
|
|
883
892
|
);
|
|
884
893
|
if (!revision || revision.anchor.kind === "detached") return;
|
|
885
|
-
activeRuntime.
|
|
886
|
-
type: "selection.set",
|
|
887
|
-
selection: toRuntimeSelectionSnapshot(createSelectionFromAnchor(revision.anchor)),
|
|
888
|
-
});
|
|
894
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(revision.anchor));
|
|
889
895
|
},
|
|
890
896
|
scrollToComment: (commentId: string) => {
|
|
891
897
|
const comment = activeRuntime.getRenderSnapshot().comments.threads.find(
|
|
892
898
|
(t) => t.commentId === commentId,
|
|
893
899
|
);
|
|
894
900
|
if (!comment || comment.anchor.kind === "detached") return;
|
|
895
|
-
activeRuntime.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
901
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(comment.anchor));
|
|
902
|
+
},
|
|
903
|
+
openStory: (target: EditorStoryTarget) => {
|
|
904
|
+
activeRuntime.openStory(target);
|
|
905
|
+
},
|
|
906
|
+
closeStory: () => {
|
|
907
|
+
activeRuntime.closeStory();
|
|
908
|
+
},
|
|
909
|
+
getPageLayoutSnapshot: () => {
|
|
910
|
+
return clonePublicValue(activeRuntime.getPageLayoutSnapshot());
|
|
911
|
+
},
|
|
912
|
+
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);
|
|
923
|
+
},
|
|
924
|
+
getViewState: () => {
|
|
925
|
+
return clonePublicValue(activeRuntime.getViewState());
|
|
926
|
+
},
|
|
927
|
+
setDocumentMode: (mode) => {
|
|
928
|
+
activeRuntime.setDocumentMode(mode);
|
|
929
|
+
},
|
|
930
|
+
getProtectionSnapshot: () => {
|
|
931
|
+
return clonePublicValue(activeRuntime.getProtectionSnapshot());
|
|
932
|
+
},
|
|
933
|
+
setWorkspaceMode: (mode) => {
|
|
934
|
+
activeRuntime.setWorkspaceMode(mode);
|
|
935
|
+
},
|
|
936
|
+
setZoom: (level) => {
|
|
937
|
+
activeRuntime.setZoom(level);
|
|
938
|
+
},
|
|
939
|
+
insertSectionBreak: (type, options) => {
|
|
940
|
+
applyRuntimeInsertSectionBreak(activeRuntime, type, options);
|
|
941
|
+
},
|
|
942
|
+
deleteSectionBreak: (sectionIndex) => {
|
|
943
|
+
applyRuntimeDeleteSectionBreak(activeRuntime, sectionIndex);
|
|
944
|
+
},
|
|
945
|
+
updateSectionLayout: (sectionIndex, patch) => {
|
|
946
|
+
applyRuntimeUpdateSectionLayout(activeRuntime, sectionIndex, patch);
|
|
947
|
+
},
|
|
948
|
+
setSectionPageNumbering: (sectionIndex, patch) => {
|
|
949
|
+
applyRuntimeSetSectionPageNumbering(activeRuntime, sectionIndex, patch);
|
|
950
|
+
},
|
|
951
|
+
setHeaderFooterLink: (sectionIndex, params) => {
|
|
952
|
+
applyRuntimeSetHeaderFooterLink(activeRuntime, sectionIndex, params);
|
|
953
|
+
},
|
|
954
|
+
setImageLayout: (mediaId, dimensions) => {
|
|
955
|
+
applyRuntimeImageResize(activeRuntime, mediaId, dimensions);
|
|
956
|
+
},
|
|
957
|
+
setImageFrame: (mediaId, offsets) => {
|
|
958
|
+
applyRuntimeImageReposition(activeRuntime, mediaId, offsets);
|
|
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);
|
|
899
980
|
},
|
|
900
981
|
}),
|
|
901
982
|
[activeRuntime, currentUser.userId, documentId, runtime],
|
|
902
983
|
);
|
|
903
984
|
|
|
904
985
|
useEffect(() => {
|
|
905
|
-
|
|
986
|
+
const canPersist =
|
|
987
|
+
Boolean(hostAdapterRef.current?.saveSession) || Boolean(datastoreRef.current?.saveSnapshot);
|
|
988
|
+
if (!canPersist || props.autosave?.enabled === false || !runtime || readOnly) {
|
|
906
989
|
if (autosaveTimerRef.current) {
|
|
907
990
|
clearTimeout(autosaveTimerRef.current);
|
|
908
991
|
autosaveTimerRef.current = null;
|
|
@@ -924,7 +1007,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
924
1007
|
|
|
925
1008
|
const debounceMs = props.autosave?.debounceMs ?? 800;
|
|
926
1009
|
if (debounceMs <= 0) {
|
|
927
|
-
void
|
|
1010
|
+
void persistSessionFromBoundary({
|
|
1011
|
+
hostAdapter: hostAdapterRef.current,
|
|
928
1012
|
datastore: datastoreRef.current,
|
|
929
1013
|
documentId,
|
|
930
1014
|
runtime,
|
|
@@ -937,7 +1021,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
937
1021
|
}
|
|
938
1022
|
|
|
939
1023
|
autosaveTimerRef.current = setTimeout(() => {
|
|
940
|
-
void
|
|
1024
|
+
void persistSessionFromBoundary({
|
|
1025
|
+
hostAdapter: hostAdapterRef.current,
|
|
941
1026
|
datastore: datastoreRef.current,
|
|
942
1027
|
documentId,
|
|
943
1028
|
runtime,
|
|
@@ -989,29 +1074,29 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
989
1074
|
if (anchor.kind === "detached") {
|
|
990
1075
|
return;
|
|
991
1076
|
}
|
|
992
|
-
|
|
993
|
-
activeRuntime.dispatch({
|
|
994
|
-
type: "selection.set",
|
|
995
|
-
selection: toRuntimeSelectionSnapshot(createSelectionFromAnchor(anchor)),
|
|
996
|
-
});
|
|
1077
|
+
applyRuntimeSelection(activeRuntime, createSelectionFromAnchor(anchor));
|
|
997
1078
|
}
|
|
998
1079
|
|
|
999
|
-
function addReviewComment():
|
|
1080
|
+
function addReviewComment(): string | null {
|
|
1000
1081
|
try {
|
|
1001
|
-
activeRuntime.addComment({
|
|
1082
|
+
const commentId = activeRuntime.addComment({
|
|
1002
1083
|
anchor: snapshot.selection.activeRange,
|
|
1003
|
-
body: "
|
|
1084
|
+
body: "",
|
|
1004
1085
|
authorId: currentUser.userId,
|
|
1005
1086
|
});
|
|
1087
|
+
activeRuntime.openComment(commentId);
|
|
1006
1088
|
setActiveRailTab("comments");
|
|
1089
|
+
return commentId;
|
|
1007
1090
|
} catch {
|
|
1008
1091
|
// Runtime already emitted a concrete export-safety error for invalid anchors.
|
|
1092
|
+
return null;
|
|
1009
1093
|
}
|
|
1010
1094
|
}
|
|
1011
1095
|
|
|
1012
1096
|
function exportCurrentDocument(): void {
|
|
1013
1097
|
void (runtime
|
|
1014
|
-
?
|
|
1098
|
+
? persistAndExportFromBoundary({
|
|
1099
|
+
hostAdapter: hostAdapterRef.current,
|
|
1015
1100
|
datastore: datastoreRef.current,
|
|
1016
1101
|
documentId,
|
|
1017
1102
|
runtime,
|
|
@@ -1020,41 +1105,260 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1020
1105
|
lastSavedRevisionTokenRef,
|
|
1021
1106
|
autosaveTimerRef,
|
|
1022
1107
|
})
|
|
1023
|
-
:
|
|
1108
|
+
: rejectExportWhileLoadingFromBoundary({
|
|
1024
1109
|
documentId,
|
|
1110
|
+
hostAdapter: hostAdapterRef.current,
|
|
1025
1111
|
datastore: datastoreRef.current,
|
|
1026
1112
|
onError: onErrorRef.current,
|
|
1027
1113
|
onEvent: onEventRef.current,
|
|
1028
1114
|
}));
|
|
1029
1115
|
}
|
|
1030
1116
|
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1117
|
+
const derivedCapabilities = deriveCapabilities(
|
|
1118
|
+
snapshot,
|
|
1119
|
+
reviewMode,
|
|
1120
|
+
workflowScopeSnapshot,
|
|
1121
|
+
);
|
|
1033
1122
|
const capabilities = showReviewPanel
|
|
1034
1123
|
? derivedCapabilities
|
|
1035
1124
|
: { ...derivedCapabilities, reviewRailVisible: false };
|
|
1125
|
+
const formattingState = getFormattingStateFromRenderSnapshot(snapshot);
|
|
1126
|
+
const styleCatalog = useMemo(
|
|
1127
|
+
() => getRuntimeStyleCatalog(canonicalDocument.styles),
|
|
1128
|
+
[canonicalDocument.styles],
|
|
1129
|
+
);
|
|
1036
1130
|
const diagnosticsModeMessage = getDiagnosticsModeMessage(loadError ?? snapshot.fatalError);
|
|
1037
1131
|
const addCommentDisabledReason =
|
|
1038
1132
|
!capabilities.canAddComment && !snapshot.selection.isCollapsed
|
|
1039
1133
|
? "Select text within one paragraph to add a DOCX comment."
|
|
1040
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
|
+
);
|
|
1200
|
+
const selectionToolbar = buildSelectionToolbarModel({
|
|
1201
|
+
snapshot,
|
|
1202
|
+
viewState,
|
|
1203
|
+
capabilities,
|
|
1204
|
+
documentNavigation,
|
|
1205
|
+
styleCatalog,
|
|
1206
|
+
formattingState,
|
|
1207
|
+
addCommentDisabledReason,
|
|
1208
|
+
});
|
|
1209
|
+
const selectionToolbarSelectionKey = useMemo(
|
|
1210
|
+
() => createSelectionToolbarSelectionKey(snapshot.selection, viewState.activeStory),
|
|
1211
|
+
[snapshot.selection, viewState.activeStory],
|
|
1212
|
+
);
|
|
1213
|
+
const shouldRenderSelectionToolbar = Boolean(
|
|
1214
|
+
selectionToolbar &&
|
|
1215
|
+
selectionToolbarSelectionKey &&
|
|
1216
|
+
selectionToolbarDismissedKey !== selectionToolbarSelectionKey &&
|
|
1217
|
+
(viewState.isFocused || selectionToolbarFocusWithin),
|
|
1218
|
+
);
|
|
1041
1219
|
const accessibilityInstructionsId = `${documentId}-accessibility-instructions`;
|
|
1042
1220
|
const accessibilityStatusId = `${documentId}-accessibility-status`;
|
|
1043
1221
|
const accessibilityAlertId = `${documentId}-accessibility-alert`;
|
|
1044
1222
|
|
|
1045
1223
|
const dispatchSelection = (selection: PublicSelectionSnapshot) =>
|
|
1046
|
-
activeRuntime
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1224
|
+
applyRuntimeSelection(activeRuntime, selection);
|
|
1225
|
+
|
|
1226
|
+
const dismissSelectionToolbar = useCallback(
|
|
1227
|
+
(_reason: SelectionToolbarDismissReason) => {
|
|
1228
|
+
if (selectionToolbarSelectionKey) {
|
|
1229
|
+
setSelectionToolbarDismissedKey(selectionToolbarSelectionKey);
|
|
1230
|
+
} else {
|
|
1231
|
+
setSelectionToolbarDismissedKey(null);
|
|
1232
|
+
}
|
|
1233
|
+
setSelectionToolbarAnchor(null);
|
|
1234
|
+
setSelectionToolbarFocusWithin(false);
|
|
1235
|
+
},
|
|
1236
|
+
[selectionToolbarSelectionKey],
|
|
1237
|
+
);
|
|
1050
1238
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1239
|
+
const getDocumentSurfaceElement = useCallback((): HTMLElement | null => {
|
|
1240
|
+
return shellRef.current?.querySelector<HTMLElement>("[data-wre-document-surface='true']") ?? null;
|
|
1241
|
+
}, []);
|
|
1056
1242
|
|
|
1057
|
-
|
|
1243
|
+
const isTargetWithinSelectionToolbar = useCallback((target: EventTarget | null): boolean => {
|
|
1244
|
+
return target instanceof Node && Boolean(selectionToolbarElementRef.current?.contains(target));
|
|
1245
|
+
}, []);
|
|
1246
|
+
|
|
1247
|
+
const isTargetWithinDocumentSurface = useCallback(
|
|
1248
|
+
(target: EventTarget | null): boolean => {
|
|
1249
|
+
const surfaceElement = getDocumentSurfaceElement();
|
|
1250
|
+
return target instanceof Node && Boolean(surfaceElement?.contains(target));
|
|
1251
|
+
},
|
|
1252
|
+
[getDocumentSurfaceElement],
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
const focusDocumentSurface = useCallback((): void => {
|
|
1256
|
+
const surfaceElement = getDocumentSurfaceElement();
|
|
1257
|
+
surfaceElement?.focus();
|
|
1258
|
+
activeRuntime.focus();
|
|
1259
|
+
}, [activeRuntime, getDocumentSurfaceElement]);
|
|
1260
|
+
|
|
1261
|
+
const handleSurfaceFocus = useCallback(
|
|
1262
|
+
(_event: FocusEvent<HTMLDivElement>) => {
|
|
1263
|
+
setSelectionToolbarFocusWithin(false);
|
|
1264
|
+
activeRuntime.focus();
|
|
1265
|
+
},
|
|
1266
|
+
[activeRuntime],
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
const handleSurfaceBlur = useCallback(
|
|
1270
|
+
(event: FocusEvent<HTMLDivElement>) => {
|
|
1271
|
+
if (isTargetWithinSelectionToolbar(event.relatedTarget)) {
|
|
1272
|
+
setSelectionToolbarFocusWithin(true);
|
|
1273
|
+
activeRuntime.focus();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
setSelectionToolbarFocusWithin(false);
|
|
1278
|
+
activeRuntime.blur();
|
|
1279
|
+
dismissSelectionToolbar("blur");
|
|
1280
|
+
},
|
|
1281
|
+
[activeRuntime, dismissSelectionToolbar, isTargetWithinSelectionToolbar],
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
const handleSelectionToolbarFocusCapture = useCallback(() => {
|
|
1285
|
+
setSelectionToolbarFocusWithin(true);
|
|
1286
|
+
activeRuntime.focus();
|
|
1287
|
+
}, [activeRuntime]);
|
|
1288
|
+
|
|
1289
|
+
const handleSelectionToolbarBlurCapture = useCallback(
|
|
1290
|
+
(event: FocusEvent<HTMLDivElement>) => {
|
|
1291
|
+
if (isTargetWithinSelectionToolbar(event.relatedTarget)) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (isTargetWithinDocumentSurface(event.relatedTarget)) {
|
|
1296
|
+
setSelectionToolbarFocusWithin(false);
|
|
1297
|
+
activeRuntime.focus();
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
setSelectionToolbarFocusWithin(false);
|
|
1302
|
+
activeRuntime.blur();
|
|
1303
|
+
dismissSelectionToolbar("blur");
|
|
1304
|
+
},
|
|
1305
|
+
[
|
|
1306
|
+
activeRuntime,
|
|
1307
|
+
dismissSelectionToolbar,
|
|
1308
|
+
isTargetWithinDocumentSurface,
|
|
1309
|
+
isTargetWithinSelectionToolbar,
|
|
1310
|
+
],
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
const addSelectionToolbarComment = useCallback(() => {
|
|
1314
|
+
const commentId = addReviewComment();
|
|
1315
|
+
if (!commentId) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
dismissSelectionToolbar("comment-action");
|
|
1319
|
+
queueMicrotask(() => {
|
|
1320
|
+
focusDocumentSurface();
|
|
1321
|
+
});
|
|
1322
|
+
}, [dismissSelectionToolbar, focusDocumentSurface]);
|
|
1323
|
+
|
|
1324
|
+
const handleSelectionToolbarAnchorChange = useCallback(
|
|
1325
|
+
(nextAnchor: SelectionToolbarAnchor | null) => {
|
|
1326
|
+
setSelectionToolbarAnchor((current) =>
|
|
1327
|
+
selectionToolbarAnchorsEqual(current, nextAnchor) ? current : nextAnchor);
|
|
1328
|
+
},
|
|
1329
|
+
[],
|
|
1330
|
+
);
|
|
1331
|
+
|
|
1332
|
+
useEffect(() => {
|
|
1333
|
+
if (!selectionToolbarSelectionKey) {
|
|
1334
|
+
setSelectionToolbarDismissedKey(null);
|
|
1335
|
+
setSelectionToolbarFocusWithin(false);
|
|
1336
|
+
setSelectionToolbarAnchor(null);
|
|
1337
|
+
lastSelectionToolbarKeyRef.current = null;
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (lastSelectionToolbarKeyRef.current !== selectionToolbarSelectionKey) {
|
|
1342
|
+
lastSelectionToolbarKeyRef.current = selectionToolbarSelectionKey;
|
|
1343
|
+
setSelectionToolbarDismissedKey(null);
|
|
1344
|
+
setSelectionToolbarFocusWithin(false);
|
|
1345
|
+
}
|
|
1346
|
+
}, [selectionToolbarSelectionKey]);
|
|
1347
|
+
|
|
1348
|
+
useEffect(() => {
|
|
1349
|
+
if (!selectionToolbar) {
|
|
1350
|
+
setSelectionToolbarAnchor(null);
|
|
1351
|
+
setSelectionToolbarFocusWithin(false);
|
|
1352
|
+
}
|
|
1353
|
+
}, [selectionToolbar]);
|
|
1354
|
+
|
|
1355
|
+
useEffect(() => {
|
|
1356
|
+
const shell = shellRef.current;
|
|
1357
|
+
if (!shell) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
applyRegionAttributes(shell);
|
|
1058
1362
|
}, [runtime, snapshot.fatalError, snapshot.isReady]);
|
|
1059
1363
|
|
|
1060
1364
|
useEffect(() => {
|
|
@@ -1075,6 +1379,23 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1075
1379
|
}, [loadError, snapshot.fatalError]);
|
|
1076
1380
|
|
|
1077
1381
|
function handleShellKeyDownCapture(event: React.KeyboardEvent<HTMLDivElement>): void {
|
|
1382
|
+
if (
|
|
1383
|
+
event.key === "Escape" &&
|
|
1384
|
+
shouldRenderSelectionToolbar &&
|
|
1385
|
+
(isTargetWithinDocumentSurface(event.target) || isTargetWithinSelectionToolbar(event.target))
|
|
1386
|
+
) {
|
|
1387
|
+
event.preventDefault();
|
|
1388
|
+
event.stopPropagation();
|
|
1389
|
+
const restoreSurfaceFocus = isTargetWithinSelectionToolbar(event.target);
|
|
1390
|
+
dismissSelectionToolbar("escape");
|
|
1391
|
+
if (restoreSurfaceFocus) {
|
|
1392
|
+
queueMicrotask(() => {
|
|
1393
|
+
focusDocumentSurface();
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1078
1399
|
if (event.key !== "F6") {
|
|
1079
1400
|
return;
|
|
1080
1401
|
}
|
|
@@ -1089,15 +1410,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1089
1410
|
}
|
|
1090
1411
|
|
|
1091
1412
|
const editorCallbacks = {
|
|
1092
|
-
onFocus:
|
|
1093
|
-
onBlur:
|
|
1413
|
+
onFocus: handleSurfaceFocus,
|
|
1414
|
+
onBlur: handleSurfaceBlur,
|
|
1094
1415
|
onSelectionChange: dispatchSelection,
|
|
1095
|
-
onInsertText: (text: string) => activeRuntime
|
|
1096
|
-
onDeleteBackward: () => activeRuntime
|
|
1097
|
-
onDeleteForward: () => activeRuntime
|
|
1098
|
-
onInsertTab: () => activeRuntime
|
|
1099
|
-
|
|
1100
|
-
|
|
1416
|
+
onInsertText: (text: string) => applyRuntimeTextCommand(activeRuntime, { type: "insert-text", text }),
|
|
1417
|
+
onDeleteBackward: () => applyRuntimeTextCommand(activeRuntime, { type: "delete-backward" }),
|
|
1418
|
+
onDeleteForward: () => applyRuntimeTextCommand(activeRuntime, { type: "delete-forward" }),
|
|
1419
|
+
onInsertTab: () => applyRuntimeTextCommand(activeRuntime, { type: "insert-tab" }),
|
|
1420
|
+
onOutdentTab: () => applyRuntimeTextCommand(activeRuntime, { type: "outdent-tab" }),
|
|
1421
|
+
onInsertHardBreak: () => applyRuntimeTextCommand(activeRuntime, { type: "insert-hard-break" }),
|
|
1422
|
+
onSplitParagraph: () => applyRuntimeTextCommand(activeRuntime, { type: "split-paragraph" }),
|
|
1101
1423
|
};
|
|
1102
1424
|
|
|
1103
1425
|
const reviewCallbacks = {
|
|
@@ -1145,82 +1467,207 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1145
1467
|
activeRuntime.rejectAllChanges();
|
|
1146
1468
|
setActiveRailTab("changes");
|
|
1147
1469
|
},
|
|
1470
|
+
onCloseStory: () => {
|
|
1471
|
+
activeRuntime.closeStory();
|
|
1472
|
+
},
|
|
1148
1473
|
};
|
|
1149
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
|
+
|
|
1150
1627
|
return (
|
|
1151
|
-
<
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
{
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
)
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
activeRevisionId={activeRevisionId}
|
|
1195
|
-
showTrackedChanges={showTrackedChanges}
|
|
1196
|
-
selectionPreview={selectionPreview}
|
|
1197
|
-
addCommentDisabledReason={addCommentDisabledReason}
|
|
1198
|
-
onViewModeChange={setViewMode}
|
|
1199
|
-
onActiveRailTabChange={setActiveRailTab}
|
|
1200
|
-
onShowTrackedChangesChange={setShowTrackedChanges}
|
|
1201
|
-
{...reviewCallbacks}
|
|
1202
|
-
document={
|
|
1203
|
-
<TwProseMirrorSurface
|
|
1204
|
-
ref={surfaceRef}
|
|
1205
|
-
currentUser={currentUser}
|
|
1206
|
-
snapshot={snapshot}
|
|
1207
|
-
reviewMode={reviewMode}
|
|
1208
|
-
markupDisplay={liveMarkupDisplay}
|
|
1209
|
-
activeRevisionId={activeRevisionId}
|
|
1210
|
-
showTrackedChanges={showTrackedChanges}
|
|
1211
|
-
{...editorCallbacks}
|
|
1212
|
-
onCommentActivated={(commentId) => {
|
|
1213
|
-
activeRuntime.openComment(commentId);
|
|
1214
|
-
setActiveRailTab("comments");
|
|
1215
|
-
}}
|
|
1216
|
-
onRevisionActivated={(revisionId) => {
|
|
1217
|
-
setActiveRevisionId(revisionId);
|
|
1218
|
-
setActiveRailTab("changes");
|
|
1219
|
-
}}
|
|
1220
|
-
/>
|
|
1221
|
-
}
|
|
1222
|
-
/>
|
|
1223
|
-
</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
|
+
/>
|
|
1224
1671
|
);
|
|
1225
1672
|
},
|
|
1226
1673
|
);
|
|
@@ -1237,206 +1684,1034 @@ function applyRuntimeFormattingOperation(
|
|
|
1237
1684
|
| { type: "indent" }
|
|
1238
1685
|
| { type: "outdent" },
|
|
1239
1686
|
): void {
|
|
1240
|
-
const
|
|
1241
|
-
if (!
|
|
1687
|
+
const context = getStoryMutationContext(runtime);
|
|
1688
|
+
if (!context) {
|
|
1242
1689
|
return;
|
|
1243
1690
|
}
|
|
1244
1691
|
|
|
1245
1692
|
const result = applyFormattingOperationToDocument(
|
|
1246
|
-
|
|
1247
|
-
|
|
1693
|
+
context.localDocument,
|
|
1694
|
+
context.localSnapshot,
|
|
1248
1695
|
operation,
|
|
1249
1696
|
);
|
|
1250
|
-
|
|
1697
|
+
dispatchStoryMutationResult(
|
|
1698
|
+
runtime,
|
|
1699
|
+
context,
|
|
1700
|
+
{
|
|
1701
|
+
...result,
|
|
1702
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1703
|
+
},
|
|
1704
|
+
context.timestamp,
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
function getRuntimeStyleCatalog(
|
|
1709
|
+
input:
|
|
1710
|
+
| WordReviewEditorRuntime
|
|
1711
|
+
| EditorSessionState["canonicalDocument"]["styles"],
|
|
1712
|
+
): StyleCatalogSnapshot {
|
|
1713
|
+
const styles =
|
|
1714
|
+
"getSessionState" in input
|
|
1715
|
+
? input.getSessionState().canonicalDocument.styles
|
|
1716
|
+
: input;
|
|
1717
|
+
const mapRecord = <
|
|
1718
|
+
T extends {
|
|
1719
|
+
styleId: string;
|
|
1720
|
+
displayName: string;
|
|
1721
|
+
kind: "paragraph" | "character" | "table";
|
|
1722
|
+
isDefault: boolean;
|
|
1723
|
+
basedOn?: string;
|
|
1724
|
+
nextStyle?: string;
|
|
1725
|
+
},
|
|
1726
|
+
>(
|
|
1727
|
+
record: Record<string, T>,
|
|
1728
|
+
) =>
|
|
1729
|
+
Object.values(record)
|
|
1730
|
+
.map((entry) => ({
|
|
1731
|
+
styleId: entry.styleId,
|
|
1732
|
+
displayName: entry.displayName,
|
|
1733
|
+
kind: entry.kind,
|
|
1734
|
+
isDefault: entry.isDefault,
|
|
1735
|
+
...(entry.basedOn ? { basedOn: entry.basedOn } : {}),
|
|
1736
|
+
...(entry.nextStyle ? { nextStyle: entry.nextStyle } : {}),
|
|
1737
|
+
}))
|
|
1738
|
+
.sort((left, right) =>
|
|
1739
|
+
left.displayName.localeCompare(right.displayName) ||
|
|
1740
|
+
left.styleId.localeCompare(right.styleId),
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
return {
|
|
1744
|
+
paragraphs: mapRecord(styles.paragraphs),
|
|
1745
|
+
characters: mapRecord(styles.characters),
|
|
1746
|
+
tables: mapRecord(styles.tables),
|
|
1747
|
+
fromPackage: styles.fromPackage === true,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function applyRuntimeParagraphStyle(
|
|
1752
|
+
runtime: WordReviewEditorRuntime,
|
|
1753
|
+
styleId: string | null,
|
|
1754
|
+
): void {
|
|
1755
|
+
const context = getStoryMutationContext(runtime);
|
|
1756
|
+
if (!context) {
|
|
1251
1757
|
return;
|
|
1252
1758
|
}
|
|
1253
1759
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1760
|
+
const result = applyParagraphStyleToDocument(
|
|
1761
|
+
context.localDocument,
|
|
1762
|
+
context.localSnapshot,
|
|
1763
|
+
styleId,
|
|
1764
|
+
);
|
|
1765
|
+
dispatchStoryMutationResult(
|
|
1766
|
+
runtime,
|
|
1767
|
+
context,
|
|
1768
|
+
{
|
|
1769
|
+
...result,
|
|
1770
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1261
1771
|
},
|
|
1262
|
-
|
|
1772
|
+
context.timestamp,
|
|
1773
|
+
);
|
|
1263
1774
|
}
|
|
1264
1775
|
|
|
1265
|
-
function
|
|
1266
|
-
|
|
1267
|
-
|
|
1776
|
+
function applyRuntimeTableStyle(
|
|
1777
|
+
runtime: WordReviewEditorRuntime,
|
|
1778
|
+
styleId: string | null,
|
|
1779
|
+
): void {
|
|
1780
|
+
const context = getStoryMutationContext(runtime);
|
|
1781
|
+
if (!context) {
|
|
1268
1782
|
return;
|
|
1269
1783
|
}
|
|
1270
1784
|
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1785
|
+
const result = applyTableStyleToDocument(
|
|
1786
|
+
context.localDocument,
|
|
1787
|
+
context.localSnapshot,
|
|
1788
|
+
styleId,
|
|
1789
|
+
);
|
|
1790
|
+
dispatchStoryMutationResult(
|
|
1791
|
+
runtime,
|
|
1792
|
+
context,
|
|
1793
|
+
{
|
|
1794
|
+
...result,
|
|
1795
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1796
|
+
},
|
|
1797
|
+
context.timestamp,
|
|
1276
1798
|
);
|
|
1277
|
-
dispatchRuntimeDocumentMutation(runtime, result, timestamp);
|
|
1278
1799
|
}
|
|
1279
1800
|
|
|
1280
|
-
function
|
|
1801
|
+
function applyRuntimeParagraphIndentation(
|
|
1281
1802
|
runtime: WordReviewEditorRuntime,
|
|
1282
|
-
|
|
1803
|
+
indentation: {
|
|
1804
|
+
left?: number;
|
|
1805
|
+
right?: number;
|
|
1806
|
+
firstLine?: number;
|
|
1807
|
+
hanging?: number;
|
|
1808
|
+
},
|
|
1283
1809
|
): void {
|
|
1284
|
-
const
|
|
1285
|
-
if (!
|
|
1810
|
+
const context = getStoryMutationContext(runtime);
|
|
1811
|
+
if (!context) {
|
|
1286
1812
|
return;
|
|
1287
1813
|
}
|
|
1288
1814
|
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1815
|
+
const result = setActiveParagraphIndentation(
|
|
1816
|
+
context.localDocument,
|
|
1817
|
+
context.localSnapshot,
|
|
1818
|
+
indentation,
|
|
1819
|
+
{ timestamp: context.timestamp },
|
|
1820
|
+
);
|
|
1821
|
+
dispatchStoryMutationResult(
|
|
1822
|
+
runtime,
|
|
1823
|
+
context,
|
|
1824
|
+
{
|
|
1825
|
+
...result,
|
|
1826
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1827
|
+
},
|
|
1828
|
+
context.timestamp,
|
|
1295
1829
|
);
|
|
1296
|
-
dispatchRuntimeDocumentMutation(runtime, result, timestamp);
|
|
1297
1830
|
}
|
|
1298
1831
|
|
|
1299
|
-
function
|
|
1832
|
+
function applyRuntimeParagraphTabStops(
|
|
1300
1833
|
runtime: WordReviewEditorRuntime,
|
|
1301
|
-
|
|
1834
|
+
tabStops: Array<{ pos: number; val?: string; leader?: string }>,
|
|
1302
1835
|
): void {
|
|
1303
|
-
const
|
|
1304
|
-
if (!
|
|
1836
|
+
const context = getStoryMutationContext(runtime);
|
|
1837
|
+
if (!context) {
|
|
1305
1838
|
return;
|
|
1306
1839
|
}
|
|
1307
1840
|
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1841
|
+
const result = setActiveParagraphTabStops(
|
|
1842
|
+
context.localDocument,
|
|
1843
|
+
context.localSnapshot,
|
|
1844
|
+
tabStops,
|
|
1845
|
+
{ timestamp: context.timestamp },
|
|
1846
|
+
);
|
|
1847
|
+
dispatchStoryMutationResult(
|
|
1848
|
+
runtime,
|
|
1849
|
+
context,
|
|
1850
|
+
{
|
|
1851
|
+
...result,
|
|
1852
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1853
|
+
},
|
|
1854
|
+
context.timestamp,
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function applyRuntimeNumberingFlow(
|
|
1859
|
+
runtime: WordReviewEditorRuntime,
|
|
1860
|
+
operation: { type: "restart"; startAt?: number } | { type: "continue" },
|
|
1861
|
+
): void {
|
|
1862
|
+
const context = getStoryMutationContext(runtime);
|
|
1863
|
+
if (!context) {
|
|
1329
1864
|
return;
|
|
1330
1865
|
}
|
|
1866
|
+
|
|
1867
|
+
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
1868
|
+
if (!paragraphContext?.paragraph.numbering) {
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const result =
|
|
1873
|
+
operation.type === "restart"
|
|
1874
|
+
? restartListNumbering(
|
|
1875
|
+
context.localDocument,
|
|
1876
|
+
paragraphContext.paragraphIndex,
|
|
1877
|
+
{ timestamp: context.timestamp },
|
|
1878
|
+
operation.startAt,
|
|
1879
|
+
)
|
|
1880
|
+
: continueListNumbering(
|
|
1881
|
+
context.localDocument,
|
|
1882
|
+
paragraphContext.paragraphIndex,
|
|
1883
|
+
{ timestamp: context.timestamp },
|
|
1884
|
+
);
|
|
1885
|
+
|
|
1886
|
+
dispatchStoryMutationResult(
|
|
1887
|
+
runtime,
|
|
1888
|
+
context,
|
|
1889
|
+
{
|
|
1890
|
+
changed: result.affectedParagraphIndexes.length > 0,
|
|
1891
|
+
document: result.document,
|
|
1892
|
+
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
1893
|
+
},
|
|
1894
|
+
context.timestamp,
|
|
1895
|
+
);
|
|
1331
1896
|
}
|
|
1332
1897
|
|
|
1333
|
-
function
|
|
1898
|
+
function applyRuntimeInsertSectionBreak(
|
|
1334
1899
|
runtime: WordReviewEditorRuntime,
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
| { type: "add-row-before" }
|
|
1338
|
-
| { type: "add-row-after" }
|
|
1339
|
-
| { type: "add-column-before" }
|
|
1340
|
-
| { type: "add-column-after" }
|
|
1341
|
-
| { type: "delete-row" }
|
|
1342
|
-
| { type: "delete-column" }
|
|
1343
|
-
| { type: "delete-table" }
|
|
1344
|
-
| { type: "merge-cells" }
|
|
1345
|
-
| { type: "split-cell" }
|
|
1346
|
-
| { type: "set-cell-background"; color: string },
|
|
1900
|
+
breakType: SectionBreakType,
|
|
1901
|
+
options?: { afterSectionIndex?: number },
|
|
1347
1902
|
): void {
|
|
1348
1903
|
const snapshot = runtime.getRenderSnapshot();
|
|
1349
|
-
if (!canApplyRuntimeMutation(snapshot)) {
|
|
1904
|
+
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1350
1905
|
return;
|
|
1351
1906
|
}
|
|
1352
1907
|
|
|
1908
|
+
const sessionState = runtime.getSessionState();
|
|
1353
1909
|
const timestamp = new Date().toISOString();
|
|
1354
|
-
const result =
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1910
|
+
const result =
|
|
1911
|
+
typeof options?.afterSectionIndex === "number"
|
|
1912
|
+
? insertSectionBreakAfterSectionIndex(
|
|
1913
|
+
sessionState.canonicalDocument,
|
|
1914
|
+
options.afterSectionIndex,
|
|
1915
|
+
breakType,
|
|
1916
|
+
{ timestamp },
|
|
1917
|
+
)
|
|
1918
|
+
: insertSectionBreakAfterSectionIndex(
|
|
1919
|
+
sessionState.canonicalDocument,
|
|
1920
|
+
runtime.getDocumentNavigationSnapshot().activeSectionIndex,
|
|
1921
|
+
breakType,
|
|
1922
|
+
{ timestamp },
|
|
1923
|
+
);
|
|
1924
|
+
|
|
1925
|
+
dispatchRuntimeDocumentMutation(
|
|
1926
|
+
runtime,
|
|
1927
|
+
{
|
|
1928
|
+
changed: result.changed,
|
|
1929
|
+
document: result.document,
|
|
1930
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1931
|
+
},
|
|
1932
|
+
timestamp,
|
|
1359
1933
|
);
|
|
1360
|
-
dispatchRuntimeDocumentMutation(runtime, result, timestamp);
|
|
1361
1934
|
}
|
|
1362
1935
|
|
|
1363
|
-
function
|
|
1364
|
-
|
|
1936
|
+
function applyRuntimeDeleteSectionBreak(
|
|
1937
|
+
runtime: WordReviewEditorRuntime,
|
|
1938
|
+
sectionIndex: number,
|
|
1939
|
+
): void {
|
|
1940
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
1941
|
+
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const sessionState = runtime.getSessionState();
|
|
1946
|
+
const timestamp = new Date().toISOString();
|
|
1947
|
+
const result = deleteSectionBreakAtSectionIndex(
|
|
1948
|
+
sessionState.canonicalDocument,
|
|
1949
|
+
sectionIndex,
|
|
1950
|
+
{ timestamp },
|
|
1951
|
+
);
|
|
1952
|
+
|
|
1953
|
+
dispatchRuntimeDocumentMutation(
|
|
1954
|
+
runtime,
|
|
1955
|
+
{
|
|
1956
|
+
changed: result.changed,
|
|
1957
|
+
document: result.document,
|
|
1958
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1959
|
+
},
|
|
1960
|
+
timestamp,
|
|
1961
|
+
);
|
|
1365
1962
|
}
|
|
1366
1963
|
|
|
1367
|
-
function
|
|
1964
|
+
function applyRuntimeUpdateSectionLayout(
|
|
1368
1965
|
runtime: WordReviewEditorRuntime,
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
document: PersistedEditorSnapshot["canonicalDocument"];
|
|
1372
|
-
selection: InternalSelectionSnapshot;
|
|
1373
|
-
mapping?: TransactionMapping;
|
|
1374
|
-
},
|
|
1375
|
-
timestamp: string,
|
|
1966
|
+
sectionIndex: number,
|
|
1967
|
+
patch: SectionLayoutPatch,
|
|
1376
1968
|
): void {
|
|
1377
|
-
|
|
1969
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
1970
|
+
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1378
1971
|
return;
|
|
1379
1972
|
}
|
|
1380
1973
|
|
|
1381
|
-
runtime.
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1974
|
+
const sessionState = runtime.getSessionState();
|
|
1975
|
+
const timestamp = new Date().toISOString();
|
|
1976
|
+
const result = updateSectionLayoutAtSectionIndex(
|
|
1977
|
+
sessionState.canonicalDocument,
|
|
1978
|
+
sectionIndex,
|
|
1979
|
+
{
|
|
1980
|
+
...(patch.pageSize ? { pageSize: patch.pageSize } : {}),
|
|
1981
|
+
...(patch.pageMargins ? { pageMargins: patch.pageMargins } : {}),
|
|
1982
|
+
...(patch.columns ? { columns: patch.columns } : {}),
|
|
1983
|
+
...(patch.titlePage !== undefined ? { titlePage: patch.titlePage } : {}),
|
|
1984
|
+
...(patch.sectionType ? { sectionType: patch.sectionType } : {}),
|
|
1386
1985
|
},
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1986
|
+
{ timestamp },
|
|
1987
|
+
);
|
|
1988
|
+
|
|
1989
|
+
dispatchRuntimeDocumentMutation(
|
|
1990
|
+
runtime,
|
|
1991
|
+
{
|
|
1992
|
+
changed: result.changed,
|
|
1993
|
+
document: result.document,
|
|
1994
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1392
1995
|
},
|
|
1393
|
-
|
|
1996
|
+
timestamp,
|
|
1997
|
+
);
|
|
1394
1998
|
}
|
|
1395
1999
|
|
|
1396
|
-
function
|
|
2000
|
+
function applyRuntimeSetSectionPageNumbering(
|
|
1397
2001
|
runtime: WordReviewEditorRuntime,
|
|
1398
|
-
|
|
2002
|
+
sectionIndex: number,
|
|
2003
|
+
patch: SectionPageNumberingPatch | null,
|
|
1399
2004
|
): void {
|
|
1400
2005
|
const snapshot = runtime.getRenderSnapshot();
|
|
1401
|
-
if (!snapshot
|
|
2006
|
+
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1402
2007
|
return;
|
|
1403
2008
|
}
|
|
1404
2009
|
|
|
1405
|
-
const
|
|
1406
|
-
|
|
2010
|
+
const sessionState = runtime.getSessionState();
|
|
2011
|
+
const timestamp = new Date().toISOString();
|
|
2012
|
+
const normalizedPatch =
|
|
2013
|
+
patch === null
|
|
2014
|
+
? null
|
|
2015
|
+
: {
|
|
2016
|
+
...(patch.format !== undefined
|
|
2017
|
+
? { format: patch.format ?? undefined }
|
|
2018
|
+
: {}),
|
|
2019
|
+
...(patch.start !== undefined
|
|
2020
|
+
? { start: patch.start ?? undefined }
|
|
2021
|
+
: {}),
|
|
2022
|
+
...(patch.chapterStyle !== undefined
|
|
2023
|
+
? { chapStyle: patch.chapterStyle ?? undefined }
|
|
2024
|
+
: {}),
|
|
2025
|
+
...(patch.chapterSeparator !== undefined
|
|
2026
|
+
? { chapSep: patch.chapterSeparator ?? undefined }
|
|
2027
|
+
: {}),
|
|
2028
|
+
};
|
|
2029
|
+
const result = setSectionPageNumberingAtSectionIndex(
|
|
2030
|
+
sessionState.canonicalDocument,
|
|
2031
|
+
sectionIndex,
|
|
2032
|
+
normalizedPatch,
|
|
2033
|
+
{ timestamp },
|
|
2034
|
+
);
|
|
2035
|
+
|
|
2036
|
+
dispatchRuntimeDocumentMutation(
|
|
2037
|
+
runtime,
|
|
2038
|
+
{
|
|
2039
|
+
changed: result.changed,
|
|
2040
|
+
document: result.document,
|
|
2041
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
2042
|
+
},
|
|
2043
|
+
timestamp,
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
function applyRuntimeSetHeaderFooterLink(
|
|
2048
|
+
runtime: WordReviewEditorRuntime,
|
|
2049
|
+
sectionIndex: number,
|
|
2050
|
+
patch: HeaderFooterLinkPatch,
|
|
2051
|
+
): void {
|
|
2052
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2053
|
+
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
1407
2054
|
return;
|
|
1408
2055
|
}
|
|
1409
2056
|
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2057
|
+
const sessionState = runtime.getSessionState();
|
|
2058
|
+
const timestamp = new Date().toISOString();
|
|
2059
|
+
const result = setHeaderFooterLinkAtSectionIndex(
|
|
2060
|
+
sessionState.canonicalDocument,
|
|
2061
|
+
sectionIndex,
|
|
2062
|
+
patch,
|
|
2063
|
+
{ timestamp },
|
|
2064
|
+
);
|
|
1414
2065
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
comments: nextComments,
|
|
1422
|
-
},
|
|
1423
|
-
},
|
|
1424
|
-
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
1425
|
-
origin: {
|
|
1426
|
-
source: "api",
|
|
1427
|
-
timestamp: new Date().toISOString(),
|
|
2066
|
+
dispatchRuntimeDocumentMutation(
|
|
2067
|
+
runtime,
|
|
2068
|
+
{
|
|
2069
|
+
changed: result.changed,
|
|
2070
|
+
document: result.document,
|
|
2071
|
+
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
1428
2072
|
},
|
|
1429
|
-
|
|
2073
|
+
timestamp,
|
|
2074
|
+
);
|
|
1430
2075
|
}
|
|
1431
2076
|
|
|
1432
|
-
function
|
|
1433
|
-
|
|
1434
|
-
|
|
2077
|
+
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
2078
|
+
const context = getStoryMutationContext(runtime);
|
|
2079
|
+
if (!context) {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
const result = insertPageBreakInDocument(
|
|
2084
|
+
context.localDocument,
|
|
2085
|
+
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
2086
|
+
{ timestamp: context.timestamp },
|
|
2087
|
+
);
|
|
2088
|
+
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function applyRuntimeInsertTable(
|
|
2092
|
+
runtime: WordReviewEditorRuntime,
|
|
2093
|
+
options: InsertTableOptions,
|
|
2094
|
+
): void {
|
|
2095
|
+
const context = getStoryMutationContext(runtime);
|
|
2096
|
+
if (!context) {
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
const result = insertTableInDocument(
|
|
2101
|
+
context.localDocument,
|
|
2102
|
+
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
2103
|
+
options,
|
|
2104
|
+
{ timestamp: context.timestamp },
|
|
2105
|
+
);
|
|
2106
|
+
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
function applyRuntimeInsertImage(
|
|
2110
|
+
runtime: WordReviewEditorRuntime,
|
|
2111
|
+
options: InsertImageOptions,
|
|
2112
|
+
): void {
|
|
2113
|
+
const context = getStoryMutationContext(runtime);
|
|
2114
|
+
if (!context) {
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
try {
|
|
2119
|
+
const result = insertImageInDocument(
|
|
2120
|
+
context.localDocument,
|
|
2121
|
+
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
2122
|
+
options.data,
|
|
2123
|
+
options.mimeType,
|
|
2124
|
+
options.width,
|
|
2125
|
+
options.height,
|
|
2126
|
+
{
|
|
2127
|
+
timestamp: context.timestamp,
|
|
2128
|
+
altText: options.altText,
|
|
2129
|
+
},
|
|
2130
|
+
);
|
|
2131
|
+
dispatchStoryMutationResult(runtime, context, {
|
|
2132
|
+
changed: true,
|
|
2133
|
+
document: result.document,
|
|
2134
|
+
selection: result.selection,
|
|
2135
|
+
mapping: result.mapping,
|
|
2136
|
+
}, context.timestamp);
|
|
2137
|
+
} catch {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
function applyRuntimeImageResize(
|
|
2143
|
+
runtime: WordReviewEditorRuntime,
|
|
2144
|
+
mediaId: string,
|
|
2145
|
+
dimensions: { widthEmu: number; heightEmu: number },
|
|
2146
|
+
): void {
|
|
2147
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2148
|
+
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
try {
|
|
2153
|
+
const sessionState = runtime.getSessionState();
|
|
2154
|
+
const result = resizeImageInCatalog(
|
|
2155
|
+
sessionState.canonicalDocument,
|
|
2156
|
+
mediaId,
|
|
2157
|
+
dimensions,
|
|
2158
|
+
);
|
|
2159
|
+
runtime.dispatch({
|
|
2160
|
+
type: "document.replace",
|
|
2161
|
+
document: result.document,
|
|
2162
|
+
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
2163
|
+
origin: { source: "api", timestamp: new Date().toISOString() },
|
|
2164
|
+
});
|
|
2165
|
+
} catch {
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
function applyRuntimeImageReposition(
|
|
2171
|
+
runtime: WordReviewEditorRuntime,
|
|
2172
|
+
mediaId: string,
|
|
2173
|
+
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
2174
|
+
): void {
|
|
2175
|
+
const context = getStoryMutationContext(runtime);
|
|
2176
|
+
if (!context) {
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
try {
|
|
2181
|
+
const result = repositionFloatingImageInDocument(
|
|
2182
|
+
context.localDocument,
|
|
2183
|
+
mediaId,
|
|
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,
|
|
2196
|
+
);
|
|
2197
|
+
} catch {
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
// deriveViewState removed — the runtime's getViewState() is now the single
|
|
2203
|
+
// source of truth for EditorViewStateSnapshot, backed by view-state.ts.
|
|
2204
|
+
|
|
2205
|
+
function applyRuntimeTableStructureOperation(
|
|
2206
|
+
runtime: WordReviewEditorRuntime,
|
|
2207
|
+
mountedSurface: TwProseMirrorSurfaceRef | null | undefined,
|
|
2208
|
+
operation:
|
|
2209
|
+
| { type: "add-row-before" }
|
|
2210
|
+
| { type: "add-row-after" }
|
|
2211
|
+
| { type: "add-column-before" }
|
|
2212
|
+
| { type: "add-column-after" }
|
|
2213
|
+
| { type: "delete-row" }
|
|
2214
|
+
| { type: "delete-column" }
|
|
2215
|
+
| { type: "delete-table" }
|
|
2216
|
+
| { type: "merge-cells" }
|
|
2217
|
+
| { type: "split-cell" }
|
|
2218
|
+
| { type: "set-cell-background"; color: string },
|
|
2219
|
+
): void {
|
|
2220
|
+
const context = getStoryMutationContext(runtime);
|
|
2221
|
+
if (!context) {
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
const result = applyTableStructureOperation(
|
|
2226
|
+
context.localDocument,
|
|
2227
|
+
context.localSnapshot,
|
|
2228
|
+
mountedSurface?.getTableSelection() ?? null,
|
|
2229
|
+
operation,
|
|
2230
|
+
);
|
|
2231
|
+
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function applyRuntimeTextCommand(
|
|
2235
|
+
runtime: WordReviewEditorRuntime,
|
|
2236
|
+
command:
|
|
2237
|
+
| { type: "insert-text"; text: string }
|
|
2238
|
+
| { type: "delete-backward" }
|
|
2239
|
+
| { type: "delete-forward" }
|
|
2240
|
+
| { type: "insert-tab" }
|
|
2241
|
+
| { type: "outdent-tab" }
|
|
2242
|
+
| { type: "insert-hard-break" }
|
|
2243
|
+
| { type: "split-paragraph" },
|
|
2244
|
+
): void {
|
|
2245
|
+
const context = getStoryMutationContext(runtime);
|
|
2246
|
+
if (!context) {
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
const listAwareResult = applyListAwareTextCommand(context, command);
|
|
2251
|
+
if (listAwareResult) {
|
|
2252
|
+
dispatchStoryMutationResult(runtime, context, listAwareResult, context.timestamp);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
if (context.activeStory.kind === "main") {
|
|
2257
|
+
switch (command.type) {
|
|
2258
|
+
case "insert-text":
|
|
2259
|
+
runtime.dispatch({ type: "text.insert", text: command.text });
|
|
2260
|
+
return;
|
|
2261
|
+
case "delete-backward":
|
|
2262
|
+
runtime.dispatch({ type: "text.delete-backward" });
|
|
2263
|
+
return;
|
|
2264
|
+
case "delete-forward":
|
|
2265
|
+
runtime.dispatch({ type: "text.delete-forward" });
|
|
2266
|
+
return;
|
|
2267
|
+
case "insert-tab":
|
|
2268
|
+
runtime.dispatch({ type: "text.insert-tab" });
|
|
2269
|
+
return;
|
|
2270
|
+
case "outdent-tab":
|
|
2271
|
+
return;
|
|
2272
|
+
case "insert-hard-break":
|
|
2273
|
+
runtime.dispatch({ type: "text.insert-hard-break" });
|
|
2274
|
+
return;
|
|
2275
|
+
case "split-paragraph":
|
|
2276
|
+
runtime.dispatch({ type: "paragraph.split" });
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
const selection = toRuntimeSelectionSnapshot(context.localSnapshot.selection);
|
|
2282
|
+
const localResult = (() => {
|
|
2283
|
+
switch (command.type) {
|
|
2284
|
+
case "insert-text":
|
|
2285
|
+
return insertTextInDocument(
|
|
2286
|
+
context.localDocument,
|
|
2287
|
+
selection,
|
|
2288
|
+
command.text,
|
|
2289
|
+
{ timestamp: context.timestamp },
|
|
2290
|
+
);
|
|
2291
|
+
case "delete-backward":
|
|
2292
|
+
return deleteSelectionOrBackward(
|
|
2293
|
+
context.localDocument,
|
|
2294
|
+
selection,
|
|
2295
|
+
{ timestamp: context.timestamp },
|
|
2296
|
+
);
|
|
2297
|
+
case "delete-forward":
|
|
2298
|
+
return deleteSelectionOrForward(
|
|
2299
|
+
context.localDocument,
|
|
2300
|
+
selection,
|
|
2301
|
+
{ timestamp: context.timestamp },
|
|
2302
|
+
);
|
|
2303
|
+
case "insert-tab":
|
|
2304
|
+
return insertTabInDocument(
|
|
2305
|
+
context.localDocument,
|
|
2306
|
+
selection,
|
|
2307
|
+
{ timestamp: context.timestamp },
|
|
2308
|
+
);
|
|
2309
|
+
case "outdent-tab":
|
|
2310
|
+
return {
|
|
2311
|
+
changed: false,
|
|
2312
|
+
document: context.localDocument,
|
|
2313
|
+
selection,
|
|
2314
|
+
};
|
|
2315
|
+
case "insert-hard-break":
|
|
2316
|
+
return insertHardBreakInDocument(
|
|
2317
|
+
context.localDocument,
|
|
2318
|
+
selection,
|
|
2319
|
+
{ timestamp: context.timestamp },
|
|
2320
|
+
);
|
|
2321
|
+
case "split-paragraph":
|
|
2322
|
+
return splitParagraphInDocument(
|
|
2323
|
+
context.localDocument,
|
|
2324
|
+
selection,
|
|
2325
|
+
{ timestamp: context.timestamp },
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
})();
|
|
2329
|
+
|
|
2330
|
+
dispatchStoryMutationResult(
|
|
2331
|
+
runtime,
|
|
2332
|
+
context,
|
|
2333
|
+
{
|
|
2334
|
+
changed: "changed" in localResult ? localResult.changed : true,
|
|
2335
|
+
document: localResult.document,
|
|
2336
|
+
selection: localResult.selection,
|
|
2337
|
+
mapping: "mapping" in localResult ? localResult.mapping : undefined,
|
|
2338
|
+
},
|
|
2339
|
+
context.timestamp,
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
function applyListAwareTextCommand(
|
|
2344
|
+
context: NonNullable<ReturnType<typeof getStoryMutationContext>>,
|
|
2345
|
+
command:
|
|
2346
|
+
| { type: "insert-text"; text: string }
|
|
2347
|
+
| { type: "delete-backward" }
|
|
2348
|
+
| { type: "delete-forward" }
|
|
2349
|
+
| { type: "insert-tab" }
|
|
2350
|
+
| { type: "outdent-tab" }
|
|
2351
|
+
| { type: "insert-hard-break" }
|
|
2352
|
+
| { type: "split-paragraph" },
|
|
2353
|
+
): {
|
|
2354
|
+
changed: boolean;
|
|
2355
|
+
document: EditorSessionState["canonicalDocument"];
|
|
2356
|
+
selection: InternalSelectionSnapshot;
|
|
2357
|
+
} | null {
|
|
2358
|
+
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
2359
|
+
if (!paragraphContext?.paragraph.numbering) {
|
|
2360
|
+
return null;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
switch (command.type) {
|
|
2364
|
+
case "insert-tab": {
|
|
2365
|
+
const result = indentListItems(
|
|
2366
|
+
context.localDocument,
|
|
2367
|
+
[paragraphContext.paragraphIndex],
|
|
2368
|
+
{ timestamp: context.timestamp },
|
|
2369
|
+
);
|
|
2370
|
+
return createListMutationResult(result, context.localSnapshot.selection);
|
|
2371
|
+
}
|
|
2372
|
+
case "outdent-tab": {
|
|
2373
|
+
const result = outdentListItems(
|
|
2374
|
+
context.localDocument,
|
|
2375
|
+
[paragraphContext.paragraphIndex],
|
|
2376
|
+
{ timestamp: context.timestamp },
|
|
2377
|
+
);
|
|
2378
|
+
return createListMutationResult(result, context.localSnapshot.selection);
|
|
2379
|
+
}
|
|
2380
|
+
case "delete-backward": {
|
|
2381
|
+
if (!paragraphContext.atParagraphStart || !context.localSnapshot.selection.isCollapsed) {
|
|
2382
|
+
return null;
|
|
2383
|
+
}
|
|
2384
|
+
const result = backspaceAtListStart(
|
|
2385
|
+
context.localDocument,
|
|
2386
|
+
paragraphContext.paragraphIndex,
|
|
2387
|
+
{ timestamp: context.timestamp },
|
|
2388
|
+
);
|
|
2389
|
+
return result.handled
|
|
2390
|
+
? createListMutationResult(result, context.localSnapshot.selection)
|
|
2391
|
+
: null;
|
|
2392
|
+
}
|
|
2393
|
+
case "split-paragraph": {
|
|
2394
|
+
if (!context.localSnapshot.selection.isCollapsed || !paragraphContext.isEmpty) {
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
const result = splitListParagraph(
|
|
2398
|
+
context.localDocument,
|
|
2399
|
+
paragraphContext.paragraphIndex,
|
|
2400
|
+
true,
|
|
2401
|
+
{ timestamp: context.timestamp },
|
|
2402
|
+
);
|
|
2403
|
+
return result.action === "split"
|
|
2404
|
+
? null
|
|
2405
|
+
: createListMutationResult(result, context.localSnapshot.selection);
|
|
2406
|
+
}
|
|
2407
|
+
default:
|
|
2408
|
+
return null;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function createListMutationResult(
|
|
2413
|
+
result: {
|
|
2414
|
+
document: EditorSessionState["canonicalDocument"];
|
|
2415
|
+
affectedParagraphIndexes: number[];
|
|
2416
|
+
},
|
|
2417
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
2418
|
+
): {
|
|
2419
|
+
changed: boolean;
|
|
2420
|
+
document: EditorSessionState["canonicalDocument"];
|
|
2421
|
+
selection: InternalSelectionSnapshot;
|
|
2422
|
+
} {
|
|
2423
|
+
return {
|
|
2424
|
+
changed: result.affectedParagraphIndexes.length > 0,
|
|
2425
|
+
document: result.document,
|
|
2426
|
+
selection: toRuntimeSelectionSnapshot(selection),
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
function resolveActiveParagraphContext(
|
|
2431
|
+
snapshot: Pick<RuntimeRenderSnapshot, "surface" | "selection">,
|
|
2432
|
+
): {
|
|
2433
|
+
paragraphIndex: number;
|
|
2434
|
+
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>;
|
|
2435
|
+
atParagraphStart: boolean;
|
|
2436
|
+
isEmpty: boolean;
|
|
2437
|
+
} | null {
|
|
2438
|
+
if (!snapshot.surface) {
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
const paragraphIndex = resolveActiveParagraphIndex(
|
|
2443
|
+
snapshot.surface.blocks,
|
|
2444
|
+
snapshot.selection,
|
|
2445
|
+
);
|
|
2446
|
+
if (paragraphIndex === null) {
|
|
2447
|
+
return null;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
const selectionPosition =
|
|
2451
|
+
snapshot.selection.activeRange.kind === "node"
|
|
2452
|
+
? snapshot.selection.activeRange.at
|
|
2453
|
+
: snapshot.selection.head;
|
|
2454
|
+
const paragraph = findSurfaceParagraphAtPosition(snapshot.surface.blocks, selectionPosition);
|
|
2455
|
+
if (!paragraph) {
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
return {
|
|
2460
|
+
paragraphIndex,
|
|
2461
|
+
paragraph,
|
|
2462
|
+
atParagraphStart:
|
|
2463
|
+
snapshot.selection.isCollapsed &&
|
|
2464
|
+
snapshot.selection.activeRange.kind !== "node" &&
|
|
2465
|
+
snapshot.selection.anchor === snapshot.selection.head &&
|
|
2466
|
+
snapshot.selection.head === paragraph.from,
|
|
2467
|
+
isEmpty: isSurfaceParagraphEmpty(paragraph),
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
function findSurfaceParagraphAtPosition(
|
|
2472
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
2473
|
+
position: number,
|
|
2474
|
+
): Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null {
|
|
2475
|
+
for (const block of blocks) {
|
|
2476
|
+
if (position < block.from || position > block.to) {
|
|
2477
|
+
continue;
|
|
2478
|
+
}
|
|
2479
|
+
if (block.kind === "paragraph") {
|
|
2480
|
+
return block;
|
|
2481
|
+
}
|
|
2482
|
+
if (block.kind === "table") {
|
|
2483
|
+
for (const row of block.rows) {
|
|
2484
|
+
for (const cell of row.cells) {
|
|
2485
|
+
const paragraph = findSurfaceParagraphAtPosition(cell.content, position);
|
|
2486
|
+
if (paragraph) {
|
|
2487
|
+
return paragraph;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
if (block.kind === "sdt_block") {
|
|
2494
|
+
const paragraph = findSurfaceParagraphAtPosition(block.children, position);
|
|
2495
|
+
if (paragraph) {
|
|
2496
|
+
return paragraph;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function isSurfaceParagraphEmpty(
|
|
2504
|
+
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
2505
|
+
): boolean {
|
|
2506
|
+
if (paragraph.segments.length === 0) {
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2509
|
+
return paragraph.segments.every((segment) => segment.kind === "text" && segment.text.length === 0);
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
function applyRuntimeSelection(
|
|
2513
|
+
runtime: WordReviewEditorRuntime,
|
|
2514
|
+
selection: PublicSelectionSnapshot,
|
|
2515
|
+
): void {
|
|
2516
|
+
const requestedStory = selection.storyTarget ?? { kind: "main" };
|
|
2517
|
+
if (requestedStory.kind === "main") {
|
|
2518
|
+
runtime.closeStory();
|
|
2519
|
+
} else if (!storyTargetsEqual(runtime.getActiveStory(), requestedStory)) {
|
|
2520
|
+
if (!runtime.openStory(requestedStory)) {
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
runtime.dispatch({
|
|
2526
|
+
type: "selection.set",
|
|
2527
|
+
selection: toRuntimeSelectionSnapshot(stripStoryTarget(selection)),
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function canApplyRuntimeMutation(snapshot: RuntimeRenderSnapshot): boolean {
|
|
2532
|
+
return snapshot.isReady && !snapshot.readOnly && !snapshot.fatalError;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
function getStoryMutationContext(
|
|
2536
|
+
runtime: WordReviewEditorRuntime,
|
|
2537
|
+
): {
|
|
2538
|
+
timestamp: string;
|
|
2539
|
+
activeStory: EditorStoryTarget;
|
|
2540
|
+
persistedDocument: EditorSessionState["canonicalDocument"];
|
|
2541
|
+
localDocument: EditorSessionState["canonicalDocument"];
|
|
2542
|
+
localSnapshot: RuntimeRenderSnapshot;
|
|
2543
|
+
} | null {
|
|
2544
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2545
|
+
if (!canApplyRuntimeMutation(snapshot)) {
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
const persistedDocument = runtime.getSessionState().canonicalDocument;
|
|
2550
|
+
const activeStory = snapshot.activeStory;
|
|
2551
|
+
if (activeStory.kind === "main") {
|
|
2552
|
+
return {
|
|
2553
|
+
timestamp: new Date().toISOString(),
|
|
2554
|
+
activeStory,
|
|
2555
|
+
persistedDocument,
|
|
2556
|
+
localDocument: persistedDocument,
|
|
2557
|
+
localSnapshot: snapshot,
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
return {
|
|
2562
|
+
timestamp: new Date().toISOString(),
|
|
2563
|
+
activeStory,
|
|
2564
|
+
persistedDocument,
|
|
2565
|
+
localDocument: {
|
|
2566
|
+
...persistedDocument,
|
|
2567
|
+
content: {
|
|
2568
|
+
type: "doc",
|
|
2569
|
+
children: [...getStoryBlocks(persistedDocument, activeStory)],
|
|
2570
|
+
},
|
|
2571
|
+
},
|
|
2572
|
+
localSnapshot: {
|
|
2573
|
+
...snapshot,
|
|
2574
|
+
activeStory: { kind: "main" },
|
|
2575
|
+
selection: stripStoryTarget(snapshot.selection),
|
|
2576
|
+
},
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
function dispatchStoryMutationResult(
|
|
2581
|
+
runtime: WordReviewEditorRuntime,
|
|
2582
|
+
context: {
|
|
2583
|
+
activeStory: EditorStoryTarget;
|
|
2584
|
+
persistedDocument: EditorSessionState["canonicalDocument"];
|
|
2585
|
+
},
|
|
2586
|
+
result: {
|
|
2587
|
+
changed: boolean;
|
|
2588
|
+
document: EditorSessionState["canonicalDocument"];
|
|
2589
|
+
selection: InternalSelectionSnapshot;
|
|
2590
|
+
mapping?: TransactionMapping;
|
|
2591
|
+
},
|
|
2592
|
+
timestamp: string,
|
|
2593
|
+
): void {
|
|
2594
|
+
if (context.activeStory.kind === "main") {
|
|
2595
|
+
dispatchRuntimeDocumentMutation(runtime, result, timestamp);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
if (!result.changed) {
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
const nextDocument = replaceStoryBlocks(
|
|
2604
|
+
context.persistedDocument,
|
|
2605
|
+
context.activeStory,
|
|
2606
|
+
result.document.content.children,
|
|
2607
|
+
);
|
|
2608
|
+
dispatchRuntimeDocumentMutation(
|
|
2609
|
+
runtime,
|
|
2610
|
+
{
|
|
2611
|
+
changed: true,
|
|
2612
|
+
document: nextDocument,
|
|
2613
|
+
selection: result.selection,
|
|
2614
|
+
},
|
|
2615
|
+
timestamp,
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
function dispatchRuntimeDocumentMutation(
|
|
2620
|
+
runtime: WordReviewEditorRuntime,
|
|
2621
|
+
result: {
|
|
2622
|
+
changed: boolean;
|
|
2623
|
+
document: EditorSessionState["canonicalDocument"];
|
|
2624
|
+
selection: InternalSelectionSnapshot;
|
|
2625
|
+
mapping?: TransactionMapping;
|
|
2626
|
+
},
|
|
2627
|
+
timestamp: string,
|
|
2628
|
+
): void {
|
|
2629
|
+
if (!result.changed) {
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
runtime.dispatch({
|
|
2634
|
+
type: "document.replace",
|
|
2635
|
+
document: {
|
|
2636
|
+
...result.document,
|
|
2637
|
+
updatedAt: timestamp,
|
|
2638
|
+
},
|
|
2639
|
+
mapping: result.mapping,
|
|
2640
|
+
selection: result.selection,
|
|
2641
|
+
origin: {
|
|
2642
|
+
source: "api",
|
|
2643
|
+
timestamp,
|
|
2644
|
+
},
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
function stripStoryTarget(
|
|
2649
|
+
selection: PublicSelectionSnapshot,
|
|
2650
|
+
): PublicSelectionSnapshot {
|
|
2651
|
+
const { storyTarget: _storyTarget, ...rest } = selection;
|
|
2652
|
+
return rest;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function applyRuntimeDeleteComment(
|
|
2656
|
+
runtime: WordReviewEditorRuntime,
|
|
2657
|
+
commentId: string,
|
|
2658
|
+
): void {
|
|
2659
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2660
|
+
if (!snapshot.isReady || snapshot.readOnly || snapshot.fatalError) {
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
const sessionState = runtime.getSessionState();
|
|
2665
|
+
if (!sessionState.canonicalDocument.review.comments[commentId]) {
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
const nextComments = {
|
|
2670
|
+
...sessionState.canonicalDocument.review.comments,
|
|
2671
|
+
};
|
|
2672
|
+
delete nextComments[commentId];
|
|
2673
|
+
|
|
2674
|
+
runtime.dispatch({
|
|
2675
|
+
type: "document.replace",
|
|
2676
|
+
document: {
|
|
2677
|
+
...sessionState.canonicalDocument,
|
|
2678
|
+
review: {
|
|
2679
|
+
...sessionState.canonicalDocument.review,
|
|
2680
|
+
comments: nextComments,
|
|
2681
|
+
},
|
|
2682
|
+
},
|
|
2683
|
+
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
2684
|
+
origin: {
|
|
2685
|
+
source: "api",
|
|
2686
|
+
timestamp: new Date().toISOString(),
|
|
2687
|
+
},
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
function normalizeRequestedSelection(
|
|
2692
|
+
snapshot: RuntimeRenderSnapshot,
|
|
2693
|
+
selection: PublicSelectionSnapshot | null,
|
|
1435
2694
|
): PublicSelectionSnapshot {
|
|
1436
|
-
return
|
|
2695
|
+
return (
|
|
2696
|
+
selection ??
|
|
2697
|
+
createCollapsedPublicSelection(
|
|
2698
|
+
snapshot.selection.head,
|
|
2699
|
+
snapshot.activeStory.kind === "main" ? undefined : snapshot.activeStory,
|
|
2700
|
+
)
|
|
2701
|
+
);
|
|
1437
2702
|
}
|
|
1438
2703
|
|
|
1439
|
-
function
|
|
2704
|
+
export function __resolveLiveMarkupDisplay(
|
|
2705
|
+
requested: MarkupDisplay | undefined,
|
|
2706
|
+
isPageWorkspace: boolean,
|
|
2707
|
+
): MarkupDisplay {
|
|
2708
|
+
return requested ?? (isPageWorkspace ? "all" : "clean");
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
function createCollapsedPublicSelection(
|
|
2712
|
+
position: number,
|
|
2713
|
+
storyTarget?: EditorStoryTarget,
|
|
2714
|
+
): PublicSelectionSnapshot {
|
|
1440
2715
|
return {
|
|
1441
2716
|
anchor: position,
|
|
1442
2717
|
head: position,
|
|
@@ -1450,6 +2725,7 @@ function createCollapsedPublicSelection(position: number): PublicSelectionSnapsh
|
|
|
1450
2725
|
end: 1,
|
|
1451
2726
|
},
|
|
1452
2727
|
},
|
|
2728
|
+
...(storyTarget ? { storyTarget } : {}),
|
|
1453
2729
|
};
|
|
1454
2730
|
}
|
|
1455
2731
|
|
|
@@ -1457,61 +2733,66 @@ function clonePublicValue<T>(value: T): T {
|
|
|
1457
2733
|
return structuredClone(value);
|
|
1458
2734
|
}
|
|
1459
2735
|
|
|
1460
|
-
function
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
2736
|
+
function openDefaultStoryVariant(
|
|
2737
|
+
runtime: WordReviewEditorRuntime,
|
|
2738
|
+
pageLayout: PageLayoutSnapshot | undefined,
|
|
2739
|
+
navigation: ReturnType<WordReviewEditorRuntime["getDocumentNavigationSnapshot"]> | undefined,
|
|
2740
|
+
kind: "header" | "footer",
|
|
2741
|
+
): void {
|
|
2742
|
+
const variants =
|
|
2743
|
+
kind === "header"
|
|
2744
|
+
? pageLayout?.headerVariants
|
|
2745
|
+
: pageLayout?.footerVariants;
|
|
2746
|
+
const activePage = navigation?.pages[navigation.activePageIndex];
|
|
2747
|
+
const isFirstPageInSection =
|
|
2748
|
+
activePage !== undefined &&
|
|
2749
|
+
activePage.sectionIndex === pageLayout?.sectionIndex &&
|
|
2750
|
+
activePage.pageInSection === 0;
|
|
2751
|
+
const isEvenDocumentPage = activePage !== undefined && (activePage.pageIndex + 1) % 2 === 0;
|
|
2752
|
+
|
|
2753
|
+
let variant =
|
|
2754
|
+
pageLayout?.differentFirstPage && isFirstPageInSection
|
|
2755
|
+
? variants?.find((entry) => entry.variant === "first")
|
|
2756
|
+
: undefined;
|
|
2757
|
+
|
|
2758
|
+
if (!variant && pageLayout?.differentOddEvenPages && isEvenDocumentPage) {
|
|
2759
|
+
variant = variants?.find((entry) => entry.variant === "even");
|
|
1468
2760
|
}
|
|
1469
2761
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
options,
|
|
1474
|
-
).slice(0, options.limit ?? Number.POSITIVE_INFINITY);
|
|
1475
|
-
const activeResultIndex = getActiveSearchResultIndex(rawResults, snapshot.selection);
|
|
2762
|
+
if (!variant) {
|
|
2763
|
+
variant = variants?.find((entry) => entry.variant === "default") ?? variants?.[0];
|
|
2764
|
+
}
|
|
1476
2765
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
},
|
|
1487
|
-
},
|
|
1488
|
-
excerpt: createSearchExcerpt(
|
|
1489
|
-
snapshot.surface?.plainText ?? "",
|
|
1490
|
-
result.from,
|
|
1491
|
-
result.to,
|
|
1492
|
-
),
|
|
1493
|
-
isActive: index === activeResultIndex,
|
|
1494
|
-
}));
|
|
2766
|
+
if (!variant) {
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
runtime.openStory({
|
|
2770
|
+
kind,
|
|
2771
|
+
relationshipId: variant.relationshipId,
|
|
2772
|
+
variant: variant.variant,
|
|
2773
|
+
sectionIndex: pageLayout?.sectionIndex,
|
|
2774
|
+
});
|
|
1495
2775
|
}
|
|
1496
2776
|
|
|
1497
|
-
function
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
2777
|
+
function searchRuntimeDocument(
|
|
2778
|
+
runtime: WordReviewEditorRuntime,
|
|
2779
|
+
mountedSurface: TwProseMirrorSurfaceRef | null,
|
|
2780
|
+
query: string,
|
|
2781
|
+
options: SearchOptions = {},
|
|
2782
|
+
): SearchResultSnapshot[] {
|
|
2783
|
+
if (mountedSurface) {
|
|
2784
|
+
return mountedSurface.search(query, options);
|
|
1503
2785
|
}
|
|
1504
2786
|
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
return activeIndex >= 0 ? activeIndex : 0;
|
|
2787
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
2788
|
+
return searchDocument(
|
|
2789
|
+
runtime.getSessionState().canonicalDocument,
|
|
2790
|
+
snapshot.selection,
|
|
2791
|
+
snapshot.activeStory,
|
|
2792
|
+
runtime.getDocumentNavigationSnapshot(),
|
|
2793
|
+
query,
|
|
2794
|
+
options,
|
|
2795
|
+
);
|
|
1515
2796
|
}
|
|
1516
2797
|
|
|
1517
2798
|
function applyRegionAttributes(shell: HTMLElement): void {
|
|
@@ -1582,663 +2863,59 @@ function findRegionFocusTarget(
|
|
|
1582
2863
|
}
|
|
1583
2864
|
|
|
1584
2865
|
if (regionId === "document" || regionId === "toolbar" || regionId === "review-rail" || regionId === "status") {
|
|
1585
|
-
return region;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
return null;
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
function isAccessibleRegionId(value: string | undefined): value is AccessibleRegionId {
|
|
1592
|
-
return value === "toolbar" || value === "document" || value === "review-rail" || value === "status";
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
function buildAccessibilityStatusMessage(
|
|
1596
|
-
snapshot: RuntimeRenderSnapshot,
|
|
1597
|
-
loadError?: EditorError,
|
|
1598
|
-
): string {
|
|
1599
|
-
if (loadError) {
|
|
1600
|
-
return `Editor failed to load. ${loadError.message}`;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
if (!snapshot.isReady) {
|
|
1604
|
-
return "Editor loading. Document surface is not ready yet.";
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
if (snapshot.fatalError) {
|
|
1608
|
-
return `Editor opened in diagnostics mode. ${snapshot.fatalError.message}`;
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
return [
|
|
1612
|
-
snapshot.readOnly ? "Read-only mode." : "Editing enabled.",
|
|
1613
|
-
`${snapshot.comments.totalCount} comment${snapshot.comments.totalCount === 1 ? "" : "s"}.`,
|
|
1614
|
-
`${snapshot.trackedChanges.totalCount} tracked change${
|
|
1615
|
-
snapshot.trackedChanges.totalCount === 1 ? "" : "s"
|
|
1616
|
-
}.`,
|
|
1617
|
-
snapshot.compatibility.blockExport ? "Export blocked." : "Export available.",
|
|
1618
|
-
].join(" ");
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
function getDiagnosticsModeMessage(error?: EditorError | null): string | null {
|
|
1622
|
-
if (!error) {
|
|
1623
|
-
return null;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
if (error.code === "package_corrupt") {
|
|
1627
|
-
return `${error.message} The document opened in read-only diagnostics mode so you can inspect the issue safely, but editing and export stay blocked until you reload a valid .docx package.`;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
if (error.code === "validation_failed") {
|
|
1631
|
-
return `${error.message} The document opened in read-only diagnostics mode because OOXML validation failed, so editing and export stay blocked until the source package is repaired.`;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
return error.isFatal ? error.message : null;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
function normalizeEditorError(error: unknown): EditorError {
|
|
1638
|
-
if (
|
|
1639
|
-
typeof error === "object" &&
|
|
1640
|
-
error !== null &&
|
|
1641
|
-
"errorId" in error &&
|
|
1642
|
-
"code" in error &&
|
|
1643
|
-
"message" in error
|
|
1644
|
-
) {
|
|
1645
|
-
return error as EditorError;
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
return {
|
|
1649
|
-
errorId: "word-review-editor-load",
|
|
1650
|
-
code: "internal_invariant",
|
|
1651
|
-
message: error instanceof Error ? error.message : "Unknown editor load failure.",
|
|
1652
|
-
isFatal: true,
|
|
1653
|
-
source: "runtime",
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
function guessSourceLabel(
|
|
1658
|
-
initialSourceLabel?: string,
|
|
1659
|
-
initialSnapshot?: PersistedEditorSnapshot,
|
|
1660
|
-
externalDocSource?: WordReviewEditorProps["externalDocSource"],
|
|
1661
|
-
): string | undefined {
|
|
1662
|
-
return (
|
|
1663
|
-
externalDocSource?.sourceLabel ??
|
|
1664
|
-
initialSourceLabel ??
|
|
1665
|
-
initialSnapshot?.sourcePackage?.sourceLabel ??
|
|
1666
|
-
initialSnapshot?.editorBuild ??
|
|
1667
|
-
undefined
|
|
1668
|
-
);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
function createLoadingSnapshot(
|
|
1672
|
-
documentId: string,
|
|
1673
|
-
readOnly: boolean,
|
|
1674
|
-
sourceLabel?: string,
|
|
1675
|
-
): RuntimeRenderSnapshot {
|
|
1676
|
-
return {
|
|
1677
|
-
documentId,
|
|
1678
|
-
sessionId: `${documentId}-loading`,
|
|
1679
|
-
sourceLabel,
|
|
1680
|
-
revisionToken: `${documentId}:loading`,
|
|
1681
|
-
isReady: false,
|
|
1682
|
-
isDirty: false,
|
|
1683
|
-
readOnly,
|
|
1684
|
-
selection: collapsedSelection(),
|
|
1685
|
-
documentStats: {
|
|
1686
|
-
storyLength: 0,
|
|
1687
|
-
commentCount: 0,
|
|
1688
|
-
revisionCount: 0,
|
|
1689
|
-
opaqueFragmentCount: 0,
|
|
1690
|
-
},
|
|
1691
|
-
comments: {
|
|
1692
|
-
openCommentIds: [],
|
|
1693
|
-
resolvedCommentIds: [],
|
|
1694
|
-
detachedCommentIds: [],
|
|
1695
|
-
totalCount: 0,
|
|
1696
|
-
threads: [],
|
|
1697
|
-
},
|
|
1698
|
-
trackedChanges: {
|
|
1699
|
-
pendingChangeIds: [],
|
|
1700
|
-
acceptedChangeIds: [],
|
|
1701
|
-
rejectedChangeIds: [],
|
|
1702
|
-
detachedChangeIds: [],
|
|
1703
|
-
actionableChangeIds: [],
|
|
1704
|
-
preserveOnlyChangeIds: [],
|
|
1705
|
-
totalCount: 0,
|
|
1706
|
-
revisions: [],
|
|
1707
|
-
},
|
|
1708
|
-
compatibility: {
|
|
1709
|
-
blockExport: false,
|
|
1710
|
-
blockExportReasons: [],
|
|
1711
|
-
warningCount: 0,
|
|
1712
|
-
errorCount: 0,
|
|
1713
|
-
featureEntries: [],
|
|
1714
|
-
},
|
|
1715
|
-
warnings: [],
|
|
1716
|
-
commandState: {
|
|
1717
|
-
canUndo: false,
|
|
1718
|
-
canRedo: false,
|
|
1719
|
-
readOnly,
|
|
1720
|
-
},
|
|
1721
|
-
};
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
function createErrorSnapshot(documentId: string, error: EditorError): RuntimeRenderSnapshot {
|
|
1725
|
-
return {
|
|
1726
|
-
...createLoadingSnapshot(documentId, true),
|
|
1727
|
-
isReady: true,
|
|
1728
|
-
sessionId: `${documentId}-error`,
|
|
1729
|
-
revisionToken: `${documentId}:error`,
|
|
1730
|
-
compatibility: {
|
|
1731
|
-
blockExport: true,
|
|
1732
|
-
blockExportReasons: [error.message],
|
|
1733
|
-
warningCount: 0,
|
|
1734
|
-
errorCount: 1,
|
|
1735
|
-
featureEntries: [],
|
|
1736
|
-
},
|
|
1737
|
-
fatalError: error,
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
async function persistAndExport(input: {
|
|
1742
|
-
datastore?: EditorDatastoreAdapter;
|
|
1743
|
-
documentId: string;
|
|
1744
|
-
runtime: WordReviewEditorRuntime;
|
|
1745
|
-
onError?: (error: EditorError) => void;
|
|
1746
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
1747
|
-
options?: ExportDocxOptions;
|
|
1748
|
-
lastSavedRevisionTokenRef: React.MutableRefObject<string | null>;
|
|
1749
|
-
autosaveTimerRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>;
|
|
1750
|
-
}): Promise<ExportResult> {
|
|
1751
|
-
if (input.autosaveTimerRef.current) {
|
|
1752
|
-
clearTimeout(input.autosaveTimerRef.current);
|
|
1753
|
-
input.autosaveTimerRef.current = null;
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
await persistSnapshot({
|
|
1757
|
-
datastore: input.datastore,
|
|
1758
|
-
documentId: input.documentId,
|
|
1759
|
-
runtime: input.runtime,
|
|
1760
|
-
isAutosave: false,
|
|
1761
|
-
onError: input.onError,
|
|
1762
|
-
onEvent: input.onEvent,
|
|
1763
|
-
lastSavedRevisionTokenRef: input.lastSavedRevisionTokenRef,
|
|
1764
|
-
});
|
|
1765
|
-
|
|
1766
|
-
let result: ExportResult;
|
|
1767
|
-
try {
|
|
1768
|
-
result = await input.runtime.exportDocx(input.options);
|
|
1769
|
-
} catch (error) {
|
|
1770
|
-
const normalized = normalizeExportError(error, input.documentId, input.options);
|
|
1771
|
-
input.onError?.(normalized);
|
|
1772
|
-
emitEditorEvent({
|
|
1773
|
-
datastore: input.datastore,
|
|
1774
|
-
onEvent: input.onEvent,
|
|
1775
|
-
event: {
|
|
1776
|
-
type: "error",
|
|
1777
|
-
documentId: input.documentId,
|
|
1778
|
-
error: normalized,
|
|
1779
|
-
},
|
|
1780
|
-
});
|
|
1781
|
-
throw normalized;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
if (!input.datastore) {
|
|
1785
|
-
return result;
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
try {
|
|
1789
|
-
await input.datastore.saveExport({
|
|
1790
|
-
documentId: input.documentId,
|
|
1791
|
-
result,
|
|
1792
|
-
});
|
|
1793
|
-
} catch (error) {
|
|
1794
|
-
const normalized = normalizeDatastoreError(error, {
|
|
1795
|
-
message: "Export persisted bytes could not be stored.",
|
|
1796
|
-
details: {
|
|
1797
|
-
operation: "saveExport",
|
|
1798
|
-
},
|
|
1799
|
-
});
|
|
1800
|
-
input.onError?.(normalized);
|
|
1801
|
-
emitEditorEvent({
|
|
1802
|
-
datastore: input.datastore,
|
|
1803
|
-
onEvent: input.onEvent,
|
|
1804
|
-
event: {
|
|
1805
|
-
type: "error",
|
|
1806
|
-
documentId: input.documentId,
|
|
1807
|
-
error: normalized,
|
|
1808
|
-
},
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
return result;
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
function rejectExportWhileLoading(input: {
|
|
1816
|
-
documentId: string;
|
|
1817
|
-
datastore?: EditorDatastoreAdapter;
|
|
1818
|
-
onError?: (error: EditorError) => void;
|
|
1819
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
1820
|
-
}): Promise<never> {
|
|
1821
|
-
const error: EditorError = {
|
|
1822
|
-
errorId: "word-review-editor-loading-export",
|
|
1823
|
-
code: "internal_invariant",
|
|
1824
|
-
message: "WordReviewEditor is still loading and cannot export yet.",
|
|
1825
|
-
isFatal: false,
|
|
1826
|
-
source: "runtime",
|
|
1827
|
-
};
|
|
1828
|
-
input.onError?.(error);
|
|
1829
|
-
emitEditorEvent({
|
|
1830
|
-
datastore: input.datastore,
|
|
1831
|
-
onEvent: input.onEvent,
|
|
1832
|
-
event: {
|
|
1833
|
-
type: "error",
|
|
1834
|
-
documentId: input.documentId,
|
|
1835
|
-
error,
|
|
1836
|
-
},
|
|
1837
|
-
});
|
|
1838
|
-
return Promise.reject(error);
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
async function persistSnapshot(input: {
|
|
1842
|
-
datastore?: EditorDatastoreAdapter;
|
|
1843
|
-
documentId: string;
|
|
1844
|
-
runtime: WordReviewEditorRuntime;
|
|
1845
|
-
isAutosave: boolean;
|
|
1846
|
-
onError?: (error: EditorError) => void;
|
|
1847
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
1848
|
-
lastSavedRevisionTokenRef: React.MutableRefObject<string | null>;
|
|
1849
|
-
}): Promise<void> {
|
|
1850
|
-
if (!input.datastore) {
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
const snapshot = input.runtime.getPersistedSnapshot();
|
|
1855
|
-
const revisionToken = input.runtime.getRenderSnapshot().revisionToken;
|
|
1856
|
-
|
|
1857
|
-
if (input.isAutosave) {
|
|
1858
|
-
emitEditorEvent({
|
|
1859
|
-
datastore: input.datastore,
|
|
1860
|
-
onEvent: input.onEvent,
|
|
1861
|
-
event: {
|
|
1862
|
-
type: "autosave_state",
|
|
1863
|
-
documentId: input.documentId,
|
|
1864
|
-
state: {
|
|
1865
|
-
status: "saving",
|
|
1866
|
-
} satisfies AutosaveState,
|
|
1867
|
-
},
|
|
1868
|
-
});
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
try {
|
|
1872
|
-
const result = await input.datastore.saveSnapshot({
|
|
1873
|
-
documentId: input.documentId,
|
|
1874
|
-
snapshot,
|
|
1875
|
-
isAutosave: input.isAutosave,
|
|
1876
|
-
});
|
|
1877
|
-
const savedSnapshot: PersistedEditorSnapshot = {
|
|
1878
|
-
...snapshot,
|
|
1879
|
-
savedAt: result.savedAt,
|
|
1880
|
-
};
|
|
1881
|
-
input.lastSavedRevisionTokenRef.current = revisionToken;
|
|
1882
|
-
emitEditorEvent({
|
|
1883
|
-
datastore: input.datastore,
|
|
1884
|
-
onEvent: input.onEvent,
|
|
1885
|
-
event: {
|
|
1886
|
-
type: "snapshot_saved",
|
|
1887
|
-
documentId: input.documentId,
|
|
1888
|
-
snapshot: savedSnapshot,
|
|
1889
|
-
isAutosave: input.isAutosave,
|
|
1890
|
-
},
|
|
1891
|
-
});
|
|
1892
|
-
if (input.isAutosave) {
|
|
1893
|
-
emitEditorEvent({
|
|
1894
|
-
datastore: input.datastore,
|
|
1895
|
-
onEvent: input.onEvent,
|
|
1896
|
-
event: {
|
|
1897
|
-
type: "autosave_state",
|
|
1898
|
-
documentId: input.documentId,
|
|
1899
|
-
state: {
|
|
1900
|
-
status: "saved",
|
|
1901
|
-
savedAt: result.savedAt,
|
|
1902
|
-
} satisfies AutosaveState,
|
|
1903
|
-
},
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
} catch (error) {
|
|
1907
|
-
const normalized = normalizeDatastoreError(error, {
|
|
1908
|
-
message: input.isAutosave
|
|
1909
|
-
? "Autosave failed while storing the editor snapshot."
|
|
1910
|
-
: "Snapshot save failed while preparing the export checkpoint.",
|
|
1911
|
-
details: {
|
|
1912
|
-
operation: "saveSnapshot",
|
|
1913
|
-
isAutosave: input.isAutosave,
|
|
1914
|
-
},
|
|
1915
|
-
});
|
|
1916
|
-
input.onError?.(normalized);
|
|
1917
|
-
emitEditorEvent({
|
|
1918
|
-
datastore: input.datastore,
|
|
1919
|
-
onEvent: input.onEvent,
|
|
1920
|
-
event: {
|
|
1921
|
-
type: "error",
|
|
1922
|
-
documentId: input.documentId,
|
|
1923
|
-
error: normalized,
|
|
1924
|
-
},
|
|
1925
|
-
});
|
|
1926
|
-
if (input.isAutosave) {
|
|
1927
|
-
emitEditorEvent({
|
|
1928
|
-
datastore: input.datastore,
|
|
1929
|
-
onEvent: input.onEvent,
|
|
1930
|
-
event: {
|
|
1931
|
-
type: "autosave_state",
|
|
1932
|
-
documentId: input.documentId,
|
|
1933
|
-
state: {
|
|
1934
|
-
status: "error",
|
|
1935
|
-
error: normalized,
|
|
1936
|
-
} satisfies AutosaveState,
|
|
1937
|
-
},
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1940
|
-
if (!input.isAutosave) {
|
|
1941
|
-
throw normalized;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
function emitEditorEvent(input: {
|
|
1947
|
-
datastore?: EditorDatastoreAdapter;
|
|
1948
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
1949
|
-
event: WordReviewEditorEvent;
|
|
1950
|
-
}): void {
|
|
1951
|
-
input.onEvent?.(input.event);
|
|
1952
|
-
input.datastore?.logEvent?.({
|
|
1953
|
-
type: input.event.type,
|
|
1954
|
-
documentId: input.event.documentId,
|
|
1955
|
-
detail: summarizeEventDetail(input.event),
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
function summarizeEventDetail(
|
|
1960
|
-
event: WordReviewEditorEvent,
|
|
1961
|
-
): Record<string, unknown> | undefined {
|
|
1962
|
-
switch (event.type) {
|
|
1963
|
-
case "dirty_changed":
|
|
1964
|
-
return { isDirty: event.isDirty };
|
|
1965
|
-
case "comment_added":
|
|
1966
|
-
return { commentId: event.commentId };
|
|
1967
|
-
case "comment_resolved":
|
|
1968
|
-
return { commentId: event.commentId };
|
|
1969
|
-
case "change_accepted":
|
|
1970
|
-
case "change_rejected":
|
|
1971
|
-
return { changeId: event.changeId };
|
|
1972
|
-
case "warning_added":
|
|
1973
|
-
return { warningId: event.warning.warningId, code: event.warning.code };
|
|
1974
|
-
case "warning_cleared":
|
|
1975
|
-
return { warningId: event.warningId, code: event.code };
|
|
1976
|
-
case "error":
|
|
1977
|
-
return { errorId: event.error.errorId, code: event.error.code };
|
|
1978
|
-
case "autosave_state":
|
|
1979
|
-
return { status: event.state.status };
|
|
1980
|
-
case "snapshot_saved":
|
|
1981
|
-
return { isAutosave: event.isAutosave, savedAt: event.snapshot.savedAt };
|
|
1982
|
-
case "export_completed":
|
|
1983
|
-
return { fileName: event.result.fileName };
|
|
1984
|
-
case "selection_changed":
|
|
1985
|
-
return {
|
|
1986
|
-
anchor: event.selection.anchor,
|
|
1987
|
-
head: event.selection.head,
|
|
1988
|
-
};
|
|
1989
|
-
case "ready":
|
|
1990
|
-
return {
|
|
1991
|
-
source: event.source,
|
|
1992
|
-
blockExport: event.compatibility.blockExport,
|
|
1993
|
-
};
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
function createReadyEvent(
|
|
1998
|
-
runtime: Pick<WordReviewEditorRuntime, "getCompatibilityReport" | "getRenderSnapshot">,
|
|
1999
|
-
source: "docx" | "snapshot" | "datastore" | "canonical",
|
|
2000
|
-
): Extract<WordReviewEditorEvent, { type: "ready" }> {
|
|
2001
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
2002
|
-
return {
|
|
2003
|
-
type: "ready",
|
|
2004
|
-
documentId: snapshot.documentId,
|
|
2005
|
-
sessionId: snapshot.sessionId,
|
|
2006
|
-
source,
|
|
2007
|
-
stats: snapshot.documentStats,
|
|
2008
|
-
compatibility: runtime.getCompatibilityReport(),
|
|
2009
|
-
comments: snapshot.comments,
|
|
2010
|
-
trackedChanges: snapshot.trackedChanges,
|
|
2011
|
-
};
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
function normalizeDatastoreError(
|
|
2015
|
-
error: unknown,
|
|
2016
|
-
fallback: {
|
|
2017
|
-
message: string;
|
|
2018
|
-
details?: Record<string, unknown>;
|
|
2019
|
-
},
|
|
2020
|
-
): EditorError {
|
|
2021
|
-
if (
|
|
2022
|
-
typeof error === "object" &&
|
|
2023
|
-
error !== null &&
|
|
2024
|
-
"errorId" in error &&
|
|
2025
|
-
"code" in error &&
|
|
2026
|
-
"message" in error
|
|
2027
|
-
) {
|
|
2028
|
-
return error as EditorError;
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
return {
|
|
2032
|
-
errorId: "word-review-editor-datastore",
|
|
2033
|
-
code: "datastore_failed",
|
|
2034
|
-
message: error instanceof Error ? error.message : fallback.message,
|
|
2035
|
-
isFatal: false,
|
|
2036
|
-
source: "datastore",
|
|
2037
|
-
details: fallback.details,
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
function createFallbackSnapshot(args: CreateRuntimeArgs): RuntimeRenderSnapshot {
|
|
2042
|
-
const warnings = args.source.initialSnapshot?.warningLog ?? [];
|
|
2043
|
-
const compatibility = args.source.initialSnapshot?.compatibility ?? emptyCompatibilityReport();
|
|
2866
|
+
return region;
|
|
2867
|
+
}
|
|
2044
2868
|
|
|
2045
|
-
return
|
|
2046
|
-
...createLoadingSnapshot(args.documentId, args.readOnly, args.source.sourceLabel),
|
|
2047
|
-
sessionId: `${args.documentId}-session`,
|
|
2048
|
-
revisionToken: `${args.documentId}:0`,
|
|
2049
|
-
isReady: true,
|
|
2050
|
-
documentStats: {
|
|
2051
|
-
storyLength: estimateStoryLength(args.source.initialSnapshot),
|
|
2052
|
-
commentCount: 0,
|
|
2053
|
-
revisionCount: 0,
|
|
2054
|
-
opaqueFragmentCount: 0,
|
|
2055
|
-
},
|
|
2056
|
-
compatibility: {
|
|
2057
|
-
blockExport: compatibility.blockExport,
|
|
2058
|
-
blockExportReasons: [],
|
|
2059
|
-
warningCount: compatibility.warnings.length,
|
|
2060
|
-
errorCount: compatibility.errors.length,
|
|
2061
|
-
featureEntries: compatibility.featureEntries,
|
|
2062
|
-
},
|
|
2063
|
-
warnings,
|
|
2064
|
-
};
|
|
2869
|
+
return null;
|
|
2065
2870
|
}
|
|
2066
2871
|
|
|
2067
|
-
function
|
|
2068
|
-
|
|
2069
|
-
label = "Generated shell snapshot",
|
|
2070
|
-
): PersistedEditorSnapshot {
|
|
2071
|
-
const docId = createCanonicalDocumentId(documentId);
|
|
2072
|
-
return {
|
|
2073
|
-
snapshotVersion: "persisted-editor-snapshot/2",
|
|
2074
|
-
schemaVersion: "cds/1.0.0",
|
|
2075
|
-
documentId,
|
|
2076
|
-
docId,
|
|
2077
|
-
createdAt: "1970-01-01T00:00:00.000Z",
|
|
2078
|
-
updatedAt: "1970-01-01T00:00:00.000Z",
|
|
2079
|
-
savedAt: "1970-01-01T00:00:00.000Z",
|
|
2080
|
-
editorBuild: label,
|
|
2081
|
-
canonicalDocument: {
|
|
2082
|
-
schemaVersion: "cds/1.0.0",
|
|
2083
|
-
docId,
|
|
2084
|
-
createdAt: "1970-01-01T00:00:00.000Z",
|
|
2085
|
-
updatedAt: "1970-01-01T00:00:00.000Z",
|
|
2086
|
-
metadata: {
|
|
2087
|
-
customProperties: {},
|
|
2088
|
-
},
|
|
2089
|
-
styles: {
|
|
2090
|
-
paragraphs: {},
|
|
2091
|
-
characters: {},
|
|
2092
|
-
tables: {},
|
|
2093
|
-
},
|
|
2094
|
-
numbering: {
|
|
2095
|
-
abstractDefinitions: {},
|
|
2096
|
-
instances: {},
|
|
2097
|
-
},
|
|
2098
|
-
media: {
|
|
2099
|
-
items: {},
|
|
2100
|
-
},
|
|
2101
|
-
content: {
|
|
2102
|
-
type: "doc",
|
|
2103
|
-
children: [{ type: "paragraph", children: [] }],
|
|
2104
|
-
},
|
|
2105
|
-
review: {
|
|
2106
|
-
comments: {},
|
|
2107
|
-
revisions: {},
|
|
2108
|
-
},
|
|
2109
|
-
preservation: {
|
|
2110
|
-
opaqueFragments: {},
|
|
2111
|
-
packageParts: {},
|
|
2112
|
-
},
|
|
2113
|
-
diagnostics: {
|
|
2114
|
-
warnings: [],
|
|
2115
|
-
errors: [],
|
|
2116
|
-
},
|
|
2117
|
-
},
|
|
2118
|
-
compatibility: emptyCompatibilityReport(),
|
|
2119
|
-
warningLog: [],
|
|
2120
|
-
};
|
|
2872
|
+
function isAccessibleRegionId(value: string | undefined): value is AccessibleRegionId {
|
|
2873
|
+
return value === "toolbar" || value === "document" || value === "review-rail" || value === "status";
|
|
2121
2874
|
}
|
|
2122
2875
|
|
|
2123
|
-
function
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
errors: [],
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2876
|
+
function buildAccessibilityStatusMessage(
|
|
2877
|
+
snapshot: RuntimeRenderSnapshot,
|
|
2878
|
+
loadError?: EditorError,
|
|
2879
|
+
): string {
|
|
2880
|
+
if (loadError) {
|
|
2881
|
+
return `Editor failed to load. ${loadError.message}`;
|
|
2882
|
+
}
|
|
2133
2883
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
barrier?: SnapshotExportBarrier;
|
|
2137
|
-
} {
|
|
2138
|
-
const sourcePackage = args.source.initialSnapshot?.sourcePackage;
|
|
2139
|
-
if (!sourcePackage) {
|
|
2140
|
-
return {
|
|
2141
|
-
barrier: {
|
|
2142
|
-
reason: "missing_source_package_provenance",
|
|
2143
|
-
message:
|
|
2144
|
-
"DOCX export is blocked because this snapshot was loaded without embedded source package provenance.",
|
|
2145
|
-
},
|
|
2146
|
-
};
|
|
2884
|
+
if (!snapshot.isReady) {
|
|
2885
|
+
return "Editor loading. Document surface is not ready yet.";
|
|
2147
2886
|
}
|
|
2148
2887
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
return {
|
|
2153
|
-
barrier: {
|
|
2154
|
-
reason: "invalid_source_package_provenance",
|
|
2155
|
-
message:
|
|
2156
|
-
"DOCX export is blocked because the embedded source package provenance failed its integrity check.",
|
|
2157
|
-
},
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2888
|
+
if (snapshot.fatalError) {
|
|
2889
|
+
return `Editor opened in diagnostics mode. ${snapshot.fatalError.message}`;
|
|
2890
|
+
}
|
|
2160
2891
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
reason: "invalid_source_package_provenance",
|
|
2171
|
-
message:
|
|
2172
|
-
"DOCX export is blocked because the embedded source package provenance is no longer loadable as a valid package-backed session.",
|
|
2173
|
-
},
|
|
2174
|
-
};
|
|
2175
|
-
}
|
|
2892
|
+
return [
|
|
2893
|
+
snapshot.readOnly ? "Read-only mode." : "Editing enabled.",
|
|
2894
|
+
`${snapshot.comments.totalCount} comment${snapshot.comments.totalCount === 1 ? "" : "s"}.`,
|
|
2895
|
+
`${snapshot.trackedChanges.totalCount} tracked change${
|
|
2896
|
+
snapshot.trackedChanges.totalCount === 1 ? "" : "s"
|
|
2897
|
+
}.`,
|
|
2898
|
+
snapshot.compatibility.blockExport ? "Export blocked." : "Export available.",
|
|
2899
|
+
].join(" ");
|
|
2900
|
+
}
|
|
2176
2901
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
return
|
|
2180
|
-
barrier: {
|
|
2181
|
-
reason: "invalid_source_package_provenance",
|
|
2182
|
-
message:
|
|
2183
|
-
"DOCX export is blocked because the embedded source package provenance could not be decoded into a package-backed session.",
|
|
2184
|
-
},
|
|
2185
|
-
};
|
|
2902
|
+
function getDiagnosticsModeMessage(error?: EditorError | null): string | null {
|
|
2903
|
+
if (!error) {
|
|
2904
|
+
return null;
|
|
2186
2905
|
}
|
|
2187
|
-
}
|
|
2188
2906
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
): PersistedEditorSnapshot {
|
|
2193
|
-
const featureEntryId = `feature:source-package-provenance:${barrier.reason}`;
|
|
2194
|
-
const featureEntries = snapshot.compatibility.featureEntries.some(
|
|
2195
|
-
(entry) => entry.featureEntryId === featureEntryId,
|
|
2196
|
-
)
|
|
2197
|
-
? snapshot.compatibility.featureEntries
|
|
2198
|
-
: [
|
|
2199
|
-
...snapshot.compatibility.featureEntries,
|
|
2200
|
-
{
|
|
2201
|
-
featureEntryId,
|
|
2202
|
-
featureKey: "source-package-provenance",
|
|
2203
|
-
featureClass: "unsupported-fatal" as const,
|
|
2204
|
-
message: barrier.message,
|
|
2205
|
-
details: {
|
|
2206
|
-
reason: barrier.reason,
|
|
2207
|
-
},
|
|
2208
|
-
},
|
|
2209
|
-
];
|
|
2907
|
+
if (error.code === "package_corrupt") {
|
|
2908
|
+
return `${error.message} The document opened in read-only diagnostics mode so you can inspect the issue safely, but editing and export stay blocked until you reload a valid .docx package.`;
|
|
2909
|
+
}
|
|
2210
2910
|
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
...snapshot.compatibility,
|
|
2215
|
-
blockExport: true,
|
|
2216
|
-
featureEntries,
|
|
2217
|
-
},
|
|
2218
|
-
};
|
|
2219
|
-
}
|
|
2911
|
+
if (error.code === "validation_failed") {
|
|
2912
|
+
return `${error.message} The document opened in read-only diagnostics mode because OOXML validation failed, so editing and export stay blocked until the source package is repaired.`;
|
|
2913
|
+
}
|
|
2220
2914
|
|
|
2221
|
-
|
|
2222
|
-
documentId: string,
|
|
2223
|
-
barrier: SnapshotExportBarrier,
|
|
2224
|
-
): EditorError {
|
|
2225
|
-
return {
|
|
2226
|
-
errorId: `${documentId}:export:${barrier.reason}`,
|
|
2227
|
-
code: "export_failed",
|
|
2228
|
-
message: barrier.message,
|
|
2229
|
-
isFatal: false,
|
|
2230
|
-
source: "export",
|
|
2231
|
-
details: {
|
|
2232
|
-
reason: barrier.reason,
|
|
2233
|
-
},
|
|
2234
|
-
};
|
|
2915
|
+
return error.isFatal ? error.message : null;
|
|
2235
2916
|
}
|
|
2236
2917
|
|
|
2237
|
-
function
|
|
2238
|
-
error: unknown,
|
|
2239
|
-
documentId: string,
|
|
2240
|
-
options?: ExportDocxOptions,
|
|
2241
|
-
): EditorError {
|
|
2918
|
+
function normalizeEditorError(error: unknown): EditorError {
|
|
2242
2919
|
if (
|
|
2243
2920
|
typeof error === "object" &&
|
|
2244
2921
|
error !== null &&
|
|
@@ -2250,18 +2927,47 @@ function normalizeExportError(
|
|
|
2250
2927
|
}
|
|
2251
2928
|
|
|
2252
2929
|
return {
|
|
2253
|
-
errorId:
|
|
2254
|
-
code: "
|
|
2255
|
-
message:
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
source: "export",
|
|
2259
|
-
details: {
|
|
2260
|
-
requestedOptions: options ?? {},
|
|
2261
|
-
},
|
|
2930
|
+
errorId: "word-review-editor-load",
|
|
2931
|
+
code: "internal_invariant",
|
|
2932
|
+
message: error instanceof Error ? error.message : "Unknown editor load failure.",
|
|
2933
|
+
isFatal: true,
|
|
2934
|
+
source: "runtime",
|
|
2262
2935
|
};
|
|
2263
2936
|
}
|
|
2264
2937
|
|
|
2938
|
+
function guessSourceLabel(
|
|
2939
|
+
initialSourceLabel?: string,
|
|
2940
|
+
initialSessionState?: EditorSessionState,
|
|
2941
|
+
initialSnapshot?: PersistedEditorSnapshot,
|
|
2942
|
+
externalDocSource?: WordReviewEditorProps["externalDocSource"],
|
|
2943
|
+
): string | undefined {
|
|
2944
|
+
return (
|
|
2945
|
+
externalDocSource?.sourceLabel ??
|
|
2946
|
+
(externalDocSource?.kind === "session"
|
|
2947
|
+
? externalDocSource.sessionState.sourcePackage?.sourceLabel
|
|
2948
|
+
: undefined) ??
|
|
2949
|
+
(externalDocSource?.kind === "snapshot"
|
|
2950
|
+
? externalDocSource.snapshot.sourcePackage?.sourceLabel
|
|
2951
|
+
: undefined) ??
|
|
2952
|
+
initialSourceLabel ??
|
|
2953
|
+
initialSessionState?.sourcePackage?.sourceLabel ??
|
|
2954
|
+
initialSessionState?.editorBuild ??
|
|
2955
|
+
initialSnapshot?.sourcePackage?.sourceLabel ??
|
|
2956
|
+
initialSnapshot?.editorBuild ??
|
|
2957
|
+
undefined
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
function deriveEditorViewMode(
|
|
2962
|
+
readOnly: boolean,
|
|
2963
|
+
reviewMode: WordReviewEditorProps["reviewMode"] = "review",
|
|
2964
|
+
): EditorViewMode {
|
|
2965
|
+
if (readOnly) {
|
|
2966
|
+
return "view";
|
|
2967
|
+
}
|
|
2968
|
+
return reviewMode === "editing" ? "editing" : "review";
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2265
2971
|
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
2266
2972
|
return {
|
|
2267
2973
|
anchor: selection.anchor,
|
|
@@ -2311,9 +3017,17 @@ function createSelectionFromAnchor(
|
|
|
2311
3017
|
}
|
|
2312
3018
|
}
|
|
2313
3019
|
|
|
2314
|
-
function estimateStoryLength(
|
|
2315
|
-
|
|
2316
|
-
|
|
3020
|
+
function estimateStoryLength(
|
|
3021
|
+
sessionStateOrSnapshot?: EditorSessionState | PersistedEditorSnapshot,
|
|
3022
|
+
): number {
|
|
3023
|
+
if (!sessionStateOrSnapshot) {
|
|
3024
|
+
return 0;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
const content = "sessionVersion" in sessionStateOrSnapshot
|
|
3028
|
+
? sessionStateOrSnapshot.canonicalDocument.content
|
|
3029
|
+
: sessionStateOrSnapshot.canonicalDocument.content;
|
|
3030
|
+
return Array.isArray(content?.children) ? content.children.length : 0;
|
|
2317
3031
|
}
|
|
2318
3032
|
|
|
2319
3033
|
function collapsedSelection(): RuntimeRenderSnapshot["selection"] {
|
|
@@ -2358,3 +3072,414 @@ function summarizeSelectionPreview(snapshot: RuntimeRenderSnapshot): string | nu
|
|
|
2358
3072
|
|
|
2359
3073
|
return preview.length > 48 ? `${preview.slice(0, 45)}...` : preview;
|
|
2360
3074
|
}
|
|
3075
|
+
|
|
3076
|
+
function selectionToolbarAnchorsEqual(
|
|
3077
|
+
left: SelectionToolbarAnchor | null,
|
|
3078
|
+
right: SelectionToolbarAnchor | null,
|
|
3079
|
+
): boolean {
|
|
3080
|
+
if (left === right) {
|
|
3081
|
+
return true;
|
|
3082
|
+
}
|
|
3083
|
+
if (!left || !right) {
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
return (
|
|
3087
|
+
left.left === right.left &&
|
|
3088
|
+
left.right === right.right &&
|
|
3089
|
+
left.top === right.top &&
|
|
3090
|
+
left.bottom === right.bottom
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
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
|
+
|
|
3190
|
+
function createSelectionToolbarSelectionKey(
|
|
3191
|
+
selection: RuntimeRenderSnapshot["selection"],
|
|
3192
|
+
activeStory: EditorStoryTarget,
|
|
3193
|
+
): string | null {
|
|
3194
|
+
if (selection.isCollapsed || selection.activeRange.kind !== "range") {
|
|
3195
|
+
return null;
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
return JSON.stringify({
|
|
3199
|
+
story: activeStory,
|
|
3200
|
+
from: selection.activeRange.from,
|
|
3201
|
+
to: selection.activeRange.to,
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
function buildSelectionToolbarModel(args: {
|
|
3206
|
+
snapshot: RuntimeRenderSnapshot;
|
|
3207
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>;
|
|
3208
|
+
capabilities: ReturnType<typeof deriveCapabilities>;
|
|
3209
|
+
documentNavigation: ReturnType<WordReviewEditorRuntime["getDocumentNavigationSnapshot"]>;
|
|
3210
|
+
styleCatalog: StyleCatalogSnapshot;
|
|
3211
|
+
formattingState: FormattingStateSnapshot;
|
|
3212
|
+
addCommentDisabledReason?: string;
|
|
3213
|
+
}): SelectionToolbarModel | null {
|
|
3214
|
+
const {
|
|
3215
|
+
snapshot,
|
|
3216
|
+
viewState,
|
|
3217
|
+
capabilities,
|
|
3218
|
+
documentNavigation,
|
|
3219
|
+
styleCatalog,
|
|
3220
|
+
formattingState,
|
|
3221
|
+
addCommentDisabledReason,
|
|
3222
|
+
} = args;
|
|
3223
|
+
|
|
3224
|
+
if (
|
|
3225
|
+
!snapshot.surface ||
|
|
3226
|
+
snapshot.selection.isCollapsed ||
|
|
3227
|
+
snapshot.selection.activeRange.kind !== "range" ||
|
|
3228
|
+
!capabilities.canEdit ||
|
|
3229
|
+
viewState.viewMode === "view"
|
|
3230
|
+
) {
|
|
3231
|
+
return null;
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
const previewText = summarizeSelectionPreview(snapshot);
|
|
3235
|
+
if (!previewText) {
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
const badges = [
|
|
3240
|
+
createSelectionToolbarStoryBadge(viewState.activeStory),
|
|
3241
|
+
viewState.workspaceMode === "page" && documentNavigation.pageCount > 0
|
|
3242
|
+
? { label: `Page ${documentNavigation.activePageIndex + 1}` as const }
|
|
3243
|
+
: null,
|
|
3244
|
+
createSelectionToolbarStyleBadge(styleCatalog, formattingState),
|
|
3245
|
+
createSelectionToolbarListBadge(viewState),
|
|
3246
|
+
].filter((badge): badge is SelectionToolbarModel["badges"][number] => Boolean(badge));
|
|
3247
|
+
|
|
3248
|
+
return {
|
|
3249
|
+
previewText,
|
|
3250
|
+
badges,
|
|
3251
|
+
canToggleFormatting: true,
|
|
3252
|
+
boldActive: formattingState.bold,
|
|
3253
|
+
italicActive: formattingState.italic,
|
|
3254
|
+
underlineActive: formattingState.underline,
|
|
3255
|
+
canAddComment: capabilities.canAddComment,
|
|
3256
|
+
...(addCommentDisabledReason ? { disabledReason: addCommentDisabledReason } : {}),
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function createSelectionToolbarStoryBadge(
|
|
3261
|
+
target: EditorStoryTarget,
|
|
3262
|
+
): SelectionToolbarModel["badges"][number] | null {
|
|
3263
|
+
if (target.kind === "main") {
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
return {
|
|
3268
|
+
label:
|
|
3269
|
+
target.kind === "header"
|
|
3270
|
+
? target.variant === "default"
|
|
3271
|
+
? "Header"
|
|
3272
|
+
: `Header ${target.variant}`
|
|
3273
|
+
: target.kind === "footer"
|
|
3274
|
+
? target.variant === "default"
|
|
3275
|
+
? "Footer"
|
|
3276
|
+
: `Footer ${target.variant}`
|
|
3277
|
+
: target.kind === "footnote"
|
|
3278
|
+
? "Footnote"
|
|
3279
|
+
: "Endnote",
|
|
3280
|
+
tone: "accent",
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
function createSelectionToolbarStyleBadge(
|
|
3285
|
+
styleCatalog: StyleCatalogSnapshot,
|
|
3286
|
+
formattingState: FormattingStateSnapshot,
|
|
3287
|
+
): SelectionToolbarModel["badges"][number] | null {
|
|
3288
|
+
if (!formattingState.paragraphStyleId) {
|
|
3289
|
+
return null;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
const styleEntry = styleCatalog.paragraphs.find(
|
|
3293
|
+
(entry) => entry.styleId === formattingState.paragraphStyleId,
|
|
3294
|
+
);
|
|
3295
|
+
if (!styleEntry || styleEntry.isDefault) {
|
|
3296
|
+
return null;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
return { label: styleEntry.displayName };
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
function createSelectionToolbarListBadge(
|
|
3303
|
+
viewState: ReturnType<WordReviewEditorRuntime["getViewState"]>,
|
|
3304
|
+
): SelectionToolbarModel["badges"][number] | null {
|
|
3305
|
+
if (!viewState.activeListContext) {
|
|
3306
|
+
return null;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
return {
|
|
3310
|
+
label: viewState.activeListContext.isOrdered ? "Numbered list" : "Bulleted list",
|
|
3311
|
+
};
|
|
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
|
+
}
|