@beyondwork/docx-react-component 1.0.83 → 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 +86 -4
- package/src/api/v3/_runtime-handle.ts +15 -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/api/v3/runtime/workflow.ts +154 -6
- package/src/core/commands/index.ts +81 -25
- package/src/core/state/editor-state.ts +15 -0
- package/src/io/export/serialize-main-document.ts +72 -6
- 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/io/ooxml/workflow-payload-validator.ts +24 -0
- package/src/io/ooxml/workflow-payload.ts +12 -0
- package/src/model/canonical-document.ts +9 -0
- package/src/model/review/comment-types.ts +2 -0
- package/src/runtime/document-runtime.ts +718 -68
- 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/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +54 -45
- package/src/runtime/scopes/workflow-overlap.ts +41 -9
- package/src/runtime/suggestions-snapshot.ts +24 -0
- package/src/runtime/surface-projection.ts +59 -2
- package/src/runtime/workflow/coordinator.ts +66 -14
- package/src/runtime/workflow/scope-writer.ts +83 -5
- package/src/shell/ref-commands.ts +3 -354
- package/src/shell/session-bootstrap.ts +10 -0
- package/src/ui/WordReviewEditor.tsx +99 -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
|
@@ -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");
|
|
@@ -1289,6 +1297,8 @@ function createLoadingRuntimeBridge(input: {
|
|
|
1289
1297
|
addInvisibleScope: () => ({ scopeId: "", anchor: { kind: "range", from: 0, to: 0, assoc: { start: -1, end: 1 } } }),
|
|
1290
1298
|
setScopeVisibility: () => undefined,
|
|
1291
1299
|
getScopeVisibility: () => "visible" as const,
|
|
1300
|
+
setScopeGuardPolicy: () => undefined,
|
|
1301
|
+
getScopeGuardPolicy: () => "none" as const,
|
|
1292
1302
|
setScopeChromeVisibility: () => undefined,
|
|
1293
1303
|
getScopeChromeVisibility: () => ({ mode: "all" as const }),
|
|
1294
1304
|
subscribeToScopeQuery: (_filter, _callback) => () => undefined,
|
|
@@ -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);
|
|
@@ -393,6 +414,8 @@ export function __createWordReviewEditorRefBridge(
|
|
|
393
414
|
addInvisibleScope: (params) => runtime.addInvisibleScope(params),
|
|
394
415
|
setScopeVisibility: (scopeId, visibility) => runtime.setScopeVisibility(scopeId, visibility),
|
|
395
416
|
getScopeVisibility: (scopeId) => runtime.getScopeVisibility(scopeId),
|
|
417
|
+
setScopeGuardPolicy: (scopeId, guardPolicy) => runtime.setScopeGuardPolicy(scopeId, guardPolicy),
|
|
418
|
+
getScopeGuardPolicy: (scopeId) => runtime.getScopeGuardPolicy(scopeId),
|
|
396
419
|
setScopeChromeVisibility: (state) => runtime.setScopeChromeVisibility(state),
|
|
397
420
|
getScopeChromeVisibility: () => runtime.getScopeChromeVisibility(),
|
|
398
421
|
subscribeToScopeQuery: (filter, callback) => runtime.subscribeToScopeQuery(filter, callback),
|
|
@@ -1039,7 +1062,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1039
1062
|
} = props;
|
|
1040
1063
|
|
|
1041
1064
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
1042
|
-
const [showTrackedChanges, setShowTrackedChanges] = useState(
|
|
1065
|
+
const [showTrackedChanges, setShowTrackedChanges] = useState(() => suggestionsEnabled);
|
|
1066
|
+
const [localMarkupDisplay, setLocalMarkupDisplay] =
|
|
1067
|
+
useState<WorkflowMarkupMode | null>(() => suggestionsEnabled ? "all" : null);
|
|
1043
1068
|
const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
|
|
1044
1069
|
const [suppressedSuggestionRevisionId, setSuppressedSuggestionRevisionId] = useState<string | null>(null);
|
|
1045
1070
|
const [selectionToolbarAnchor, setSelectionToolbarAnchor] = useState<SelectionToolbarAnchor | null>(null);
|
|
@@ -1341,7 +1366,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1341
1366
|
loadingViewState,
|
|
1342
1367
|
);
|
|
1343
1368
|
const isPageWorkspace = viewState.workspaceMode === "page";
|
|
1344
|
-
const liveMarkupDisplay =
|
|
1369
|
+
const liveMarkupDisplay = localMarkupDisplay ??
|
|
1370
|
+
__resolveLiveMarkupDisplay(markupDisplay, isPageWorkspace);
|
|
1345
1371
|
const documentNavigation = useRuntimeValue(
|
|
1346
1372
|
runtime
|
|
1347
1373
|
? {
|
|
@@ -1481,9 +1507,29 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1481
1507
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
1482
1508
|
}, [activeRuntime, effectiveViewMode]);
|
|
1483
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
|
+
|
|
1484
1525
|
useEffect(() => {
|
|
1485
|
-
|
|
1486
|
-
|
|
1526
|
+
if (suggestionsEnabled) {
|
|
1527
|
+
setTrackedChangesAuthoring(true);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
setShowTrackedChanges(false);
|
|
1531
|
+
api.runtime.document.setMode("editing");
|
|
1532
|
+
}, [api, setTrackedChangesAuthoring, suggestionsEnabled]);
|
|
1487
1533
|
|
|
1488
1534
|
// design-close-chrome Phase 2 — density contract (designsystem §4.2).
|
|
1489
1535
|
// When the `density` prop is supplied, drive the root `data-density`
|
|
@@ -1643,6 +1689,12 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1643
1689
|
reopenComment: (commentId) => activeRuntime.reopenComment(commentId),
|
|
1644
1690
|
addCommentReply: (commentId, body) =>
|
|
1645
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),
|
|
1646
1698
|
editCommentBody: (commentId, body) =>
|
|
1647
1699
|
activeRuntime.editCommentBody(commentId, body),
|
|
1648
1700
|
deleteComment: (commentId) => {
|
|
@@ -1654,6 +1706,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1654
1706
|
addInvisibleScope: (params) => activeRuntime.addInvisibleScope(params),
|
|
1655
1707
|
setScopeVisibility: (scopeId, visibility) => activeRuntime.setScopeVisibility(scopeId, visibility),
|
|
1656
1708
|
getScopeVisibility: (scopeId) => activeRuntime.getScopeVisibility(scopeId),
|
|
1709
|
+
setScopeGuardPolicy: (scopeId, guardPolicy) => activeRuntime.setScopeGuardPolicy(scopeId, guardPolicy),
|
|
1710
|
+
getScopeGuardPolicy: (scopeId) => activeRuntime.getScopeGuardPolicy(scopeId),
|
|
1657
1711
|
setScopeChromeVisibility: (state) => activeRuntime.setScopeChromeVisibility(state),
|
|
1658
1712
|
getScopeChromeVisibility: () => activeRuntime.getScopeChromeVisibility(),
|
|
1659
1713
|
subscribeToScopeQuery: (filter, callback) => activeRuntime.subscribeToScopeQuery(filter, callback),
|
|
@@ -2736,7 +2790,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2736
2790
|
);
|
|
2737
2791
|
|
|
2738
2792
|
const addSelectionToolbarComment = useCallback(() => {
|
|
2739
|
-
|
|
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
|
+
}
|
|
2740
2807
|
if (!commentId) {
|
|
2741
2808
|
return;
|
|
2742
2809
|
}
|
|
@@ -2744,7 +2811,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2744
2811
|
queueMicrotask(() => {
|
|
2745
2812
|
focusDocumentSurface();
|
|
2746
2813
|
});
|
|
2747
|
-
}, [
|
|
2814
|
+
}, [
|
|
2815
|
+
activeRuntime,
|
|
2816
|
+
activeSelectionTool,
|
|
2817
|
+
currentUser.userId,
|
|
2818
|
+
dismissSelectionToolbar,
|
|
2819
|
+
focusDocumentSurface,
|
|
2820
|
+
]);
|
|
2748
2821
|
|
|
2749
2822
|
const handleSelectionToolbarAnchorChange = useCallback(
|
|
2750
2823
|
(nextAnchor: SelectionToolbarAnchor | null) => {
|
|
@@ -3162,7 +3235,8 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3162
3235
|
onWorkspaceModeChange: (mode) => activeRuntime.setWorkspaceMode(mode),
|
|
3163
3236
|
onZoomChange: (level) => activeRuntime.setZoom(level),
|
|
3164
3237
|
onActiveRailTabChange: setActiveRailTab,
|
|
3165
|
-
onShowTrackedChangesChange:
|
|
3238
|
+
onShowTrackedChangesChange: setTrackedChangesAuthoring,
|
|
3239
|
+
onReviewMarkupModeChange: setReviewMarkupMode,
|
|
3166
3240
|
onToggleBold: () =>
|
|
3167
3241
|
applyRuntimeFormattingOperation(activeRuntime, { type: "toggle", mark: "bold" }),
|
|
3168
3242
|
onToggleItalic: () =>
|
|
@@ -3259,10 +3333,20 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3259
3333
|
// with a mount-time `console.warn`; hosts that still pass them can
|
|
3260
3334
|
// migrate to `onOpenStory` at their leisure.
|
|
3261
3335
|
onOpenHeaderStoryForPage: (pageIndex: number) =>
|
|
3262
|
-
|
|
3336
|
+
isPageWorkspace
|
|
3337
|
+
? openStoryForPage(activeRuntime, pageIndex, "header")
|
|
3338
|
+
: undefined,
|
|
3263
3339
|
onOpenFooterStoryForPage: (pageIndex: number) =>
|
|
3264
|
-
|
|
3340
|
+
isPageWorkspace
|
|
3341
|
+
? openStoryForPage(activeRuntime, pageIndex, "footer")
|
|
3342
|
+
: undefined,
|
|
3265
3343
|
onOpenStory: (target) => {
|
|
3344
|
+
if (
|
|
3345
|
+
!isPageWorkspace &&
|
|
3346
|
+
(target.kind === "header" || target.kind === "footer")
|
|
3347
|
+
) {
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3266
3350
|
activeRuntime.openStory(target);
|
|
3267
3351
|
},
|
|
3268
3352
|
onDeleteSectionBreak: (sectionIndex) =>
|
|
@@ -4736,6 +4820,12 @@ function buildSuggestionCardModel(args: {
|
|
|
4736
4820
|
canReject: canReviewSuggestion && capabilities.canRejectChange && focusedSuggestion.canReject,
|
|
4737
4821
|
canEditSuggestion: canReviewSuggestion && focusedSuggestion.editable,
|
|
4738
4822
|
canAddComment,
|
|
4823
|
+
...(focusedSuggestion.commentThreadIds
|
|
4824
|
+
? { commentThreadIds: focusedSuggestion.commentThreadIds }
|
|
4825
|
+
: {}),
|
|
4826
|
+
...(focusedSuggestion.replyCount !== undefined
|
|
4827
|
+
? { replyCount: focusedSuggestion.replyCount }
|
|
4828
|
+
: {}),
|
|
4739
4829
|
...(disabledReason ? { disabledReason } : {}),
|
|
4740
4830
|
};
|
|
4741
4831
|
}
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
WorkspaceMode,
|
|
15
15
|
} from "../api/public-types.ts";
|
|
16
16
|
import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail.tsx";
|
|
17
|
+
import type { MarkupDisplay } from "./headless/comment-decoration-model.ts";
|
|
17
18
|
|
|
18
19
|
type CommandHandler = (...args: any[]) => unknown;
|
|
19
20
|
|
|
@@ -22,6 +23,7 @@ export interface EditorCommandBag {
|
|
|
22
23
|
onZoomChange?(level: ZoomLevel): void;
|
|
23
24
|
onActiveRailTabChange(value: ReviewRailTab): void;
|
|
24
25
|
onShowTrackedChangesChange(show: boolean): void;
|
|
26
|
+
onReviewMarkupModeChange?(mode: MarkupDisplay): void;
|
|
25
27
|
onUndo(): void;
|
|
26
28
|
onRedo(): void;
|
|
27
29
|
onSetParagraphStyle?(styleId: string): void;
|
|
@@ -102,7 +104,7 @@ export interface EditorCommandBag {
|
|
|
102
104
|
/** Open the footer story for a specific page (double-click on its band). */
|
|
103
105
|
onOpenFooterStoryForPage?(pageIndex: number): void;
|
|
104
106
|
/**
|
|
105
|
-
* P8.11 — per-page header/footer band click handler.
|
|
107
|
+
* P8.11 — per-page header/footer band double-click handler. Receives the
|
|
106
108
|
* exact `EditorStoryTarget` the band represents; the command bag wires
|
|
107
109
|
* this to `runtime.openStory(target)`.
|
|
108
110
|
*/
|
|
@@ -225,6 +225,19 @@ export function buildClassFromRevisionDisplay(
|
|
|
225
225
|
parts.push("text-secondary");
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Formatting/property-change revisions carry their semantics through
|
|
229
|
+
// `kind` even when markup posture has no underline/strike flag. Give
|
|
230
|
+
// mounted suggestion authoring a visible, non-destructive cue instead
|
|
231
|
+
// of silently relying on the sidebar/card path.
|
|
232
|
+
if (
|
|
233
|
+
parts.length === 0 &&
|
|
234
|
+
(display.kind === "formatting" || display.kind === "property-change")
|
|
235
|
+
) {
|
|
236
|
+
parts.push(
|
|
237
|
+
"underline decoration-accent/70 decoration-dotted decoration-1 underline-offset-2",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
// Surface the author palette color as a CSS variable the renderer
|
|
229
242
|
// can pick up via `var(--wre-revision-author)`. Consumer stylesheet
|
|
230
243
|
// composes this into ring/underline color when the palette slot is
|
|
@@ -81,6 +81,8 @@ export interface SuggestionReviewSelectionToolModel extends BaseSelectionToolMod
|
|
|
81
81
|
canReject: boolean;
|
|
82
82
|
canEditSuggestion: boolean;
|
|
83
83
|
canAddComment: boolean;
|
|
84
|
+
commentThreadIds?: string[];
|
|
85
|
+
replyCount?: number;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
export type StructureContextKind = "table" | "image" | "object" | "list";
|