@beyondwork/docx-react-component 1.0.66 → 1.0.69
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 +75 -931
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4795
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
|
@@ -0,0 +1,1834 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loadDocxSessionAsync` — session-layer owner of the async load path.
|
|
3
|
+
*
|
|
4
|
+
* Slice 5e-7.e.1 relocates the body of `loadDocxEditorSessionAsync` from
|
|
5
|
+
* `src/io/docx-session.ts` into this module. The legacy symbol in
|
|
6
|
+
* `docx-session.ts` now delegates here in one function call so existing
|
|
7
|
+
* consumers (tests, services/debug, `DocxSession.open`) observe
|
|
8
|
+
* byte-identical behavior while the orchestration lives under
|
|
9
|
+
* `src/session/**`.
|
|
10
|
+
*
|
|
11
|
+
* Slice 5e-7.f: the export pipeline (`runStatefulExport` at
|
|
12
|
+
* `src/session/export/stateful-export.ts`) is imported directly —
|
|
13
|
+
* both load and export now live under `src/session/**`, so the earlier
|
|
14
|
+
* `LoaderDependencies` injection pattern was removed. The
|
|
15
|
+
* `session.ts → docx-session.ts` debt exception remains the only
|
|
16
|
+
* cross-layer transit; the loader body is fully in-session.
|
|
17
|
+
*
|
|
18
|
+
* Runtime-type seams:
|
|
19
|
+
* - `laycacheEnvelope` is `unknown` on `LoadDocxEditorSessionAsyncOptions`
|
|
20
|
+
* (the concrete `CacheEnvelope` lives at
|
|
21
|
+
* `src/runtime/prerender/cache-envelope.ts`). This module narrows it
|
|
22
|
+
* to a structural subset `LaycacheEnvelopeSubset` defined locally —
|
|
23
|
+
* only the fields the loader actually reads (`canonicalDocument`,
|
|
24
|
+
* `compatibilityReport`). Zero runtime-layer imports.
|
|
25
|
+
* - `telemetryBus` is `unknown` on the same options. This module
|
|
26
|
+
* narrows to `Parameters<typeof setActiveParseTelemetryBus>[0]`
|
|
27
|
+
* (the parser's own type for its bus parameter) at the single
|
|
28
|
+
* call site. Zero runtime-layer imports.
|
|
29
|
+
*
|
|
30
|
+
* Performance invariants preserved verbatim from the legacy body:
|
|
31
|
+
* - Invariant 8 — `onProgressiveSnapshot` fires after the body-normalize
|
|
32
|
+
* stage + post-yield, only on the cold path (no laycache envelope).
|
|
33
|
+
* - Invariant 9 — chart-preview resolution deferred off critical path
|
|
34
|
+
* when `onChartPreviewsReady` is supplied; inline otherwise.
|
|
35
|
+
* - Invariant 10 — no blocking work between the laycache probe (run
|
|
36
|
+
* in `DocxSession.open` before this function is called) and the
|
|
37
|
+
* loader body. The short-circuit branch is the first work this
|
|
38
|
+
* function does after the OPC read + relationship resolve.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type {
|
|
42
|
+
CompatibilityReport,
|
|
43
|
+
EditorSessionState,
|
|
44
|
+
} from "../../api/public-types.ts";
|
|
45
|
+
import { runStatefulExport } from "../export/stateful-export.ts";
|
|
46
|
+
import { editorSessionStateFromPersistedSnapshot } from "../../api/session-state.ts";
|
|
47
|
+
// `src/api/diagnostic-conversion.ts` deleted 2026-04-22 when the
|
|
48
|
+
// Internal* diagnostic types collapsed to public-type aliases (see
|
|
49
|
+
// `docs/plans/cross-layer-coord-02.md §8`). Compatibility reports
|
|
50
|
+
// flow through as-is — no conversion needed.
|
|
51
|
+
import type { CanonicalDocument } from "../../model/canonical-document.ts";
|
|
52
|
+
import type {
|
|
53
|
+
CommentImportDiagnostic,
|
|
54
|
+
ImportedCommentDefinition,
|
|
55
|
+
ParsedCommentsResult,
|
|
56
|
+
} from "../../io/ooxml/parse-comments.ts";
|
|
57
|
+
import type {
|
|
58
|
+
BlockNode as _BlockNode,
|
|
59
|
+
FootnoteCollection,
|
|
60
|
+
HeaderDocument,
|
|
61
|
+
FooterDocument,
|
|
62
|
+
InlineNode as _InlineNode,
|
|
63
|
+
MediaCatalog as _MediaCatalog,
|
|
64
|
+
NumberingCatalog as _NumberingCatalog,
|
|
65
|
+
OpaqueFragmentRecord as _OpaqueFragmentRecord,
|
|
66
|
+
PreservedPackagePart as _PreservedPackagePart,
|
|
67
|
+
SubPartsCatalog,
|
|
68
|
+
} from "../../model/canonical-document.ts";
|
|
69
|
+
import { readOpcPackage, type OpcPackage } from "../../io/opc/package-reader.ts";
|
|
70
|
+
import {
|
|
71
|
+
parseMainDocumentXml,
|
|
72
|
+
setActiveParseTelemetryBus,
|
|
73
|
+
} from "../../io/ooxml/parse-main-document.ts";
|
|
74
|
+
import {
|
|
75
|
+
normalizeParsedTextDocument,
|
|
76
|
+
normalizeParsedTextDocumentAsync,
|
|
77
|
+
} from "../../io/normalize/normalize-text.ts";
|
|
78
|
+
import {
|
|
79
|
+
createChartPartLookup,
|
|
80
|
+
resolveChartPreviewsForDocument,
|
|
81
|
+
scheduleChartPreviewResolution,
|
|
82
|
+
} from "../../io/chart-preview-resolver.ts";
|
|
83
|
+
import {
|
|
84
|
+
resolveRelationshipTarget,
|
|
85
|
+
} from "../../io/ooxml/part-manifest.ts";
|
|
86
|
+
import {
|
|
87
|
+
parseWorkflowPayloadEnvelopeFromPackage,
|
|
88
|
+
} from "../../io/ooxml/workflow-payload.ts";
|
|
89
|
+
import {
|
|
90
|
+
classifyCorruptPackageError,
|
|
91
|
+
createMissingPartIssue,
|
|
92
|
+
} from "../../io/opc/corrupt-package.ts";
|
|
93
|
+
import {
|
|
94
|
+
parseRevisionsFromDocumentXml,
|
|
95
|
+
parseRevisionsFromStoryXml,
|
|
96
|
+
type ParsedRevisionsResult,
|
|
97
|
+
} from "../../io/ooxml/parse-revisions.ts";
|
|
98
|
+
import { parseCommentsFromOoxml } from "../../io/ooxml/parse-comments.ts";
|
|
99
|
+
import { parseNumberingXml } from "../../io/ooxml/parse-numbering.ts";
|
|
100
|
+
import {
|
|
101
|
+
parseHeaderFooterReferences,
|
|
102
|
+
parseHeaderXml,
|
|
103
|
+
parseFooterXml,
|
|
104
|
+
} from "../../io/ooxml/parse-headers-footers.ts";
|
|
105
|
+
import {
|
|
106
|
+
parseFootnotesXml,
|
|
107
|
+
parseEndnotesXml,
|
|
108
|
+
} from "../../io/ooxml/parse-footnotes.ts";
|
|
109
|
+
import {
|
|
110
|
+
materializeCanonicalTheme,
|
|
111
|
+
parseThemeXml,
|
|
112
|
+
resolveTheme,
|
|
113
|
+
} from "../../io/ooxml/parse-theme.ts";
|
|
114
|
+
import { parseSettingsXml } from "../../io/ooxml/parse-settings.ts";
|
|
115
|
+
import { parseStylesXml } from "../../io/ooxml/parse-styles.ts";
|
|
116
|
+
import { parseFontTable } from "../../io/ooxml/parse-font-table.ts";
|
|
117
|
+
import { createPersistedSourcePackage } from "../../io/source-package-provenance.ts";
|
|
118
|
+
import { validatePersistedEditorSnapshot } from "../../model/snapshot.ts";
|
|
119
|
+
import { buildCompatibilityReport } from "../../validation/compatibility-engine.ts";
|
|
120
|
+
import {
|
|
121
|
+
createPackageImportDiagnostics,
|
|
122
|
+
createValidationImportDiagnostics,
|
|
123
|
+
} from "../../validation/import-diagnostics.ts";
|
|
124
|
+
import {
|
|
125
|
+
extractDocumentProtection,
|
|
126
|
+
extractProtectionRanges,
|
|
127
|
+
buildProtectionSnapshot,
|
|
128
|
+
} from "../shared/protection.ts";
|
|
129
|
+
import {
|
|
130
|
+
createEmptyNumberingCatalog,
|
|
131
|
+
decodeUtf8,
|
|
132
|
+
extractDocumentRootAttributes,
|
|
133
|
+
serializeCanonicalDocumentForExport,
|
|
134
|
+
toUint8Array,
|
|
135
|
+
} from "../shared/session-utils.ts";
|
|
136
|
+
import {
|
|
137
|
+
createImportDiagnosticsFromError,
|
|
138
|
+
createStageEmitter,
|
|
139
|
+
} from "./parse-orchestration.ts";
|
|
140
|
+
import { mergeSecondaryStoryMediaCatalog } from "./styles-consolidation.ts";
|
|
141
|
+
import {
|
|
142
|
+
createImportedCanonicalDocument,
|
|
143
|
+
createImportedSnapshot,
|
|
144
|
+
} from "./canonical-assembly.ts";
|
|
145
|
+
import {
|
|
146
|
+
normalizeImportedCommentThreads,
|
|
147
|
+
normalizeImportedRevisionRecords,
|
|
148
|
+
toRuntimeCommentRecords,
|
|
149
|
+
toRuntimeRevisionRecords,
|
|
150
|
+
} from "./review-import.ts";
|
|
151
|
+
import { translateClmCommentsToWorkflow } from "./workflow-scope-import.ts";
|
|
152
|
+
import {
|
|
153
|
+
mapCommentDiagnosticsToWarnings,
|
|
154
|
+
mapRevisionDiagnosticsToWarnings,
|
|
155
|
+
mapStoryRevisionDiagnosticsToWarnings,
|
|
156
|
+
} from "./import-diagnostics.ts";
|
|
157
|
+
import { buildImportPreservation } from "./preservation-index.ts";
|
|
158
|
+
import {
|
|
159
|
+
hydrateOffloadEntriesFromPayload,
|
|
160
|
+
processEmbeddedOffload,
|
|
161
|
+
type EmbeddingOffloadEntry,
|
|
162
|
+
} from "./embedded-offload.ts";
|
|
163
|
+
import {
|
|
164
|
+
COMMENTS_PART_PATH,
|
|
165
|
+
COMMENTS_RELATIONSHIP_TYPE,
|
|
166
|
+
MAIN_DOCUMENT_PATH,
|
|
167
|
+
resolveCommentsPartPath,
|
|
168
|
+
resolveDocumentRelatedPartPath,
|
|
169
|
+
resolveMainDocumentPartPath,
|
|
170
|
+
} from "./part-discovery.ts";
|
|
171
|
+
import {
|
|
172
|
+
collectBrokenInternalRelationshipIssues,
|
|
173
|
+
createBrokenRelationshipWarnings,
|
|
174
|
+
summarizeBrokenRelationshipIssues,
|
|
175
|
+
} from "./package-read.ts";
|
|
176
|
+
import {
|
|
177
|
+
createSubPartOpaqueImportState,
|
|
178
|
+
normalizeFootnoteCollectionOpaqueBlocks,
|
|
179
|
+
normalizeSubPartOpaqueBlocks,
|
|
180
|
+
} from "./normalize.ts";
|
|
181
|
+
import {
|
|
182
|
+
collectInlineMediaParts,
|
|
183
|
+
} from "./package-parts.ts";
|
|
184
|
+
import { createDiagnosticsSession } from "./diagnostics-session.ts";
|
|
185
|
+
import { collectEmbeddedDocuments } from "./embedded-discovery.ts";
|
|
186
|
+
import type { CommentThread } from "../../model/review/comment-types.ts";
|
|
187
|
+
import type { RevisionRecord as ReviewRevisionRecord } from "../../model/review/revision-types.ts";
|
|
188
|
+
import {
|
|
189
|
+
BLOCKING_COMMENT_DIAGNOSTIC_CODES,
|
|
190
|
+
PROGRESSIVE_VIEWPORT_BLOCKS,
|
|
191
|
+
type ImportedDocxState,
|
|
192
|
+
type LoadDocxEditorSessionAsyncOptions,
|
|
193
|
+
type LoadDocxEditorSessionOptions,
|
|
194
|
+
type LoadedDocxEditorSession,
|
|
195
|
+
} from "./loader-types.ts";
|
|
196
|
+
import {
|
|
197
|
+
COMMENTS_EXTENDED_PART_PATH,
|
|
198
|
+
COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
199
|
+
COMMENTS_IDS_PART_PATH,
|
|
200
|
+
COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
201
|
+
ENDNOTES_PART_PATH,
|
|
202
|
+
ENDNOTES_RELATIONSHIP_TYPE,
|
|
203
|
+
FONT_TABLE_PART_PATH,
|
|
204
|
+
FONT_TABLE_RELATIONSHIP_TYPE,
|
|
205
|
+
FOOTNOTES_PART_PATH,
|
|
206
|
+
FOOTNOTES_RELATIONSHIP_TYPE,
|
|
207
|
+
MAIN_DOCUMENT_CONTENT_TYPE,
|
|
208
|
+
NUMBERING_PART_PATH,
|
|
209
|
+
NUMBERING_RELATIONSHIP_TYPE,
|
|
210
|
+
PEOPLE_PART_PATH,
|
|
211
|
+
PEOPLE_RELATIONSHIP_TYPE,
|
|
212
|
+
SETTINGS_PART_PATH,
|
|
213
|
+
SETTINGS_RELATIONSHIP_TYPE,
|
|
214
|
+
STYLES_PART_PATH,
|
|
215
|
+
STYLES_RELATIONSHIP_TYPE,
|
|
216
|
+
} from "./part-constants.ts";
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Structural subset of `CacheEnvelope` (from
|
|
220
|
+
* `src/runtime/prerender/cache-envelope.ts`) that the loader reads.
|
|
221
|
+
*
|
|
222
|
+
* Defined locally so this module stays §P6-clean — the concrete
|
|
223
|
+
* `CacheEnvelope` type is owned by the runtime/prerender layer and may
|
|
224
|
+
* not be imported here. Callers cast to this subset at the single
|
|
225
|
+
* consumption site (`options.laycacheEnvelope as LaycacheEnvelopeSubset
|
|
226
|
+
* | undefined`); the envelope carries additional fields (graph,
|
|
227
|
+
* surface, hashes) that this module does not consume.
|
|
228
|
+
*/
|
|
229
|
+
interface LaycacheEnvelopeSubset {
|
|
230
|
+
readonly canonicalDocument: CanonicalDocument;
|
|
231
|
+
/**
|
|
232
|
+
* Concrete type at the boundary is the `Internal*` flavor of
|
|
233
|
+
* `CompatibilityReport` (from `src/core/state/editor-state.ts`) that
|
|
234
|
+
* the runtime-layer `CacheEnvelope` carries. We keep it as `unknown`
|
|
235
|
+
* here so this module does not transit `src/core/**` (§P6); the one
|
|
236
|
+
* consumption site narrows via `Parameters<typeof
|
|
237
|
+
* toPublicCompatibilityReport>[0]` at call time.
|
|
238
|
+
*/
|
|
239
|
+
readonly compatibilityReport?: unknown;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Narrow type for the caller-supplied telemetry bus. The concrete
|
|
244
|
+
* `TelemetryBus` type lives at `src/runtime/debug/telemetry-bus.ts`
|
|
245
|
+
* (runtime-layer, forbidden here by §P6). We reach the parser's own
|
|
246
|
+
* parameter type via `Parameters<typeof setActiveParseTelemetryBus>[0]`
|
|
247
|
+
* so the single bracketing call site passes a strongly-typed value
|
|
248
|
+
* without this module transiting runtime internals.
|
|
249
|
+
*/
|
|
250
|
+
type ParseTelemetryBusParam = Parameters<typeof setActiveParseTelemetryBus>[0];
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Session-layer owner of the async load path. Body is byte-identical to
|
|
254
|
+
* the pre-5e-7.e.1 `loadDocxEditorSessionAsync` in
|
|
255
|
+
* `src/io/docx-session.ts`; only the imports were renamed to point at
|
|
256
|
+
* the already-extracted session-layer modules and the two runtime-type
|
|
257
|
+
* casts were replaced with the local `LaycacheEnvelopeSubset` + the
|
|
258
|
+
* `ParseTelemetryBusParam` narrow.
|
|
259
|
+
*
|
|
260
|
+
* Slice 5e-7.f removed the `deps` parameter — the stateful export
|
|
261
|
+
* pipeline now lives in-session at
|
|
262
|
+
* `src/session/export/stateful-export.ts` and is imported directly.
|
|
263
|
+
*/
|
|
264
|
+
export async function loadDocxSessionAsync(
|
|
265
|
+
options: LoadDocxEditorSessionAsyncOptions,
|
|
266
|
+
): Promise<LoadedDocxEditorSession> {
|
|
267
|
+
const { scheduler } = options;
|
|
268
|
+
const editorBuild =
|
|
269
|
+
typeof options.editorBuild === "string" && options.editorBuild.length > 0
|
|
270
|
+
? options.editorBuild
|
|
271
|
+
: "dev";
|
|
272
|
+
const sourceBytes = toUint8Array(options.bytes);
|
|
273
|
+
|
|
274
|
+
const stages = createStageEmitter(options.onLoadStage);
|
|
275
|
+
|
|
276
|
+
let sourcePackage: OpcPackage;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
sourcePackage = readOpcPackage(sourceBytes);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return createDiagnosticsSession(
|
|
282
|
+
options,
|
|
283
|
+
createPackageImportDiagnostics({
|
|
284
|
+
issue: classifyCorruptPackageError(error),
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
stages.emit("opc");
|
|
289
|
+
await scheduler.yield();
|
|
290
|
+
const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
|
|
291
|
+
const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
|
|
292
|
+
const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
|
|
293
|
+
|
|
294
|
+
const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
|
|
295
|
+
const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
|
|
296
|
+
sourcePackage,
|
|
297
|
+
mainDocumentPath,
|
|
298
|
+
);
|
|
299
|
+
if (brokenRelationshipIssues.length > 0) {
|
|
300
|
+
return createDiagnosticsSession(
|
|
301
|
+
options,
|
|
302
|
+
createPackageImportDiagnostics({
|
|
303
|
+
issue: {
|
|
304
|
+
...brokenRelationshipIssues[0],
|
|
305
|
+
message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
|
|
306
|
+
details: {
|
|
307
|
+
issueCount: brokenRelationshipIssues.length,
|
|
308
|
+
targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!mainDocumentPath) {
|
|
316
|
+
return createDiagnosticsSession(
|
|
317
|
+
options,
|
|
318
|
+
createPackageImportDiagnostics({
|
|
319
|
+
issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const documentPart = sourcePackage.parts.get(mainDocumentPath);
|
|
325
|
+
if (!documentPart) {
|
|
326
|
+
return createDiagnosticsSession(
|
|
327
|
+
options,
|
|
328
|
+
createPackageImportDiagnostics({
|
|
329
|
+
issue: createMissingPartIssue(mainDocumentPath),
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
|
|
334
|
+
return createDiagnosticsSession(
|
|
335
|
+
options,
|
|
336
|
+
createValidationImportDiagnostics({
|
|
337
|
+
message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
// L7 Phase 2.5 Plan B B.6b — loader short-circuit. Hand
|
|
344
|
+
// `envelope.canonicalDocument` through reference-equal and skip the
|
|
345
|
+
// five expensive parse stages. The four `onLoadStage` callbacks still
|
|
346
|
+
// fire in order — `body` and `styles-numbering-comments` emit with
|
|
347
|
+
// near-zero duration — so host progress bars are unaffected.
|
|
348
|
+
const laycacheEnvelope = options.laycacheEnvelope as
|
|
349
|
+
| LaycacheEnvelopeSubset
|
|
350
|
+
| undefined;
|
|
351
|
+
if (laycacheEnvelope) {
|
|
352
|
+
stages.emit("body");
|
|
353
|
+
stages.emit("styles-numbering-comments");
|
|
354
|
+
|
|
355
|
+
const canonicalDocument = laycacheEnvelope.canonicalDocument;
|
|
356
|
+
|
|
357
|
+
// `extractProtectionRanges` needs `parsedDocument.blocks` (which we
|
|
358
|
+
// are skipping), so the short-circuit uses an empty ranges list;
|
|
359
|
+
// document-level `editType` / `enforcement` still come from
|
|
360
|
+
// settings.xml so read-only docs stay read-only.
|
|
361
|
+
const settingsPartPath = resolveDocumentRelatedPartPath(
|
|
362
|
+
sourcePackage,
|
|
363
|
+
mainDocumentPath,
|
|
364
|
+
documentPart.relationships,
|
|
365
|
+
SETTINGS_RELATIONSHIP_TYPE,
|
|
366
|
+
SETTINGS_PART_PATH,
|
|
367
|
+
);
|
|
368
|
+
const settingsXml =
|
|
369
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
370
|
+
? decodeUtf8(
|
|
371
|
+
sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array(),
|
|
372
|
+
)
|
|
373
|
+
: "";
|
|
374
|
+
const documentProtection = extractDocumentProtection(settingsXml);
|
|
375
|
+
const protectionSnapshot = buildProtectionSnapshot(documentProtection, []);
|
|
376
|
+
|
|
377
|
+
// Chart previews (`previewMediaId` is host-dependent) aren't cached
|
|
378
|
+
// in the envelope. C3b: when onChartPreviewsReady is provided, defer
|
|
379
|
+
// resolution out of the critical path. Otherwise block (legacy behavior).
|
|
380
|
+
let documentWithChartPreviews: CanonicalDocument;
|
|
381
|
+
if (options.onChartPreviewsReady) {
|
|
382
|
+
scheduleChartPreviewResolution(
|
|
383
|
+
canonicalDocument,
|
|
384
|
+
sourcePackage,
|
|
385
|
+
options.hostAdapter,
|
|
386
|
+
options.onChartPreviewsReady,
|
|
387
|
+
);
|
|
388
|
+
documentWithChartPreviews = canonicalDocument as CanonicalDocument;
|
|
389
|
+
} else {
|
|
390
|
+
documentWithChartPreviews = (await resolveChartPreviewsForDocument(
|
|
391
|
+
canonicalDocument,
|
|
392
|
+
sourcePackage,
|
|
393
|
+
options.hostAdapter,
|
|
394
|
+
)) as CanonicalDocument;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const timestamp = new Date().toISOString();
|
|
398
|
+
// Phase 2 Finale C3: skip `buildCompatibilityReport` (60–100 ms on
|
|
399
|
+
// extra-large) when the envelope carries a pre-computed report.
|
|
400
|
+
// Pure-function determinism of the report is enforced by
|
|
401
|
+
// `canonicalDocumentHash` (5th input to `deriveCacheKey`): any
|
|
402
|
+
// change to the canonical doc flips the hash and rejects the
|
|
403
|
+
// envelope on load.
|
|
404
|
+
//
|
|
405
|
+
// The cached report's `generatedAt` is a fixed sentinel
|
|
406
|
+
// (`CACHE_NORMALIZED_GENERATED_AT`) for envelope byte-identity.
|
|
407
|
+
// Swap it for the live ISO8601 timestamp here because downstream
|
|
408
|
+
// `validatePersistedEditorSnapshot` requires
|
|
409
|
+
// `$.compatibility.generatedAt` to be ISO 8601.
|
|
410
|
+
const cachedReport = laycacheEnvelope?.compatibilityReport as
|
|
411
|
+
| CompatibilityReport
|
|
412
|
+
| undefined;
|
|
413
|
+
const compatibility = cachedReport
|
|
414
|
+
? { ...cachedReport, generatedAt: timestamp }
|
|
415
|
+
: buildCompatibilityReport({
|
|
416
|
+
document: documentWithChartPreviews,
|
|
417
|
+
generatedAt: timestamp,
|
|
418
|
+
});
|
|
419
|
+
await scheduler.yield();
|
|
420
|
+
|
|
421
|
+
const snapshot = createImportedSnapshot({
|
|
422
|
+
documentId: options.documentId,
|
|
423
|
+
editorBuild,
|
|
424
|
+
timestamp,
|
|
425
|
+
document: documentWithChartPreviews,
|
|
426
|
+
compatibility,
|
|
427
|
+
protectionSnapshot,
|
|
428
|
+
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
429
|
+
workflowOverlay: embeddedWorkflowOverlay,
|
|
430
|
+
workflowMetadata: embeddedWorkflowMetadata,
|
|
431
|
+
visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
|
|
432
|
+
markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
|
|
433
|
+
});
|
|
434
|
+
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
435
|
+
if (snapshotIssues.length > 0) {
|
|
436
|
+
const firstIssue = snapshotIssues[0];
|
|
437
|
+
return createDiagnosticsSession(
|
|
438
|
+
options,
|
|
439
|
+
createValidationImportDiagnostics({
|
|
440
|
+
message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
|
|
441
|
+
source: "import",
|
|
442
|
+
details: {
|
|
443
|
+
issueCount: snapshotIssues.length,
|
|
444
|
+
firstIssuePath: firstIssue?.path,
|
|
445
|
+
},
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Build `initialSessionState` inline — bypassing
|
|
451
|
+
// `editorSessionStateFromPersistedSnapshot`'s structuredClone so
|
|
452
|
+
// `session.initialSessionState.canonicalDocument` is reference-equal
|
|
453
|
+
// to `envelope.canonicalDocument` (cloning a large canonical document
|
|
454
|
+
// defeats part of the cache gain).
|
|
455
|
+
const sessionState: EditorSessionState = {
|
|
456
|
+
sessionVersion: "editor-session-state/1",
|
|
457
|
+
schemaVersion: snapshot.schemaVersion,
|
|
458
|
+
documentId: snapshot.documentId,
|
|
459
|
+
docId: snapshot.docId,
|
|
460
|
+
createdAt: snapshot.createdAt,
|
|
461
|
+
updatedAt: snapshot.updatedAt,
|
|
462
|
+
editorBuild: snapshot.editorBuild,
|
|
463
|
+
canonicalDocument: snapshot.canonicalDocument,
|
|
464
|
+
compatibility: snapshot.compatibility,
|
|
465
|
+
warningLog: snapshot.warningLog,
|
|
466
|
+
protectionSnapshot: snapshot.protectionSnapshot,
|
|
467
|
+
sourcePackage: snapshot.sourcePackage,
|
|
468
|
+
workflowOverlay: snapshot.workflowOverlay,
|
|
469
|
+
workflowMetadata: snapshot.workflowMetadata,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// The short-circuit path does not carry an `ImportedDocxState`, so
|
|
473
|
+
// `exportDocx` lazily re-runs the cold path on first invocation and
|
|
474
|
+
// memoizes. Keeps the warm-load fast while preserving byte-exact
|
|
475
|
+
// export correctness.
|
|
476
|
+
let lazyColdExport: LoadedDocxEditorSession["exportDocx"] | undefined;
|
|
477
|
+
const exportDocx: LoadedDocxEditorSession["exportDocx"] = async (
|
|
478
|
+
nextSessionStateOrSnapshot,
|
|
479
|
+
exportOptions,
|
|
480
|
+
) => {
|
|
481
|
+
if (!lazyColdExport) {
|
|
482
|
+
const { laycacheEnvelope: _unused, ...coldOptions } = options;
|
|
483
|
+
void _unused;
|
|
484
|
+
const coldSession = await loadDocxSessionAsync(coldOptions);
|
|
485
|
+
if (coldSession.fatalError) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`DOCX export via short-circuit fallback failed cold load: ${coldSession.fatalError.message ?? "fatal error"}`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
lazyColdExport = coldSession.exportDocx;
|
|
491
|
+
}
|
|
492
|
+
return lazyColdExport(nextSessionStateOrSnapshot, exportOptions);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
stages.emit("skeleton-ready");
|
|
496
|
+
return {
|
|
497
|
+
initialSessionState: sessionState,
|
|
498
|
+
initialSnapshot: snapshot,
|
|
499
|
+
readOnly: false,
|
|
500
|
+
protectionSnapshot,
|
|
501
|
+
exportDocx,
|
|
502
|
+
...(embeddedWorkflowPayload?.editorState
|
|
503
|
+
? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
|
|
504
|
+
: {}),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const sourceDocumentXml = decodeUtf8(documentPart.bytes);
|
|
509
|
+
const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
|
|
510
|
+
const numberingPartPath = resolveDocumentRelatedPartPath(
|
|
511
|
+
sourcePackage,
|
|
512
|
+
mainDocumentPath,
|
|
513
|
+
documentPart.relationships,
|
|
514
|
+
NUMBERING_RELATIONSHIP_TYPE,
|
|
515
|
+
NUMBERING_PART_PATH,
|
|
516
|
+
);
|
|
517
|
+
const parsedNumbering = numberingPartPath
|
|
518
|
+
? parseNumberingXml(
|
|
519
|
+
decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
|
|
520
|
+
{
|
|
521
|
+
relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
|
|
522
|
+
partPath: numberingPartPath,
|
|
523
|
+
},
|
|
524
|
+
)
|
|
525
|
+
: createEmptyNumberingCatalog();
|
|
526
|
+
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
527
|
+
const chartPartLookup = createChartPartLookup(
|
|
528
|
+
sourcePackage,
|
|
529
|
+
mainDocumentPath,
|
|
530
|
+
documentPart.relationships,
|
|
531
|
+
);
|
|
532
|
+
if (options.telemetryBus) {
|
|
533
|
+
setActiveParseTelemetryBus(options.telemetryBus as ParseTelemetryBusParam);
|
|
534
|
+
}
|
|
535
|
+
let parsedDocument;
|
|
536
|
+
try {
|
|
537
|
+
parsedDocument = parseMainDocumentXml(
|
|
538
|
+
sourceDocumentXml,
|
|
539
|
+
documentPart.relationships,
|
|
540
|
+
mediaParts,
|
|
541
|
+
mainDocumentPath,
|
|
542
|
+
chartPartLookup,
|
|
543
|
+
);
|
|
544
|
+
} finally {
|
|
545
|
+
if (options.telemetryBus) setActiveParseTelemetryBus(undefined);
|
|
546
|
+
}
|
|
547
|
+
await scheduler.yield();
|
|
548
|
+
const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
|
|
549
|
+
const normalizedDocument = await normalizeParsedTextDocumentAsync(
|
|
550
|
+
parsedDocument,
|
|
551
|
+
mainDocumentPath,
|
|
552
|
+
scheduler,
|
|
553
|
+
);
|
|
554
|
+
stages.emit("body");
|
|
555
|
+
await scheduler.yield();
|
|
556
|
+
|
|
557
|
+
// L7 Phase 2 Finale C2 — progressive initial mount.
|
|
558
|
+
//
|
|
559
|
+
// Fire `onProgressiveSnapshot` exactly once, after the body stage and
|
|
560
|
+
// its post-yield. At this point `normalizedDocument.content` carries
|
|
561
|
+
// the full block tree with per-block runProperties already resolved
|
|
562
|
+
// during `normalizeParsedTextDocumentAsync`. We synthesize a
|
|
563
|
+
// throw-away `CanonicalDocument` using the normalized content
|
|
564
|
+
// + empty style/review/preservation catalogs, then project a
|
|
565
|
+
// viewport-windowed `EditorSurfaceSnapshot` (first `PROGRESSIVE_VIEWPORT_BLOCKS`
|
|
566
|
+
// blocks real, rest as culled placeholders via the Phase 2.9 flag).
|
|
567
|
+
//
|
|
568
|
+
// The bench's measured signal: time from `loadDocxEditorSessionAsync`
|
|
569
|
+
// entry to this callback's fire is `firstViewportCommitMs` — the
|
|
570
|
+
// metric C2 gates on.
|
|
571
|
+
//
|
|
572
|
+
// Skipped on the Plan B short-circuit: `laycacheEnvelope !== undefined`
|
|
573
|
+
// already completes ~376 ms faster than cold — adding a progressive
|
|
574
|
+
// synthesis on top costs more than it saves. The short-circuit path
|
|
575
|
+
// returns the real snapshot fast enough.
|
|
576
|
+
if (
|
|
577
|
+
options.onProgressiveSnapshot !== undefined &&
|
|
578
|
+
options.laycacheEnvelope === undefined
|
|
579
|
+
) {
|
|
580
|
+
const blocksTotal = normalizedDocument.content.children.length;
|
|
581
|
+
const blocksRealized = Math.min(
|
|
582
|
+
PROGRESSIVE_VIEWPORT_BLOCKS,
|
|
583
|
+
blocksTotal,
|
|
584
|
+
);
|
|
585
|
+
const progressiveSurface = options.surfaceProjector
|
|
586
|
+
? options.surfaceProjector({
|
|
587
|
+
rootContent: normalizedDocument.content,
|
|
588
|
+
documentId: options.documentId,
|
|
589
|
+
blocksRealized,
|
|
590
|
+
blocksTotal,
|
|
591
|
+
})
|
|
592
|
+
: undefined;
|
|
593
|
+
try {
|
|
594
|
+
options.onProgressiveSnapshot({
|
|
595
|
+
...(progressiveSurface !== undefined
|
|
596
|
+
? { surface: progressiveSurface }
|
|
597
|
+
: {}),
|
|
598
|
+
phase: "viewport",
|
|
599
|
+
blocksRealized,
|
|
600
|
+
blocksTotal,
|
|
601
|
+
});
|
|
602
|
+
} catch {
|
|
603
|
+
// A throwing consumer must not abort the load. Progressive is
|
|
604
|
+
// a best-effort optimization; errors on the callback side
|
|
605
|
+
// silently fall through to the normal full-commit path.
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const commentsPartPath = resolveCommentsPartPath(
|
|
610
|
+
sourcePackage,
|
|
611
|
+
mainDocumentPath,
|
|
612
|
+
documentPart.relationships,
|
|
613
|
+
);
|
|
614
|
+
const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
|
|
615
|
+
sourcePackage,
|
|
616
|
+
mainDocumentPath,
|
|
617
|
+
documentPart.relationships,
|
|
618
|
+
COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
619
|
+
COMMENTS_EXTENDED_PART_PATH,
|
|
620
|
+
);
|
|
621
|
+
const commentsIdsPartPath = resolveDocumentRelatedPartPath(
|
|
622
|
+
sourcePackage,
|
|
623
|
+
mainDocumentPath,
|
|
624
|
+
documentPart.relationships,
|
|
625
|
+
COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
626
|
+
COMMENTS_IDS_PART_PATH,
|
|
627
|
+
);
|
|
628
|
+
const peoplePartPath = resolveDocumentRelatedPartPath(
|
|
629
|
+
sourcePackage,
|
|
630
|
+
mainDocumentPath,
|
|
631
|
+
documentPart.relationships,
|
|
632
|
+
PEOPLE_RELATIONSHIP_TYPE,
|
|
633
|
+
PEOPLE_PART_PATH,
|
|
634
|
+
);
|
|
635
|
+
const parsedComments = commentsPartPath
|
|
636
|
+
? parseCommentsFromOoxml(
|
|
637
|
+
sourceDocumentXml,
|
|
638
|
+
{
|
|
639
|
+
commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
|
|
640
|
+
commentsExtendedXml: decodeUtf8(
|
|
641
|
+
sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
642
|
+
),
|
|
643
|
+
commentsIdsXml: decodeUtf8(
|
|
644
|
+
sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
645
|
+
),
|
|
646
|
+
peopleXml: decodeUtf8(
|
|
647
|
+
sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
648
|
+
),
|
|
649
|
+
},
|
|
650
|
+
)
|
|
651
|
+
: {
|
|
652
|
+
threads: [] as CommentThread[],
|
|
653
|
+
diagnostics: [] as CommentImportDiagnostic[],
|
|
654
|
+
definitions: [] as ImportedCommentDefinition[],
|
|
655
|
+
sourceRootTag: undefined,
|
|
656
|
+
sourceExtendedRootTag: undefined,
|
|
657
|
+
sourceIdsRootTag: undefined,
|
|
658
|
+
sourcePeopleRootTag: undefined,
|
|
659
|
+
peopleAuthors: [] as string[],
|
|
660
|
+
};
|
|
661
|
+
const normalizedRevisions = normalizeImportedRevisionRecords(
|
|
662
|
+
importedRevisions,
|
|
663
|
+
normalizedDocument.content,
|
|
664
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
665
|
+
);
|
|
666
|
+
const normalizedComments = normalizeImportedCommentThreads(
|
|
667
|
+
parsedComments,
|
|
668
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
669
|
+
normalizedRevisions.revisions,
|
|
670
|
+
);
|
|
671
|
+
stages.emit("styles-numbering-comments");
|
|
672
|
+
const importedStoryRevisions: ReviewRevisionRecord[] = [];
|
|
673
|
+
const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
|
|
674
|
+
const subPartOpaqueState = createSubPartOpaqueImportState(
|
|
675
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
676
|
+
normalizedDocument.diagnostics.warnings,
|
|
677
|
+
);
|
|
678
|
+
// ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
|
|
679
|
+
const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
|
|
680
|
+
const parsedHeaders: HeaderDocument[] = [];
|
|
681
|
+
const parsedFooters: FooterDocument[] = [];
|
|
682
|
+
const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
683
|
+
const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
684
|
+
const seenSubPartKeys = new Set<string>();
|
|
685
|
+
|
|
686
|
+
for (const ref of headerFooterRefs) {
|
|
687
|
+
const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
|
|
688
|
+
if (seenSubPartKeys.has(dedupeKey)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
seenSubPartKeys.add(dedupeKey);
|
|
692
|
+
|
|
693
|
+
const relationship = documentPart.relationships.find(
|
|
694
|
+
(r) => r.id === ref.relationshipId && r.targetMode === "internal",
|
|
695
|
+
);
|
|
696
|
+
if (!relationship) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
|
|
701
|
+
const part = sourcePackage.parts.get(partPath);
|
|
702
|
+
const partBytes = part?.bytes;
|
|
703
|
+
if (!partBytes) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
await scheduler.yield();
|
|
708
|
+
const xml = decodeUtf8(partBytes);
|
|
709
|
+
const subPartRelationships = part?.relationships ?? [];
|
|
710
|
+
const subPartChartPartLookup = createChartPartLookup(
|
|
711
|
+
sourcePackage,
|
|
712
|
+
partPath,
|
|
713
|
+
subPartRelationships,
|
|
714
|
+
);
|
|
715
|
+
if (ref.kind === "header") {
|
|
716
|
+
const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
|
|
717
|
+
const parsed = parseHeaderXml(xml, {
|
|
718
|
+
relationships: subPartRelationships,
|
|
719
|
+
mediaParts,
|
|
720
|
+
sourcePartPath: partPath,
|
|
721
|
+
chartPartLookup: subPartChartPartLookup,
|
|
722
|
+
});
|
|
723
|
+
parsedHeaders.push({
|
|
724
|
+
variant: ref.variant,
|
|
725
|
+
partPath,
|
|
726
|
+
relationshipId: ref.relationshipId,
|
|
727
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
728
|
+
blocks: normalizeSubPartOpaqueBlocks(
|
|
729
|
+
parsed.blocks,
|
|
730
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
731
|
+
normalizedDocument.diagnostics.warnings,
|
|
732
|
+
partPath,
|
|
733
|
+
subPartOpaqueState,
|
|
734
|
+
),
|
|
735
|
+
});
|
|
736
|
+
importedStoryRevisions.push(
|
|
737
|
+
...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
738
|
+
...revision,
|
|
739
|
+
metadata: {
|
|
740
|
+
...revision.metadata,
|
|
741
|
+
storyTarget: {
|
|
742
|
+
kind: "header" as const,
|
|
743
|
+
relationshipId: ref.relationshipId,
|
|
744
|
+
variant: ref.variant,
|
|
745
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
})),
|
|
749
|
+
);
|
|
750
|
+
importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
|
|
751
|
+
sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
752
|
+
} else {
|
|
753
|
+
const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
|
|
754
|
+
const parsed = parseFooterXml(xml, {
|
|
755
|
+
relationships: subPartRelationships,
|
|
756
|
+
mediaParts,
|
|
757
|
+
sourcePartPath: partPath,
|
|
758
|
+
chartPartLookup: subPartChartPartLookup,
|
|
759
|
+
});
|
|
760
|
+
parsedFooters.push({
|
|
761
|
+
variant: ref.variant,
|
|
762
|
+
partPath,
|
|
763
|
+
relationshipId: ref.relationshipId,
|
|
764
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
765
|
+
blocks: normalizeSubPartOpaqueBlocks(
|
|
766
|
+
parsed.blocks,
|
|
767
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
768
|
+
normalizedDocument.diagnostics.warnings,
|
|
769
|
+
partPath,
|
|
770
|
+
subPartOpaqueState,
|
|
771
|
+
),
|
|
772
|
+
});
|
|
773
|
+
importedStoryRevisions.push(
|
|
774
|
+
...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
775
|
+
...revision,
|
|
776
|
+
metadata: {
|
|
777
|
+
...revision.metadata,
|
|
778
|
+
storyTarget: {
|
|
779
|
+
kind: "footer" as const,
|
|
780
|
+
relationshipId: ref.relationshipId,
|
|
781
|
+
variant: ref.variant,
|
|
782
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
})),
|
|
786
|
+
);
|
|
787
|
+
importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
|
|
788
|
+
sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const footnotesPartPath = resolveDocumentRelatedPartPath(
|
|
793
|
+
sourcePackage,
|
|
794
|
+
mainDocumentPath,
|
|
795
|
+
documentPart.relationships,
|
|
796
|
+
FOOTNOTES_RELATIONSHIP_TYPE,
|
|
797
|
+
FOOTNOTES_PART_PATH,
|
|
798
|
+
);
|
|
799
|
+
const footnotesRelationshipId = documentPart.relationships.find(
|
|
800
|
+
(r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
801
|
+
)?.id;
|
|
802
|
+
const endnotesPartPath = resolveDocumentRelatedPartPath(
|
|
803
|
+
sourcePackage,
|
|
804
|
+
mainDocumentPath,
|
|
805
|
+
documentPart.relationships,
|
|
806
|
+
ENDNOTES_RELATIONSHIP_TYPE,
|
|
807
|
+
ENDNOTES_PART_PATH,
|
|
808
|
+
);
|
|
809
|
+
const endnotesRelationshipId = documentPart.relationships.find(
|
|
810
|
+
(r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
811
|
+
)?.id;
|
|
812
|
+
|
|
813
|
+
let footnoteCollection: FootnoteCollection | undefined;
|
|
814
|
+
if (footnotesPartPath) {
|
|
815
|
+
footnoteCollection = parseFootnotesXml(
|
|
816
|
+
decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
817
|
+
);
|
|
818
|
+
normalizeFootnoteCollectionOpaqueBlocks(
|
|
819
|
+
footnoteCollection,
|
|
820
|
+
"footnote",
|
|
821
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
822
|
+
normalizedDocument.diagnostics.warnings,
|
|
823
|
+
footnotesPartPath,
|
|
824
|
+
subPartOpaqueState,
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
if (endnotesPartPath) {
|
|
828
|
+
footnoteCollection = parseEndnotesXml(
|
|
829
|
+
decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
830
|
+
footnoteCollection,
|
|
831
|
+
);
|
|
832
|
+
normalizeFootnoteCollectionOpaqueBlocks(
|
|
833
|
+
footnoteCollection,
|
|
834
|
+
"endnote",
|
|
835
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
836
|
+
normalizedDocument.diagnostics.warnings,
|
|
837
|
+
endnotesPartPath,
|
|
838
|
+
subPartOpaqueState,
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
await scheduler.yield();
|
|
842
|
+
|
|
843
|
+
const themeRelationship = documentPart.relationships.find(
|
|
844
|
+
(r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
|
|
845
|
+
r.targetMode === "internal",
|
|
846
|
+
);
|
|
847
|
+
const themePartPath = themeRelationship
|
|
848
|
+
? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
|
|
849
|
+
: undefined;
|
|
850
|
+
const parsedTheme =
|
|
851
|
+
themePartPath && sourcePackage.parts.has(themePartPath)
|
|
852
|
+
? parseThemeXml(
|
|
853
|
+
decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
|
|
854
|
+
)
|
|
855
|
+
: undefined;
|
|
856
|
+
const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
|
|
857
|
+
const settingsPartPath = resolveDocumentRelatedPartPath(
|
|
858
|
+
sourcePackage,
|
|
859
|
+
mainDocumentPath,
|
|
860
|
+
documentPart.relationships,
|
|
861
|
+
SETTINGS_RELATIONSHIP_TYPE,
|
|
862
|
+
SETTINGS_PART_PATH,
|
|
863
|
+
);
|
|
864
|
+
const parsedSettings =
|
|
865
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
866
|
+
? parseSettingsXml(
|
|
867
|
+
decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
|
|
868
|
+
)
|
|
869
|
+
: undefined;
|
|
870
|
+
const canonicalTheme =
|
|
871
|
+
parsedTheme !== undefined
|
|
872
|
+
? materializeCanonicalTheme(
|
|
873
|
+
parsedTheme,
|
|
874
|
+
parsedSettings?.clrSchemeMapping ?? {},
|
|
875
|
+
)
|
|
876
|
+
: undefined;
|
|
877
|
+
const settingsXmlForProtection =
|
|
878
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
879
|
+
? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
|
|
880
|
+
: "";
|
|
881
|
+
const documentProtection = extractDocumentProtection(settingsXmlForProtection);
|
|
882
|
+
const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
|
|
883
|
+
|
|
884
|
+
// ---- Parse styles.xml for canonical style catalog ----
|
|
885
|
+
const stylesPartPath = resolveDocumentRelatedPartPath(
|
|
886
|
+
sourcePackage,
|
|
887
|
+
mainDocumentPath,
|
|
888
|
+
documentPart.relationships,
|
|
889
|
+
STYLES_RELATIONSHIP_TYPE,
|
|
890
|
+
STYLES_PART_PATH,
|
|
891
|
+
);
|
|
892
|
+
const parsedStyles =
|
|
893
|
+
stylesPartPath && sourcePackage.parts.has(stylesPartPath)
|
|
894
|
+
? parseStylesXml(
|
|
895
|
+
decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
|
|
896
|
+
)
|
|
897
|
+
: parseStylesXml("");
|
|
898
|
+
await scheduler.yield();
|
|
899
|
+
|
|
900
|
+
// ---- Parse fontTable.xml for canonical font catalog ----
|
|
901
|
+
const fontTablePartPath = resolveDocumentRelatedPartPath(
|
|
902
|
+
sourcePackage,
|
|
903
|
+
mainDocumentPath,
|
|
904
|
+
documentPart.relationships,
|
|
905
|
+
FONT_TABLE_RELATIONSHIP_TYPE,
|
|
906
|
+
FONT_TABLE_PART_PATH,
|
|
907
|
+
);
|
|
908
|
+
const parsedFontTable =
|
|
909
|
+
fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
|
|
910
|
+
? parseFontTable(
|
|
911
|
+
decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
|
|
912
|
+
)
|
|
913
|
+
: undefined;
|
|
914
|
+
|
|
915
|
+
const mergedMedia = mergeSecondaryStoryMediaCatalog(normalizedDocument.media, {
|
|
916
|
+
headers: parsedHeaders,
|
|
917
|
+
footers: parsedFooters,
|
|
918
|
+
footnoteCollection,
|
|
919
|
+
mediaParts,
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
const subParts: SubPartsCatalog | undefined =
|
|
923
|
+
parsedHeaders.length > 0 ||
|
|
924
|
+
parsedFooters.length > 0 ||
|
|
925
|
+
footnoteCollection !== undefined ||
|
|
926
|
+
parsedTheme !== undefined ||
|
|
927
|
+
normalizedDocument.finalSectionProperties !== undefined ||
|
|
928
|
+
resolvedTheme !== undefined ||
|
|
929
|
+
canonicalTheme !== undefined ||
|
|
930
|
+
parsedSettings !== undefined
|
|
931
|
+
? {
|
|
932
|
+
headers: parsedHeaders,
|
|
933
|
+
footers: parsedFooters,
|
|
934
|
+
...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
|
|
935
|
+
...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
|
|
936
|
+
...(normalizedDocument.finalSectionProperties !== undefined
|
|
937
|
+
? { finalSectionProperties: normalizedDocument.finalSectionProperties }
|
|
938
|
+
: {}),
|
|
939
|
+
...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
|
|
940
|
+
...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
|
|
941
|
+
...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
|
|
942
|
+
}
|
|
943
|
+
: undefined;
|
|
944
|
+
|
|
945
|
+
const timestamp = new Date().toISOString();
|
|
946
|
+
const translatedWorkflowState = translateClmCommentsToWorkflow({
|
|
947
|
+
comments: normalizedComments.threads,
|
|
948
|
+
workflowOverlay: embeddedWorkflowOverlay,
|
|
949
|
+
workflowMetadata: embeddedWorkflowMetadata,
|
|
950
|
+
timestamp,
|
|
951
|
+
});
|
|
952
|
+
// P8 Step 7 — embedded-document offload. First, check whether the
|
|
953
|
+
// source package already carries a `bw:embeddings` customXml
|
|
954
|
+
// namespace from a prior offload (reopen path). If so, skip the
|
|
955
|
+
// fresh `storeEmbeddedDocument` pass and reuse the hydrated
|
|
956
|
+
// entries. Otherwise run discovery + offload; entries are empty
|
|
957
|
+
// when no adapter is present, when zero offloadable manifests
|
|
958
|
+
// were found, or when every adapter call failed (fall-through to
|
|
959
|
+
// store-only semantics). See architecture §P8 and
|
|
960
|
+
// `src/session/import/embedded-offload.ts`.
|
|
961
|
+
const embeddedDocumentManifestsEarly = collectEmbeddedDocuments(
|
|
962
|
+
sourcePackage,
|
|
963
|
+
mainDocumentPath,
|
|
964
|
+
).manifests;
|
|
965
|
+
const rehydratedOffload = hydrateOffloadEntriesFromPayload(
|
|
966
|
+
embeddedWorkflowPayload?.editorState,
|
|
967
|
+
);
|
|
968
|
+
let offloadEntries: readonly EmbeddingOffloadEntry[];
|
|
969
|
+
let offloadOwnedPartPaths: readonly string[];
|
|
970
|
+
if (rehydratedOffload.entries.length > 0) {
|
|
971
|
+
// REOPEN path — entries already exist in customXml; no fresh
|
|
972
|
+
// adapter call, no sha / base64 work on the critical path.
|
|
973
|
+
offloadEntries = rehydratedOffload.entries;
|
|
974
|
+
offloadOwnedPartPaths = rehydratedOffload.ownedPartPaths;
|
|
975
|
+
} else if (options.offloadEmbeddedDocuments === true) {
|
|
976
|
+
// Fresh offload opt-in. Hosts that want offload pay the cold-
|
|
977
|
+
// open cost only when they explicitly set the flag. Supplying
|
|
978
|
+
// `hostAdapter` alone (for chart-preview / save-session /
|
|
979
|
+
// log-event etc.) does NOT trigger offload — offload is a
|
|
980
|
+
// separate decision from adapter presence per refactor/04's
|
|
981
|
+
// cross-lane perf finding (cross-layer-coord-04 §1.13): sha256
|
|
982
|
+
// + base64 of large offloadable embeddings costs ~300 ms per
|
|
983
|
+
// MB on the critical path.
|
|
984
|
+
const offloadResult = await processEmbeddedOffload({
|
|
985
|
+
parentDocumentId: options.documentId,
|
|
986
|
+
manifests: embeddedDocumentManifestsEarly,
|
|
987
|
+
hostAdapter: options.hostAdapter,
|
|
988
|
+
resolvePartPath: (rId) => {
|
|
989
|
+
const rel = documentPart.relationships.find(
|
|
990
|
+
(r) => r.id === rId && r.targetMode === "internal",
|
|
991
|
+
);
|
|
992
|
+
return rel ? resolveRelationshipTarget(mainDocumentPath, rel) : undefined;
|
|
993
|
+
},
|
|
994
|
+
resolveContentType: (path) =>
|
|
995
|
+
sourcePackage.parts.get(path)?.contentType ?? undefined,
|
|
996
|
+
});
|
|
997
|
+
offloadEntries = offloadResult.entries;
|
|
998
|
+
offloadOwnedPartPaths = offloadResult.ownedPartPaths;
|
|
999
|
+
} else {
|
|
1000
|
+
// Default path — no offload. Offloadable manifests fall
|
|
1001
|
+
// through to the preservation store (same as store-only
|
|
1002
|
+
// behavior). Zero-cost; hosts that never opt in never pay.
|
|
1003
|
+
offloadEntries = [];
|
|
1004
|
+
offloadOwnedPartPaths = [];
|
|
1005
|
+
}
|
|
1006
|
+
const importedDocument = createImportedCanonicalDocument({
|
|
1007
|
+
documentId: options.documentId,
|
|
1008
|
+
timestamp,
|
|
1009
|
+
numbering: parsedNumbering,
|
|
1010
|
+
media: mergedMedia,
|
|
1011
|
+
content: normalizedDocument.content,
|
|
1012
|
+
subParts,
|
|
1013
|
+
parsedStyles,
|
|
1014
|
+
fontTable: parsedFontTable,
|
|
1015
|
+
preservation: buildImportPreservation(normalizedDocument.preservation, sourcePackage, [
|
|
1016
|
+
mainDocumentPath,
|
|
1017
|
+
numberingPartPath,
|
|
1018
|
+
commentsPartPath,
|
|
1019
|
+
commentsExtendedPartPath,
|
|
1020
|
+
commentsIdsPartPath,
|
|
1021
|
+
peoplePartPath,
|
|
1022
|
+
...offloadOwnedPartPaths,
|
|
1023
|
+
]),
|
|
1024
|
+
diagnostics: {
|
|
1025
|
+
warnings: [
|
|
1026
|
+
...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
|
|
1027
|
+
...normalizedDocument.diagnostics.warnings,
|
|
1028
|
+
...mapRevisionDiagnosticsToWarnings(normalizedRevisions.diagnostics),
|
|
1029
|
+
...mapStoryRevisionDiagnosticsToWarnings(importedStoryRevisionDiagnostics),
|
|
1030
|
+
...mapCommentDiagnosticsToWarnings(normalizedComments.diagnostics),
|
|
1031
|
+
],
|
|
1032
|
+
errors: [],
|
|
1033
|
+
},
|
|
1034
|
+
review: {
|
|
1035
|
+
comments: toRuntimeCommentRecords(translatedWorkflowState.comments),
|
|
1036
|
+
revisions: toRuntimeRevisionRecords([
|
|
1037
|
+
...normalizedRevisions.revisions,
|
|
1038
|
+
...importedStoryRevisions,
|
|
1039
|
+
]),
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
// Stage 0B.1: if the host implements `renderChartPreview`, resolve
|
|
1043
|
+
// chart_preview nodes inline so the first snapshot already carries the
|
|
1044
|
+
// synthesized `previewMediaId`. Fallback-safe: returning null or throwing
|
|
1045
|
+
// is per-chart — the typed badge renders as if the adapter weren't set.
|
|
1046
|
+
// C3b: when onChartPreviewsReady is provided, defer resolution out of
|
|
1047
|
+
// the critical path (same pattern as the short-circuit branch above).
|
|
1048
|
+
let document: CanonicalDocument;
|
|
1049
|
+
if (options.onChartPreviewsReady) {
|
|
1050
|
+
scheduleChartPreviewResolution(
|
|
1051
|
+
importedDocument,
|
|
1052
|
+
sourcePackage,
|
|
1053
|
+
options.hostAdapter,
|
|
1054
|
+
options.onChartPreviewsReady,
|
|
1055
|
+
);
|
|
1056
|
+
document = importedDocument as CanonicalDocument;
|
|
1057
|
+
} else {
|
|
1058
|
+
document = (await resolveChartPreviewsForDocument(
|
|
1059
|
+
importedDocument,
|
|
1060
|
+
sourcePackage,
|
|
1061
|
+
options.hostAdapter,
|
|
1062
|
+
)) as CanonicalDocument;
|
|
1063
|
+
}
|
|
1064
|
+
const compatibility = buildCompatibilityReport({
|
|
1065
|
+
document,
|
|
1066
|
+
generatedAt: timestamp,
|
|
1067
|
+
});
|
|
1068
|
+
await scheduler.yield();
|
|
1069
|
+
const snapshot = createImportedSnapshot({
|
|
1070
|
+
documentId: options.documentId,
|
|
1071
|
+
editorBuild,
|
|
1072
|
+
timestamp,
|
|
1073
|
+
document,
|
|
1074
|
+
compatibility,
|
|
1075
|
+
protectionSnapshot: importedProtectionSnapshot,
|
|
1076
|
+
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
1077
|
+
workflowOverlay: translatedWorkflowState.workflowOverlay,
|
|
1078
|
+
workflowMetadata: translatedWorkflowState.workflowMetadata,
|
|
1079
|
+
visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
|
|
1080
|
+
markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
|
|
1081
|
+
});
|
|
1082
|
+
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
1083
|
+
if (snapshotIssues.length > 0) {
|
|
1084
|
+
const firstIssue = snapshotIssues[0];
|
|
1085
|
+
return createDiagnosticsSession(
|
|
1086
|
+
options,
|
|
1087
|
+
createValidationImportDiagnostics({
|
|
1088
|
+
message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
|
|
1089
|
+
source: "import",
|
|
1090
|
+
details: {
|
|
1091
|
+
issueCount: snapshotIssues.length,
|
|
1092
|
+
firstIssuePath: firstIssue?.path,
|
|
1093
|
+
},
|
|
1094
|
+
}),
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
|
|
1098
|
+
const importedState: ImportedDocxState = {
|
|
1099
|
+
sourceBytes: new Uint8Array(sourceBytes),
|
|
1100
|
+
sourcePackage,
|
|
1101
|
+
sourceDocumentXml,
|
|
1102
|
+
sourceDocumentPartPath: mainDocumentPath,
|
|
1103
|
+
sourceDocumentRelationships: documentPart.relationships,
|
|
1104
|
+
sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
|
|
1105
|
+
sourceNumberingPartPath: numberingPartPath,
|
|
1106
|
+
sourceNumberingRelationshipId: documentPart.relationships.find(
|
|
1107
|
+
(relationship) =>
|
|
1108
|
+
relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
|
|
1109
|
+
relationship.targetMode === "internal",
|
|
1110
|
+
)?.id,
|
|
1111
|
+
sourceSettingsPartPath: settingsPartPath,
|
|
1112
|
+
sourceSettingsXml:
|
|
1113
|
+
settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
|
|
1114
|
+
sourceCommentsPartPath: commentsPartPath,
|
|
1115
|
+
sourceCommentsRelationshipId: documentPart.relationships.find(
|
|
1116
|
+
(relationship) =>
|
|
1117
|
+
relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
|
|
1118
|
+
relationship.targetMode === "internal",
|
|
1119
|
+
)?.id,
|
|
1120
|
+
sourceCommentsRootTag: normalizedComments.sourceRootTag,
|
|
1121
|
+
sourceCommentsExtendedPartPath: commentsExtendedPartPath,
|
|
1122
|
+
sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
|
|
1123
|
+
(relationship) =>
|
|
1124
|
+
relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
|
|
1125
|
+
relationship.targetMode === "internal",
|
|
1126
|
+
)?.id,
|
|
1127
|
+
sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
|
|
1128
|
+
sourceCommentsIdsPartPath: commentsIdsPartPath,
|
|
1129
|
+
sourceCommentsIdsRelationshipId: documentPart.relationships.find(
|
|
1130
|
+
(relationship) =>
|
|
1131
|
+
relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
|
|
1132
|
+
relationship.targetMode === "internal",
|
|
1133
|
+
)?.id,
|
|
1134
|
+
sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
|
|
1135
|
+
sourcePeoplePartPath: peoplePartPath,
|
|
1136
|
+
sourcePeopleRelationshipId: documentPart.relationships.find(
|
|
1137
|
+
(relationship) =>
|
|
1138
|
+
relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
|
|
1139
|
+
relationship.targetMode === "internal",
|
|
1140
|
+
)?.id,
|
|
1141
|
+
sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
|
|
1142
|
+
sourcePeopleAuthors: normalizedComments.peopleAuthors,
|
|
1143
|
+
protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
|
|
1144
|
+
preservedCommentDefinitions: normalizedComments.preservedDefinitions,
|
|
1145
|
+
blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
|
|
1146
|
+
BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
|
|
1147
|
+
),
|
|
1148
|
+
initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
|
|
1149
|
+
sourceSubPartPaths: {
|
|
1150
|
+
headers: sourceHeaderPaths,
|
|
1151
|
+
footers: sourceFooterPaths,
|
|
1152
|
+
footnotesPartPath,
|
|
1153
|
+
footnotesRelationshipId,
|
|
1154
|
+
endnotesPartPath,
|
|
1155
|
+
endnotesRelationshipId,
|
|
1156
|
+
themePartPath,
|
|
1157
|
+
themeRelationshipId: themeRelationship?.id,
|
|
1158
|
+
},
|
|
1159
|
+
embeddingOffloadEntries: offloadEntries,
|
|
1160
|
+
...(options.hostAdapter !== undefined
|
|
1161
|
+
? { hostAdapter: options.hostAdapter }
|
|
1162
|
+
: {}),
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
stages.emit("skeleton-ready");
|
|
1166
|
+
return {
|
|
1167
|
+
initialSessionState,
|
|
1168
|
+
initialSnapshot: snapshot,
|
|
1169
|
+
readOnly: false,
|
|
1170
|
+
protectionSnapshot: importedProtectionSnapshot,
|
|
1171
|
+
exportDocx: async (nextSessionState, exportOptions) =>
|
|
1172
|
+
runStatefulExport(importedState, nextSessionState, exportOptions),
|
|
1173
|
+
embeddedDocumentManifests: embeddedDocumentManifestsEarly,
|
|
1174
|
+
...(embeddedWorkflowPayload?.editorState
|
|
1175
|
+
? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
|
|
1176
|
+
: {}),
|
|
1177
|
+
};
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
return createDiagnosticsSession(
|
|
1180
|
+
options,
|
|
1181
|
+
createImportDiagnosticsFromError(error),
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Session-layer owner of the sync load path.
|
|
1188
|
+
*
|
|
1189
|
+
* Slice 5e-7.e.2 relocates the body of `loadDocxEditorSession` from
|
|
1190
|
+
* `src/io/docx-session.ts` into this module. Body mirrors
|
|
1191
|
+
* `loadDocxSessionAsync` minus the async-only orchestration shell:
|
|
1192
|
+
* - No `scheduler.yield()` calls (sync execution).
|
|
1193
|
+
* - No laycache short-circuit branch (`laycacheEnvelope` is not on
|
|
1194
|
+
* `LoadDocxEditorSessionOptions`).
|
|
1195
|
+
* - No progressive-snapshot branch (`onProgressiveSnapshot` /
|
|
1196
|
+
* `surfaceProjector` not on `LoadDocxEditorSessionOptions`).
|
|
1197
|
+
* - Uses `normalizeParsedTextDocument` (sync).
|
|
1198
|
+
*
|
|
1199
|
+
* Byte-equivalence with the async path is asserted by
|
|
1200
|
+
* `test/io/fastload-parity.test.ts` on every F*.docx fixture. Drift
|
|
1201
|
+
* between the two sync/async bodies will fail that test.
|
|
1202
|
+
*/
|
|
1203
|
+
export function loadDocxSessionSync(
|
|
1204
|
+
options: LoadDocxEditorSessionOptions,
|
|
1205
|
+
): LoadedDocxEditorSession {
|
|
1206
|
+
const editorBuild =
|
|
1207
|
+
typeof options.editorBuild === "string" && options.editorBuild.length > 0
|
|
1208
|
+
? options.editorBuild
|
|
1209
|
+
: "dev";
|
|
1210
|
+
const sourceBytes = toUint8Array(options.bytes);
|
|
1211
|
+
|
|
1212
|
+
const stages = createStageEmitter(options.onLoadStage);
|
|
1213
|
+
|
|
1214
|
+
let sourcePackage: OpcPackage;
|
|
1215
|
+
|
|
1216
|
+
try {
|
|
1217
|
+
sourcePackage = readOpcPackage(sourceBytes);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
return createDiagnosticsSession(
|
|
1220
|
+
options,
|
|
1221
|
+
createPackageImportDiagnostics({
|
|
1222
|
+
issue: classifyCorruptPackageError(error),
|
|
1223
|
+
}),
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
stages.emit("opc");
|
|
1227
|
+
const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
|
|
1228
|
+
const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
|
|
1229
|
+
const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
|
|
1230
|
+
|
|
1231
|
+
const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
|
|
1232
|
+
const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
|
|
1233
|
+
sourcePackage,
|
|
1234
|
+
mainDocumentPath,
|
|
1235
|
+
);
|
|
1236
|
+
if (brokenRelationshipIssues.length > 0) {
|
|
1237
|
+
return createDiagnosticsSession(
|
|
1238
|
+
options,
|
|
1239
|
+
createPackageImportDiagnostics({
|
|
1240
|
+
issue: {
|
|
1241
|
+
...brokenRelationshipIssues[0],
|
|
1242
|
+
message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
|
|
1243
|
+
details: {
|
|
1244
|
+
issueCount: brokenRelationshipIssues.length,
|
|
1245
|
+
targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
|
|
1246
|
+
},
|
|
1247
|
+
},
|
|
1248
|
+
}),
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (!mainDocumentPath) {
|
|
1253
|
+
return createDiagnosticsSession(
|
|
1254
|
+
options,
|
|
1255
|
+
createPackageImportDiagnostics({
|
|
1256
|
+
issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
|
|
1257
|
+
}),
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const documentPart = sourcePackage.parts.get(mainDocumentPath);
|
|
1262
|
+
if (!documentPart) {
|
|
1263
|
+
return createDiagnosticsSession(
|
|
1264
|
+
options,
|
|
1265
|
+
createPackageImportDiagnostics({
|
|
1266
|
+
issue: createMissingPartIssue(mainDocumentPath),
|
|
1267
|
+
}),
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
|
|
1271
|
+
return createDiagnosticsSession(
|
|
1272
|
+
options,
|
|
1273
|
+
createValidationImportDiagnostics({
|
|
1274
|
+
message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
|
|
1275
|
+
}),
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
try {
|
|
1280
|
+
const sourceDocumentXml = decodeUtf8(documentPart.bytes);
|
|
1281
|
+
const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
|
|
1282
|
+
const numberingPartPath = resolveDocumentRelatedPartPath(
|
|
1283
|
+
sourcePackage,
|
|
1284
|
+
mainDocumentPath,
|
|
1285
|
+
documentPart.relationships,
|
|
1286
|
+
NUMBERING_RELATIONSHIP_TYPE,
|
|
1287
|
+
NUMBERING_PART_PATH,
|
|
1288
|
+
);
|
|
1289
|
+
const parsedNumbering = numberingPartPath
|
|
1290
|
+
? parseNumberingXml(
|
|
1291
|
+
decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
|
|
1292
|
+
{
|
|
1293
|
+
relationships: sourcePackage.parts.get(numberingPartPath)?.relationships,
|
|
1294
|
+
partPath: numberingPartPath,
|
|
1295
|
+
},
|
|
1296
|
+
)
|
|
1297
|
+
: createEmptyNumberingCatalog();
|
|
1298
|
+
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
1299
|
+
const chartPartLookup = createChartPartLookup(
|
|
1300
|
+
sourcePackage,
|
|
1301
|
+
mainDocumentPath,
|
|
1302
|
+
documentPart.relationships,
|
|
1303
|
+
);
|
|
1304
|
+
if (options.telemetryBus) {
|
|
1305
|
+
setActiveParseTelemetryBus(options.telemetryBus as ParseTelemetryBusParam);
|
|
1306
|
+
}
|
|
1307
|
+
let parsedDocument;
|
|
1308
|
+
try {
|
|
1309
|
+
parsedDocument = parseMainDocumentXml(
|
|
1310
|
+
sourceDocumentXml,
|
|
1311
|
+
documentPart.relationships,
|
|
1312
|
+
mediaParts,
|
|
1313
|
+
mainDocumentPath,
|
|
1314
|
+
chartPartLookup,
|
|
1315
|
+
);
|
|
1316
|
+
} finally {
|
|
1317
|
+
if (options.telemetryBus) setActiveParseTelemetryBus(undefined);
|
|
1318
|
+
}
|
|
1319
|
+
const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
|
|
1320
|
+
const normalizedDocument = normalizeParsedTextDocument(
|
|
1321
|
+
parsedDocument,
|
|
1322
|
+
mainDocumentPath,
|
|
1323
|
+
);
|
|
1324
|
+
stages.emit("body");
|
|
1325
|
+
const commentsPartPath = resolveCommentsPartPath(
|
|
1326
|
+
sourcePackage,
|
|
1327
|
+
mainDocumentPath,
|
|
1328
|
+
documentPart.relationships,
|
|
1329
|
+
);
|
|
1330
|
+
const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
|
|
1331
|
+
sourcePackage,
|
|
1332
|
+
mainDocumentPath,
|
|
1333
|
+
documentPart.relationships,
|
|
1334
|
+
COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
1335
|
+
COMMENTS_EXTENDED_PART_PATH,
|
|
1336
|
+
);
|
|
1337
|
+
const commentsIdsPartPath = resolveDocumentRelatedPartPath(
|
|
1338
|
+
sourcePackage,
|
|
1339
|
+
mainDocumentPath,
|
|
1340
|
+
documentPart.relationships,
|
|
1341
|
+
COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
1342
|
+
COMMENTS_IDS_PART_PATH,
|
|
1343
|
+
);
|
|
1344
|
+
const peoplePartPath = resolveDocumentRelatedPartPath(
|
|
1345
|
+
sourcePackage,
|
|
1346
|
+
mainDocumentPath,
|
|
1347
|
+
documentPart.relationships,
|
|
1348
|
+
PEOPLE_RELATIONSHIP_TYPE,
|
|
1349
|
+
PEOPLE_PART_PATH,
|
|
1350
|
+
);
|
|
1351
|
+
const parsedComments = commentsPartPath
|
|
1352
|
+
? parseCommentsFromOoxml(
|
|
1353
|
+
sourceDocumentXml,
|
|
1354
|
+
{
|
|
1355
|
+
commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
|
|
1356
|
+
commentsExtendedXml: decodeUtf8(
|
|
1357
|
+
sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
1358
|
+
),
|
|
1359
|
+
commentsIdsXml: decodeUtf8(
|
|
1360
|
+
sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
1361
|
+
),
|
|
1362
|
+
peopleXml: decodeUtf8(
|
|
1363
|
+
sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
1364
|
+
),
|
|
1365
|
+
},
|
|
1366
|
+
)
|
|
1367
|
+
: {
|
|
1368
|
+
threads: [] as CommentThread[],
|
|
1369
|
+
diagnostics: [] as CommentImportDiagnostic[],
|
|
1370
|
+
definitions: [] as ImportedCommentDefinition[],
|
|
1371
|
+
sourceRootTag: undefined,
|
|
1372
|
+
sourceExtendedRootTag: undefined,
|
|
1373
|
+
sourceIdsRootTag: undefined,
|
|
1374
|
+
sourcePeopleRootTag: undefined,
|
|
1375
|
+
peopleAuthors: [] as string[],
|
|
1376
|
+
};
|
|
1377
|
+
const normalizedRevisions = normalizeImportedRevisionRecords(
|
|
1378
|
+
importedRevisions,
|
|
1379
|
+
normalizedDocument.content,
|
|
1380
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1381
|
+
);
|
|
1382
|
+
const normalizedComments = normalizeImportedCommentThreads(
|
|
1383
|
+
parsedComments,
|
|
1384
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1385
|
+
normalizedRevisions.revisions,
|
|
1386
|
+
);
|
|
1387
|
+
stages.emit("styles-numbering-comments");
|
|
1388
|
+
const importedStoryRevisions: ReviewRevisionRecord[] = [];
|
|
1389
|
+
const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
|
|
1390
|
+
const subPartOpaqueState = createSubPartOpaqueImportState(
|
|
1391
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1392
|
+
normalizedDocument.diagnostics.warnings,
|
|
1393
|
+
);
|
|
1394
|
+
// ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
|
|
1395
|
+
const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
|
|
1396
|
+
const parsedHeaders: HeaderDocument[] = [];
|
|
1397
|
+
const parsedFooters: FooterDocument[] = [];
|
|
1398
|
+
const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
1399
|
+
const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
1400
|
+
const seenSubPartKeys = new Set<string>();
|
|
1401
|
+
|
|
1402
|
+
for (const ref of headerFooterRefs) {
|
|
1403
|
+
const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
|
|
1404
|
+
if (seenSubPartKeys.has(dedupeKey)) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
seenSubPartKeys.add(dedupeKey);
|
|
1408
|
+
|
|
1409
|
+
const relationship = documentPart.relationships.find(
|
|
1410
|
+
(r) => r.id === ref.relationshipId && r.targetMode === "internal",
|
|
1411
|
+
);
|
|
1412
|
+
if (!relationship) {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
|
|
1417
|
+
const part = sourcePackage.parts.get(partPath);
|
|
1418
|
+
const partBytes = part?.bytes;
|
|
1419
|
+
if (!partBytes) {
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const xml = decodeUtf8(partBytes);
|
|
1424
|
+
const subPartRelationships = part?.relationships ?? [];
|
|
1425
|
+
const subPartChartPartLookup = createChartPartLookup(
|
|
1426
|
+
sourcePackage,
|
|
1427
|
+
partPath,
|
|
1428
|
+
subPartRelationships,
|
|
1429
|
+
);
|
|
1430
|
+
if (ref.kind === "header") {
|
|
1431
|
+
const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
|
|
1432
|
+
const parsed = parseHeaderXml(xml, {
|
|
1433
|
+
relationships: subPartRelationships,
|
|
1434
|
+
mediaParts,
|
|
1435
|
+
sourcePartPath: partPath,
|
|
1436
|
+
chartPartLookup: subPartChartPartLookup,
|
|
1437
|
+
});
|
|
1438
|
+
parsedHeaders.push({
|
|
1439
|
+
variant: ref.variant,
|
|
1440
|
+
partPath,
|
|
1441
|
+
relationshipId: ref.relationshipId,
|
|
1442
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
1443
|
+
blocks: normalizeSubPartOpaqueBlocks(
|
|
1444
|
+
parsed.blocks,
|
|
1445
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1446
|
+
normalizedDocument.diagnostics.warnings,
|
|
1447
|
+
partPath,
|
|
1448
|
+
subPartOpaqueState,
|
|
1449
|
+
),
|
|
1450
|
+
});
|
|
1451
|
+
importedStoryRevisions.push(
|
|
1452
|
+
...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
1453
|
+
...revision,
|
|
1454
|
+
metadata: {
|
|
1455
|
+
...revision.metadata,
|
|
1456
|
+
storyTarget: {
|
|
1457
|
+
kind: "header" as const,
|
|
1458
|
+
relationshipId: ref.relationshipId,
|
|
1459
|
+
variant: ref.variant,
|
|
1460
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
1461
|
+
},
|
|
1462
|
+
},
|
|
1463
|
+
})),
|
|
1464
|
+
);
|
|
1465
|
+
importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
|
|
1466
|
+
sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
1467
|
+
} else {
|
|
1468
|
+
const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
|
|
1469
|
+
const parsed = parseFooterXml(xml, {
|
|
1470
|
+
relationships: subPartRelationships,
|
|
1471
|
+
mediaParts,
|
|
1472
|
+
sourcePartPath: partPath,
|
|
1473
|
+
chartPartLookup: subPartChartPartLookup,
|
|
1474
|
+
});
|
|
1475
|
+
parsedFooters.push({
|
|
1476
|
+
variant: ref.variant,
|
|
1477
|
+
partPath,
|
|
1478
|
+
relationshipId: ref.relationshipId,
|
|
1479
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
1480
|
+
blocks: normalizeSubPartOpaqueBlocks(
|
|
1481
|
+
parsed.blocks,
|
|
1482
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1483
|
+
normalizedDocument.diagnostics.warnings,
|
|
1484
|
+
partPath,
|
|
1485
|
+
subPartOpaqueState,
|
|
1486
|
+
),
|
|
1487
|
+
});
|
|
1488
|
+
importedStoryRevisions.push(
|
|
1489
|
+
...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
1490
|
+
...revision,
|
|
1491
|
+
metadata: {
|
|
1492
|
+
...revision.metadata,
|
|
1493
|
+
storyTarget: {
|
|
1494
|
+
kind: "footer" as const,
|
|
1495
|
+
relationshipId: ref.relationshipId,
|
|
1496
|
+
variant: ref.variant,
|
|
1497
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
1498
|
+
},
|
|
1499
|
+
},
|
|
1500
|
+
})),
|
|
1501
|
+
);
|
|
1502
|
+
importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
|
|
1503
|
+
sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const footnotesPartPath = resolveDocumentRelatedPartPath(
|
|
1508
|
+
sourcePackage,
|
|
1509
|
+
mainDocumentPath,
|
|
1510
|
+
documentPart.relationships,
|
|
1511
|
+
FOOTNOTES_RELATIONSHIP_TYPE,
|
|
1512
|
+
FOOTNOTES_PART_PATH,
|
|
1513
|
+
);
|
|
1514
|
+
const footnotesRelationshipId = documentPart.relationships.find(
|
|
1515
|
+
(r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
1516
|
+
)?.id;
|
|
1517
|
+
const endnotesPartPath = resolveDocumentRelatedPartPath(
|
|
1518
|
+
sourcePackage,
|
|
1519
|
+
mainDocumentPath,
|
|
1520
|
+
documentPart.relationships,
|
|
1521
|
+
ENDNOTES_RELATIONSHIP_TYPE,
|
|
1522
|
+
ENDNOTES_PART_PATH,
|
|
1523
|
+
);
|
|
1524
|
+
const endnotesRelationshipId = documentPart.relationships.find(
|
|
1525
|
+
(r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
1526
|
+
)?.id;
|
|
1527
|
+
|
|
1528
|
+
let footnoteCollection: FootnoteCollection | undefined;
|
|
1529
|
+
if (footnotesPartPath) {
|
|
1530
|
+
footnoteCollection = parseFootnotesXml(
|
|
1531
|
+
decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
1532
|
+
);
|
|
1533
|
+
normalizeFootnoteCollectionOpaqueBlocks(
|
|
1534
|
+
footnoteCollection,
|
|
1535
|
+
"footnote",
|
|
1536
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1537
|
+
normalizedDocument.diagnostics.warnings,
|
|
1538
|
+
footnotesPartPath,
|
|
1539
|
+
subPartOpaqueState,
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
if (endnotesPartPath) {
|
|
1543
|
+
footnoteCollection = parseEndnotesXml(
|
|
1544
|
+
decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
1545
|
+
footnoteCollection,
|
|
1546
|
+
);
|
|
1547
|
+
normalizeFootnoteCollectionOpaqueBlocks(
|
|
1548
|
+
footnoteCollection,
|
|
1549
|
+
"endnote",
|
|
1550
|
+
normalizedDocument.preservation.opaqueFragments,
|
|
1551
|
+
normalizedDocument.diagnostics.warnings,
|
|
1552
|
+
endnotesPartPath,
|
|
1553
|
+
subPartOpaqueState,
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
const themeRelationship = documentPart.relationships.find(
|
|
1558
|
+
(r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
|
|
1559
|
+
r.targetMode === "internal",
|
|
1560
|
+
);
|
|
1561
|
+
const themePartPath = themeRelationship
|
|
1562
|
+
? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
|
|
1563
|
+
: undefined;
|
|
1564
|
+
const parsedTheme =
|
|
1565
|
+
themePartPath && sourcePackage.parts.has(themePartPath)
|
|
1566
|
+
? parseThemeXml(
|
|
1567
|
+
decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
|
|
1568
|
+
)
|
|
1569
|
+
: undefined;
|
|
1570
|
+
const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
|
|
1571
|
+
const settingsPartPath = resolveDocumentRelatedPartPath(
|
|
1572
|
+
sourcePackage,
|
|
1573
|
+
mainDocumentPath,
|
|
1574
|
+
documentPart.relationships,
|
|
1575
|
+
SETTINGS_RELATIONSHIP_TYPE,
|
|
1576
|
+
SETTINGS_PART_PATH,
|
|
1577
|
+
);
|
|
1578
|
+
const parsedSettings =
|
|
1579
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
1580
|
+
? parseSettingsXml(
|
|
1581
|
+
decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
|
|
1582
|
+
)
|
|
1583
|
+
: undefined;
|
|
1584
|
+
const canonicalTheme =
|
|
1585
|
+
parsedTheme !== undefined
|
|
1586
|
+
? materializeCanonicalTheme(
|
|
1587
|
+
parsedTheme,
|
|
1588
|
+
parsedSettings?.clrSchemeMapping ?? {},
|
|
1589
|
+
)
|
|
1590
|
+
: undefined;
|
|
1591
|
+
const settingsXmlForProtection =
|
|
1592
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
1593
|
+
? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
|
|
1594
|
+
: "";
|
|
1595
|
+
const documentProtection = extractDocumentProtection(settingsXmlForProtection);
|
|
1596
|
+
const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
|
|
1597
|
+
|
|
1598
|
+
// ---- Parse styles.xml for canonical style catalog ----
|
|
1599
|
+
const stylesPartPath = resolveDocumentRelatedPartPath(
|
|
1600
|
+
sourcePackage,
|
|
1601
|
+
mainDocumentPath,
|
|
1602
|
+
documentPart.relationships,
|
|
1603
|
+
STYLES_RELATIONSHIP_TYPE,
|
|
1604
|
+
STYLES_PART_PATH,
|
|
1605
|
+
);
|
|
1606
|
+
const parsedStyles =
|
|
1607
|
+
stylesPartPath && sourcePackage.parts.has(stylesPartPath)
|
|
1608
|
+
? parseStylesXml(
|
|
1609
|
+
decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
|
|
1610
|
+
)
|
|
1611
|
+
: parseStylesXml("");
|
|
1612
|
+
|
|
1613
|
+
// ---- Parse fontTable.xml for canonical font catalog ----
|
|
1614
|
+
const fontTablePartPath = resolveDocumentRelatedPartPath(
|
|
1615
|
+
sourcePackage,
|
|
1616
|
+
mainDocumentPath,
|
|
1617
|
+
documentPart.relationships,
|
|
1618
|
+
FONT_TABLE_RELATIONSHIP_TYPE,
|
|
1619
|
+
FONT_TABLE_PART_PATH,
|
|
1620
|
+
);
|
|
1621
|
+
const parsedFontTable =
|
|
1622
|
+
fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
|
|
1623
|
+
? parseFontTable(
|
|
1624
|
+
decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
|
|
1625
|
+
)
|
|
1626
|
+
: undefined;
|
|
1627
|
+
|
|
1628
|
+
const mergedMedia = mergeSecondaryStoryMediaCatalog(normalizedDocument.media, {
|
|
1629
|
+
headers: parsedHeaders,
|
|
1630
|
+
footers: parsedFooters,
|
|
1631
|
+
footnoteCollection,
|
|
1632
|
+
mediaParts,
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
const subParts: SubPartsCatalog | undefined =
|
|
1636
|
+
parsedHeaders.length > 0 ||
|
|
1637
|
+
parsedFooters.length > 0 ||
|
|
1638
|
+
footnoteCollection !== undefined ||
|
|
1639
|
+
parsedTheme !== undefined ||
|
|
1640
|
+
normalizedDocument.finalSectionProperties !== undefined ||
|
|
1641
|
+
resolvedTheme !== undefined ||
|
|
1642
|
+
canonicalTheme !== undefined ||
|
|
1643
|
+
parsedSettings !== undefined
|
|
1644
|
+
? {
|
|
1645
|
+
headers: parsedHeaders,
|
|
1646
|
+
footers: parsedFooters,
|
|
1647
|
+
...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
|
|
1648
|
+
...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
|
|
1649
|
+
...(normalizedDocument.finalSectionProperties !== undefined
|
|
1650
|
+
? { finalSectionProperties: normalizedDocument.finalSectionProperties }
|
|
1651
|
+
: {}),
|
|
1652
|
+
...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
|
|
1653
|
+
...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
|
|
1654
|
+
...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
|
|
1655
|
+
}
|
|
1656
|
+
: undefined;
|
|
1657
|
+
|
|
1658
|
+
const timestamp = new Date().toISOString();
|
|
1659
|
+
const translatedWorkflowState = translateClmCommentsToWorkflow({
|
|
1660
|
+
comments: normalizedComments.threads,
|
|
1661
|
+
workflowOverlay: embeddedWorkflowOverlay,
|
|
1662
|
+
workflowMetadata: embeddedWorkflowMetadata,
|
|
1663
|
+
timestamp,
|
|
1664
|
+
});
|
|
1665
|
+
// P8 Step 7 — sync loader path never calls `storeEmbeddedDocument`
|
|
1666
|
+
// (the host adapter is async, and this entire loader is sync by
|
|
1667
|
+
// contract — it's the SSR / Node fallback used by
|
|
1668
|
+
// `editor-runtime-boundary`'s `createRuntime`). The REOPEN path
|
|
1669
|
+
// is still supported: if the package already carries a
|
|
1670
|
+
// `bw:embeddings` customXml namespace from a prior offload, we
|
|
1671
|
+
// hydrate entries so the export reconstitute path runs correctly.
|
|
1672
|
+
const rehydratedOffloadSync = hydrateOffloadEntriesFromPayload(
|
|
1673
|
+
embeddedWorkflowPayload?.editorState,
|
|
1674
|
+
);
|
|
1675
|
+
const embeddedDocumentManifestsSync = collectEmbeddedDocuments(
|
|
1676
|
+
sourcePackage,
|
|
1677
|
+
mainDocumentPath,
|
|
1678
|
+
).manifests;
|
|
1679
|
+
const document = createImportedCanonicalDocument({
|
|
1680
|
+
documentId: options.documentId,
|
|
1681
|
+
timestamp,
|
|
1682
|
+
numbering: parsedNumbering,
|
|
1683
|
+
media: mergedMedia,
|
|
1684
|
+
content: normalizedDocument.content,
|
|
1685
|
+
subParts,
|
|
1686
|
+
parsedStyles,
|
|
1687
|
+
fontTable: parsedFontTable,
|
|
1688
|
+
preservation: buildImportPreservation(normalizedDocument.preservation, sourcePackage, [
|
|
1689
|
+
mainDocumentPath,
|
|
1690
|
+
numberingPartPath,
|
|
1691
|
+
commentsPartPath,
|
|
1692
|
+
commentsExtendedPartPath,
|
|
1693
|
+
commentsIdsPartPath,
|
|
1694
|
+
peoplePartPath,
|
|
1695
|
+
...rehydratedOffloadSync.ownedPartPaths,
|
|
1696
|
+
]),
|
|
1697
|
+
diagnostics: {
|
|
1698
|
+
warnings: [
|
|
1699
|
+
...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
|
|
1700
|
+
...normalizedDocument.diagnostics.warnings,
|
|
1701
|
+
...mapRevisionDiagnosticsToWarnings(normalizedRevisions.diagnostics),
|
|
1702
|
+
...mapStoryRevisionDiagnosticsToWarnings(importedStoryRevisionDiagnostics),
|
|
1703
|
+
...mapCommentDiagnosticsToWarnings(normalizedComments.diagnostics),
|
|
1704
|
+
],
|
|
1705
|
+
errors: [],
|
|
1706
|
+
},
|
|
1707
|
+
review: {
|
|
1708
|
+
comments: toRuntimeCommentRecords(translatedWorkflowState.comments),
|
|
1709
|
+
revisions: toRuntimeRevisionRecords([
|
|
1710
|
+
...normalizedRevisions.revisions,
|
|
1711
|
+
...importedStoryRevisions,
|
|
1712
|
+
]),
|
|
1713
|
+
},
|
|
1714
|
+
});
|
|
1715
|
+
const compatibility = buildCompatibilityReport({
|
|
1716
|
+
document,
|
|
1717
|
+
generatedAt: timestamp,
|
|
1718
|
+
});
|
|
1719
|
+
const snapshot = createImportedSnapshot({
|
|
1720
|
+
documentId: options.documentId,
|
|
1721
|
+
editorBuild,
|
|
1722
|
+
timestamp,
|
|
1723
|
+
document,
|
|
1724
|
+
compatibility,
|
|
1725
|
+
protectionSnapshot: importedProtectionSnapshot,
|
|
1726
|
+
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
1727
|
+
workflowOverlay: translatedWorkflowState.workflowOverlay,
|
|
1728
|
+
workflowMetadata: translatedWorkflowState.workflowMetadata,
|
|
1729
|
+
visibilityPolicies: embeddedWorkflowPayload?.visibilityPolicies,
|
|
1730
|
+
markupModePolicy: embeddedWorkflowPayload?.markupModePolicy ?? undefined,
|
|
1731
|
+
});
|
|
1732
|
+
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
1733
|
+
if (snapshotIssues.length > 0) {
|
|
1734
|
+
const firstIssue = snapshotIssues[0];
|
|
1735
|
+
return createDiagnosticsSession(
|
|
1736
|
+
options,
|
|
1737
|
+
createValidationImportDiagnostics({
|
|
1738
|
+
message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
|
|
1739
|
+
source: "import",
|
|
1740
|
+
details: {
|
|
1741
|
+
issueCount: snapshotIssues.length,
|
|
1742
|
+
firstIssuePath: firstIssue?.path,
|
|
1743
|
+
},
|
|
1744
|
+
}),
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
|
|
1748
|
+
const importedState: ImportedDocxState = {
|
|
1749
|
+
sourceBytes: new Uint8Array(sourceBytes),
|
|
1750
|
+
sourcePackage,
|
|
1751
|
+
sourceDocumentXml,
|
|
1752
|
+
sourceDocumentPartPath: mainDocumentPath,
|
|
1753
|
+
sourceDocumentRelationships: documentPart.relationships,
|
|
1754
|
+
sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
|
|
1755
|
+
sourceNumberingPartPath: numberingPartPath,
|
|
1756
|
+
sourceNumberingRelationshipId: documentPart.relationships.find(
|
|
1757
|
+
(relationship) =>
|
|
1758
|
+
relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
|
|
1759
|
+
relationship.targetMode === "internal",
|
|
1760
|
+
)?.id,
|
|
1761
|
+
sourceSettingsPartPath: settingsPartPath,
|
|
1762
|
+
sourceSettingsXml:
|
|
1763
|
+
settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
|
|
1764
|
+
sourceCommentsPartPath: commentsPartPath,
|
|
1765
|
+
sourceCommentsRelationshipId: documentPart.relationships.find(
|
|
1766
|
+
(relationship) =>
|
|
1767
|
+
relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
|
|
1768
|
+
relationship.targetMode === "internal",
|
|
1769
|
+
)?.id,
|
|
1770
|
+
sourceCommentsRootTag: normalizedComments.sourceRootTag,
|
|
1771
|
+
sourceCommentsExtendedPartPath: commentsExtendedPartPath,
|
|
1772
|
+
sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
|
|
1773
|
+
(relationship) =>
|
|
1774
|
+
relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
|
|
1775
|
+
relationship.targetMode === "internal",
|
|
1776
|
+
)?.id,
|
|
1777
|
+
sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
|
|
1778
|
+
sourceCommentsIdsPartPath: commentsIdsPartPath,
|
|
1779
|
+
sourceCommentsIdsRelationshipId: documentPart.relationships.find(
|
|
1780
|
+
(relationship) =>
|
|
1781
|
+
relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
|
|
1782
|
+
relationship.targetMode === "internal",
|
|
1783
|
+
)?.id,
|
|
1784
|
+
sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
|
|
1785
|
+
sourcePeoplePartPath: peoplePartPath,
|
|
1786
|
+
sourcePeopleRelationshipId: documentPart.relationships.find(
|
|
1787
|
+
(relationship) =>
|
|
1788
|
+
relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
|
|
1789
|
+
relationship.targetMode === "internal",
|
|
1790
|
+
)?.id,
|
|
1791
|
+
sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
|
|
1792
|
+
sourcePeopleAuthors: normalizedComments.peopleAuthors,
|
|
1793
|
+
protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
|
|
1794
|
+
preservedCommentDefinitions: normalizedComments.preservedDefinitions,
|
|
1795
|
+
blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
|
|
1796
|
+
BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
|
|
1797
|
+
),
|
|
1798
|
+
initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
|
|
1799
|
+
sourceSubPartPaths: {
|
|
1800
|
+
headers: sourceHeaderPaths,
|
|
1801
|
+
footers: sourceFooterPaths,
|
|
1802
|
+
footnotesPartPath,
|
|
1803
|
+
footnotesRelationshipId,
|
|
1804
|
+
endnotesPartPath,
|
|
1805
|
+
endnotesRelationshipId,
|
|
1806
|
+
themePartPath,
|
|
1807
|
+
themeRelationshipId: themeRelationship?.id,
|
|
1808
|
+
},
|
|
1809
|
+
embeddingOffloadEntries: rehydratedOffloadSync.entries,
|
|
1810
|
+
...(options.hostAdapter !== undefined
|
|
1811
|
+
? { hostAdapter: options.hostAdapter }
|
|
1812
|
+
: {}),
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
stages.emit("skeleton-ready");
|
|
1816
|
+
return {
|
|
1817
|
+
initialSessionState,
|
|
1818
|
+
initialSnapshot: snapshot,
|
|
1819
|
+
readOnly: false,
|
|
1820
|
+
protectionSnapshot: importedProtectionSnapshot,
|
|
1821
|
+
exportDocx: async (nextSessionState, exportOptions) =>
|
|
1822
|
+
runStatefulExport(importedState, nextSessionState, exportOptions),
|
|
1823
|
+
embeddedDocumentManifests: embeddedDocumentManifestsSync,
|
|
1824
|
+
...(embeddedWorkflowPayload?.editorState
|
|
1825
|
+
? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
|
|
1826
|
+
: {}),
|
|
1827
|
+
};
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
return createDiagnosticsSession(
|
|
1830
|
+
options,
|
|
1831
|
+
createImportDiagnosticsFromError(error),
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
}
|