@beyondwork/docx-react-component 1.0.84 → 1.0.86
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 +1 -1
- package/src/api/internal/build-ref-projections.ts +3 -0
- package/src/api/public-types.ts +38 -0
- package/src/api/v3/_runtime-handle.ts +11 -0
- package/src/api/v3/runtime/content.ts +148 -1
- package/src/api/v3/runtime/formatting.ts +41 -0
- package/src/api/v3/runtime/review.ts +98 -0
- package/src/core/commands/index.ts +81 -25
- package/src/core/state/editor-state.ts +15 -0
- package/src/io/ooxml/header-footer-reference.ts +38 -0
- package/src/io/ooxml/parse-headers-footers.ts +11 -23
- package/src/io/ooxml/parse-main-document.ts +7 -10
- package/src/model/canonical-document.ts +9 -0
- package/src/model/review/comment-types.ts +2 -0
- package/src/runtime/document-runtime.ts +677 -54
- package/src/runtime/formatting/field/resolver.ts +73 -8
- package/src/runtime/layout/layout-engine-version.ts +31 -12
- package/src/runtime/layout/paginated-layout-engine.ts +18 -11
- package/src/runtime/layout/public-facet.ts +119 -16
- package/src/runtime/layout/resolve-page-fields.ts +68 -6
- package/src/runtime/layout/resolve-page-previews.ts +1 -1
- package/src/runtime/suggestions-snapshot.ts +24 -0
- package/src/runtime/surface-projection.ts +59 -2
- package/src/shell/ref-commands.ts +3 -354
- package/src/shell/session-bootstrap.ts +8 -0
- package/src/ui/WordReviewEditor.tsx +192 -35
- package/src/ui/editor-command-bag.ts +7 -1
- package/src/ui/editor-shell-view.tsx +1 -0
- package/src/ui/headless/revision-decoration-model.ts +13 -0
- package/src/ui/headless/selection-tool-types.ts +2 -0
- package/src/ui-tailwind/chrome/editor-action-registry.ts +7 -26
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +6 -2
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +7 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +175 -25
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -1
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +12 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +18 -30
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +1 -1
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +20 -11
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +9 -4
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +12 -7
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +29 -10
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +1 -1
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +8 -3
- package/src/ui-tailwind/review/tw-review-rail.tsx +2 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +46 -31
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +159 -8
- package/src/ui-tailwind/review-workspace/types.ts +7 -2
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +11 -1
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +8 -9
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +21 -16
- package/src/ui-tailwind/tw-review-workspace.tsx +27 -4
|
@@ -66,6 +66,7 @@ import type {
|
|
|
66
66
|
ViewMode as EditorViewMode,
|
|
67
67
|
WorkflowBlockedCommandReason,
|
|
68
68
|
WorkflowMarkupSnapshot,
|
|
69
|
+
WorkflowMarkupMode,
|
|
69
70
|
WorkflowScopeSnapshot,
|
|
70
71
|
WordReviewEditorChromeOptions,
|
|
71
72
|
WordReviewEditorChromePreset,
|
|
@@ -250,6 +251,21 @@ function normalizeHostMarkupDisplay(
|
|
|
250
251
|
return "markup";
|
|
251
252
|
}
|
|
252
253
|
|
|
254
|
+
function toWorkflowMarkupMode(mode: MarkupDisplay): WorkflowMarkupMode {
|
|
255
|
+
switch (mode) {
|
|
256
|
+
case "all":
|
|
257
|
+
case "all-markup":
|
|
258
|
+
return "all";
|
|
259
|
+
case "simple":
|
|
260
|
+
case "simple-markup":
|
|
261
|
+
return "simple";
|
|
262
|
+
case "clean":
|
|
263
|
+
case "no-markup":
|
|
264
|
+
case "original":
|
|
265
|
+
return "clean";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
253
269
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
254
270
|
position: "absolute",
|
|
255
271
|
width: "1px",
|
|
@@ -383,6 +399,11 @@ export function __createWordReviewEditorRefBridge(
|
|
|
383
399
|
resolveComment: (commentId) => runtime.resolveComment(commentId),
|
|
384
400
|
reopenComment: (commentId) => runtime.reopenComment(commentId),
|
|
385
401
|
addCommentReply: (commentId, body) => runtime.addCommentReply(commentId, body),
|
|
402
|
+
getCommentThreadForChange: (changeId) =>
|
|
403
|
+
clonePublicValue(runtime.getCommentThreadForChange(changeId)),
|
|
404
|
+
ensureCommentThreadForChange: (changeId) =>
|
|
405
|
+
runtime.ensureCommentThreadForChange(changeId),
|
|
406
|
+
addReplyToChange: (changeId, body) => runtime.addReplyToChange(changeId, body),
|
|
386
407
|
editCommentBody: (commentId, body) => runtime.editCommentBody(commentId, body),
|
|
387
408
|
deleteComment: (commentId) => {
|
|
388
409
|
applyRuntimeDeleteComment(runtime, commentId);
|
|
@@ -1041,7 +1062,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1041
1062
|
} = props;
|
|
1042
1063
|
|
|
1043
1064
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
1044
|
-
const [
|
|
1065
|
+
const [localMarkupDisplay, setLocalMarkupDisplay] =
|
|
1066
|
+
useState<WorkflowMarkupMode | null>(() => suggestionsEnabled ? "all" : null);
|
|
1045
1067
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
1046
1068
|
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
1047
1069
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
@@ -1343,7 +1365,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1343
1365
|
loadingViewState,
|
|
1344
1366
|
);
|
|
1345
1367
|
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
1346
|
-
const liveMarkupDisplay =
|
|
1368
|
+
const liveMarkupDisplay = localMarkupDisplay ??
|
|
1369
|
+
__resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
1370
|
+
const trackedChangesAuthoringEnabled = viewState.documentMode === "suggesting";
|
|
1371
|
+
const showTrackedChanges = toWorkflowMarkupMode(liveMarkupDisplay) !== "clean";
|
|
1347
1372
|
const documentNavigation = useRuntimeValue(
|
|
1348
1373
|
runtime
|
|
1349
1374
|
? {
|
|
@@ -1483,9 +1508,27 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1483
1508
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
1484
1509
|
}, [activeRuntime, effectiveViewMode]);
|
|
1485
1510
|
|
|
1511
|
+
const setReviewMarkupMode = useCallback((mode: MarkupDisplay) => {
|
|
1512
|
+
const workflowMode = toWorkflowMarkupMode(mode);
|
|
1513
|
+
setLocalMarkupDisplay(workflowMode);
|
|
1514
|
+
api.ui?.viewport.setLocalMarkupMode(workflowMode);
|
|
1515
|
+
}, [api]);
|
|
1516
|
+
|
|
1517
|
+
const setTrackedChangesAuthoring = useCallback((enabled: boolean) => {
|
|
1518
|
+
api.runtime.document.setMode(enabled ? "suggesting" : "editing");
|
|
1519
|
+
if (enabled) {
|
|
1520
|
+
setLocalMarkupDisplay("all");
|
|
1521
|
+
api.ui?.viewport.setLocalMarkupMode("all");
|
|
1522
|
+
}
|
|
1523
|
+
}, [api]);
|
|
1524
|
+
|
|
1486
1525
|
useEffect(() => {
|
|
1487
|
-
|
|
1488
|
-
|
|
1526
|
+
if (suggestionsEnabled) {
|
|
1527
|
+
setTrackedChangesAuthoring(true);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
api.runtime.document.setMode("editing");
|
|
1531
|
+
}, [api, setTrackedChangesAuthoring, suggestionsEnabled]);
|
|
1489
1532
|
|
|
1490
1533
|
// design-close-chrome Phase 2 — density contract (designsystem §4.2).
|
|
1491
1534
|
// When the `density` prop is supplied, drive the root `data-density`
|
|
@@ -1645,6 +1688,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1645
1688
|
reopenComment: (commentId) => activeRuntime.reopenComment(commentId),
|
|
1646
1689
|
addCommentReply: (commentId, body) =>
|
|
1647
1690
|
activeRuntime.addCommentReply(commentId, body, currentUser.userId),
|
|
1691
|
+
getCommentThreadForChange: (changeId) =>
|
|
1692
|
+
clonePublicValue(activeRuntime.getCommentThreadForChange(changeId)),
|
|
1693
|
+
ensureCommentThreadForChange: (changeId) =>
|
|
1694
|
+
activeRuntime.ensureCommentThreadForChange(changeId, currentUser.userId),
|
|
1695
|
+
addReplyToChange: (changeId, body) =>
|
|
1696
|
+
activeRuntime.addReplyToChange(changeId, body, currentUser.userId),
|
|
1648
1697
|
editCommentBody: (commentId, body) =>
|
|
1649
1698
|
activeRuntime.editCommentBody(commentId, body),
|
|
1650
1699
|
deleteComment: (commentId) => {
|
|
@@ -2417,13 +2466,15 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2417
2466
|
|
|
2418
2467
|
function addReviewComment(): string | null {
|
|
2419
2468
|
try {
|
|
2469
|
+
const currentSnapshot = activeRuntime.getRenderSnapshot();
|
|
2420
2470
|
const { commentId } = activeRuntime.addComment({
|
|
2421
|
-
anchor: resolveCommentCommandAnchor(
|
|
2471
|
+
anchor: resolveCommentCommandAnchor(currentSnapshot),
|
|
2422
2472
|
body: "",
|
|
2423
2473
|
authorId: currentUser.userId,
|
|
2424
2474
|
snapToSafeBoundary: true,
|
|
2425
2475
|
});
|
|
2426
2476
|
activeRuntime.openComment(commentId);
|
|
2477
|
+
setActiveReviewQueueItemId(`comment:${commentId}`);
|
|
2427
2478
|
setActiveRailTab("comments");
|
|
2428
2479
|
return commentId;
|
|
2429
2480
|
} catch {
|
|
@@ -2740,7 +2791,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2740
2791
|
);
|
|
2741
2792
|
|
|
2742
2793
|
const addSelectionToolbarComment = useCallback(() => {
|
|
2743
|
-
|
|
2794
|
+
let commentId: string | null = null;
|
|
2795
|
+
if (activeSelectionTool?.kind === "suggestion-review") {
|
|
2796
|
+
const primaryChangeId = activeSelectionTool.changeIds[0];
|
|
2797
|
+
const linkedThread = primaryChangeId
|
|
2798
|
+
? activeRuntime.ensureCommentThreadForChange(primaryChangeId, currentUser.userId)
|
|
2799
|
+
: null;
|
|
2800
|
+
commentId = linkedThread?.commentId ?? null;
|
|
2801
|
+
if (commentId) {
|
|
2802
|
+
activeRuntime.openComment(commentId);
|
|
2803
|
+
setActiveRailTab("comments");
|
|
2804
|
+
}
|
|
2805
|
+
} else {
|
|
2806
|
+
commentId = addReviewComment();
|
|
2807
|
+
}
|
|
2744
2808
|
if (!commentId) {
|
|
2745
2809
|
return;
|
|
2746
2810
|
}
|
|
@@ -2748,7 +2812,51 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2748
2812
|
queueMicrotask(() => {
|
|
2749
2813
|
focusDocumentSurface();
|
|
2750
2814
|
});
|
|
2751
|
-
}, [
|
|
2815
|
+
}, [
|
|
2816
|
+
activeRuntime,
|
|
2817
|
+
activeSelectionTool,
|
|
2818
|
+
addReviewComment,
|
|
2819
|
+
currentUser.userId,
|
|
2820
|
+
dismissSelectionToolbar,
|
|
2821
|
+
focusDocumentSurface,
|
|
2822
|
+
]);
|
|
2823
|
+
|
|
2824
|
+
const acceptActiveSuggestion = useCallback(() => {
|
|
2825
|
+
if (activeSelectionTool?.kind !== "suggestion-review") {
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
for (const changeId of activeSelectionTool.changeIds) {
|
|
2829
|
+
activeRuntime.acceptChange(changeId);
|
|
2830
|
+
}
|
|
2831
|
+
dismissSelectionToolbar("chrome-action");
|
|
2832
|
+
}, [activeRuntime, activeSelectionTool, dismissSelectionToolbar]);
|
|
2833
|
+
|
|
2834
|
+
const rejectActiveSuggestion = useCallback(() => {
|
|
2835
|
+
if (activeSelectionTool?.kind !== "suggestion-review") {
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
for (const changeId of activeSelectionTool.changeIds) {
|
|
2839
|
+
activeRuntime.rejectChange(changeId);
|
|
2840
|
+
}
|
|
2841
|
+
dismissSelectionToolbar("chrome-action");
|
|
2842
|
+
}, [activeRuntime, activeSelectionTool, dismissSelectionToolbar]);
|
|
2843
|
+
|
|
2844
|
+
const editActiveSuggestion = useCallback(() => {
|
|
2845
|
+
if (activeSelectionTool?.kind !== "suggestion-review") {
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
setSuppressedSuggestionRevisionId(activeSelectionTool.suggestionId);
|
|
2849
|
+
const activeSuggestion = suggestionsSnapshot.suggestions.find(
|
|
2850
|
+
(suggestion) => suggestion.suggestionId === activeSelectionTool.suggestionId,
|
|
2851
|
+
);
|
|
2852
|
+
if (activeSuggestion) {
|
|
2853
|
+
applyRuntimeSelection(
|
|
2854
|
+
activeRuntime,
|
|
2855
|
+
createSelectionFromAnchor(activeSuggestion.anchor, activeSuggestion.storyTarget),
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
setSelectionToolbarFocusWithin(true);
|
|
2859
|
+
}, [activeRuntime, activeSelectionTool, suggestionsSnapshot.suggestions]);
|
|
2752
2860
|
|
|
2753
2861
|
const handleSelectionToolbarAnchorChange = useCallback(
|
|
2754
2862
|
(nextAnchor: SelectionToolbarAnchor | null) => {
|
|
@@ -3140,6 +3248,19 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3140
3248
|
focusAnchor(revision.anchor, revision.storyTarget);
|
|
3141
3249
|
setActiveRailTab("changes");
|
|
3142
3250
|
},
|
|
3251
|
+
onReplyToRevision: (revision: typeof snapshot.trackedChanges.revisions[number]) => {
|
|
3252
|
+
const linkedThread = activeRuntime.ensureCommentThreadForChange(
|
|
3253
|
+
revision.revisionId,
|
|
3254
|
+
currentUser.userId,
|
|
3255
|
+
);
|
|
3256
|
+
if (!linkedThread?.commentId) {
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
setActiveRevisionId(revision.revisionId);
|
|
3260
|
+
focusAnchor(revision.anchor, revision.storyTarget);
|
|
3261
|
+
activeRuntime.openComment(linkedThread.commentId);
|
|
3262
|
+
setActiveRailTab("comments");
|
|
3263
|
+
},
|
|
3143
3264
|
onAcceptRevision: (revisionId: string) => {
|
|
3144
3265
|
activeRuntime.acceptChange(revisionId);
|
|
3145
3266
|
setActiveRailTab("changes");
|
|
@@ -3166,7 +3287,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3166
3287
|
onWorkspaceModeChange: (mode) => activeRuntime.setWorkspaceMode(mode),
|
|
3167
3288
|
onZoomChange: (level) => activeRuntime.setZoom(level),
|
|
3168
3289
|
onActiveRailTabChange: setActiveRailTab,
|
|
3169
|
-
onShowTrackedChangesChange:
|
|
3290
|
+
onShowTrackedChangesChange: setTrackedChangesAuthoring,
|
|
3291
|
+
onReviewMarkupModeChange: setReviewMarkupMode,
|
|
3292
|
+
onChromePinChange: (surface, pin) => activeRuntime.setChromePin(surface, pin),
|
|
3170
3293
|
onToggleBold: () =>
|
|
3171
3294
|
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" }),
|
|
3172
3295
|
onToggleItalic: () =>
|
|
@@ -3263,10 +3386,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3263
3386
|
// with a mount-time `console.warn`; hosts that still pass them can
|
|
3264
3387
|
// migrate to `onOpenStory` at their leisure.
|
|
3265
3388
|
onOpenHeaderStoryForPage: (pageIndex: number) =>
|
|
3266
|
-
|
|
3389
|
+
isPageWorkspace
|
|
3390
|
+
? openStoryForPage(activeRuntime, pageIndex, "header")
|
|
3391
|
+
: undefined,
|
|
3267
3392
|
onOpenFooterStoryForPage: (pageIndex: number) =>
|
|
3268
|
-
|
|
3393
|
+
isPageWorkspace
|
|
3394
|
+
? openStoryForPage(activeRuntime, pageIndex, "footer")
|
|
3395
|
+
: undefined,
|
|
3269
3396
|
onOpenStory: (target) => {
|
|
3397
|
+
if (
|
|
3398
|
+
!isPageWorkspace &&
|
|
3399
|
+
(target.kind === "header" || target.kind === "footer")
|
|
3400
|
+
) {
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3270
3403
|
activeRuntime.openStory(target);
|
|
3271
3404
|
},
|
|
3272
3405
|
onDeleteSectionBreak: (sectionIndex) =>
|
|
@@ -3369,6 +3502,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3369
3502
|
onInsertPageBreak: commands.onInsertPageBreak,
|
|
3370
3503
|
onInsertSectionBreak: (type) => commands.onInsertSectionBreak?.(type),
|
|
3371
3504
|
onInsertTable: commands.onInsertTable,
|
|
3505
|
+
onInsertImage: commands.onInsertImage
|
|
3506
|
+
? () => requestImageInsertFromPicker(commands.onInsertImage)
|
|
3507
|
+
: undefined,
|
|
3372
3508
|
onAddComment: commands.onAddComment,
|
|
3373
3509
|
onFindRequested: onFindRequested
|
|
3374
3510
|
? () => onFindRequested({ selectionText: "", selectionRange: snapshot.selection })
|
|
@@ -3512,6 +3648,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3512
3648
|
activeRailTab={activeRailTab}
|
|
3513
3649
|
activeCommentId={snapshot.comments.activeCommentId}
|
|
3514
3650
|
activeRevisionId={activeRevisionId}
|
|
3651
|
+
trackedChangesAuthoringEnabled={trackedChangesAuthoringEnabled}
|
|
3515
3652
|
showTrackedChanges={showTrackedChanges}
|
|
3516
3653
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
3517
3654
|
layoutFacet={activeRuntime.layout}
|
|
@@ -3546,35 +3683,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3546
3683
|
onAddCommentFromSelection={addSelectionToolbarComment}
|
|
3547
3684
|
onAddCommentFromSuggestion={addSelectionToolbarComment}
|
|
3548
3685
|
onAcceptSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
3549
|
-
?
|
|
3550
|
-
for (const changeId of activeSelectionTool.changeIds) {
|
|
3551
|
-
activeRuntime.acceptChange(changeId);
|
|
3552
|
-
}
|
|
3553
|
-
dismissSelectionToolbar("chrome-action");
|
|
3554
|
-
}
|
|
3686
|
+
? acceptActiveSuggestion
|
|
3555
3687
|
: undefined}
|
|
3556
3688
|
onRejectSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
3557
|
-
?
|
|
3558
|
-
for (const changeId of activeSelectionTool.changeIds) {
|
|
3559
|
-
activeRuntime.rejectChange(changeId);
|
|
3560
|
-
}
|
|
3561
|
-
dismissSelectionToolbar("chrome-action");
|
|
3562
|
-
}
|
|
3689
|
+
? rejectActiveSuggestion
|
|
3563
3690
|
: undefined}
|
|
3564
3691
|
onEditSuggestion={activeSelectionTool?.kind === "suggestion-review"
|
|
3565
|
-
?
|
|
3566
|
-
setSuppressedSuggestionRevisionId(activeSelectionTool.suggestionId);
|
|
3567
|
-
const activeSuggestion = suggestionsSnapshot.suggestions.find(
|
|
3568
|
-
(suggestion) => suggestion.suggestionId === activeSelectionTool.suggestionId,
|
|
3569
|
-
);
|
|
3570
|
-
if (activeSuggestion) {
|
|
3571
|
-
applyRuntimeSelection(
|
|
3572
|
-
activeRuntime,
|
|
3573
|
-
createSelectionFromAnchor(activeSuggestion.anchor, activeSuggestion.storyTarget),
|
|
3574
|
-
);
|
|
3575
|
-
}
|
|
3576
|
-
setSelectionToolbarFocusWithin(true);
|
|
3577
|
-
}
|
|
3692
|
+
? editActiveSuggestion
|
|
3578
3693
|
: undefined}
|
|
3579
3694
|
onDismissSelectionToolbar={() => dismissSelectionToolbar("chrome-action")}
|
|
3580
3695
|
onSelectionToolbarFocusCapture={handleSelectionToolbarFocusCapture}
|
|
@@ -4216,6 +4331,42 @@ function resolveCommentCommandAnchor(
|
|
|
4216
4331
|
: selection.activeRange;
|
|
4217
4332
|
}
|
|
4218
4333
|
|
|
4334
|
+
function requestImageInsertFromPicker(
|
|
4335
|
+
onInsertImage: ((options: InsertImageOptions) => void) | undefined,
|
|
4336
|
+
): void {
|
|
4337
|
+
const document = globalThis.document;
|
|
4338
|
+
if (!onInsertImage || typeof document?.createElement !== "function" || !document.body) {
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
const input = document.createElement("input");
|
|
4343
|
+
input.type = "file";
|
|
4344
|
+
input.accept = "image/png,image/jpeg,image/gif";
|
|
4345
|
+
input.style.position = "fixed";
|
|
4346
|
+
input.style.left = "-9999px";
|
|
4347
|
+
input.style.top = "-9999px";
|
|
4348
|
+
input.addEventListener("change", () => {
|
|
4349
|
+
const file = input.files?.[0];
|
|
4350
|
+
if (!file) {
|
|
4351
|
+
input.remove();
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
void file.arrayBuffer()
|
|
4355
|
+
.then((buffer) => {
|
|
4356
|
+
onInsertImage({
|
|
4357
|
+
data: new Uint8Array(buffer),
|
|
4358
|
+
mimeType: file.type || "image/png",
|
|
4359
|
+
altText: file.name,
|
|
4360
|
+
});
|
|
4361
|
+
})
|
|
4362
|
+
.finally(() => {
|
|
4363
|
+
input.remove();
|
|
4364
|
+
});
|
|
4365
|
+
}, { once: true });
|
|
4366
|
+
document.body.appendChild(input);
|
|
4367
|
+
input.click();
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4219
4370
|
function resolveCollapsedCommentRange(
|
|
4220
4371
|
surface: RuntimeRenderSnapshot["surface"],
|
|
4221
4372
|
selection: RuntimeRenderSnapshot["selection"],
|
|
@@ -4740,6 +4891,12 @@ function buildSuggestionCardModel(args: {
|
|
|
4740
4891
|
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedSuggestion.canReject,
|
|
4741
4892
|
canEditSuggestion: canReviewSuggestion && focusedSuggestion.editable,
|
|
4742
4893
|
canAddComment,
|
|
4894
|
+
...(focusedSuggestion.commentThreadIds
|
|
4895
|
+
? { commentThreadIds: focusedSuggestion.commentThreadIds }
|
|
4896
|
+
: {}),
|
|
4897
|
+
...(focusedSuggestion.replyCount !== undefined
|
|
4898
|
+
? { replyCount: focusedSuggestion.replyCount }
|
|
4899
|
+
: {}),
|
|
4743
4900
|
...(disabledReason ? { disabledReason } : {}),
|
|
4744
4901
|
};
|
|
4745
4902
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ChromePinSurface,
|
|
4
5
|
CommentSidebarThreadSnapshot,
|
|
5
6
|
EditorStoryTarget,
|
|
6
7
|
FormattingAlignment,
|
|
@@ -9,11 +10,13 @@ import type {
|
|
|
9
10
|
SectionBreakType,
|
|
10
11
|
SectionLayoutPatch,
|
|
11
12
|
SectionPageNumberingPatch,
|
|
13
|
+
PinState,
|
|
12
14
|
TrackedChangeEntrySnapshot,
|
|
13
15
|
ZoomLevel,
|
|
14
16
|
WorkspaceMode,
|
|
15
17
|
} from "../api/public-types.ts";
|
|
16
18
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail.tsx";
|
|
19
|
+
import type { MarkupDisplay } from "./headless/comment-decoration-model.ts";
|
|
17
20
|
|
|
18
21
|
type CommandHandler = (...args: any[]) => unknown;
|
|
19
22
|
|
|
@@ -22,6 +25,8 @@ export interface EditorCommandBag {
|
|
|
22
25
|
onZoomChange?(level: ZoomLevel): void;
|
|
23
26
|
onActiveRailTabChange(value: ReviewRailTab): void;
|
|
24
27
|
onShowTrackedChangesChange(show: boolean): void;
|
|
28
|
+
onReviewMarkupModeChange?(mode: MarkupDisplay): void;
|
|
29
|
+
onChromePinChange?(surface: ChromePinSurface, pin: PinState | null): void;
|
|
25
30
|
onUndo(): void;
|
|
26
31
|
onRedo(): void;
|
|
27
32
|
onSetParagraphStyle?(styleId: string): void;
|
|
@@ -83,6 +88,7 @@ export interface EditorCommandBag {
|
|
|
83
88
|
onAddReply?(commentId: string, body: string): void;
|
|
84
89
|
onEditBody?(commentId: string, body: string): void;
|
|
85
90
|
onOpenRevision(revision: TrackedChangeEntrySnapshot): void;
|
|
91
|
+
onReplyToRevision?(revision: TrackedChangeEntrySnapshot): void;
|
|
86
92
|
onAcceptRevision(revisionId: string): void;
|
|
87
93
|
onRejectRevision(revisionId: string): void;
|
|
88
94
|
onAcceptAllChanges(): void;
|
|
@@ -102,7 +108,7 @@ export interface EditorCommandBag {
|
|
|
102
108
|
/** Open the footer story for a specific page (double-click on its band). */
|
|
103
109
|
onOpenFooterStoryForPage?(pageIndex: number): void;
|
|
104
110
|
/**
|
|
105
|
-
* P8.11 — per-page header/footer band click handler.
|
|
111
|
+
* P8.11 — per-page header/footer band double-click handler. Receives the
|
|
106
112
|
* exact `EditorStoryTarget` the band represents; the command bag wires
|
|
107
113
|
* this to `runtime.openStory(target)`.
|
|
108
114
|
*/
|
|
@@ -55,6 +55,7 @@ export interface EditorShellViewProps {
|
|
|
55
55
|
activeRailTab: ReviewRailTab;
|
|
56
56
|
activeCommentId?: string;
|
|
57
57
|
activeRevisionId?: string;
|
|
58
|
+
trackedChangesAuthoringEnabled?: boolean;
|
|
58
59
|
showTrackedChanges: boolean;
|
|
59
60
|
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
60
61
|
/**
|
|
@@ -225,6 +225,19 @@ export function buildClassFromRevisionDisplay(
|
|
|
225
225
|
parts.push("text-secondary");
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Formatting/property-change revisions carry their semantics through
|
|
229
|
+
// `kind` even when markup posture has no underline/strike flag. Give
|
|
230
|
+
// mounted suggestion authoring a visible, non-destructive cue instead
|
|
231
|
+
// of silently relying on the sidebar/card path.
|
|
232
|
+
if (
|
|
233
|
+
parts.length === 0 &&
|
|
234
|
+
(display.kind === "formatting" || display.kind === "property-change")
|
|
235
|
+
) {
|
|
236
|
+
parts.push(
|
|
237
|
+
"underline decoration-accent/70 decoration-dotted decoration-1 underline-offset-2",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
// Surface the author palette color as a CSS variable the renderer
|
|
229
242
|
// can pick up via `var(--wre-revision-author)`. Consumer stylesheet
|
|
230
243
|
// composes this into ring/underline color when the palette slot is
|
|
@@ -81,6 +81,8 @@ export interface SuggestionReviewSelectionToolModel extends BaseSelectionToolMod
|
|
|
81
81
|
canReject: boolean;
|
|
82
82
|
canEditSuggestion: boolean;
|
|
83
83
|
canAddComment: boolean;
|
|
84
|
+
commentThreadIds?: string[];
|
|
85
|
+
replyCount?: number;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
export type StructureContextKind = "table" | "image" | "object" | "list";
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
* contextual actions exist, keyed by `TargetKind`.
|
|
4
4
|
*
|
|
5
5
|
* Consumed by three access routes so DESIGN-EDITOR.md §6.4 holds by
|
|
6
|
-
* construction
|
|
7
|
-
* cannot duplicate command trees"):
|
|
6
|
+
* construction for general editor actions:
|
|
8
7
|
*
|
|
9
8
|
* 1. Right-click context menu (via `build-context-menu-entries.ts`)
|
|
10
9
|
* 2. Inline "More…" affordance on reduced floating toolbars (Phase D)
|
|
11
10
|
* 3. Command palette groups (Phase C.δ)
|
|
12
11
|
*
|
|
13
|
-
* All three routes produce the same actions from this registry
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* All three routes produce the same general actions from this registry.
|
|
13
|
+
* Suggestion-specific review actions are intentionally absent for now:
|
|
14
|
+
* they require an exact revision/suggestion id payload, so they live on
|
|
15
|
+
* the floating suggestion card and Changes rail until the context-menu
|
|
16
|
+
* target resolver can supply that payload.
|
|
16
17
|
*
|
|
17
18
|
* Perf discipline: pure data + pure filter helpers. No DOM reads, no
|
|
18
19
|
* runtime calls, no observers. Actions are dispatched through host-
|
|
@@ -106,10 +107,6 @@ export interface EditorActionHostCallbacks {
|
|
|
106
107
|
readonly onPrintRequested?: () => void;
|
|
107
108
|
readonly onGoToRequested?: () => void;
|
|
108
109
|
|
|
109
|
-
// Tracked-change operations (suggestion target)
|
|
110
|
-
readonly onAcceptSuggestion?: () => void;
|
|
111
|
-
readonly onRejectSuggestion?: () => void;
|
|
112
|
-
|
|
113
110
|
// Comment operations (comment-anchor target)
|
|
114
111
|
readonly onResolveComment?: () => void;
|
|
115
112
|
readonly onReplyToComment?: () => void;
|
|
@@ -576,7 +573,7 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
576
573
|
mkImportant({
|
|
577
574
|
id: "insert-image",
|
|
578
575
|
label: "Insert image…",
|
|
579
|
-
description: "
|
|
576
|
+
description: "Choose an image file and insert it at the caret.",
|
|
580
577
|
group: "misc",
|
|
581
578
|
targetKinds: [],
|
|
582
579
|
callback: "onInsertImage",
|
|
@@ -636,22 +633,6 @@ export const EDITOR_ACTION_REGISTRY: readonly EditorAction[] = [
|
|
|
636
633
|
callback: "onPrintRequested",
|
|
637
634
|
}),
|
|
638
635
|
|
|
639
|
-
// -------- Suggestion / tracked change --------
|
|
640
|
-
mk({
|
|
641
|
-
id: "accept-suggestion",
|
|
642
|
-
label: "Accept suggestion",
|
|
643
|
-
group: "suggestion",
|
|
644
|
-
targetKinds: ["suggestion"],
|
|
645
|
-
callback: "onAcceptSuggestion",
|
|
646
|
-
}),
|
|
647
|
-
mk({
|
|
648
|
-
id: "reject-suggestion",
|
|
649
|
-
label: "Reject suggestion",
|
|
650
|
-
group: "suggestion",
|
|
651
|
-
targetKinds: ["suggestion"],
|
|
652
|
-
callback: "onRejectSuggestion",
|
|
653
|
-
}),
|
|
654
|
-
|
|
655
636
|
// -------- Comment --------
|
|
656
637
|
mk({
|
|
657
638
|
id: "resolve-comment",
|
|
@@ -43,6 +43,10 @@ export function TwDetachHandle(props: TwDetachHandleProps): React.JSX.Element {
|
|
|
43
43
|
const { surface, pin, onChange, label } = props;
|
|
44
44
|
const isDetached = pin?.detached ?? false;
|
|
45
45
|
const offset = pin?.offset ?? { x: 0, y: 0 };
|
|
46
|
+
const dragHandleTestId =
|
|
47
|
+
surface === "selectionTier" ? "selection-tool-drag-handle" : `${surface}-detach-drag-handle`;
|
|
48
|
+
const toggleTestId =
|
|
49
|
+
surface === "selectionTier" ? "selection-tool-attach-toggle" : `${surface}-detach-toggle`;
|
|
46
50
|
const dragState = useRef<
|
|
47
51
|
| {
|
|
48
52
|
startX: number;
|
|
@@ -117,7 +121,7 @@ export function TwDetachHandle(props: TwDetachHandleProps): React.JSX.Element {
|
|
|
117
121
|
<button
|
|
118
122
|
type="button"
|
|
119
123
|
aria-label={isDetached ? "Drag floating menu" : "Drag to float menu"}
|
|
120
|
-
data-testid={
|
|
124
|
+
data-testid={dragHandleTestId}
|
|
121
125
|
className="inline-flex h-6 items-center justify-center rounded-md border border-transparent px-1.5 text-tertiary transition-colors hover:border-border/60 hover:bg-surface hover:text-primary"
|
|
122
126
|
onMouseDown={beginDrag}
|
|
123
127
|
>
|
|
@@ -133,7 +137,7 @@ export function TwDetachHandle(props: TwDetachHandleProps): React.JSX.Element {
|
|
|
133
137
|
type="button"
|
|
134
138
|
aria-label={isDetached ? "Dock menu" : "Float menu"}
|
|
135
139
|
aria-pressed={isDetached}
|
|
136
|
-
data-testid={
|
|
140
|
+
data-testid={toggleTestId}
|
|
137
141
|
className="inline-flex h-6 items-center rounded-md border border-border/60 px-2 text-[10px] font-medium text-secondary transition-colors hover:bg-surface hover:text-primary"
|
|
138
142
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
139
143
|
onClick={toggle}
|
|
@@ -32,9 +32,12 @@ const focusRingClass =
|
|
|
32
32
|
export function TwSuggestionCard(props: TwSuggestionCardProps) {
|
|
33
33
|
const contextLabel = summarizeSuggestionContext(props.model);
|
|
34
34
|
const commentDisabled = !props.model.canAddComment;
|
|
35
|
+
const replyCount = props.model.replyCount ?? 0;
|
|
35
36
|
const tooltipLabel = commentDisabled
|
|
36
37
|
? props.model.disabledReason ?? "Commenting is unavailable for this selection"
|
|
37
|
-
:
|
|
38
|
+
: props.model.commentThreadIds?.length
|
|
39
|
+
? "Reply to tracked change"
|
|
40
|
+
: "Start tracked-change discussion";
|
|
38
41
|
|
|
39
42
|
return (
|
|
40
43
|
<div
|
|
@@ -87,14 +90,15 @@ export function TwSuggestionCard(props: TwSuggestionCardProps) {
|
|
|
87
90
|
<Tooltip.Trigger asChild>
|
|
88
91
|
<button
|
|
89
92
|
type="button"
|
|
90
|
-
aria-label="
|
|
93
|
+
aria-label="Reply to tracked change"
|
|
94
|
+
data-testid="suggestion-card-reply"
|
|
91
95
|
disabled={commentDisabled}
|
|
92
96
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
93
97
|
onClick={props.onAddComment}
|
|
94
98
|
className={`inline-flex h-7 items-center gap-1 rounded-md border border-[var(--color-border-default)] px-2 text-[11px] font-medium text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-hover)] disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
95
99
|
>
|
|
96
100
|
<MessageSquare className="h-3 w-3" />
|
|
97
|
-
|
|
101
|
+
Reply{replyCount > 0 ? ` ${replyCount}` : ""}
|
|
98
102
|
</button>
|
|
99
103
|
</Tooltip.Trigger>
|
|
100
104
|
<Tooltip.Portal>
|