@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
|
@@ -34,8 +34,9 @@ export function createPMStateFromSnapshot(
|
|
|
34
34
|
selection: SelectionSnapshot,
|
|
35
35
|
plugins: Plugin[],
|
|
36
36
|
mediaPreviews: Record<string, MediaPreviewDescriptor> = {},
|
|
37
|
+
showUnsupportedObjectPreviews = true,
|
|
37
38
|
): PMStateResult {
|
|
38
|
-
const doc = buildPMDoc(surface, mediaPreviews);
|
|
39
|
+
const doc = buildPMDoc(surface, mediaPreviews, showUnsupportedObjectPreviews);
|
|
39
40
|
const positionMap = buildPositionMap(surface);
|
|
40
41
|
const pmSelection = createPMSelectionFromSnapshot(doc, positionMap, selection);
|
|
41
42
|
|
|
@@ -109,8 +110,9 @@ function resolveInlineBoundary(
|
|
|
109
110
|
function buildPMDoc(
|
|
110
111
|
surface: EditorSurfaceSnapshot,
|
|
111
112
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
113
|
+
showUnsupportedObjectPreviews: boolean,
|
|
112
114
|
): PMNode {
|
|
113
|
-
const blocks = buildPMBlocks(surface.blocks, mediaPreviews);
|
|
115
|
+
const blocks = buildPMBlocks(surface.blocks, mediaPreviews, showUnsupportedObjectPreviews);
|
|
114
116
|
|
|
115
117
|
// Ensure at least one block (PM requires non-empty doc)
|
|
116
118
|
if (blocks.length === 0) {
|
|
@@ -123,6 +125,7 @@ function buildPMDoc(
|
|
|
123
125
|
function buildPMBlocks(
|
|
124
126
|
blocks: SurfaceBlockSnapshot[],
|
|
125
127
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
128
|
+
showUnsupportedObjectPreviews: boolean,
|
|
126
129
|
): PMNode[] {
|
|
127
130
|
const nodes: PMNode[] = [];
|
|
128
131
|
|
|
@@ -134,11 +137,19 @@ function buildPMBlocks(
|
|
|
134
137
|
const nextParagraph = nextBlock?.kind === "paragraph" ? nextBlock : null;
|
|
135
138
|
|
|
136
139
|
if (block.kind === "paragraph") {
|
|
137
|
-
nodes.push(
|
|
140
|
+
nodes.push(
|
|
141
|
+
buildParagraph(
|
|
142
|
+
block,
|
|
143
|
+
previousParagraph,
|
|
144
|
+
nextParagraph,
|
|
145
|
+
mediaPreviews,
|
|
146
|
+
showUnsupportedObjectPreviews,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
138
149
|
} else if (block.kind === "table") {
|
|
139
|
-
nodes.push(buildTable(block, mediaPreviews));
|
|
150
|
+
nodes.push(buildTable(block, mediaPreviews, showUnsupportedObjectPreviews));
|
|
140
151
|
} else if (block.kind === "sdt_block") {
|
|
141
|
-
nodes.push(buildSdtBlock(block, mediaPreviews));
|
|
152
|
+
nodes.push(buildSdtBlock(block, mediaPreviews, showUnsupportedObjectPreviews));
|
|
142
153
|
} else {
|
|
143
154
|
nodes.push(buildOpaqueBlock(block));
|
|
144
155
|
}
|
|
@@ -152,10 +163,22 @@ function buildParagraph(
|
|
|
152
163
|
previousParagraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null,
|
|
153
164
|
nextParagraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null,
|
|
154
165
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
166
|
+
showUnsupportedObjectPreviews: boolean,
|
|
155
167
|
): PMNode {
|
|
156
168
|
const content: PMNode[] = [];
|
|
157
|
-
const
|
|
169
|
+
const paragraphLayout = resolveParagraphLayout(block);
|
|
170
|
+
const tabStops = paragraphLayout.tabStops;
|
|
158
171
|
let tabIndex = 0;
|
|
172
|
+
const textSegments = block.segments.filter(
|
|
173
|
+
(segment): segment is Extract<SurfaceInlineSegment, { kind: "text" }> => segment.kind === "text",
|
|
174
|
+
);
|
|
175
|
+
const fullyVanishedParagraph =
|
|
176
|
+
textSegments.length > 0 &&
|
|
177
|
+
textSegments.every((segment) => segment.marks?.includes("vanish")) &&
|
|
178
|
+
block.segments.every((segment) => segment.kind === "text");
|
|
179
|
+
|
|
180
|
+
if (fullyVanishedParagraph) {
|
|
181
|
+
}
|
|
159
182
|
|
|
160
183
|
for (const segment of block.segments) {
|
|
161
184
|
if (segment.kind === "tab" && tabIndex < tabStops.length) {
|
|
@@ -177,7 +200,7 @@ function buildParagraph(
|
|
|
177
200
|
);
|
|
178
201
|
tabIndex++;
|
|
179
202
|
} else {
|
|
180
|
-
const nodes = buildInlineContent(segment, mediaPreviews);
|
|
203
|
+
const nodes = buildInlineContent(segment, mediaPreviews, showUnsupportedObjectPreviews);
|
|
181
204
|
content.push(...nodes);
|
|
182
205
|
}
|
|
183
206
|
}
|
|
@@ -211,18 +234,20 @@ function buildParagraph(
|
|
|
211
234
|
(block as typeof block & { numberingSuffix?: string }).numberingSuffix ??
|
|
212
235
|
null,
|
|
213
236
|
alignment: block.alignment ?? null,
|
|
214
|
-
spacingBefore:
|
|
215
|
-
spacingAfter:
|
|
216
|
-
lineSpacing:
|
|
217
|
-
lineRule:
|
|
237
|
+
spacingBefore: paragraphLayout.spacing?.before ?? null,
|
|
238
|
+
spacingAfter: paragraphLayout.spacing?.after ?? null,
|
|
239
|
+
lineSpacing: paragraphLayout.spacing?.line ?? null,
|
|
240
|
+
lineRule: paragraphLayout.spacing?.lineRule ?? null,
|
|
218
241
|
contextualSpacing: block.contextualSpacing ?? null,
|
|
219
242
|
listContinuation: listContinuation || null,
|
|
220
243
|
contextualSpacingBefore: contextualSpacingBefore || null,
|
|
221
244
|
contextualSpacingAfter: contextualSpacingAfter || null,
|
|
222
|
-
indentLeft:
|
|
223
|
-
indentRight:
|
|
224
|
-
indentFirstLine:
|
|
225
|
-
indentHanging:
|
|
245
|
+
indentLeft: paragraphLayout.indentation?.left ?? null,
|
|
246
|
+
indentRight: paragraphLayout.indentation?.right ?? null,
|
|
247
|
+
indentFirstLine: paragraphLayout.indentation?.firstLine ?? null,
|
|
248
|
+
indentHanging: paragraphLayout.indentation?.hanging ?? null,
|
|
249
|
+
numberingMarkerWidth: paragraphLayout.markerLane?.width ?? null,
|
|
250
|
+
numberingMarkerJustification: paragraphLayout.markerJustification ?? null,
|
|
226
251
|
shadingFill: block.shading?.fill ?? null,
|
|
227
252
|
borderTop: (block.borders as Record<string, unknown>)?.top ?? null,
|
|
228
253
|
borderBottom: (block.borders as Record<string, unknown>)?.bottom ?? null,
|
|
@@ -231,14 +256,38 @@ function buildParagraph(
|
|
|
231
256
|
outlineLevel: block.outlineLevel ?? null,
|
|
232
257
|
bidi: block.bidi ?? null,
|
|
233
258
|
pageBreakBefore: block.pageBreakBefore ?? null,
|
|
259
|
+
hiddenTextOnly: fullyVanishedParagraph || null,
|
|
234
260
|
},
|
|
235
261
|
content.length > 0 ? Fragment.from(content) : undefined,
|
|
236
262
|
);
|
|
237
263
|
}
|
|
238
264
|
|
|
265
|
+
function resolveParagraphLayout(
|
|
266
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
267
|
+
): {
|
|
268
|
+
spacing: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["spacing"];
|
|
269
|
+
indentation: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["indentation"];
|
|
270
|
+
tabStops: Array<{ pos: number; val?: string; leader?: string }>;
|
|
271
|
+
markerLane: NonNullable<
|
|
272
|
+
NonNullable<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["resolvedNumbering"]>["geometry"]["markerLane"]
|
|
273
|
+
> | undefined;
|
|
274
|
+
markerJustification: NonNullable<
|
|
275
|
+
NonNullable<Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["resolvedNumbering"]>["geometry"]["markerJustification"]
|
|
276
|
+
> | undefined;
|
|
277
|
+
} {
|
|
278
|
+
return {
|
|
279
|
+
spacing: block.resolvedNumbering?.geometry.spacing ?? block.spacing,
|
|
280
|
+
indentation: block.resolvedNumbering?.geometry.indentation ?? block.indentation,
|
|
281
|
+
tabStops: block.resolvedNumbering?.geometry.tabStops ?? block.tabStops ?? [],
|
|
282
|
+
markerLane: block.resolvedNumbering?.geometry.markerLane,
|
|
283
|
+
markerJustification: block.resolvedNumbering?.geometry.markerJustification,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
239
287
|
function buildInlineContent(
|
|
240
288
|
segment: SurfaceInlineSegment,
|
|
241
289
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
290
|
+
showUnsupportedObjectPreviews: boolean,
|
|
242
291
|
): PMNode[] {
|
|
243
292
|
switch (segment.kind) {
|
|
244
293
|
case "text": {
|
|
@@ -324,7 +373,7 @@ function buildInlineContent(
|
|
|
324
373
|
}
|
|
325
374
|
|
|
326
375
|
case "opaque_inline":
|
|
327
|
-
return [buildOpaqueInlineOrComplexAtom(segment)];
|
|
376
|
+
return [buildOpaqueInlineOrComplexAtom(segment, showUnsupportedObjectPreviews)];
|
|
328
377
|
|
|
329
378
|
case "note_ref": {
|
|
330
379
|
const text = editorSchema.text(
|
|
@@ -353,18 +402,26 @@ function buildInlineContent(
|
|
|
353
402
|
function buildTable(
|
|
354
403
|
block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
|
|
355
404
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
405
|
+
showUnsupportedObjectPreviews: boolean,
|
|
356
406
|
): PMNode {
|
|
357
407
|
const rows: PMNode[] = [];
|
|
358
408
|
for (const row of block.rows) {
|
|
359
409
|
const cells: PMNode[] = [];
|
|
360
410
|
for (const cell of row.cells) {
|
|
361
|
-
const cellContent = buildPMBlocks(
|
|
411
|
+
const cellContent = buildPMBlocks(
|
|
412
|
+
cell.content,
|
|
413
|
+
mediaPreviews,
|
|
414
|
+
showUnsupportedObjectPreviews,
|
|
415
|
+
);
|
|
362
416
|
// Ensure at least one paragraph in cell (PM requires non-empty)
|
|
363
417
|
if (cellContent.length === 0) {
|
|
364
418
|
cellContent.push(editorSchema.nodes.paragraph.create());
|
|
365
419
|
}
|
|
420
|
+
const cellNodeType = row.isHeader
|
|
421
|
+
? editorSchema.nodes.table_header_cell
|
|
422
|
+
: editorSchema.nodes.table_cell;
|
|
366
423
|
cells.push(
|
|
367
|
-
|
|
424
|
+
cellNodeType.create(
|
|
368
425
|
{
|
|
369
426
|
colspan: cell.colspan,
|
|
370
427
|
rowspan: cell.rowspan,
|
|
@@ -411,8 +468,13 @@ function buildTable(
|
|
|
411
468
|
function buildSdtBlock(
|
|
412
469
|
block: Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>,
|
|
413
470
|
mediaPreviews: Record<string, MediaPreviewDescriptor>,
|
|
471
|
+
showUnsupportedObjectPreviews: boolean,
|
|
414
472
|
): PMNode {
|
|
415
|
-
const children = buildPMBlocks(
|
|
473
|
+
const children = buildPMBlocks(
|
|
474
|
+
block.children,
|
|
475
|
+
mediaPreviews,
|
|
476
|
+
showUnsupportedObjectPreviews,
|
|
477
|
+
);
|
|
416
478
|
|
|
417
479
|
if (children.length === 0) {
|
|
418
480
|
children.push(editorSchema.nodes.paragraph.create());
|
|
@@ -441,17 +503,18 @@ function buildSdtBlock(
|
|
|
441
503
|
*/
|
|
442
504
|
function buildOpaqueInlineOrComplexAtom(
|
|
443
505
|
segment: Extract<import("../../api/public-types").SurfaceInlineSegment, { kind: "opaque_inline" }>,
|
|
506
|
+
showUnsupportedObjectPreviews: boolean,
|
|
444
507
|
): PMNode {
|
|
445
508
|
const label = segment.label;
|
|
446
509
|
const detail = segment.detail;
|
|
447
510
|
|
|
448
|
-
if (label === "Embedded chart") {
|
|
511
|
+
if (showUnsupportedObjectPreviews && label === "Embedded chart") {
|
|
449
512
|
return editorSchema.nodes.chart_atom.create({ detail });
|
|
450
513
|
}
|
|
451
|
-
if (label === "SmartArt diagram") {
|
|
514
|
+
if (showUnsupportedObjectPreviews && label === "SmartArt diagram") {
|
|
452
515
|
return editorSchema.nodes.smartart_atom.create({ detail });
|
|
453
516
|
}
|
|
454
|
-
if (label === "Drawing shape" || label === "Text box") {
|
|
517
|
+
if (showUnsupportedObjectPreviews && (label === "Drawing shape" || label === "Text box")) {
|
|
455
518
|
const textMatch = /(?:Text content|Content): "([^"]+)"/.exec(detail);
|
|
456
519
|
const geometryMatch = /Geometry: ([^.]+)\./.exec(detail);
|
|
457
520
|
return editorSchema.nodes.shape_atom.create({
|
|
@@ -460,7 +523,7 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
460
523
|
detail,
|
|
461
524
|
});
|
|
462
525
|
}
|
|
463
|
-
if (label === "WordArt") {
|
|
526
|
+
if (showUnsupportedObjectPreviews && label === "WordArt") {
|
|
464
527
|
const textMatch = /Text: "([^"]+)"/.exec(detail);
|
|
465
528
|
const effectMatch = /Effect: ([^.]+)\./.exec(detail);
|
|
466
529
|
return editorSchema.nodes.wordart_atom.create({
|
|
@@ -469,7 +532,7 @@ function buildOpaqueInlineOrComplexAtom(
|
|
|
469
532
|
detail,
|
|
470
533
|
});
|
|
471
534
|
}
|
|
472
|
-
if (label === "Legacy VML drawing") {
|
|
535
|
+
if (showUnsupportedObjectPreviews && label === "Legacy VML drawing") {
|
|
473
536
|
const textMatch = /Text content: "([^"]+)"/.exec(detail);
|
|
474
537
|
const typeMatch = /Type: ([^.]+)\./.exec(detail);
|
|
475
538
|
return editorSchema.nodes.vml_atom.create({
|
|
@@ -21,6 +21,7 @@ export function createSurfaceDocumentBuildKey(input: {
|
|
|
21
21
|
surface: EditorSurfaceSnapshot | null | undefined;
|
|
22
22
|
activeStory: EditorStoryTarget;
|
|
23
23
|
mediaPreviewKey: string;
|
|
24
|
+
showUnsupportedObjectPreviews?: boolean;
|
|
24
25
|
}): string {
|
|
25
26
|
return JSON.stringify({
|
|
26
27
|
surfaceIdentity:
|
|
@@ -29,6 +30,7 @@ export function createSurfaceDocumentBuildKey(input: {
|
|
|
29
30
|
: getSurfaceIdentity(input.surface),
|
|
30
31
|
activeStory: input.activeStory,
|
|
31
32
|
mediaPreviewKey: input.mediaPreviewKey,
|
|
33
|
+
showUnsupportedObjectPreviews: input.showUnsupportedObjectPreviews ?? true,
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -41,6 +43,7 @@ export function createSurfaceDecorationKey(input: {
|
|
|
41
43
|
workflowScopeSignature?: string;
|
|
42
44
|
workflowCandidateSignature?: string;
|
|
43
45
|
workflowBlockedSignature?: string;
|
|
46
|
+
workflowMetadataSignature?: string;
|
|
44
47
|
activeWorkflowWorkItemId?: string | null;
|
|
45
48
|
activeWorkflowScopeIds?: readonly string[];
|
|
46
49
|
suggestionsEnabled?: boolean;
|
|
@@ -54,6 +57,7 @@ export function createSurfaceDecorationKey(input: {
|
|
|
54
57
|
workflowScopeSignature: input.workflowScopeSignature ?? null,
|
|
55
58
|
workflowCandidateSignature: input.workflowCandidateSignature ?? null,
|
|
56
59
|
workflowBlockedSignature: input.workflowBlockedSignature ?? null,
|
|
60
|
+
workflowMetadataSignature: input.workflowMetadataSignature ?? null,
|
|
57
61
|
activeWorkflowWorkItemId: input.activeWorkflowWorkItemId ?? null,
|
|
58
62
|
activeWorkflowScopeIds: input.activeWorkflowScopeIds ?? [],
|
|
59
63
|
suggestionsEnabled: input.suggestionsEnabled ?? false,
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
SelectionSnapshot,
|
|
19
19
|
WorkflowBlockedCommandReason,
|
|
20
20
|
WorkflowCandidateRange,
|
|
21
|
+
WorkflowMetadataMarkup,
|
|
21
22
|
WorkflowScope,
|
|
22
23
|
} from "../../api/public-types";
|
|
23
24
|
import type { CanonicalDocumentEnvelope } from "../../core/state/editor-state.ts";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
createPMSelectionFromSnapshot,
|
|
36
37
|
createPMStateFromSnapshot,
|
|
37
38
|
} from "./pm-state-from-snapshot";
|
|
39
|
+
import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
38
40
|
import {
|
|
39
41
|
createCommandBridgePlugins,
|
|
40
42
|
type CommandBridgeCallbacks,
|
|
@@ -73,9 +75,10 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
73
75
|
documentNavigation: DocumentNavigationSnapshot;
|
|
74
76
|
reviewMode: "editing" | "review";
|
|
75
77
|
markupDisplay: MarkupDisplay;
|
|
78
|
+
showUnsupportedObjectPreviews?: boolean;
|
|
76
79
|
activeRevisionId?: string;
|
|
80
|
+
activeSelectionToolKind?: ActiveSelectionToolModel["kind"] | null;
|
|
77
81
|
showTrackedChanges?: boolean;
|
|
78
|
-
suggestionsEnabled?: boolean;
|
|
79
82
|
/** When true, the surface renders inside the page workspace (vs canvas). */
|
|
80
83
|
isPageWorkspace?: boolean;
|
|
81
84
|
onFocus: FocusEventHandler<HTMLDivElement>;
|
|
@@ -100,6 +103,7 @@ export interface TwProseMirrorSurfaceProps {
|
|
|
100
103
|
workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[];
|
|
101
104
|
activeWorkflowWorkItemId?: string | null;
|
|
102
105
|
activeWorkflowScopeIds?: readonly string[];
|
|
106
|
+
workflowMetadata?: readonly WorkflowMetadataMarkup[];
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
export interface TwProseMirrorSurfaceRef {
|
|
@@ -202,10 +206,10 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
202
206
|
surface,
|
|
203
207
|
activeStory: snapshot.activeStory,
|
|
204
208
|
mediaPreviewKey,
|
|
209
|
+
showUnsupportedObjectPreviews: props.showUnsupportedObjectPreviews,
|
|
205
210
|
}),
|
|
206
|
-
[mediaPreviewKey, snapshot.activeStory, surface],
|
|
211
|
+
[mediaPreviewKey, props.showUnsupportedObjectPreviews, snapshot.activeStory, surface],
|
|
207
212
|
);
|
|
208
|
-
const suggestionsEnabled = props.suggestionsEnabled === true;
|
|
209
213
|
const decorationBuildKey = useMemo(
|
|
210
214
|
() =>
|
|
211
215
|
createSurfaceDecorationKey({
|
|
@@ -217,9 +221,9 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
217
221
|
workflowScopeSignature: createWorkflowScopeSignature(props.workflowScopes),
|
|
218
222
|
workflowCandidateSignature: createWorkflowCandidateSignature(props.workflowCandidates),
|
|
219
223
|
workflowBlockedSignature: createWorkflowBlockedSignature(props.workflowBlockedReasons),
|
|
224
|
+
workflowMetadataSignature: createWorkflowMetadataSignature(props.workflowMetadata),
|
|
220
225
|
activeWorkflowWorkItemId: props.activeWorkflowWorkItemId ?? null,
|
|
221
226
|
activeWorkflowScopeIds: props.activeWorkflowScopeIds ?? [],
|
|
222
|
-
suggestionsEnabled,
|
|
223
227
|
}),
|
|
224
228
|
[
|
|
225
229
|
canEdit,
|
|
@@ -227,12 +231,12 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
227
231
|
props.activeRevisionId,
|
|
228
232
|
props.workflowCandidates,
|
|
229
233
|
props.workflowBlockedReasons,
|
|
234
|
+
props.workflowMetadata,
|
|
230
235
|
props.activeWorkflowWorkItemId,
|
|
231
236
|
props.activeWorkflowScopeIds,
|
|
232
237
|
props.workflowScopes,
|
|
233
238
|
showTrackedChanges,
|
|
234
239
|
snapshot.comments.activeCommentId,
|
|
235
|
-
suggestionsEnabled,
|
|
236
240
|
],
|
|
237
241
|
);
|
|
238
242
|
|
|
@@ -278,7 +282,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
278
282
|
props.workflowBlockedReasons,
|
|
279
283
|
props.activeWorkflowWorkItemId,
|
|
280
284
|
props.activeWorkflowScopeIds,
|
|
281
|
-
|
|
285
|
+
props.workflowMetadata,
|
|
282
286
|
);
|
|
283
287
|
view.setProps({
|
|
284
288
|
editable: () => canEdit,
|
|
@@ -296,6 +300,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
296
300
|
props.activeWorkflowScopeIds,
|
|
297
301
|
props.activeWorkflowWorkItemId,
|
|
298
302
|
props.workflowBlockedReasons,
|
|
303
|
+
props.workflowMetadata,
|
|
299
304
|
props.workflowCandidates,
|
|
300
305
|
props.workflowScopes,
|
|
301
306
|
revisionModel,
|
|
@@ -316,6 +321,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
316
321
|
snapshot.selection,
|
|
317
322
|
plugins,
|
|
318
323
|
props.mediaPreviews,
|
|
324
|
+
props.showUnsupportedObjectPreviews,
|
|
319
325
|
);
|
|
320
326
|
positionMapRef.current = positionMap;
|
|
321
327
|
const decorations = buildDecorations(
|
|
@@ -331,6 +337,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
331
337
|
props.workflowBlockedReasons,
|
|
332
338
|
props.activeWorkflowWorkItemId,
|
|
333
339
|
props.activeWorkflowScopeIds,
|
|
340
|
+
props.workflowMetadata,
|
|
334
341
|
);
|
|
335
342
|
recordPerfSample("pm.rebuild");
|
|
336
343
|
incrementInvalidationCounter("pm.laneA.rebuilds");
|
|
@@ -598,8 +605,13 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
598
605
|
const workspaceLabel = props.isPageWorkspace ? "Document page" : "Document canvas";
|
|
599
606
|
|
|
600
607
|
const selectionToolbarMeasurementKey = useMemo(
|
|
601
|
-
() =>
|
|
602
|
-
|
|
608
|
+
() =>
|
|
609
|
+
buildSelectionToolbarMeasurementKey(
|
|
610
|
+
snapshot.selection,
|
|
611
|
+
snapshot.activeStory,
|
|
612
|
+
props.activeSelectionToolKind,
|
|
613
|
+
),
|
|
614
|
+
[props.activeSelectionToolKind, snapshot.activeStory, snapshot.selection],
|
|
603
615
|
);
|
|
604
616
|
|
|
605
617
|
const emitSelectionToolbarAnchor = useCallback((): void => {
|
|
@@ -652,6 +664,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
652
664
|
useEffect(() => {
|
|
653
665
|
scheduleSelectionToolbarAnchorUpdate();
|
|
654
666
|
}, [
|
|
667
|
+
props.activeSelectionToolKind,
|
|
655
668
|
scheduleSelectionToolbarAnchorUpdate,
|
|
656
669
|
snapshot.revisionToken,
|
|
657
670
|
snapshot.selection,
|
|
@@ -692,6 +705,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
692
705
|
resizeObserver?.disconnect();
|
|
693
706
|
};
|
|
694
707
|
}, [
|
|
708
|
+
props.activeSelectionToolKind,
|
|
695
709
|
props.onSelectionToolbarAnchorChange,
|
|
696
710
|
scheduleSelectionToolbarAnchorUpdate,
|
|
697
711
|
snapshot.revisionToken,
|
|
@@ -793,7 +807,7 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
793
807
|
const positionMap = positionMapRef.current;
|
|
794
808
|
const range = snapshot.selection.activeRange;
|
|
795
809
|
|
|
796
|
-
if (!callback || !view || !mount || !positionMap ||
|
|
810
|
+
if (!callback || !view || !mount || !positionMap || range.kind === "detached") {
|
|
797
811
|
return null;
|
|
798
812
|
}
|
|
799
813
|
|
|
@@ -803,29 +817,33 @@ export const TwProseMirrorSurface = forwardRef<
|
|
|
803
817
|
}
|
|
804
818
|
|
|
805
819
|
try {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const right = Math.min(rootRect.right, Math.max(startRect.right, endRect.right));
|
|
812
|
-
const top = Math.max(rootRect.top, Math.min(startRect.top, endRect.top));
|
|
813
|
-
const bottom = Math.min(rootRect.bottom, Math.max(startRect.bottom, endRect.bottom));
|
|
820
|
+
if (!snapshot.selection.isCollapsed && range.kind === "range") {
|
|
821
|
+
const pmFrom = positionMap.runtimeToPm(range.from);
|
|
822
|
+
const pmTo = positionMap.runtimeToPm(range.to);
|
|
823
|
+
return createSelectionToolbarAnchor(rootRect, view.coordsAtPos(pmFrom), view.coordsAtPos(pmTo));
|
|
824
|
+
}
|
|
814
825
|
|
|
815
826
|
if (
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
827
|
+
range.kind === "node" ||
|
|
828
|
+
(
|
|
829
|
+
snapshot.selection.isCollapsed &&
|
|
830
|
+
range.kind === "range" &&
|
|
831
|
+
(
|
|
832
|
+
props.activeSelectionToolKind === "comment-thread" ||
|
|
833
|
+
props.activeSelectionToolKind === "structure-context"
|
|
834
|
+
)
|
|
835
|
+
)
|
|
824
836
|
) {
|
|
825
|
-
|
|
837
|
+
const runtimePosition = range.kind === "node" ? range.at : range.from;
|
|
838
|
+
const pmAt = positionMap.runtimeToPm(runtimePosition);
|
|
839
|
+
return createSelectionToolbarAnchor(
|
|
840
|
+
rootRect,
|
|
841
|
+
view.coordsAtPos(pmAt, -1),
|
|
842
|
+
view.coordsAtPos(pmAt, 1),
|
|
843
|
+
);
|
|
826
844
|
}
|
|
827
845
|
|
|
828
|
-
return
|
|
846
|
+
return null;
|
|
829
847
|
} catch {
|
|
830
848
|
return null;
|
|
831
849
|
}
|
|
@@ -880,7 +898,33 @@ function createWorkflowBlockedSignature(
|
|
|
880
898
|
).join("|");
|
|
881
899
|
}
|
|
882
900
|
|
|
883
|
-
function
|
|
901
|
+
function createWorkflowMetadataSignature(
|
|
902
|
+
metadata: readonly WorkflowMetadataMarkup[] | undefined,
|
|
903
|
+
): string {
|
|
904
|
+
if (!metadata || metadata.length === 0) {
|
|
905
|
+
return "";
|
|
906
|
+
}
|
|
907
|
+
return metadata.map((entry) =>
|
|
908
|
+
[
|
|
909
|
+
entry.entryId,
|
|
910
|
+
entry.metadataId,
|
|
911
|
+
entry.color ?? "",
|
|
912
|
+
entry.persistence,
|
|
913
|
+
serializeAnchorSignature(entry.anchor),
|
|
914
|
+
serializeStoryTargetSignature(entry.storyTarget),
|
|
915
|
+
JSON.stringify(entry.value ?? {}),
|
|
916
|
+
].join(":")
|
|
917
|
+
).join("|");
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function serializeAnchorSignature(
|
|
921
|
+
anchor:
|
|
922
|
+
| WorkflowScope["anchor"]
|
|
923
|
+
| WorkflowCandidateRange["anchor"]
|
|
924
|
+
| WorkflowBlockedCommandReason["anchor"]
|
|
925
|
+
| WorkflowMetadataMarkup["anchor"]
|
|
926
|
+
| undefined,
|
|
927
|
+
): string {
|
|
884
928
|
if (!anchor) {
|
|
885
929
|
return "";
|
|
886
930
|
}
|
|
@@ -894,7 +938,13 @@ function serializeAnchorSignature(anchor: WorkflowScope["anchor"] | WorkflowCand
|
|
|
894
938
|
}
|
|
895
939
|
}
|
|
896
940
|
|
|
897
|
-
function serializeStoryTargetSignature(
|
|
941
|
+
function serializeStoryTargetSignature(
|
|
942
|
+
storyTarget:
|
|
943
|
+
| WorkflowScope["storyTarget"]
|
|
944
|
+
| WorkflowCandidateRange["storyTarget"]
|
|
945
|
+
| WorkflowBlockedCommandReason["storyTarget"]
|
|
946
|
+
| WorkflowMetadataMarkup["storyTarget"],
|
|
947
|
+
): string {
|
|
898
948
|
if (!storyTarget) {
|
|
899
949
|
return "";
|
|
900
950
|
}
|
|
@@ -913,18 +963,71 @@ function serializeStoryTargetSignature(storyTarget: WorkflowScope["storyTarget"]
|
|
|
913
963
|
function buildSelectionToolbarMeasurementKey(
|
|
914
964
|
selection: SelectionSnapshot,
|
|
915
965
|
activeStory: RuntimeRenderSnapshot["activeStory"],
|
|
966
|
+
activeSelectionToolKind?: ActiveSelectionToolModel["kind"] | null,
|
|
916
967
|
): string | null {
|
|
917
|
-
if (
|
|
968
|
+
if (!activeSelectionToolKind || selection.activeRange.kind === "detached") {
|
|
918
969
|
return null;
|
|
919
970
|
}
|
|
920
971
|
|
|
921
972
|
return JSON.stringify({
|
|
922
973
|
story: activeStory,
|
|
923
|
-
|
|
924
|
-
|
|
974
|
+
tool: activeSelectionToolKind,
|
|
975
|
+
...(selection.activeRange.kind === "node"
|
|
976
|
+
? { nodeAt: selection.activeRange.at }
|
|
977
|
+
: {
|
|
978
|
+
from: selection.activeRange.from,
|
|
979
|
+
to: selection.activeRange.to,
|
|
980
|
+
collapsed: selection.isCollapsed,
|
|
981
|
+
}),
|
|
925
982
|
});
|
|
926
983
|
}
|
|
927
984
|
|
|
985
|
+
function createSelectionToolbarAnchor(
|
|
986
|
+
rootRect: DOMRect,
|
|
987
|
+
...rects: Array<Pick<DOMRect, "left" | "right" | "top" | "bottom">>
|
|
988
|
+
): SelectionToolbarAnchor | null {
|
|
989
|
+
const validRects = rects.filter((rect) =>
|
|
990
|
+
Number.isFinite(rect.left) &&
|
|
991
|
+
Number.isFinite(rect.right) &&
|
|
992
|
+
Number.isFinite(rect.top) &&
|
|
993
|
+
Number.isFinite(rect.bottom),
|
|
994
|
+
);
|
|
995
|
+
if (validRects.length === 0) {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
let left = Math.max(rootRect.left, Math.min(...validRects.map((rect) => Math.min(rect.left, rect.right))));
|
|
1000
|
+
let right = Math.min(rootRect.right, Math.max(...validRects.map((rect) => Math.max(rect.left, rect.right))));
|
|
1001
|
+
let top = Math.max(rootRect.top, Math.min(...validRects.map((rect) => Math.min(rect.top, rect.bottom))));
|
|
1002
|
+
let bottom = Math.min(rootRect.bottom, Math.max(...validRects.map((rect) => Math.max(rect.top, rect.bottom))));
|
|
1003
|
+
|
|
1004
|
+
if (right <= left) {
|
|
1005
|
+
const centerX = Math.min(rootRect.right, Math.max(rootRect.left, (left + right) / 2 || left));
|
|
1006
|
+
left = Math.max(rootRect.left, centerX - 1);
|
|
1007
|
+
right = Math.min(rootRect.right, centerX + 1);
|
|
1008
|
+
}
|
|
1009
|
+
if (bottom <= top) {
|
|
1010
|
+
const centerY = Math.min(rootRect.bottom, Math.max(rootRect.top, (top + bottom) / 2 || top));
|
|
1011
|
+
top = Math.max(rootRect.top, centerY - 1);
|
|
1012
|
+
bottom = Math.min(rootRect.bottom, centerY + 1);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (
|
|
1016
|
+
!Number.isFinite(left) ||
|
|
1017
|
+
!Number.isFinite(right) ||
|
|
1018
|
+
!Number.isFinite(top) ||
|
|
1019
|
+
!Number.isFinite(bottom) ||
|
|
1020
|
+
right <= left ||
|
|
1021
|
+
bottom <= top ||
|
|
1022
|
+
bottom < rootRect.top ||
|
|
1023
|
+
top > rootRect.bottom
|
|
1024
|
+
) {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return { left, right, top, bottom };
|
|
1029
|
+
}
|
|
1030
|
+
|
|
928
1031
|
function selectionToolbarAnchorsEqual(
|
|
929
1032
|
left: SelectionToolbarAnchor | null,
|
|
930
1033
|
right: SelectionToolbarAnchor | null,
|