@beyondwork/docx-react-component 1.0.41 → 1.0.43
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 +38 -37
- package/src/api/awareness-identity-types.ts +35 -0
- package/src/api/comment-negotiation-types.ts +130 -0
- package/src/api/comment-presentation-types.ts +106 -0
- package/src/api/editor-state-types.ts +110 -0
- package/src/api/external-custody-types.ts +74 -0
- package/src/api/participants-types.ts +18 -0
- package/src/api/public-types.ts +541 -5
- package/src/api/scope-metadata-resolver-types.ts +88 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +601 -9
- package/src/core/search/search-text.ts +15 -2
- package/src/index.ts +131 -1
- package/src/io/docx-session.ts +672 -2
- package/src/io/export/escape-xml-attribute.ts +26 -0
- package/src/io/export/external-send.ts +188 -0
- package/src/io/export/serialize-comments.ts +13 -16
- package/src/io/export/serialize-footnotes.ts +17 -24
- package/src/io/export/serialize-headers-footers.ts +17 -24
- package/src/io/export/serialize-main-document.ts +59 -62
- package/src/io/export/serialize-numbering.ts +20 -27
- package/src/io/export/serialize-runtime-revisions.ts +2 -9
- package/src/io/export/serialize-tables.ts +8 -15
- package/src/io/export/table-properties-xml.ts +25 -32
- package/src/io/import/external-reimport.ts +40 -0
- package/src/io/load-scheduler.ts +230 -0
- package/src/io/normalize/normalize-text.ts +83 -0
- package/src/io/ooxml/bw-xml.ts +244 -0
- package/src/io/ooxml/canonicalize-payload.ts +301 -0
- package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
- package/src/io/ooxml/comment-presentation-payload.ts +311 -0
- package/src/io/ooxml/external-custody-payload.ts +102 -0
- package/src/io/ooxml/participants-payload.ts +97 -0
- package/src/io/ooxml/payload-signature.ts +112 -0
- package/src/io/ooxml/workflow-payload-validator.ts +367 -0
- package/src/io/ooxml/workflow-payload.ts +317 -7
- package/src/runtime/awareness-identity.ts +173 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab-session-bridge.ts +157 -0
- package/src/runtime/collab-session-facet.ts +193 -0
- package/src/runtime/collab-session.ts +273 -0
- package/src/runtime/comment-negotiation-sync.ts +91 -0
- package/src/runtime/comment-negotiation.ts +158 -0
- package/src/runtime/comment-presentation.ts +223 -0
- package/src/runtime/document-runtime.ts +639 -124
- package/src/runtime/editor-state-channel.ts +544 -0
- package/src/runtime/editor-state-integration.ts +217 -0
- package/src/runtime/external-send-runtime.ts +117 -0
- package/src/runtime/layout/docx-font-loader.ts +11 -30
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +4 -0
- package/src/runtime/layout/layout-engine-instance.ts +139 -14
- package/src/runtime/layout/page-graph.ts +79 -7
- package/src/runtime/layout/paginated-layout-engine.ts +441 -48
- package/src/runtime/layout/public-facet.ts +585 -14
- package/src/runtime/layout/table-row-split.ts +316 -0
- package/src/runtime/markdown-sanitizer.ts +132 -0
- package/src/runtime/participants.ts +134 -0
- package/src/runtime/perf-counters.ts +28 -0
- package/src/runtime/render/render-frame-types.ts +17 -0
- package/src/runtime/render/render-kernel.ts +172 -29
- package/src/runtime/resign-payload.ts +120 -0
- package/src/runtime/surface-projection.ts +10 -5
- package/src/runtime/tamper-gate.ts +157 -0
- package/src/runtime/workflow-markup.ts +80 -16
- package/src/runtime/workflow-rail-segments.ts +244 -5
- package/src/ui/WordReviewEditor.tsx +654 -45
- package/src/ui/editor-command-bag.ts +14 -0
- package/src/ui/editor-runtime-boundary.ts +111 -11
- package/src/ui/editor-shell-view.tsx +21 -0
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-helpers.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
- package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
- package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
- package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
- package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +106 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
- package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +167 -17
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +10 -256
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
- package/src/ui-tailwind/index.ts +37 -1
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
- package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +455 -118
|
@@ -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:
|
|
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);
|
|
636
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
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return out as { [K in keyof T]?: Exclude<T[K], null> };
|
|
637
1204
|
}
|
|
638
1205
|
|
|
639
1206
|
export function remapSelection(
|
|
@@ -1224,22 +1791,46 @@ function combineMappingSteps(
|
|
|
1224
1791
|
// Suggesting mode: creates revision records instead of (or alongside) text mutations
|
|
1225
1792
|
// ---------------------------------------------------------------------------
|
|
1226
1793
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1794
|
+
/**
|
|
1795
|
+
* Builds a suggesting-mode revision id that is deterministic across
|
|
1796
|
+
* clients. The id is a pure function of `(existing, timestamp, authorId)`
|
|
1797
|
+
* so that origin and replica — both of whom see the same `existing`
|
|
1798
|
+
* revisions record after a Yjs-ordered replay and receive the same
|
|
1799
|
+
* `timestamp` + `authorId` on the broadcast command event — produce
|
|
1800
|
+
* the same id for the same authored revision.
|
|
1801
|
+
*
|
|
1802
|
+
* Previously this used a module-level counter; that counter was shared
|
|
1803
|
+
* across every `DocumentRuntime` in a single process and drifted between
|
|
1804
|
+
* origin and replica whenever either side had other suggesting-mode work
|
|
1805
|
+
* bump the counter before replay. Cross-client `change.accept(changeId)`
|
|
1806
|
+
* / `change.reject(changeId)` then missed because the target id only
|
|
1807
|
+
* existed on one side.
|
|
1808
|
+
*/
|
|
1229
1809
|
function createSuggestingRevisionId(
|
|
1230
1810
|
existing: Record<string, unknown>,
|
|
1231
1811
|
timestamp: string,
|
|
1812
|
+
authorId: string,
|
|
1232
1813
|
): string {
|
|
1233
|
-
suggestingRevisionCounter += 1;
|
|
1234
1814
|
const ts = timestamp.replace(/[^0-9]/gu, "");
|
|
1235
|
-
|
|
1815
|
+
const authorMarker = sanitizeAuthorMarker(authorId);
|
|
1816
|
+
const prefix = `change-${authorMarker}-${ts}-s`;
|
|
1817
|
+
let n = 1;
|
|
1818
|
+
let id = `${prefix}${n}`;
|
|
1236
1819
|
while (existing[id]) {
|
|
1237
|
-
|
|
1238
|
-
id =
|
|
1820
|
+
n += 1;
|
|
1821
|
+
id = `${prefix}${n}`;
|
|
1239
1822
|
}
|
|
1240
1823
|
return id;
|
|
1241
1824
|
}
|
|
1242
1825
|
|
|
1826
|
+
function sanitizeAuthorMarker(authorId: string): string {
|
|
1827
|
+
// OOXML `w:id` accepts alphanumerics plus `._-`; keep within that set so
|
|
1828
|
+
// the id survives round-trips unchanged (see `sanitizeRevisionId` on the
|
|
1829
|
+
// serializer side).
|
|
1830
|
+
const cleaned = authorId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
1831
|
+
return cleaned.length > 0 ? cleaned : "u";
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1243
1834
|
function createAuthoredRevision(
|
|
1244
1835
|
existing: Record<string, unknown>,
|
|
1245
1836
|
kind: "insertion" | "deletion",
|
|
@@ -1249,7 +1840,7 @@ function createAuthoredRevision(
|
|
|
1249
1840
|
timestamp: string,
|
|
1250
1841
|
metadata: Partial<NonNullable<CanonicalRevisionRecord["metadata"]>> = {},
|
|
1251
1842
|
): CanonicalRevisionRecord {
|
|
1252
|
-
const changeId = createSuggestingRevisionId(existing, timestamp);
|
|
1843
|
+
const changeId = createSuggestingRevisionId(existing, timestamp, authorId);
|
|
1253
1844
|
return {
|
|
1254
1845
|
changeId,
|
|
1255
1846
|
kind,
|
|
@@ -1499,6 +2090,7 @@ function applySuggestingInsert(
|
|
|
1499
2090
|
const replacementSuggestionId = createSuggestingRevisionId(
|
|
1500
2091
|
reviewState.document.review.revisions,
|
|
1501
2092
|
context.timestamp,
|
|
2093
|
+
authorId,
|
|
1502
2094
|
);
|
|
1503
2095
|
|
|
1504
2096
|
// Step 3: Create deletion revision for the selected range (text stays in place).
|
|
@@ -1757,7 +2349,7 @@ function applySuggestingInsertUnit(
|
|
|
1757
2349
|
result.mapping,
|
|
1758
2350
|
);
|
|
1759
2351
|
const replacementSuggestionId = from !== to
|
|
1760
|
-
? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp)
|
|
2352
|
+
? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp, authorId)
|
|
1761
2353
|
: undefined;
|
|
1762
2354
|
|
|
1763
2355
|
// If non-collapsed, mark selected range as deletion (positions are pre-mapping, content preserved)
|
|
@@ -26,7 +26,7 @@ export interface SecondaryStorySearchResult extends SurfaceSearchResult {
|
|
|
26
26
|
storyTarget: EditorStoryTarget;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
interface ProjectedSurfaceText {
|
|
29
|
+
export interface ProjectedSurfaceText {
|
|
30
30
|
text: string;
|
|
31
31
|
offsetMap: Array<number | null>;
|
|
32
32
|
}
|
|
@@ -112,7 +112,20 @@ export function searchSurfaceBlocks(
|
|
|
112
112
|
query: string,
|
|
113
113
|
options: SearchTextOptions = {},
|
|
114
114
|
): SurfaceSearchResult[] {
|
|
115
|
-
|
|
115
|
+
return searchProjectedSurfaceText(projectSurfaceText(blocks), query, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Search a pre-projected surface text. Hoist `projectSurfaceText(blocks)` out
|
|
120
|
+
* of a per-query loop to avoid rebuilding the projection N times for N queries
|
|
121
|
+
* against the same surface — L7 Phase 1.5 discovered this cost dominates
|
|
122
|
+
* `collectFieldMarkup` on the CCEP large-tables fixture.
|
|
123
|
+
*/
|
|
124
|
+
export function searchProjectedSurfaceText(
|
|
125
|
+
projection: ProjectedSurfaceText,
|
|
126
|
+
query: string,
|
|
127
|
+
options: SearchTextOptions = {},
|
|
128
|
+
): SurfaceSearchResult[] {
|
|
116
129
|
return findSearchMatches(projection.text, query, options)
|
|
117
130
|
.map((match) => {
|
|
118
131
|
const range = resolveProjectedRuntimeRange(
|