@beyondwork/docx-react-component 1.0.19 → 1.0.20
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/public-types.ts +336 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +14 -2
- package/src/core/search/search-text.ts +28 -0
- package/src/core/state/editor-state.ts +3 -0
- package/src/index.ts +21 -0
- package/src/io/docx-session.ts +363 -17
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +83 -3
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +82 -8
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +240 -2
- package/src/io/ooxml/parse-headers-footers.ts +431 -7
- package/src/io/ooxml/parse-inline-media.ts +15 -1
- package/src/io/ooxml/parse-main-document.ts +396 -14
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +117 -1
- package/src/model/snapshot.ts +85 -1
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-navigation.ts +52 -13
- package/src/runtime/document-runtime.ts +1521 -75
- package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
- package/src/runtime/session-capabilities.ts +33 -3
- package/src/runtime/surface-projection.ts +86 -25
- package/src/runtime/table-schema.ts +2 -2
- package/src/runtime/view-state.ts +24 -6
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +850 -1315
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1422 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +51 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +27 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +174 -48
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
- package/src/validation/compatibility-engine.ts +27 -4
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/docx-comment-proof.ts +220 -0
|
@@ -18,10 +18,17 @@ import type {
|
|
|
18
18
|
DocumentNavigationSnapshot,
|
|
19
19
|
EditorViewStateSnapshot,
|
|
20
20
|
FormattingStateSnapshot,
|
|
21
|
+
FormattingAlignment,
|
|
22
|
+
HeaderFooterLinkPatch,
|
|
23
|
+
InteractionGuardSnapshot,
|
|
24
|
+
InsertImageOptions,
|
|
21
25
|
RuntimeRenderSnapshot,
|
|
26
|
+
SectionPageNumberingPatch,
|
|
27
|
+
SectionBreakType,
|
|
22
28
|
StyleCatalogSnapshot,
|
|
23
29
|
SurfaceBlockSnapshot,
|
|
24
30
|
TrackedChangeEntrySnapshot,
|
|
31
|
+
WorkflowScopeSnapshot,
|
|
25
32
|
WorkspaceMode,
|
|
26
33
|
ZoomLevel,
|
|
27
34
|
} from "../api/public-types";
|
|
@@ -33,16 +40,26 @@ import {
|
|
|
33
40
|
estimateParagraphLineHeight,
|
|
34
41
|
getUsableColumnWidth,
|
|
35
42
|
} from "../runtime/page-layout-estimation.ts";
|
|
43
|
+
import {
|
|
44
|
+
incrementInvalidationCounter,
|
|
45
|
+
recordPerfSample,
|
|
46
|
+
} from "./editor-surface/perf-probe.ts";
|
|
47
|
+
import { computeLineMarkersIfEnabled } from "./page-chrome-model.ts";
|
|
36
48
|
import type { SessionCapabilities } from "../runtime/session-capabilities";
|
|
37
49
|
import type {
|
|
38
50
|
SelectionToolbarAnchor,
|
|
39
51
|
SelectionToolbarModel,
|
|
40
52
|
} from "../ui/headless/selection-toolbar-model";
|
|
41
53
|
import type { MarkupDisplay } from "../ui/headless/comment-decoration-model";
|
|
54
|
+
import type { EditorCommandBag } from "../ui/editor-command-bag.ts";
|
|
42
55
|
import { preserveEditorSelectionMouseDown } from "../ui/headless/preserve-editor-selection";
|
|
43
56
|
import { TwAlertBanner } from "./chrome/tw-alert-banner";
|
|
57
|
+
import { TwImageContextToolbar, type ActiveImageContext } from "./chrome/tw-image-context-toolbar";
|
|
58
|
+
import { TwLayoutPanel } from "./chrome/tw-layout-panel";
|
|
59
|
+
import { TwObjectContextToolbar, type ActiveObjectContext } from "./chrome/tw-object-context-toolbar";
|
|
44
60
|
import { TwPageRuler } from "./chrome/tw-page-ruler";
|
|
45
61
|
import { TwSelectionToolbar } from "./chrome/tw-selection-toolbar";
|
|
62
|
+
import { TwTableContextToolbar } from "./chrome/tw-table-context-toolbar";
|
|
46
63
|
import { TwReviewRail, type ReviewRailTab } from "./review/tw-review-rail";
|
|
47
64
|
import { TwStatusBar } from "./status/tw-status-bar";
|
|
48
65
|
import { TwToolbar } from "./toolbar/tw-toolbar";
|
|
@@ -50,6 +67,7 @@ import { TwToolbar } from "./toolbar/tw-toolbar";
|
|
|
50
67
|
export interface TwReviewWorkspaceProps {
|
|
51
68
|
snapshot: RuntimeRenderSnapshot;
|
|
52
69
|
viewState: EditorViewStateSnapshot;
|
|
70
|
+
markupDisplay: MarkupDisplay;
|
|
53
71
|
currentUserId?: string;
|
|
54
72
|
capabilities?: SessionCapabilities;
|
|
55
73
|
reviewMode?: "editing" | "review";
|
|
@@ -62,38 +80,109 @@ export interface TwReviewWorkspaceProps {
|
|
|
62
80
|
activeCommentId?: string;
|
|
63
81
|
activeRevisionId?: string;
|
|
64
82
|
showTrackedChanges: boolean;
|
|
83
|
+
workflowScopeSnapshot?: WorkflowScopeSnapshot | null;
|
|
84
|
+
interactionGuardSnapshot?: InteractionGuardSnapshot;
|
|
85
|
+
commands: EditorCommandBag;
|
|
65
86
|
selectionToolbar?: SelectionToolbarModel | null;
|
|
66
87
|
selectionToolbarAnchor?: SelectionToolbarAnchor | null;
|
|
67
88
|
documentNavigation?: DocumentNavigationSnapshot;
|
|
68
|
-
onWorkspaceModeChange
|
|
89
|
+
onWorkspaceModeChange?: (value: WorkspaceMode) => void;
|
|
69
90
|
onZoomChange?: (level: ZoomLevel) => void;
|
|
70
|
-
onActiveRailTabChange
|
|
71
|
-
onShowTrackedChangesChange
|
|
72
|
-
onUndo
|
|
73
|
-
onRedo
|
|
91
|
+
onActiveRailTabChange?: (value: ReviewRailTab) => void;
|
|
92
|
+
onShowTrackedChangesChange?: (show: boolean) => void;
|
|
93
|
+
onUndo?: () => void;
|
|
94
|
+
onRedo?: () => void;
|
|
74
95
|
onSetParagraphStyle?: (styleId: string) => void;
|
|
75
96
|
onToggleBold?: () => void;
|
|
76
97
|
onToggleItalic?: () => void;
|
|
77
98
|
onToggleUnderline?: () => void;
|
|
99
|
+
onSetSelectionTextColor?: (color: string) => void;
|
|
100
|
+
onSetSelectionHighlightColor?: (color: string | null) => void;
|
|
101
|
+
onToggleStrikethrough?: () => void;
|
|
102
|
+
onToggleSuperscript?: () => void;
|
|
103
|
+
onToggleSubscript?: () => void;
|
|
104
|
+
onSetFontFamily?: (fontFamily: string) => void;
|
|
105
|
+
onSetFontSize?: (fontSize: number) => void;
|
|
106
|
+
onSetTextColor?: (color: string) => void;
|
|
107
|
+
onSetHighlightColor?: (color: string | null) => void;
|
|
108
|
+
onSetAlignment?: (alignment: FormattingAlignment) => void;
|
|
78
109
|
onOutdent?: () => void;
|
|
79
110
|
onIndent?: () => void;
|
|
80
|
-
onAddComment
|
|
111
|
+
onAddComment?: () => void;
|
|
112
|
+
onInsertPageBreak?: () => void;
|
|
113
|
+
onInsertTable?: () => void;
|
|
114
|
+
onInsertSectionBreak?: (type: SectionBreakType) => void;
|
|
115
|
+
onInsertImage?: (options: InsertImageOptions) => void;
|
|
116
|
+
onSetTableStyle?: (styleId: string) => void;
|
|
117
|
+
onAddRowBefore?: () => void;
|
|
118
|
+
onAddRowAfter?: () => void;
|
|
119
|
+
onAddColumnBefore?: () => void;
|
|
120
|
+
onAddColumnAfter?: () => void;
|
|
121
|
+
onDeleteRow?: () => void;
|
|
122
|
+
onDeleteColumn?: () => void;
|
|
123
|
+
onDeleteTable?: () => void;
|
|
124
|
+
onMergeCells?: () => void;
|
|
125
|
+
onSplitCell?: () => void;
|
|
126
|
+
onSetCellBackground?: (color: string) => void;
|
|
127
|
+
activeImageContext?: ActiveImageContext | null;
|
|
128
|
+
activeObjectContext?: ActiveObjectContext | null;
|
|
129
|
+
onSetImageLayout?: (
|
|
130
|
+
mediaId: string,
|
|
131
|
+
dimensions: { widthEmu: number; heightEmu: number },
|
|
132
|
+
) => void;
|
|
133
|
+
onSetImageFrame?: (
|
|
134
|
+
mediaId: string,
|
|
135
|
+
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
136
|
+
) => void;
|
|
137
|
+
onDeleteSectionBreak?: (sectionIndex: number) => void;
|
|
138
|
+
onUpdateSectionLayout?: (
|
|
139
|
+
sectionIndex: number,
|
|
140
|
+
patch: {
|
|
141
|
+
pageSize?: { width?: number; height?: number; orientation?: "portrait" | "landscape" };
|
|
142
|
+
pageMargins?: {
|
|
143
|
+
top?: number;
|
|
144
|
+
right?: number;
|
|
145
|
+
bottom?: number;
|
|
146
|
+
left?: number;
|
|
147
|
+
header?: number;
|
|
148
|
+
footer?: number;
|
|
149
|
+
gutter?: number;
|
|
150
|
+
};
|
|
151
|
+
columns?: {
|
|
152
|
+
count?: number;
|
|
153
|
+
space?: number;
|
|
154
|
+
equalWidth?: boolean;
|
|
155
|
+
columns?: Array<{ width: number; space?: number }>;
|
|
156
|
+
separator?: boolean;
|
|
157
|
+
};
|
|
158
|
+
titlePage?: boolean;
|
|
159
|
+
sectionType?: SectionBreakType;
|
|
160
|
+
},
|
|
161
|
+
) => void;
|
|
162
|
+
onSetSectionPageNumbering?: (
|
|
163
|
+
sectionIndex: number,
|
|
164
|
+
patch: SectionPageNumberingPatch | null,
|
|
165
|
+
) => void;
|
|
166
|
+
onSetHeaderFooterLink?: (
|
|
167
|
+
sectionIndex: number,
|
|
168
|
+
patch: HeaderFooterLinkPatch,
|
|
169
|
+
) => void;
|
|
81
170
|
onAddCommentFromSelection?: () => void;
|
|
82
|
-
onExport
|
|
171
|
+
onExport?: () => void;
|
|
83
172
|
onDismissSelectionToolbar?: () => void;
|
|
84
173
|
onSelectionToolbarFocusCapture?: FocusEventHandler<HTMLDivElement>;
|
|
85
174
|
onSelectionToolbarBlurCapture?: FocusEventHandler<HTMLDivElement>;
|
|
86
175
|
selectionToolbarRef?: Ref<HTMLDivElement>;
|
|
87
|
-
onOpenComment
|
|
88
|
-
onResolveComment
|
|
176
|
+
onOpenComment?: (thread: CommentSidebarThreadSnapshot) => void;
|
|
177
|
+
onResolveComment?: (commentId: string) => void;
|
|
89
178
|
onReopenComment?: (commentId: string) => void;
|
|
90
179
|
onAddReply?: (commentId: string, body: string) => void;
|
|
91
180
|
onEditBody?: (commentId: string, body: string) => void;
|
|
92
|
-
onOpenRevision
|
|
93
|
-
onAcceptRevision
|
|
94
|
-
onRejectRevision
|
|
95
|
-
onAcceptAllChanges
|
|
96
|
-
onRejectAllChanges
|
|
181
|
+
onOpenRevision?: (revision: TrackedChangeEntrySnapshot) => void;
|
|
182
|
+
onAcceptRevision?: (revisionId: string) => void;
|
|
183
|
+
onRejectRevision?: (revisionId: string) => void;
|
|
184
|
+
onAcceptAllChanges?: () => void;
|
|
185
|
+
onRejectAllChanges?: () => void;
|
|
97
186
|
onCloseStory?: () => void;
|
|
98
187
|
onOpenHeaderStory?: () => void;
|
|
99
188
|
onOpenFooterStory?: () => void;
|
|
@@ -109,12 +198,16 @@ export interface TwReviewWorkspaceProps {
|
|
|
109
198
|
onNavigateHeading?: (headingId: string) => void;
|
|
110
199
|
}
|
|
111
200
|
|
|
112
|
-
export function TwReviewWorkspace(
|
|
201
|
+
export function TwReviewWorkspace(inputProps: TwReviewWorkspaceProps) {
|
|
202
|
+
const props = {
|
|
203
|
+
...inputProps,
|
|
204
|
+
...inputProps.commands,
|
|
205
|
+
} as TwReviewWorkspaceProps & EditorCommandBag;
|
|
113
206
|
const { snapshot, viewState } = props;
|
|
114
207
|
const selectionToolbarRootRef = useRef<HTMLDivElement>(null);
|
|
115
208
|
const caps = props.capabilities;
|
|
116
209
|
const isPageWorkspace = props.workspaceMode === "page";
|
|
117
|
-
const markupDisplay
|
|
210
|
+
const markupDisplay = props.markupDisplay;
|
|
118
211
|
const [navOpen, setNavOpen] = useState(false);
|
|
119
212
|
const [layoutToolsOpen, setLayoutToolsOpen] = useState(false);
|
|
120
213
|
const zoomLevel = props.zoomLevel ?? 100;
|
|
@@ -123,13 +216,33 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
123
216
|
snapshot.compatibility.featureEntries.filter(
|
|
124
217
|
(entry) => entry.featureClass === "preserve-only",
|
|
125
218
|
).length;
|
|
219
|
+
const blockedReasons =
|
|
220
|
+
props.interactionGuardSnapshot?.blockedReasons ??
|
|
221
|
+
props.workflowScopeSnapshot?.blockedReasons ??
|
|
222
|
+
[];
|
|
126
223
|
const showReviewRail = caps?.reviewRailVisible ?? true;
|
|
127
224
|
const headings = props.documentNavigation?.headings ?? [];
|
|
225
|
+
const headerVariant = snapshot.pageLayout?.headerVariants[0]?.variant ?? "default";
|
|
226
|
+
const footerVariant = snapshot.pageLayout?.footerVariants[0]?.variant ?? "default";
|
|
128
227
|
const selectionPosition =
|
|
129
228
|
viewState.selection.activeRange.kind === "node"
|
|
130
229
|
? viewState.selection.activeRange.at
|
|
131
230
|
: viewState.selection.head;
|
|
132
|
-
const activeParagraphLayout =
|
|
231
|
+
const activeParagraphLayout = useMemo(
|
|
232
|
+
() => resolveActiveParagraphLayout(snapshot.surface, selectionPosition),
|
|
233
|
+
[selectionPosition, snapshot.surface],
|
|
234
|
+
);
|
|
235
|
+
const isTableContext = Boolean(
|
|
236
|
+
props.formattingState?.breadcrumb.some((item) => item.kind === "table" || item.kind === "table_cell" || item.kind === "table_row"),
|
|
237
|
+
);
|
|
238
|
+
const contextualSurface =
|
|
239
|
+
props.activeImageContext
|
|
240
|
+
? "image"
|
|
241
|
+
: props.activeObjectContext
|
|
242
|
+
? "object"
|
|
243
|
+
: isTableContext
|
|
244
|
+
? "table"
|
|
245
|
+
: null;
|
|
133
246
|
const pageChromeModel = useMemo(
|
|
134
247
|
() =>
|
|
135
248
|
buildPageChromeModel(
|
|
@@ -155,6 +268,11 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
155
268
|
snapshot.activeStory.kind === "main" &&
|
|
156
269
|
shouldHidePageBorderForSelection(viewState.selection);
|
|
157
270
|
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
recordPerfSample("workspace.chrome");
|
|
273
|
+
incrementInvalidationCounter("workspace.chrome.recomputes");
|
|
274
|
+
}, [activeParagraphLayout, pageChromeModel, pageShellMetrics]);
|
|
275
|
+
|
|
158
276
|
useEffect(() => {
|
|
159
277
|
if (isPageWorkspace && snapshot.activeStory.kind !== "main") {
|
|
160
278
|
setLayoutToolsOpen(true);
|
|
@@ -197,9 +315,56 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
197
315
|
onToggleBold={runWithSelectionToolbarDismiss(props.onToggleBold)}
|
|
198
316
|
onToggleItalic={runWithSelectionToolbarDismiss(props.onToggleItalic)}
|
|
199
317
|
onToggleUnderline={runWithSelectionToolbarDismiss(props.onToggleUnderline)}
|
|
318
|
+
onToggleStrikethrough={runWithSelectionToolbarDismiss(props.onToggleStrikethrough)}
|
|
319
|
+
onToggleSuperscript={runWithSelectionToolbarDismiss(props.onToggleSuperscript)}
|
|
320
|
+
onToggleSubscript={runWithSelectionToolbarDismiss(props.onToggleSubscript)}
|
|
321
|
+
onSetFontFamily={props.onSetFontFamily
|
|
322
|
+
? (fontFamily) => {
|
|
323
|
+
dismissSelectionToolbar();
|
|
324
|
+
props.onSetFontFamily?.(fontFamily);
|
|
325
|
+
}
|
|
326
|
+
: undefined}
|
|
327
|
+
onSetFontSize={props.onSetFontSize
|
|
328
|
+
? (fontSize) => {
|
|
329
|
+
dismissSelectionToolbar();
|
|
330
|
+
props.onSetFontSize?.(fontSize);
|
|
331
|
+
}
|
|
332
|
+
: undefined}
|
|
333
|
+
onSetTextColor={props.onSetTextColor
|
|
334
|
+
? (color) => {
|
|
335
|
+
dismissSelectionToolbar();
|
|
336
|
+
props.onSetTextColor?.(color);
|
|
337
|
+
}
|
|
338
|
+
: undefined}
|
|
339
|
+
onSetHighlightColor={props.onSetHighlightColor
|
|
340
|
+
? (color) => {
|
|
341
|
+
dismissSelectionToolbar();
|
|
342
|
+
props.onSetHighlightColor?.(color);
|
|
343
|
+
}
|
|
344
|
+
: undefined}
|
|
345
|
+
onSetAlignment={props.onSetAlignment
|
|
346
|
+
? (alignment) => {
|
|
347
|
+
dismissSelectionToolbar();
|
|
348
|
+
props.onSetAlignment?.(alignment);
|
|
349
|
+
}
|
|
350
|
+
: undefined}
|
|
200
351
|
onOutdent={runWithSelectionToolbarDismiss(props.onOutdent)}
|
|
201
352
|
onIndent={runWithSelectionToolbarDismiss(props.onIndent)}
|
|
202
353
|
onAddComment={runWithSelectionToolbarDismiss(props.onAddComment)}
|
|
354
|
+
onInsertPageBreak={runWithSelectionToolbarDismiss(props.onInsertPageBreak)}
|
|
355
|
+
onInsertTable={runWithSelectionToolbarDismiss(props.onInsertTable)}
|
|
356
|
+
onInsertSectionBreak={props.onInsertSectionBreak
|
|
357
|
+
? (type) => {
|
|
358
|
+
dismissSelectionToolbar();
|
|
359
|
+
props.onInsertSectionBreak?.(type);
|
|
360
|
+
}
|
|
361
|
+
: undefined}
|
|
362
|
+
onInsertImage={props.onInsertImage
|
|
363
|
+
? (options) => {
|
|
364
|
+
dismissSelectionToolbar();
|
|
365
|
+
props.onInsertImage?.(options);
|
|
366
|
+
}
|
|
367
|
+
: undefined}
|
|
203
368
|
onExport={runWithSelectionToolbarDismiss(props.onExport)}
|
|
204
369
|
activeStory={snapshot.activeStory}
|
|
205
370
|
onCloseStory={props.onCloseStory
|
|
@@ -219,9 +384,14 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
219
384
|
dismissSelectionToolbar();
|
|
220
385
|
props.onShowTrackedChangesChange(show);
|
|
221
386
|
}}
|
|
387
|
+
blockedReasons={blockedReasons}
|
|
222
388
|
/>
|
|
223
389
|
|
|
224
|
-
<TwAlertBanner
|
|
390
|
+
<TwAlertBanner
|
|
391
|
+
snapshot={snapshot}
|
|
392
|
+
preserveOnlyCount={preserveOnlyCount}
|
|
393
|
+
workflowBlockedReasons={blockedReasons}
|
|
394
|
+
/>
|
|
225
395
|
|
|
226
396
|
<div className="flex flex-1 min-h-0">
|
|
227
397
|
{/* Collapsible document navigator — page mode only */}
|
|
@@ -356,6 +526,44 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
356
526
|
Body
|
|
357
527
|
</button>
|
|
358
528
|
) : null}
|
|
529
|
+
{snapshot.activeStory.kind === "main" && snapshot.pageLayout.sectionIndex > 0 ? (
|
|
530
|
+
<>
|
|
531
|
+
<button
|
|
532
|
+
type="button"
|
|
533
|
+
aria-label="Link header to previous"
|
|
534
|
+
disabled={!props.onSetHeaderFooterLink}
|
|
535
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
536
|
+
onClick={() => {
|
|
537
|
+
dismissSelectionToolbar();
|
|
538
|
+
props.onSetHeaderFooterLink?.(snapshot.pageLayout!.sectionIndex, {
|
|
539
|
+
kind: "header",
|
|
540
|
+
variant: headerVariant,
|
|
541
|
+
linkToPrevious: true,
|
|
542
|
+
});
|
|
543
|
+
}}
|
|
544
|
+
className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
|
|
545
|
+
>
|
|
546
|
+
Link header
|
|
547
|
+
</button>
|
|
548
|
+
<button
|
|
549
|
+
type="button"
|
|
550
|
+
aria-label="Link footer to previous"
|
|
551
|
+
disabled={!props.onSetHeaderFooterLink}
|
|
552
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
553
|
+
onClick={() => {
|
|
554
|
+
dismissSelectionToolbar();
|
|
555
|
+
props.onSetHeaderFooterLink?.(snapshot.pageLayout!.sectionIndex, {
|
|
556
|
+
kind: "footer",
|
|
557
|
+
variant: footerVariant,
|
|
558
|
+
linkToPrevious: true,
|
|
559
|
+
});
|
|
560
|
+
}}
|
|
561
|
+
className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
|
|
562
|
+
>
|
|
563
|
+
Link footer
|
|
564
|
+
</button>
|
|
565
|
+
</>
|
|
566
|
+
) : null}
|
|
359
567
|
<button
|
|
360
568
|
type="button"
|
|
361
569
|
aria-label="Toggle layout tools"
|
|
@@ -409,6 +617,86 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
409
617
|
? runWithSelectionToolbarDismiss(props.onContinueNumbering)
|
|
410
618
|
: undefined}
|
|
411
619
|
/>
|
|
620
|
+
<TwLayoutPanel
|
|
621
|
+
pageLayout={snapshot.pageLayout}
|
|
622
|
+
readOnly={snapshot.readOnly || snapshot.activeStory.kind !== "main"}
|
|
623
|
+
onInsertSectionBreak={props.onInsertSectionBreak
|
|
624
|
+
? (type) => {
|
|
625
|
+
dismissSelectionToolbar();
|
|
626
|
+
props.onInsertSectionBreak?.(type);
|
|
627
|
+
}
|
|
628
|
+
: undefined}
|
|
629
|
+
onDeleteSectionBreak={props.onDeleteSectionBreak
|
|
630
|
+
? (sectionIndex) => {
|
|
631
|
+
dismissSelectionToolbar();
|
|
632
|
+
props.onDeleteSectionBreak?.(sectionIndex);
|
|
633
|
+
}
|
|
634
|
+
: undefined}
|
|
635
|
+
onUpdateSectionLayout={props.onUpdateSectionLayout
|
|
636
|
+
? (sectionIndex, patch) => {
|
|
637
|
+
dismissSelectionToolbar();
|
|
638
|
+
props.onUpdateSectionLayout?.(sectionIndex, patch);
|
|
639
|
+
}
|
|
640
|
+
: undefined}
|
|
641
|
+
onSetSectionPageNumbering={props.onSetSectionPageNumbering
|
|
642
|
+
? (sectionIndex, patch) => {
|
|
643
|
+
dismissSelectionToolbar();
|
|
644
|
+
props.onSetSectionPageNumbering?.(sectionIndex, patch);
|
|
645
|
+
}
|
|
646
|
+
: undefined}
|
|
647
|
+
/>
|
|
648
|
+
</div>
|
|
649
|
+
) : null}
|
|
650
|
+
{contextualSurface ? (
|
|
651
|
+
<div className="px-5 pt-3 space-y-3">
|
|
652
|
+
{contextualSurface === "table" ? (
|
|
653
|
+
<TwTableContextToolbar
|
|
654
|
+
disabled={!caps?.canEdit}
|
|
655
|
+
tableStyles={props.styleCatalog?.tables ?? []}
|
|
656
|
+
onSetTableStyle={props.onSetTableStyle
|
|
657
|
+
? (styleId) => {
|
|
658
|
+
dismissSelectionToolbar();
|
|
659
|
+
props.onSetTableStyle?.(styleId);
|
|
660
|
+
}
|
|
661
|
+
: undefined}
|
|
662
|
+
onAddRowBefore={runWithSelectionToolbarDismiss(props.onAddRowBefore)}
|
|
663
|
+
onAddRowAfter={runWithSelectionToolbarDismiss(props.onAddRowAfter)}
|
|
664
|
+
onAddColumnBefore={runWithSelectionToolbarDismiss(props.onAddColumnBefore)}
|
|
665
|
+
onAddColumnAfter={runWithSelectionToolbarDismiss(props.onAddColumnAfter)}
|
|
666
|
+
onDeleteRow={runWithSelectionToolbarDismiss(props.onDeleteRow)}
|
|
667
|
+
onDeleteColumn={runWithSelectionToolbarDismiss(props.onDeleteColumn)}
|
|
668
|
+
onDeleteTable={runWithSelectionToolbarDismiss(props.onDeleteTable)}
|
|
669
|
+
onMergeCells={runWithSelectionToolbarDismiss(props.onMergeCells)}
|
|
670
|
+
onSplitCell={runWithSelectionToolbarDismiss(props.onSplitCell)}
|
|
671
|
+
onSetCellBackground={props.onSetCellBackground
|
|
672
|
+
? (color) => {
|
|
673
|
+
dismissSelectionToolbar();
|
|
674
|
+
props.onSetCellBackground?.(color);
|
|
675
|
+
}
|
|
676
|
+
: undefined}
|
|
677
|
+
/>
|
|
678
|
+
) : null}
|
|
679
|
+
{contextualSurface === "image" && props.activeImageContext ? (
|
|
680
|
+
<TwImageContextToolbar
|
|
681
|
+
activeImage={props.activeImageContext}
|
|
682
|
+
disabled={!caps?.canEdit}
|
|
683
|
+
onSetImageLayout={props.onSetImageLayout
|
|
684
|
+
? (mediaId, dimensions) => {
|
|
685
|
+
dismissSelectionToolbar();
|
|
686
|
+
props.onSetImageLayout?.(mediaId, dimensions);
|
|
687
|
+
}
|
|
688
|
+
: undefined}
|
|
689
|
+
onSetImageFrame={props.onSetImageFrame
|
|
690
|
+
? (mediaId, offsets) => {
|
|
691
|
+
dismissSelectionToolbar();
|
|
692
|
+
props.onSetImageFrame?.(mediaId, offsets);
|
|
693
|
+
}
|
|
694
|
+
: undefined}
|
|
695
|
+
/>
|
|
696
|
+
) : null}
|
|
697
|
+
{contextualSurface === "object" && props.activeObjectContext ? (
|
|
698
|
+
<TwObjectContextToolbar activeObject={props.activeObjectContext} />
|
|
699
|
+
) : null}
|
|
412
700
|
</div>
|
|
413
701
|
) : null}
|
|
414
702
|
{props.selectionToolbar && selectionToolbarPlacement ? (
|
|
@@ -427,6 +715,8 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
427
715
|
onToggleBold={props.onToggleBold}
|
|
428
716
|
onToggleItalic={props.onToggleItalic}
|
|
429
717
|
onToggleUnderline={props.onToggleUnderline}
|
|
718
|
+
onSetTextColor={props.onSetSelectionTextColor}
|
|
719
|
+
onSetHighlightColor={props.onSetSelectionHighlightColor}
|
|
430
720
|
onAddComment={props.onAddCommentFromSelection ?? props.onAddComment}
|
|
431
721
|
/>
|
|
432
722
|
</div>
|
|
@@ -447,6 +737,8 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
447
737
|
onToggleBold={props.onToggleBold}
|
|
448
738
|
onToggleItalic={props.onToggleItalic}
|
|
449
739
|
onToggleUnderline={props.onToggleUnderline}
|
|
740
|
+
onSetTextColor={props.onSetSelectionTextColor}
|
|
741
|
+
onSetHighlightColor={props.onSetSelectionHighlightColor}
|
|
450
742
|
onAddComment={props.onAddCommentFromSelection ?? props.onAddComment}
|
|
451
743
|
/>
|
|
452
744
|
</div>
|
|
@@ -686,7 +978,12 @@ function buildPageChromeModel(
|
|
|
686
978
|
return EMPTY_PAGE_CHROME_MODEL;
|
|
687
979
|
}
|
|
688
980
|
|
|
689
|
-
const lineMarkers =
|
|
981
|
+
const lineMarkers = computeLineMarkersIfEnabled({
|
|
982
|
+
pageLayout,
|
|
983
|
+
surfaceBlocks: surface.blocks,
|
|
984
|
+
pages: navigation.pages,
|
|
985
|
+
buildLineNumberMarkers,
|
|
986
|
+
});
|
|
690
987
|
const lineNumberingEnabled =
|
|
691
988
|
Boolean(pageLayout.lineNumbering) && lineMarkers.length > 0;
|
|
692
989
|
const distance = pageLayout.lineNumbering?.distance ?? 0;
|
|
@@ -479,6 +479,15 @@ function collectLossyBlocks(
|
|
|
479
479
|
const issues: string[] = [];
|
|
480
480
|
|
|
481
481
|
for (const block of blocks) {
|
|
482
|
+
if (block.type === "table") {
|
|
483
|
+
for (const row of block.rows) {
|
|
484
|
+
for (const cell of row.cells) {
|
|
485
|
+
issues.push(...collectLossyBlocks(cell.children, `${surface}:table-cell`));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
482
491
|
if (block.type !== "paragraph") {
|
|
483
492
|
issues.push(`${surface}:${block.type}`);
|
|
484
493
|
continue;
|
|
@@ -486,9 +495,6 @@ function collectLossyBlocks(
|
|
|
486
495
|
|
|
487
496
|
if (
|
|
488
497
|
block.numbering !== undefined ||
|
|
489
|
-
block.spacing !== undefined ||
|
|
490
|
-
block.indentation !== undefined ||
|
|
491
|
-
block.tabStops !== undefined ||
|
|
492
498
|
block.keepNext !== undefined ||
|
|
493
499
|
block.keepLines !== undefined ||
|
|
494
500
|
block.outlineLevel !== undefined ||
|
|
@@ -517,6 +523,8 @@ function collectLossyInlineContent(
|
|
|
517
523
|
): string[] {
|
|
518
524
|
switch (node.type) {
|
|
519
525
|
case "text": {
|
|
526
|
+
const allowSecondaryStoryColorMarks =
|
|
527
|
+
surface.startsWith("header:") || surface.startsWith("footer:");
|
|
520
528
|
const unsupportedMarks = (node.marks ?? [])
|
|
521
529
|
.filter(
|
|
522
530
|
(mark) =>
|
|
@@ -524,7 +532,14 @@ function collectLossyInlineContent(
|
|
|
524
532
|
mark.type !== "italic" &&
|
|
525
533
|
mark.type !== "underline" &&
|
|
526
534
|
mark.type !== "strikethrough" &&
|
|
527
|
-
mark.type !== "doubleStrikethrough"
|
|
535
|
+
mark.type !== "doubleStrikethrough" &&
|
|
536
|
+
mark.type !== "fontFamily" &&
|
|
537
|
+
mark.type !== "fontSize" &&
|
|
538
|
+
mark.type !== "textColor" &&
|
|
539
|
+
(!allowSecondaryStoryColorMarks || mark.type !== "backgroundColor") &&
|
|
540
|
+
(!allowSecondaryStoryColorMarks || mark.type !== "highlight") &&
|
|
541
|
+
mark.type !== "smallCaps" &&
|
|
542
|
+
mark.type !== "allCaps",
|
|
528
543
|
)
|
|
529
544
|
.map((mark) => `${surface}:mark:${mark.type}`);
|
|
530
545
|
return unsupportedMarks;
|
|
@@ -532,6 +547,14 @@ function collectLossyInlineContent(
|
|
|
532
547
|
case "tab":
|
|
533
548
|
case "hard_break":
|
|
534
549
|
case "footnote_ref":
|
|
550
|
+
case "field":
|
|
551
|
+
case "bookmark_start":
|
|
552
|
+
case "bookmark_end":
|
|
553
|
+
case "shape":
|
|
554
|
+
case "wordart":
|
|
555
|
+
case "vml_shape":
|
|
556
|
+
case "chart_preview":
|
|
557
|
+
case "smartart_preview":
|
|
535
558
|
return [];
|
|
536
559
|
default:
|
|
537
560
|
return [`${surface}:${node.type}`];
|