@beyondwork/docx-react-component 1.0.13 → 1.0.15
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 +32 -35
- package/src/api/public-types.ts +6 -0
- package/src/compare/diff-engine.ts +84 -7
- package/src/compare/index.ts +25 -0
- package/src/compare/snapshot.ts +31 -0
- package/src/core/selection/review-anchors.ts +89 -0
- package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
- package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
- package/src/formats/xlsx/io/serialize-styles.ts +98 -0
- package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
- package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
- package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
- package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
- package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
- package/src/io/ooxml/parse-main-document.ts +6 -6
- package/src/io/ooxml/parse-revisions.ts +18 -24
- package/src/legal/bookmarks.ts +35 -0
- package/src/legal/index.ts +32 -0
- package/src/legal/signature-blocks.ts +259 -0
- package/src/runtime/document-runtime.ts +13 -0
- package/src/runtime/numbering-prefix.ts +195 -0
- package/src/runtime/session-capabilities.ts +22 -1
- package/src/runtime/surface-projection.ts +287 -8
- package/src/ui/WordReviewEditor.tsx +120 -10
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +8 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +148 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +15 -29
- package/src/ui-tailwind/tw-review-workspace.tsx +3 -0
|
@@ -10,6 +10,55 @@ const HEX_COLOR_RE = /^[0-9A-Fa-f]{3,8}$/;
|
|
|
10
10
|
const SAFE_FONT_RE = /^[A-Za-z0-9 ,\-'"]+$/;
|
|
11
11
|
const SAFE_ALIGNMENT = new Set(["left", "center", "right", "justify", "start", "end"]);
|
|
12
12
|
|
|
13
|
+
function resolveHeadingLevel(
|
|
14
|
+
styleId: string | null,
|
|
15
|
+
outlineLevel: number | null,
|
|
16
|
+
): number | null {
|
|
17
|
+
if (styleId) {
|
|
18
|
+
const normalized = styleId.toLowerCase();
|
|
19
|
+
const headingMatch = /^heading([1-6])$/.exec(normalized);
|
|
20
|
+
if (headingMatch) {
|
|
21
|
+
return Number.parseInt(headingMatch[1], 10);
|
|
22
|
+
}
|
|
23
|
+
if (normalized === "title") {
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
if (normalized === "subtitle") {
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
typeof outlineLevel === "number" &&
|
|
33
|
+
Number.isInteger(outlineLevel) &&
|
|
34
|
+
outlineLevel >= 0 &&
|
|
35
|
+
outlineLevel <= 5
|
|
36
|
+
) {
|
|
37
|
+
return outlineLevel + 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function headingClassList(level: number): string[] {
|
|
44
|
+
switch (level) {
|
|
45
|
+
case 1:
|
|
46
|
+
return ["text-3xl", "font-semibold", "tracking-tight", "leading-tight"];
|
|
47
|
+
case 2:
|
|
48
|
+
return ["text-2xl", "font-semibold", "tracking-tight"];
|
|
49
|
+
case 3:
|
|
50
|
+
return ["text-xl", "font-medium"];
|
|
51
|
+
case 4:
|
|
52
|
+
return ["text-lg", "font-medium"];
|
|
53
|
+
case 5:
|
|
54
|
+
return ["text-base", "font-semibold", "uppercase", "tracking-[0.12em]"];
|
|
55
|
+
case 6:
|
|
56
|
+
return ["text-sm", "font-semibold", "uppercase", "tracking-[0.16em]"];
|
|
57
|
+
default:
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
13
62
|
/** Validate a raw hex color string from OOXML (no leading #). Returns sanitized `#hex` or null. */
|
|
14
63
|
function safeHexColor(raw: string | null | undefined): string | null {
|
|
15
64
|
if (!raw || raw === "auto") return null;
|
|
@@ -45,6 +94,7 @@ export const editorSchema = new Schema({
|
|
|
45
94
|
styleId: { default: null },
|
|
46
95
|
numberingInstanceId: { default: null },
|
|
47
96
|
numberingLevel: { default: null },
|
|
97
|
+
numberingPrefix: { default: null },
|
|
48
98
|
alignment: { default: null },
|
|
49
99
|
spacingBefore: { default: null },
|
|
50
100
|
spacingAfter: { default: null },
|
|
@@ -58,6 +108,7 @@ export const editorSchema = new Schema({
|
|
|
58
108
|
borderBottom: { default: null },
|
|
59
109
|
borderLeft: { default: null },
|
|
60
110
|
borderRight: { default: null },
|
|
111
|
+
outlineLevel: { default: null },
|
|
61
112
|
bidi: { default: null },
|
|
62
113
|
pageBreakBefore: { default: null },
|
|
63
114
|
},
|
|
@@ -65,11 +116,10 @@ export const editorSchema = new Schema({
|
|
|
65
116
|
toDOM(node) {
|
|
66
117
|
const classes: string[] = ["leading-relaxed"];
|
|
67
118
|
const styleId = node.attrs.styleId as string | null;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
else if (lower === "heading3") classes.push("text-lg font-medium");
|
|
119
|
+
const outlineLevel = node.attrs.outlineLevel as number | null;
|
|
120
|
+
const headingLevel = resolveHeadingLevel(styleId, outlineLevel);
|
|
121
|
+
if (headingLevel) {
|
|
122
|
+
classes.push(...headingClassList(headingLevel));
|
|
73
123
|
}
|
|
74
124
|
const attrs: Record<string, string> = { class: classes.join(" ") };
|
|
75
125
|
const styles: string[] = [];
|
|
@@ -106,8 +156,50 @@ export const editorSchema = new Schema({
|
|
|
106
156
|
if (pageBreak) styles.push("border-top: 2px dashed rgba(0,0,0,0.1); padding-top: 8px; margin-top: 16px");
|
|
107
157
|
const bidi = node.attrs.bidi as boolean | null;
|
|
108
158
|
if (bidi) attrs.dir = "rtl";
|
|
159
|
+
if (headingLevel) {
|
|
160
|
+
attrs["data-heading-level"] = String(headingLevel);
|
|
161
|
+
}
|
|
109
162
|
if (styles.length > 0) attrs.style = styles.join("; ");
|
|
110
|
-
|
|
163
|
+
const numberingPrefix = node.attrs.numberingPrefix as string | null;
|
|
164
|
+
const numberingLevel = node.attrs.numberingLevel as number | null;
|
|
165
|
+
const children: Array<string | number | readonly unknown[]> = [];
|
|
166
|
+
if (pageBreak) {
|
|
167
|
+
children.push([
|
|
168
|
+
"span",
|
|
169
|
+
{
|
|
170
|
+
class:
|
|
171
|
+
"mb-2 inline-flex items-center gap-2 text-[10px] font-medium uppercase tracking-[0.18em] text-tertiary",
|
|
172
|
+
contenteditable: "false",
|
|
173
|
+
"data-page-break-before": "true",
|
|
174
|
+
},
|
|
175
|
+
"Page break",
|
|
176
|
+
]);
|
|
177
|
+
}
|
|
178
|
+
if (numberingPrefix) {
|
|
179
|
+
const minWidth = Math.min(Math.max(numberingPrefix.length + 1, 4), 14);
|
|
180
|
+
children.push([
|
|
181
|
+
"span",
|
|
182
|
+
{
|
|
183
|
+
class:
|
|
184
|
+
"inline-flex select-none items-center justify-end text-tertiary font-[family-name:var(--font-legal-sans)]",
|
|
185
|
+
contenteditable: "false",
|
|
186
|
+
"data-numbering-prefix": numberingPrefix,
|
|
187
|
+
...(typeof numberingLevel === "number"
|
|
188
|
+
? { "data-numbering-level": String(numberingLevel) }
|
|
189
|
+
: {}),
|
|
190
|
+
style: `min-width: ${minWidth}ch; margin-right: 0.75rem; font-variant-numeric: tabular-nums;`,
|
|
191
|
+
},
|
|
192
|
+
numberingPrefix,
|
|
193
|
+
]);
|
|
194
|
+
}
|
|
195
|
+
children.push([
|
|
196
|
+
"span",
|
|
197
|
+
{
|
|
198
|
+
class: "pm-paragraph-content",
|
|
199
|
+
},
|
|
200
|
+
0,
|
|
201
|
+
]);
|
|
202
|
+
return ["p", attrs, ...children];
|
|
111
203
|
},
|
|
112
204
|
},
|
|
113
205
|
|
|
@@ -132,13 +224,42 @@ export const editorSchema = new Schema({
|
|
|
132
224
|
selectable: false,
|
|
133
225
|
attrs: {
|
|
134
226
|
tabWidth: { default: null },
|
|
227
|
+
leader: { default: null },
|
|
228
|
+
align: { default: null },
|
|
135
229
|
},
|
|
136
230
|
toDOM(node) {
|
|
137
231
|
const width = node.attrs.tabWidth as number | null;
|
|
138
|
-
|
|
139
|
-
|
|
232
|
+
const leader = node.attrs.leader as string | null;
|
|
233
|
+
const align = node.attrs.align as string | null;
|
|
234
|
+
const styles = [
|
|
235
|
+
`display: inline-block`,
|
|
236
|
+
`width: ${width && width > 0 ? width : 32}px`,
|
|
237
|
+
`min-width: 8px`,
|
|
238
|
+
];
|
|
239
|
+
if (leader === "dot" || leader === "middleDot") {
|
|
240
|
+
styles.push(
|
|
241
|
+
`background-image: radial-gradient(circle, currentColor 1px, transparent 1.25px)`,
|
|
242
|
+
`background-size: 6px 3px`,
|
|
243
|
+
`background-repeat: repeat-x`,
|
|
244
|
+
`background-position: left calc(100% - 2px)`,
|
|
245
|
+
`opacity: 0.55`,
|
|
246
|
+
);
|
|
247
|
+
} else if (leader === "hyphen") {
|
|
248
|
+
styles.push(`border-bottom: 1px dashed rgba(107,107,107,0.65)`);
|
|
249
|
+
} else if (leader === "underscore") {
|
|
250
|
+
styles.push(`border-bottom: 1px solid rgba(107,107,107,0.65)`);
|
|
251
|
+
} else if (leader === "heavy") {
|
|
252
|
+
styles.push(`border-bottom: 2px solid rgba(107,107,107,0.75)`);
|
|
140
253
|
}
|
|
141
|
-
return [
|
|
254
|
+
return [
|
|
255
|
+
"span",
|
|
256
|
+
{
|
|
257
|
+
style: styles.join("; "),
|
|
258
|
+
"data-node-type": "tab",
|
|
259
|
+
title: align ? `Tab stop · ${align}` : "Tab stop",
|
|
260
|
+
},
|
|
261
|
+
"\u00A0",
|
|
262
|
+
];
|
|
142
263
|
},
|
|
143
264
|
},
|
|
144
265
|
|
|
@@ -366,19 +487,33 @@ export const editorSchema = new Schema({
|
|
|
366
487
|
detail: { default: "" },
|
|
367
488
|
},
|
|
368
489
|
toDOM(node) {
|
|
490
|
+
const fragmentId = node.attrs.fragmentId as string;
|
|
491
|
+
const isPreview = fragmentId.startsWith("preview:");
|
|
369
492
|
return [
|
|
370
493
|
"div",
|
|
371
494
|
{
|
|
372
|
-
class:
|
|
495
|
+
class: isPreview
|
|
496
|
+
? "my-3 rounded-xl border border-primary/15 bg-surface-raised/50 px-4 py-3"
|
|
497
|
+
: "border-l-2 border-dashed border-warning/30 pl-4 py-2 rounded-r bg-warning-soft/20 my-2",
|
|
373
498
|
contenteditable: "false",
|
|
374
499
|
"data-node-type": "opaque_block",
|
|
375
500
|
},
|
|
376
501
|
[
|
|
377
502
|
"div",
|
|
378
|
-
{
|
|
379
|
-
|
|
503
|
+
{
|
|
504
|
+
class: isPreview
|
|
505
|
+
? "mb-2 text-[11px] uppercase tracking-[0.18em] text-tertiary"
|
|
506
|
+
: "flex items-center gap-1.5 text-xs text-tertiary mb-1",
|
|
507
|
+
},
|
|
508
|
+
`${isPreview ? "" : "\uD83D\uDD12 "}${node.attrs.label as string}`,
|
|
509
|
+
],
|
|
510
|
+
[
|
|
511
|
+
"p",
|
|
512
|
+
{
|
|
513
|
+
class: isPreview ? "text-sm text-secondary whitespace-pre-wrap leading-relaxed" : "text-sm text-secondary whitespace-pre-wrap",
|
|
514
|
+
},
|
|
515
|
+
node.attrs.detail as string,
|
|
380
516
|
],
|
|
381
|
-
["p", { class: "text-sm text-secondary" }, node.attrs.detail as string],
|
|
382
517
|
];
|
|
383
518
|
},
|
|
384
519
|
},
|
|
@@ -100,7 +100,15 @@ function buildParagraph(
|
|
|
100
100
|
? ((prevStop as { pos?: number }).pos ?? (prevStop as { position?: number }).position ?? 0)
|
|
101
101
|
: 0;
|
|
102
102
|
const widthPx = Math.round((stopPos - prevPos) / 15);
|
|
103
|
-
|
|
103
|
+
const leader = (stop as { leader?: string }).leader ?? null;
|
|
104
|
+
const align = (stop as { val?: string }).val ?? null;
|
|
105
|
+
content.push(
|
|
106
|
+
editorSchema.nodes.tab_char.create({
|
|
107
|
+
tabWidth: widthPx > 8 ? widthPx : null,
|
|
108
|
+
leader,
|
|
109
|
+
align,
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
104
112
|
tabIndex++;
|
|
105
113
|
} else {
|
|
106
114
|
const nodes = buildInlineContent(segment);
|
|
@@ -113,6 +121,9 @@ function buildParagraph(
|
|
|
113
121
|
styleId: block.styleId ?? null,
|
|
114
122
|
numberingInstanceId: block.numbering?.numberingInstanceId ?? null,
|
|
115
123
|
numberingLevel: block.numbering?.level ?? null,
|
|
124
|
+
numberingPrefix:
|
|
125
|
+
(block as typeof block & { numberingPrefix?: string }).numberingPrefix ??
|
|
126
|
+
null,
|
|
116
127
|
alignment: block.alignment ?? null,
|
|
117
128
|
spacingBefore: block.spacing?.before ?? null,
|
|
118
129
|
spacingAfter: block.spacing?.after ?? null,
|
|
@@ -126,6 +137,7 @@ function buildParagraph(
|
|
|
126
137
|
borderBottom: (block.borders as Record<string, unknown>)?.bottom ?? null,
|
|
127
138
|
borderLeft: (block.borders as Record<string, unknown>)?.left ?? null,
|
|
128
139
|
borderRight: (block.borders as Record<string, unknown>)?.right ?? null,
|
|
140
|
+
outlineLevel: block.outlineLevel ?? null,
|
|
129
141
|
bidi: block.bidi ?? null,
|
|
130
142
|
pageBreakBefore: block.pageBreakBefore ?? null,
|
|
131
143
|
},
|
|
@@ -231,19 +243,9 @@ function buildTable(
|
|
|
231
243
|
if (child.kind === "paragraph") {
|
|
232
244
|
cellContent.push(buildParagraph(child as Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>));
|
|
233
245
|
} else if (child.kind === "table") {
|
|
234
|
-
cellContent.push(
|
|
246
|
+
cellContent.push(buildTable(child as Extract<SurfaceBlockSnapshot, { kind: "table" }>));
|
|
235
247
|
} else if (child.kind === "sdt_block") {
|
|
236
|
-
cellContent.push(
|
|
237
|
-
blockId: child.blockId,
|
|
238
|
-
kind: "opaque_block",
|
|
239
|
-
from: child.from,
|
|
240
|
-
to: child.to,
|
|
241
|
-
fragmentId: child.blockId,
|
|
242
|
-
warningId: child.blockId,
|
|
243
|
-
label: child.alias ?? child.tag ?? "Content control",
|
|
244
|
-
detail: "Structured content control remains read-only inside table cells.",
|
|
245
|
-
state: "locked-preserve-only",
|
|
246
|
-
}));
|
|
248
|
+
cellContent.push(buildSdtBlock(child as Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>));
|
|
247
249
|
} else if (child.kind === "opaque_block") {
|
|
248
250
|
cellContent.push(buildOpaqueBlock(child as Extract<SurfaceBlockSnapshot, { kind: "opaque_block" }>));
|
|
249
251
|
}
|
|
@@ -276,22 +278,6 @@ function buildTable(
|
|
|
276
278
|
);
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
function buildNestedTablePlaceholder(
|
|
280
|
-
block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
|
|
281
|
-
): PMNode {
|
|
282
|
-
return buildOpaqueBlock({
|
|
283
|
-
blockId: block.blockId,
|
|
284
|
-
kind: "opaque_block",
|
|
285
|
-
from: block.from,
|
|
286
|
-
to: block.to,
|
|
287
|
-
fragmentId: block.blockId,
|
|
288
|
-
warningId: block.blockId,
|
|
289
|
-
label: "Nested table",
|
|
290
|
-
detail: "Nested table remains read-only in the live ProseMirror cell surface.",
|
|
291
|
-
state: "locked-preserve-only",
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
281
|
function buildSdtBlock(
|
|
296
282
|
block: Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>,
|
|
297
283
|
): PMNode {
|
|
@@ -27,6 +27,7 @@ export interface TwReviewWorkspaceProps {
|
|
|
27
27
|
activeRevisionId?: string;
|
|
28
28
|
showTrackedChanges: boolean;
|
|
29
29
|
selectionPreview?: string | null;
|
|
30
|
+
addCommentDisabledReason?: string;
|
|
30
31
|
onViewModeChange: (value: ViewMode) => void;
|
|
31
32
|
onActiveRailTabChange: (value: ReviewRailTab) => void;
|
|
32
33
|
onShowTrackedChangesChange: (show: boolean) => void;
|
|
@@ -92,6 +93,8 @@ export function TwReviewWorkspace(props: TwReviewWorkspaceProps) {
|
|
|
92
93
|
<TwSelectionToolbar
|
|
93
94
|
selectionPreview={props.selectionPreview}
|
|
94
95
|
readOnly={snapshot.readOnly}
|
|
96
|
+
canAddComment={props.capabilities?.canAddComment}
|
|
97
|
+
disabledReason={props.addCommentDisabledReason}
|
|
95
98
|
onAddComment={props.onAddComment}
|
|
96
99
|
/>
|
|
97
100
|
</div>
|