@beyondwork/docx-react-component 1.0.41 → 1.0.42

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.
Files changed (88) hide show
  1. package/package.json +13 -1
  2. package/src/api/awareness-identity-types.ts +35 -0
  3. package/src/api/comment-negotiation-types.ts +130 -0
  4. package/src/api/comment-presentation-types.ts +106 -0
  5. package/src/api/external-custody-types.ts +74 -0
  6. package/src/api/participants-types.ts +18 -0
  7. package/src/api/public-types.ts +347 -4
  8. package/src/api/scope-metadata-resolver-types.ts +88 -0
  9. package/src/core/commands/formatting-commands.ts +1 -1
  10. package/src/core/commands/index.ts +568 -1
  11. package/src/index.ts +118 -1
  12. package/src/io/export/escape-xml-attribute.ts +26 -0
  13. package/src/io/export/external-send.ts +188 -0
  14. package/src/io/export/serialize-comments.ts +13 -16
  15. package/src/io/export/serialize-footnotes.ts +17 -24
  16. package/src/io/export/serialize-headers-footers.ts +17 -24
  17. package/src/io/export/serialize-main-document.ts +59 -62
  18. package/src/io/export/serialize-numbering.ts +20 -27
  19. package/src/io/export/serialize-runtime-revisions.ts +2 -9
  20. package/src/io/export/serialize-tables.ts +8 -15
  21. package/src/io/export/table-properties-xml.ts +25 -32
  22. package/src/io/import/external-reimport.ts +40 -0
  23. package/src/io/ooxml/bw-xml.ts +244 -0
  24. package/src/io/ooxml/canonicalize-payload.ts +301 -0
  25. package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
  26. package/src/io/ooxml/comment-presentation-payload.ts +311 -0
  27. package/src/io/ooxml/external-custody-payload.ts +102 -0
  28. package/src/io/ooxml/participants-payload.ts +97 -0
  29. package/src/io/ooxml/payload-signature.ts +112 -0
  30. package/src/io/ooxml/workflow-payload-validator.ts +271 -0
  31. package/src/io/ooxml/workflow-payload.ts +146 -7
  32. package/src/runtime/awareness-identity.ts +173 -0
  33. package/src/runtime/collab/event-types.ts +27 -0
  34. package/src/runtime/collab-session-bridge.ts +157 -0
  35. package/src/runtime/collab-session-facet.ts +193 -0
  36. package/src/runtime/collab-session.ts +273 -0
  37. package/src/runtime/comment-negotiation-sync.ts +91 -0
  38. package/src/runtime/comment-negotiation.ts +158 -0
  39. package/src/runtime/comment-presentation.ts +223 -0
  40. package/src/runtime/document-runtime.ts +280 -93
  41. package/src/runtime/external-send-runtime.ts +117 -0
  42. package/src/runtime/layout/docx-font-loader.ts +11 -30
  43. package/src/runtime/layout/inert-layout-facet.ts +2 -0
  44. package/src/runtime/layout/layout-engine-instance.ts +122 -12
  45. package/src/runtime/layout/page-graph.ts +79 -7
  46. package/src/runtime/layout/paginated-layout-engine.ts +230 -34
  47. package/src/runtime/layout/public-facet.ts +185 -13
  48. package/src/runtime/layout/table-row-split.ts +316 -0
  49. package/src/runtime/markdown-sanitizer.ts +132 -0
  50. package/src/runtime/participants.ts +134 -0
  51. package/src/runtime/resign-payload.ts +120 -0
  52. package/src/runtime/tamper-gate.ts +157 -0
  53. package/src/runtime/workflow-markup.ts +9 -0
  54. package/src/runtime/workflow-rail-segments.ts +244 -5
  55. package/src/ui/WordReviewEditor.tsx +587 -0
  56. package/src/ui/editor-runtime-boundary.ts +1 -0
  57. package/src/ui/editor-shell-view.tsx +11 -0
  58. package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
  59. package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
  60. package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
  61. package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
  62. package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
  63. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
  64. package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
  65. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
  66. package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
  67. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
  68. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
  69. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
  70. package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
  71. package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
  72. package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
  73. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +58 -0
  74. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
  75. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
  76. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
  77. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
  78. package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
  79. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
  80. package/src/ui-tailwind/editor-surface/pm-schema.ts +15 -13
  81. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
  82. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +15 -14
  83. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
  84. package/src/ui-tailwind/index.ts +32 -0
  85. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
  86. package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
  87. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  88. package/src/ui-tailwind/tw-review-workspace.tsx +293 -34
@@ -38,6 +38,52 @@ import { remapCommentThreads } from "../../review/store/comment-remapping.ts";
38
38
  import { collectScopeTagTouches } from "../../review/store/scope-tag-diff.ts";
39
39
  import { applyRevisionRuntimeCommand } from "../../runtime/revision-runtime.ts";
40
40
  import type { RevisionStore } from "../../review/store/revision-store.ts";
41
+ import type {
42
+ HeaderFooterLinkPatch,
43
+ HostAnnotationOverlay,
44
+ InsertTableOptions,
45
+ RuntimeRenderSnapshot,
46
+ SectionBreakType,
47
+ SectionLayoutPatch,
48
+ SectionPageNumberingPatch,
49
+ WorkflowMetadataDefinition,
50
+ WorkflowMetadataEntry,
51
+ WorkflowOverlay,
52
+ } from "../../api/public-types.ts";
53
+ import {
54
+ applyFormattingOperationToDocument,
55
+ type FormattingOperation,
56
+ } from "./formatting-commands.ts";
57
+ import {
58
+ applyParagraphStyleToDocument,
59
+ applyTableStyleToDocument,
60
+ } from "./style-commands.ts";
61
+ import {
62
+ continueNumbering,
63
+ indentListItems,
64
+ outdentListItems,
65
+ restartNumbering,
66
+ toggleBulletedList,
67
+ toggleNumberedList,
68
+ } from "./list-commands.ts";
69
+ import {
70
+ applyTableStructureOperation,
71
+ type TableStructureOperation,
72
+ } from "./table-structure-commands.ts";
73
+ import {
74
+ insertImage,
75
+ repositionFloatingImage,
76
+ resizeImage,
77
+ } from "./image-commands.ts";
78
+ import {
79
+ insertSectionBreakAfterSectionIndex,
80
+ deleteSectionBreakAtSectionIndex,
81
+ updateSectionLayoutAtSectionIndex,
82
+ setSectionPageNumberingAtSectionIndex,
83
+ setHeaderFooterLinkAtSectionIndex,
84
+ } from "./section-layout-commands.ts";
85
+ import { insertPageBreak, insertTable } from "./text-commands.ts";
86
+ import type { TableSelectionDescriptor } from "../../runtime/table-commands.ts";
41
87
 
42
88
  export interface CommandOrigin {
43
89
  source:
@@ -170,6 +216,149 @@ export type EditorCommand =
170
216
  | {
171
217
  type: "history.redo";
172
218
  origin?: CommandOrigin;
219
+ }
220
+ | {
221
+ type: "workflow.set-overlay";
222
+ overlay: WorkflowOverlay;
223
+ origin?: CommandOrigin;
224
+ }
225
+ | {
226
+ type: "workflow.clear-overlay";
227
+ origin?: CommandOrigin;
228
+ }
229
+ | {
230
+ type: "workflow.set-metadata-definitions";
231
+ definitions: WorkflowMetadataDefinition[];
232
+ origin?: CommandOrigin;
233
+ }
234
+ | {
235
+ type: "workflow.clear-metadata-definitions";
236
+ origin?: CommandOrigin;
237
+ }
238
+ | {
239
+ type: "workflow.set-metadata-entries";
240
+ entries: WorkflowMetadataEntry[];
241
+ origin?: CommandOrigin;
242
+ }
243
+ | {
244
+ type: "workflow.clear-metadata-entries";
245
+ origin?: CommandOrigin;
246
+ }
247
+ | {
248
+ type: "host-annotation.set-overlay";
249
+ overlay: HostAnnotationOverlay;
250
+ origin?: CommandOrigin;
251
+ }
252
+ | {
253
+ type: "host-annotation.clear-overlay";
254
+ origin?: CommandOrigin;
255
+ }
256
+ | {
257
+ type: "formatting.apply";
258
+ operation: FormattingOperation;
259
+ origin?: CommandOrigin;
260
+ }
261
+ | {
262
+ type: "style.set-paragraph";
263
+ styleId: string | null;
264
+ origin?: CommandOrigin;
265
+ }
266
+ | {
267
+ type: "style.set-table";
268
+ styleId: string | null;
269
+ origin?: CommandOrigin;
270
+ }
271
+ | {
272
+ type: "list.toggle";
273
+ kind: "bulleted" | "numbered";
274
+ paragraphIndexes: readonly number[];
275
+ origin?: CommandOrigin;
276
+ }
277
+ | {
278
+ type: "list.indent";
279
+ paragraphIndexes: readonly number[];
280
+ origin?: CommandOrigin;
281
+ }
282
+ | {
283
+ type: "list.outdent";
284
+ paragraphIndexes: readonly number[];
285
+ origin?: CommandOrigin;
286
+ }
287
+ | {
288
+ type: "list.restart-numbering";
289
+ paragraphIndex: number;
290
+ startAt?: number;
291
+ origin?: CommandOrigin;
292
+ }
293
+ | {
294
+ type: "list.continue-numbering";
295
+ paragraphIndex: number;
296
+ origin?: CommandOrigin;
297
+ }
298
+ | {
299
+ type: "table.apply-structure";
300
+ operation: TableStructureOperation;
301
+ selectionDescriptor: TableSelectionDescriptor | null;
302
+ origin?: CommandOrigin;
303
+ }
304
+ | {
305
+ type: "image.insert";
306
+ data: string;
307
+ mimeType: string;
308
+ width?: number;
309
+ height?: number;
310
+ altText?: string;
311
+ origin?: CommandOrigin;
312
+ }
313
+ | {
314
+ type: "image.set-layout";
315
+ mediaId: string;
316
+ dimensions: { widthEmu: number; heightEmu: number };
317
+ origin?: CommandOrigin;
318
+ }
319
+ | {
320
+ type: "image.set-frame";
321
+ mediaId: string;
322
+ offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number };
323
+ origin?: CommandOrigin;
324
+ }
325
+ | {
326
+ type: "section.insert-break";
327
+ sectionIndex: number;
328
+ breakType: SectionBreakType;
329
+ origin?: CommandOrigin;
330
+ }
331
+ | {
332
+ type: "section.delete-break";
333
+ sectionIndex: number;
334
+ origin?: CommandOrigin;
335
+ }
336
+ | {
337
+ type: "section.update-layout";
338
+ sectionIndex: number;
339
+ patch: SectionLayoutPatch;
340
+ origin?: CommandOrigin;
341
+ }
342
+ | {
343
+ type: "section.set-page-numbering";
344
+ sectionIndex: number;
345
+ patch: SectionPageNumberingPatch | null;
346
+ origin?: CommandOrigin;
347
+ }
348
+ | {
349
+ type: "section.set-header-footer-link";
350
+ sectionIndex: number;
351
+ params: HeaderFooterLinkPatch;
352
+ origin?: CommandOrigin;
353
+ }
354
+ | {
355
+ type: "content.insert-page-break";
356
+ origin?: CommandOrigin;
357
+ }
358
+ | {
359
+ type: "content.insert-table";
360
+ options: InsertTableOptions;
361
+ origin?: CommandOrigin;
173
362
  };
174
363
 
175
364
  export interface TransactionEffects {
@@ -198,11 +387,40 @@ export interface CommandExecutionContext {
198
387
  timestamp: string;
199
388
  documentMode?: "editing" | "suggesting" | "viewing" | "commenting";
200
389
  defaultAuthorId?: string;
390
+ /**
391
+ * Runtime-owned render snapshot made available for document-mutating
392
+ * commands that need per-paragraph surface lookups (formatting, styles,
393
+ * section layout). Populated by the runtime at dispatch time; absent for
394
+ * callers that cannot produce it.
395
+ */
396
+ renderSnapshot?: RuntimeRenderSnapshot;
201
397
  }
202
398
 
399
+ /**
400
+ * Commands that are surfaced on the event log but do NOT mutate `EditorState`.
401
+ * They mutate runtime-scoped overlays (workflow, host annotations) and are
402
+ * applied directly by the runtime rather than through `executeEditorCommand`.
403
+ */
404
+ export type RuntimeStateCommandType =
405
+ | "history.undo"
406
+ | "history.redo"
407
+ | "workflow.set-overlay"
408
+ | "workflow.clear-overlay"
409
+ | "workflow.set-metadata-definitions"
410
+ | "workflow.clear-metadata-definitions"
411
+ | "workflow.set-metadata-entries"
412
+ | "workflow.clear-metadata-entries"
413
+ | "host-annotation.set-overlay"
414
+ | "host-annotation.clear-overlay";
415
+
416
+ export type StateMutatingCommand = Exclude<
417
+ EditorCommand,
418
+ { type: RuntimeStateCommandType }
419
+ >;
420
+
203
421
  export function executeEditorCommand(
204
422
  state: EditorState,
205
- command: Exclude<EditorCommand, { type: "history.undo" } | { type: "history.redo" }>,
423
+ command: StateMutatingCommand,
206
424
  context: CommandExecutionContext,
207
425
  ): EditorTransaction {
208
426
  switch (command.type) {
@@ -633,7 +851,356 @@ export function executeEditorCommand(
633
851
  },
634
852
  context.timestamp,
635
853
  );
854
+ case "formatting.apply": {
855
+ if (!context.renderSnapshot) {
856
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
857
+ }
858
+ const result = applyFormattingOperationToDocument(
859
+ state.document,
860
+ context.renderSnapshot,
861
+ command.operation,
862
+ );
863
+ return buildDocumentReplaceTransaction(state, context, {
864
+ changed: result.changed,
865
+ document: result.document,
866
+ selection: toInternalSelection(result.selection, state.selection),
867
+ });
868
+ }
869
+ case "style.set-paragraph": {
870
+ if (!context.renderSnapshot) {
871
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
872
+ }
873
+ const result = applyParagraphStyleToDocument(
874
+ state.document,
875
+ context.renderSnapshot,
876
+ command.styleId,
877
+ );
878
+ return buildDocumentReplaceTransaction(state, context, {
879
+ changed: result.changed,
880
+ document: result.document,
881
+ selection: toInternalSelection(result.selection, state.selection),
882
+ });
883
+ }
884
+ case "style.set-table": {
885
+ if (!context.renderSnapshot) {
886
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
887
+ }
888
+ const result = applyTableStyleToDocument(
889
+ state.document,
890
+ context.renderSnapshot,
891
+ command.styleId,
892
+ );
893
+ return buildDocumentReplaceTransaction(state, context, {
894
+ changed: result.changed,
895
+ document: result.document,
896
+ selection: toInternalSelection(result.selection, state.selection),
897
+ });
898
+ }
899
+ case "list.toggle": {
900
+ const result = command.kind === "bulleted"
901
+ ? toggleBulletedList(state.document, command.paragraphIndexes, { timestamp: context.timestamp })
902
+ : toggleNumberedList(state.document, command.paragraphIndexes, { timestamp: context.timestamp });
903
+ return buildDocumentReplaceTransaction(state, context, {
904
+ changed: result.affectedParagraphIndexes.length > 0,
905
+ document: result.document,
906
+ selection: state.selection,
907
+ });
908
+ }
909
+ case "list.indent": {
910
+ const result = indentListItems(state.document, command.paragraphIndexes, { timestamp: context.timestamp });
911
+ return buildDocumentReplaceTransaction(state, context, {
912
+ changed: result.affectedParagraphIndexes.length > 0,
913
+ document: result.document,
914
+ selection: state.selection,
915
+ });
916
+ }
917
+ case "list.outdent": {
918
+ const result = outdentListItems(state.document, command.paragraphIndexes, { timestamp: context.timestamp });
919
+ return buildDocumentReplaceTransaction(state, context, {
920
+ changed: result.affectedParagraphIndexes.length > 0,
921
+ document: result.document,
922
+ selection: state.selection,
923
+ });
924
+ }
925
+ case "list.restart-numbering": {
926
+ const result = restartNumbering(
927
+ state.document,
928
+ command.paragraphIndex,
929
+ { timestamp: context.timestamp },
930
+ command.startAt,
931
+ );
932
+ return buildDocumentReplaceTransaction(state, context, {
933
+ changed: result.affectedParagraphIndexes.length > 0,
934
+ document: result.document,
935
+ selection: state.selection,
936
+ });
937
+ }
938
+ case "list.continue-numbering": {
939
+ const result = continueNumbering(
940
+ state.document,
941
+ command.paragraphIndex,
942
+ { timestamp: context.timestamp },
943
+ );
944
+ return buildDocumentReplaceTransaction(state, context, {
945
+ changed: result.affectedParagraphIndexes.length > 0,
946
+ document: result.document,
947
+ selection: state.selection,
948
+ });
949
+ }
950
+ case "table.apply-structure": {
951
+ if (!context.renderSnapshot) {
952
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
953
+ }
954
+ const result = applyTableStructureOperation(
955
+ state.document,
956
+ context.renderSnapshot,
957
+ command.selectionDescriptor,
958
+ command.operation,
959
+ );
960
+ return buildDocumentReplaceTransaction(state, context, {
961
+ changed: result.changed,
962
+ document: result.document,
963
+ selection: result.selection,
964
+ mapping: result.mapping,
965
+ });
966
+ }
967
+ case "image.insert": {
968
+ const dataBytes = decodeBase64ToBytes(command.data);
969
+ const result = insertImage(
970
+ state.document,
971
+ state.selection,
972
+ dataBytes,
973
+ command.mimeType,
974
+ command.width,
975
+ command.height,
976
+ {
977
+ timestamp: context.timestamp,
978
+ ...(command.altText ? { altText: command.altText } : {}),
979
+ },
980
+ );
981
+ return buildDocumentReplaceTransaction(state, context, {
982
+ changed: true,
983
+ document: result.document,
984
+ selection: result.selection,
985
+ mapping: result.mapping,
986
+ });
987
+ }
988
+ case "image.set-layout": {
989
+ try {
990
+ const result = resizeImage(
991
+ state.document,
992
+ command.mediaId,
993
+ command.dimensions,
994
+ context.timestamp,
995
+ );
996
+ return buildDocumentReplaceTransaction(state, context, {
997
+ changed: true,
998
+ document: result.document,
999
+ selection: state.selection,
1000
+ });
1001
+ } catch {
1002
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1003
+ }
1004
+ }
1005
+ case "image.set-frame": {
1006
+ try {
1007
+ const result = repositionFloatingImage(
1008
+ state.document,
1009
+ command.mediaId,
1010
+ command.offsets,
1011
+ context.timestamp,
1012
+ );
1013
+ return buildDocumentReplaceTransaction(state, context, {
1014
+ changed: true,
1015
+ document: result.document,
1016
+ selection: state.selection,
1017
+ });
1018
+ } catch {
1019
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1020
+ }
1021
+ }
1022
+ case "section.insert-break": {
1023
+ const result = insertSectionBreakAfterSectionIndex(
1024
+ state.document,
1025
+ command.sectionIndex,
1026
+ command.breakType,
1027
+ { timestamp: context.timestamp },
1028
+ );
1029
+ return buildDocumentReplaceTransaction(state, context, {
1030
+ changed: result.changed,
1031
+ document: result.document,
1032
+ selection: state.selection,
1033
+ });
1034
+ }
1035
+ case "section.delete-break": {
1036
+ const result = deleteSectionBreakAtSectionIndex(
1037
+ state.document,
1038
+ command.sectionIndex,
1039
+ { timestamp: context.timestamp },
1040
+ );
1041
+ return buildDocumentReplaceTransaction(state, context, {
1042
+ changed: result.changed,
1043
+ document: result.document,
1044
+ selection: state.selection,
1045
+ });
1046
+ }
1047
+ case "section.update-layout": {
1048
+ const result = updateSectionLayoutAtSectionIndex(
1049
+ state.document,
1050
+ command.sectionIndex,
1051
+ command.patch,
1052
+ { timestamp: context.timestamp },
1053
+ );
1054
+ return buildDocumentReplaceTransaction(state, context, {
1055
+ changed: result.changed,
1056
+ document: result.document,
1057
+ selection: state.selection,
1058
+ });
1059
+ }
1060
+ case "section.set-page-numbering": {
1061
+ const normalizedPatch = command.patch === null
1062
+ ? null
1063
+ : stripNullsFromRecord(command.patch as unknown as Record<string, unknown>);
1064
+ const result = setSectionPageNumberingAtSectionIndex(
1065
+ state.document,
1066
+ command.sectionIndex,
1067
+ normalizedPatch as never,
1068
+ { timestamp: context.timestamp },
1069
+ );
1070
+ return buildDocumentReplaceTransaction(state, context, {
1071
+ changed: result.changed,
1072
+ document: result.document,
1073
+ selection: state.selection,
1074
+ });
1075
+ }
1076
+ case "section.set-header-footer-link": {
1077
+ const result = setHeaderFooterLinkAtSectionIndex(
1078
+ state.document,
1079
+ command.sectionIndex,
1080
+ command.params,
1081
+ { timestamp: context.timestamp },
1082
+ );
1083
+ return buildDocumentReplaceTransaction(state, context, {
1084
+ changed: result.changed,
1085
+ document: result.document,
1086
+ selection: state.selection,
1087
+ });
1088
+ }
1089
+ case "content.insert-page-break": {
1090
+ const result = insertPageBreak(
1091
+ state.document,
1092
+ state.selection,
1093
+ { timestamp: context.timestamp },
1094
+ );
1095
+ return buildDocumentReplaceTransaction(state, context, {
1096
+ changed: result.changed,
1097
+ document: result.document,
1098
+ selection: result.selection,
1099
+ mapping: result.mapping,
1100
+ });
1101
+ }
1102
+ case "content.insert-table": {
1103
+ const result = insertTable(
1104
+ state.document,
1105
+ state.selection,
1106
+ command.options,
1107
+ { timestamp: context.timestamp },
1108
+ );
1109
+ return buildDocumentReplaceTransaction(state, context, {
1110
+ changed: result.changed,
1111
+ document: result.document,
1112
+ selection: result.selection,
1113
+ mapping: result.mapping,
1114
+ });
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ function buildDocumentReplaceTransaction(
1120
+ state: EditorState,
1121
+ context: CommandExecutionContext,
1122
+ result: {
1123
+ changed: boolean;
1124
+ document: EditorState["document"];
1125
+ selection: SelectionSnapshot;
1126
+ mapping?: TransactionMapping;
1127
+ },
1128
+ ): EditorTransaction {
1129
+ if (!result.changed) {
1130
+ return createTransaction(state, { historyBoundary: "skip", markDirty: false });
1131
+ }
1132
+ const mapping = result.mapping ?? createEmptyMapping();
1133
+ const reviewState = remapReviewStateAfterContentChange(
1134
+ state,
1135
+ result.document,
1136
+ mapping,
1137
+ );
1138
+ return createTransaction(
1139
+ {
1140
+ ...state,
1141
+ document: reviewState.document,
1142
+ selection: result.selection,
1143
+ warnings: reviewState.warnings,
1144
+ runtime: {
1145
+ ...state.runtime,
1146
+ activeCommentId: reviewState.activeCommentId,
1147
+ },
1148
+ compatibility: {
1149
+ ...state.compatibility,
1150
+ generatedAt: context.timestamp,
1151
+ warnings: reviewState.warnings,
1152
+ featureEntries: state.compatibility.featureEntries.map((entry) =>
1153
+ entry.affectedAnchor
1154
+ ? {
1155
+ ...entry,
1156
+ affectedAnchor: mapAnchor(entry.affectedAnchor, mapping),
1157
+ }
1158
+ : entry,
1159
+ ),
1160
+ },
1161
+ },
1162
+ {
1163
+ historyBoundary: "push",
1164
+ markDirty: true,
1165
+ mapping,
1166
+ effects: reviewState.effects,
1167
+ },
1168
+ );
1169
+ }
1170
+
1171
+ function decodeBase64ToBytes(value: string): Uint8Array {
1172
+ const binary = atob(value);
1173
+ const bytes = new Uint8Array(binary.length);
1174
+ for (let i = 0; i < binary.length; i++) {
1175
+ bytes[i] = binary.charCodeAt(i);
1176
+ }
1177
+ return bytes;
1178
+ }
1179
+
1180
+ function toInternalSelection(
1181
+ publicSelection: RuntimeRenderSnapshot["selection"],
1182
+ fallback: SelectionSnapshot,
1183
+ ): SelectionSnapshot {
1184
+ if (
1185
+ publicSelection.anchor === fallback.anchor &&
1186
+ publicSelection.head === fallback.head
1187
+ ) {
1188
+ return fallback;
1189
+ }
1190
+ return createSelectionSnapshot(publicSelection.anchor, publicSelection.head);
1191
+ }
1192
+
1193
+ function stripNullsFromRecord<T extends Record<string, unknown>>(
1194
+ value: T,
1195
+ ): { [K in keyof T]?: Exclude<T[K], null> } {
1196
+ const out: Record<string, unknown> = {};
1197
+ for (const key of Object.keys(value) as Array<keyof T>) {
1198
+ const v = value[key];
1199
+ if (v !== null && v !== undefined) {
1200
+ out[key as string] = v;
1201
+ }
636
1202
  }
1203
+ return out as { [K in keyof T]?: Exclude<T[K], null> };
637
1204
  }
638
1205
 
639
1206
  export function remapSelection(