@beyondwork/docx-react-component 1.0.31 → 1.0.33
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/README.md +6 -0
- package/package.json +1 -1
- package/src/api/public-types.ts +16 -1
- package/src/api/session-state.ts +2 -0
- package/src/io/docx-session.ts +16 -3
- package/src/io/ooxml/parse-footnotes.ts +23 -33
- package/src/io/ooxml/parse-headers-footers.ts +20 -21
- package/src/io/ooxml/workflow-payload.ts +311 -8
- package/src/model/snapshot.ts +113 -1
- package/src/runtime/document-runtime.ts +207 -33
- package/src/runtime/surface-projection.ts +156 -7
- package/src/ui/WordReviewEditor.tsx +13 -5
- package/src/ui/editor-surface-controller.tsx +2 -0
- package/src/ui/headless/selection-tool-resolver.ts +4 -1
- package/src/ui/headless/selection-tool-types.ts +1 -2
- package/src/ui/workflow-surface-blocked-rails.ts +19 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +1 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +1 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +136 -24
- package/src/ui-tailwind/editor-surface/pm-schema.ts +29 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +12 -0
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +20 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +31 -0
- package/src/ui-tailwind/theme/editor-theme.css +8 -0
|
@@ -354,7 +354,12 @@ export function createDocumentRuntime(
|
|
|
354
354
|
enforcedRangeCount: 0,
|
|
355
355
|
preservedRangeCount: 0,
|
|
356
356
|
};
|
|
357
|
-
let workflowOverlay: WorkflowOverlay | null =
|
|
357
|
+
let workflowOverlay: WorkflowOverlay | null =
|
|
358
|
+
structuredClone(
|
|
359
|
+
options.initialSessionState?.workflowOverlay ??
|
|
360
|
+
options.initialSnapshot?.workflowOverlay ??
|
|
361
|
+
null,
|
|
362
|
+
);
|
|
358
363
|
let workflowMetadataDefinitions: WorkflowMetadataDefinition[] =
|
|
359
364
|
options.initialSessionState?.workflowMetadata?.definitions
|
|
360
365
|
?? options.initialSnapshot?.workflowMetadata?.definitions
|
|
@@ -1995,6 +2000,7 @@ export function createDocumentRuntime(
|
|
|
1995
2000
|
compatibility,
|
|
1996
2001
|
protectionSnapshot,
|
|
1997
2002
|
}) as unknown as PersistedEditorSnapshot),
|
|
2003
|
+
workflowOverlay: workflowOverlay ?? undefined,
|
|
1998
2004
|
workflowMetadata: deriveWorkflowMetadataSnapshot(),
|
|
1999
2005
|
},
|
|
2000
2006
|
);
|
|
@@ -3484,39 +3490,155 @@ function refreshDocumentFields(
|
|
|
3484
3490
|
let changedFrom: number | undefined;
|
|
3485
3491
|
let changedTo: number | undefined;
|
|
3486
3492
|
|
|
3487
|
-
const
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
+
const refreshStoryFields = (
|
|
3494
|
+
blocks: readonly BlockNode[],
|
|
3495
|
+
storyTarget: EditorStoryTarget,
|
|
3496
|
+
): { blocks: BlockNode[]; changed: boolean } => {
|
|
3497
|
+
let storyChanged = false;
|
|
3498
|
+
let storyChangedFrom: number | undefined;
|
|
3499
|
+
let storyChangedTo: number | undefined;
|
|
3500
|
+
const refreshed = refreshBlocksWithCursor(blocks, (field, range) => {
|
|
3501
|
+
if (!field.fieldFamily || !isSupportedFieldFamily(field.fieldFamily)) {
|
|
3502
|
+
return field;
|
|
3503
|
+
}
|
|
3504
|
+
if (supportedOnly && field.fieldFamily === "TOC") {
|
|
3505
|
+
return field;
|
|
3506
|
+
}
|
|
3507
|
+
const display = resolveSupportedFieldDisplay(
|
|
3508
|
+
field,
|
|
3509
|
+
document,
|
|
3510
|
+
bookmarkMap,
|
|
3511
|
+
paragraphs,
|
|
3512
|
+
navigation,
|
|
3513
|
+
storyTarget,
|
|
3514
|
+
);
|
|
3515
|
+
if (!display) {
|
|
3516
|
+
return field;
|
|
3517
|
+
}
|
|
3518
|
+
updatedCount += 1;
|
|
3519
|
+
const nextField: FieldNode = {
|
|
3520
|
+
...field,
|
|
3521
|
+
children: buildInlineNodesFromDisplayText(display.displayText),
|
|
3522
|
+
refreshStatus: display.refreshStatus,
|
|
3523
|
+
};
|
|
3524
|
+
if (
|
|
3525
|
+
nextField.refreshStatus !== field.refreshStatus ||
|
|
3526
|
+
flattenInlineDisplayText(nextField.children) !== flattenInlineDisplayText(field.children)
|
|
3527
|
+
) {
|
|
3528
|
+
storyChanged = true;
|
|
3529
|
+
storyChangedFrom = storyChangedFrom === undefined ? range.from : Math.min(storyChangedFrom, range.from);
|
|
3530
|
+
storyChangedTo = storyChangedTo === undefined ? range.to : Math.max(storyChangedTo, range.to);
|
|
3531
|
+
}
|
|
3532
|
+
return nextField;
|
|
3533
|
+
});
|
|
3534
|
+
if (storyChanged) {
|
|
3535
|
+
changed = true;
|
|
3536
|
+
if (storyTargetsEqual(activeStory, storyTarget)) {
|
|
3537
|
+
if (storyChangedFrom !== undefined) {
|
|
3538
|
+
changedFrom = changedFrom === undefined ? storyChangedFrom : Math.min(changedFrom, storyChangedFrom);
|
|
3539
|
+
}
|
|
3540
|
+
if (storyChangedTo !== undefined) {
|
|
3541
|
+
changedTo = changedTo === undefined ? storyChangedTo : Math.max(changedTo, storyChangedTo);
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3493
3544
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3545
|
+
return { blocks: refreshed.blocks, changed: storyChanged };
|
|
3546
|
+
};
|
|
3547
|
+
|
|
3548
|
+
const refreshedMain = refreshStoryFields(document.content.children, MAIN_STORY_TARGET);
|
|
3549
|
+
let nextSubParts = document.subParts;
|
|
3550
|
+
|
|
3551
|
+
if (document.subParts) {
|
|
3552
|
+
let subPartsChanged = false;
|
|
3553
|
+
const nextHeaders = document.subParts.headers.map((header) => {
|
|
3554
|
+
const storyTarget: EditorStoryTarget = {
|
|
3555
|
+
kind: "header",
|
|
3556
|
+
relationshipId: header.relationshipId,
|
|
3557
|
+
variant: header.variant,
|
|
3558
|
+
...(header.sectionIndex !== undefined ? { sectionIndex: header.sectionIndex } : {}),
|
|
3559
|
+
};
|
|
3560
|
+
const refreshed = refreshStoryFields(header.blocks, storyTarget);
|
|
3561
|
+
if (refreshed.changed) {
|
|
3562
|
+
subPartsChanged = true;
|
|
3563
|
+
return {
|
|
3564
|
+
...header,
|
|
3565
|
+
blocks: refreshed.blocks,
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
return header;
|
|
3569
|
+
});
|
|
3570
|
+
const nextFooters = document.subParts.footers.map((footer) => {
|
|
3571
|
+
const storyTarget: EditorStoryTarget = {
|
|
3572
|
+
kind: "footer",
|
|
3573
|
+
relationshipId: footer.relationshipId,
|
|
3574
|
+
variant: footer.variant,
|
|
3575
|
+
...(footer.sectionIndex !== undefined ? { sectionIndex: footer.sectionIndex } : {}),
|
|
3576
|
+
};
|
|
3577
|
+
const refreshed = refreshStoryFields(footer.blocks, storyTarget);
|
|
3578
|
+
if (refreshed.changed) {
|
|
3579
|
+
subPartsChanged = true;
|
|
3580
|
+
return {
|
|
3581
|
+
...footer,
|
|
3582
|
+
blocks: refreshed.blocks,
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
return footer;
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3588
|
+
let nextFootnoteCollection = document.subParts.footnoteCollection;
|
|
3589
|
+
if (document.subParts.footnoteCollection) {
|
|
3590
|
+
let noteCollectionChanged = false;
|
|
3591
|
+
const nextFootnotes = Object.fromEntries(
|
|
3592
|
+
Object.entries(document.subParts.footnoteCollection.footnotes).map(([noteId, note]) => {
|
|
3593
|
+
const refreshed = refreshStoryFields(note.blocks, { kind: "footnote", noteId });
|
|
3594
|
+
if (refreshed.changed) {
|
|
3595
|
+
noteCollectionChanged = true;
|
|
3596
|
+
return [
|
|
3597
|
+
noteId,
|
|
3598
|
+
{
|
|
3599
|
+
...note,
|
|
3600
|
+
blocks: refreshed.blocks,
|
|
3601
|
+
},
|
|
3602
|
+
];
|
|
3603
|
+
}
|
|
3604
|
+
return [noteId, note];
|
|
3605
|
+
}),
|
|
3606
|
+
);
|
|
3607
|
+
const nextEndnotes = Object.fromEntries(
|
|
3608
|
+
Object.entries(document.subParts.footnoteCollection.endnotes).map(([noteId, note]) => {
|
|
3609
|
+
const refreshed = refreshStoryFields(note.blocks, { kind: "endnote", noteId });
|
|
3610
|
+
if (refreshed.changed) {
|
|
3611
|
+
noteCollectionChanged = true;
|
|
3612
|
+
return [
|
|
3613
|
+
noteId,
|
|
3614
|
+
{
|
|
3615
|
+
...note,
|
|
3616
|
+
blocks: refreshed.blocks,
|
|
3617
|
+
},
|
|
3618
|
+
];
|
|
3619
|
+
}
|
|
3620
|
+
return [noteId, note];
|
|
3621
|
+
}),
|
|
3622
|
+
);
|
|
3623
|
+
if (noteCollectionChanged) {
|
|
3624
|
+
subPartsChanged = true;
|
|
3625
|
+
nextFootnoteCollection = {
|
|
3626
|
+
footnotes: nextFootnotes,
|
|
3627
|
+
endnotes: nextEndnotes,
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3503
3630
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
flattenInlineDisplayText(nextField.children) !== flattenInlineDisplayText(field.children)
|
|
3513
|
-
) {
|
|
3514
|
-
changed = true;
|
|
3515
|
-
changedFrom = changedFrom === undefined ? range.from : Math.min(changedFrom, range.from);
|
|
3516
|
-
changedTo = changedTo === undefined ? range.to : Math.max(changedTo, range.to);
|
|
3631
|
+
|
|
3632
|
+
if (subPartsChanged) {
|
|
3633
|
+
nextSubParts = {
|
|
3634
|
+
...document.subParts,
|
|
3635
|
+
headers: nextHeaders,
|
|
3636
|
+
footers: nextFooters,
|
|
3637
|
+
...(nextFootnoteCollection ? { footnoteCollection: nextFootnoteCollection } : {}),
|
|
3638
|
+
};
|
|
3517
3639
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3520
3642
|
if (!changed) {
|
|
3521
3643
|
return { document, updatedCount, changed: false };
|
|
3522
3644
|
}
|
|
@@ -3525,8 +3647,9 @@ function refreshDocumentFields(
|
|
|
3525
3647
|
...document,
|
|
3526
3648
|
content: {
|
|
3527
3649
|
...document.content,
|
|
3528
|
-
children:
|
|
3650
|
+
children: refreshedMain.blocks,
|
|
3529
3651
|
},
|
|
3652
|
+
...(nextSubParts ? { subParts: nextSubParts } : {}),
|
|
3530
3653
|
};
|
|
3531
3654
|
const nextRegistry = buildFieldRegistry({
|
|
3532
3655
|
content: nextDocument.content,
|
|
@@ -3821,6 +3944,7 @@ function resolveSupportedFieldDisplay(
|
|
|
3821
3944
|
bookmarkMap: Map<string, { bookmarkId: string; paragraphIndex: number }>,
|
|
3822
3945
|
paragraphs: readonly ParagraphContext[],
|
|
3823
3946
|
navigation: DocumentNavigationSnapshot,
|
|
3947
|
+
storyTarget: EditorStoryTarget,
|
|
3824
3948
|
): { displayText: string; refreshStatus: FieldRefreshStatus } | undefined {
|
|
3825
3949
|
if (!field.fieldFamily || !isSupportedFieldFamily(field.fieldFamily)) {
|
|
3826
3950
|
return undefined;
|
|
@@ -3846,7 +3970,10 @@ function resolveSupportedFieldDisplay(
|
|
|
3846
3970
|
return { displayText: "", refreshStatus: "unresolvable" };
|
|
3847
3971
|
}
|
|
3848
3972
|
const pageIndex = findPageForOffset(navigation.pages, paragraph.startOffset);
|
|
3849
|
-
|
|
3973
|
+
const page = navigation.pages[pageIndex] ?? navigation.pages[0];
|
|
3974
|
+
return page
|
|
3975
|
+
? { displayText: String(resolveDisplayedPageNumber(page)), refreshStatus: "current" }
|
|
3976
|
+
: { displayText: "", refreshStatus: "unresolvable" };
|
|
3850
3977
|
}
|
|
3851
3978
|
if (field.fieldFamily === "NOTEREF") {
|
|
3852
3979
|
const paragraph = paragraphs[bookmark.paragraphIndex]?.paragraph;
|
|
@@ -3861,6 +3988,53 @@ function resolveSupportedFieldDisplay(
|
|
|
3861
3988
|
return undefined;
|
|
3862
3989
|
}
|
|
3863
3990
|
|
|
3991
|
+
function resolveRepresentativePageForStory(
|
|
3992
|
+
navigation: DocumentNavigationSnapshot,
|
|
3993
|
+
storyTarget: EditorStoryTarget,
|
|
3994
|
+
): DocumentNavigationSnapshot["pages"][number] | undefined {
|
|
3995
|
+
if (storyTarget.kind === "main") {
|
|
3996
|
+
return navigation.pages[navigation.activePageIndex] ?? navigation.pages[0];
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
if (storyTarget.kind === "header" || storyTarget.kind === "footer") {
|
|
4000
|
+
const sectionIndex = storyTarget.sectionIndex ?? navigation.activeSectionIndex;
|
|
4001
|
+
const sectionPages = navigation.pages.filter((page) => page.sectionIndex === sectionIndex);
|
|
4002
|
+
if (sectionPages.length === 0) {
|
|
4003
|
+
return navigation.pages[0];
|
|
4004
|
+
}
|
|
4005
|
+
if (storyTarget.variant === "first") {
|
|
4006
|
+
return sectionPages[0];
|
|
4007
|
+
}
|
|
4008
|
+
if (storyTarget.variant === "even") {
|
|
4009
|
+
return sectionPages.find((page) => (page.pageInSection + 1) % 2 === 0) ?? sectionPages[0];
|
|
4010
|
+
}
|
|
4011
|
+
return (
|
|
4012
|
+
sectionPages.find((page) => isDefaultHeaderFooterPage(page)) ??
|
|
4013
|
+
sectionPages[0]
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
return navigation.pages[navigation.activePageIndex] ?? navigation.pages[0];
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
function isDefaultHeaderFooterPage(
|
|
4021
|
+
page: DocumentNavigationSnapshot["pages"][number],
|
|
4022
|
+
): boolean {
|
|
4023
|
+
if (page.layout.differentFirstPage && page.pageInSection === 0) {
|
|
4024
|
+
return false;
|
|
4025
|
+
}
|
|
4026
|
+
if (page.layout.differentOddEvenPages) {
|
|
4027
|
+
return (page.pageInSection + 1) % 2 === 1;
|
|
4028
|
+
}
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
function resolveDisplayedPageNumber(
|
|
4033
|
+
page: DocumentNavigationSnapshot["pages"][number],
|
|
4034
|
+
): number {
|
|
4035
|
+
return (page.layout.pageNumbering?.start ?? 1) + page.pageInSection;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
3864
4038
|
interface ParagraphContext {
|
|
3865
4039
|
paragraph: ParagraphNode;
|
|
3866
4040
|
startOffset: number;
|
|
@@ -90,6 +90,7 @@ export function createEditorSurfaceSnapshot(
|
|
|
90
90
|
cursor,
|
|
91
91
|
counters,
|
|
92
92
|
numberingPrefixResolver,
|
|
93
|
+
activeStory.kind !== "main",
|
|
93
94
|
);
|
|
94
95
|
blocks.push(surfaceBlock.block);
|
|
95
96
|
lockedFragmentIds.push(...surfaceBlock.lockedFragmentIds);
|
|
@@ -123,6 +124,7 @@ function createSurfaceBlock(
|
|
|
123
124
|
altChunk: number;
|
|
124
125
|
},
|
|
125
126
|
numberingPrefixResolver: NumberingPrefixResolver,
|
|
127
|
+
promoteSecondaryStoryTextBoxes: boolean,
|
|
126
128
|
): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
|
|
127
129
|
if (block.type === "opaque_block") {
|
|
128
130
|
const fragment = getOpaqueFragment(document.preservation as never, block.fragmentId);
|
|
@@ -162,6 +164,7 @@ function createSurfaceBlock(
|
|
|
162
164
|
cursor,
|
|
163
165
|
counters,
|
|
164
166
|
numberingPrefixResolver,
|
|
167
|
+
promoteSecondaryStoryTextBoxes,
|
|
165
168
|
);
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -200,6 +203,7 @@ function createSurfaceBlock(
|
|
|
200
203
|
cursor,
|
|
201
204
|
counters,
|
|
202
205
|
numberingPrefixResolver,
|
|
206
|
+
promoteSecondaryStoryTextBoxes,
|
|
203
207
|
);
|
|
204
208
|
}
|
|
205
209
|
|
|
@@ -291,6 +295,7 @@ function createSurfaceBlock(
|
|
|
291
295
|
document,
|
|
292
296
|
cursor,
|
|
293
297
|
numberingPrefixResolver,
|
|
298
|
+
promoteSecondaryStoryTextBoxes,
|
|
294
299
|
);
|
|
295
300
|
}
|
|
296
301
|
|
|
@@ -308,6 +313,7 @@ function createTableBlock(
|
|
|
308
313
|
altChunk: number;
|
|
309
314
|
},
|
|
310
315
|
numberingPrefixResolver: NumberingPrefixResolver,
|
|
316
|
+
promoteSecondaryStoryTextBoxes: boolean,
|
|
311
317
|
): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
|
|
312
318
|
const lockedFragmentIds: string[] = [];
|
|
313
319
|
let innerCursor = cursor;
|
|
@@ -327,6 +333,7 @@ function createTableBlock(
|
|
|
327
333
|
innerCursor,
|
|
328
334
|
counters,
|
|
329
335
|
numberingPrefixResolver,
|
|
336
|
+
promoteSecondaryStoryTextBoxes,
|
|
330
337
|
);
|
|
331
338
|
cellContent.push(result.block);
|
|
332
339
|
lockedFragmentIds.push(...result.lockedFragmentIds);
|
|
@@ -458,6 +465,7 @@ function createSdtBlock(
|
|
|
458
465
|
altChunk: number;
|
|
459
466
|
},
|
|
460
467
|
numberingPrefixResolver: NumberingPrefixResolver,
|
|
468
|
+
promoteSecondaryStoryTextBoxes: boolean,
|
|
461
469
|
): { block: SurfaceBlockSnapshot; lockedFragmentIds: string[]; nextCursor: number } {
|
|
462
470
|
const children: SurfaceBlockSnapshot[] = [];
|
|
463
471
|
const lockedFragmentIds: string[] = [];
|
|
@@ -470,6 +478,7 @@ function createSdtBlock(
|
|
|
470
478
|
innerCursor,
|
|
471
479
|
counters,
|
|
472
480
|
numberingPrefixResolver,
|
|
481
|
+
promoteSecondaryStoryTextBoxes,
|
|
473
482
|
);
|
|
474
483
|
children.push(result.block);
|
|
475
484
|
lockedFragmentIds.push(...result.lockedFragmentIds);
|
|
@@ -504,6 +513,7 @@ function createParagraphBlock(
|
|
|
504
513
|
document: CanonicalDocumentEnvelope,
|
|
505
514
|
start: number,
|
|
506
515
|
numberingPrefixResolver: NumberingPrefixResolver,
|
|
516
|
+
promoteSecondaryStoryTextBoxes: boolean,
|
|
507
517
|
): {
|
|
508
518
|
block: SurfaceBlockSnapshot;
|
|
509
519
|
nextCursor: number;
|
|
@@ -553,7 +563,13 @@ function createParagraphBlock(
|
|
|
553
563
|
const children = Array.isArray(paragraph.children) ? paragraph.children : [];
|
|
554
564
|
|
|
555
565
|
for (const child of children) {
|
|
556
|
-
const result = appendInlineSegments(
|
|
566
|
+
const result = appendInlineSegments(
|
|
567
|
+
accumulator,
|
|
568
|
+
child,
|
|
569
|
+
document,
|
|
570
|
+
cursor,
|
|
571
|
+
promoteSecondaryStoryTextBoxes,
|
|
572
|
+
);
|
|
557
573
|
cursor = result.nextCursor;
|
|
558
574
|
lockedFragmentIds.push(...result.lockedFragmentIds);
|
|
559
575
|
}
|
|
@@ -671,6 +687,7 @@ function appendInlineSegments(
|
|
|
671
687
|
node: InlineNode,
|
|
672
688
|
document: CanonicalDocumentEnvelope,
|
|
673
689
|
start: number,
|
|
690
|
+
promoteSecondaryStoryTextBoxes: boolean,
|
|
674
691
|
hyperlinkHref?: string,
|
|
675
692
|
): { nextCursor: number; lockedFragmentIds: string[] } {
|
|
676
693
|
switch (node.type) {
|
|
@@ -712,7 +729,14 @@ function appendInlineSegments(
|
|
|
712
729
|
case "hyperlink": {
|
|
713
730
|
let cursor = start;
|
|
714
731
|
for (const child of node.children) {
|
|
715
|
-
const result = appendInlineSegments(
|
|
732
|
+
const result = appendInlineSegments(
|
|
733
|
+
paragraph,
|
|
734
|
+
child,
|
|
735
|
+
document,
|
|
736
|
+
cursor,
|
|
737
|
+
promoteSecondaryStoryTextBoxes,
|
|
738
|
+
node.href,
|
|
739
|
+
);
|
|
716
740
|
cursor = result.nextCursor;
|
|
717
741
|
}
|
|
718
742
|
return { nextCursor: cursor, lockedFragmentIds: [] };
|
|
@@ -756,6 +780,7 @@ function appendInlineSegments(
|
|
|
756
780
|
"Locked whole-unit to keep unsupported inline OOXML intact through export.",
|
|
757
781
|
...(descriptor ? { featureKey: descriptor.featureKey, blockedReasonCode } : {}),
|
|
758
782
|
...(preview?.presentation ? { presentation: preview.presentation } : {}),
|
|
783
|
+
...(preview?.displayText ? { displayText: preview.displayText } : {}),
|
|
759
784
|
state: "locked-preserve-only",
|
|
760
785
|
});
|
|
761
786
|
return { nextCursor: start + 1, lockedFragmentIds: [node.fragmentId] };
|
|
@@ -765,11 +790,29 @@ function appendInlineSegments(
|
|
|
765
790
|
case "smartart_preview":
|
|
766
791
|
return appendComplexPreviewSegment(paragraph, node, start, "SmartArt diagram", createSmartArtDetail(node));
|
|
767
792
|
case "shape":
|
|
793
|
+
if (promoteSecondaryStoryTextBoxes && node.isTextBox && node.text) {
|
|
794
|
+
return appendTextBoxSegment(
|
|
795
|
+
paragraph,
|
|
796
|
+
start,
|
|
797
|
+
"Text box",
|
|
798
|
+
createShapeDetail(node),
|
|
799
|
+
node.text,
|
|
800
|
+
);
|
|
801
|
+
}
|
|
768
802
|
return appendComplexPreviewSegment(paragraph, node, start,
|
|
769
803
|
node.isTextBox ? "Text box" : "Drawing shape", createShapeDetail(node));
|
|
770
804
|
case "wordart":
|
|
771
805
|
return appendComplexPreviewSegment(paragraph, node, start, "WordArt", createWordArtDetail(node));
|
|
772
806
|
case "vml_shape":
|
|
807
|
+
if (promoteSecondaryStoryTextBoxes && shouldRenderSecondaryStoryVmlTextBox(node)) {
|
|
808
|
+
return appendTextBoxSegment(
|
|
809
|
+
paragraph,
|
|
810
|
+
start,
|
|
811
|
+
"Text box",
|
|
812
|
+
createVmlDetail(node),
|
|
813
|
+
node.text!,
|
|
814
|
+
);
|
|
815
|
+
}
|
|
773
816
|
return appendComplexPreviewSegment(paragraph, node, start, "Legacy VML drawing", createVmlDetail(node));
|
|
774
817
|
case "symbol":
|
|
775
818
|
paragraph.segments.push({
|
|
@@ -810,12 +853,20 @@ function appendInlineSegments(
|
|
|
810
853
|
node.fieldFamily === "REF" ||
|
|
811
854
|
node.fieldFamily === "PAGEREF" ||
|
|
812
855
|
node.fieldFamily === "NOTEREF" ||
|
|
813
|
-
node.fieldFamily === "TOC"
|
|
856
|
+
node.fieldFamily === "TOC" ||
|
|
857
|
+
node.fieldFamily === "PAGE" ||
|
|
858
|
+
node.fieldFamily === "NUMPAGES";
|
|
814
859
|
if (node.children && node.children.length > 0) {
|
|
815
860
|
let cursor = start;
|
|
816
861
|
const lockedIds: string[] = [];
|
|
817
862
|
for (const child of node.children) {
|
|
818
|
-
const result = appendInlineSegments(
|
|
863
|
+
const result = appendInlineSegments(
|
|
864
|
+
paragraph,
|
|
865
|
+
child,
|
|
866
|
+
document,
|
|
867
|
+
cursor,
|
|
868
|
+
promoteSecondaryStoryTextBoxes,
|
|
869
|
+
);
|
|
819
870
|
cursor = result.nextCursor;
|
|
820
871
|
lockedIds.push(...result.lockedFragmentIds);
|
|
821
872
|
}
|
|
@@ -826,7 +877,11 @@ function appendInlineSegments(
|
|
|
826
877
|
const fieldLabel =
|
|
827
878
|
node.fieldFamily === "TOC"
|
|
828
879
|
? "Table of Contents"
|
|
829
|
-
:
|
|
880
|
+
: node.fieldFamily === "PAGE"
|
|
881
|
+
? "Current page number"
|
|
882
|
+
: node.fieldFamily === "NUMPAGES"
|
|
883
|
+
? "Total pages"
|
|
884
|
+
: `${node.fieldFamily ?? "Field"}: ${node.fieldTarget ?? node.instruction.trim()}`;
|
|
830
885
|
paragraph.segments.push({
|
|
831
886
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
832
887
|
kind: "field_ref",
|
|
@@ -883,6 +938,33 @@ function appendComplexPreviewSegment(
|
|
|
883
938
|
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
884
939
|
}
|
|
885
940
|
|
|
941
|
+
function appendTextBoxSegment(
|
|
942
|
+
paragraph: ParagraphAccumulator,
|
|
943
|
+
start: number,
|
|
944
|
+
label: string,
|
|
945
|
+
detail: string,
|
|
946
|
+
displayText: string,
|
|
947
|
+
): { nextCursor: number; lockedFragmentIds: string[] } {
|
|
948
|
+
paragraph.segments.push({
|
|
949
|
+
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
950
|
+
kind: "opaque_inline",
|
|
951
|
+
from: start,
|
|
952
|
+
to: start + 1,
|
|
953
|
+
fragmentId: `complex:text-box:${start}`,
|
|
954
|
+
warningId: `warning:text-box:${start}`,
|
|
955
|
+
label,
|
|
956
|
+
detail,
|
|
957
|
+
presentation: "text-box",
|
|
958
|
+
displayText,
|
|
959
|
+
state: "locked-preserve-only",
|
|
960
|
+
});
|
|
961
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function shouldRenderSecondaryStoryVmlTextBox(node: VmlShapeNode): boolean {
|
|
965
|
+
return Boolean(node.text) && (!node.shapeType || /_x0000_t202$/iu.test(node.shapeType));
|
|
966
|
+
}
|
|
967
|
+
|
|
886
968
|
function createChartDetail(node: ChartPreviewNode): string {
|
|
887
969
|
const parts = ["Embedded chart."];
|
|
888
970
|
if (node.previewMediaId) {
|
|
@@ -977,7 +1059,11 @@ function createPlainText(
|
|
|
977
1059
|
text.push("\uFFFC");
|
|
978
1060
|
break;
|
|
979
1061
|
case "opaque_inline":
|
|
980
|
-
text.
|
|
1062
|
+
if ((segment.presentation === "text-box" || segment.presentation === "checkbox") && segment.displayText) {
|
|
1063
|
+
text.push(segment.displayText);
|
|
1064
|
+
} else {
|
|
1065
|
+
text.push("\uFFF9");
|
|
1066
|
+
}
|
|
981
1067
|
break;
|
|
982
1068
|
}
|
|
983
1069
|
}
|
|
@@ -1095,6 +1181,7 @@ function createStorySurface(
|
|
|
1095
1181
|
cursor,
|
|
1096
1182
|
counters,
|
|
1097
1183
|
numberingPrefixResolver,
|
|
1184
|
+
true,
|
|
1098
1185
|
);
|
|
1099
1186
|
surfaceBlocks.push(surfaceBlock.block);
|
|
1100
1187
|
cursor = surfaceBlock.nextCursor;
|
|
@@ -1317,7 +1404,8 @@ function describePreservedInlinePreview(
|
|
|
1317
1404
|
): {
|
|
1318
1405
|
label: string;
|
|
1319
1406
|
detail: string;
|
|
1320
|
-
presentation?: "inline-chip" | "quiet-marker";
|
|
1407
|
+
presentation?: "inline-chip" | "quiet-marker" | "text-box" | "checkbox";
|
|
1408
|
+
displayText?: string;
|
|
1321
1409
|
} | null {
|
|
1322
1410
|
if (/\b(?:w:)?proofErr\b/u.test(payloadReference)) {
|
|
1323
1411
|
const proofType = /\bw:type="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
@@ -1359,6 +1447,11 @@ function describePreservedInlinePreview(
|
|
|
1359
1447
|
};
|
|
1360
1448
|
}
|
|
1361
1449
|
|
|
1450
|
+
const checkboxPreview = describePreservedCheckboxPreview(payloadReference);
|
|
1451
|
+
if (checkboxPreview) {
|
|
1452
|
+
return checkboxPreview;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1362
1455
|
if (/\b(?:w:)?bookmarkStart\b/u.test(payloadReference)) {
|
|
1363
1456
|
const name = /\bw:name="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1364
1457
|
return {
|
|
@@ -1402,6 +1495,62 @@ function describePreservedInlinePreview(
|
|
|
1402
1495
|
return null;
|
|
1403
1496
|
}
|
|
1404
1497
|
|
|
1498
|
+
function describePreservedCheckboxPreview(
|
|
1499
|
+
payloadReference: string,
|
|
1500
|
+
): {
|
|
1501
|
+
label: string;
|
|
1502
|
+
detail: string;
|
|
1503
|
+
presentation: "checkbox";
|
|
1504
|
+
displayText: string;
|
|
1505
|
+
} | null {
|
|
1506
|
+
if (!/\b(?:w:)?sdt\b/u.test(payloadReference) || !/\b(?:w14:)?checkbox\b/u.test(payloadReference)) {
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const checkedValue = /\b(?:w14:)?checked\b[^>]*\b(?:w14:)?val="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1511
|
+
const isChecked = checkedValue === "1" || /^true$/iu.test(checkedValue ?? "");
|
|
1512
|
+
const checkedState = /\b(?:w14:)?checkedState\b[^>]*\b(?:w14:)?val="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1513
|
+
const uncheckedState = /\b(?:w14:)?uncheckedState\b[^>]*\b(?:w14:)?val="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1514
|
+
const displayText =
|
|
1515
|
+
extractCheckboxGlyph(payloadReference) ??
|
|
1516
|
+
decodeCheckboxCodePoint(isChecked ? checkedState : uncheckedState) ??
|
|
1517
|
+
(isChecked ? "☒" : "☐");
|
|
1518
|
+
|
|
1519
|
+
return {
|
|
1520
|
+
label: isChecked ? "Checked checkbox" : "Unchecked checkbox",
|
|
1521
|
+
detail: `Checkbox content control is visible while the original OOXML wrapper remains preserve-backed for export. State: ${isChecked ? "checked" : "unchecked"}.`,
|
|
1522
|
+
presentation: "checkbox",
|
|
1523
|
+
displayText,
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function extractCheckboxGlyph(payloadReference: string): string | null {
|
|
1528
|
+
const content = /<(?:\w+:)?sdtContent\b[^>]*>([\s\S]*?)<\/(?:\w+:)?sdtContent>/u.exec(payloadReference)?.[1];
|
|
1529
|
+
if (!content) {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
const text = [...content.matchAll(/<(?:\w+:)?t\b[^>]*>([\s\S]*?)<\/(?:\w+:)?t>/gu)]
|
|
1533
|
+
.map((match) => decodeXmlEntities(match[1] ?? ""))
|
|
1534
|
+
.join("")
|
|
1535
|
+
.trim();
|
|
1536
|
+
return text.length > 0 ? text : null;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function decodeCheckboxCodePoint(value: string | undefined): string | null {
|
|
1540
|
+
if (!value) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
const codePoint = Number.parseInt(value, 16);
|
|
1544
|
+
if (!Number.isFinite(codePoint)) {
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
return String.fromCodePoint(codePoint);
|
|
1549
|
+
} catch {
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1405
1554
|
function decodeXmlEntities(text: string): string {
|
|
1406
1555
|
return text
|
|
1407
1556
|
.replace(/</g, "<")
|
|
@@ -176,7 +176,10 @@ import {
|
|
|
176
176
|
resolveHeadingShortcutStyleId,
|
|
177
177
|
resolveShellShortcut,
|
|
178
178
|
} from "./runtime-shortcut-dispatch";
|
|
179
|
-
import {
|
|
179
|
+
import {
|
|
180
|
+
deriveVisibleWorkflowBlockedRails,
|
|
181
|
+
deriveVisibleWorkflowLockedZones,
|
|
182
|
+
} from "./workflow-surface-blocked-rails.ts";
|
|
180
183
|
import {
|
|
181
184
|
type WordReviewEditorRuntime,
|
|
182
185
|
persistAndExport as persistAndExportFromBoundary,
|
|
@@ -869,6 +872,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
869
872
|
() => deriveVisibleWorkflowBlockedRails(snapshot.surface, workflowMarkupSnapshot),
|
|
870
873
|
[snapshot.surface, workflowMarkupSnapshot],
|
|
871
874
|
);
|
|
875
|
+
const workflowLockedZones = useMemo(
|
|
876
|
+
() => deriveVisibleWorkflowLockedZones(snapshot.surface, workflowMarkupSnapshot),
|
|
877
|
+
[snapshot.surface, workflowMarkupSnapshot],
|
|
878
|
+
);
|
|
872
879
|
const canonicalDocument = useMemo(
|
|
873
880
|
() => (runtime ? runtime.getCanonicalDocument() : loadingSessionState.canonicalDocument),
|
|
874
881
|
[loadingSessionState.canonicalDocument, runtime, snapshot.revisionToken],
|
|
@@ -902,6 +909,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
902
909
|
activeRuntime.setViewMode(effectiveViewMode);
|
|
903
910
|
}, [activeRuntime, effectiveViewMode]);
|
|
904
911
|
|
|
912
|
+
useEffect(() => {
|
|
913
|
+
activeRuntime.setDocumentMode(suggestionsEnabled ? "suggesting" : "editing");
|
|
914
|
+
}, [activeRuntime, suggestionsEnabled]);
|
|
915
|
+
|
|
905
916
|
const markCurrentSectionForReview = useCallback((input?: {
|
|
906
917
|
sectionIndex?: number;
|
|
907
918
|
label?: string;
|
|
@@ -971,10 +982,6 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
971
982
|
[activeReviewQueueItemId, activeRuntime, reviewQueueSnapshot],
|
|
972
983
|
);
|
|
973
984
|
|
|
974
|
-
useEffect(() => {
|
|
975
|
-
activeRuntime.setDocumentMode(suggestionsEnabled ? "suggesting" : "editing");
|
|
976
|
-
}, [activeRuntime, suggestionsEnabled]);
|
|
977
|
-
|
|
978
985
|
useEffect(() => {
|
|
979
986
|
runtimeViewStateSeedRef.current = {
|
|
980
987
|
workspaceMode: viewState.workspaceMode,
|
|
@@ -2217,6 +2224,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2217
2224
|
workflowScopes={workflowScopeSnapshot?.scopes}
|
|
2218
2225
|
workflowCandidates={workflowScopeSnapshot?.candidates}
|
|
2219
2226
|
workflowBlockedReasons={workflowBlockedRails}
|
|
2227
|
+
workflowLockedZones={workflowLockedZones}
|
|
2220
2228
|
activeWorkflowWorkItemId={workflowScopeSnapshot?.activeWorkItemId ?? null}
|
|
2221
2229
|
activeWorkflowScopeIds={workflowScopeSnapshot?.activeWorkItem?.scopeIds ?? []}
|
|
2222
2230
|
workflowMetadata={workflowMarkupSnapshot?.metadata}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
SelectionSnapshot,
|
|
8
8
|
WorkflowBlockedCommandReason,
|
|
9
9
|
WorkflowCandidateRange,
|
|
10
|
+
WorkflowLockedZone,
|
|
10
11
|
WorkflowMetadataMarkup,
|
|
11
12
|
WorkflowScope,
|
|
12
13
|
} from "../api/public-types.ts";
|
|
@@ -53,6 +54,7 @@ export interface EditorSurfaceControllerProps {
|
|
|
53
54
|
workflowScopes?: readonly WorkflowScope[];
|
|
54
55
|
workflowCandidates?: readonly WorkflowCandidateRange[];
|
|
55
56
|
workflowBlockedReasons?: readonly WorkflowBlockedCommandReason[];
|
|
57
|
+
workflowLockedZones?: readonly WorkflowLockedZone[];
|
|
56
58
|
activeWorkflowWorkItemId?: string | null;
|
|
57
59
|
activeWorkflowScopeIds?: readonly string[];
|
|
58
60
|
workflowMetadata?: readonly WorkflowMetadataMarkup[];
|
|
@@ -357,7 +357,10 @@ export function buildStructureContextSelectionToolModel(
|
|
|
357
357
|
kind: "structure-context",
|
|
358
358
|
structureKind: "table",
|
|
359
359
|
badges,
|
|
360
|
-
tableStyles: input.styleCatalog.tables
|
|
360
|
+
tableStyles: input.styleCatalog.tables.map((entry) => ({
|
|
361
|
+
styleId: entry.styleId,
|
|
362
|
+
displayName: entry.displayName,
|
|
363
|
+
})),
|
|
361
364
|
activeTable: input.activeTableContext ?? null,
|
|
362
365
|
canMutate,
|
|
363
366
|
...(disabledReason ? { disabledReason } : {}),
|