@beyondwork/docx-react-component 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -17,8 +17,13 @@ import type {
|
|
|
17
17
|
CommentSidebarSnapshot,
|
|
18
18
|
CommentSidebarThreadSnapshot,
|
|
19
19
|
CompatibilityReport,
|
|
20
|
+
DocumentChunkSnapshot,
|
|
21
|
+
DocumentLocationSnapshot,
|
|
20
22
|
DocumentMode,
|
|
21
23
|
DocumentNavigationSnapshot,
|
|
24
|
+
DocumentOutlineSnapshot,
|
|
25
|
+
DocumentSectionSnapshot,
|
|
26
|
+
DocumentTextToken,
|
|
22
27
|
EditorSessionState,
|
|
23
28
|
EditorAnchorProjection,
|
|
24
29
|
EditorError,
|
|
@@ -36,8 +41,19 @@ import type {
|
|
|
36
41
|
PageLayoutSnapshot,
|
|
37
42
|
PersistedEditorSnapshot,
|
|
38
43
|
ProtectionSnapshot,
|
|
44
|
+
RestorePointSnapshot,
|
|
45
|
+
RestoreResult,
|
|
46
|
+
ReviewWorkSnapshot,
|
|
47
|
+
RuntimeContextAnalyticsQuery,
|
|
48
|
+
RuntimeContextAnalyticsSnapshot,
|
|
39
49
|
RuntimeRenderSnapshot,
|
|
40
50
|
SelectionSnapshot,
|
|
51
|
+
SnapshotRefreshHints,
|
|
52
|
+
SuggestionsSnapshot,
|
|
53
|
+
SurfaceBlockSnapshot,
|
|
54
|
+
SurfaceInlineSegment,
|
|
55
|
+
StoryTextStreamSnapshot,
|
|
56
|
+
TocSnapshot,
|
|
41
57
|
StyleCatalogSnapshot,
|
|
42
58
|
TocRefreshOptions,
|
|
43
59
|
TocRefreshResult,
|
|
@@ -49,6 +65,9 @@ import type {
|
|
|
49
65
|
WorkflowCandidateRange,
|
|
50
66
|
WorkflowCandidateRangeOptions,
|
|
51
67
|
WorkflowBlockedCommandReason,
|
|
68
|
+
WorkflowMetadataDefinition,
|
|
69
|
+
WorkflowMetadataEntry,
|
|
70
|
+
WorkflowMetadataSnapshot,
|
|
52
71
|
WorkflowMarkupSnapshot,
|
|
53
72
|
WorkflowOverlay,
|
|
54
73
|
WorkflowScopeSnapshot,
|
|
@@ -82,6 +101,7 @@ import { buildBookmarkNameMap } from "../legal/bookmarks.ts";
|
|
|
82
101
|
import {
|
|
83
102
|
describeOpaqueFragment,
|
|
84
103
|
findOpaqueFragmentsIntersectingRange,
|
|
104
|
+
isBlockedImportFeatureKey,
|
|
85
105
|
} from "../preservation/store.ts";
|
|
86
106
|
import { createCommentSidebarProjection } from "../review/store/comment-store.ts";
|
|
87
107
|
import { createCommentStoreFromRuntimeComments } from "../review/store/runtime-comment-store.ts";
|
|
@@ -89,6 +109,7 @@ import {
|
|
|
89
109
|
createRevisionSidebarProjection,
|
|
90
110
|
type RevisionStore,
|
|
91
111
|
} from "../review/store/revision-store.ts";
|
|
112
|
+
import { createSuggestionsSnapshot } from "./suggestions-snapshot.ts";
|
|
92
113
|
import { buildCompatibilityReport } from "../validation/compatibility-engine.ts";
|
|
93
114
|
import { mergeCompatibilityReports } from "../validation/compatibility-report.ts";
|
|
94
115
|
import { createEditorSurfaceSnapshot } from "./surface-projection.ts";
|
|
@@ -100,6 +121,29 @@ import {
|
|
|
100
121
|
createDocumentNavigationSnapshot,
|
|
101
122
|
findPageForOffset,
|
|
102
123
|
} from "./document-navigation.ts";
|
|
124
|
+
import {
|
|
125
|
+
createDocumentOutlineSnapshot,
|
|
126
|
+
createDocumentSectionSnapshots,
|
|
127
|
+
createSectionLocations,
|
|
128
|
+
createTocSnapshot,
|
|
129
|
+
findDocumentSectionSnapshot,
|
|
130
|
+
} from "./document-outline.ts";
|
|
131
|
+
import {
|
|
132
|
+
createCurrentLocation,
|
|
133
|
+
createDocumentChunks,
|
|
134
|
+
createDocumentLocationSnapshot,
|
|
135
|
+
createDocumentTextStreamSnapshots,
|
|
136
|
+
createLocationFromSelection,
|
|
137
|
+
createRestorePoint,
|
|
138
|
+
createReviewWorkSnapshot,
|
|
139
|
+
createWorkflowChunks,
|
|
140
|
+
} from "./document-locations.ts";
|
|
141
|
+
import { describeEventImpact } from "./event-refresh-hints.ts";
|
|
142
|
+
import {
|
|
143
|
+
createRuntimeContextAnalyticsSnapshot,
|
|
144
|
+
resolveCurrentContextAnalyticsQuery,
|
|
145
|
+
runtimeContextAnalyticsSnapshotsEqual,
|
|
146
|
+
} from "./context-analytics.ts";
|
|
103
147
|
import {
|
|
104
148
|
buildPageLayoutSnapshot,
|
|
105
149
|
buildResolvedSections,
|
|
@@ -177,6 +221,7 @@ export interface DocumentRuntime {
|
|
|
177
221
|
focus(): void;
|
|
178
222
|
blur(): void;
|
|
179
223
|
setDefaultAuthorId?(authorId?: string): void;
|
|
224
|
+
getDefaultAuthorId?(): string | undefined;
|
|
180
225
|
addComment(params: AddCommentParams): string;
|
|
181
226
|
openComment(commentId: string): void;
|
|
182
227
|
resolveComment(commentId: string): void;
|
|
@@ -198,6 +243,28 @@ export interface DocumentRuntime {
|
|
|
198
243
|
setZoom(level: ZoomLevel): void;
|
|
199
244
|
getPageLayoutSnapshot(): PageLayoutSnapshot | null;
|
|
200
245
|
getDocumentNavigationSnapshot(): DocumentNavigationSnapshot;
|
|
246
|
+
getCurrentLocation(): DocumentLocationSnapshot | null;
|
|
247
|
+
getLocationForSelection(selection: SelectionSnapshot): DocumentLocationSnapshot | null;
|
|
248
|
+
getLocationForAnchor(
|
|
249
|
+
anchor: EditorAnchorProjection,
|
|
250
|
+
storyTarget?: EditorStoryTarget,
|
|
251
|
+
): DocumentLocationSnapshot | null;
|
|
252
|
+
captureRestorePoint(
|
|
253
|
+
input?: SelectionSnapshot | EditorAnchorProjection,
|
|
254
|
+
): RestorePointSnapshot | null;
|
|
255
|
+
restoreToPoint(
|
|
256
|
+
point: RestorePointSnapshot,
|
|
257
|
+
options?: { behavior?: "exact" | "semantic"; scroll?: boolean },
|
|
258
|
+
): RestoreResult;
|
|
259
|
+
getOutlineSnapshot(): DocumentOutlineSnapshot;
|
|
260
|
+
getTocSnapshot(): TocSnapshot | null;
|
|
261
|
+
getSections(): DocumentSectionSnapshot[];
|
|
262
|
+
getSectionSnapshot(input: {
|
|
263
|
+
sectionIndex?: number;
|
|
264
|
+
headingId?: string;
|
|
265
|
+
bookmarkName?: string;
|
|
266
|
+
}): DocumentSectionSnapshot | null;
|
|
267
|
+
describeEventImpact(event: WordReviewEditorEvent): SnapshotRefreshHints;
|
|
201
268
|
getFieldSnapshot(): FieldSnapshot;
|
|
202
269
|
updateFields(options?: UpdateFieldsOptions): UpdateFieldsResult;
|
|
203
270
|
updateTableOfContents(options?: TocRefreshOptions): TocRefreshResult;
|
|
@@ -206,16 +273,30 @@ export interface DocumentRuntime {
|
|
|
206
273
|
getCompatibilityReport(): CompatibilityReport;
|
|
207
274
|
getWarnings(): EditorWarning[];
|
|
208
275
|
exportDocx(options?: ExportDocxOptions): Promise<ExportResult>;
|
|
276
|
+
getSuggestionsSnapshot(): SuggestionsSnapshot;
|
|
209
277
|
setWorkflowOverlay(overlay: WorkflowOverlay): void;
|
|
210
278
|
clearWorkflowOverlay(): void;
|
|
211
279
|
getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
|
|
212
280
|
getInteractionGuardSnapshot(): InteractionGuardSnapshot;
|
|
213
281
|
getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
|
|
282
|
+
setWorkflowMetadataDefinitions(definitions: WorkflowMetadataDefinition[]): void;
|
|
283
|
+
clearWorkflowMetadataDefinitions(): void;
|
|
284
|
+
setWorkflowMetadataEntries(entries: WorkflowMetadataEntry[]): void;
|
|
285
|
+
clearWorkflowMetadataEntries(): void;
|
|
286
|
+
getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
|
|
214
287
|
setHostAnnotationOverlay(overlay: HostAnnotationOverlay): void;
|
|
215
288
|
clearHostAnnotationOverlay(): void;
|
|
216
289
|
getHostAnnotationSnapshot(): HostAnnotationSnapshot;
|
|
217
290
|
getWorkflowCandidateRanges(options?: WorkflowCandidateRangeOptions): WorkflowCandidateRange[];
|
|
218
291
|
replaceWorkflowMarkupText(markupId: string, text: string): void;
|
|
292
|
+
getDocumentTextStream(): StoryTextStreamSnapshot[];
|
|
293
|
+
getStoryTextStream(target: EditorStoryTarget): StoryTextStreamSnapshot | null;
|
|
294
|
+
getDocumentChunks(): DocumentChunkSnapshot[];
|
|
295
|
+
getWorkflowChunks(): DocumentChunkSnapshot[];
|
|
296
|
+
getReviewWorkSnapshot(): ReviewWorkSnapshot;
|
|
297
|
+
getRuntimeContextAnalytics(
|
|
298
|
+
query?: RuntimeContextAnalyticsQuery,
|
|
299
|
+
): RuntimeContextAnalyticsSnapshot | null;
|
|
219
300
|
}
|
|
220
301
|
|
|
221
302
|
export interface CreateDocumentRuntimeOptions {
|
|
@@ -274,6 +355,14 @@ export function createDocumentRuntime(
|
|
|
274
355
|
preservedRangeCount: 0,
|
|
275
356
|
};
|
|
276
357
|
let workflowOverlay: WorkflowOverlay | null = null;
|
|
358
|
+
let workflowMetadataDefinitions: WorkflowMetadataDefinition[] =
|
|
359
|
+
options.initialSessionState?.workflowMetadata?.definitions
|
|
360
|
+
?? options.initialSnapshot?.workflowMetadata?.definitions
|
|
361
|
+
?? [];
|
|
362
|
+
let workflowMetadataEntries: WorkflowMetadataEntry[] =
|
|
363
|
+
options.initialSessionState?.workflowMetadata?.entries
|
|
364
|
+
?? options.initialSnapshot?.workflowMetadata?.entries
|
|
365
|
+
?? [];
|
|
277
366
|
let hostAnnotationOverlay: HostAnnotationOverlay | null = null;
|
|
278
367
|
const initialPersistedSnapshot = options.initialSessionState
|
|
279
368
|
? persistedSnapshotFromEditorSessionState(options.initialSessionState, {
|
|
@@ -320,6 +409,12 @@ export function createDocumentRuntime(
|
|
|
320
409
|
snapshot: TrackedChangesSnapshot;
|
|
321
410
|
}
|
|
322
411
|
| undefined;
|
|
412
|
+
let cachedSuggestions:
|
|
413
|
+
| {
|
|
414
|
+
trackedChanges: TrackedChangesSnapshot;
|
|
415
|
+
snapshot: SuggestionsSnapshot;
|
|
416
|
+
}
|
|
417
|
+
| undefined;
|
|
323
418
|
let cachedPageLayout:
|
|
324
419
|
| {
|
|
325
420
|
revisionToken: string;
|
|
@@ -371,9 +466,30 @@ export function createDocumentRuntime(
|
|
|
371
466
|
activeStoryKey: string;
|
|
372
467
|
protectionSnapshot: ProtectionSnapshot;
|
|
373
468
|
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
469
|
+
workflowOverlay: WorkflowOverlay | null;
|
|
470
|
+
workflowMetadataDefinitions: WorkflowMetadataDefinition[];
|
|
471
|
+
workflowMetadataEntries: WorkflowMetadataEntry[];
|
|
374
472
|
snapshot: WorkflowMarkupSnapshot;
|
|
375
473
|
}
|
|
376
474
|
| undefined;
|
|
475
|
+
const cachedContextAnalyticsSnapshots = new Map<
|
|
476
|
+
string,
|
|
477
|
+
{
|
|
478
|
+
revisionToken: string;
|
|
479
|
+
activeStoryKey: string;
|
|
480
|
+
selection: EditorState["selection"];
|
|
481
|
+
readOnly: boolean;
|
|
482
|
+
documentMode: DocumentMode;
|
|
483
|
+
workflowOverlay: WorkflowOverlay | null;
|
|
484
|
+
protectionSnapshot: ProtectionSnapshot;
|
|
485
|
+
warnings: EditorState["warnings"];
|
|
486
|
+
fatalError: EditorState["fatalError"];
|
|
487
|
+
snapshot: RuntimeContextAnalyticsSnapshot | null;
|
|
488
|
+
}
|
|
489
|
+
>();
|
|
490
|
+
let lastEmittedContextAnalyticsSnapshots:
|
|
491
|
+
| Map<string, RuntimeContextAnalyticsSnapshot | null>
|
|
492
|
+
| undefined;
|
|
377
493
|
|
|
378
494
|
function getCachedSurface(
|
|
379
495
|
document: CanonicalDocumentEnvelope,
|
|
@@ -471,6 +587,30 @@ export function createDocumentRuntime(
|
|
|
471
587
|
return snapshot;
|
|
472
588
|
}
|
|
473
589
|
|
|
590
|
+
function getCachedSuggestionsSnapshot(nextState: EditorState): SuggestionsSnapshot {
|
|
591
|
+
const trackedChanges = getCachedTrackedChangesSnapshot(nextState, undefined);
|
|
592
|
+
if (
|
|
593
|
+
cachedSuggestions &&
|
|
594
|
+
cachedSuggestions.trackedChanges === trackedChanges
|
|
595
|
+
) {
|
|
596
|
+
return cachedSuggestions.snapshot;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const snapshot = createSuggestionsSnapshot(trackedChanges);
|
|
600
|
+
cachedSuggestions = {
|
|
601
|
+
trackedChanges,
|
|
602
|
+
snapshot,
|
|
603
|
+
};
|
|
604
|
+
return snapshot;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function findSuggestionByChangeId(
|
|
608
|
+
snapshot: SuggestionsSnapshot,
|
|
609
|
+
changeId: string,
|
|
610
|
+
) {
|
|
611
|
+
return snapshot.suggestions.find((suggestion) => suggestion.changeIds.includes(changeId));
|
|
612
|
+
}
|
|
613
|
+
|
|
474
614
|
function getCachedDocumentNavigationSnapshot(
|
|
475
615
|
nextState: EditorState,
|
|
476
616
|
nextActiveStory: EditorStoryTarget,
|
|
@@ -612,15 +752,6 @@ export function createDocumentRuntime(
|
|
|
612
752
|
const effectiveDocumentMode = getEffectiveDocumentMode(selection);
|
|
613
753
|
|
|
614
754
|
if (effectiveDocumentMode === "suggesting" && commandType) {
|
|
615
|
-
if (
|
|
616
|
-
activeStory.kind !== "main" &&
|
|
617
|
-
SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS.has(commandType)
|
|
618
|
-
) {
|
|
619
|
-
reasons.push({
|
|
620
|
-
code: "suggesting_unsupported",
|
|
621
|
-
message: "Suggesting mode is not yet export-safe in this story.",
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
755
|
if (SUGGESTING_UNSUPPORTED_COMMANDS.has(commandType)) {
|
|
625
756
|
reasons.push({
|
|
626
757
|
code: "suggesting_unsupported",
|
|
@@ -708,6 +839,11 @@ export function createDocumentRuntime(
|
|
|
708
839
|
function deriveOpaqueWorkflowBlockedReason(
|
|
709
840
|
range: { from: number; to: number },
|
|
710
841
|
): WorkflowBlockedCommandReason | null {
|
|
842
|
+
const surfaceReason = deriveSurfaceOpaqueWorkflowBlockedReason(range);
|
|
843
|
+
if (surfaceReason) {
|
|
844
|
+
return surfaceReason;
|
|
845
|
+
}
|
|
846
|
+
|
|
711
847
|
const targetPartPath = getStoryTargetOpaquePartPath(activeStory);
|
|
712
848
|
if (!targetPartPath) {
|
|
713
849
|
return null;
|
|
@@ -721,14 +857,9 @@ export function createDocumentRuntime(
|
|
|
721
857
|
return null;
|
|
722
858
|
}
|
|
723
859
|
|
|
724
|
-
const blockedImportFeatureKeys = new Set([
|
|
725
|
-
"alt-chunk",
|
|
726
|
-
"alternate-content",
|
|
727
|
-
"custom-xml",
|
|
728
|
-
]);
|
|
729
860
|
const blockedImportFragment =
|
|
730
861
|
fragments.find((fragment) =>
|
|
731
|
-
|
|
862
|
+
isBlockedImportFeatureKey(describeOpaqueFragment(fragment).featureKey),
|
|
732
863
|
) ?? null;
|
|
733
864
|
const fragment = blockedImportFragment ?? fragments[0]!;
|
|
734
865
|
const descriptor = describeOpaqueFragment(fragment);
|
|
@@ -749,6 +880,149 @@ export function createDocumentRuntime(
|
|
|
749
880
|
};
|
|
750
881
|
}
|
|
751
882
|
|
|
883
|
+
function deriveSurfaceOpaqueWorkflowBlockedReason(
|
|
884
|
+
range: { from: number; to: number },
|
|
885
|
+
): WorkflowBlockedCommandReason | null {
|
|
886
|
+
const blocks = getActiveStorySurfaceBlocks();
|
|
887
|
+
if (!blocks) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const opaqueTarget = findSurfaceOpaqueTargetIntersectingRange(blocks, range);
|
|
892
|
+
if (!opaqueTarget) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const code = opaqueTarget.blockedReasonCode ?? "workflow_preserve_only";
|
|
897
|
+
return {
|
|
898
|
+
code,
|
|
899
|
+
message:
|
|
900
|
+
code === "workflow_blocked_import"
|
|
901
|
+
? `${opaqueTarget.label} remains a blocked import and cannot be edited.`
|
|
902
|
+
: `${opaqueTarget.label} remains preserve-only and cannot be edited.`,
|
|
903
|
+
anchor: toPublicAnchorProjection(
|
|
904
|
+
createRangeAnchor(opaqueTarget.from, opaqueTarget.to, {
|
|
905
|
+
start: -1,
|
|
906
|
+
end: 1,
|
|
907
|
+
}),
|
|
908
|
+
),
|
|
909
|
+
storyTarget: activeStory,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function getActiveStorySurfaceBlocks(): readonly SurfaceBlockSnapshot[] | null {
|
|
914
|
+
const surface = cachedRenderSnapshot.surface;
|
|
915
|
+
if (!surface) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (activeStory.kind === "main") {
|
|
920
|
+
return surface.blocks;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const activeStoryKey = storyTargetKey(activeStory);
|
|
924
|
+
return (
|
|
925
|
+
surface.secondaryStories.find((story) => storyTargetKey(story.target) === activeStoryKey)?.blocks ??
|
|
926
|
+
null
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function findSurfaceOpaqueTargetIntersectingRange(
|
|
931
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
932
|
+
range: { from: number; to: number },
|
|
933
|
+
): {
|
|
934
|
+
from: number;
|
|
935
|
+
to: number;
|
|
936
|
+
label: string;
|
|
937
|
+
blockedReasonCode: "workflow_preserve_only" | "workflow_blocked_import";
|
|
938
|
+
} | null {
|
|
939
|
+
for (const block of blocks) {
|
|
940
|
+
if (block.kind === "paragraph") {
|
|
941
|
+
for (const segment of block.segments) {
|
|
942
|
+
const match = matchOpaqueInlineSegment(segment, range);
|
|
943
|
+
if (match) {
|
|
944
|
+
return match;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (block.kind === "table") {
|
|
951
|
+
for (const row of block.rows) {
|
|
952
|
+
for (const cell of row.cells) {
|
|
953
|
+
const match = findSurfaceOpaqueTargetIntersectingRange(cell.content, range);
|
|
954
|
+
if (match) {
|
|
955
|
+
return match;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (block.kind === "sdt_block") {
|
|
963
|
+
const match = findSurfaceOpaqueTargetIntersectingRange(block.children, range);
|
|
964
|
+
if (match) {
|
|
965
|
+
return match;
|
|
966
|
+
}
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (block.kind !== "opaque_block") {
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const blockRange = {
|
|
975
|
+
from: block.from,
|
|
976
|
+
to: block.to > block.from ? block.to : block.from + 1,
|
|
977
|
+
};
|
|
978
|
+
if (rangesIntersect(blockRange, range)) {
|
|
979
|
+
if (!block.blockedReasonCode) {
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
from: blockRange.from,
|
|
984
|
+
to: blockRange.to,
|
|
985
|
+
label: block.label,
|
|
986
|
+
blockedReasonCode: block.blockedReasonCode,
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function matchOpaqueInlineSegment(
|
|
995
|
+
segment: SurfaceInlineSegment,
|
|
996
|
+
range: { from: number; to: number },
|
|
997
|
+
): {
|
|
998
|
+
from: number;
|
|
999
|
+
to: number;
|
|
1000
|
+
label: string;
|
|
1001
|
+
blockedReasonCode: "workflow_preserve_only" | "workflow_blocked_import";
|
|
1002
|
+
} | null {
|
|
1003
|
+
if (segment.kind !== "opaque_inline") {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
if (!segment.blockedReasonCode) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const segmentRange = {
|
|
1011
|
+
from: segment.from,
|
|
1012
|
+
to: segment.to > segment.from ? segment.to : segment.from + 1,
|
|
1013
|
+
};
|
|
1014
|
+
if (!rangesIntersect(segmentRange, range)) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
from: segmentRange.from,
|
|
1020
|
+
to: segmentRange.to,
|
|
1021
|
+
label: segment.label,
|
|
1022
|
+
blockedReasonCode: segment.blockedReasonCode,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
752
1026
|
function getStoryTargetOpaquePartPath(storyTarget: EditorStoryTarget): string | null {
|
|
753
1027
|
if (storyTarget.kind === "main") {
|
|
754
1028
|
return "/word/document.xml";
|
|
@@ -778,6 +1052,13 @@ export function createDocumentRuntime(
|
|
|
778
1052
|
return null;
|
|
779
1053
|
}
|
|
780
1054
|
|
|
1055
|
+
function rangesIntersect(
|
|
1056
|
+
left: { from: number; to: number },
|
|
1057
|
+
right: { from: number; to: number },
|
|
1058
|
+
): boolean {
|
|
1059
|
+
return left.from < right.to && right.from < left.to;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
781
1062
|
function deriveWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null {
|
|
782
1063
|
if (!workflowOverlay) return null;
|
|
783
1064
|
const blockedReasons = getCachedInteractionGuardSnapshot().blockedReasons;
|
|
@@ -803,6 +1084,13 @@ export function createDocumentRuntime(
|
|
|
803
1084
|
};
|
|
804
1085
|
}
|
|
805
1086
|
|
|
1087
|
+
function deriveWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot {
|
|
1088
|
+
return {
|
|
1089
|
+
definitions: structuredClone(workflowMetadataDefinitions),
|
|
1090
|
+
entries: structuredClone(workflowMetadataEntries),
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
806
1094
|
function getEffectiveWorkflowScopes(overlay: WorkflowOverlay): WorkflowOverlay["scopes"] {
|
|
807
1095
|
const activeWorkItemId = overlay.activeWorkItemId ?? null;
|
|
808
1096
|
const activeWorkItemScopeIds =
|
|
@@ -847,20 +1135,48 @@ export function createDocumentRuntime(
|
|
|
847
1135
|
const blockedReasons = evaluateWorkflowBlockedReasons(state.selection);
|
|
848
1136
|
const matchingScope = getMatchingWorkflowScope(state.selection);
|
|
849
1137
|
const primaryBlockedReason = blockedReasons[0];
|
|
1138
|
+
const effectiveMode = primaryBlockedReason
|
|
1139
|
+
? (
|
|
1140
|
+
primaryBlockedReason.code === "workflow_comment_only"
|
|
1141
|
+
? "comment"
|
|
1142
|
+
: primaryBlockedReason.code === "workflow_view_only"
|
|
1143
|
+
? "view"
|
|
1144
|
+
: "blocked"
|
|
1145
|
+
)
|
|
1146
|
+
: getEffectiveDocumentMode(state.selection) === "suggesting"
|
|
1147
|
+
? "suggest"
|
|
1148
|
+
: matchingScope?.mode ?? "edit";
|
|
850
1149
|
const snapshot: InteractionGuardSnapshot = {
|
|
851
|
-
effectiveMode
|
|
852
|
-
? (
|
|
853
|
-
primaryBlockedReason.code === "workflow_comment_only"
|
|
854
|
-
? "comment"
|
|
855
|
-
: primaryBlockedReason.code === "workflow_view_only"
|
|
856
|
-
? "view"
|
|
857
|
-
: "blocked"
|
|
858
|
-
)
|
|
859
|
-
: getEffectiveDocumentMode(state.selection) === "suggesting"
|
|
860
|
-
? "suggest"
|
|
861
|
-
: matchingScope?.mode ?? "edit",
|
|
1150
|
+
effectiveMode,
|
|
862
1151
|
...(matchingScope?.scopeId ? { matchedScopeId: matchingScope.scopeId } : {}),
|
|
863
1152
|
...(matchingScope?.mode ? { matchedScopeMode: matchingScope.mode } : {}),
|
|
1153
|
+
targetAccess:
|
|
1154
|
+
effectiveMode === "edit"
|
|
1155
|
+
? "direct-edit"
|
|
1156
|
+
: effectiveMode === "suggest"
|
|
1157
|
+
? "suggest"
|
|
1158
|
+
: effectiveMode === "comment"
|
|
1159
|
+
? "comment-only"
|
|
1160
|
+
: effectiveMode === "view"
|
|
1161
|
+
? "view-only"
|
|
1162
|
+
: "blocked",
|
|
1163
|
+
commandCapabilities: [
|
|
1164
|
+
{
|
|
1165
|
+
family: "text",
|
|
1166
|
+
supported: evaluateWorkflowBlockedReasons(state.selection, "text.insert").length === 0,
|
|
1167
|
+
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "text.insert"),
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
family: "formatting",
|
|
1171
|
+
supported: evaluateWorkflowBlockedReasons(state.selection, "toggleBold").length === 0,
|
|
1172
|
+
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "toggleBold"),
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
family: "structure",
|
|
1176
|
+
supported: evaluateWorkflowBlockedReasons(state.selection, "insertTable").length === 0,
|
|
1177
|
+
blockedReasons: evaluateWorkflowBlockedReasons(state.selection, "insertTable"),
|
|
1178
|
+
},
|
|
1179
|
+
],
|
|
864
1180
|
...(primaryBlockedReason ? { disabledReason: primaryBlockedReason.message } : {}),
|
|
865
1181
|
blockedReasons,
|
|
866
1182
|
};
|
|
@@ -907,7 +1223,10 @@ export function createDocumentRuntime(
|
|
|
907
1223
|
cachedWorkflowMarkupSnapshot.revisionToken === state.revisionToken &&
|
|
908
1224
|
cachedWorkflowMarkupSnapshot.activeStoryKey === activeStoryKey &&
|
|
909
1225
|
cachedWorkflowMarkupSnapshot.protectionSnapshot === protectionSnapshot &&
|
|
910
|
-
cachedWorkflowMarkupSnapshot.preservation === state.document.preservation
|
|
1226
|
+
cachedWorkflowMarkupSnapshot.preservation === state.document.preservation &&
|
|
1227
|
+
cachedWorkflowMarkupSnapshot.workflowOverlay === workflowOverlay &&
|
|
1228
|
+
cachedWorkflowMarkupSnapshot.workflowMetadataDefinitions === workflowMetadataDefinitions &&
|
|
1229
|
+
cachedWorkflowMarkupSnapshot.workflowMetadataEntries === workflowMetadataEntries
|
|
911
1230
|
) {
|
|
912
1231
|
return cachedWorkflowMarkupSnapshot.snapshot;
|
|
913
1232
|
}
|
|
@@ -917,17 +1236,150 @@ export function createDocumentRuntime(
|
|
|
917
1236
|
fieldSnapshot: buildFieldSnapshot(state.document),
|
|
918
1237
|
protectionSnapshot,
|
|
919
1238
|
preservation: state.document.preservation,
|
|
1239
|
+
workflowMetadataSnapshot: deriveWorkflowMetadataSnapshot(),
|
|
920
1240
|
});
|
|
921
1241
|
cachedWorkflowMarkupSnapshot = {
|
|
922
1242
|
revisionToken: state.revisionToken,
|
|
923
1243
|
activeStoryKey,
|
|
924
1244
|
protectionSnapshot,
|
|
925
1245
|
preservation: state.document.preservation,
|
|
1246
|
+
workflowOverlay,
|
|
1247
|
+
workflowMetadataDefinitions,
|
|
1248
|
+
workflowMetadataEntries,
|
|
926
1249
|
snapshot,
|
|
927
1250
|
};
|
|
928
1251
|
return snapshot;
|
|
929
1252
|
}
|
|
930
1253
|
|
|
1254
|
+
function getCachedRuntimeContextAnalytics(
|
|
1255
|
+
query?: RuntimeContextAnalyticsQuery,
|
|
1256
|
+
): RuntimeContextAnalyticsSnapshot | null {
|
|
1257
|
+
const activeStoryKey = storyTargetKey(activeStory);
|
|
1258
|
+
const queryKey = getRuntimeContextAnalyticsQueryKey(query);
|
|
1259
|
+
const cachedEntry = cachedContextAnalyticsSnapshots.get(queryKey);
|
|
1260
|
+
if (
|
|
1261
|
+
cachedEntry &&
|
|
1262
|
+
cachedEntry.revisionToken === state.revisionToken &&
|
|
1263
|
+
cachedEntry.activeStoryKey === activeStoryKey &&
|
|
1264
|
+
cachedEntry.selection === state.selection &&
|
|
1265
|
+
cachedEntry.readOnly === state.readOnly &&
|
|
1266
|
+
cachedEntry.documentMode === viewState.documentMode &&
|
|
1267
|
+
cachedEntry.workflowOverlay === workflowOverlay &&
|
|
1268
|
+
cachedEntry.protectionSnapshot === protectionSnapshot &&
|
|
1269
|
+
cachedEntry.warnings === state.warnings &&
|
|
1270
|
+
cachedEntry.fatalError === state.fatalError
|
|
1271
|
+
) {
|
|
1272
|
+
return cachedEntry.snapshot;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
const snapshot = createRuntimeContextAnalyticsSnapshot({
|
|
1276
|
+
query,
|
|
1277
|
+
renderSnapshot: cachedRenderSnapshot,
|
|
1278
|
+
workflowOverlay,
|
|
1279
|
+
workflowScopeSnapshot: getCachedWorkflowScopeSnapshot(),
|
|
1280
|
+
interactionGuardSnapshot: getCachedInteractionGuardSnapshot(),
|
|
1281
|
+
workflowMarkupSnapshot: getCachedWorkflowMarkupSnapshot(),
|
|
1282
|
+
suggestionsSnapshot: getCachedSuggestionsSnapshot(state),
|
|
1283
|
+
reviewWorkSnapshot: createReviewWorkSnapshot({
|
|
1284
|
+
comments: cachedRenderSnapshot.comments,
|
|
1285
|
+
trackedChanges: cachedRenderSnapshot.trackedChanges,
|
|
1286
|
+
workflowMarkup: getCachedWorkflowMarkupSnapshot(),
|
|
1287
|
+
document: state.document,
|
|
1288
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
1289
|
+
}),
|
|
1290
|
+
warnings: state.warnings.map(toPublicWarning),
|
|
1291
|
+
compatibility: toPublicCompatibilityReport(createDerivedCompatibility(state)),
|
|
1292
|
+
});
|
|
1293
|
+
cachedContextAnalyticsSnapshots.set(queryKey, {
|
|
1294
|
+
revisionToken: state.revisionToken,
|
|
1295
|
+
activeStoryKey,
|
|
1296
|
+
selection: state.selection,
|
|
1297
|
+
readOnly: state.readOnly,
|
|
1298
|
+
documentMode: viewState.documentMode,
|
|
1299
|
+
workflowOverlay,
|
|
1300
|
+
protectionSnapshot,
|
|
1301
|
+
warnings: state.warnings,
|
|
1302
|
+
fatalError: state.fatalError,
|
|
1303
|
+
snapshot,
|
|
1304
|
+
});
|
|
1305
|
+
return snapshot;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function getRuntimeContextAnalyticsQueryKey(query?: RuntimeContextAnalyticsQuery): string {
|
|
1309
|
+
return JSON.stringify({
|
|
1310
|
+
scopeKind: query?.scopeKind ?? "selection",
|
|
1311
|
+
scopeId: query?.scopeId ?? null,
|
|
1312
|
+
workItemId: query?.workItemId ?? null,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function getTrackedContextAnalyticsQueries(): Array<RuntimeContextAnalyticsQuery | undefined> {
|
|
1317
|
+
const queries: Array<RuntimeContextAnalyticsQuery | undefined> = [
|
|
1318
|
+
undefined,
|
|
1319
|
+
{ scopeKind: "document" },
|
|
1320
|
+
];
|
|
1321
|
+
const workflowScopeSnapshot = getCachedWorkflowScopeSnapshot();
|
|
1322
|
+
const seenScopeIds = new Set<string>();
|
|
1323
|
+
for (const scope of workflowScopeSnapshot?.scopes ?? []) {
|
|
1324
|
+
if (seenScopeIds.has(scope.scopeId)) {
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
seenScopeIds.add(scope.scopeId);
|
|
1328
|
+
queries.push({
|
|
1329
|
+
scopeKind: "workflow_scope",
|
|
1330
|
+
scopeId: scope.scopeId,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
const seenWorkItemIds = new Set<string>();
|
|
1334
|
+
for (const workItem of workflowOverlay?.workItems ?? []) {
|
|
1335
|
+
if (seenWorkItemIds.has(workItem.workItemId)) {
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
seenWorkItemIds.add(workItem.workItemId);
|
|
1339
|
+
queries.push({
|
|
1340
|
+
scopeKind: "work_item",
|
|
1341
|
+
workItemId: workItem.workItemId,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
const activeWorkItemId = workflowScopeSnapshot?.activeWorkItemId ?? null;
|
|
1345
|
+
if (activeWorkItemId && !seenWorkItemIds.has(activeWorkItemId)) {
|
|
1346
|
+
queries.push({
|
|
1347
|
+
scopeKind: "work_item",
|
|
1348
|
+
workItemId: activeWorkItemId,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return queries;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function collectTrackedContextAnalyticsSnapshots(): Map<string, RuntimeContextAnalyticsSnapshot | null> {
|
|
1355
|
+
const snapshots = new Map<string, RuntimeContextAnalyticsSnapshot | null>();
|
|
1356
|
+
for (const query of getTrackedContextAnalyticsQueries()) {
|
|
1357
|
+
snapshots.set(
|
|
1358
|
+
getRuntimeContextAnalyticsQueryKey(query),
|
|
1359
|
+
getCachedRuntimeContextAnalytics(query),
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
return snapshots;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function trackedContextAnalyticsSnapshotsEqual(
|
|
1366
|
+
left: Map<string, RuntimeContextAnalyticsSnapshot | null>,
|
|
1367
|
+
right: Map<string, RuntimeContextAnalyticsSnapshot | null>,
|
|
1368
|
+
): boolean {
|
|
1369
|
+
if (left.size !== right.size) {
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
for (const [key, snapshot] of left.entries()) {
|
|
1373
|
+
if (!right.has(key)) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
if (!runtimeContextAnalyticsSnapshotsEqual(snapshot, right.get(key) ?? null)) {
|
|
1377
|
+
return false;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return true;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
931
1383
|
function refreshRenderSnapshot(): RuntimeRenderSnapshot {
|
|
932
1384
|
const surface = getCachedSurface(state.document, activeStory);
|
|
933
1385
|
return {
|
|
@@ -1124,6 +1576,9 @@ export function createDocumentRuntime(
|
|
|
1124
1576
|
setDefaultAuthorId(authorId) {
|
|
1125
1577
|
defaultAuthorId = authorId;
|
|
1126
1578
|
},
|
|
1579
|
+
getDefaultAuthorId() {
|
|
1580
|
+
return defaultAuthorId;
|
|
1581
|
+
},
|
|
1127
1582
|
replaceText(text, target) {
|
|
1128
1583
|
try {
|
|
1129
1584
|
const timestamp = clock();
|
|
@@ -1151,7 +1606,18 @@ export function createDocumentRuntime(
|
|
|
1151
1606
|
},
|
|
1152
1607
|
addComment(params) {
|
|
1153
1608
|
if (viewState.documentMode === "viewing") {
|
|
1154
|
-
|
|
1609
|
+
const error: InternalEditorError = {
|
|
1610
|
+
errorId: createSessionId("comment-viewing-mode", clock()),
|
|
1611
|
+
code: "validation_failed",
|
|
1612
|
+
isFatal: false,
|
|
1613
|
+
message: "Cannot add comments in viewing mode.",
|
|
1614
|
+
source: "runtime",
|
|
1615
|
+
details: {
|
|
1616
|
+
reason: "viewing_mode",
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
1619
|
+
emitError(error);
|
|
1620
|
+
throw toStructuredRuntimeException(error);
|
|
1155
1621
|
}
|
|
1156
1622
|
const commentId = createEntityId("comment", state.document.review.comments, clock());
|
|
1157
1623
|
const anchor = params.anchor
|
|
@@ -1161,16 +1627,19 @@ export function createDocumentRuntime(
|
|
|
1161
1627
|
? createSelectionFromPublicAnchor(params.anchor)
|
|
1162
1628
|
: state.selection;
|
|
1163
1629
|
if (!canCreateDocxCommentAnchor(state.document.content, anchor)) {
|
|
1164
|
-
const
|
|
1165
|
-
"DOCX comments must use a non-empty range that stays within a single paragraph.";
|
|
1166
|
-
emitError({
|
|
1630
|
+
const error: InternalEditorError = {
|
|
1167
1631
|
errorId: createSessionId("comment-anchor", clock()),
|
|
1168
1632
|
code: "validation_failed",
|
|
1169
1633
|
isFatal: false,
|
|
1170
|
-
message
|
|
1634
|
+
message:
|
|
1635
|
+
"DOCX comments must use a non-empty range that stays within a single paragraph.",
|
|
1171
1636
|
source: "runtime",
|
|
1172
|
-
|
|
1173
|
-
|
|
1637
|
+
details: {
|
|
1638
|
+
reason: "invalid_comment_anchor",
|
|
1639
|
+
},
|
|
1640
|
+
};
|
|
1641
|
+
emitError(error);
|
|
1642
|
+
throw toStructuredRuntimeException(error);
|
|
1174
1643
|
}
|
|
1175
1644
|
const authorId = params.authorId ?? defaultAuthorId ?? "unknown";
|
|
1176
1645
|
const createdAt = clock();
|
|
@@ -1339,6 +1808,139 @@ export function createDocumentRuntime(
|
|
|
1339
1808
|
getDocumentNavigationSnapshot() {
|
|
1340
1809
|
return getCachedDocumentNavigationSnapshot(state, activeStory);
|
|
1341
1810
|
},
|
|
1811
|
+
getCurrentLocation() {
|
|
1812
|
+
const navigation = getCachedDocumentNavigationSnapshot(state, activeStory);
|
|
1813
|
+
return createCurrentLocation({
|
|
1814
|
+
document: state.document,
|
|
1815
|
+
renderSnapshot: cachedRenderSnapshot,
|
|
1816
|
+
navigation,
|
|
1817
|
+
sections: createDocumentSectionSnapshots(state.document, navigation),
|
|
1818
|
+
});
|
|
1819
|
+
},
|
|
1820
|
+
getLocationForSelection(selection: SelectionSnapshot) {
|
|
1821
|
+
const selectionStory = selection.storyTarget ?? activeStory;
|
|
1822
|
+
const navigation = getCachedDocumentNavigationSnapshot(state, selectionStory);
|
|
1823
|
+
return createLocationFromSelection({
|
|
1824
|
+
document: state.document,
|
|
1825
|
+
navigation,
|
|
1826
|
+
sections: createDocumentSectionSnapshots(state.document, navigation),
|
|
1827
|
+
selection,
|
|
1828
|
+
});
|
|
1829
|
+
},
|
|
1830
|
+
getLocationForAnchor(anchor: EditorAnchorProjection, storyTarget?: EditorStoryTarget) {
|
|
1831
|
+
const resolvedStory = storyTarget ?? activeStory;
|
|
1832
|
+
const navigation = getCachedDocumentNavigationSnapshot(state, resolvedStory);
|
|
1833
|
+
return createDocumentLocationSnapshot({
|
|
1834
|
+
document: state.document,
|
|
1835
|
+
navigation,
|
|
1836
|
+
sections: createDocumentSectionSnapshots(state.document, navigation),
|
|
1837
|
+
anchor,
|
|
1838
|
+
storyTarget: resolvedStory,
|
|
1839
|
+
source: { kind: "navigation" },
|
|
1840
|
+
});
|
|
1841
|
+
},
|
|
1842
|
+
captureRestorePoint(input?: SelectionSnapshot | EditorAnchorProjection) {
|
|
1843
|
+
const location =
|
|
1844
|
+
input === undefined
|
|
1845
|
+
? this.getCurrentLocation()
|
|
1846
|
+
: "activeRange" in input
|
|
1847
|
+
? this.getLocationForSelection(input)
|
|
1848
|
+
: this.getLocationForAnchor(input, activeStory);
|
|
1849
|
+
if (!location) {
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
return createRestorePoint({
|
|
1853
|
+
location,
|
|
1854
|
+
revisionToken: state.revisionToken,
|
|
1855
|
+
createdAt: clock(),
|
|
1856
|
+
checkpointType:
|
|
1857
|
+
input === undefined
|
|
1858
|
+
? "selection"
|
|
1859
|
+
: "activeRange" in input
|
|
1860
|
+
? "selection"
|
|
1861
|
+
: "manual",
|
|
1862
|
+
});
|
|
1863
|
+
},
|
|
1864
|
+
restoreToPoint(point, options) {
|
|
1865
|
+
if (point.location.anchor.kind === "detached") {
|
|
1866
|
+
return {
|
|
1867
|
+
status: "detached",
|
|
1868
|
+
reasons: [point.location.anchor.reason],
|
|
1869
|
+
location: point.location,
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const behavior = options?.behavior ?? "semantic";
|
|
1874
|
+
if (behavior === "exact" && point.revisionToken !== state.revisionToken) {
|
|
1875
|
+
return {
|
|
1876
|
+
status: "blocked",
|
|
1877
|
+
reasons: [
|
|
1878
|
+
"restore point no longer matches the current revision; use semantic replay after content changes",
|
|
1879
|
+
],
|
|
1880
|
+
location: point.location,
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (
|
|
1885
|
+
point.location.storyTarget &&
|
|
1886
|
+
!storyTargetsEqual(activeStory, point.location.storyTarget)
|
|
1887
|
+
) {
|
|
1888
|
+
if (point.location.storyTarget.kind === "main") {
|
|
1889
|
+
this.closeStory();
|
|
1890
|
+
} else {
|
|
1891
|
+
this.openStory(point.location.storyTarget);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
const selection = createSelectionFromPublicAnchor(point.location.anchor);
|
|
1895
|
+
this.dispatch({
|
|
1896
|
+
type: "selection.set",
|
|
1897
|
+
selection,
|
|
1898
|
+
origin: createOrigin("api", clock()),
|
|
1899
|
+
});
|
|
1900
|
+
const location = this.getCurrentLocation() ?? point.location;
|
|
1901
|
+
return {
|
|
1902
|
+
status: point.revisionToken === state.revisionToken ? "restored" : "remapped",
|
|
1903
|
+
selection: cachedRenderSnapshot.selection,
|
|
1904
|
+
location,
|
|
1905
|
+
};
|
|
1906
|
+
},
|
|
1907
|
+
getOutlineSnapshot() {
|
|
1908
|
+
return createDocumentOutlineSnapshot({
|
|
1909
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
1910
|
+
activeStory,
|
|
1911
|
+
selectionHead: state.selection.head,
|
|
1912
|
+
});
|
|
1913
|
+
},
|
|
1914
|
+
getTocSnapshot() {
|
|
1915
|
+
return createTocSnapshot(
|
|
1916
|
+
state.document,
|
|
1917
|
+
getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
1918
|
+
);
|
|
1919
|
+
},
|
|
1920
|
+
getSections() {
|
|
1921
|
+
return createDocumentSectionSnapshots(
|
|
1922
|
+
state.document,
|
|
1923
|
+
getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
1924
|
+
);
|
|
1925
|
+
},
|
|
1926
|
+
getSectionSnapshot(input) {
|
|
1927
|
+
const navigation = getCachedDocumentNavigationSnapshot(state, activeStory);
|
|
1928
|
+
const sections = createDocumentSectionSnapshots(state.document, navigation);
|
|
1929
|
+
if (input.headingId) {
|
|
1930
|
+
const heading = navigation.headings.find((entry) => entry.headingId === input.headingId);
|
|
1931
|
+
if (heading) {
|
|
1932
|
+
return sections.find((section) => section.sectionIndex === heading.sectionIndex) ?? null;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return findDocumentSectionSnapshot(
|
|
1936
|
+
sections,
|
|
1937
|
+
input,
|
|
1938
|
+
createSectionLocations(state.document, sections),
|
|
1939
|
+
);
|
|
1940
|
+
},
|
|
1941
|
+
describeEventImpact(event) {
|
|
1942
|
+
return describeEventImpact(event);
|
|
1943
|
+
},
|
|
1342
1944
|
getFieldSnapshot() {
|
|
1343
1945
|
return buildFieldSnapshot(state.document);
|
|
1344
1946
|
},
|
|
@@ -1386,12 +1988,15 @@ export function createDocumentRuntime(
|
|
|
1386
1988
|
getSessionState() {
|
|
1387
1989
|
const compatibility = createDerivedCompatibility(state);
|
|
1388
1990
|
return editorSessionStateFromPersistedSnapshot(
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1991
|
+
{
|
|
1992
|
+
...(createPersistedEditorSnapshot(state, {
|
|
1993
|
+
editorBuild,
|
|
1994
|
+
savedAt: clock(),
|
|
1995
|
+
compatibility,
|
|
1996
|
+
protectionSnapshot,
|
|
1997
|
+
}) as unknown as PersistedEditorSnapshot),
|
|
1998
|
+
workflowMetadata: deriveWorkflowMetadataSnapshot(),
|
|
1999
|
+
},
|
|
1395
2000
|
);
|
|
1396
2001
|
},
|
|
1397
2002
|
getPersistedSnapshot() {
|
|
@@ -1405,6 +2010,9 @@ export function createDocumentRuntime(
|
|
|
1405
2010
|
getWarnings() {
|
|
1406
2011
|
return state.warnings.map((warning) => toPublicWarning(warning));
|
|
1407
2012
|
},
|
|
2013
|
+
getSuggestionsSnapshot() {
|
|
2014
|
+
return getCachedSuggestionsSnapshot(state);
|
|
2015
|
+
},
|
|
1408
2016
|
async exportDocx(exportOptions) {
|
|
1409
2017
|
if (!options.exportDocx) {
|
|
1410
2018
|
const error: InternalEditorError = {
|
|
@@ -1418,7 +2026,7 @@ export function createDocumentRuntime(
|
|
|
1418
2026
|
},
|
|
1419
2027
|
};
|
|
1420
2028
|
emitError(error);
|
|
1421
|
-
throw
|
|
2029
|
+
throw toStructuredRuntimeException(error);
|
|
1422
2030
|
}
|
|
1423
2031
|
|
|
1424
2032
|
const result = await options.exportDocx(this.getSessionState(), exportOptions);
|
|
@@ -1483,6 +2091,57 @@ export function createDocumentRuntime(
|
|
|
1483
2091
|
getWorkflowMarkupSnapshot() {
|
|
1484
2092
|
return getCachedWorkflowMarkupSnapshot();
|
|
1485
2093
|
},
|
|
2094
|
+
setWorkflowMetadataDefinitions(definitions) {
|
|
2095
|
+
workflowMetadataDefinitions = structuredClone(definitions);
|
|
2096
|
+
const snapshot = deriveWorkflowMetadataSnapshot();
|
|
2097
|
+
emit({
|
|
2098
|
+
type: "workflow_metadata_changed",
|
|
2099
|
+
documentId: state.documentId,
|
|
2100
|
+
snapshot,
|
|
2101
|
+
});
|
|
2102
|
+
for (const listener of listeners) {
|
|
2103
|
+
listener();
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
clearWorkflowMetadataDefinitions() {
|
|
2107
|
+
workflowMetadataDefinitions = [];
|
|
2108
|
+
const snapshot = deriveWorkflowMetadataSnapshot();
|
|
2109
|
+
emit({
|
|
2110
|
+
type: "workflow_metadata_changed",
|
|
2111
|
+
documentId: state.documentId,
|
|
2112
|
+
snapshot,
|
|
2113
|
+
});
|
|
2114
|
+
for (const listener of listeners) {
|
|
2115
|
+
listener();
|
|
2116
|
+
}
|
|
2117
|
+
},
|
|
2118
|
+
setWorkflowMetadataEntries(entries) {
|
|
2119
|
+
workflowMetadataEntries = structuredClone(entries);
|
|
2120
|
+
const snapshot = deriveWorkflowMetadataSnapshot();
|
|
2121
|
+
emit({
|
|
2122
|
+
type: "workflow_metadata_changed",
|
|
2123
|
+
documentId: state.documentId,
|
|
2124
|
+
snapshot,
|
|
2125
|
+
});
|
|
2126
|
+
for (const listener of listeners) {
|
|
2127
|
+
listener();
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
clearWorkflowMetadataEntries() {
|
|
2131
|
+
workflowMetadataEntries = [];
|
|
2132
|
+
const snapshot = deriveWorkflowMetadataSnapshot();
|
|
2133
|
+
emit({
|
|
2134
|
+
type: "workflow_metadata_changed",
|
|
2135
|
+
documentId: state.documentId,
|
|
2136
|
+
snapshot,
|
|
2137
|
+
});
|
|
2138
|
+
for (const listener of listeners) {
|
|
2139
|
+
listener();
|
|
2140
|
+
}
|
|
2141
|
+
},
|
|
2142
|
+
getWorkflowMetadataSnapshot() {
|
|
2143
|
+
return deriveWorkflowMetadataSnapshot();
|
|
2144
|
+
},
|
|
1486
2145
|
setHostAnnotationOverlay(overlay) {
|
|
1487
2146
|
hostAnnotationOverlay = structuredClone(overlay);
|
|
1488
2147
|
emit({
|
|
@@ -1528,6 +2187,43 @@ export function createDocumentRuntime(
|
|
|
1528
2187
|
}
|
|
1529
2188
|
this.replaceText(text, target.anchor);
|
|
1530
2189
|
},
|
|
2190
|
+
getDocumentTextStream() {
|
|
2191
|
+
return cachedRenderSnapshot.surface
|
|
2192
|
+
? createDocumentTextStreamSnapshots({
|
|
2193
|
+
surface: cachedRenderSnapshot.surface,
|
|
2194
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
2195
|
+
})
|
|
2196
|
+
: [];
|
|
2197
|
+
},
|
|
2198
|
+
getStoryTextStream(target: EditorStoryTarget) {
|
|
2199
|
+
return this.getDocumentTextStream().find((stream) => storyTargetsEqual(stream.target, target)) ?? null;
|
|
2200
|
+
},
|
|
2201
|
+
getDocumentChunks() {
|
|
2202
|
+
return createDocumentChunks({
|
|
2203
|
+
document: state.document,
|
|
2204
|
+
renderSnapshot: cachedRenderSnapshot,
|
|
2205
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
2206
|
+
});
|
|
2207
|
+
},
|
|
2208
|
+
getWorkflowChunks() {
|
|
2209
|
+
return createWorkflowChunks({
|
|
2210
|
+
document: state.document,
|
|
2211
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
2212
|
+
workflowMarkup: getCachedWorkflowMarkupSnapshot(),
|
|
2213
|
+
});
|
|
2214
|
+
},
|
|
2215
|
+
getReviewWorkSnapshot() {
|
|
2216
|
+
return createReviewWorkSnapshot({
|
|
2217
|
+
comments: cachedRenderSnapshot.comments,
|
|
2218
|
+
trackedChanges: cachedRenderSnapshot.trackedChanges,
|
|
2219
|
+
workflowMarkup: getCachedWorkflowMarkupSnapshot(),
|
|
2220
|
+
document: state.document,
|
|
2221
|
+
navigation: getCachedDocumentNavigationSnapshot(state, activeStory),
|
|
2222
|
+
});
|
|
2223
|
+
},
|
|
2224
|
+
getRuntimeContextAnalytics(query) {
|
|
2225
|
+
return getCachedRuntimeContextAnalytics(query);
|
|
2226
|
+
},
|
|
1531
2227
|
};
|
|
1532
2228
|
|
|
1533
2229
|
function applyHistory(direction: "undo" | "redo"): void {
|
|
@@ -1579,6 +2275,7 @@ export function createDocumentRuntime(
|
|
|
1579
2275
|
next: EditorState,
|
|
1580
2276
|
transaction: EditorTransaction,
|
|
1581
2277
|
): void {
|
|
2278
|
+
const emittedSuggestionIds = new Set<string>();
|
|
1582
2279
|
if (previous.isDirty !== next.isDirty) {
|
|
1583
2280
|
emit({
|
|
1584
2281
|
type: "dirty_changed",
|
|
@@ -1618,6 +2315,22 @@ export function createDocumentRuntime(
|
|
|
1618
2315
|
documentId: next.documentId,
|
|
1619
2316
|
changeId: transaction.effects.changeAccepted.changeId,
|
|
1620
2317
|
});
|
|
2318
|
+
const updatedSuggestion = findSuggestionByChangeId(
|
|
2319
|
+
getCachedSuggestionsSnapshot(next),
|
|
2320
|
+
transaction.effects.changeAccepted.changeId,
|
|
2321
|
+
);
|
|
2322
|
+
if (updatedSuggestion) {
|
|
2323
|
+
emit({
|
|
2324
|
+
type: "suggestion_updated",
|
|
2325
|
+
documentId: next.documentId,
|
|
2326
|
+
suggestionId: updatedSuggestion.suggestionId,
|
|
2327
|
+
changeIds: updatedSuggestion.changeIds,
|
|
2328
|
+
suggestionKind: updatedSuggestion.kind,
|
|
2329
|
+
status: updatedSuggestion.status,
|
|
2330
|
+
storyTarget: updatedSuggestion.storyTarget,
|
|
2331
|
+
anchor: updatedSuggestion.anchor,
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
1621
2334
|
}
|
|
1622
2335
|
|
|
1623
2336
|
if (transaction.effects.changeRejected) {
|
|
@@ -1626,6 +2339,22 @@ export function createDocumentRuntime(
|
|
|
1626
2339
|
documentId: next.documentId,
|
|
1627
2340
|
changeId: transaction.effects.changeRejected.changeId,
|
|
1628
2341
|
});
|
|
2342
|
+
const updatedSuggestion = findSuggestionByChangeId(
|
|
2343
|
+
getCachedSuggestionsSnapshot(next),
|
|
2344
|
+
transaction.effects.changeRejected.changeId,
|
|
2345
|
+
);
|
|
2346
|
+
if (updatedSuggestion) {
|
|
2347
|
+
emit({
|
|
2348
|
+
type: "suggestion_updated",
|
|
2349
|
+
documentId: next.documentId,
|
|
2350
|
+
suggestionId: updatedSuggestion.suggestionId,
|
|
2351
|
+
changeIds: updatedSuggestion.changeIds,
|
|
2352
|
+
suggestionKind: updatedSuggestion.kind,
|
|
2353
|
+
status: updatedSuggestion.status,
|
|
2354
|
+
storyTarget: updatedSuggestion.storyTarget,
|
|
2355
|
+
anchor: updatedSuggestion.anchor,
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
1629
2358
|
}
|
|
1630
2359
|
|
|
1631
2360
|
if (transaction.effects.revisionAuthored) {
|
|
@@ -1635,6 +2364,47 @@ export function createDocumentRuntime(
|
|
|
1635
2364
|
changeId: transaction.effects.revisionAuthored.changeId,
|
|
1636
2365
|
kind: transaction.effects.revisionAuthored.kind,
|
|
1637
2366
|
});
|
|
2367
|
+
const authoredSuggestion = findSuggestionByChangeId(
|
|
2368
|
+
getCachedSuggestionsSnapshot(next),
|
|
2369
|
+
transaction.effects.revisionAuthored.changeId,
|
|
2370
|
+
);
|
|
2371
|
+
if (authoredSuggestion) {
|
|
2372
|
+
emittedSuggestionIds.add(authoredSuggestion.suggestionId);
|
|
2373
|
+
emit({
|
|
2374
|
+
type: "suggestion_authored",
|
|
2375
|
+
documentId: next.documentId,
|
|
2376
|
+
suggestionId: authoredSuggestion.suggestionId,
|
|
2377
|
+
changeIds: authoredSuggestion.changeIds,
|
|
2378
|
+
suggestionKind: authoredSuggestion.kind,
|
|
2379
|
+
storyTarget: authoredSuggestion.storyTarget,
|
|
2380
|
+
anchor: authoredSuggestion.anchor,
|
|
2381
|
+
isReplacement: authoredSuggestion.isReplacement,
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
const addedRevisionIds = Object.keys(next.document.review.revisions).filter(
|
|
2387
|
+
(revisionId) => previous.document.review.revisions[revisionId] === undefined,
|
|
2388
|
+
);
|
|
2389
|
+
if (addedRevisionIds.length > 0) {
|
|
2390
|
+
const suggestionsSnapshot = getCachedSuggestionsSnapshot(next);
|
|
2391
|
+
for (const revisionId of addedRevisionIds) {
|
|
2392
|
+
const authoredSuggestion = findSuggestionByChangeId(suggestionsSnapshot, revisionId);
|
|
2393
|
+
if (!authoredSuggestion || emittedSuggestionIds.has(authoredSuggestion.suggestionId)) {
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
emittedSuggestionIds.add(authoredSuggestion.suggestionId);
|
|
2397
|
+
emit({
|
|
2398
|
+
type: "suggestion_authored",
|
|
2399
|
+
documentId: next.documentId,
|
|
2400
|
+
suggestionId: authoredSuggestion.suggestionId,
|
|
2401
|
+
changeIds: authoredSuggestion.changeIds,
|
|
2402
|
+
suggestionKind: authoredSuggestion.kind,
|
|
2403
|
+
storyTarget: authoredSuggestion.storyTarget,
|
|
2404
|
+
anchor: authoredSuggestion.anchor,
|
|
2405
|
+
isReplacement: authoredSuggestion.isReplacement,
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
1638
2408
|
}
|
|
1639
2409
|
|
|
1640
2410
|
if (transaction.effects.commandBlocked) {
|
|
@@ -1681,6 +2451,23 @@ export function createDocumentRuntime(
|
|
|
1681
2451
|
} = {},
|
|
1682
2452
|
): void {
|
|
1683
2453
|
const selection = options.selection ?? state.selection;
|
|
2454
|
+
if (
|
|
2455
|
+
activeStory.kind !== "main" &&
|
|
2456
|
+
getEffectiveDocumentMode(selection) === "suggesting" &&
|
|
2457
|
+
command.type === "paragraph.split"
|
|
2458
|
+
) {
|
|
2459
|
+
emit({
|
|
2460
|
+
type: "command_blocked",
|
|
2461
|
+
documentId: state.documentId,
|
|
2462
|
+
command: options.blockedCommandName ?? command.type,
|
|
2463
|
+
reasons: [{
|
|
2464
|
+
code: "suggesting_unsupported",
|
|
2465
|
+
message: `"${command.type}" is not supported in suggesting mode for this story.`,
|
|
2466
|
+
storyTarget: activeStory,
|
|
2467
|
+
}],
|
|
2468
|
+
});
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
1684
2471
|
const blockedReasons = evaluateWorkflowBlockedReasons(selection, command.type);
|
|
1685
2472
|
if (blockedReasons.length > 0) {
|
|
1686
2473
|
emit({
|
|
@@ -1832,12 +2619,75 @@ export function createDocumentRuntime(
|
|
|
1832
2619
|
}
|
|
1833
2620
|
|
|
1834
2621
|
function emit(event: DocumentRuntimeEvent): void {
|
|
2622
|
+
emitInternal(event);
|
|
2623
|
+
if (shouldEmitContextAnalyticsChanged(event)) {
|
|
2624
|
+
emitContextAnalyticsChanged();
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
function emitInternal(event: DocumentRuntimeEvent): void {
|
|
1835
2629
|
options.onEvent?.(event);
|
|
1836
2630
|
for (const listener of eventListeners) {
|
|
1837
2631
|
listener(event);
|
|
1838
2632
|
}
|
|
1839
2633
|
}
|
|
1840
2634
|
|
|
2635
|
+
function shouldEmitContextAnalyticsChanged(event: DocumentRuntimeEvent): boolean {
|
|
2636
|
+
switch (event.type) {
|
|
2637
|
+
case "selection_changed":
|
|
2638
|
+
case "story_changed":
|
|
2639
|
+
case "workflow_overlay_changed":
|
|
2640
|
+
case "workflow_active_work_item_changed":
|
|
2641
|
+
case "change_authored":
|
|
2642
|
+
case "change_accepted":
|
|
2643
|
+
case "change_rejected":
|
|
2644
|
+
case "comment_added":
|
|
2645
|
+
case "comment_resolved":
|
|
2646
|
+
case "warning_added":
|
|
2647
|
+
case "warning_cleared":
|
|
2648
|
+
case "error":
|
|
2649
|
+
case "ready":
|
|
2650
|
+
case "command_blocked":
|
|
2651
|
+
case "suggestion_authored":
|
|
2652
|
+
case "suggestion_updated":
|
|
2653
|
+
return true;
|
|
2654
|
+
case "context_analytics_changed":
|
|
2655
|
+
case "dirty_changed":
|
|
2656
|
+
case "autosave_state":
|
|
2657
|
+
case "snapshot_saved":
|
|
2658
|
+
case "session_saved":
|
|
2659
|
+
case "export_completed":
|
|
2660
|
+
case "host_annotation_overlay_changed":
|
|
2661
|
+
return false;
|
|
2662
|
+
default:
|
|
2663
|
+
return false;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function emitContextAnalyticsChanged(): void {
|
|
2668
|
+
const trackedSnapshots = collectTrackedContextAnalyticsSnapshots();
|
|
2669
|
+
if (
|
|
2670
|
+
lastEmittedContextAnalyticsSnapshots !== undefined &&
|
|
2671
|
+
trackedContextAnalyticsSnapshotsEqual(lastEmittedContextAnalyticsSnapshots, trackedSnapshots)
|
|
2672
|
+
) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
lastEmittedContextAnalyticsSnapshots = trackedSnapshots;
|
|
2676
|
+
emitInternal({
|
|
2677
|
+
type: "context_analytics_changed",
|
|
2678
|
+
documentId: state.documentId,
|
|
2679
|
+
snapshot:
|
|
2680
|
+
trackedSnapshots.get(
|
|
2681
|
+
getRuntimeContextAnalyticsQueryKey(
|
|
2682
|
+
resolveCurrentContextAnalyticsQuery({
|
|
2683
|
+
workflowScopeSnapshot: getCachedWorkflowScopeSnapshot(),
|
|
2684
|
+
interactionGuardSnapshot: getCachedInteractionGuardSnapshot(),
|
|
2685
|
+
}),
|
|
2686
|
+
),
|
|
2687
|
+
) ?? null,
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
|
|
1841
2691
|
function emitError(error: InternalEditorError): void {
|
|
1842
2692
|
const nextState: EditorState = {
|
|
1843
2693
|
...state,
|
|
@@ -1949,6 +2799,30 @@ function finalizeState(
|
|
|
1949
2799
|
|
|
1950
2800
|
function toRuntimeError(error: unknown): InternalEditorError {
|
|
1951
2801
|
if (typeof error === "object" && error && "message" in error) {
|
|
2802
|
+
if (
|
|
2803
|
+
"code" in error
|
|
2804
|
+
&& "source" in error
|
|
2805
|
+
&& "isFatal" in error
|
|
2806
|
+
&& typeof (error as { code?: unknown }).code === "string"
|
|
2807
|
+
&& typeof (error as { source?: unknown }).source === "string"
|
|
2808
|
+
&& typeof (error as { isFatal?: unknown }).isFatal === "boolean"
|
|
2809
|
+
) {
|
|
2810
|
+
return {
|
|
2811
|
+
errorId:
|
|
2812
|
+
typeof (error as { errorId?: unknown }).errorId === "string"
|
|
2813
|
+
? String((error as { errorId?: unknown }).errorId)
|
|
2814
|
+
: createSessionId("runtime-error", new Date().toISOString()),
|
|
2815
|
+
code: (error as { code: InternalEditorError["code"] }).code,
|
|
2816
|
+
isFatal: (error as { isFatal: boolean }).isFatal,
|
|
2817
|
+
message: String((error as { message?: unknown }).message ?? "Runtime error"),
|
|
2818
|
+
source: (error as { source: InternalEditorError["source"] }).source,
|
|
2819
|
+
details:
|
|
2820
|
+
typeof (error as { details?: unknown }).details === "object"
|
|
2821
|
+
&& (error as { details?: unknown }).details !== null
|
|
2822
|
+
? ((error as { details?: Record<string, unknown> }).details)
|
|
2823
|
+
: undefined,
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
1952
2826
|
return {
|
|
1953
2827
|
errorId: createSessionId("runtime-error", new Date().toISOString()),
|
|
1954
2828
|
code: "internal_invariant",
|
|
@@ -1967,6 +2841,12 @@ function toRuntimeError(error: unknown): InternalEditorError {
|
|
|
1967
2841
|
};
|
|
1968
2842
|
}
|
|
1969
2843
|
|
|
2844
|
+
function toStructuredRuntimeException<T extends InternalEditorError>(
|
|
2845
|
+
error: T,
|
|
2846
|
+
): Error & T {
|
|
2847
|
+
return Object.assign(new Error(error.message), error);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
1970
2850
|
function toPublicDocumentStats(state: Pick<EditorState, "document">) {
|
|
1971
2851
|
const stats = deriveDocumentStats(state);
|
|
1972
2852
|
return {
|
|
@@ -2150,6 +3030,8 @@ function toPublicCommentSidebarSnapshot(
|
|
|
2150
3030
|
createdBy: thread.createdBy,
|
|
2151
3031
|
warningCount: thread.warningCount,
|
|
2152
3032
|
anchorLabel: thread.anchorLabel,
|
|
3033
|
+
detachedReason: sourceThread?.metadata?.detachedReason,
|
|
3034
|
+
actionabilityNote: sourceThread?.metadata?.actionabilityNote,
|
|
2153
3035
|
isActive: thread.isActive,
|
|
2154
3036
|
resolvedAt: thread.resolvedAt,
|
|
2155
3037
|
resolvedBy: thread.resolvedBy,
|
|
@@ -2188,6 +3070,11 @@ function toPublicTrackedChangesSnapshot(
|
|
|
2188
3070
|
return {
|
|
2189
3071
|
revisionId: revision.revisionId,
|
|
2190
3072
|
kind: revision.kind,
|
|
3073
|
+
source: sourceRevision?.metadata?.source ?? "runtime",
|
|
3074
|
+
suggestionId: sourceRevision?.metadata?.suggestionId,
|
|
3075
|
+
semanticKind: sourceRevision?.metadata?.semanticKind,
|
|
3076
|
+
linkedRevisionIds: sourceRevision?.metadata?.linkedRevisionIds,
|
|
3077
|
+
predecessorSuggestionId: sourceRevision?.metadata?.predecessorSuggestionId,
|
|
2191
3078
|
label: revision.label,
|
|
2192
3079
|
status: revision.status,
|
|
2193
3080
|
actionability: revision.actionability,
|
|
@@ -2234,6 +3121,10 @@ function createRevisionStoreFromDocument(
|
|
|
2234
3121
|
source: revision.metadata?.source ?? "runtime",
|
|
2235
3122
|
storyTarget: revision.metadata?.storyTarget,
|
|
2236
3123
|
preserveOnlyReason: revision.metadata?.preserveOnlyReason,
|
|
3124
|
+
suggestionId: revision.metadata?.suggestionId,
|
|
3125
|
+
semanticKind: revision.metadata?.semanticKind,
|
|
3126
|
+
linkedRevisionIds: revision.metadata?.linkedRevisionIds,
|
|
3127
|
+
predecessorSuggestionId: revision.metadata?.predecessorSuggestionId,
|
|
2237
3128
|
importedRevisionForm: revision.metadata?.importedRevisionForm,
|
|
2238
3129
|
originalRevisionType: revision.metadata?.originalRevisionType,
|
|
2239
3130
|
ooxmlRevisionId: revision.metadata?.ooxmlRevisionId,
|
|
@@ -2251,6 +3142,13 @@ function getRevisionStoryTarget(
|
|
|
2251
3142
|
return storyTarget ? { ...storyTarget } : MAIN_STORY_TARGET;
|
|
2252
3143
|
}
|
|
2253
3144
|
|
|
3145
|
+
function findSuggestionByChangeId(
|
|
3146
|
+
snapshot: SuggestionsSnapshot,
|
|
3147
|
+
changeId: string,
|
|
3148
|
+
) {
|
|
3149
|
+
return snapshot.suggestions.find((suggestion) => suggestion.changeIds.includes(changeId));
|
|
3150
|
+
}
|
|
3151
|
+
|
|
2254
3152
|
function createSecondaryStoryLocalReviewState(
|
|
2255
3153
|
review: EditorState["document"]["review"],
|
|
2256
3154
|
storyTarget: EditorStoryTarget,
|
|
@@ -2451,11 +3349,9 @@ const NON_MUTATION_COMMANDS = new Set([
|
|
|
2451
3349
|
]);
|
|
2452
3350
|
|
|
2453
3351
|
/** Mutation commands that are not yet supported in suggesting mode. */
|
|
2454
|
-
const SUGGESTING_UNSUPPORTED_COMMANDS = new Set([
|
|
2455
|
-
"paragraph.split",
|
|
2456
|
-
]);
|
|
3352
|
+
const SUGGESTING_UNSUPPORTED_COMMANDS = new Set<string>([]);
|
|
2457
3353
|
|
|
2458
|
-
const SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS = new Set([
|
|
3354
|
+
const SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS = new Set<string>([
|
|
2459
3355
|
"text.insert",
|
|
2460
3356
|
"text.delete-backward",
|
|
2461
3357
|
"text.delete-forward",
|