@beyondwork/docx-react-component 1.0.84 → 1.0.85
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 +95 -9
- package/src/ui/editor-command-bag.ts +3 -1
- 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/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-workspace/types.ts +3 -2
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +11 -1
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -1
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +2 -1
- package/src/ui-tailwind/tw-review-workspace.tsx +18 -2
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
WorkspaceMode,
|
|
15
15
|
} from "../api/public-types.ts";
|
|
16
16
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail.tsx";
|
|
17
|
+
import type { MarkupDisplay } from "./headless/comment-decoration-model.ts";
|
|
17
18
|
|
|
18
19
|
type CommandHandler = (...args: any[]) => unknown;
|
|
19
20
|
|
|
@@ -22,6 +23,7 @@ export interface EditorCommandBag {
|
|
|
22
23
|
onZoomChange?(level: ZoomLevel): void;
|
|
23
24
|
onActiveRailTabChange(value: ReviewRailTab): void;
|
|
24
25
|
onShowTrackedChangesChange(show: boolean): void;
|
|
26
|
+
onReviewMarkupModeChange?(mode: MarkupDisplay): void;
|
|
25
27
|
onUndo(): void;
|
|
26
28
|
onRedo(): void;
|
|
27
29
|
onSetParagraphStyle?(styleId: string): void;
|
|
@@ -102,7 +104,7 @@ export interface EditorCommandBag {
|
|
|
102
104
|
/** Open the footer story for a specific page (double-click on its band). */
|
|
103
105
|
onOpenFooterStoryForPage?(pageIndex: number): void;
|
|
104
106
|
/**
|
|
105
|
-
* P8.11 — per-page header/footer band click handler.
|
|
107
|
+
* P8.11 — per-page header/footer band double-click handler. Receives the
|
|
106
108
|
* exact `EditorStoryTarget` the band represents; the command bag wires
|
|
107
109
|
* this to `runtime.openStory(target)`.
|
|
108
110
|
*/
|
|
@@ -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";
|
|
@@ -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>
|
|
@@ -31,23 +31,18 @@ export interface TwTableContextToolbarProps {
|
|
|
31
31
|
/**
|
|
32
32
|
* Phase D.2 — progressive-disclosure compact mode.
|
|
33
33
|
*
|
|
34
|
-
* When `true`, the toolbar
|
|
35
|
-
* -
|
|
36
|
-
* -
|
|
34
|
+
* When `true`, the toolbar stays action-first:
|
|
35
|
+
* - a small context label
|
|
36
|
+
* - the tier's highest-frequency table actions
|
|
37
|
+
* - a single "More…" button that opens the shared command graph
|
|
37
38
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* accessed via right-click OR the "More…" button (which opens the
|
|
42
|
-
* same context menu). Per DESIGN-EDITOR.md §6.4 ("right-click
|
|
43
|
-
* cannot be richer than the floating surface; cannot duplicate
|
|
44
|
-
* command trees") the compact variant pins that rule — there IS no
|
|
45
|
-
* richer surface to diverge from.
|
|
39
|
+
* Diagnostic metadata such as "3 x 4" or "R1 C1" is intentionally
|
|
40
|
+
* omitted from the compact surface. It belongs in properties /
|
|
41
|
+
* diagnostics, not primary editing chrome.
|
|
46
42
|
*
|
|
47
|
-
* Default `false` preserves the rich in-tree behavior
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `onContextMenuRequested` wiring is proven.
|
|
43
|
+
* Default `false` preserves the rich in-tree behavior for standalone
|
|
44
|
+
* and back-compat mounts. The product selection-tool path opts into
|
|
45
|
+
* compact mode.
|
|
51
46
|
*/
|
|
52
47
|
compact?: boolean;
|
|
53
48
|
/**
|
|
@@ -146,29 +141,37 @@ export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
|
|
|
146
141
|
: null;
|
|
147
142
|
const selectionLabel = tableContext ? formatSelectionLabel(tableContext, tier) : null;
|
|
148
143
|
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
144
|
+
// Product compact variant: action-first local chrome + one "More…"
|
|
145
|
+
// button that opens the shared context menu. The full action set
|
|
146
|
+
// still lives in editor-action-registry so right-click and "More…"
|
|
147
|
+
// stay identical; this surface only promotes the tier's obvious next
|
|
148
|
+
// actions and leaves metadata to deeper surfaces.
|
|
153
149
|
if (props.compact) {
|
|
154
150
|
return (
|
|
155
151
|
<div
|
|
156
152
|
data-testid="table-context-toolbar"
|
|
157
153
|
data-tier={tier}
|
|
158
154
|
data-variant="compact"
|
|
159
|
-
|
|
155
|
+
data-purpose="table-actions"
|
|
156
|
+
className="inline-flex max-w-[min(28rem,calc(100vw-1.5rem))] flex-wrap items-center gap-[4px] rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-bg-canvas)] px-2 py-1 shadow-[var(--shadow-float)]"
|
|
160
157
|
>
|
|
161
|
-
<span
|
|
162
|
-
|
|
158
|
+
<span
|
|
159
|
+
data-testid="table-context-toolbar-label"
|
|
160
|
+
className="mr-0.5 text-[9px] font-semibold uppercase tracking-[0.12em] text-[var(--color-text-tertiary)]"
|
|
161
|
+
>
|
|
162
|
+
{compactTierLabel(tier)}
|
|
163
163
|
</span>
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
<CompactTableActions
|
|
165
|
+
props={props}
|
|
166
|
+
tableContext={tableContext}
|
|
167
|
+
tier={tier}
|
|
168
|
+
/>
|
|
166
169
|
<button
|
|
167
170
|
type="button"
|
|
168
171
|
data-testid="table-context-toolbar-more"
|
|
169
172
|
aria-label="Table actions menu"
|
|
170
173
|
className="inline-flex h-6 items-center gap-1 rounded-[var(--radius-sm)] px-2 text-xs font-medium text-secondary hover:bg-hover focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]"
|
|
171
|
-
disabled={props.disabled}
|
|
174
|
+
disabled={props.disabled || !props.onOpenMore}
|
|
172
175
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
173
176
|
onClick={(ev) => {
|
|
174
177
|
const rect = (ev.currentTarget as HTMLButtonElement).getBoundingClientRect();
|
|
@@ -457,6 +460,149 @@ export function TwTableContextToolbar(props: TwTableContextToolbarProps) {
|
|
|
457
460
|
);
|
|
458
461
|
}
|
|
459
462
|
|
|
463
|
+
function CompactTableActions(args: {
|
|
464
|
+
props: TwTableContextToolbarProps;
|
|
465
|
+
tableContext: TableStructureContextSnapshot | null;
|
|
466
|
+
tier: TableTier;
|
|
467
|
+
}) {
|
|
468
|
+
const { props, tableContext, tier } = args;
|
|
469
|
+
switch (tier) {
|
|
470
|
+
case "caret-in-cell":
|
|
471
|
+
return (
|
|
472
|
+
<>
|
|
473
|
+
<ToolbarButton
|
|
474
|
+
ariaLabel="Insert row below"
|
|
475
|
+
capability={tableContext?.operations.addRowAfter}
|
|
476
|
+
disabled={props.disabled}
|
|
477
|
+
onClick={props.onAddRowAfter}
|
|
478
|
+
>
|
|
479
|
+
Row +
|
|
480
|
+
</ToolbarButton>
|
|
481
|
+
<ToolbarButton
|
|
482
|
+
ariaLabel="Insert column right"
|
|
483
|
+
capability={tableContext?.operations.addColumnAfter}
|
|
484
|
+
disabled={props.disabled}
|
|
485
|
+
onClick={props.onAddColumnAfter}
|
|
486
|
+
>
|
|
487
|
+
Col +
|
|
488
|
+
</ToolbarButton>
|
|
489
|
+
</>
|
|
490
|
+
);
|
|
491
|
+
case "multi-cell":
|
|
492
|
+
return (
|
|
493
|
+
<>
|
|
494
|
+
<ToolbarButton
|
|
495
|
+
ariaLabel="Merge cells"
|
|
496
|
+
capability={tableContext?.operations.mergeCells}
|
|
497
|
+
disabled={props.disabled}
|
|
498
|
+
onClick={props.onMergeCells}
|
|
499
|
+
>
|
|
500
|
+
Merge
|
|
501
|
+
</ToolbarButton>
|
|
502
|
+
<ToolbarButton
|
|
503
|
+
ariaLabel="Split cell"
|
|
504
|
+
capability={tableContext?.operations.splitCell}
|
|
505
|
+
disabled={props.disabled}
|
|
506
|
+
onClick={props.onSplitCell}
|
|
507
|
+
>
|
|
508
|
+
Split
|
|
509
|
+
</ToolbarButton>
|
|
510
|
+
</>
|
|
511
|
+
);
|
|
512
|
+
case "row-selected":
|
|
513
|
+
return (
|
|
514
|
+
<>
|
|
515
|
+
<ToolbarButton
|
|
516
|
+
ariaLabel="Insert row below"
|
|
517
|
+
capability={tableContext?.operations.addRowAfter}
|
|
518
|
+
disabled={props.disabled}
|
|
519
|
+
onClick={props.onAddRowAfter}
|
|
520
|
+
>
|
|
521
|
+
Row +
|
|
522
|
+
</ToolbarButton>
|
|
523
|
+
<ToolbarButton
|
|
524
|
+
ariaLabel="Delete row"
|
|
525
|
+
capability={tableContext?.operations.deleteRow}
|
|
526
|
+
danger
|
|
527
|
+
disabled={props.disabled}
|
|
528
|
+
onClick={props.onDeleteRow}
|
|
529
|
+
>
|
|
530
|
+
Delete
|
|
531
|
+
</ToolbarButton>
|
|
532
|
+
<ToolbarButton
|
|
533
|
+
ariaLabel="Toggle header row"
|
|
534
|
+
capability={tableContext?.operations.setRowIsHeader}
|
|
535
|
+
active={tableContext?.currentCell.isHeader}
|
|
536
|
+
disabled={props.disabled}
|
|
537
|
+
onClick={props.onToggleRowHeader}
|
|
538
|
+
>
|
|
539
|
+
Header
|
|
540
|
+
</ToolbarButton>
|
|
541
|
+
</>
|
|
542
|
+
);
|
|
543
|
+
case "column-selected":
|
|
544
|
+
return (
|
|
545
|
+
<>
|
|
546
|
+
<ToolbarButton
|
|
547
|
+
ariaLabel="Insert column right"
|
|
548
|
+
capability={tableContext?.operations.addColumnAfter}
|
|
549
|
+
disabled={props.disabled}
|
|
550
|
+
onClick={props.onAddColumnAfter}
|
|
551
|
+
>
|
|
552
|
+
Col +
|
|
553
|
+
</ToolbarButton>
|
|
554
|
+
<ToolbarButton
|
|
555
|
+
ariaLabel="Delete column"
|
|
556
|
+
capability={tableContext?.operations.deleteColumn}
|
|
557
|
+
danger
|
|
558
|
+
disabled={props.disabled}
|
|
559
|
+
onClick={props.onDeleteColumn}
|
|
560
|
+
>
|
|
561
|
+
Delete
|
|
562
|
+
</ToolbarButton>
|
|
563
|
+
<ToolbarButton
|
|
564
|
+
ariaLabel="Distribute columns evenly"
|
|
565
|
+
capability={tableContext?.operations.distributeColumnsEvenly}
|
|
566
|
+
disabled={props.disabled}
|
|
567
|
+
onClick={props.onDistributeColumnsEvenly}
|
|
568
|
+
>
|
|
569
|
+
Distribute
|
|
570
|
+
</ToolbarButton>
|
|
571
|
+
</>
|
|
572
|
+
);
|
|
573
|
+
case "whole-table":
|
|
574
|
+
return (
|
|
575
|
+
<>
|
|
576
|
+
<ToolbarButton
|
|
577
|
+
ariaLabel="Insert row below"
|
|
578
|
+
capability={tableContext?.operations.addRowAfter}
|
|
579
|
+
disabled={props.disabled}
|
|
580
|
+
onClick={props.onAddRowAfter}
|
|
581
|
+
>
|
|
582
|
+
Row +
|
|
583
|
+
</ToolbarButton>
|
|
584
|
+
<ToolbarButton
|
|
585
|
+
ariaLabel="Insert column right"
|
|
586
|
+
capability={tableContext?.operations.addColumnAfter}
|
|
587
|
+
disabled={props.disabled}
|
|
588
|
+
onClick={props.onAddColumnAfter}
|
|
589
|
+
>
|
|
590
|
+
Col +
|
|
591
|
+
</ToolbarButton>
|
|
592
|
+
<ToolbarButton
|
|
593
|
+
ariaLabel="Delete table"
|
|
594
|
+
capability={tableContext?.operations.deleteTable}
|
|
595
|
+
danger
|
|
596
|
+
disabled={props.disabled}
|
|
597
|
+
onClick={props.onDeleteTable}
|
|
598
|
+
>
|
|
599
|
+
Delete
|
|
600
|
+
</ToolbarButton>
|
|
601
|
+
</>
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
460
606
|
function formatSelectionLabel(
|
|
461
607
|
ctx: TableStructureContextSnapshot,
|
|
462
608
|
tier: TableTier,
|
|
@@ -491,6 +637,10 @@ function tierLabel(tier: TableTier): string {
|
|
|
491
637
|
}
|
|
492
638
|
}
|
|
493
639
|
|
|
640
|
+
function compactTierLabel(tier: TableTier): string {
|
|
641
|
+
return tier === "whole-table" ? "Table" : tierLabel(tier);
|
|
642
|
+
}
|
|
643
|
+
|
|
494
644
|
function tierWidthCap(tier: TableTier): string {
|
|
495
645
|
switch (tier) {
|
|
496
646
|
case "caret-in-cell":
|
|
@@ -154,7 +154,7 @@ export interface TwChromeOverlayProps {
|
|
|
154
154
|
*/
|
|
155
155
|
activeStory?: EditorStoryTarget;
|
|
156
156
|
/**
|
|
157
|
-
* Fired when the user clicks a per-page header / footer band to
|
|
157
|
+
* Fired when the user double-clicks a per-page header / footer band to
|
|
158
158
|
* promote it into the active editing surface. Task 10 will route PM
|
|
159
159
|
* into the matching band via React portals; today the handler is a
|
|
160
160
|
* pass-through to `runtime.openStory`.
|
|
@@ -513,6 +513,18 @@ export function buildDecorations(
|
|
|
513
513
|
}),
|
|
514
514
|
);
|
|
515
515
|
revisionCount += 1;
|
|
516
|
+
} else if (rev.kind === "property-change" || rev.kind === "formatting") {
|
|
517
|
+
const propertyChangeClass =
|
|
518
|
+
buildClassFromRevisionDisplay(revDisplayFlags) ||
|
|
519
|
+
"underline decoration-accent/70 decoration-dotted decoration-1 underline-offset-2";
|
|
520
|
+
decorations.push(
|
|
521
|
+
Decoration.inline(pmFrom, pmTo, {
|
|
522
|
+
class: propertyChangeClass,
|
|
523
|
+
"data-revision-id": rev.revisionId,
|
|
524
|
+
"data-revision-kind": rev.kind,
|
|
525
|
+
}),
|
|
526
|
+
);
|
|
527
|
+
revisionCount += 1;
|
|
516
528
|
}
|
|
517
529
|
continue;
|
|
518
530
|
}
|
|
@@ -392,7 +392,7 @@ function buildChromeWidgetDomUncached(input: ChromeWidgetInput): HTMLElement {
|
|
|
392
392
|
root.style.userSelect = "none";
|
|
393
393
|
|
|
394
394
|
if (input.posture === "canvas") {
|
|
395
|
-
// Single dotted horizontal line with
|
|
395
|
+
// Single dotted horizontal line with an unframed page-number label.
|
|
396
396
|
root.style.height = `${input.interGapPx + 1}px`;
|
|
397
397
|
root.style.position = "relative";
|
|
398
398
|
|
|
@@ -406,35 +406,23 @@ function buildChromeWidgetDomUncached(input: ChromeWidgetInput): HTMLElement {
|
|
|
406
406
|
line.style.borderTop = "1px dotted var(--color-border-default)";
|
|
407
407
|
root.appendChild(line);
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
badge.style.alignItems = "center";
|
|
427
|
-
badge.style.height = `${PILL_HEIGHT_PX}px`;
|
|
428
|
-
badge.style.padding = "0 10px";
|
|
429
|
-
badge.style.fontSize = "10px";
|
|
430
|
-
badge.style.letterSpacing = "0.12em";
|
|
431
|
-
badge.style.textTransform = "uppercase";
|
|
432
|
-
badge.style.color = "var(--color-text-tertiary)";
|
|
433
|
-
badge.style.backgroundColor = "var(--color-surface)";
|
|
434
|
-
badge.style.border = "1px solid var(--color-border-default)";
|
|
435
|
-
badge.style.borderRadius = "var(--radius-pill)";
|
|
436
|
-
badge.style.boxShadow = "var(--shadow-soft)";
|
|
437
|
-
root.appendChild(badge);
|
|
409
|
+
const label = document.createElement("span");
|
|
410
|
+
label.className = "wre-page-chrome-canvas-page-number";
|
|
411
|
+
label.setAttribute("data-kind", "canvas-seam-page-number");
|
|
412
|
+
label.textContent = input.nextPageLabel;
|
|
413
|
+
label.style.position = "absolute";
|
|
414
|
+
label.style.top = `${Math.round(input.interGapPx / 2)}px`;
|
|
415
|
+
label.style.left = "50%";
|
|
416
|
+
label.style.transform = "translate(-50%, -50%)";
|
|
417
|
+
label.style.display = "inline-flex";
|
|
418
|
+
label.style.alignItems = "center";
|
|
419
|
+
label.style.padding = "0 2px";
|
|
420
|
+
label.style.fontSize = "10px";
|
|
421
|
+
label.style.letterSpacing = "0.12em";
|
|
422
|
+
label.style.textTransform = "uppercase";
|
|
423
|
+
label.style.color = "var(--color-text-tertiary)";
|
|
424
|
+
label.style.pointerEvents = "none";
|
|
425
|
+
root.appendChild(label);
|
|
438
426
|
return root;
|
|
439
427
|
}
|
|
440
428
|
|
|
@@ -35,7 +35,8 @@ export interface TwPageChromeEntryProps {
|
|
|
35
35
|
page: PublicPageNode;
|
|
36
36
|
facet: WordReviewEditorLayoutFacet;
|
|
37
37
|
activeStory: EditorStoryTarget;
|
|
38
|
-
|
|
38
|
+
activeStoryPageIndex?: number | null;
|
|
39
|
+
onOpenStory?: (target: EditorStoryTarget, pageIndex: number) => void;
|
|
39
40
|
visiblePageIndexRange?: { start: number; end: number } | null;
|
|
40
41
|
renderFrameRevision: number;
|
|
41
42
|
/** Preview catalog threaded into header/footer/footnote region renderers
|
|
@@ -58,6 +59,7 @@ function TwPageChromeEntryInner({
|
|
|
58
59
|
page,
|
|
59
60
|
facet,
|
|
60
61
|
activeStory,
|
|
62
|
+
activeStoryPageIndex,
|
|
61
63
|
onOpenStory,
|
|
62
64
|
visiblePageIndexRange,
|
|
63
65
|
renderFrameRevision,
|
|
@@ -129,14 +131,14 @@ function TwPageChromeEntryInner({
|
|
|
129
131
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
130
132
|
}, [facet, pageIndex, page, renderFrameRevision]);
|
|
131
133
|
|
|
132
|
-
const
|
|
133
|
-
() => headerStory && onOpenStory?.(headerStory),
|
|
134
|
-
[onOpenStory, headerStory],
|
|
134
|
+
const handleHeaderDoubleClick = React.useCallback(
|
|
135
|
+
() => headerStory && onOpenStory?.(headerStory, pageIndex),
|
|
136
|
+
[onOpenStory, headerStory, pageIndex],
|
|
135
137
|
);
|
|
136
138
|
|
|
137
|
-
const
|
|
138
|
-
() => footerStory && onOpenStory?.(footerStory),
|
|
139
|
-
[onOpenStory, footerStory],
|
|
139
|
+
const handleFooterDoubleClick = React.useCallback(
|
|
140
|
+
() => footerStory && onOpenStory?.(footerStory, pageIndex),
|
|
141
|
+
[onOpenStory, footerStory, pageIndex],
|
|
140
142
|
);
|
|
141
143
|
|
|
142
144
|
const frameHeightPx = rect.bottomPx - rect.topPx;
|
|
@@ -175,8 +177,14 @@ function TwPageChromeEntryInner({
|
|
|
175
177
|
const sectionNumber = (page.sectionIndex ?? 0) + 1;
|
|
176
178
|
const headerSectionLabel = `Header — Section ${sectionNumber}`;
|
|
177
179
|
const footerSectionLabel = `Footer — Section ${sectionNumber}`;
|
|
178
|
-
const headerActive =
|
|
179
|
-
|
|
180
|
+
const headerActive =
|
|
181
|
+
headerStory &&
|
|
182
|
+
isActiveStoryMatch(activeStory, headerStory) &&
|
|
183
|
+
(activeStoryPageIndex == null || activeStoryPageIndex === pageIndex);
|
|
184
|
+
const footerActive =
|
|
185
|
+
footerStory &&
|
|
186
|
+
isActiveStoryMatch(activeStory, footerStory) &&
|
|
187
|
+
(activeStoryPageIndex == null || activeStoryPageIndex === pageIndex);
|
|
180
188
|
|
|
181
189
|
return (
|
|
182
190
|
<div
|
|
@@ -201,7 +209,7 @@ function TwPageChromeEntryInner({
|
|
|
201
209
|
bandHeightPx={px(headerRegion.heightTwips)}
|
|
202
210
|
isActiveSlot={Boolean(headerActive)}
|
|
203
211
|
sectionLabel={headerActive ? headerSectionLabel : undefined}
|
|
204
|
-
|
|
212
|
+
onDoubleClick={handleHeaderDoubleClick}
|
|
205
213
|
mediaPreviews={mediaPreviews}
|
|
206
214
|
ribbonProps={headerActive ? activeBandRibbonProps ?? null : null}
|
|
207
215
|
/>
|
|
@@ -216,7 +224,7 @@ function TwPageChromeEntryInner({
|
|
|
216
224
|
bandHeightPx={px(footerRegion.heightTwips)}
|
|
217
225
|
isActiveSlot={Boolean(footerActive)}
|
|
218
226
|
sectionLabel={footerActive ? footerSectionLabel : undefined}
|
|
219
|
-
|
|
227
|
+
onDoubleClick={handleFooterDoubleClick}
|
|
220
228
|
mediaPreviews={mediaPreviews}
|
|
221
229
|
ribbonProps={footerActive ? activeBandRibbonProps ?? null : null}
|
|
222
230
|
/>
|
|
@@ -256,6 +264,7 @@ function propsAreEqual(
|
|
|
256
264
|
prev.page === next.page &&
|
|
257
265
|
prev.facet === next.facet &&
|
|
258
266
|
prev.activeStory === next.activeStory &&
|
|
267
|
+
prev.activeStoryPageIndex === next.activeStoryPageIndex &&
|
|
259
268
|
prev.onOpenStory === next.onOpenStory &&
|
|
260
269
|
prev.visiblePageIndexRange === next.visiblePageIndexRange &&
|
|
261
270
|
prev.renderFrameRevision === next.renderFrameRevision &&
|
|
@@ -37,7 +37,7 @@ export interface TwPageFooterBandProps {
|
|
|
37
37
|
* Only rendered when `isActiveSlot` is true.
|
|
38
38
|
*/
|
|
39
39
|
sectionLabel?: string;
|
|
40
|
-
|
|
40
|
+
onDoubleClick: () => void;
|
|
41
41
|
"data-testid"?: string;
|
|
42
42
|
mediaPreviews?: Record<string, MediaPreviewDescriptor>;
|
|
43
43
|
/**
|
|
@@ -57,7 +57,7 @@ export const TwPageFooterBand: React.FC<TwPageFooterBandProps> = React.memo(({
|
|
|
57
57
|
widthPx,
|
|
58
58
|
isActiveSlot,
|
|
59
59
|
sectionLabel,
|
|
60
|
-
|
|
60
|
+
onDoubleClick,
|
|
61
61
|
"data-testid": testId,
|
|
62
62
|
mediaPreviews,
|
|
63
63
|
ribbonProps,
|
|
@@ -69,14 +69,18 @@ export const TwPageFooterBand: React.FC<TwPageFooterBandProps> = React.memo(({
|
|
|
69
69
|
data-page-index={pageIndex}
|
|
70
70
|
data-active={isActiveSlot ? "true" : undefined}
|
|
71
71
|
data-testid={testId}
|
|
72
|
-
|
|
72
|
+
onDoubleClick={(event) => {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
onDoubleClick();
|
|
76
|
+
}}
|
|
73
77
|
style={{
|
|
74
78
|
position: "absolute",
|
|
75
79
|
bottom: `${bottomPx}px`,
|
|
76
80
|
left: `${leftPx}px`,
|
|
77
81
|
width: `${widthPx}px`,
|
|
78
82
|
height: `${bandHeightPx}px`,
|
|
79
|
-
cursor: "
|
|
83
|
+
cursor: "text",
|
|
80
84
|
}}
|
|
81
85
|
>
|
|
82
86
|
{isActiveSlot && sectionLabel ? (
|
|
@@ -95,6 +99,7 @@ export const TwPageFooterBand: React.FC<TwPageFooterBandProps> = React.memo(({
|
|
|
95
99
|
<div
|
|
96
100
|
data-pm-portal-slot
|
|
97
101
|
data-page-band-slot="footer"
|
|
102
|
+
data-page-index={pageIndex}
|
|
98
103
|
style={{ width: "100%", height: "100%" }}
|
|
99
104
|
/>
|
|
100
105
|
) : (
|
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
// promoted this band to the active story slot (P8.10 wires the PM
|
|
20
20
|
// surface into this target via React portal).
|
|
21
21
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// the
|
|
22
|
+
// Double-clicks on the band dispatch to the chrome layer's `openStory`
|
|
23
|
+
// handler so legal reviewers can promote a header into the active editing
|
|
24
|
+
// surface without single-clicking out of the main body flow.
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
|
|
27
27
|
export interface TwPageHeaderBandProps {
|
|
@@ -38,7 +38,7 @@ export interface TwPageHeaderBandProps {
|
|
|
38
38
|
* Only rendered when `isActiveSlot` is true.
|
|
39
39
|
*/
|
|
40
40
|
sectionLabel?: string;
|
|
41
|
-
|
|
41
|
+
onDoubleClick: () => void;
|
|
42
42
|
"data-testid"?: string;
|
|
43
43
|
/** Preview catalog threaded to the region renderer so header images
|
|
44
44
|
* (CCEP logos on 7-of-8 CCEP docs) render as real <img>s instead of
|
|
@@ -63,7 +63,7 @@ export const TwPageHeaderBand: React.FC<TwPageHeaderBandProps> = React.memo(({
|
|
|
63
63
|
widthPx,
|
|
64
64
|
isActiveSlot,
|
|
65
65
|
sectionLabel,
|
|
66
|
-
|
|
66
|
+
onDoubleClick,
|
|
67
67
|
"data-testid": testId,
|
|
68
68
|
mediaPreviews,
|
|
69
69
|
ribbonProps,
|
|
@@ -75,14 +75,18 @@ export const TwPageHeaderBand: React.FC<TwPageHeaderBandProps> = React.memo(({
|
|
|
75
75
|
data-page-index={pageIndex}
|
|
76
76
|
data-active={isActiveSlot ? "true" : undefined}
|
|
77
77
|
data-testid={testId}
|
|
78
|
-
|
|
78
|
+
onDoubleClick={(event) => {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
event.stopPropagation();
|
|
81
|
+
onDoubleClick();
|
|
82
|
+
}}
|
|
79
83
|
style={{
|
|
80
84
|
position: "absolute",
|
|
81
85
|
top: `${topPx}px`,
|
|
82
86
|
left: `${leftPx}px`,
|
|
83
87
|
width: `${widthPx}px`,
|
|
84
88
|
height: `${bandHeightPx}px`,
|
|
85
|
-
cursor: "
|
|
89
|
+
cursor: "text",
|
|
86
90
|
}}
|
|
87
91
|
>
|
|
88
92
|
{isActiveSlot && sectionLabel ? (
|
|
@@ -101,6 +105,7 @@ export const TwPageHeaderBand: React.FC<TwPageHeaderBandProps> = React.memo(({
|
|
|
101
105
|
<div
|
|
102
106
|
data-pm-portal-slot
|
|
103
107
|
data-page-band-slot="header"
|
|
108
|
+
data-page-index={pageIndex}
|
|
104
109
|
style={{ width: "100%", height: "100%" }}
|
|
105
110
|
/>
|
|
106
111
|
) : (
|