@beyondwork/docx-react-component 1.0.33 → 1.0.35
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 +20 -3
- package/src/api/public-types.ts +13 -8
- package/src/api/session-state.ts +24 -0
- package/src/core/commands/index.ts +73 -0
- package/src/index.ts +2 -0
- package/src/io/docx-session.ts +260 -3
- package/src/io/ooxml/workflow-payload.ts +122 -0
- package/src/model/snapshot.ts +58 -0
- package/src/runtime/collab-review-sync.ts +254 -0
- package/src/runtime/document-runtime.ts +4 -3
- package/src/runtime/surface-projection.ts +8 -0
- package/src/ui/WordReviewEditor.tsx +11 -0
- package/src/ui/editor-surface-controller.tsx +2 -0
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +40 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +27 -33
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +8 -7
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +55 -24
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
createCommandBridgePlugins,
|
|
43
43
|
type CommandBridgeCallbacks,
|
|
44
44
|
} from "./pm-command-bridge";
|
|
45
|
+
import { createCollabPlugins } from "./pm-collab-plugins";
|
|
46
|
+
import { prosemirrorToYXmlFragment } from "y-prosemirror";
|
|
45
47
|
import { buildDecorations } from "./pm-decorations";
|
|
46
48
|
import { createContextualInteractionPlugin } from "./pm-contextual-ui";
|
|
47
49
|
import {
|
|
@@ -71,6 +73,8 @@ import type { MediaPreviewDescriptor } from "./pm-state-from-snapshot";
|
|
|
71
73
|
*/
|
|
72
74
|
export interface TwProseMirrorSurfaceProps {
|
|
73
75
|
currentUser: EditorUser;
|
|
76
|
+
ydoc?: import("yjs").Doc;
|
|
77
|
+
awareness?: import("y-protocols/awareness").Awareness;
|
|
74
78
|
snapshot: RuntimeRenderSnapshot;
|
|
75
79
|
canonicalDocument: CanonicalDocumentEnvelope;
|
|
76
80
|
documentNavigation: DocumentNavigationSnapshot;
|
|
@@ -219,6 +223,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
219
223
|
createSurfaceDecorationKey({
|
|
220
224
|
markupDisplay,
|
|
221
225
|
showTrackedChanges,
|
|
226
|
+
suggestionsEnabled,
|
|
222
227
|
canEdit,
|
|
223
228
|
activeCommentId: snapshot.comments.activeCommentId,
|
|
224
229
|
activeRevisionId: props.activeRevisionId,
|
|
@@ -242,36 +247,51 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
242
247
|
props.activeWorkflowScopeIds,
|
|
243
248
|
props.workflowScopes,
|
|
244
249
|
showTrackedChanges,
|
|
250
|
+
suggestionsEnabled,
|
|
245
251
|
snapshot.comments.activeCommentId,
|
|
246
252
|
],
|
|
247
253
|
);
|
|
248
254
|
|
|
255
|
+
const isCollabMode = Boolean(props.ydoc);
|
|
256
|
+
|
|
249
257
|
// Create PM plugins (stable across renders — callbacks accessed via ref)
|
|
250
258
|
const plugins = useMemo(() => {
|
|
259
|
+
const selectionCallbacks = {
|
|
260
|
+
onSelectionChange: (sel: SelectionSnapshot) => callbacksRef.current?.onSelectionChange(sel),
|
|
261
|
+
getPositionMap: () => callbacksRef.current?.getPositionMap() ?? null,
|
|
262
|
+
isSelectionSyncSuppressed: () =>
|
|
263
|
+
callbacksRef.current?.isSelectionSyncSuppressed?.() ?? false,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const corePlugins = props.ydoc
|
|
267
|
+
? createCollabPlugins({
|
|
268
|
+
ydoc: props.ydoc,
|
|
269
|
+
awareness: props.awareness,
|
|
270
|
+
selectionCallbacks,
|
|
271
|
+
})
|
|
272
|
+
: createCommandBridgePlugins({
|
|
273
|
+
...selectionCallbacks,
|
|
274
|
+
onInsertText: (text) => callbacksRef.current?.onInsertText(text),
|
|
275
|
+
onDeleteBackward: () => callbacksRef.current?.onDeleteBackward(),
|
|
276
|
+
onDeleteForward: () => callbacksRef.current?.onDeleteForward(),
|
|
277
|
+
onSplitParagraph: () => callbacksRef.current?.onSplitParagraph(),
|
|
278
|
+
onInsertHardBreak: () => callbacksRef.current?.onInsertHardBreak(),
|
|
279
|
+
onInsertTab: () => callbacksRef.current?.onInsertTab(),
|
|
280
|
+
onOutdentTab: () => callbacksRef.current?.onOutdentTab?.(),
|
|
281
|
+
onUndo: () => callbacksRef.current?.onUndo(),
|
|
282
|
+
onRedo: () => callbacksRef.current?.onRedo(),
|
|
283
|
+
onBlockedInput: (command, message) => callbacksRef.current?.onBlockedInput?.(command, message),
|
|
284
|
+
});
|
|
285
|
+
|
|
251
286
|
return [
|
|
252
|
-
...
|
|
253
|
-
onInsertText: (text) => callbacksRef.current?.onInsertText(text),
|
|
254
|
-
onDeleteBackward: () => callbacksRef.current?.onDeleteBackward(),
|
|
255
|
-
onDeleteForward: () => callbacksRef.current?.onDeleteForward(),
|
|
256
|
-
onSplitParagraph: () => callbacksRef.current?.onSplitParagraph(),
|
|
257
|
-
onInsertHardBreak: () => callbacksRef.current?.onInsertHardBreak(),
|
|
258
|
-
onInsertTab: () => callbacksRef.current?.onInsertTab(),
|
|
259
|
-
onOutdentTab: () => callbacksRef.current?.onOutdentTab?.(),
|
|
260
|
-
onUndo: () => callbacksRef.current?.onUndo(),
|
|
261
|
-
onRedo: () => callbacksRef.current?.onRedo(),
|
|
262
|
-
onBlockedInput: (command, message) => callbacksRef.current?.onBlockedInput?.(command, message),
|
|
263
|
-
onSelectionChange: (sel) => callbacksRef.current?.onSelectionChange(sel),
|
|
264
|
-
getPositionMap: () => callbacksRef.current?.getPositionMap() ?? null,
|
|
265
|
-
isSelectionSyncSuppressed: () =>
|
|
266
|
-
callbacksRef.current?.isSelectionSyncSuppressed?.() ?? false,
|
|
267
|
-
}),
|
|
287
|
+
...corePlugins,
|
|
268
288
|
createContextualInteractionPlugin({
|
|
269
289
|
onCommentActivated: (commentId) => props.onCommentActivated?.(commentId),
|
|
270
290
|
onRevisionActivated: (revisionId) => props.onRevisionActivated?.(revisionId),
|
|
271
291
|
}),
|
|
272
292
|
createSearchPlugin(),
|
|
273
293
|
];
|
|
274
|
-
}, [props.onCommentActivated, props.onRevisionActivated]);
|
|
294
|
+
}, [props.onCommentActivated, props.onRevisionActivated, props.ydoc, props.awareness]);
|
|
275
295
|
|
|
276
296
|
const applyDecorationProps = useCallback(
|
|
277
297
|
(view: EditorView, positionMap: PositionMap): void => {
|
|
@@ -318,10 +338,14 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
318
338
|
],
|
|
319
339
|
);
|
|
320
340
|
|
|
321
|
-
// Create or update the PM document only when the structural key changes.
|
|
322
341
|
useEffect(() => {
|
|
323
342
|
if (!mountRef.current || !surface) return;
|
|
324
343
|
|
|
344
|
+
// Collab mode: y-prosemirror owns the doc after initial mount
|
|
345
|
+
if (isCollabMode && viewRef.current) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
325
349
|
if (viewRef.current && documentBuildKeyRef.current === documentBuildKey) {
|
|
326
350
|
return;
|
|
327
351
|
}
|
|
@@ -355,19 +379,23 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
355
379
|
incrementInvalidationCounter("pm.laneA.rebuilds");
|
|
356
380
|
|
|
357
381
|
if (!viewRef.current) {
|
|
358
|
-
// First time surface is available — create the EditorView
|
|
359
382
|
const view = new EditorView(mountRef.current, {
|
|
360
383
|
state,
|
|
361
384
|
nodeViews: tableNodeViews,
|
|
362
385
|
editable: () => canEdit,
|
|
363
386
|
decorations: () => decorations,
|
|
364
|
-
dispatchTransaction(tr) {
|
|
365
|
-
const newState = view.state.apply(tr);
|
|
366
|
-
view.updateState(newState);
|
|
367
|
-
},
|
|
368
387
|
});
|
|
369
388
|
viewRef.current = view;
|
|
370
389
|
recordPerfSample("pm.mount");
|
|
390
|
+
|
|
391
|
+
if (isCollabMode && props.ydoc) {
|
|
392
|
+
const yXmlFragment = props.ydoc.getXmlFragment("prosemirror");
|
|
393
|
+
if (yXmlFragment.length === 0) {
|
|
394
|
+
props.ydoc.transact(() => {
|
|
395
|
+
prosemirrorToYXmlFragment(view.state.doc, yXmlFragment);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
371
399
|
} else {
|
|
372
400
|
suppressSelectionEchoRef.current = true;
|
|
373
401
|
viewRef.current.updateState(state);
|
|
@@ -391,10 +419,12 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
391
419
|
}, [
|
|
392
420
|
applyDecorationProps,
|
|
393
421
|
documentBuildKey,
|
|
422
|
+
isCollabMode,
|
|
394
423
|
surface,
|
|
395
424
|
snapshot.selection,
|
|
396
425
|
plugins,
|
|
397
426
|
props.mediaPreviews,
|
|
427
|
+
props.ydoc,
|
|
398
428
|
]);
|
|
399
429
|
|
|
400
430
|
// Update decorations and editability without rebuilding the PM document.
|
|
@@ -427,6 +457,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
427
457
|
]);
|
|
428
458
|
|
|
429
459
|
useEffect(() => {
|
|
460
|
+
if (isCollabMode) return;
|
|
430
461
|
const view = viewRef.current;
|
|
431
462
|
const positionMap = positionMapRef.current;
|
|
432
463
|
if (!view || !surface || !positionMap) {
|
|
@@ -448,7 +479,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
448
479
|
queueMicrotask(() => {
|
|
449
480
|
suppressSelectionEchoRef.current = false;
|
|
450
481
|
});
|
|
451
|
-
}, [snapshot.selection, surface]);
|
|
482
|
+
}, [isCollabMode, snapshot.selection, surface]);
|
|
452
483
|
|
|
453
484
|
useEffect(() => {
|
|
454
485
|
if (!pendingSelectionProbeRef.current) {
|