@beyondwork/docx-react-component 1.0.84 → 1.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/internal/build-ref-projections.ts +3 -0
- package/src/api/public-types.ts +38 -0
- package/src/api/v3/_runtime-handle.ts +11 -0
- package/src/api/v3/runtime/content.ts +148 -1
- package/src/api/v3/runtime/formatting.ts +41 -0
- package/src/api/v3/runtime/review.ts +98 -0
- package/src/core/commands/index.ts +81 -25
- package/src/core/state/editor-state.ts +15 -0
- package/src/io/ooxml/header-footer-reference.ts +38 -0
- package/src/io/ooxml/parse-headers-footers.ts +11 -23
- package/src/io/ooxml/parse-main-document.ts +7 -10
- package/src/model/canonical-document.ts +9 -0
- package/src/model/review/comment-types.ts +2 -0
- package/src/runtime/document-runtime.ts +677 -54
- package/src/runtime/formatting/field/resolver.ts +73 -8
- package/src/runtime/layout/layout-engine-version.ts +31 -12
- package/src/runtime/layout/paginated-layout-engine.ts +18 -11
- package/src/runtime/layout/public-facet.ts +119 -16
- package/src/runtime/layout/resolve-page-fields.ts +68 -6
- package/src/runtime/layout/resolve-page-previews.ts +1 -1
- package/src/runtime/suggestions-snapshot.ts +24 -0
- package/src/runtime/surface-projection.ts +59 -2
- package/src/shell/ref-commands.ts +3 -354
- package/src/shell/session-bootstrap.ts +8 -0
- package/src/ui/WordReviewEditor.tsx +95 -9
- package/src/ui/editor-command-bag.ts +3 -1
- package/src/ui/headless/revision-decoration-model.ts +13 -0
- package/src/ui/headless/selection-tool-types.ts +2 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +7 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +175 -25
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -1
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +12 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +18 -30
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +1 -1
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +20 -11
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +9 -4
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +12 -7
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +29 -10
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +1 -1
- package/src/ui-tailwind/review-workspace/types.ts +3 -2
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +11 -1
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -1
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +2 -1
- package/src/ui-tailwind/tw-review-workspace.tsx +18 -2
|
@@ -1536,7 +1536,34 @@ function appendInlineSegments(
|
|
|
1536
1536
|
node.fieldFamily === "NOTEREF" ||
|
|
1537
1537
|
node.fieldFamily === "TOC" ||
|
|
1538
1538
|
node.fieldFamily === "PAGE" ||
|
|
1539
|
-
node.fieldFamily === "NUMPAGES"
|
|
1539
|
+
node.fieldFamily === "NUMPAGES" ||
|
|
1540
|
+
node.fieldFamily === "SECTIONPAGES";
|
|
1541
|
+
const isPageScopedField =
|
|
1542
|
+
node.fieldFamily === "PAGE" ||
|
|
1543
|
+
node.fieldFamily === "NUMPAGES" ||
|
|
1544
|
+
node.fieldFamily === "SECTIONPAGES";
|
|
1545
|
+
if (isPageScopedField) {
|
|
1546
|
+
const fieldLabel =
|
|
1547
|
+
node.fieldFamily === "PAGE"
|
|
1548
|
+
? "Current page number"
|
|
1549
|
+
: node.fieldFamily === "NUMPAGES"
|
|
1550
|
+
? "Total pages"
|
|
1551
|
+
: "Section pages";
|
|
1552
|
+
const displayText = flattenSurfaceFieldDisplayText(node.children);
|
|
1553
|
+
paragraph.segments.push({
|
|
1554
|
+
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
1555
|
+
kind: "field_ref",
|
|
1556
|
+
from: start,
|
|
1557
|
+
to: start + 1,
|
|
1558
|
+
fieldFamily: node.fieldFamily,
|
|
1559
|
+
fieldTarget: node.fieldTarget,
|
|
1560
|
+
instruction: node.instruction,
|
|
1561
|
+
refreshStatus: node.refreshStatus ?? "stale",
|
|
1562
|
+
label: fieldLabel,
|
|
1563
|
+
...(displayText ? { displayText } : {}),
|
|
1564
|
+
} as SurfaceInlineSegment);
|
|
1565
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
1566
|
+
}
|
|
1540
1567
|
if (node.children && node.children.length > 0) {
|
|
1541
1568
|
// For REF \h, pass the bookmark as a hyperlink href so child text gets hyperlink styling
|
|
1542
1569
|
const refHyperlinkHref =
|
|
@@ -1574,7 +1601,9 @@ function appendInlineSegments(
|
|
|
1574
1601
|
? "Current page number"
|
|
1575
1602
|
: node.fieldFamily === "NUMPAGES"
|
|
1576
1603
|
? "Total pages"
|
|
1577
|
-
:
|
|
1604
|
+
: node.fieldFamily === "SECTIONPAGES"
|
|
1605
|
+
? "Section pages"
|
|
1606
|
+
: `${node.fieldFamily ?? "Field"}: ${node.fieldTarget ?? node.instruction.trim()}`;
|
|
1578
1607
|
paragraph.segments.push({
|
|
1579
1608
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
1580
1609
|
kind: "field_ref",
|
|
@@ -1743,6 +1772,34 @@ function normalizeSafeCssHexColor(value: string | undefined): string | undefined
|
|
|
1743
1772
|
return trimmed.replace(/^#/, "").toUpperCase();
|
|
1744
1773
|
}
|
|
1745
1774
|
|
|
1775
|
+
function flattenSurfaceFieldDisplayText(
|
|
1776
|
+
children: readonly InlineNode[] | undefined,
|
|
1777
|
+
): string {
|
|
1778
|
+
if (!children || children.length === 0) return "";
|
|
1779
|
+
const parts: string[] = [];
|
|
1780
|
+
for (const child of children) {
|
|
1781
|
+
switch (child.type) {
|
|
1782
|
+
case "text":
|
|
1783
|
+
parts.push(child.text);
|
|
1784
|
+
break;
|
|
1785
|
+
case "tab":
|
|
1786
|
+
case "hard_break":
|
|
1787
|
+
parts.push(" ");
|
|
1788
|
+
break;
|
|
1789
|
+
case "symbol":
|
|
1790
|
+
parts.push(child.char ? String.fromCodePoint(parseInt(child.char, 16)) : "\uFFFD");
|
|
1791
|
+
break;
|
|
1792
|
+
case "field":
|
|
1793
|
+
case "hyperlink":
|
|
1794
|
+
parts.push(flattenSurfaceFieldDisplayText(child.children));
|
|
1795
|
+
break;
|
|
1796
|
+
default:
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return parts.join("");
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1746
1803
|
/**
|
|
1747
1804
|
* V2c.5 — Extract the first paragraph's plain text from a parsed
|
|
1748
1805
|
* `txbxBlocks` tree for the `txbxText` segment preview. The recursion
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
import type {
|
|
31
31
|
EditorSessionState,
|
|
32
32
|
EditorStoryTarget,
|
|
33
|
-
FormattingAlignment,
|
|
34
33
|
HeaderFooterLinkPatch,
|
|
35
34
|
InsertImageOptions,
|
|
36
35
|
InsertTableOptions,
|
|
@@ -41,7 +40,6 @@ import type {
|
|
|
41
40
|
SelectionSnapshot as PublicSelectionSnapshot,
|
|
42
41
|
StyleCatalogSnapshot,
|
|
43
42
|
SurfaceBlockSnapshot,
|
|
44
|
-
SurfaceInlineSegment,
|
|
45
43
|
TableOp,
|
|
46
44
|
} from "../api/public-types";
|
|
47
45
|
import {
|
|
@@ -51,7 +49,7 @@ import {
|
|
|
51
49
|
storyTargetsEqual,
|
|
52
50
|
type TransactionMapping,
|
|
53
51
|
} from "../core/selection/mapping.ts";
|
|
54
|
-
import {
|
|
52
|
+
import type { FormattingOperation } from "../core/commands/formatting-commands.ts";
|
|
55
53
|
import {
|
|
56
54
|
applyParagraphStyleToDocument,
|
|
57
55
|
applyTableStyleToDocument,
|
|
@@ -220,178 +218,15 @@ export function getRuntimeStyleCatalog(
|
|
|
220
218
|
// Formatting (flat + suggesting-mode)
|
|
221
219
|
// ---------------------------------------------------------------------------
|
|
222
220
|
|
|
223
|
-
type FormattingOperation =
|
|
224
|
-
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
225
|
-
| { type: "set-font-family"; fontFamily: string | null }
|
|
226
|
-
| { type: "set-font-size"; size: number | null }
|
|
227
|
-
| { type: "set-text-color"; color: string | null }
|
|
228
|
-
| { type: "set-highlight-color"; color: string | null }
|
|
229
|
-
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
230
|
-
| { type: "indent" }
|
|
231
|
-
| { type: "outdent" };
|
|
232
|
-
|
|
233
221
|
export function applyRuntimeFormattingOperation(
|
|
234
222
|
runtime: WordReviewEditorRuntime,
|
|
235
223
|
operation: FormattingOperation,
|
|
236
224
|
): void {
|
|
237
|
-
|
|
238
|
-
if (applySuggestingFormattingOperation(runtime, operation)) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (emitSuggestingUnsupportedMutation(runtime, getFormattingOperationCommandName(operation))) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
const context = getStoryMutationContext(runtime, getFormattingOperationCommandName(operation));
|
|
246
|
-
if (!context) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const result = applyFormattingOperationToDocument(
|
|
251
|
-
context.localDocument,
|
|
252
|
-
context.localSnapshot,
|
|
253
|
-
operation,
|
|
254
|
-
);
|
|
255
|
-
dispatchStoryMutationResult(
|
|
256
|
-
runtime,
|
|
257
|
-
context,
|
|
258
|
-
{
|
|
259
|
-
...result,
|
|
260
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
261
|
-
},
|
|
262
|
-
context.timestamp,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function applySuggestingFormattingOperation(
|
|
267
|
-
runtime: WordReviewEditorRuntime,
|
|
268
|
-
operation: FormattingOperation,
|
|
269
|
-
): boolean {
|
|
270
|
-
const commandName = getFormattingOperationCommandName(operation);
|
|
271
|
-
const context = getStoryMutationContext(runtime, commandName);
|
|
272
|
-
if (!context) {
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
if (context.activeStory.kind !== "main") {
|
|
276
|
-
runtime.emitBlockedCommand(commandName, [{
|
|
277
|
-
code: "suggesting_unsupported",
|
|
278
|
-
message: `"${commandName}" is not supported in suggesting mode for this story.`,
|
|
279
|
-
}]);
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (operation.type === "set-alignment" || operation.type === "indent" || operation.type === "outdent") {
|
|
284
|
-
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
285
|
-
if (!paragraphContext) {
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
const beforeXml = buildParagraphPropertyBeforeXml(paragraphContext.paragraph);
|
|
289
|
-
const result = applyFormattingOperationToDocument(
|
|
290
|
-
context.localDocument,
|
|
291
|
-
context.localSnapshot,
|
|
292
|
-
operation,
|
|
293
|
-
);
|
|
294
|
-
if (!result.changed) {
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
const nextDocument = appendPropertyChangeSuggestion(
|
|
298
|
-
result.document,
|
|
299
|
-
{
|
|
300
|
-
from: paragraphContext.paragraph.from,
|
|
301
|
-
to: paragraphContext.paragraph.to,
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
originalRevisionType: "pPrChange",
|
|
305
|
-
xmlTag: "pPrChange",
|
|
306
|
-
beforeXml,
|
|
307
|
-
semanticKind: "paragraph-property-change",
|
|
308
|
-
storyTarget: context.activeStory,
|
|
309
|
-
authorId: runtime.getDefaultAuthorId?.(),
|
|
310
|
-
},
|
|
311
|
-
context.timestamp,
|
|
312
|
-
);
|
|
313
|
-
dispatchStoryMutationResult(
|
|
314
|
-
runtime,
|
|
315
|
-
context,
|
|
316
|
-
{
|
|
317
|
-
changed: true,
|
|
318
|
-
document: nextDocument,
|
|
319
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
320
|
-
},
|
|
321
|
-
context.timestamp,
|
|
322
|
-
);
|
|
323
|
-
return true;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const segment = findSingleSelectedTextSegment(context.localSnapshot);
|
|
327
|
-
if (!segment) {
|
|
328
|
-
runtime.emitBlockedCommand(commandName, [{
|
|
329
|
-
code: "suggesting_unsupported",
|
|
330
|
-
message: `"${commandName}" requires one bounded text segment in suggesting mode.`,
|
|
331
|
-
}]);
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
const beforeXml = buildRunPropertyBeforeXml(segment);
|
|
335
|
-
const result = applyFormattingOperationToDocument(
|
|
336
|
-
context.localDocument,
|
|
337
|
-
context.localSnapshot,
|
|
338
|
-
operation,
|
|
339
|
-
);
|
|
340
|
-
if (!result.changed) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
const nextDocument = appendPropertyChangeSuggestion(
|
|
344
|
-
result.document,
|
|
345
|
-
{
|
|
346
|
-
from: segment.from,
|
|
347
|
-
to: segment.to,
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
originalRevisionType: "rPrChange",
|
|
351
|
-
xmlTag: "rPrChange",
|
|
352
|
-
beforeXml,
|
|
353
|
-
semanticKind: "formatting-change",
|
|
354
|
-
storyTarget: context.activeStory,
|
|
355
|
-
authorId: runtime.getDefaultAuthorId?.(),
|
|
356
|
-
},
|
|
357
|
-
context.timestamp,
|
|
358
|
-
);
|
|
359
|
-
dispatchStoryMutationResult(
|
|
360
|
-
runtime,
|
|
361
|
-
context,
|
|
362
|
-
{
|
|
363
|
-
changed: true,
|
|
364
|
-
document: nextDocument,
|
|
365
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
366
|
-
},
|
|
367
|
-
context.timestamp,
|
|
368
|
-
);
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function getFormattingOperationCommandName(operation: FormattingOperation): string {
|
|
373
|
-
switch (operation.type) {
|
|
374
|
-
case "toggle":
|
|
375
|
-
return `toggle${operation.mark.charAt(0).toUpperCase()}${operation.mark.slice(1)}`;
|
|
376
|
-
case "set-font-family":
|
|
377
|
-
return "setFontFamily";
|
|
378
|
-
case "set-font-size":
|
|
379
|
-
return "setFontSize";
|
|
380
|
-
case "set-text-color":
|
|
381
|
-
return "setTextColor";
|
|
382
|
-
case "set-highlight-color":
|
|
383
|
-
return "setHighlightColor";
|
|
384
|
-
case "set-alignment":
|
|
385
|
-
return "setAlignment";
|
|
386
|
-
case "indent":
|
|
387
|
-
return "indent";
|
|
388
|
-
case "outdent":
|
|
389
|
-
return "outdent";
|
|
390
|
-
}
|
|
225
|
+
runtime.applyFormattingOperation(operation);
|
|
391
226
|
}
|
|
392
227
|
|
|
393
228
|
// ---------------------------------------------------------------------------
|
|
394
|
-
// Suggesting-mode
|
|
229
|
+
// Suggesting-mode guard helper for commands without bounded redline support
|
|
395
230
|
// ---------------------------------------------------------------------------
|
|
396
231
|
|
|
397
232
|
function emitSuggestingUnsupportedMutation(
|
|
@@ -409,192 +244,6 @@ function emitSuggestingUnsupportedMutation(
|
|
|
409
244
|
return true;
|
|
410
245
|
}
|
|
411
246
|
|
|
412
|
-
function appendPropertyChangeSuggestion(
|
|
413
|
-
document: EditorSessionState["canonicalDocument"],
|
|
414
|
-
anchor: { from: number; to: number },
|
|
415
|
-
input: {
|
|
416
|
-
originalRevisionType: "rPrChange" | "pPrChange";
|
|
417
|
-
xmlTag: "rPrChange" | "pPrChange";
|
|
418
|
-
beforeXml: string;
|
|
419
|
-
semanticKind: "formatting-change" | "paragraph-property-change";
|
|
420
|
-
storyTarget: EditorStoryTarget;
|
|
421
|
-
authorId?: string;
|
|
422
|
-
},
|
|
423
|
-
timestamp: string,
|
|
424
|
-
): EditorSessionState["canonicalDocument"] {
|
|
425
|
-
const existing = document.review.revisions;
|
|
426
|
-
const changeId = createRuntimeSuggestionChangeId(existing, timestamp);
|
|
427
|
-
const resolvedAuthorId = input.authorId ?? "unknown";
|
|
428
|
-
return {
|
|
429
|
-
...document,
|
|
430
|
-
review: {
|
|
431
|
-
...document.review,
|
|
432
|
-
revisions: {
|
|
433
|
-
...existing,
|
|
434
|
-
[changeId]: {
|
|
435
|
-
changeId,
|
|
436
|
-
kind: "property-change",
|
|
437
|
-
anchor: createRangeAnchor(anchor.from, anchor.to, { start: 1, end: -1 }),
|
|
438
|
-
authorId: resolvedAuthorId,
|
|
439
|
-
createdAt: timestamp,
|
|
440
|
-
warningIds: [],
|
|
441
|
-
metadata: {
|
|
442
|
-
source: "runtime",
|
|
443
|
-
storyTarget: input.storyTarget,
|
|
444
|
-
suggestionId: changeId,
|
|
445
|
-
semanticKind: input.semanticKind,
|
|
446
|
-
originalRevisionType: input.originalRevisionType,
|
|
447
|
-
propertyChangeData: {
|
|
448
|
-
xmlTag: input.xmlTag,
|
|
449
|
-
beforeXml: input.beforeXml,
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
status: "open",
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function createRuntimeSuggestionChangeId(
|
|
460
|
-
existing: EditorSessionState["canonicalDocument"]["review"]["revisions"],
|
|
461
|
-
timestamp: string,
|
|
462
|
-
): string {
|
|
463
|
-
const base = `change-${timestamp.replace(/[^0-9]/gu, "")}`;
|
|
464
|
-
let counter = Object.keys(existing).length + 1;
|
|
465
|
-
let candidate = `${base}-p${counter}`;
|
|
466
|
-
while (existing[candidate]) {
|
|
467
|
-
counter += 1;
|
|
468
|
-
candidate = `${base}-p${counter}`;
|
|
469
|
-
}
|
|
470
|
-
return candidate;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function findSingleSelectedTextSegment(
|
|
474
|
-
snapshot: Pick<RuntimeRenderSnapshot, "surface" | "selection">,
|
|
475
|
-
): Extract<SurfaceInlineSegment, { kind: "text" }> | null {
|
|
476
|
-
if (!snapshot.surface || snapshot.selection.activeRange.kind !== "range" || snapshot.selection.isCollapsed) {
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
const selectionFrom = Math.min(snapshot.selection.anchor, snapshot.selection.head);
|
|
480
|
-
const selectionTo = Math.max(snapshot.selection.anchor, snapshot.selection.head);
|
|
481
|
-
const segments = collectSelectedTextSegments(snapshot.surface.blocks, selectionFrom, selectionTo);
|
|
482
|
-
if (segments.length !== 1) {
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
const [segment] = segments;
|
|
486
|
-
if (!segment || segment.from !== selectionFrom || segment.to !== selectionTo) {
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
return segment;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function collectSelectedTextSegments(
|
|
493
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
494
|
-
selectionFrom: number,
|
|
495
|
-
selectionTo: number,
|
|
496
|
-
output: Array<Extract<SurfaceInlineSegment, { kind: "text" }>> = [],
|
|
497
|
-
): Array<Extract<SurfaceInlineSegment, { kind: "text" }>> {
|
|
498
|
-
for (const block of blocks) {
|
|
499
|
-
if (block.kind === "paragraph") {
|
|
500
|
-
for (const segment of block.segments) {
|
|
501
|
-
if (
|
|
502
|
-
segment.kind === "text" &&
|
|
503
|
-
rangesOverlap(selectionFrom, selectionTo, segment.from, segment.to)
|
|
504
|
-
) {
|
|
505
|
-
output.push(segment);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (block.kind === "table") {
|
|
511
|
-
for (const row of block.rows) {
|
|
512
|
-
for (const cell of row.cells) {
|
|
513
|
-
collectSelectedTextSegments(cell.content, selectionFrom, selectionTo, output);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
if (block.kind === "sdt_block") {
|
|
519
|
-
collectSelectedTextSegments(block.children, selectionFrom, selectionTo, output);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return output;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function rangesOverlap(
|
|
526
|
-
leftFrom: number,
|
|
527
|
-
leftTo: number,
|
|
528
|
-
rightFrom: number,
|
|
529
|
-
rightTo: number,
|
|
530
|
-
): boolean {
|
|
531
|
-
return leftFrom < rightTo && rightFrom < leftTo;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function buildRunPropertyBeforeXml(
|
|
535
|
-
segment: Extract<SurfaceInlineSegment, { kind: "text" }>,
|
|
536
|
-
): string {
|
|
537
|
-
const parts: string[] = [];
|
|
538
|
-
const marks = new Set(segment.marks ?? []);
|
|
539
|
-
if (marks.has("bold")) parts.push("<w:b/>");
|
|
540
|
-
if (marks.has("italic")) parts.push("<w:i/>");
|
|
541
|
-
if (marks.has("underline")) parts.push("<w:u w:val=\"single\"/>");
|
|
542
|
-
if (marks.has("strikethrough")) parts.push("<w:strike/>");
|
|
543
|
-
if (marks.has("superscript")) parts.push("<w:vertAlign w:val=\"superscript\"/>");
|
|
544
|
-
if (marks.has("subscript")) parts.push("<w:vertAlign w:val=\"subscript\"/>");
|
|
545
|
-
if (segment.markAttrs?.fontFamily) {
|
|
546
|
-
parts.push(`<w:rFonts w:ascii="${escapeAttributeXml(segment.markAttrs.fontFamily)}" w:hAnsi="${escapeAttributeXml(segment.markAttrs.fontFamily)}"/>`);
|
|
547
|
-
}
|
|
548
|
-
if (segment.markAttrs?.fontSize !== undefined) {
|
|
549
|
-
parts.push(`<w:sz w:val="${segment.markAttrs.fontSize}"/>`);
|
|
550
|
-
}
|
|
551
|
-
if (segment.markAttrs?.textColor) {
|
|
552
|
-
parts.push(`<w:color w:val="${escapeAttributeXml(segment.markAttrs.textColor)}"/>`);
|
|
553
|
-
}
|
|
554
|
-
if (segment.markAttrs?.backgroundColor) {
|
|
555
|
-
parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${escapeAttributeXml(segment.markAttrs.backgroundColor)}"/>`);
|
|
556
|
-
}
|
|
557
|
-
return `<w:rPr>${parts.join("")}</w:rPr>`;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function buildParagraphPropertyBeforeXml(
|
|
561
|
-
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
562
|
-
): string {
|
|
563
|
-
const parts: string[] = [];
|
|
564
|
-
if (paragraph.styleId) {
|
|
565
|
-
parts.push(`<w:pStyle w:val="${escapeAttributeXml(paragraph.styleId)}"/>`);
|
|
566
|
-
}
|
|
567
|
-
if (paragraph.numbering) {
|
|
568
|
-
parts.push(
|
|
569
|
-
`<w:numPr><w:ilvl w:val="${paragraph.numbering.level}"/><w:numId w:val="${escapeAttributeXml(
|
|
570
|
-
paragraph.numbering.numberingInstanceId.replace(/^num:/u, ""),
|
|
571
|
-
)}"/></w:numPr>`,
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
if (paragraph.alignment) {
|
|
575
|
-
parts.push(`<w:jc w:val="${escapeAttributeXml(paragraph.alignment)}"/>`);
|
|
576
|
-
}
|
|
577
|
-
if (paragraph.indentation) {
|
|
578
|
-
const attrs: string[] = [];
|
|
579
|
-
if (paragraph.indentation.left !== undefined) attrs.push(`w:left="${paragraph.indentation.left}"`);
|
|
580
|
-
if (paragraph.indentation.right !== undefined) attrs.push(`w:right="${paragraph.indentation.right}"`);
|
|
581
|
-
if (paragraph.indentation.firstLine !== undefined) attrs.push(`w:firstLine="${paragraph.indentation.firstLine}"`);
|
|
582
|
-
if (paragraph.indentation.hanging !== undefined) attrs.push(`w:hanging="${paragraph.indentation.hanging}"`);
|
|
583
|
-
if (attrs.length > 0) {
|
|
584
|
-
parts.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function escapeAttributeXml(value: string): string {
|
|
591
|
-
return value
|
|
592
|
-
.replace(/&/g, "&")
|
|
593
|
-
.replace(/</g, "<")
|
|
594
|
-
.replace(/>/g, ">")
|
|
595
|
-
.replace(/"/g, """);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
247
|
// ---------------------------------------------------------------------------
|
|
599
248
|
// List toggle / paragraph style / table style
|
|
600
249
|
// ---------------------------------------------------------------------------
|
|
@@ -1098,6 +1098,7 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1098
1098
|
getFontEntry: (name: string) =>
|
|
1099
1099
|
input.sessionState.canonicalDocument.fontTable?.fonts[name],
|
|
1100
1100
|
replaceText: () => undefined,
|
|
1101
|
+
applyFormattingOperation: () => undefined,
|
|
1101
1102
|
applyScopeReplacement: () => undefined,
|
|
1102
1103
|
insertFragment: () => undefined,
|
|
1103
1104
|
copy: () => undefined,
|
|
@@ -1133,6 +1134,13 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1133
1134
|
addCommentReply: () => {
|
|
1134
1135
|
throw createLoadingBoundaryError(input.snapshot.documentId, "comment");
|
|
1135
1136
|
},
|
|
1137
|
+
getCommentThreadForChange: () => null,
|
|
1138
|
+
ensureCommentThreadForChange: () => {
|
|
1139
|
+
throw createLoadingBoundaryError(input.snapshot.documentId, "comment");
|
|
1140
|
+
},
|
|
1141
|
+
addReplyToChange: () => {
|
|
1142
|
+
throw createLoadingBoundaryError(input.snapshot.documentId, "comment");
|
|
1143
|
+
},
|
|
1136
1144
|
editCommentBody: () => undefined,
|
|
1137
1145
|
addScope: () => {
|
|
1138
1146
|
throw createLoadingBoundaryError(input.snapshot.documentId, "scope");
|
|
@@ -66,6 +66,7 @@ import type {
|
|
|
66
66
|
ViewMode as EditorViewMode,
|
|
67
67
|
WorkflowBlockedCommandReason,
|
|
68
68
|
WorkflowMarkupSnapshot,
|
|
69
|
+
WorkflowMarkupMode,
|
|
69
70
|
WorkflowScopeSnapshot,
|
|
70
71
|
WordReviewEditorChromeOptions,
|
|
71
72
|
WordReviewEditorChromePreset,
|
|
@@ -250,6 +251,21 @@ function normalizeHostMarkupDisplay(
|
|
|
250
251
|
return "markup";
|
|
251
252
|
}
|
|
252
253
|
|
|
254
|
+
function toWorkflowMarkupMode(mode: MarkupDisplay): WorkflowMarkupMode {
|
|
255
|
+
switch (mode) {
|
|
256
|
+
case "all":
|
|
257
|
+
case "all-markup":
|
|
258
|
+
return "all";
|
|
259
|
+
case "simple":
|
|
260
|
+
case "simple-markup":
|
|
261
|
+
return "simple";
|
|
262
|
+
case "clean":
|
|
263
|
+
case "no-markup":
|
|
264
|
+
case "original":
|
|
265
|
+
return "clean";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
253
269
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
254
270
|
position: "absolute",
|
|
255
271
|
width: "1px",
|
|
@@ -383,6 +399,11 @@ export function __createWordReviewEditorRefBridge(
|
|
|
383
399
|
resolveComment: (commentId) => runtime.resolveComment(commentId),
|
|
384
400
|
reopenComment: (commentId) => runtime.reopenComment(commentId),
|
|
385
401
|
addCommentReply: (commentId, body) => runtime.addCommentReply(commentId, body),
|
|
402
|
+
getCommentThreadForChange: (changeId) =>
|
|
403
|
+
clonePublicValue(runtime.getCommentThreadForChange(changeId)),
|
|
404
|
+
ensureCommentThreadForChange: (changeId) =>
|
|
405
|
+
runtime.ensureCommentThreadForChange(changeId),
|
|
406
|
+
addReplyToChange: (changeId, body) => runtime.addReplyToChange(changeId, body),
|
|
386
407
|
editCommentBody: (commentId, body) => runtime.editCommentBody(commentId, body),
|
|
387
408
|
deleteComment: (commentId) => {
|
|
388
409
|
applyRuntimeDeleteComment(runtime, commentId);
|
|
@@ -1041,7 +1062,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1041
1062
|
} = props;
|
|
1042
1063
|
|
|
1043
1064
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
1044
|
-
const [showTrackedChanges, setShowTrackedChanges] = useState(
|
|
1065
|
+
const [showTrackedChanges, setShowTrackedChanges] = useState(() => suggestionsEnabled);
|
|
1066
|
+
const [localMarkupDisplay, setLocalMarkupDisplay] =
|
|
1067
|
+
useState<WorkflowMarkupMode | null>(() => suggestionsEnabled ? "all" : null);
|
|
1045
1068
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
1046
1069
|
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
1047
1070
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
@@ -1343,7 +1366,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1343
1366
|
loadingViewState,
|
|
1344
1367
|
);
|
|
1345
1368
|
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
1346
|
-
const liveMarkupDisplay =
|
|
1369
|
+
const liveMarkupDisplay = localMarkupDisplay ??
|
|
1370
|
+
__resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
1347
1371
|
const documentNavigation = useRuntimeValue(
|
|
1348
1372
|
runtime
|
|
1349
1373
|
? {
|
|
@@ -1483,9 +1507,29 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1483
1507
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
1484
1508
|
}, [activeRuntime, effectiveViewMode]);
|
|
1485
1509
|
|
|
1510
|
+
const setReviewMarkupMode = useCallback((mode: MarkupDisplay) => {
|
|
1511
|
+
const workflowMode = toWorkflowMarkupMode(mode);
|
|
1512
|
+
setLocalMarkupDisplay(workflowMode);
|
|
1513
|
+
setShowTrackedChanges(workflowMode !== "clean");
|
|
1514
|
+
api.ui?.viewport.setLocalMarkupMode(workflowMode);
|
|
1515
|
+
}, [api]);
|
|
1516
|
+
|
|
1517
|
+
const setTrackedChangesAuthoring = useCallback((enabled: boolean) => {
|
|
1518
|
+
const workflowMode: WorkflowMarkupMode = enabled ? "all" : "clean";
|
|
1519
|
+
setShowTrackedChanges(enabled);
|
|
1520
|
+
setLocalMarkupDisplay(workflowMode);
|
|
1521
|
+
api.runtime.document.setMode(enabled ? "suggesting" : "editing");
|
|
1522
|
+
api.ui?.viewport.setLocalMarkupMode(workflowMode);
|
|
1523
|
+
}, [api]);
|
|
1524
|
+
|
|
1486
1525
|
useEffect(() => {
|
|
1487
|
-
|
|
1488
|
-
|
|
1526
|
+
if (suggestionsEnabled) {
|
|
1527
|
+
setTrackedChangesAuthoring(true);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
setShowTrackedChanges(false);
|
|
1531
|
+
api.runtime.document.setMode("editing");
|
|
1532
|
+
}, [api, setTrackedChangesAuthoring, suggestionsEnabled]);
|
|
1489
1533
|
|
|
1490
1534
|
// design-close-chrome Phase 2 — density contract (designsystem §4.2).
|
|
1491
1535
|
// When the `density` prop is supplied, drive the root `data-density`
|
|
@@ -1645,6 +1689,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1645
1689
|
reopenComment: (commentId) => activeRuntime.reopenComment(commentId),
|
|
1646
1690
|
addCommentReply: (commentId, body) =>
|
|
1647
1691
|
activeRuntime.addCommentReply(commentId, body, currentUser.userId),
|
|
1692
|
+
getCommentThreadForChange: (changeId) =>
|
|
1693
|
+
clonePublicValue(activeRuntime.getCommentThreadForChange(changeId)),
|
|
1694
|
+
ensureCommentThreadForChange: (changeId) =>
|
|
1695
|
+
activeRuntime.ensureCommentThreadForChange(changeId, currentUser.userId),
|
|
1696
|
+
addReplyToChange: (changeId, body) =>
|
|
1697
|
+
activeRuntime.addReplyToChange(changeId, body, currentUser.userId),
|
|
1648
1698
|
editCommentBody: (commentId, body) =>
|
|
1649
1699
|
activeRuntime.editCommentBody(commentId, body),
|
|
1650
1700
|
deleteComment: (commentId) => {
|
|
@@ -2740,7 +2790,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2740
2790
|
);
|
|
2741
2791
|
|
|
2742
2792
|
const addSelectionToolbarComment = useCallback(() => {
|
|
2743
|
-
|
|
2793
|
+
let commentId: string | null = null;
|
|
2794
|
+
if (activeSelectionTool?.kind === "suggestion-review") {
|
|
2795
|
+
const primaryChangeId = activeSelectionTool.changeIds[0];
|
|
2796
|
+
const linkedThread = primaryChangeId
|
|
2797
|
+
? activeRuntime.ensureCommentThreadForChange(primaryChangeId, currentUser.userId)
|
|
2798
|
+
: null;
|
|
2799
|
+
commentId = linkedThread?.commentId ?? null;
|
|
2800
|
+
if (commentId) {
|
|
2801
|
+
activeRuntime.openComment(commentId);
|
|
2802
|
+
setActiveRailTab("comments");
|
|
2803
|
+
}
|
|
2804
|
+
} else {
|
|
2805
|
+
commentId = addReviewComment();
|
|
2806
|
+
}
|
|
2744
2807
|
if (!commentId) {
|
|
2745
2808
|
return;
|
|
2746
2809
|
}
|
|
@@ -2748,7 +2811,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2748
2811
|
queueMicrotask(() => {
|
|
2749
2812
|
focusDocumentSurface();
|
|
2750
2813
|
});
|
|
2751
|
-
}, [
|
|
2814
|
+
}, [
|
|
2815
|
+
activeRuntime,
|
|
2816
|
+
activeSelectionTool,
|
|
2817
|
+
currentUser.userId,
|
|
2818
|
+
dismissSelectionToolbar,
|
|
2819
|
+
focusDocumentSurface,
|
|
2820
|
+
]);
|
|
2752
2821
|
|
|
2753
2822
|
const handleSelectionToolbarAnchorChange = useCallback(
|
|
2754
2823
|
(nextAnchor: SelectionToolbarAnchor | null) => {
|
|
@@ -3166,7 +3235,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3166
3235
|
onWorkspaceModeChange: (mode) => activeRuntime.setWorkspaceMode(mode),
|
|
3167
3236
|
onZoomChange: (level) => activeRuntime.setZoom(level),
|
|
3168
3237
|
onActiveRailTabChange: setActiveRailTab,
|
|
3169
|
-
onShowTrackedChangesChange:
|
|
3238
|
+
onShowTrackedChangesChange: setTrackedChangesAuthoring,
|
|
3239
|
+
onReviewMarkupModeChange: setReviewMarkupMode,
|
|
3170
3240
|
onToggleBold: () =>
|
|
3171
3241
|
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" }),
|
|
3172
3242
|
onToggleItalic: () =>
|
|
@@ -3263,10 +3333,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3263
3333
|
// with a mount-time `console.warn`; hosts that still pass them can
|
|
3264
3334
|
// migrate to `onOpenStory` at their leisure.
|
|
3265
3335
|
onOpenHeaderStoryForPage: (pageIndex: number) =>
|
|
3266
|
-
|
|
3336
|
+
isPageWorkspace
|
|
3337
|
+
? openStoryForPage(activeRuntime, pageIndex, "header")
|
|
3338
|
+
: undefined,
|
|
3267
3339
|
onOpenFooterStoryForPage: (pageIndex: number) =>
|
|
3268
|
-
|
|
3340
|
+
isPageWorkspace
|
|
3341
|
+
? openStoryForPage(activeRuntime, pageIndex, "footer")
|
|
3342
|
+
: undefined,
|
|
3269
3343
|
onOpenStory: (target) => {
|
|
3344
|
+
if (
|
|
3345
|
+
!isPageWorkspace &&
|
|
3346
|
+
(target.kind === "header" || target.kind === "footer")
|
|
3347
|
+
) {
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3270
3350
|
activeRuntime.openStory(target);
|
|
3271
3351
|
},
|
|
3272
3352
|
onDeleteSectionBreak: (sectionIndex) =>
|
|
@@ -4740,6 +4820,12 @@ function buildSuggestionCardModel(args: {
|
|
|
4740
4820
|
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedSuggestion.canReject,
|
|
4741
4821
|
canEditSuggestion: canReviewSuggestion && focusedSuggestion.editable,
|
|
4742
4822
|
canAddComment,
|
|
4823
|
+
...(focusedSuggestion.commentThreadIds
|
|
4824
|
+
? { commentThreadIds: focusedSuggestion.commentThreadIds }
|
|
4825
|
+
: {}),
|
|
4826
|
+
...(focusedSuggestion.replyCount !== undefined
|
|
4827
|
+
? { replyCount: focusedSuggestion.replyCount }
|
|
4828
|
+
: {}),
|
|
4743
4829
|
...(disabledReason ? { disabledReason } : {}),
|
|
4744
4830
|
};
|
|
4745
4831
|
}
|