@beyondwork/docx-react-component 1.0.28 → 1.0.30
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 +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { BookmarkPlus, ChevronLeft, ChevronRight, MessageSquareText, Rows3 } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import type { ReviewQueueSnapshot } from "../../api/public-types";
|
|
6
|
+
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
7
|
+
|
|
8
|
+
export interface TwReviewQueueBarProps {
|
|
9
|
+
queue: ReviewQueueSnapshot;
|
|
10
|
+
onPrevious?: () => void;
|
|
11
|
+
onNext?: () => void;
|
|
12
|
+
onMarkSection?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const buttonClass =
|
|
16
|
+
"inline-flex h-8 items-center gap-1 rounded-lg border border-border/80 bg-canvas px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas disabled:cursor-not-allowed disabled:opacity-40";
|
|
17
|
+
|
|
18
|
+
export function TwReviewQueueBar(props: TwReviewQueueBarProps) {
|
|
19
|
+
const activeItem = props.queue.items[props.queue.activeIndex] ?? null;
|
|
20
|
+
const activeLabel =
|
|
21
|
+
activeItem?.label ??
|
|
22
|
+
(props.queue.totalCount > 0 ? "Review queue" : "No review items");
|
|
23
|
+
const activeKindLabel = activeItem ? formatReviewQueueItemKind(activeItem.kind) : null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="border-b border-border/80 bg-surface/80 px-3 py-2.5 backdrop-blur-sm">
|
|
27
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
aria-label="Previous review item"
|
|
31
|
+
className={buttonClass}
|
|
32
|
+
disabled={props.queue.totalCount === 0}
|
|
33
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
34
|
+
onClick={props.onPrevious}
|
|
35
|
+
>
|
|
36
|
+
<ChevronLeft className="h-3.5 w-3.5" />
|
|
37
|
+
Prev
|
|
38
|
+
</button>
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
aria-label="Next review item"
|
|
42
|
+
className={buttonClass}
|
|
43
|
+
disabled={props.queue.totalCount === 0}
|
|
44
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
45
|
+
onClick={props.onNext}
|
|
46
|
+
>
|
|
47
|
+
Next
|
|
48
|
+
<ChevronRight className="h-3.5 w-3.5" />
|
|
49
|
+
</button>
|
|
50
|
+
{props.onMarkSection ? (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
aria-label="Mark section for review"
|
|
54
|
+
className={buttonClass}
|
|
55
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
56
|
+
onClick={props.onMarkSection}
|
|
57
|
+
>
|
|
58
|
+
<BookmarkPlus className="h-3.5 w-3.5" />
|
|
59
|
+
Mark section
|
|
60
|
+
</button>
|
|
61
|
+
) : null}
|
|
62
|
+
<div className="ml-auto flex flex-wrap items-center gap-2 text-xs text-secondary">
|
|
63
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-canvas px-2.5 py-1 font-medium text-primary">
|
|
64
|
+
<MessageSquareText className="h-3.5 w-3.5 text-comment" />
|
|
65
|
+
{props.queue.openCount} open
|
|
66
|
+
</span>
|
|
67
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-canvas px-2.5 py-1 font-medium text-primary">
|
|
68
|
+
<Rows3 className="h-3.5 w-3.5 text-accent" />
|
|
69
|
+
{props.queue.totalCount} total
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="mt-2 flex items-center gap-2 min-w-0">
|
|
74
|
+
{activeKindLabel ? (
|
|
75
|
+
<span
|
|
76
|
+
className="inline-flex shrink-0 rounded-full bg-canvas px-2 py-0.5 text-[11px] font-medium text-secondary"
|
|
77
|
+
data-testid="review-queue-item-kind"
|
|
78
|
+
>
|
|
79
|
+
{activeKindLabel}
|
|
80
|
+
</span>
|
|
81
|
+
) : null}
|
|
82
|
+
<div className="min-w-0 truncate text-sm font-medium text-primary">{activeLabel}</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatReviewQueueItemKind(kind: ReviewQueueSnapshot["items"][number]["kind"]): string {
|
|
89
|
+
switch (kind) {
|
|
90
|
+
case "comment":
|
|
91
|
+
return "Comment";
|
|
92
|
+
case "change":
|
|
93
|
+
return "Redline";
|
|
94
|
+
case "section_mark":
|
|
95
|
+
return "Tag";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import type { RuntimeContextAnalyticsSnapshot } from "../../api/public-types";
|
|
4
|
+
|
|
5
|
+
const TONE_CLASS: Record<NonNullable<RuntimeContextAnalyticsSnapshot["badges"][number]["tone"]>, string> = {
|
|
6
|
+
neutral: "bg-surface text-secondary",
|
|
7
|
+
accent: "bg-accent-soft text-accent",
|
|
8
|
+
warning: "bg-warning-soft text-warning",
|
|
9
|
+
danger: "bg-danger-soft text-danger",
|
|
10
|
+
success: "bg-success-soft text-success",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface TwContextAnalyticsSummaryProps {
|
|
14
|
+
snapshot?: RuntimeContextAnalyticsSnapshot | null;
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
testId?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TwContextAnalyticsSummary(props: TwContextAnalyticsSummaryProps) {
|
|
21
|
+
if (!props.snapshot) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const badges = props.snapshot.badges.slice(0, props.compact ? 3 : 4);
|
|
26
|
+
const primaryAction = props.snapshot.nextActions[0];
|
|
27
|
+
if (badges.length === 0 && !primaryAction) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-testid={props.testId}
|
|
34
|
+
className={[
|
|
35
|
+
"rounded-lg bg-surface/92 p-2.5 shadow-[0_1px_0_var(--color-shadow)] ring-1 ring-border/40",
|
|
36
|
+
props.className,
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(" ")}
|
|
40
|
+
>
|
|
41
|
+
{badges.length > 0 ? (
|
|
42
|
+
<div className="flex flex-wrap gap-1.5">
|
|
43
|
+
{badges.map((badge) => (
|
|
44
|
+
<span
|
|
45
|
+
key={badge.key}
|
|
46
|
+
className={[
|
|
47
|
+
"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium",
|
|
48
|
+
TONE_CLASS[badge.tone ?? "neutral"],
|
|
49
|
+
].join(" ")}
|
|
50
|
+
>
|
|
51
|
+
{formatBadgeLabel(badge)}
|
|
52
|
+
</span>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
) : null}
|
|
56
|
+
{primaryAction ? (
|
|
57
|
+
<p className="mt-1 text-[11px] text-secondary">
|
|
58
|
+
{primaryAction.label}
|
|
59
|
+
</p>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatContextAnalyticsInlineSummary(
|
|
66
|
+
snapshot?: RuntimeContextAnalyticsSnapshot | null,
|
|
67
|
+
): string | null {
|
|
68
|
+
if (!snapshot) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parts: string[] = [];
|
|
73
|
+
if (typeof snapshot.counts.openWorkItems === "number" && snapshot.counts.openWorkItems > 0) {
|
|
74
|
+
parts.push(`${snapshot.counts.openWorkItems} open ${pluralize(snapshot.counts.openWorkItems, "work item")}`);
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
typeof snapshot.counts.unresolvedComments === "number" &&
|
|
78
|
+
snapshot.counts.unresolvedComments > 0
|
|
79
|
+
) {
|
|
80
|
+
parts.push(
|
|
81
|
+
`${snapshot.counts.unresolvedComments} unresolved ${pluralize(snapshot.counts.unresolvedComments, "comment")}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
typeof snapshot.counts.pendingRevisions === "number" &&
|
|
86
|
+
snapshot.counts.pendingRevisions > 0
|
|
87
|
+
) {
|
|
88
|
+
parts.push(
|
|
89
|
+
`${snapshot.counts.pendingRevisions} pending ${pluralize(snapshot.counts.pendingRevisions, "change")}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
parts.length === 0 &&
|
|
94
|
+
typeof snapshot.counts.blockedCommands === "number" &&
|
|
95
|
+
snapshot.counts.blockedCommands > 0
|
|
96
|
+
) {
|
|
97
|
+
parts.push(
|
|
98
|
+
`${snapshot.counts.blockedCommands} blocked ${pluralize(snapshot.counts.blockedCommands, "action")}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (parts.length === 0) {
|
|
102
|
+
if (snapshot.state.exportReadiness === "blocked") {
|
|
103
|
+
parts.push("Export blocked");
|
|
104
|
+
} else if (snapshot.state.exportReadiness === "warning") {
|
|
105
|
+
parts.push("Export warnings");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (parts.length === 0) {
|
|
109
|
+
return snapshot.badges[0] ? formatBadgeLabel(snapshot.badges[0]) : null;
|
|
110
|
+
}
|
|
111
|
+
return parts.slice(0, 3).join(" · ");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatBadgeLabel(
|
|
115
|
+
badge: RuntimeContextAnalyticsSnapshot["badges"][number],
|
|
116
|
+
): string {
|
|
117
|
+
return badge.value === undefined ? badge.label : `${badge.label}: ${badge.value}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pluralize(count: number, singular: string): string {
|
|
121
|
+
return count === 1 ? singular : `${singular}s`;
|
|
122
|
+
}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
4
|
-
|
|
5
|
-
export interface ActiveImageContext {
|
|
6
|
-
mediaId: string;
|
|
7
|
-
display: "inline" | "floating";
|
|
8
|
-
widthEmu?: number;
|
|
9
|
-
heightEmu?: number;
|
|
10
|
-
horizontalOffsetEmu?: number;
|
|
11
|
-
verticalOffsetEmu?: number;
|
|
12
|
-
}
|
|
4
|
+
import type { ActiveImageContext } from "../../ui/headless/selection-tool-types";
|
|
13
5
|
|
|
14
6
|
export interface TwImageContextToolbarProps {
|
|
15
7
|
activeImage: ActiveImageContext;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
3
|
-
export interface ActiveObjectContext {
|
|
4
|
-
kind: "textbox" | "shape";
|
|
5
|
-
display: "inline" | "floating";
|
|
6
|
-
}
|
|
2
|
+
import type { ActiveObjectContext } from "../../ui/headless/selection-tool-types";
|
|
7
3
|
|
|
8
4
|
export interface TwObjectContextToolbarProps {
|
|
9
5
|
activeObject: ActiveObjectContext;
|
|
@@ -10,6 +10,8 @@ interface ActiveParagraphLayout {
|
|
|
10
10
|
rightIndent: number;
|
|
11
11
|
firstLineOffset: number;
|
|
12
12
|
tabStops: Array<{ pos: number; val?: string; leader?: string }>;
|
|
13
|
+
indentationReadOnly?: boolean;
|
|
14
|
+
tabStopsReadOnly?: boolean;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface TwPageRulerProps {
|
|
@@ -53,6 +55,8 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
53
55
|
const activeRegion = props.viewState.activePageRegion?.region ?? "body";
|
|
54
56
|
const isBodyParagraphContext =
|
|
55
57
|
activeRegion === "body" && Boolean(props.paragraphLayout);
|
|
58
|
+
const indentationReadOnly = props.readOnly || Boolean(props.paragraphLayout?.indentationReadOnly);
|
|
59
|
+
const tabStopsReadOnly = props.readOnly || Boolean(props.paragraphLayout?.tabStopsReadOnly);
|
|
56
60
|
const availableHeader = props.pageLayout.headerVariants[0];
|
|
57
61
|
const availableFooter = props.pageLayout.footerVariants[0];
|
|
58
62
|
|
|
@@ -68,7 +72,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
68
72
|
}, []);
|
|
69
73
|
|
|
70
74
|
function beginDrag(kind: DragKind, clientX: number): void {
|
|
71
|
-
if (!isBodyParagraphContext || !props.paragraphLayout ||
|
|
75
|
+
if (!isBodyParagraphContext || !props.paragraphLayout || indentationReadOnly) {
|
|
72
76
|
return;
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -211,31 +215,6 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
211
215
|
Footer
|
|
212
216
|
</button>
|
|
213
217
|
) : null}
|
|
214
|
-
{props.viewState.activeListContext ? (
|
|
215
|
-
<>
|
|
216
|
-
<div className="h-4 w-px bg-border" />
|
|
217
|
-
<button
|
|
218
|
-
type="button"
|
|
219
|
-
aria-label="Continue numbering"
|
|
220
|
-
title="Continue numbering from previous list"
|
|
221
|
-
disabled={props.readOnly}
|
|
222
|
-
onClick={props.onContinueNumbering}
|
|
223
|
-
className={controlButtonClass}
|
|
224
|
-
>
|
|
225
|
-
Continue
|
|
226
|
-
</button>
|
|
227
|
-
<button
|
|
228
|
-
type="button"
|
|
229
|
-
aria-label="Restart numbering"
|
|
230
|
-
title="Restart numbering at 1"
|
|
231
|
-
disabled={props.readOnly}
|
|
232
|
-
onClick={props.onRestartNumbering}
|
|
233
|
-
className={controlButtonClass}
|
|
234
|
-
>
|
|
235
|
-
Restart
|
|
236
|
-
</button>
|
|
237
|
-
</>
|
|
238
|
-
) : null}
|
|
239
218
|
</div>
|
|
240
219
|
|
|
241
220
|
<div
|
|
@@ -252,7 +231,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
252
231
|
className="relative h-14 overflow-hidden rounded-xl border border-border bg-canvas"
|
|
253
232
|
onClick={(event) => {
|
|
254
233
|
if (
|
|
255
|
-
|
|
234
|
+
tabStopsReadOnly ||
|
|
256
235
|
!isBodyParagraphContext ||
|
|
257
236
|
!props.paragraphLayout ||
|
|
258
237
|
!props.onSetTabStops
|
|
@@ -287,7 +266,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
287
266
|
data-handle="true"
|
|
288
267
|
aria-label="Left indent handle"
|
|
289
268
|
title={`Left indent: ${effectiveLayout?.leftIndent ?? 0} twips`}
|
|
290
|
-
disabled={
|
|
269
|
+
disabled={indentationReadOnly}
|
|
291
270
|
className={`absolute top-8 h-4 w-4 -translate-x-1/2 rounded-[5px] border border-accent/40 bg-accent-soft shadow-sm transition-opacity ${
|
|
292
271
|
handlesOverlap ? "opacity-80 z-10" : ""
|
|
293
272
|
}`}
|
|
@@ -302,7 +281,7 @@ export function TwPageRuler(props: TwPageRulerProps) {
|
|
|
302
281
|
data-handle="true"
|
|
303
282
|
aria-label="First line indent handle"
|
|
304
283
|
title={`First line offset: ${effectiveLayout?.firstLineOffset ?? 0} twips`}
|
|
305
|
-
disabled={
|
|
284
|
+
disabled={indentationReadOnly}
|
|
306
285
|
className={`absolute top-1 h-4 w-4 -translate-x-1/2 rotate-45 rounded-[4px] border border-primary/30 bg-surface-raised shadow-sm transition-opacity ${
|
|
307
286
|
handlesOverlap ? "opacity-80 z-20" : ""
|
|
308
287
|
}`}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import type { BlockedExplainerSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
4
|
+
|
|
5
|
+
export interface TwSelectionToolBlockedProps {
|
|
6
|
+
model: BlockedExplainerSelectionToolModel;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function TwSelectionToolBlocked(props: TwSelectionToolBlockedProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-testid="blocked-selection-tool"
|
|
13
|
+
className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
|
|
14
|
+
role="status"
|
|
15
|
+
aria-live="polite"
|
|
16
|
+
>
|
|
17
|
+
<div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
18
|
+
Unavailable here
|
|
19
|
+
</div>
|
|
20
|
+
<div className="mt-1 text-sm text-primary">{props.model.disabledReason}</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import type { CommentThreadSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
4
|
+
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
5
|
+
|
|
6
|
+
export interface TwSelectionToolCommentProps {
|
|
7
|
+
model: CommentThreadSelectionToolModel;
|
|
8
|
+
onAddComment?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TwSelectionToolComment(props: TwSelectionToolCommentProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-testid="comment-thread-selection-tool"
|
|
15
|
+
className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
|
|
16
|
+
>
|
|
17
|
+
<div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
18
|
+
Comment
|
|
19
|
+
</div>
|
|
20
|
+
<div className="mt-1 truncate text-sm text-primary">
|
|
21
|
+
{props.model.previewText ?? "Comment thread"}
|
|
22
|
+
</div>
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
aria-label="Add comment from thread"
|
|
26
|
+
disabled={!props.model.canAddComment || !props.onAddComment}
|
|
27
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
28
|
+
onClick={props.onAddComment}
|
|
29
|
+
className="mt-2 inline-flex h-8 items-center rounded-lg border border-border px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
|
|
30
|
+
>
|
|
31
|
+
Comment
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import type { FocusEventHandler } from "react";
|
|
3
|
+
|
|
4
|
+
import type { FormattingInlineSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
5
|
+
import { TwSelectionToolbar } from "./tw-selection-toolbar";
|
|
6
|
+
|
|
7
|
+
export interface TwSelectionToolFormattingProps {
|
|
8
|
+
model: FormattingInlineSelectionToolModel;
|
|
9
|
+
onFocusCapture?: FocusEventHandler<HTMLDivElement>;
|
|
10
|
+
onBlurCapture?: FocusEventHandler<HTMLDivElement>;
|
|
11
|
+
onToggleBold?: () => void;
|
|
12
|
+
onToggleItalic?: () => void;
|
|
13
|
+
onToggleUnderline?: () => void;
|
|
14
|
+
onSetTextColor?: (color: string) => void;
|
|
15
|
+
onSetHighlightColor?: (color: string | null) => void;
|
|
16
|
+
onAddComment?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const TwSelectionToolFormatting = forwardRef<HTMLDivElement, TwSelectionToolFormattingProps>(
|
|
20
|
+
function TwSelectionToolFormatting(props, ref) {
|
|
21
|
+
return (
|
|
22
|
+
<TwSelectionToolbar
|
|
23
|
+
ref={ref}
|
|
24
|
+
model={props.model}
|
|
25
|
+
disabledReason={props.model.disabledReason}
|
|
26
|
+
onFocusCapture={props.onFocusCapture}
|
|
27
|
+
onBlurCapture={props.onBlurCapture}
|
|
28
|
+
onToggleBold={props.onToggleBold}
|
|
29
|
+
onToggleItalic={props.onToggleItalic}
|
|
30
|
+
onToggleUnderline={props.onToggleUnderline}
|
|
31
|
+
onSetTextColor={props.onSetTextColor}
|
|
32
|
+
onSetHighlightColor={props.onSetHighlightColor}
|
|
33
|
+
onAddComment={props.onAddComment}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
);
|