@beyondwork/docx-react-component 1.0.67 → 1.0.70
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 -932
- 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 -4797
- 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,903 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 03 — production formatting orchestrator (`FormattingContext`).
|
|
3
|
+
*
|
|
4
|
+
* The authoritative entry point for the hot path. One context is built per
|
|
5
|
+
* canonical-document projection pass (`surface-projection`, layout resolve
|
|
6
|
+
* pass, etc.); it internally owns the per-document resolvers that the
|
|
7
|
+
* layer previously forced every consumer to construct on their own:
|
|
8
|
+
*
|
|
9
|
+
* - `ThemeColorResolver` (theme-color.ts)
|
|
10
|
+
* - `NumberingPrefixResolver` (numbering/prefix.ts, stateful counters)
|
|
11
|
+
* - the `StylesCatalog` for cascade walks
|
|
12
|
+
* - the `FontTable` for ECMA-376 §17.3.2.26 font precedence
|
|
13
|
+
* - the `FieldResolver` (when a page-graph is threaded in)
|
|
14
|
+
*
|
|
15
|
+
* Every method below is a production boundary call. The earlier
|
|
16
|
+
* `resolveEffectiveFormatting(doc, nodeRef)` API is a thin façade built on
|
|
17
|
+
* top of this context so tests + external callers exercise the same code
|
|
18
|
+
* path as the hot production path.
|
|
19
|
+
*
|
|
20
|
+
* Architecture contract F1: every consumer that needs effective formatting
|
|
21
|
+
* flows through this context (directly, or via
|
|
22
|
+
* `resolveEffectiveFormatting`). Scattered direct calls to sub-resolvers
|
|
23
|
+
* from non-Layer-03 code are rejected by
|
|
24
|
+
* `scripts/ci-check-formatting-layer-purity.mjs` (guards under
|
|
25
|
+
* `src/runtime/formatting/`) and by
|
|
26
|
+
* `scripts/ci-check-formatting-production-boundary.mjs` (guards the
|
|
27
|
+
* production consumers that migrated).
|
|
28
|
+
*
|
|
29
|
+
* Per F2, the context holds NO document state — it only caches the
|
|
30
|
+
* resolvers built from the input document. Caller is responsible for
|
|
31
|
+
* discarding the context when the document changes.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type {
|
|
35
|
+
CanonicalDocument,
|
|
36
|
+
CanonicalFontTable,
|
|
37
|
+
CanonicalParagraphFormatting,
|
|
38
|
+
CanonicalRunFormatting,
|
|
39
|
+
CanonicalTheme,
|
|
40
|
+
FieldRegistryEntry,
|
|
41
|
+
HyperlinkNode,
|
|
42
|
+
InlineNode,
|
|
43
|
+
ParagraphNode,
|
|
44
|
+
RevisionRecord,
|
|
45
|
+
TableNode,
|
|
46
|
+
TableStyleConditionalRegion,
|
|
47
|
+
} from "../../model/canonical-document.ts";
|
|
48
|
+
import type { FieldPageGraph, FieldResolver, ResolvedField } from "./field/resolver.ts";
|
|
49
|
+
import { createFieldResolver } from "./field/resolver.ts";
|
|
50
|
+
import { ThemeColorResolver, concretizeThemeColors } from "./theme-color.ts";
|
|
51
|
+
import type { NumberingPrefixResolver, NumberingPrefixResult } from "./numbering/prefix.ts";
|
|
52
|
+
import { createNumberingPrefixResolver } from "./numbering/prefix.ts";
|
|
53
|
+
import {
|
|
54
|
+
resolveEffectiveParagraphFormatting,
|
|
55
|
+
resolveEffectiveRunFormatting,
|
|
56
|
+
resolveNumberingMarkerRunFormatting,
|
|
57
|
+
resolveTableCellTextFormatting,
|
|
58
|
+
type RunResolveInput,
|
|
59
|
+
} from "./style-cascade.ts";
|
|
60
|
+
import { resolveParagraphStyleChain } from "./paragraph-style-resolver.ts";
|
|
61
|
+
import { resolveTableStyleResolution, type ResolvedTableStyleResolution } from "./table-style-resolver.ts";
|
|
62
|
+
import { resolveHyperlinkRunFormatting } from "./hyperlink-color.ts";
|
|
63
|
+
import { resolveRunFontFamily } from "./font-resolution.ts";
|
|
64
|
+
import { applyRevisionDisplay } from "./revision-display.ts";
|
|
65
|
+
import type {
|
|
66
|
+
EffectiveFieldDisplay,
|
|
67
|
+
EffectiveNumbering,
|
|
68
|
+
EffectiveParagraphFormatting,
|
|
69
|
+
EffectiveRunFormatting,
|
|
70
|
+
} from "./formatting-types.ts";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Direct-text-mark direct formatting. Mirrors (and replaces) the
|
|
74
|
+
* `buildDirectRunFormattingFromMarks` helper that previously lived
|
|
75
|
+
* inside `surface-projection.ts`. The production hot path passes
|
|
76
|
+
* projected marks + markAttrs in; `resolveEffectiveFormatting(doc,
|
|
77
|
+
* nodeRef)` callers use `resolveDirectRunFormattingAtSegment` on a
|
|
78
|
+
* canonical `TextNode` instead.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Projected run marks — a string-literal union a size wider than the
|
|
82
|
+
* canonical direct-formatting projection. The extra marks (`superscript`,
|
|
83
|
+
* `subscript`, `emboss`, `imprint`, `shadow`) are preserved through the
|
|
84
|
+
* surface snapshot but have no direct-formatting projection into
|
|
85
|
+
* `CanonicalRunFormatting`; they flow through the surface marks array
|
|
86
|
+
* untouched. The context ignores them.
|
|
87
|
+
*/
|
|
88
|
+
export type ProjectedSurfaceMark =
|
|
89
|
+
| "bold"
|
|
90
|
+
| "italic"
|
|
91
|
+
| "underline"
|
|
92
|
+
| "strikethrough"
|
|
93
|
+
| "doubleStrikethrough"
|
|
94
|
+
| "superscript"
|
|
95
|
+
| "subscript"
|
|
96
|
+
| "vanish"
|
|
97
|
+
| "emboss"
|
|
98
|
+
| "imprint"
|
|
99
|
+
| "shadow"
|
|
100
|
+
| "smallCaps"
|
|
101
|
+
| "allCaps";
|
|
102
|
+
|
|
103
|
+
export interface ProjectedRunMarks {
|
|
104
|
+
readonly marks?: ReadonlyArray<ProjectedSurfaceMark>;
|
|
105
|
+
readonly markAttrs?: {
|
|
106
|
+
readonly backgroundColor?: string;
|
|
107
|
+
readonly charSpacing?: number;
|
|
108
|
+
readonly kerning?: number;
|
|
109
|
+
readonly textFill?: string;
|
|
110
|
+
readonly fontFamily?: string;
|
|
111
|
+
readonly fontSize?: number;
|
|
112
|
+
readonly textColor?: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Numbering resolution result shape returned to the production consumer.
|
|
118
|
+
* Kept structurally compatible with the legacy `NumberingPrefixResult`
|
|
119
|
+
* from `./numbering/prefix.ts` so surface-projection can keep its
|
|
120
|
+
* downstream adapters unchanged.
|
|
121
|
+
*/
|
|
122
|
+
export type NumberingResolution = NumberingPrefixResult;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cascade-tier provenance for a run resolution. Research doc
|
|
126
|
+
* `ResolvedProperty { value, source, sourceId }` maps onto this shape:
|
|
127
|
+
* each populated field points at the tier that won, plus (for style
|
|
128
|
+
* tiers) the styleId whose rPr carried the winning value.
|
|
129
|
+
*
|
|
130
|
+
* `source: "direct"` ≥ `source: "characterStyle"` ≥
|
|
131
|
+
* `source: "style"` ≥ `source: "docDefaults"` — highest priority wins.
|
|
132
|
+
*/
|
|
133
|
+
export interface RunResolvedProperty<TValue = unknown> {
|
|
134
|
+
readonly value: TValue;
|
|
135
|
+
readonly source: "direct" | "characterStyle" | "style" | "docDefaults";
|
|
136
|
+
/** Winning styleId for `style` / `characterStyle` sources. */
|
|
137
|
+
readonly sourceId?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface RunProvenance {
|
|
141
|
+
readonly run: EffectiveRunFormatting;
|
|
142
|
+
readonly properties: { readonly [K in keyof CanonicalRunFormatting]?: RunResolvedProperty };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface FormattingContextOptions {
|
|
146
|
+
readonly fontTable?: CanonicalFontTable;
|
|
147
|
+
/** Active markup mode. When set, per-run resolution attaches
|
|
148
|
+
* `revisionDisplay` flags via `applyRevisionDisplay`. */
|
|
149
|
+
readonly revisionMarkupMode?: "clean" | "simple" | "all";
|
|
150
|
+
/**
|
|
151
|
+
* Live markup-mode callback. When set, it is consulted on every
|
|
152
|
+
* per-run resolve — composed L06 `workflow.getMarkupModePolicy()`
|
|
153
|
+
* (class-A document default) with L10 `ui.viewport.getEffectiveMarkupMode()`
|
|
154
|
+
* (class-C user preference) drive the active mode without the
|
|
155
|
+
* context being rebuilt. Precedence: callback result (if not
|
|
156
|
+
* `undefined`) wins over the static `revisionMarkupMode` field.
|
|
157
|
+
*
|
|
158
|
+
* The layer itself stays F6-neutral (applies posture, does not decide
|
|
159
|
+
* the mode — see `03-formatting-semantics.md` §F6). When the callback
|
|
160
|
+
* returns `undefined`, no posture is applied on that pass (same as
|
|
161
|
+
* the static field being unset).
|
|
162
|
+
*/
|
|
163
|
+
readonly getEffectiveMarkupMode?: () =>
|
|
164
|
+
| "clean"
|
|
165
|
+
| "simple"
|
|
166
|
+
| "all"
|
|
167
|
+
| undefined;
|
|
168
|
+
/** Author-id → color palette for revision-display posture. */
|
|
169
|
+
readonly authorColorPalette?: ReadonlyMap<string, string>;
|
|
170
|
+
/**
|
|
171
|
+
* Page-graph reference for field resolution. When absent, the field
|
|
172
|
+
* resolver is not built and field dispatches return
|
|
173
|
+
* `refreshStatus: "unresolved"` with the registry's cached displayText.
|
|
174
|
+
*/
|
|
175
|
+
readonly pageGraph?: FieldPageGraph;
|
|
176
|
+
/** Active page index for PAGE field resolution. Ignored when
|
|
177
|
+
* `pageGraph` is absent. */
|
|
178
|
+
readonly activePageIndex?: number;
|
|
179
|
+
/**
|
|
180
|
+
* Optional pre-built bookmark map keyed on bookmark name. The field
|
|
181
|
+
* resolver needs it for REF / PAGEREF. The context accepts it so
|
|
182
|
+
* consumers that already compute bookmarks don't duplicate the work.
|
|
183
|
+
*/
|
|
184
|
+
readonly bookmarkMap?: Map<string, { bookmarkId: string; paragraphIndex: number }>;
|
|
185
|
+
/** Per-paragraph cursor offsets used by REF/PAGEREF relative-position
|
|
186
|
+
* resolution. */
|
|
187
|
+
readonly paragraphOffsets?: readonly number[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface FormattingContext {
|
|
191
|
+
readonly doc: CanonicalDocument;
|
|
192
|
+
readonly theme: ThemeColorResolver | undefined;
|
|
193
|
+
|
|
194
|
+
/** Build (or reuse) the stateful numbering prefix resolver. */
|
|
195
|
+
readonly numbering: NumberingPrefixResolver;
|
|
196
|
+
|
|
197
|
+
/** Lazy field resolver; undefined until a page graph is threaded via opts. */
|
|
198
|
+
readonly field: FieldResolver | undefined;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Return the first open revision whose anchor range covers the
|
|
202
|
+
* half-open segment `[from, to)`, or `undefined` when no revision is
|
|
203
|
+
* attached. Pre-computed once per document from
|
|
204
|
+
* `doc.review.revisions`; consumers call this per segment to obtain
|
|
205
|
+
* the attached revision that `resolveRunFromMarks({ revision })` will
|
|
206
|
+
* use for F6 posture application.
|
|
207
|
+
*
|
|
208
|
+
* Accepted / rejected / detached revisions are ignored — F6's markup
|
|
209
|
+
* modes only change how *open* revisions render.
|
|
210
|
+
*/
|
|
211
|
+
findRevisionAtRange(from: number, to: number): RevisionRecord | undefined;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Full paragraph cascade: docDefaults → style chain → direct. Returns
|
|
215
|
+
* the flat canonical shape for downstream consumers that want to read
|
|
216
|
+
* individual fields directly.
|
|
217
|
+
*/
|
|
218
|
+
resolveParagraphCascade(para: ParagraphNode): CanonicalParagraphFormatting;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Per-paragraph "effective numbering" — inherits from
|
|
222
|
+
* `paragraph.numbering` or (when missing) walks the paragraph-style
|
|
223
|
+
* chain for a style-linked numbering reference. Mirrors the
|
|
224
|
+
* `resolveEffectiveParagraphNumbering` logic that previously lived in
|
|
225
|
+
* `surface-projection.ts` and moves it into L03 ownership.
|
|
226
|
+
*/
|
|
227
|
+
resolveEffectiveParagraphNumbering(para: ParagraphNode): ParagraphNode["numbering"] | undefined;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Resolve the per-paragraph numbering prefix + geometry (marker,
|
|
231
|
+
* indent, tab) for rendering. Returns `null` when the paragraph has
|
|
232
|
+
* no numbering. Advances the stateful counter when `advance = true`
|
|
233
|
+
* (the default).
|
|
234
|
+
*
|
|
235
|
+
* For culled paragraphs on the viewport edge, callers pass
|
|
236
|
+
* `advance: true, emitGeometry: false` so the counter stays in sync
|
|
237
|
+
* without paying the geometry-projection cost.
|
|
238
|
+
*/
|
|
239
|
+
resolveParagraphNumbering(
|
|
240
|
+
para: ParagraphNode,
|
|
241
|
+
options?: { advance?: boolean; emitGeometry?: boolean },
|
|
242
|
+
): NumberingResolution | null;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Per-paragraph numbering-marker rPr cascade (ECMA-376 §17.9).
|
|
246
|
+
* Mirrors `resolveNumberingMarkerRunFormatting` with the layer's
|
|
247
|
+
* owned style catalog.
|
|
248
|
+
*/
|
|
249
|
+
resolveNumberingMarkerRunFormatting(
|
|
250
|
+
paragraphStyleId: string | undefined,
|
|
251
|
+
levelRunProperties: CanonicalRunFormatting | undefined,
|
|
252
|
+
): CanonicalRunFormatting;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Production hot-path run cascade. Accepts the surface-projected marks
|
|
256
|
+
* + markAttrs representation and does:
|
|
257
|
+
* 1. `buildDirectRunFormattingFromMarks` equivalent → direct rPr.
|
|
258
|
+
* 2. docDefaults → paragraph-style chain → character-style chain →
|
|
259
|
+
* direct cascade.
|
|
260
|
+
* 3. Hyperlink-color cascade when the run is inside a `HyperlinkNode`.
|
|
261
|
+
* 4. Theme-color concretization via the layer-owned `ThemeColorResolver`.
|
|
262
|
+
* 5. Revision-display posture application when opts configured.
|
|
263
|
+
*
|
|
264
|
+
* The return value is `EffectiveRunFormatting` — the same type the
|
|
265
|
+
* entry-point `resolveEffectiveFormatting` produces; consumers should
|
|
266
|
+
* treat them as interchangeable. When revision posture is attached,
|
|
267
|
+
* `run.revisionDisplay` is populated.
|
|
268
|
+
*/
|
|
269
|
+
resolveRunFromMarks(input: {
|
|
270
|
+
paragraphStyleId: string | undefined;
|
|
271
|
+
marks: ProjectedRunMarks | undefined;
|
|
272
|
+
inHyperlink: boolean;
|
|
273
|
+
characterStyleId?: string | undefined;
|
|
274
|
+
revision?: RevisionRecord;
|
|
275
|
+
}): EffectiveRunFormatting;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Pure canonical-inline-oriented run cascade. Used by the
|
|
279
|
+
* `resolveEffectiveFormatting(doc, nodeRef)` façade when callers pass
|
|
280
|
+
* a `TextNode` with canonical `marks` instead of the projected form.
|
|
281
|
+
*/
|
|
282
|
+
resolveRunFromCanonicalInline(input: {
|
|
283
|
+
paragraphStyleId: string | undefined;
|
|
284
|
+
characterStyleId: string | undefined;
|
|
285
|
+
direct: CanonicalRunFormatting | undefined;
|
|
286
|
+
inHyperlink: boolean;
|
|
287
|
+
revision?: RevisionRecord;
|
|
288
|
+
}): EffectiveRunFormatting;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Table-style resolution; delegates to the pure resolver and exposes
|
|
292
|
+
* the same shape. Kept on the context so production consumers
|
|
293
|
+
* (surface-projection, layout public-facet) import from one place.
|
|
294
|
+
*/
|
|
295
|
+
resolveTable(table: TableNode): ResolvedTableStyleResolution;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Cell-text cascade floor (docDefaults → table-style chain →
|
|
299
|
+
* optional conditional-region layering). Paragraph + run direct
|
|
300
|
+
* formatting from the cell's content layers on top via the normal
|
|
301
|
+
* paragraph/run cascades.
|
|
302
|
+
*
|
|
303
|
+
* When `activeRegions` is supplied (from `getActiveCellRegions()`
|
|
304
|
+
* via `resolveTable(table).rows[i].cells[j].activeConditionalRegions`)
|
|
305
|
+
* the cascade layers each region's `tblStylePr/pPr` + `rPr` after the
|
|
306
|
+
* basedOn-chain walk — fixes the L03 pre-A1 gap where banded tables
|
|
307
|
+
* and header rows received no region-level paragraph/run properties.
|
|
308
|
+
* Borders / shading / margins were already region-aware via
|
|
309
|
+
* `mergeStyleFormattingCell`; this brings text formatting to parity.
|
|
310
|
+
*/
|
|
311
|
+
resolveTableCellFloor(
|
|
312
|
+
tableStyleId: string | undefined,
|
|
313
|
+
activeRegions?: readonly TableStyleConditionalRegion[],
|
|
314
|
+
): { paragraph: CanonicalParagraphFormatting; run: CanonicalRunFormatting };
|
|
315
|
+
|
|
316
|
+
/** Resolve a registered field entry. Returns `undefined` when the
|
|
317
|
+
* context has no page-graph (so the resolver was never built). */
|
|
318
|
+
resolveField(entry: FieldRegistryEntry): ResolvedField | undefined;
|
|
319
|
+
|
|
320
|
+
/** Resolve the effective font family for a run following
|
|
321
|
+
* ECMA-376 §17.3.2.26 precedence + theme-minor fallback. */
|
|
322
|
+
resolveFontFamily(input: RunResolveInput, themeMinorFont?: string): string | undefined;
|
|
323
|
+
|
|
324
|
+
/** Build an `EffectiveParagraphFormatting` wrapper (cascade +
|
|
325
|
+
* numbering + paragraphMarkRun) for the entry-point façade. */
|
|
326
|
+
resolveParagraph(para: ParagraphNode): EffectiveParagraphFormatting;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Layer-03 §C (provenance). Resolve the run cascade AND return a
|
|
330
|
+
* per-field source map — for each populated field, name the cascade
|
|
331
|
+
* tier that won the resolution: `"docDefaults"`, `"style"` (with the
|
|
332
|
+
* winning styleId), `"characterStyle"` (ditto), or `"direct"`.
|
|
333
|
+
*
|
|
334
|
+
* This powers agent explanations ("this bold came from the Heading 1
|
|
335
|
+
* style, not from direct formatting"). It is NOT called on the hot
|
|
336
|
+
* path — production consumers use `resolveRunFromCanonicalInline`
|
|
337
|
+
* which pays zero provenance cost.
|
|
338
|
+
*
|
|
339
|
+
* Research doc `ResolvedProperty { value, source, sourceId }` maps
|
|
340
|
+
* directly onto the return shape here.
|
|
341
|
+
*/
|
|
342
|
+
resolveRunWithProvenance(input: {
|
|
343
|
+
paragraphStyleId: string | undefined;
|
|
344
|
+
characterStyleId: string | undefined;
|
|
345
|
+
direct: CanonicalRunFormatting | undefined;
|
|
346
|
+
}): RunProvenance;
|
|
347
|
+
|
|
348
|
+
/** Resolve an `EffectiveFieldDisplay` shape for an inline field. */
|
|
349
|
+
resolveFieldDisplay(
|
|
350
|
+
entry: FieldRegistryEntry | undefined,
|
|
351
|
+
cachedDisplayText: string | undefined,
|
|
352
|
+
): EffectiveFieldDisplay;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Implementation
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
class FormattingContextImpl implements FormattingContext {
|
|
360
|
+
readonly doc: CanonicalDocument;
|
|
361
|
+
readonly theme: ThemeColorResolver | undefined;
|
|
362
|
+
readonly numbering: NumberingPrefixResolver;
|
|
363
|
+
readonly field: FieldResolver | undefined;
|
|
364
|
+
private readonly opts: FormattingContextOptions;
|
|
365
|
+
// F6 revision-range index. Built once per context lifetime (O(R)),
|
|
366
|
+
// consulted per segment (O(R) linear in the common case — the document
|
|
367
|
+
// has tens of open revisions, not thousands, so a sorted walk is fine).
|
|
368
|
+
// `null` when the caller did not configure revision markup, so the
|
|
369
|
+
// segment walk pays zero cost.
|
|
370
|
+
private readonly revisionIndex:
|
|
371
|
+
| ReadonlyArray<{ from: number; to: number; revision: RevisionRecord }>
|
|
372
|
+
| null;
|
|
373
|
+
// Paragraph-cascade memoization (D). Keyed by the weak `para` reference
|
|
374
|
+
// so cache frees automatically when the paragraph is GC'd. Hot-path
|
|
375
|
+
// surface-projection calls `resolveParagraphCascade` on the same
|
|
376
|
+
// paragraph via `resolveParagraph` + numbering resolve + marker-rPr;
|
|
377
|
+
// each of those re-walked the style catalog before.
|
|
378
|
+
private readonly paragraphCascadeCache: WeakMap<
|
|
379
|
+
ParagraphNode,
|
|
380
|
+
CanonicalParagraphFormatting
|
|
381
|
+
> = new WeakMap();
|
|
382
|
+
|
|
383
|
+
constructor(doc: CanonicalDocument, opts: FormattingContextOptions) {
|
|
384
|
+
this.doc = doc;
|
|
385
|
+
this.opts = opts;
|
|
386
|
+
const canonicalTheme: CanonicalTheme | undefined = doc.subParts?.canonicalTheme;
|
|
387
|
+
this.theme = canonicalTheme ? new ThemeColorResolver(canonicalTheme) : undefined;
|
|
388
|
+
this.numbering = createNumberingPrefixResolver(doc.numbering);
|
|
389
|
+
this.field =
|
|
390
|
+
opts.pageGraph !== undefined
|
|
391
|
+
? createFieldResolver({
|
|
392
|
+
pageGraph: opts.pageGraph,
|
|
393
|
+
activePageIndex: opts.activePageIndex ?? 0,
|
|
394
|
+
bookmarkMap: opts.bookmarkMap ?? new Map(),
|
|
395
|
+
paragraphOffsets: opts.paragraphOffsets ?? [],
|
|
396
|
+
styles: doc.styles,
|
|
397
|
+
contentRoot: doc.content,
|
|
398
|
+
})
|
|
399
|
+
: undefined;
|
|
400
|
+
this.revisionIndex =
|
|
401
|
+
opts.revisionMarkupMode !== undefined ||
|
|
402
|
+
opts.getEffectiveMarkupMode !== undefined
|
|
403
|
+
? buildRevisionRangeIndex(doc)
|
|
404
|
+
: null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
findRevisionAtRange(from: number, to: number): RevisionRecord | undefined {
|
|
408
|
+
const index = this.revisionIndex;
|
|
409
|
+
if (!index) return undefined;
|
|
410
|
+
for (const entry of index) {
|
|
411
|
+
if (entry.from > to) break;
|
|
412
|
+
if (entry.from <= from && entry.to >= to) return entry.revision;
|
|
413
|
+
if (entry.from <= from && entry.to > from) return entry.revision;
|
|
414
|
+
}
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
resolveParagraphCascade(para: ParagraphNode): CanonicalParagraphFormatting {
|
|
419
|
+
const cached = this.paragraphCascadeCache.get(para);
|
|
420
|
+
if (cached !== undefined) return cached;
|
|
421
|
+
const resolved = resolveEffectiveParagraphFormatting(
|
|
422
|
+
{
|
|
423
|
+
styleId: para.styleId,
|
|
424
|
+
direct: extractDirectParagraphFormatting(para),
|
|
425
|
+
},
|
|
426
|
+
this.doc.styles,
|
|
427
|
+
);
|
|
428
|
+
this.paragraphCascadeCache.set(para, resolved);
|
|
429
|
+
return resolved;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
resolveEffectiveParagraphNumbering(
|
|
433
|
+
para: ParagraphNode,
|
|
434
|
+
): ParagraphNode["numbering"] | undefined {
|
|
435
|
+
if (para.numbering) return para.numbering;
|
|
436
|
+
if (!para.styleId) return undefined;
|
|
437
|
+
const paragraphStyles = this.doc.styles?.paragraphs ?? {};
|
|
438
|
+
const styleChain = resolveParagraphStyleChain(para.styleId, this.doc.styles);
|
|
439
|
+
let styleNumbering: { numberingInstanceId: string; level?: number } | undefined;
|
|
440
|
+
for (const styleId of styleChain) {
|
|
441
|
+
const style = paragraphStyles[styleId];
|
|
442
|
+
if (style?.numbering) {
|
|
443
|
+
styleNumbering = style.numbering;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (!styleNumbering) return undefined;
|
|
448
|
+
if (styleNumbering.level !== undefined) {
|
|
449
|
+
return {
|
|
450
|
+
numberingInstanceId: styleNumbering.numberingInstanceId,
|
|
451
|
+
level: styleNumbering.level,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const resolvedLevel = resolveStyleLinkedNumberingLevel(
|
|
455
|
+
this.doc,
|
|
456
|
+
styleNumbering.numberingInstanceId,
|
|
457
|
+
styleChain,
|
|
458
|
+
);
|
|
459
|
+
return resolvedLevel !== undefined
|
|
460
|
+
? {
|
|
461
|
+
numberingInstanceId: styleNumbering.numberingInstanceId,
|
|
462
|
+
level: resolvedLevel,
|
|
463
|
+
}
|
|
464
|
+
: undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
resolveParagraphNumbering(
|
|
468
|
+
para: ParagraphNode,
|
|
469
|
+
options: { advance?: boolean; emitGeometry?: boolean } = {},
|
|
470
|
+
): NumberingResolution | null {
|
|
471
|
+
const advance = options.advance !== false;
|
|
472
|
+
const emitGeometry = options.emitGeometry !== false;
|
|
473
|
+
const effectiveNumbering = this.resolveEffectiveParagraphNumbering(para);
|
|
474
|
+
if (!effectiveNumbering) return null;
|
|
475
|
+
if (!emitGeometry) {
|
|
476
|
+
// Advance the counter but do not project geometry.
|
|
477
|
+
if (advance) this.numbering.resolve(effectiveNumbering);
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
if (!advance) {
|
|
481
|
+
// The underlying resolver is stateful and does not expose a
|
|
482
|
+
// "peek" primitive. When the caller needs the read without
|
|
483
|
+
// mutation, they must fork the context; for now, treat as a
|
|
484
|
+
// contract violation in a dev assertion.
|
|
485
|
+
// eslint-disable-next-line no-console -- dev-time only
|
|
486
|
+
console.warn("[formatting-context] resolveParagraphNumbering({advance:false}) is not supported; counter always advances");
|
|
487
|
+
}
|
|
488
|
+
return this.numbering.resolveDetailed(effectiveNumbering, para);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
resolveNumberingMarkerRunFormatting(
|
|
492
|
+
paragraphStyleId: string | undefined,
|
|
493
|
+
levelRunProperties: CanonicalRunFormatting | undefined,
|
|
494
|
+
): CanonicalRunFormatting {
|
|
495
|
+
return resolveNumberingMarkerRunFormatting(
|
|
496
|
+
{ paragraphStyleId, levelRunProperties },
|
|
497
|
+
this.doc.styles,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
resolveRunFromMarks(input: {
|
|
502
|
+
paragraphStyleId: string | undefined;
|
|
503
|
+
marks: ProjectedRunMarks | undefined;
|
|
504
|
+
inHyperlink: boolean;
|
|
505
|
+
characterStyleId?: string | undefined;
|
|
506
|
+
revision?: RevisionRecord;
|
|
507
|
+
}): EffectiveRunFormatting {
|
|
508
|
+
const direct = buildDirectRunFormattingFromProjected(input.marks);
|
|
509
|
+
return this.resolveRunCore({
|
|
510
|
+
paragraphStyleId: input.paragraphStyleId,
|
|
511
|
+
characterStyleId: input.characterStyleId,
|
|
512
|
+
direct,
|
|
513
|
+
inHyperlink: input.inHyperlink,
|
|
514
|
+
revision: input.revision,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
resolveRunFromCanonicalInline(input: {
|
|
519
|
+
paragraphStyleId: string | undefined;
|
|
520
|
+
characterStyleId: string | undefined;
|
|
521
|
+
direct: CanonicalRunFormatting | undefined;
|
|
522
|
+
inHyperlink: boolean;
|
|
523
|
+
revision?: RevisionRecord;
|
|
524
|
+
}): EffectiveRunFormatting {
|
|
525
|
+
return this.resolveRunCore(input);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private resolveRunCore(input: {
|
|
529
|
+
paragraphStyleId: string | undefined;
|
|
530
|
+
characterStyleId: string | undefined;
|
|
531
|
+
direct: CanonicalRunFormatting | undefined;
|
|
532
|
+
inHyperlink: boolean;
|
|
533
|
+
revision?: RevisionRecord;
|
|
534
|
+
}): EffectiveRunFormatting {
|
|
535
|
+
const resolveInput: RunResolveInput = {
|
|
536
|
+
paragraphStyleId: input.paragraphStyleId,
|
|
537
|
+
characterStyleId: input.characterStyleId,
|
|
538
|
+
direct: input.direct,
|
|
539
|
+
};
|
|
540
|
+
const cascade = input.inHyperlink
|
|
541
|
+
? resolveHyperlinkRunFormatting(resolveInput, this.doc.styles, this.theme)
|
|
542
|
+
: this.theme
|
|
543
|
+
? concretizeThemeColors(
|
|
544
|
+
resolveEffectiveRunFormatting(resolveInput, this.doc.styles),
|
|
545
|
+
this.theme,
|
|
546
|
+
)
|
|
547
|
+
: resolveEffectiveRunFormatting(resolveInput, this.doc.styles);
|
|
548
|
+
const activeMarkupMode = this.resolveActiveMarkupMode();
|
|
549
|
+
if (activeMarkupMode !== undefined && input.revision !== undefined) {
|
|
550
|
+
const authorColor =
|
|
551
|
+
input.revision.authorId && this.opts.authorColorPalette
|
|
552
|
+
? this.opts.authorColorPalette.get(input.revision.authorId)
|
|
553
|
+
: undefined;
|
|
554
|
+
const flags = applyRevisionDisplay(
|
|
555
|
+
cascade,
|
|
556
|
+
input.revision,
|
|
557
|
+
activeMarkupMode,
|
|
558
|
+
authorColor,
|
|
559
|
+
);
|
|
560
|
+
return { ...cascade, revisionDisplay: flags };
|
|
561
|
+
}
|
|
562
|
+
return cascade;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Resolve the markup mode for the current per-run call. Callback
|
|
566
|
+
// wins when set; a callback that returns `undefined` means "no
|
|
567
|
+
// posture this pass" and does not fall back to the static field —
|
|
568
|
+
// that's how the composition site disables posture at runtime
|
|
569
|
+
// without rebuilding the context.
|
|
570
|
+
private resolveActiveMarkupMode():
|
|
571
|
+
| "clean"
|
|
572
|
+
| "simple"
|
|
573
|
+
| "all"
|
|
574
|
+
| undefined {
|
|
575
|
+
if (this.opts.getEffectiveMarkupMode !== undefined) {
|
|
576
|
+
return this.opts.getEffectiveMarkupMode();
|
|
577
|
+
}
|
|
578
|
+
return this.opts.revisionMarkupMode;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
resolveTable(table: TableNode): ResolvedTableStyleResolution {
|
|
582
|
+
return resolveTableStyleResolution(table, this.doc.styles.tables);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
resolveTableCellFloor(
|
|
586
|
+
tableStyleId: string | undefined,
|
|
587
|
+
activeRegions?: readonly TableStyleConditionalRegion[],
|
|
588
|
+
): { paragraph: CanonicalParagraphFormatting; run: CanonicalRunFormatting } {
|
|
589
|
+
return resolveTableCellTextFormatting(
|
|
590
|
+
tableStyleId,
|
|
591
|
+
this.doc.styles,
|
|
592
|
+
activeRegions,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
resolveField(entry: FieldRegistryEntry): ResolvedField | undefined {
|
|
597
|
+
if (!this.field) return undefined;
|
|
598
|
+
return this.field.resolve(entry);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
resolveFontFamily(
|
|
602
|
+
input: RunResolveInput,
|
|
603
|
+
themeMinorFont?: string,
|
|
604
|
+
): string | undefined {
|
|
605
|
+
return resolveRunFontFamily(
|
|
606
|
+
input,
|
|
607
|
+
this.doc.styles,
|
|
608
|
+
themeMinorFont,
|
|
609
|
+
this.opts.fontTable,
|
|
610
|
+
this.doc.subParts?.canonicalTheme?.fontScheme,
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
resolveParagraph(para: ParagraphNode): EffectiveParagraphFormatting {
|
|
615
|
+
const cascade = this.resolveParagraphCascade(para);
|
|
616
|
+
const numbering = buildEffectiveNumbering(
|
|
617
|
+
this.resolveParagraphNumbering(para, { advance: true }),
|
|
618
|
+
);
|
|
619
|
+
const paragraphMarkRun = cascade.paragraphMarkRunProperties && this.theme
|
|
620
|
+
? (concretizeThemeColors(cascade.paragraphMarkRunProperties, this.theme) as EffectiveRunFormatting)
|
|
621
|
+
: (cascade.paragraphMarkRunProperties as EffectiveRunFormatting | undefined);
|
|
622
|
+
return {
|
|
623
|
+
...cascade,
|
|
624
|
+
...(numbering ? { numbering } : {}),
|
|
625
|
+
...(paragraphMarkRun ? { paragraphMarkRun } : {}),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
resolveRunWithProvenance(input: {
|
|
630
|
+
paragraphStyleId: string | undefined;
|
|
631
|
+
characterStyleId: string | undefined;
|
|
632
|
+
direct: CanonicalRunFormatting | undefined;
|
|
633
|
+
}): RunProvenance {
|
|
634
|
+
// Walk each tier in priority-ascending order, recording which tier
|
|
635
|
+
// last set each field. Highest-priority writer wins — same order
|
|
636
|
+
// as `resolveEffectiveRunFormatting` but with provenance tracked.
|
|
637
|
+
const properties: { [K in keyof CanonicalRunFormatting]?: RunResolvedProperty } = {};
|
|
638
|
+
const applyTier = (
|
|
639
|
+
tierRecord: CanonicalRunFormatting | undefined,
|
|
640
|
+
source: RunResolvedProperty["source"],
|
|
641
|
+
sourceId: string | undefined,
|
|
642
|
+
): void => {
|
|
643
|
+
if (!tierRecord) return;
|
|
644
|
+
for (const key of Object.keys(tierRecord) as Array<keyof CanonicalRunFormatting>) {
|
|
645
|
+
const value = tierRecord[key];
|
|
646
|
+
if (value === undefined) continue;
|
|
647
|
+
properties[key] = {
|
|
648
|
+
value: value as unknown,
|
|
649
|
+
source,
|
|
650
|
+
...(sourceId !== undefined ? { sourceId } : {}),
|
|
651
|
+
} as RunResolvedProperty;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
const styles = this.doc.styles;
|
|
655
|
+
applyTier(styles.docDefaults?.run, "docDefaults", undefined);
|
|
656
|
+
if (input.paragraphStyleId) {
|
|
657
|
+
const chain = resolveParagraphStyleChain(input.paragraphStyleId, styles);
|
|
658
|
+
// Walk root-to-leaf so the most specific style wins.
|
|
659
|
+
for (let i = chain.length - 1; i >= 0; i -= 1) {
|
|
660
|
+
const styleId = chain[i]!;
|
|
661
|
+
applyTier(styles.paragraphs[styleId]?.runProperties, "style", styleId);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (input.characterStyleId) {
|
|
665
|
+
// Character chain — lazy import to avoid cyclic dep.
|
|
666
|
+
const charStyles = styles.characters;
|
|
667
|
+
const visited = new Set<string>();
|
|
668
|
+
const chain: string[] = [];
|
|
669
|
+
let cursor: string | undefined = input.characterStyleId;
|
|
670
|
+
while (cursor && !visited.has(cursor)) {
|
|
671
|
+
visited.add(cursor);
|
|
672
|
+
if (charStyles[cursor]) chain.push(cursor);
|
|
673
|
+
cursor = charStyles[cursor]?.basedOn;
|
|
674
|
+
}
|
|
675
|
+
for (let i = chain.length - 1; i >= 0; i -= 1) {
|
|
676
|
+
const styleId = chain[i]!;
|
|
677
|
+
applyTier(charStyles[styleId]?.runProperties, "characterStyle", styleId);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
applyTier(input.direct, "direct", undefined);
|
|
681
|
+
// Reconstruct the EffectiveRunFormatting from the winning values.
|
|
682
|
+
const run: EffectiveRunFormatting = {} as EffectiveRunFormatting;
|
|
683
|
+
for (const [key, prop] of Object.entries(properties) as Array<
|
|
684
|
+
[keyof CanonicalRunFormatting, RunResolvedProperty | undefined]
|
|
685
|
+
>) {
|
|
686
|
+
if (prop !== undefined) {
|
|
687
|
+
(run as Record<string, unknown>)[key] = prop.value;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Theme concretization runs downstream of cascade — keep provenance
|
|
691
|
+
// stable ("style" or "direct" owns `colorTheme`; theme concretize
|
|
692
|
+
// populates `colorHex` which is still tied to the `colorTheme` tier).
|
|
693
|
+
if (this.theme) {
|
|
694
|
+
const concrete = concretizeThemeColors(run, this.theme);
|
|
695
|
+
if (concrete.colorHex && concrete.colorHex !== run.colorHex) {
|
|
696
|
+
(run as Record<string, unknown>).colorHex = concrete.colorHex;
|
|
697
|
+
properties.colorHex = {
|
|
698
|
+
value: concrete.colorHex,
|
|
699
|
+
source: properties.colorThemeSlot?.source ?? "style",
|
|
700
|
+
...(properties.colorThemeSlot?.sourceId !== undefined
|
|
701
|
+
? { sourceId: properties.colorThemeSlot.sourceId }
|
|
702
|
+
: {}),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return { run, properties };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
resolveFieldDisplay(
|
|
710
|
+
entry: FieldRegistryEntry | undefined,
|
|
711
|
+
cachedDisplayText: string | undefined,
|
|
712
|
+
): EffectiveFieldDisplay {
|
|
713
|
+
if (!entry) {
|
|
714
|
+
return cachedDisplayText
|
|
715
|
+
? { refreshStatus: "unresolved", displayText: cachedDisplayText }
|
|
716
|
+
: { refreshStatus: "unresolved" };
|
|
717
|
+
}
|
|
718
|
+
const resolved = this.resolveField(entry);
|
|
719
|
+
if (!resolved) {
|
|
720
|
+
return {
|
|
721
|
+
refreshStatus: "unresolved",
|
|
722
|
+
...(entry.displayText ? { displayText: entry.displayText } : {}),
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const status: EffectiveFieldDisplay["refreshStatus"] =
|
|
726
|
+
resolved.refreshStatus === "current" || resolved.refreshStatus === "stale"
|
|
727
|
+
? "resolved"
|
|
728
|
+
: resolved.refreshStatus === "unresolvable"
|
|
729
|
+
? "unresolvable"
|
|
730
|
+
: "unresolved";
|
|
731
|
+
return {
|
|
732
|
+
refreshStatus: status,
|
|
733
|
+
...(resolved.displayText !== undefined
|
|
734
|
+
? { displayText: resolved.displayText }
|
|
735
|
+
: entry.displayText
|
|
736
|
+
? { displayText: entry.displayText }
|
|
737
|
+
: {}),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function createFormattingContext(
|
|
743
|
+
doc: CanonicalDocument,
|
|
744
|
+
opts: FormattingContextOptions = {},
|
|
745
|
+
): FormattingContext {
|
|
746
|
+
return new FormattingContextImpl(doc, opts);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ---------------------------------------------------------------------------
|
|
750
|
+
// Private helpers
|
|
751
|
+
// ---------------------------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
function buildDirectRunFormattingFromProjected(
|
|
754
|
+
projected: ProjectedRunMarks | undefined,
|
|
755
|
+
): CanonicalRunFormatting | undefined {
|
|
756
|
+
if (!projected) return undefined;
|
|
757
|
+
const direct: CanonicalRunFormatting = {};
|
|
758
|
+
const marks = projected.marks;
|
|
759
|
+
if (marks) {
|
|
760
|
+
if (marks.includes("bold")) direct.bold = true;
|
|
761
|
+
if (marks.includes("italic")) direct.italic = true;
|
|
762
|
+
if (marks.includes("underline")) direct.underline = "single";
|
|
763
|
+
if (marks.includes("strikethrough")) direct.strikethrough = true;
|
|
764
|
+
if (marks.includes("doubleStrikethrough")) direct.doubleStrikethrough = true;
|
|
765
|
+
if (marks.includes("vanish")) direct.vanish = true;
|
|
766
|
+
if (marks.includes("allCaps")) direct.allCaps = true;
|
|
767
|
+
if (marks.includes("smallCaps")) direct.smallCaps = true;
|
|
768
|
+
}
|
|
769
|
+
const markAttrs = projected.markAttrs;
|
|
770
|
+
if (markAttrs) {
|
|
771
|
+
if (markAttrs.fontFamily) {
|
|
772
|
+
direct.fontFamily = markAttrs.fontFamily;
|
|
773
|
+
direct.fontFamilyAscii = markAttrs.fontFamily;
|
|
774
|
+
}
|
|
775
|
+
if (typeof markAttrs.fontSize === "number") {
|
|
776
|
+
direct.fontSizeHalfPoints = markAttrs.fontSize;
|
|
777
|
+
}
|
|
778
|
+
if (markAttrs.textColor) {
|
|
779
|
+
direct.colorHex = markAttrs.textColor.replace(/^#/, "");
|
|
780
|
+
}
|
|
781
|
+
if (markAttrs.backgroundColor) {
|
|
782
|
+
direct.highlight = markAttrs.backgroundColor;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return Object.keys(direct).length > 0 ? direct : undefined;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function extractDirectParagraphFormatting(
|
|
789
|
+
para: ParagraphNode,
|
|
790
|
+
): CanonicalParagraphFormatting {
|
|
791
|
+
const direct: CanonicalParagraphFormatting = {};
|
|
792
|
+
if (para.alignment !== undefined) direct.alignment = para.alignment;
|
|
793
|
+
if (para.spacing !== undefined) direct.spacing = para.spacing;
|
|
794
|
+
if (para.contextualSpacing !== undefined) direct.contextualSpacing = para.contextualSpacing;
|
|
795
|
+
if (para.indentation !== undefined) direct.indentation = para.indentation;
|
|
796
|
+
if (para.tabStops !== undefined) direct.tabStops = para.tabStops;
|
|
797
|
+
if (para.keepNext !== undefined) direct.keepNext = para.keepNext;
|
|
798
|
+
if (para.keepLines !== undefined) direct.keepLines = para.keepLines;
|
|
799
|
+
if (para.outlineLevel !== undefined) direct.outlineLevel = para.outlineLevel;
|
|
800
|
+
if (para.pageBreakBefore !== undefined) direct.pageBreakBefore = para.pageBreakBefore;
|
|
801
|
+
if (para.widowControl !== undefined) direct.widowControl = para.widowControl;
|
|
802
|
+
if (para.borders !== undefined) direct.borders = para.borders;
|
|
803
|
+
if (para.shading !== undefined) direct.shading = para.shading;
|
|
804
|
+
if (para.bidi !== undefined) direct.bidi = para.bidi;
|
|
805
|
+
if (para.suppressLineNumbers !== undefined) direct.suppressLineNumbers = para.suppressLineNumbers;
|
|
806
|
+
return direct;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function resolveStyleLinkedNumberingLevel(
|
|
810
|
+
doc: CanonicalDocument,
|
|
811
|
+
numberingInstanceId: string,
|
|
812
|
+
styleChain: readonly string[],
|
|
813
|
+
): number | undefined {
|
|
814
|
+
const instance = doc.numbering.instances[numberingInstanceId];
|
|
815
|
+
if (!instance) return undefined;
|
|
816
|
+
|
|
817
|
+
for (const styleId of styleChain) {
|
|
818
|
+
const overrideMatch = instance.overrides.find(
|
|
819
|
+
(override) => override.levelDefinition?.paragraphStyleId === styleId,
|
|
820
|
+
);
|
|
821
|
+
if (overrideMatch) return overrideMatch.level;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const abstractDefinition = doc.numbering.abstractDefinitions[instance.abstractNumberingId];
|
|
825
|
+
if (!abstractDefinition) return undefined;
|
|
826
|
+
|
|
827
|
+
for (const styleId of styleChain) {
|
|
828
|
+
const levelMatch = abstractDefinition.levels.find(
|
|
829
|
+
(levelDefinition) => levelDefinition.paragraphStyleId === styleId,
|
|
830
|
+
);
|
|
831
|
+
if (levelMatch) return levelMatch.level;
|
|
832
|
+
}
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function buildEffectiveNumbering(
|
|
837
|
+
detail: NumberingResolution | null,
|
|
838
|
+
): EffectiveNumbering | undefined {
|
|
839
|
+
if (!detail) return undefined;
|
|
840
|
+
const result: EffectiveNumbering = {
|
|
841
|
+
level: detail.level,
|
|
842
|
+
...(detail.text !== null ? { marker: detail.text } : {}),
|
|
843
|
+
...(detail.markerRunProperties
|
|
844
|
+
? { markerRunFormatting: detail.markerRunProperties }
|
|
845
|
+
: {}),
|
|
846
|
+
...(detail.geometry
|
|
847
|
+
? {
|
|
848
|
+
indentation: {
|
|
849
|
+
...(detail.geometry.indentation?.left !== undefined
|
|
850
|
+
? { left: detail.geometry.indentation.left }
|
|
851
|
+
: {}),
|
|
852
|
+
...(detail.geometry.indentation?.hanging !== undefined
|
|
853
|
+
? { hanging: detail.geometry.indentation.hanging }
|
|
854
|
+
: {}),
|
|
855
|
+
...(detail.geometry.indentation?.firstLine !== undefined
|
|
856
|
+
? { firstLine: detail.geometry.indentation.firstLine }
|
|
857
|
+
: {}),
|
|
858
|
+
...(detail.geometry.tabStops && detail.geometry.tabStops.length > 0
|
|
859
|
+
? { tab: detail.geometry.tabStops[0]!.position }
|
|
860
|
+
: {}),
|
|
861
|
+
},
|
|
862
|
+
}
|
|
863
|
+
: {}),
|
|
864
|
+
...(detail.picBulletMediaId
|
|
865
|
+
? { pictureBulletRef: detail.picBulletMediaId }
|
|
866
|
+
: {}),
|
|
867
|
+
};
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Type-only re-export of HyperlinkNode so callers can type their walk.
|
|
872
|
+
export type { HyperlinkNode, InlineNode };
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Build a sorted-by-`from` array of the document's OPEN revisions +
|
|
876
|
+
* their anchor ranges. Accepted / rejected / detached revisions are
|
|
877
|
+
* excluded because F6 markup modes ("clean" | "simple" | "all") only
|
|
878
|
+
* change how open revisions render. The array is immutable after
|
|
879
|
+
* construction; `FormattingContext.findRevisionAtRange` walks it per
|
|
880
|
+
* segment.
|
|
881
|
+
*/
|
|
882
|
+
function buildRevisionRangeIndex(
|
|
883
|
+
doc: CanonicalDocument,
|
|
884
|
+
): ReadonlyArray<{ from: number; to: number; revision: RevisionRecord }> {
|
|
885
|
+
const records: { from: number; to: number; revision: RevisionRecord }[] = [];
|
|
886
|
+
const revisions = doc.review?.revisions;
|
|
887
|
+
if (!revisions) return records;
|
|
888
|
+
for (const revision of Object.values(revisions)) {
|
|
889
|
+
if (revision.status !== "open") continue;
|
|
890
|
+
const anchor = revision.anchor;
|
|
891
|
+
let range: { from: number; to: number } | undefined;
|
|
892
|
+
if (anchor.kind === "range") {
|
|
893
|
+
range = { from: anchor.range.from, to: anchor.range.to };
|
|
894
|
+
} else if (anchor.kind === "node") {
|
|
895
|
+
range = { from: anchor.at, to: anchor.at };
|
|
896
|
+
} else {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
records.push({ from: range.from, to: range.to, revision });
|
|
900
|
+
}
|
|
901
|
+
records.sort((a, b) => a.from - b.from);
|
|
902
|
+
return records;
|
|
903
|
+
}
|