@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
|
@@ -277,6 +277,69 @@ function requestTableLayoutSync(start: HTMLElement): void {
|
|
|
277
277
|
table?.dispatchEvent(new Event(TABLE_LAYOUT_SYNC_EVENT));
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
function applyTableAttrs(table: HTMLTableElement, node: PMNode): void {
|
|
281
|
+
table.className = "border-collapse w-full my-2 text-sm";
|
|
282
|
+
table.setAttribute("data-pm-table-root", "true");
|
|
283
|
+
table.style.marginLeft = "";
|
|
284
|
+
table.style.marginRight = "";
|
|
285
|
+
const alignment = node.attrs.alignment as string | null | undefined;
|
|
286
|
+
if (alignment === "center") {
|
|
287
|
+
table.style.marginLeft = "auto";
|
|
288
|
+
table.style.marginRight = "auto";
|
|
289
|
+
} else if (alignment === "right") {
|
|
290
|
+
table.style.marginLeft = "auto";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function applyRowAttrs(row: HTMLTableRowElement, node: PMNode): void {
|
|
295
|
+
row.style.height = "";
|
|
296
|
+
row.style.minHeight = "";
|
|
297
|
+
const height = node.attrs.height as number | null | undefined;
|
|
298
|
+
const heightRule = node.attrs.heightRule as string | null | undefined;
|
|
299
|
+
if (typeof height !== "number" || height <= 0) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const points = `${height / 20}pt`;
|
|
303
|
+
if (heightRule === "exact") {
|
|
304
|
+
row.style.height = points;
|
|
305
|
+
} else if (heightRule === "atLeast") {
|
|
306
|
+
row.style.minHeight = points;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function applyCellAttrs(cell: HTMLTableCellElement, node: PMNode, isHeader: boolean): void {
|
|
311
|
+
cell.className = isHeader
|
|
312
|
+
? "border border-primary/20 p-2 align-top font-semibold bg-surface-raised"
|
|
313
|
+
: "border border-primary/20 p-2 align-top";
|
|
314
|
+
|
|
315
|
+
const backgroundColor = node.attrs.backgroundColor as string | null | undefined;
|
|
316
|
+
const verticalAlign = node.attrs.verticalAlign as "top" | "center" | "bottom" | null | undefined;
|
|
317
|
+
const borderTop = node.attrs.borderTop as string | null | undefined;
|
|
318
|
+
const borderRight = node.attrs.borderRight as string | null | undefined;
|
|
319
|
+
const borderBottom = node.attrs.borderBottom as string | null | undefined;
|
|
320
|
+
const borderLeft = node.attrs.borderLeft as string | null | undefined;
|
|
321
|
+
|
|
322
|
+
if (backgroundColor) cell.setAttribute("data-cell-background", backgroundColor);
|
|
323
|
+
else cell.removeAttribute("data-cell-background");
|
|
324
|
+
if (verticalAlign && verticalAlign !== "top") cell.setAttribute("data-vertical-align", verticalAlign);
|
|
325
|
+
else cell.removeAttribute("data-vertical-align");
|
|
326
|
+
if (borderTop) cell.setAttribute("data-border-top", borderTop);
|
|
327
|
+
else cell.removeAttribute("data-border-top");
|
|
328
|
+
if (borderRight) cell.setAttribute("data-border-right", borderRight);
|
|
329
|
+
else cell.removeAttribute("data-border-right");
|
|
330
|
+
if (borderBottom) cell.setAttribute("data-border-bottom", borderBottom);
|
|
331
|
+
else cell.removeAttribute("data-border-bottom");
|
|
332
|
+
if (borderLeft) cell.setAttribute("data-border-left", borderLeft);
|
|
333
|
+
else cell.removeAttribute("data-border-left");
|
|
334
|
+
|
|
335
|
+
cell.style.backgroundColor = backgroundColor ?? "";
|
|
336
|
+
cell.style.verticalAlign = verticalAlign === "center" ? "middle" : (verticalAlign ?? "");
|
|
337
|
+
cell.style.borderTop = borderTop ?? "";
|
|
338
|
+
cell.style.borderRight = borderRight ?? "";
|
|
339
|
+
cell.style.borderBottom = borderBottom ?? "";
|
|
340
|
+
cell.style.borderLeft = borderLeft ?? "";
|
|
341
|
+
}
|
|
342
|
+
|
|
280
343
|
/**
|
|
281
344
|
* NodeView for the table node.
|
|
282
345
|
* Renders as <table><tbody>...</tbody></table>.
|
|
@@ -293,8 +356,7 @@ export class TableNodeView {
|
|
|
293
356
|
this.node = node;
|
|
294
357
|
|
|
295
358
|
const table = document.createElement("table");
|
|
296
|
-
table
|
|
297
|
-
table.setAttribute("data-pm-table-root", "true");
|
|
359
|
+
applyTableAttrs(table, node);
|
|
298
360
|
|
|
299
361
|
const tbody = document.createElement("tbody");
|
|
300
362
|
table.appendChild(tbody);
|
|
@@ -314,6 +376,7 @@ export class TableNodeView {
|
|
|
314
376
|
}
|
|
315
377
|
|
|
316
378
|
this.node = node;
|
|
379
|
+
applyTableAttrs(this.dom as HTMLTableElement, node);
|
|
317
380
|
this.scheduleLayoutSync();
|
|
318
381
|
return true;
|
|
319
382
|
}
|
|
@@ -357,11 +420,17 @@ export class TableRowNodeView {
|
|
|
357
420
|
dom: HTMLElement;
|
|
358
421
|
contentDOM: HTMLElement;
|
|
359
422
|
|
|
360
|
-
constructor(
|
|
423
|
+
constructor(node: PMNode) {
|
|
361
424
|
const tr = document.createElement("tr");
|
|
425
|
+
applyRowAttrs(tr, node);
|
|
362
426
|
this.dom = tr;
|
|
363
427
|
this.contentDOM = tr;
|
|
364
428
|
}
|
|
429
|
+
|
|
430
|
+
update(node: PMNode): boolean {
|
|
431
|
+
applyRowAttrs(this.dom as HTMLTableRowElement, node);
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
365
434
|
}
|
|
366
435
|
|
|
367
436
|
/**
|
|
@@ -377,10 +446,7 @@ export class TableCellNodeView {
|
|
|
377
446
|
constructor(node: PMNode) {
|
|
378
447
|
const isHeader = (node.type.spec as { tableRole?: string }).tableRole === "header_cell";
|
|
379
448
|
const cell = document.createElement(isHeader ? "th" : "td");
|
|
380
|
-
|
|
381
|
-
cell.className = isHeader
|
|
382
|
-
? "border border-primary/20 p-2 align-top font-semibold bg-surface-raised"
|
|
383
|
-
: "border border-primary/20 p-2 align-top";
|
|
449
|
+
applyCellAttrs(cell, node, isHeader);
|
|
384
450
|
|
|
385
451
|
const colspan = resolveRenderedColspan(node);
|
|
386
452
|
const rowspan = resolveRenderedRowspan(node);
|
|
@@ -409,6 +475,7 @@ export class TableCellNodeView {
|
|
|
409
475
|
const colspan = resolveRenderedColspan(node);
|
|
410
476
|
const rowspan = resolveRenderedRowspan(node);
|
|
411
477
|
const cell = this.dom as HTMLTableCellElement;
|
|
478
|
+
applyCellAttrs(cell, node, isHeader);
|
|
412
479
|
cell.colSpan = colspan > 1 ? colspan : 1;
|
|
413
480
|
cell.rowSpan = rowspan > 1 ? rowspan : 1;
|
|
414
481
|
cell.style.display = "";
|
|
@@ -22,7 +22,7 @@ export function TwCommentSidebar(props: TwCommentSidebarProps) {
|
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div className="outline-none">
|
|
25
|
-
<div className="mb-
|
|
25
|
+
<div className="mb-3 flex items-center gap-2 text-[11px] text-tertiary">
|
|
26
26
|
<span>{comments.openCommentIds.length} open</span>
|
|
27
27
|
<span className="text-border">·</span>
|
|
28
28
|
<span>{comments.resolvedCommentIds.length} resolved</span>
|
|
@@ -34,7 +34,7 @@ export function TwCommentSidebar(props: TwCommentSidebarProps) {
|
|
|
34
34
|
)}
|
|
35
35
|
</div>
|
|
36
36
|
{comments.threads.length > 0 ? (
|
|
37
|
-
<div className="space-y-
|
|
37
|
+
<div className="space-y-2">
|
|
38
38
|
{comments.threads.map((thread) => (
|
|
39
39
|
<CommentThreadCard
|
|
40
40
|
key={thread.commentId}
|
|
@@ -50,7 +50,7 @@ export function TwCommentSidebar(props: TwCommentSidebarProps) {
|
|
|
50
50
|
))}
|
|
51
51
|
</div>
|
|
52
52
|
) : (
|
|
53
|
-
<div className="rounded-lg
|
|
53
|
+
<div className="rounded-lg bg-surface px-3 py-3 text-[11px] leading-5 text-tertiary ring-1 ring-border/40">
|
|
54
54
|
No comment threads yet. Select text and add one from the toolbar.
|
|
55
55
|
</div>
|
|
56
56
|
)}
|
|
@@ -93,11 +93,11 @@ function CommentThreadCard(props: {
|
|
|
93
93
|
role="button"
|
|
94
94
|
tabIndex={0}
|
|
95
95
|
className={[
|
|
96
|
-
"cursor-pointer rounded-
|
|
96
|
+
"cursor-pointer rounded-md bg-surface/90 px-3 py-2.5 transition-colors ring-1 ring-border/40",
|
|
97
97
|
focusRingClass,
|
|
98
98
|
isActive
|
|
99
|
-
? "
|
|
100
|
-
: "
|
|
99
|
+
? "bg-accent-soft/40 ring-accent/25"
|
|
100
|
+
: "hover:bg-surface",
|
|
101
101
|
thread.status === "detached" ? "opacity-70" : "",
|
|
102
102
|
].join(" ")}
|
|
103
103
|
onClick={() => props.onOpenComment?.(thread)}
|
|
@@ -109,8 +109,8 @@ function CommentThreadCard(props: {
|
|
|
109
109
|
}}
|
|
110
110
|
>
|
|
111
111
|
{/* Header row: avatar + author + date + status */}
|
|
112
|
-
<div className="mb-1 flex items-center gap-1.5">
|
|
113
|
-
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-
|
|
112
|
+
<div className="mb-1.5 flex items-center gap-1.5">
|
|
113
|
+
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-subtle text-[8px] font-semibold text-secondary">
|
|
114
114
|
{thread.createdBy.charAt(0).toUpperCase()}
|
|
115
115
|
</span>
|
|
116
116
|
<span className="truncate text-[10px] font-medium text-primary">{thread.createdBy}</span>
|
|
@@ -125,7 +125,7 @@ function CommentThreadCard(props: {
|
|
|
125
125
|
|
|
126
126
|
{/* Excerpt — anchored text from document */}
|
|
127
127
|
{showExcerpt ? (
|
|
128
|
-
<p className="mb-1 rounded-md
|
|
128
|
+
<p className="mb-1.5 rounded-md bg-comment-soft px-2 py-1 text-[9px] leading-4 text-secondary italic whitespace-pre-wrap break-words line-clamp-2">
|
|
129
129
|
{thread.excerpt}
|
|
130
130
|
</p>
|
|
131
131
|
) : null}
|
|
@@ -159,7 +159,7 @@ function CommentThreadCard(props: {
|
|
|
159
159
|
|
|
160
160
|
{/* Reply entries (compact) */}
|
|
161
161
|
{thread.entries.slice(1).map((entry) => (
|
|
162
|
-
<div key={entry.entryId} className="mt-
|
|
162
|
+
<div key={entry.entryId} className="mt-2 ml-4 border-l border-border/50 pl-2.5">
|
|
163
163
|
<div className="mb-0.5 flex items-center gap-1">
|
|
164
164
|
<span className="text-[9px] font-medium text-secondary">{entry.authorId}</span>
|
|
165
165
|
<span className="text-[9px] text-tertiary">{formatCommentDate(entry.createdAt)}</span>
|
|
@@ -180,12 +180,12 @@ function CommentThreadCard(props: {
|
|
|
180
180
|
) : null}
|
|
181
181
|
|
|
182
182
|
{/* Inline actions — compact, horizontal */}
|
|
183
|
-
<div className="mt-
|
|
183
|
+
<div className="mt-2 flex items-center gap-1">
|
|
184
184
|
{thread.status === "open" && (
|
|
185
185
|
<>
|
|
186
186
|
<button
|
|
187
187
|
type="button"
|
|
188
|
-
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-
|
|
188
|
+
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-accent hover:bg-accent-soft transition-colors"
|
|
189
189
|
onClick={(e) => { e.stopPropagation(); props.onResolveComment?.(thread.commentId); }}
|
|
190
190
|
>
|
|
191
191
|
<Check className="h-2 w-2" /> Resolve
|
|
@@ -198,7 +198,7 @@ function CommentThreadCard(props: {
|
|
|
198
198
|
{thread.status === "resolved" && (
|
|
199
199
|
<button
|
|
200
200
|
type="button"
|
|
201
|
-
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-secondary hover:bg-surface transition-colors"
|
|
201
|
+
className="inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[9px] font-medium text-secondary hover:bg-surface-hover transition-colors"
|
|
202
202
|
data-comment-thread-action="reopen"
|
|
203
203
|
onClick={(e) => { e.stopPropagation(); props.onReopenComment?.(thread.commentId); }}
|
|
204
204
|
>
|
|
@@ -233,7 +233,7 @@ function InlineEditableBody(props: {
|
|
|
233
233
|
if (!isEditing) {
|
|
234
234
|
return (
|
|
235
235
|
<p
|
|
236
|
-
className={
|
|
236
|
+
className={`-mx-1 cursor-text rounded px-1 text-[10px] leading-[1.15rem] transition-colors hover:bg-surface ${props.body ? "text-secondary" : "text-tertiary italic"}`}
|
|
237
237
|
onClick={(e) => {
|
|
238
238
|
e.stopPropagation();
|
|
239
239
|
setDraft(props.body);
|
|
@@ -255,7 +255,7 @@ function InlineEditableBody(props: {
|
|
|
255
255
|
) : null}
|
|
256
256
|
<textarea
|
|
257
257
|
ref={textareaRef}
|
|
258
|
-
className="w-full resize-none rounded-md
|
|
258
|
+
className="w-full resize-none rounded-md bg-surface px-2 py-1.5 text-[10px] leading-4 text-primary placeholder:text-tertiary focus:outline-none focus:ring-1 focus:ring-accent ring-1 ring-border/50"
|
|
259
259
|
rows={2}
|
|
260
260
|
value={draft}
|
|
261
261
|
placeholder="Type your comment..."
|
|
@@ -301,7 +301,7 @@ function ReplyInput(props: { commentId: string; onAddReply: (commentId: string,
|
|
|
301
301
|
return (
|
|
302
302
|
<button
|
|
303
303
|
type="button"
|
|
304
|
-
className="inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 text-[10px] font-medium text-tertiary hover:
|
|
304
|
+
className="inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 text-[10px] font-medium text-tertiary hover:bg-surface-hover hover:text-secondary transition-colors"
|
|
305
305
|
onClick={(e) => {
|
|
306
306
|
e.stopPropagation();
|
|
307
307
|
setIsOpen(true);
|
|
@@ -316,7 +316,7 @@ function ReplyInput(props: { commentId: string; onAddReply: (commentId: string,
|
|
|
316
316
|
<div className="w-full mt-1.5" onClick={(e) => e.stopPropagation()}>
|
|
317
317
|
<textarea
|
|
318
318
|
ref={inputRef}
|
|
319
|
-
className="w-full rounded
|
|
319
|
+
className="w-full resize-none rounded bg-surface px-2 py-1 text-[11px] text-primary placeholder:text-tertiary focus:outline-none focus:ring-1 focus:ring-accent ring-1 ring-border/50"
|
|
320
320
|
rows={2}
|
|
321
321
|
placeholder="Reply..."
|
|
322
322
|
value={body}
|
|
@@ -8,13 +8,13 @@ import type {
|
|
|
8
8
|
CommentSidebarThreadSnapshot,
|
|
9
9
|
CompatibilityPanelSnapshot,
|
|
10
10
|
EditorWarning,
|
|
11
|
+
RuntimeContextAnalyticsSnapshot,
|
|
11
12
|
TrackedChangesSnapshot,
|
|
12
13
|
TrackedChangeEntrySnapshot,
|
|
13
14
|
} from "../../api/public-types";
|
|
14
15
|
import type { MarkupDisplay } from "../../ui/headless/comment-decoration-model";
|
|
15
16
|
import { TwCommentSidebar } from "./tw-comment-sidebar";
|
|
16
17
|
import { TwRevisionSidebar } from "./tw-revision-sidebar";
|
|
17
|
-
import { TwHealthPanel } from "./tw-health-panel";
|
|
18
18
|
|
|
19
19
|
export type ReviewRailTab = "comments" | "changes";
|
|
20
20
|
|
|
@@ -26,6 +26,7 @@ export interface TwReviewRailProps {
|
|
|
26
26
|
compatibility: CompatibilityPanelSnapshot;
|
|
27
27
|
warnings: EditorWarning[];
|
|
28
28
|
markupDisplay: MarkupDisplay;
|
|
29
|
+
contextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
29
30
|
activeCommentId?: string;
|
|
30
31
|
activeRevisionId?: string;
|
|
31
32
|
onActiveTabChange: (tab: ReviewRailTab) => void;
|
|
@@ -45,41 +46,44 @@ const focusRingClass =
|
|
|
45
46
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
46
47
|
|
|
47
48
|
export function TwReviewRail(props: TwReviewRailProps) {
|
|
48
|
-
const warningCount = props.compatibility.featureEntries.filter(
|
|
49
|
-
(e) => e.featureClass !== "supported-roundtrip",
|
|
50
|
-
).length + props.warnings.length;
|
|
51
|
-
|
|
52
49
|
return (
|
|
53
50
|
<aside
|
|
54
51
|
aria-label="Review rail"
|
|
55
|
-
className="flex w-[
|
|
52
|
+
className="flex w-[336px] shrink-0 flex-col border-l border-border/60 bg-[var(--color-sidebar-tint)]"
|
|
56
53
|
>
|
|
57
54
|
<Tabs.Root
|
|
58
55
|
value={props.activeTab}
|
|
59
56
|
onValueChange={(v: string) => props.onActiveTabChange(v as ReviewRailTab)}
|
|
60
57
|
className="flex flex-1 flex-col min-h-0"
|
|
61
58
|
>
|
|
62
|
-
<Tabs.List className="flex shrink-0 border-b border-border px-2">
|
|
59
|
+
<Tabs.List className="flex shrink-0 border-b border-border/60 px-3 py-2">
|
|
63
60
|
<Tabs.Trigger
|
|
64
61
|
value="comments"
|
|
65
|
-
className={`flex-1 py-
|
|
62
|
+
className={`flex-1 rounded-lg px-3 py-1.5 text-xs font-medium text-tertiary transition-colors data-[state=active]:bg-surface data-[state=active]:text-primary data-[state=active]:shadow-[0_1px_0_var(--color-shadow)] outline-none ${focusRingClass}`}
|
|
66
63
|
>
|
|
67
64
|
Comments{" "}
|
|
68
|
-
|
|
65
|
+
{props.comments.totalCount > 0 ? (
|
|
66
|
+
<span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">
|
|
67
|
+
{props.comments.totalCount}
|
|
68
|
+
</span>
|
|
69
|
+
) : null}
|
|
69
70
|
</Tabs.Trigger>
|
|
70
71
|
<Tabs.Trigger
|
|
71
72
|
value="changes"
|
|
72
|
-
className={`flex-1 py-
|
|
73
|
+
className={`flex-1 rounded-lg px-3 py-1.5 text-xs font-medium text-tertiary transition-colors data-[state=active]:bg-surface data-[state=active]:text-primary data-[state=active]:shadow-[0_1px_0_var(--color-shadow)] outline-none ${focusRingClass}`}
|
|
73
74
|
>
|
|
74
75
|
Changes{" "}
|
|
75
|
-
|
|
76
|
+
{props.trackedChanges.totalCount > 0 ? (
|
|
77
|
+
<span className="ml-1 inline-flex min-w-[14px] items-center justify-center rounded-full bg-subtle px-1.5 py-px text-[10px] font-medium text-tertiary">
|
|
78
|
+
{props.trackedChanges.totalCount}
|
|
79
|
+
</span>
|
|
80
|
+
) : null}
|
|
76
81
|
</Tabs.Trigger>
|
|
77
|
-
{/* Health moved to toolbar popover */}
|
|
78
82
|
</Tabs.List>
|
|
79
83
|
|
|
80
84
|
<ScrollArea.Root className="flex-1 min-h-0">
|
|
81
85
|
<ScrollArea.Viewport className="h-full w-full">
|
|
82
|
-
<Tabs.Content value="comments" className="p-
|
|
86
|
+
<Tabs.Content value="comments" className="p-3 outline-none">
|
|
83
87
|
<TwCommentSidebar
|
|
84
88
|
currentUserId={props.currentUserId}
|
|
85
89
|
comments={props.comments}
|
|
@@ -92,7 +96,7 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
92
96
|
/>
|
|
93
97
|
</Tabs.Content>
|
|
94
98
|
|
|
95
|
-
<Tabs.Content value="changes" className="p-
|
|
99
|
+
<Tabs.Content value="changes" className="p-3 outline-none">
|
|
96
100
|
<TwRevisionSidebar
|
|
97
101
|
trackedChanges={props.trackedChanges}
|
|
98
102
|
markupDisplay={props.markupDisplay}
|
|
@@ -104,14 +108,12 @@ export function TwReviewRail(props: TwReviewRailProps) {
|
|
|
104
108
|
onRejectAllChanges={props.onRejectAllChanges}
|
|
105
109
|
/>
|
|
106
110
|
</Tabs.Content>
|
|
107
|
-
|
|
108
|
-
{/* Health panel moved to toolbar popover */}
|
|
109
111
|
</ScrollArea.Viewport>
|
|
110
112
|
<ScrollArea.Scrollbar
|
|
111
113
|
orientation="vertical"
|
|
112
114
|
className="flex w-1.5 touch-none select-none p-0.5"
|
|
113
115
|
>
|
|
114
|
-
<ScrollArea.Thumb className="relative flex-1 rounded-full bg-
|
|
116
|
+
<ScrollArea.Thumb className="relative flex-1 rounded-full bg-[color:color-mix(in_srgb,var(--color-secondary)_18%,transparent)]" />
|
|
115
117
|
</ScrollArea.Scrollbar>
|
|
116
118
|
</ScrollArea.Root>
|
|
117
119
|
</Tabs.Root>
|
|
@@ -28,12 +28,12 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
30
|
<div className="outline-none">
|
|
31
|
-
<p className="mb-
|
|
31
|
+
<p className="mb-3 text-[11px] text-tertiary">
|
|
32
32
|
{trackedChanges.pendingChangeIds.length} active · {trackedChanges.acceptedChangeIds.length} accepted · {trackedChanges.preserveOnlyChangeIds.length} preserve-only
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
{/* Bulk actions */}
|
|
36
|
-
<div className="mb-
|
|
36
|
+
<div className="mb-3 flex gap-1.5">
|
|
37
37
|
<button
|
|
38
38
|
type="button"
|
|
39
39
|
disabled={actionablePendingCount === 0}
|
|
@@ -45,7 +45,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
45
45
|
<button
|
|
46
46
|
type="button"
|
|
47
47
|
disabled={actionablePendingCount === 0}
|
|
48
|
-
className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] text-secondary hover:bg-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
48
|
+
className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] text-secondary hover:bg-surface-hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
49
49
|
onClick={props.onRejectAllChanges}
|
|
50
50
|
>
|
|
51
51
|
Reject all
|
|
@@ -53,7 +53,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
55
|
{visibleRevisions.length > 0 ? (
|
|
56
|
-
<div className="space-y-
|
|
56
|
+
<div className="space-y-2">
|
|
57
57
|
{visibleRevisions.map((rev) => {
|
|
58
58
|
const isActive = activeRevisionId === rev.revisionId;
|
|
59
59
|
|
|
@@ -62,7 +62,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
62
62
|
key={rev.revisionId}
|
|
63
63
|
role="button"
|
|
64
64
|
tabIndex={0}
|
|
65
|
-
className={`flex rounded-
|
|
65
|
+
className={`flex cursor-pointer rounded-md bg-surface/90 transition-colors ring-1 ring-border/40 ${focusRingClass} ${isActive ? "bg-accent-soft/40 ring-accent/25" : "hover:bg-surface"}`}
|
|
66
66
|
onClick={() => props.onOpenRevision?.(rev)}
|
|
67
67
|
onKeyDown={(event) => {
|
|
68
68
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -71,7 +71,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
71
71
|
}
|
|
72
72
|
}}
|
|
73
73
|
>
|
|
74
|
-
<div className={`w-0.5 shrink-0 rounded-l-
|
|
74
|
+
<div className={`w-0.5 shrink-0 rounded-l-md ${
|
|
75
75
|
rev.kind === "insertion" ? "bg-insert"
|
|
76
76
|
: rev.kind === "deletion" ? "bg-danger"
|
|
77
77
|
: "bg-tertiary"
|
|
@@ -96,13 +96,13 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
96
96
|
{rev.detail ? (
|
|
97
97
|
<p className="mt-1 text-[10px] text-secondary">{rev.detail}</p>
|
|
98
98
|
) : null}
|
|
99
|
-
<div className="mt-
|
|
99
|
+
<div className="mt-2 flex gap-1.5">
|
|
100
100
|
{rev.actionability === "actionable" ? (
|
|
101
101
|
<>
|
|
102
102
|
<button
|
|
103
103
|
type="button"
|
|
104
104
|
disabled={!rev.canAccept || rev.status === "accepted"}
|
|
105
|
-
className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-
|
|
105
|
+
className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-accent hover:bg-accent-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
106
106
|
onClick={(e) => {
|
|
107
107
|
e.stopPropagation();
|
|
108
108
|
props.onAcceptRevision?.(rev.revisionId);
|
|
@@ -113,7 +113,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
113
113
|
<button
|
|
114
114
|
type="button"
|
|
115
115
|
disabled={!rev.canReject || rev.status === "rejected"}
|
|
116
|
-
className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-danger hover:bg-
|
|
116
|
+
className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] text-danger hover:bg-danger-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
117
117
|
onClick={(e) => {
|
|
118
118
|
e.stopPropagation();
|
|
119
119
|
props.onRejectRevision?.(rev.revisionId);
|
|
@@ -132,7 +132,7 @@ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
|
|
|
132
132
|
})}
|
|
133
133
|
</div>
|
|
134
134
|
) : (
|
|
135
|
-
<p className="text-xs text-tertiary
|
|
135
|
+
<p className="rounded-lg bg-surface px-3 py-4 text-xs text-tertiary ring-1 ring-border/40">
|
|
136
136
|
{trackedChanges.totalCount > 0
|
|
137
137
|
? "Switch to Full markup to see all tracked changes."
|
|
138
138
|
: "Tracked change cards will appear here when present."}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
import type { RuntimeContextAnalyticsSnapshot } from "../../api/public-types";
|
|
4
|
+
|
|
3
5
|
export interface TwStatusBarProps {
|
|
4
6
|
isDirty: boolean;
|
|
5
7
|
isExportBlocked: boolean;
|
|
@@ -7,6 +9,7 @@ export interface TwStatusBarProps {
|
|
|
7
9
|
commentCount: number;
|
|
8
10
|
changeCount: number;
|
|
9
11
|
sessionId: string;
|
|
12
|
+
contextAnalytics?: RuntimeContextAnalyticsSnapshot | null;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export function TwStatusBar(props: TwStatusBarProps) {
|
|
@@ -20,11 +23,10 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
20
23
|
: props.preserveOnlyCount > 0
|
|
21
24
|
? "Warnings"
|
|
22
25
|
: "Ready";
|
|
23
|
-
|
|
24
26
|
return (
|
|
25
27
|
<footer
|
|
26
28
|
data-testid="status-bar"
|
|
27
|
-
className="flex h-7 shrink-0 items-center gap-
|
|
29
|
+
className="flex h-7 shrink-0 items-center gap-3 border-t border-border/60 bg-surface/72 px-3 text-[11px] text-tertiary"
|
|
28
30
|
>
|
|
29
31
|
<span className="flex items-center gap-1.5">
|
|
30
32
|
<span
|
|
@@ -33,7 +35,7 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
33
35
|
? "bg-danger"
|
|
34
36
|
: props.isDirty
|
|
35
37
|
? "bg-comment"
|
|
36
|
-
: "bg-
|
|
38
|
+
: "bg-accent"
|
|
37
39
|
}`}
|
|
38
40
|
/>
|
|
39
41
|
{saveState}
|
|
@@ -44,8 +46,8 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
44
46
|
props.isExportBlocked
|
|
45
47
|
? "bg-danger"
|
|
46
48
|
: props.preserveOnlyCount > 0
|
|
47
|
-
? "bg-
|
|
48
|
-
: "bg-
|
|
49
|
+
? "bg-warning"
|
|
50
|
+
: "bg-accent"
|
|
49
51
|
}`}
|
|
50
52
|
/>
|
|
51
53
|
Export {exportState.toLowerCase()}
|
|
@@ -55,7 +57,9 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
55
57
|
{props.changeCount} change{props.changeCount !== 1 ? "s" : ""}
|
|
56
58
|
</span>
|
|
57
59
|
<span className="flex-1" />
|
|
58
|
-
<span
|
|
60
|
+
<span className="truncate text-[10px] uppercase tracking-[0.12em] text-tertiary/80">
|
|
61
|
+
Session active
|
|
62
|
+
</span>
|
|
59
63
|
</footer>
|
|
60
64
|
);
|
|
61
65
|
}
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
@theme {
|
|
19
19
|
/* Backgrounds */
|
|
20
|
-
--color-surface: #
|
|
21
|
-
--color-surface-hover: #
|
|
22
|
-
--color-surface-active: #
|
|
23
|
-
--color-subtle: #
|
|
20
|
+
--color-surface: #fafbf9;
|
|
21
|
+
--color-surface-hover: #f1f4f1;
|
|
22
|
+
--color-surface-active: #e8eeea;
|
|
23
|
+
--color-subtle: #eff3ef;
|
|
24
24
|
--color-canvas: #ffffff;
|
|
25
|
-
--color-shell-bg: #
|
|
26
|
-
--color-card: #
|
|
27
|
-
--color-editor-frame: #
|
|
25
|
+
--color-shell-bg: #f4f8f5;
|
|
26
|
+
--color-card: #ffffff;
|
|
27
|
+
--color-editor-frame: #f7faf7;
|
|
28
|
+
--color-sidebar-tint: rgba(247, 250, 247, 0.92);
|
|
28
29
|
|
|
29
30
|
/* Text */
|
|
30
31
|
--color-primary: #1f1f1f;
|
|
@@ -32,29 +33,31 @@
|
|
|
32
33
|
--color-tertiary: #616161;
|
|
33
34
|
|
|
34
35
|
/* Accent */
|
|
35
|
-
--color-accent: #
|
|
36
|
-
--color-accent-soft: rgba(
|
|
36
|
+
--color-accent: #18b394;
|
|
37
|
+
--color-accent-soft: rgba(24, 179, 148, 0.1);
|
|
38
|
+
--color-workflow: #7557d8;
|
|
39
|
+
--color-workflow-soft: rgba(117, 87, 216, 0.12);
|
|
37
40
|
|
|
38
41
|
/* Review — comments */
|
|
39
|
-
--color-comment: #
|
|
40
|
-
--color-comment-soft: rgba(
|
|
41
|
-
--color-comment-strong: rgba(
|
|
42
|
+
--color-comment: #7557d8;
|
|
43
|
+
--color-comment-soft: rgba(117, 87, 216, 0.12);
|
|
44
|
+
--color-comment-strong: rgba(117, 87, 216, 0.18);
|
|
42
45
|
|
|
43
46
|
/* Review — insertions */
|
|
44
|
-
--color-insert: #
|
|
45
|
-
--color-insert-soft: rgba(
|
|
47
|
+
--color-insert: #2f6b4f;
|
|
48
|
+
--color-insert-soft: rgba(47, 107, 79, 0.1);
|
|
46
49
|
|
|
47
50
|
/* Review — deletions */
|
|
48
|
-
--color-delete: #
|
|
49
|
-
--color-delete-soft: rgba(
|
|
51
|
+
--color-delete: #9b4f49;
|
|
52
|
+
--color-delete-soft: rgba(155, 79, 73, 0.08);
|
|
50
53
|
|
|
51
54
|
/* Semantic */
|
|
52
|
-
--color-warning: #
|
|
53
|
-
--color-warning-soft: rgba(
|
|
54
|
-
--color-danger: #
|
|
55
|
-
--color-danger-soft: rgba(
|
|
56
|
-
--color-success: #
|
|
57
|
-
--color-success-soft: rgba(
|
|
55
|
+
--color-warning: #7557d8;
|
|
56
|
+
--color-warning-soft: rgba(117, 87, 216, 0.12);
|
|
57
|
+
--color-danger: #9b4f49;
|
|
58
|
+
--color-danger-soft: rgba(155, 79, 73, 0.1);
|
|
59
|
+
--color-success: #2f6b4f;
|
|
60
|
+
--color-success-soft: rgba(47, 107, 79, 0.1);
|
|
58
61
|
|
|
59
62
|
/* Borders */
|
|
60
63
|
--color-border: rgba(0, 0, 0, 0.06);
|
|
@@ -85,28 +88,31 @@
|
|
|
85
88
|
--color-shell-bg: #171514;
|
|
86
89
|
--color-card: #23211f;
|
|
87
90
|
--color-editor-frame: #1d1b19;
|
|
91
|
+
--color-sidebar-tint: rgba(29, 27, 25, 0.94);
|
|
88
92
|
|
|
89
93
|
--color-primary: #e4e4e4;
|
|
90
94
|
--color-secondary: #a0a0a0;
|
|
91
95
|
--color-tertiary: #6e6e6e;
|
|
92
96
|
|
|
93
|
-
--color-accent: #
|
|
94
|
-
--color-accent-soft: rgba(
|
|
95
|
-
|
|
96
|
-
--color-
|
|
97
|
-
|
|
98
|
-
--color-comment
|
|
99
|
-
--color-
|
|
100
|
-
--color-
|
|
101
|
-
--color-
|
|
102
|
-
--color-
|
|
103
|
-
|
|
104
|
-
--color-
|
|
105
|
-
|
|
106
|
-
--color-
|
|
107
|
-
--color-
|
|
108
|
-
--color-
|
|
109
|
-
--color-
|
|
97
|
+
--color-accent: #5edec1;
|
|
98
|
+
--color-accent-soft: rgba(94, 222, 193, 0.14);
|
|
99
|
+
--color-workflow: #c7b6ff;
|
|
100
|
+
--color-workflow-soft: rgba(199, 182, 255, 0.16);
|
|
101
|
+
|
|
102
|
+
--color-comment: #c7b6ff;
|
|
103
|
+
--color-comment-soft: rgba(199, 182, 255, 0.18);
|
|
104
|
+
--color-comment-strong: rgba(199, 182, 255, 0.24);
|
|
105
|
+
--color-insert: #8fd2a9;
|
|
106
|
+
--color-insert-soft: rgba(143, 210, 169, 0.18);
|
|
107
|
+
--color-delete: #d78d84;
|
|
108
|
+
--color-delete-soft: rgba(215, 141, 132, 0.14);
|
|
109
|
+
|
|
110
|
+
--color-warning: #c7b6ff;
|
|
111
|
+
--color-warning-soft: rgba(199, 182, 255, 0.16);
|
|
112
|
+
--color-danger: #d78d84;
|
|
113
|
+
--color-danger-soft: rgba(215, 141, 132, 0.16);
|
|
114
|
+
--color-success: #8fd2a9;
|
|
115
|
+
--color-success-soft: rgba(143, 210, 169, 0.18);
|
|
110
116
|
|
|
111
117
|
--color-border: rgba(255, 255, 255, 0.08);
|
|
112
118
|
--color-border-strong: rgba(255, 255, 255, 0.14);
|
|
@@ -184,7 +190,7 @@
|
|
|
184
190
|
background: var(--color-page-bg);
|
|
185
191
|
border: 1px solid var(--color-page-border);
|
|
186
192
|
border-radius: 2px;
|
|
187
|
-
box-shadow: 0
|
|
193
|
+
box-shadow: 0 8px 24px -20px var(--color-page-shadow);
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
/* Canvas-mode typography — lighter, review-first baseline */
|
|
@@ -193,6 +199,12 @@
|
|
|
193
199
|
font-size: 15px;
|
|
194
200
|
line-height: 1.6;
|
|
195
201
|
color: var(--color-primary);
|
|
202
|
+
width: min(100%, 920px);
|
|
203
|
+
min-height: 100%;
|
|
204
|
+
background: var(--color-page-bg);
|
|
205
|
+
border: 1px solid var(--color-page-border);
|
|
206
|
+
border-radius: 2px;
|
|
207
|
+
box-shadow: 0 6px 18px -14px var(--color-page-shadow);
|
|
196
208
|
-webkit-font-smoothing: antialiased;
|
|
197
209
|
-moz-osx-font-smoothing: grayscale;
|
|
198
210
|
text-rendering: optimizeLegibility;
|
|
@@ -248,6 +260,7 @@
|
|
|
248
260
|
cursor: text;
|
|
249
261
|
border: none;
|
|
250
262
|
box-shadow: none;
|
|
263
|
+
white-space: pre-wrap;
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
.prosemirror-surface .ProseMirror:focus {
|
|
@@ -294,6 +307,11 @@
|
|
|
294
307
|
text-underline-offset: 0.18em;
|
|
295
308
|
}
|
|
296
309
|
|
|
310
|
+
.prosemirror-surface .ProseMirror .wre-workflow-inline-metadata {
|
|
311
|
+
background: color-mix(in srgb, var(--wre-workflow-metadata-color, var(--color-accent)) 12%, transparent);
|
|
312
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wre-workflow-metadata-color, var(--color-accent)) 22%, transparent);
|
|
313
|
+
}
|
|
314
|
+
|
|
297
315
|
.prosemirror-surface .ProseMirror .wre-workflow-inline-preserve-only {
|
|
298
316
|
background: color-mix(in srgb, var(--color-danger) 7%, transparent);
|
|
299
317
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-danger) 18%, transparent);
|