@beyondwork/docx-react-component 1.0.18 → 1.0.20
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 +8 -2
- package/package.json +24 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +710 -4
- package/src/api/session-state.ts +60 -0
- package/src/core/commands/formatting-commands.ts +2 -1
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +19 -3
- package/src/core/commands/list-commands.ts +231 -36
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/section-layout-commands.ts +680 -0
- package/src/core/commands/style-commands.ts +262 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +4 -1
- package/src/index.ts +51 -0
- package/src/io/docx-session.ts +623 -56
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +285 -8
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +144 -32
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +452 -22
- package/src/io/ooxml/parse-headers-footers.ts +657 -29
- package/src/io/ooxml/parse-inline-media.ts +30 -0
- package/src/io/ooxml/parse-main-document.ts +807 -20
- package/src/io/ooxml/parse-numbering.ts +7 -0
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +25 -0
- package/src/io/ooxml/parse-styles.ts +463 -0
- package/src/io/ooxml/parse-theme.ts +32 -0
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +250 -4
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +87 -2
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +603 -0
- package/src/runtime/document-runtime.ts +1754 -78
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/numbering-prefix.ts +47 -26
- package/src/runtime/page-layout-estimation.ts +212 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +9 -0
- package/src/runtime/session-capabilities.ts +35 -3
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +324 -36
- package/src/runtime/table-schema.ts +89 -7
- package/src/runtime/view-state.ts +477 -0
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +2469 -1344
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1422 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +51 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/headless/selection-helpers.ts +20 -0
- package/src/ui/headless/selection-toolbar-model.ts +22 -0
- package/src/ui/headless/use-editor-keyboard.ts +6 -1
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +150 -14
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +46 -7
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +3 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +186 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +191 -68
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +528 -85
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
- package/src/ui-tailwind/index.ts +2 -1
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +8 -8
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +127 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +829 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +1238 -42
- package/src/validation/compatibility-engine.ts +119 -24
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +707 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
EditorStoryTarget,
|
|
2
3
|
EditorSurfaceSnapshot,
|
|
4
|
+
SecondaryStorySurface,
|
|
3
5
|
SurfaceBlockSnapshot,
|
|
4
6
|
SurfaceInlineSegment,
|
|
5
7
|
SurfaceTableCellSnapshot,
|
|
@@ -20,6 +22,7 @@ import type {
|
|
|
20
22
|
SdtNode,
|
|
21
23
|
ShapeNode,
|
|
22
24
|
SmartArtPreviewNode,
|
|
25
|
+
TableCellBorders,
|
|
23
26
|
TableNode,
|
|
24
27
|
TextMark,
|
|
25
28
|
VmlShapeNode,
|
|
@@ -29,10 +32,16 @@ import {
|
|
|
29
32
|
describeOpaqueFragment,
|
|
30
33
|
getOpaqueFragment,
|
|
31
34
|
} from "../preservation/store.ts";
|
|
35
|
+
import { getStoryBlocks } from "./story-targeting.ts";
|
|
32
36
|
import {
|
|
33
37
|
createNumberingPrefixResolver,
|
|
34
38
|
type NumberingPrefixResolver,
|
|
35
39
|
} from "./numbering-prefix.ts";
|
|
40
|
+
import {
|
|
41
|
+
collectSectionContexts,
|
|
42
|
+
findHeaderFooterDocumentEntry,
|
|
43
|
+
resolveSectionVariants,
|
|
44
|
+
} from "./story-context.ts";
|
|
36
45
|
|
|
37
46
|
interface ParagraphAccumulator {
|
|
38
47
|
blockId: string;
|
|
@@ -42,14 +51,20 @@ interface ParagraphAccumulator {
|
|
|
42
51
|
styleId?: string;
|
|
43
52
|
numbering?: ParagraphNode["numbering"];
|
|
44
53
|
numberingPrefix?: string;
|
|
54
|
+
numberingSuffix?: "tab" | "space" | "nothing";
|
|
55
|
+
contextualSpacing?: boolean;
|
|
45
56
|
segments: SurfaceInlineSegment[];
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
export function createEditorSurfaceSnapshot(
|
|
49
60
|
document: CanonicalDocumentEnvelope,
|
|
50
61
|
_selection: SelectionSnapshot,
|
|
62
|
+
activeStory: EditorStoryTarget = { kind: "main" },
|
|
51
63
|
): EditorSurfaceSnapshot {
|
|
52
|
-
const root = normalizeDocumentRoot(
|
|
64
|
+
const root = normalizeDocumentRoot({
|
|
65
|
+
type: "doc",
|
|
66
|
+
children: [...getStoryBlocks(document, activeStory)],
|
|
67
|
+
});
|
|
53
68
|
const blocks: SurfaceBlockSnapshot[] = [];
|
|
54
69
|
const lockedFragmentIds: string[] = [];
|
|
55
70
|
const numberingPrefixResolver = createNumberingPrefixResolver(document.numbering);
|
|
@@ -79,13 +94,14 @@ export function createEditorSurfaceSnapshot(
|
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
const secondaryStories = createSecondaryStorySurfaces(document);
|
|
83
98
|
|
|
84
99
|
return {
|
|
85
100
|
storySize: cursor,
|
|
86
101
|
plainText: createPlainText(blocks),
|
|
87
102
|
blocks,
|
|
88
103
|
lockedFragmentIds,
|
|
104
|
+
secondaryStories,
|
|
89
105
|
};
|
|
90
106
|
}
|
|
91
107
|
|
|
@@ -263,16 +279,27 @@ function createTableBlock(
|
|
|
263
279
|
lockedFragmentIds.push(...result.lockedFragmentIds);
|
|
264
280
|
innerCursor = result.nextCursor;
|
|
265
281
|
}
|
|
282
|
+
const cellBorders = resolveCellBorderStyles(cell.borders);
|
|
266
283
|
cells.push({
|
|
267
284
|
gridSpan: cell.gridSpan ?? 1,
|
|
268
285
|
verticalMerge: cell.verticalMerge ?? null,
|
|
269
286
|
colspan: cell.gridSpan ?? 1,
|
|
270
287
|
rowspan: rowSpans.get(`${rowIndex}:${cellIndex}`) ?? 1,
|
|
271
288
|
...(cell.shading?.fill ? { backgroundColor: `#${cell.shading.fill}` } : {}),
|
|
289
|
+
...(cell.verticalAlign ? { verticalAlign: cell.verticalAlign } : {}),
|
|
290
|
+
...(cellBorders.borderTop ? { borderTop: cellBorders.borderTop } : {}),
|
|
291
|
+
...(cellBorders.borderRight ? { borderRight: cellBorders.borderRight } : {}),
|
|
292
|
+
...(cellBorders.borderBottom ? { borderBottom: cellBorders.borderBottom } : {}),
|
|
293
|
+
...(cellBorders.borderLeft ? { borderLeft: cellBorders.borderLeft } : {}),
|
|
272
294
|
content: cellContent,
|
|
273
295
|
});
|
|
274
296
|
}
|
|
275
|
-
rows.push({
|
|
297
|
+
rows.push({
|
|
298
|
+
cells,
|
|
299
|
+
...(row.height !== undefined ? { height: row.height } : {}),
|
|
300
|
+
...(row.heightRule ? { heightRule: row.heightRule } : {}),
|
|
301
|
+
...(row.isHeader ? { isHeader: row.isHeader } : {}),
|
|
302
|
+
});
|
|
276
303
|
}
|
|
277
304
|
|
|
278
305
|
return {
|
|
@@ -283,6 +310,8 @@ function createTableBlock(
|
|
|
283
310
|
to: innerCursor,
|
|
284
311
|
styleId: table.styleId,
|
|
285
312
|
gridColumns: table.gridColumns,
|
|
313
|
+
...(table.alignment ? { alignment: table.alignment } : {}),
|
|
314
|
+
...(table.tblLook ? { tblLook: table.tblLook } : {}),
|
|
286
315
|
rows,
|
|
287
316
|
},
|
|
288
317
|
lockedFragmentIds,
|
|
@@ -339,6 +368,23 @@ function computeTableRowSpans(table: TableNode): Map<string, number> {
|
|
|
339
368
|
return rowSpans;
|
|
340
369
|
}
|
|
341
370
|
|
|
371
|
+
function resolveCellBorderStyles(
|
|
372
|
+
borders: TableCellBorders | undefined,
|
|
373
|
+
): { borderTop?: string; borderRight?: string; borderBottom?: string; borderLeft?: string } {
|
|
374
|
+
if (!borders) return {};
|
|
375
|
+
const result: { borderTop?: string; borderRight?: string; borderBottom?: string; borderLeft?: string } = {};
|
|
376
|
+
const sides = [["top", "borderTop"], ["right", "borderRight"], ["bottom", "borderBottom"], ["left", "borderLeft"]] as const;
|
|
377
|
+
for (const [side, key] of sides) {
|
|
378
|
+
const spec = borders[side];
|
|
379
|
+
if (!spec || spec.value === "none" || spec.value === "nil") continue;
|
|
380
|
+
const width = spec.size ? `${Math.max(1, Math.round(spec.size / 8))}px` : "1px";
|
|
381
|
+
const style = spec.value === "double" ? "double" : spec.value === "dashed" || spec.value === "dashSmallGap" ? "dashed" : spec.value === "dotted" ? "dotted" : "solid";
|
|
382
|
+
const color = spec.color && spec.color !== "auto" ? `#${spec.color}` : "currentColor";
|
|
383
|
+
result[key] = `${width} ${style} ${color}`;
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
|
|
342
388
|
function createSdtBlock(
|
|
343
389
|
sdtIndex: number,
|
|
344
390
|
block: SdtNode,
|
|
@@ -381,6 +427,11 @@ function createSdtBlock(
|
|
|
381
427
|
...(block.properties.alias ? { alias: block.properties.alias } : {}),
|
|
382
428
|
...(block.properties.tag ? { tag: block.properties.tag } : {}),
|
|
383
429
|
...(block.properties.lock ? { lock: block.properties.lock } : {}),
|
|
430
|
+
...(block.properties.checkbox ? { checkboxChecked: block.properties.checkbox.checked } : {}),
|
|
431
|
+
...(block.properties.datePicker?.fullDate ? { dateValue: block.properties.datePicker.fullDate } : {}),
|
|
432
|
+
...(block.properties.dropdownList ? { dropdownItems: block.properties.dropdownList } : {}),
|
|
433
|
+
...(block.properties.comboBox ? { comboBoxItems: block.properties.comboBox } : {}),
|
|
434
|
+
...(block.properties.showingPlcHdr ? { showingPlcHdr: true } : {}),
|
|
384
435
|
children,
|
|
385
436
|
},
|
|
386
437
|
lockedFragmentIds,
|
|
@@ -407,13 +458,21 @@ function createParagraphBlock(
|
|
|
407
458
|
...(paragraph.styleId ? { styleId: paragraph.styleId } : {}),
|
|
408
459
|
...(paragraph.numbering ? { numbering: paragraph.numbering } : {}),
|
|
409
460
|
...(paragraph.numbering
|
|
410
|
-
? {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
461
|
+
? (() => {
|
|
462
|
+
const detailed = numberingPrefixResolver.resolveDetailed(paragraph.numbering);
|
|
463
|
+
return detailed
|
|
464
|
+
? {
|
|
465
|
+
numberingPrefix: detailed.text,
|
|
466
|
+
...(detailed.suffix ? { numberingSuffix: detailed.suffix } : {}),
|
|
467
|
+
}
|
|
468
|
+
: {};
|
|
469
|
+
})()
|
|
414
470
|
: {}),
|
|
415
471
|
...(paragraph.alignment ? { alignment: paragraph.alignment } : {}),
|
|
416
472
|
...(paragraph.spacing ? { spacing: paragraph.spacing } : {}),
|
|
473
|
+
...(paragraph.contextualSpacing !== undefined
|
|
474
|
+
? { contextualSpacing: paragraph.contextualSpacing }
|
|
475
|
+
: {}),
|
|
417
476
|
...(paragraph.indentation ? { indentation: paragraph.indentation } : {}),
|
|
418
477
|
...(paragraph.borders ? { borders: paragraph.borders } : {}),
|
|
419
478
|
...(paragraph.shading ? { shading: paragraph.shading } : {}),
|
|
@@ -425,6 +484,7 @@ function createParagraphBlock(
|
|
|
425
484
|
...(paragraph.pageBreakBefore ? { pageBreakBefore: true } : {}),
|
|
426
485
|
...(paragraph.outlineLevel !== undefined ? { outlineLevel: paragraph.outlineLevel } : {}),
|
|
427
486
|
...(paragraph.bidi ? { bidi: true } : {}),
|
|
487
|
+
...(paragraph.suppressLineNumbers ? { suppressLineNumbers: true } : {}),
|
|
428
488
|
segments: [],
|
|
429
489
|
};
|
|
430
490
|
const lockedFragmentIds: string[] = [];
|
|
@@ -530,20 +590,22 @@ function appendInlineSegments(
|
|
|
530
590
|
preview?.detail ??
|
|
531
591
|
descriptor?.detail ??
|
|
532
592
|
"Locked whole-unit to keep unsupported inline OOXML intact through export.",
|
|
593
|
+
...(preview?.presentation ? { presentation: preview.presentation } : {}),
|
|
533
594
|
state: "locked-preserve-only",
|
|
534
595
|
});
|
|
535
596
|
return { nextCursor: start + 1, lockedFragmentIds: [node.fragmentId] };
|
|
536
597
|
}
|
|
537
598
|
case "chart_preview":
|
|
538
|
-
return appendComplexPreviewSegment(paragraph, node, start, "
|
|
599
|
+
return appendComplexPreviewSegment(paragraph, node, start, "Embedded chart", createChartDetail(node));
|
|
539
600
|
case "smartart_preview":
|
|
540
|
-
return appendComplexPreviewSegment(paragraph, node, start, "SmartArt", createSmartArtDetail(node));
|
|
601
|
+
return appendComplexPreviewSegment(paragraph, node, start, "SmartArt diagram", createSmartArtDetail(node));
|
|
541
602
|
case "shape":
|
|
542
|
-
return appendComplexPreviewSegment(paragraph, node, start,
|
|
603
|
+
return appendComplexPreviewSegment(paragraph, node, start,
|
|
604
|
+
node.isTextBox ? "Text box" : "Drawing shape", createShapeDetail(node));
|
|
543
605
|
case "wordart":
|
|
544
606
|
return appendComplexPreviewSegment(paragraph, node, start, "WordArt", createWordArtDetail(node));
|
|
545
607
|
case "vml_shape":
|
|
546
|
-
return appendComplexPreviewSegment(paragraph, node, start, "VML
|
|
608
|
+
return appendComplexPreviewSegment(paragraph, node, start, "Legacy VML drawing", createVmlDetail(node));
|
|
547
609
|
case "symbol":
|
|
548
610
|
paragraph.segments.push({
|
|
549
611
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
@@ -569,14 +631,20 @@ function appendInlineSegments(
|
|
|
569
631
|
case "footnote_ref":
|
|
570
632
|
paragraph.segments.push({
|
|
571
633
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
572
|
-
kind: "
|
|
634
|
+
kind: "note_ref",
|
|
573
635
|
from: start,
|
|
574
636
|
to: start + 1,
|
|
575
|
-
|
|
576
|
-
|
|
637
|
+
noteKind: node.noteKind ?? "footnote",
|
|
638
|
+
noteId: node.noteId ?? "",
|
|
639
|
+
label: node.noteId ?? "*",
|
|
577
640
|
});
|
|
578
641
|
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
579
642
|
case "field": {
|
|
643
|
+
const isSupportedField =
|
|
644
|
+
node.fieldFamily === "REF" ||
|
|
645
|
+
node.fieldFamily === "PAGEREF" ||
|
|
646
|
+
node.fieldFamily === "NOTEREF" ||
|
|
647
|
+
node.fieldFamily === "TOC";
|
|
580
648
|
if (node.children && node.children.length > 0) {
|
|
581
649
|
let cursor = start;
|
|
582
650
|
const lockedIds: string[] = [];
|
|
@@ -587,6 +655,25 @@ function appendInlineSegments(
|
|
|
587
655
|
}
|
|
588
656
|
return { nextCursor: cursor, lockedFragmentIds: lockedIds };
|
|
589
657
|
}
|
|
658
|
+
if (isSupportedField) {
|
|
659
|
+
// Supported field with no resolved content — show as field chip
|
|
660
|
+
const fieldLabel =
|
|
661
|
+
node.fieldFamily === "TOC"
|
|
662
|
+
? "Table of Contents"
|
|
663
|
+
: `${node.fieldFamily ?? "Field"}: ${node.fieldTarget ?? node.instruction.trim()}`;
|
|
664
|
+
paragraph.segments.push({
|
|
665
|
+
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
666
|
+
kind: "field_ref",
|
|
667
|
+
from: start,
|
|
668
|
+
to: start + 1,
|
|
669
|
+
fieldFamily: node.fieldFamily!,
|
|
670
|
+
fieldTarget: node.fieldTarget,
|
|
671
|
+
instruction: node.instruction,
|
|
672
|
+
refreshStatus: node.refreshStatus ?? "stale",
|
|
673
|
+
label: fieldLabel,
|
|
674
|
+
} as SurfaceInlineSegment);
|
|
675
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
676
|
+
}
|
|
590
677
|
paragraph.segments.push({
|
|
591
678
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
592
679
|
kind: "opaque_inline",
|
|
@@ -595,7 +682,7 @@ function appendInlineSegments(
|
|
|
595
682
|
fragmentId: "",
|
|
596
683
|
warningId: "",
|
|
597
684
|
label: "Field",
|
|
598
|
-
detail:
|
|
685
|
+
detail: `Preserve-only field: ${node.instruction.trim()}`,
|
|
599
686
|
state: "locked-preserve-only",
|
|
600
687
|
});
|
|
601
688
|
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
@@ -631,38 +718,54 @@ function appendComplexPreviewSegment(
|
|
|
631
718
|
}
|
|
632
719
|
|
|
633
720
|
function createChartDetail(node: ChartPreviewNode): string {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
721
|
+
const parts = ["Embedded chart."];
|
|
722
|
+
if (node.previewMediaId) {
|
|
723
|
+
parts.push(`Preview available via fallback image (${node.previewMediaId}).`);
|
|
724
|
+
} else {
|
|
725
|
+
parts.push("No fallback preview image found; chart data preserved in package.");
|
|
726
|
+
}
|
|
727
|
+
parts.push("Edit in Word to modify chart data. Original DrawingML preserved for lossless export.");
|
|
728
|
+
return parts.join(" ");
|
|
637
729
|
}
|
|
638
730
|
|
|
639
731
|
function createSmartArtDetail(node: SmartArtPreviewNode): string {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
732
|
+
const parts = ["SmartArt diagram."];
|
|
733
|
+
if (node.previewMediaId) {
|
|
734
|
+
parts.push(`Preview available via fallback image (${node.previewMediaId}).`);
|
|
735
|
+
} else {
|
|
736
|
+
parts.push("No fallback preview image found; diagram structure preserved in package.");
|
|
737
|
+
}
|
|
738
|
+
parts.push("Edit in Word to modify diagram layout and content. Original DrawingML preserved for lossless export.");
|
|
739
|
+
return parts.join(" ");
|
|
643
740
|
}
|
|
644
741
|
|
|
645
742
|
function createShapeDetail(node: ShapeNode): string {
|
|
646
|
-
|
|
743
|
+
if (node.isTextBox) {
|
|
744
|
+
const parts = ["Text box."];
|
|
745
|
+
if (node.text) parts.push(`Content: "${node.text}".`);
|
|
746
|
+
parts.push("Text content is visible; formatting and geometry preserved for export.");
|
|
747
|
+
return parts.join(" ");
|
|
748
|
+
}
|
|
749
|
+
const parts = ["Drawing shape."];
|
|
647
750
|
if (node.geometry) parts.push(`Geometry: ${node.geometry}.`);
|
|
648
|
-
if (node.text) parts.push(`Text: "${node.text}".`);
|
|
649
|
-
parts.push("Original
|
|
751
|
+
if (node.text) parts.push(`Text content: "${node.text}".`);
|
|
752
|
+
parts.push("Visual geometry is preview-only. Original DrawingML preserved for export.");
|
|
650
753
|
return parts.join(" ");
|
|
651
754
|
}
|
|
652
755
|
|
|
653
756
|
function createWordArtDetail(node: WordArtNode): string {
|
|
654
|
-
const parts = ["WordArt
|
|
757
|
+
const parts = ["WordArt decorative text."];
|
|
655
758
|
if (node.text) parts.push(`Text: "${node.text}".`);
|
|
656
759
|
if (node.geometry) parts.push(`Effect: ${node.geometry}.`);
|
|
657
|
-
parts.push("Original
|
|
760
|
+
parts.push("Text effect is preview-only. Edit in Word for full formatting. Original DrawingML preserved for export.");
|
|
658
761
|
return parts.join(" ");
|
|
659
762
|
}
|
|
660
763
|
|
|
661
764
|
function createVmlDetail(node: VmlShapeNode): string {
|
|
662
|
-
const parts = ["VML
|
|
765
|
+
const parts = ["Legacy VML drawing."];
|
|
663
766
|
if (node.shapeType) parts.push(`Type: ${node.shapeType}.`);
|
|
664
|
-
if (node.text) parts.push(`Text: "${node.text}".`);
|
|
665
|
-
parts.push("
|
|
767
|
+
if (node.text) parts.push(`Text content: "${node.text}".`);
|
|
768
|
+
parts.push("VML content is preview-only. Edit in Word for full control. Original VML XML preserved for export.");
|
|
666
769
|
return parts.join(" ");
|
|
667
770
|
}
|
|
668
771
|
|
|
@@ -717,6 +820,131 @@ function createPlainText(
|
|
|
717
820
|
return text.join("");
|
|
718
821
|
}
|
|
719
822
|
|
|
823
|
+
function createSecondaryStorySurfaces(
|
|
824
|
+
document: CanonicalDocumentEnvelope,
|
|
825
|
+
): SecondaryStorySurface[] {
|
|
826
|
+
const surfaces: SecondaryStorySurface[] = [];
|
|
827
|
+
const subParts = document.subParts;
|
|
828
|
+
if (!subParts) {
|
|
829
|
+
return surfaces;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const numberingPrefixResolver = createNumberingPrefixResolver(document.numbering);
|
|
833
|
+
|
|
834
|
+
for (const section of collectSectionContexts(document)) {
|
|
835
|
+
const headerVariants = resolveSectionVariants(
|
|
836
|
+
"header",
|
|
837
|
+
section.index,
|
|
838
|
+
section.properties?.headerReferences,
|
|
839
|
+
subParts.headers ?? [],
|
|
840
|
+
);
|
|
841
|
+
for (const headerVariant of headerVariants) {
|
|
842
|
+
const target: EditorStoryTarget = {
|
|
843
|
+
kind: "header",
|
|
844
|
+
relationshipId: headerVariant.relationshipId,
|
|
845
|
+
variant: headerVariant.variant,
|
|
846
|
+
sectionIndex: section.index,
|
|
847
|
+
};
|
|
848
|
+
const header = findHeaderFooterDocumentEntry(document, target);
|
|
849
|
+
if (!header) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
surfaces.push(
|
|
853
|
+
createStorySurface(
|
|
854
|
+
target,
|
|
855
|
+
`Header · ${headerVariant.variant}`,
|
|
856
|
+
header.blocks,
|
|
857
|
+
document,
|
|
858
|
+
numberingPrefixResolver,
|
|
859
|
+
),
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const footerVariants = resolveSectionVariants(
|
|
864
|
+
"footer",
|
|
865
|
+
section.index,
|
|
866
|
+
section.properties?.footerReferences,
|
|
867
|
+
subParts.footers ?? [],
|
|
868
|
+
);
|
|
869
|
+
for (const footerVariant of footerVariants) {
|
|
870
|
+
const target: EditorStoryTarget = {
|
|
871
|
+
kind: "footer",
|
|
872
|
+
relationshipId: footerVariant.relationshipId,
|
|
873
|
+
variant: footerVariant.variant,
|
|
874
|
+
sectionIndex: section.index,
|
|
875
|
+
};
|
|
876
|
+
const footer = findHeaderFooterDocumentEntry(document, target);
|
|
877
|
+
if (!footer) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
surfaces.push(
|
|
881
|
+
createStorySurface(
|
|
882
|
+
target,
|
|
883
|
+
`Footer · ${footerVariant.variant}`,
|
|
884
|
+
footer.blocks,
|
|
885
|
+
document,
|
|
886
|
+
numberingPrefixResolver,
|
|
887
|
+
),
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const footnotes = Object.values(subParts.footnoteCollection?.footnotes ?? {}).sort(compareNoteIds);
|
|
893
|
+
for (const note of footnotes) {
|
|
894
|
+
const target: EditorStoryTarget = { kind: "footnote", noteId: note.noteId };
|
|
895
|
+
surfaces.push(createStorySurface(target, `Footnote ${note.noteId}`, note.blocks, document, numberingPrefixResolver));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const endnotes = Object.values(subParts.footnoteCollection?.endnotes ?? {}).sort(compareNoteIds);
|
|
899
|
+
for (const note of endnotes) {
|
|
900
|
+
const target: EditorStoryTarget = { kind: "endnote", noteId: note.noteId };
|
|
901
|
+
surfaces.push(createStorySurface(target, `Endnote ${note.noteId}`, note.blocks, document, numberingPrefixResolver));
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return surfaces;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function createStorySurface(
|
|
908
|
+
target: EditorStoryTarget,
|
|
909
|
+
label: string,
|
|
910
|
+
blocks: readonly BlockNode[],
|
|
911
|
+
document: CanonicalDocumentEnvelope,
|
|
912
|
+
numberingPrefixResolver: NumberingPrefixResolver,
|
|
913
|
+
): SecondaryStorySurface {
|
|
914
|
+
const surfaceBlocks: SurfaceBlockSnapshot[] = [];
|
|
915
|
+
let cursor = 0;
|
|
916
|
+
const counters = {
|
|
917
|
+
paragraph: 0,
|
|
918
|
+
table: 0,
|
|
919
|
+
opaque: 0,
|
|
920
|
+
sdt: 0,
|
|
921
|
+
customXml: 0,
|
|
922
|
+
altChunk: 0,
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
926
|
+
const surfaceBlock = createSurfaceBlock(
|
|
927
|
+
blocks[index],
|
|
928
|
+
document,
|
|
929
|
+
cursor,
|
|
930
|
+
counters,
|
|
931
|
+
numberingPrefixResolver,
|
|
932
|
+
);
|
|
933
|
+
surfaceBlocks.push(surfaceBlock.block);
|
|
934
|
+
cursor = surfaceBlock.nextCursor;
|
|
935
|
+
if (index < blocks.length - 1 && blocks[index + 1]?.type === "paragraph") {
|
|
936
|
+
cursor += 1;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return {
|
|
941
|
+
target,
|
|
942
|
+
label,
|
|
943
|
+
storySize: cursor,
|
|
944
|
+
blocks: surfaceBlocks,
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
720
948
|
function createSecondaryStoryPreviewBlocks(
|
|
721
949
|
document: CanonicalDocumentEnvelope,
|
|
722
950
|
cursor: number,
|
|
@@ -856,15 +1084,16 @@ function summarizePreviewInline(node: InlineNode): string {
|
|
|
856
1084
|
case "column_break":
|
|
857
1085
|
return "[Column break]";
|
|
858
1086
|
case "chart_preview":
|
|
859
|
-
return "[
|
|
1087
|
+
return "[Embedded chart]";
|
|
860
1088
|
case "smartart_preview":
|
|
861
|
-
return "[SmartArt]";
|
|
1089
|
+
return "[SmartArt diagram]";
|
|
862
1090
|
case "shape":
|
|
863
|
-
|
|
1091
|
+
if (node.isTextBox && node.text) return `[Text box: ${node.text}]`;
|
|
1092
|
+
return node.text ? `[Shape: ${node.text}]` : "[Drawing shape]";
|
|
864
1093
|
case "wordart":
|
|
865
1094
|
return node.text ? `[WordArt: ${node.text}]` : "[WordArt]";
|
|
866
1095
|
case "vml_shape":
|
|
867
|
-
return node.text ? `[VML: ${node.text}]` : "[VML
|
|
1096
|
+
return node.text ? `[VML: ${node.text}]` : "[Legacy VML drawing]";
|
|
868
1097
|
}
|
|
869
1098
|
}
|
|
870
1099
|
|
|
@@ -887,7 +1116,51 @@ function toSurfaceTabStop(
|
|
|
887
1116
|
|
|
888
1117
|
function describePreservedInlinePreview(
|
|
889
1118
|
payloadReference: string,
|
|
890
|
-
): {
|
|
1119
|
+
): {
|
|
1120
|
+
label: string;
|
|
1121
|
+
detail: string;
|
|
1122
|
+
presentation?: "inline-chip" | "quiet-marker";
|
|
1123
|
+
} | null {
|
|
1124
|
+
if (/\b(?:w:)?proofErr\b/u.test(payloadReference)) {
|
|
1125
|
+
const proofType = /\bw:type="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1126
|
+
return {
|
|
1127
|
+
label: "Proofing marker",
|
|
1128
|
+
detail:
|
|
1129
|
+
proofType && proofType.trim().length > 0
|
|
1130
|
+
? `Word proofing marker (${proofType}) preserved for export safety.`
|
|
1131
|
+
: "Word proofing marker preserved for export safety.",
|
|
1132
|
+
presentation: "quiet-marker",
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (/\b(?:w:)?lastRenderedPageBreak\b/u.test(payloadReference)) {
|
|
1137
|
+
return {
|
|
1138
|
+
label: "Rendered page break",
|
|
1139
|
+
detail: "Word rendered page-break marker preserved for export safety.",
|
|
1140
|
+
presentation: "quiet-marker",
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (/\b(?:w:)?permStart\b/u.test(payloadReference)) {
|
|
1145
|
+
const editorGroup = /\bw:edGrp="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
1146
|
+
return {
|
|
1147
|
+
label: "Protected range start",
|
|
1148
|
+
detail:
|
|
1149
|
+
editorGroup && editorGroup.trim().length > 0
|
|
1150
|
+
? `Protected range start for ${editorGroup} preserved for export safety.`
|
|
1151
|
+
: "Protected range start preserved for export safety.",
|
|
1152
|
+
presentation: "quiet-marker",
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (/\b(?:w:)?permEnd\b/u.test(payloadReference)) {
|
|
1157
|
+
return {
|
|
1158
|
+
label: "Protected range end",
|
|
1159
|
+
detail: "Protected range end preserved for export safety.",
|
|
1160
|
+
presentation: "quiet-marker",
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
891
1164
|
if (/\b(?:w:)?bookmarkStart\b/u.test(payloadReference)) {
|
|
892
1165
|
const name = /\bw:name="([^"]+)"/u.exec(payloadReference)?.[1];
|
|
893
1166
|
return {
|
|
@@ -912,8 +1185,15 @@ function describePreservedInlinePreview(
|
|
|
912
1185
|
.join("")
|
|
913
1186
|
.trim();
|
|
914
1187
|
const instruction = (simpleInstruction ?? complexInstruction ?? "").trim();
|
|
1188
|
+
const family = /^([A-Z]+)/i.exec(instruction)?.[1]?.toUpperCase();
|
|
1189
|
+
const label =
|
|
1190
|
+
family === "TOC"
|
|
1191
|
+
? "Table of Contents field"
|
|
1192
|
+
: family
|
|
1193
|
+
? `${family} field`
|
|
1194
|
+
: "Field";
|
|
915
1195
|
return {
|
|
916
|
-
label
|
|
1196
|
+
label,
|
|
917
1197
|
detail:
|
|
918
1198
|
instruction.length > 0
|
|
919
1199
|
? `Read-only field preserved for export safety. Instruction: ${instruction}.`
|
|
@@ -955,6 +1235,8 @@ function cloneMarks(marks: TextMark[]): {
|
|
|
955
1235
|
fontSize?: number;
|
|
956
1236
|
textColor?: string;
|
|
957
1237
|
} = {};
|
|
1238
|
+
let shadingColor: string | undefined;
|
|
1239
|
+
let highlightColor: string | undefined;
|
|
958
1240
|
for (const mark of marks) {
|
|
959
1241
|
switch (mark.type) {
|
|
960
1242
|
case "bold":
|
|
@@ -973,7 +1255,10 @@ function cloneMarks(marks: TextMark[]): {
|
|
|
973
1255
|
else if (mark.val < 0) supported.push("subscript");
|
|
974
1256
|
break;
|
|
975
1257
|
case "backgroundColor":
|
|
976
|
-
|
|
1258
|
+
shadingColor = mark.color;
|
|
1259
|
+
break;
|
|
1260
|
+
case "highlight":
|
|
1261
|
+
highlightColor = mark.color;
|
|
977
1262
|
break;
|
|
978
1263
|
case "charSpacing":
|
|
979
1264
|
attrs.charSpacing = mark.val;
|
|
@@ -1004,6 +1289,9 @@ function cloneMarks(marks: TextMark[]): {
|
|
|
1004
1289
|
break;
|
|
1005
1290
|
}
|
|
1006
1291
|
}
|
|
1292
|
+
if (highlightColor || shadingColor) {
|
|
1293
|
+
attrs.backgroundColor = highlightColor ?? shadingColor;
|
|
1294
|
+
}
|
|
1007
1295
|
const hasAttrs = Object.keys(attrs).length > 0;
|
|
1008
1296
|
return hasAttrs ? { marks: supported, markAttrs: attrs } : { marks: supported };
|
|
1009
1297
|
}
|