@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
|
@@ -85,83 +85,53 @@ import {
|
|
|
85
85
|
persistedSnapshotFromEditorSessionState,
|
|
86
86
|
} from "../api/session-state.ts";
|
|
87
87
|
import {
|
|
88
|
-
createDetachedAnchor,
|
|
89
|
-
createNodeAnchor,
|
|
90
|
-
createRangeAnchor,
|
|
91
88
|
storyTargetsEqual,
|
|
92
|
-
type TransactionMapping,
|
|
93
|
-
} from "../core/selection/mapping.ts";
|
|
94
|
-
import {
|
|
95
|
-
applyFormattingOperationToDocument,
|
|
96
89
|
getFormattingStateFromRenderSnapshot,
|
|
97
|
-
} from "../core/commands/formatting-commands.ts";
|
|
98
|
-
import {
|
|
99
|
-
applyParagraphStyleToDocument,
|
|
100
|
-
applyTableStyleToDocument,
|
|
101
|
-
} from "../core/commands/style-commands.ts";
|
|
102
|
-
import {
|
|
103
|
-
continueNumbering as continueListNumbering,
|
|
104
|
-
restartNumbering as restartListNumbering,
|
|
105
|
-
toggleBulletedList,
|
|
106
|
-
toggleNumberedList,
|
|
107
|
-
} from "../core/commands/list-commands.ts";
|
|
108
|
-
import {
|
|
109
90
|
dispatchTextCommand,
|
|
110
|
-
type DispatchContext,
|
|
111
91
|
type DispatchTextCommand,
|
|
112
|
-
} from "../runtime/edit-dispatch/index.ts";
|
|
113
|
-
import {
|
|
114
|
-
resolveActiveParagraphIndex,
|
|
115
|
-
setActiveParagraphIndentation,
|
|
116
|
-
setActiveParagraphTabStops,
|
|
117
|
-
} from "../core/commands/paragraph-layout-commands.ts";
|
|
118
|
-
import {
|
|
119
|
-
deleteSectionBreakAtSectionIndex,
|
|
120
|
-
insertSectionBreakAfterSectionIndex,
|
|
121
|
-
setHeaderFooterLinkAtSectionIndex,
|
|
122
|
-
setSectionPageNumberingAtSectionIndex,
|
|
123
|
-
updateSectionLayoutAtSectionIndex,
|
|
124
|
-
} from "../core/commands/section-layout-commands.ts";
|
|
125
|
-
import {
|
|
126
|
-
insertImage as insertImageInDocument,
|
|
127
|
-
resizeImage as resizeImageInCatalog,
|
|
128
|
-
repositionFloatingImage as repositionFloatingImageInDocument,
|
|
129
|
-
} from "../core/commands/image-commands.ts";
|
|
130
|
-
import {
|
|
131
|
-
applyTableStructureOperation,
|
|
132
92
|
getTableStructureContext,
|
|
133
|
-
type TableStructureOperation,
|
|
134
|
-
} from "../core/commands/table-structure-commands.ts";
|
|
135
|
-
import {
|
|
136
|
-
deleteSelectionOrBackward,
|
|
137
|
-
deleteSelectionOrForward,
|
|
138
|
-
insertHardBreak as insertHardBreakInDocument,
|
|
139
|
-
insertPageBreak as insertPageBreakInDocument,
|
|
140
|
-
insertTab as insertTabInDocument,
|
|
141
|
-
insertText as insertTextInDocument,
|
|
142
|
-
insertTable as insertTableInDocument,
|
|
143
|
-
splitParagraph as splitParagraphInDocument,
|
|
144
|
-
} from "../core/commands/text-commands.ts";
|
|
145
|
-
import { type SelectionSnapshot as InternalSelectionSnapshot } from "../core/state/editor-state.ts";
|
|
146
|
-
import {
|
|
147
93
|
getStoryBlocks,
|
|
148
|
-
|
|
149
|
-
} from "../runtime/story-targeting.ts";
|
|
94
|
+
} from "../shell/ref-utilities.ts";
|
|
150
95
|
import {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
96
|
+
DISPATCH_CONTEXT,
|
|
97
|
+
applyRuntimeDeleteComment,
|
|
98
|
+
applyRuntimeDeleteSectionBreak,
|
|
99
|
+
applyRuntimeFormattingOperation,
|
|
100
|
+
applyRuntimeImageReposition,
|
|
101
|
+
applyRuntimeImageResize,
|
|
102
|
+
applyRuntimeInsertImage,
|
|
103
|
+
applyRuntimeInsertPageBreak,
|
|
104
|
+
applyRuntimeInsertSectionBreak,
|
|
105
|
+
applyRuntimeInsertTable,
|
|
106
|
+
applyRuntimeListToggle,
|
|
107
|
+
applyRuntimeNumberingFlow,
|
|
108
|
+
applyRuntimeParagraphIndentation,
|
|
109
|
+
applyRuntimeParagraphStyle,
|
|
110
|
+
applyRuntimeParagraphTabStops,
|
|
111
|
+
applyRuntimeSelection,
|
|
112
|
+
applyRuntimeSetHeaderFooterLink,
|
|
113
|
+
applyRuntimeSetSectionPageNumbering,
|
|
114
|
+
applyRuntimeTableStructureOperation,
|
|
115
|
+
applyRuntimeTableStyle,
|
|
116
|
+
applyRuntimeUpdateSectionLayout,
|
|
117
|
+
applySuggestionGroupAction,
|
|
118
|
+
createCollapsedPublicSelection,
|
|
119
|
+
createSelectionFromAnchor,
|
|
120
|
+
getRuntimeStyleCatalog,
|
|
121
|
+
normalizeRequestedSelection,
|
|
122
|
+
publicTableOpToInternal,
|
|
123
|
+
toRuntimeSelectionSnapshot,
|
|
124
|
+
type TableStructureOperation,
|
|
125
|
+
} from "../shell/ref-commands.ts";
|
|
126
|
+
import { buildMediaPreviews } from "../shell/media-previews.ts";
|
|
157
127
|
import {
|
|
128
|
+
deriveCapabilities,
|
|
158
129
|
resolveCurrentContextAnalyticsQuery,
|
|
159
130
|
runtimeContextAnalyticsSnapshotsEqual,
|
|
160
|
-
} from "../runtime/context-analytics.ts";
|
|
161
|
-
import {
|
|
162
131
|
createEditorViewStateSnapshot,
|
|
163
132
|
createViewState,
|
|
164
|
-
} from "../
|
|
133
|
+
} from "../shell/ref-utilities.ts";
|
|
134
|
+
import { findTextMatchesForRuntime, searchRuntimeDocument } from "../shell/search.ts";
|
|
165
135
|
import {
|
|
166
136
|
type TwProseMirrorSurfaceRef,
|
|
167
137
|
} from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
|
|
@@ -207,35 +177,77 @@ import {
|
|
|
207
177
|
persistSession as persistSessionFromBoundary,
|
|
208
178
|
rejectExportWhileLoading as rejectExportWhileLoadingFromBoundary,
|
|
209
179
|
useEditorRuntimeBoundary,
|
|
210
|
-
} from "
|
|
180
|
+
} from "../shell/session-bootstrap.ts";
|
|
211
181
|
import {
|
|
212
182
|
downloadExportResult,
|
|
213
183
|
withExportDelivery,
|
|
214
184
|
} from "./browser-export";
|
|
215
185
|
import { EditorShellView } from "./editor-shell-view.tsx";
|
|
186
|
+
import { TwDebugPresentation } from "../ui-tailwind/debug/index.ts";
|
|
187
|
+
import { shellPasteFragmentParser as SHELL_PASTE_FRAGMENT_PARSER } from "../shell/paste-adapter.ts";
|
|
216
188
|
import { EditorSurfaceController } from "./editor-surface-controller.tsx";
|
|
189
|
+
import type { TwWorkspaceChromeHostController } from "../ui-tailwind/chrome/tw-workspace-chrome-host";
|
|
217
190
|
import {
|
|
218
191
|
resolveChromePreset,
|
|
219
192
|
resolveChromeVisibilityForPreset,
|
|
220
|
-
} from "../ui
|
|
193
|
+
} from "../api/v3/ui/chrome-preset-model.ts";
|
|
221
194
|
import { TwRuntimeReplDialog } from "../ui-tailwind/chrome/tw-runtime-repl-dialog.tsx";
|
|
222
|
-
import { createRuntimeCollabSync } from "../runtime/collab/runtime-collab-sync.ts";
|
|
223
195
|
import {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
196
|
+
useRemoteCursorPublish,
|
|
197
|
+
useRuntimeCollabSync,
|
|
198
|
+
} from "../shell/use-collab-sync.ts";
|
|
199
|
+
import {
|
|
200
|
+
collectChartSnapshots,
|
|
201
|
+
lookupChartSnapshot,
|
|
202
|
+
} from "../shell/chart-snapshots.ts";
|
|
203
|
+
import { wrapRefForTelemetry } from "../shell/ref-utilities.ts";
|
|
204
|
+
import { createApiV3, type ApiV3 } from "../api/v3/index.ts";
|
|
205
|
+
import { UiApiProvider } from "../ui-tailwind/ui-api-context.tsx";
|
|
206
|
+
import {
|
|
207
|
+
UiShellChannelsProvider,
|
|
208
|
+
type UiShellChannels,
|
|
209
|
+
} from "../ui-tailwind/ui-shell-channels-context.tsx";
|
|
210
|
+
import { OverlayAnchorBridgeProvider } from "../ui-tailwind/overlay-anchor-bridge-context.tsx";
|
|
228
211
|
import {
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
createShellOverlayAnchorBridge,
|
|
213
|
+
type ShellOverlayAnchorBridge,
|
|
214
|
+
} from "../shell/overlay-anchor-bridge.ts";
|
|
231
215
|
import {
|
|
232
|
-
|
|
233
|
-
|
|
216
|
+
createUiSubscriberChannel,
|
|
217
|
+
type UiSubscriberChannel,
|
|
218
|
+
} from "../shell/ui-subscriber-channels.ts";
|
|
219
|
+
import {
|
|
220
|
+
deriveShellPinnedSurfaces,
|
|
221
|
+
makeShellUiControllerFactory,
|
|
222
|
+
shellUiControllerFactory,
|
|
223
|
+
} from "./ui-controller-factory.ts";
|
|
224
|
+
import type {
|
|
225
|
+
ChromeHostPosture,
|
|
226
|
+
ChromePosture,
|
|
227
|
+
OverlayAnchorQuery,
|
|
228
|
+
ViewportState,
|
|
229
|
+
} from "../api/v3/ui/_types.ts";
|
|
234
230
|
|
|
235
231
|
export {
|
|
236
232
|
__createFallbackRuntime,
|
|
237
233
|
__resolveWordReviewEditorSource,
|
|
238
|
-
} from "
|
|
234
|
+
} from "../shell/session-bootstrap.ts";
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Map a host-facing `markupDisplay` prop value to the narrow
|
|
238
|
+
* `ChromeMarkupDisplay` enum on `ChromeHostPosture` (`"final" | "markup"
|
|
239
|
+
* | "original"`). Prop shape is richer for legacy reasons; `ui.chrome.
|
|
240
|
+
* getPosture` normalizes to the narrow enum.
|
|
241
|
+
*/
|
|
242
|
+
function normalizeHostMarkupDisplay(
|
|
243
|
+
raw: WordReviewEditorProps["markupDisplay"],
|
|
244
|
+
): ChromeHostPosture["markupDisplay"] | undefined {
|
|
245
|
+
if (raw === undefined) return undefined;
|
|
246
|
+
if (raw === "original") return "original";
|
|
247
|
+
if (raw === "no-markup" || raw === "clean") return "final";
|
|
248
|
+
// "all-markup" | "simple-markup" | "simple" | "all" → markup
|
|
249
|
+
return "markup";
|
|
250
|
+
}
|
|
239
251
|
|
|
240
252
|
const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
241
253
|
position: "absolute",
|
|
@@ -249,21 +261,6 @@ const VISUALLY_HIDDEN_STYLES: React.CSSProperties = {
|
|
|
249
261
|
border: 0,
|
|
250
262
|
};
|
|
251
263
|
|
|
252
|
-
const BROWSER_SAFE_PREVIEW_TYPES = new Set([
|
|
253
|
-
"image/png",
|
|
254
|
-
"image/jpeg",
|
|
255
|
-
"image/jpg",
|
|
256
|
-
"image/gif",
|
|
257
|
-
"image/webp",
|
|
258
|
-
"image/bmp",
|
|
259
|
-
// SVG is served through `<img src="data:image/svg+xml;base64,...">` by
|
|
260
|
-
// `createImageDataUrl`. Chromium sandboxes SVGs loaded via <img> — scripts
|
|
261
|
-
// don't execute, external references are blocked, XSS surface matches PNG.
|
|
262
|
-
// Needed for docs/plans/lane-5-charts.md Stage 0B synthesized chart previews and
|
|
263
|
-
// any host that ships .svg inside `word/media/` as a logo or figure.
|
|
264
|
-
"image/svg+xml",
|
|
265
|
-
]);
|
|
266
|
-
|
|
267
264
|
const ACCESSIBLE_REGION_ORDER = [
|
|
268
265
|
"toolbar",
|
|
269
266
|
"document",
|
|
@@ -280,279 +277,26 @@ type SelectionToolbarDismissReason =
|
|
|
280
277
|
| "escape";
|
|
281
278
|
|
|
282
279
|
// ---------------------------------------------------------------------------
|
|
283
|
-
// P17
|
|
280
|
+
// P17 metadata persistence — imported from Layer 06
|
|
281
|
+
//
|
|
282
|
+
// The conflict-key function, persistence-mode cascade, pending-conflict
|
|
283
|
+
// type, conflict emitter, and internal/external conversion routines used
|
|
284
|
+
// to live in this file as ~200 lines of UI-layer code. They had no UI
|
|
285
|
+
// dependencies — they operated on the runtime's workflow metadata
|
|
286
|
+
// snapshot + a host-supplied resolver — so Layer-06 Slice 4 moved them
|
|
287
|
+
// to `src/runtime/workflow/metadata-persistence.ts` (W9-purity
|
|
288
|
+
// compliant). The UI now delegates via the imports below.
|
|
284
289
|
// ---------------------------------------------------------------------------
|
|
285
290
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function resolveEffective(input: {
|
|
295
|
-
overlay?: MetadataPersistenceMode;
|
|
296
|
-
scope?: ScopeMetadataPersistence;
|
|
297
|
-
field?: ScopeMetadataPersistence;
|
|
298
|
-
}): "internal" | "external" {
|
|
299
|
-
if (input.field === "internal" || input.field === "external") return input.field;
|
|
300
|
-
if (input.scope === "internal" || input.scope === "external") return input.scope;
|
|
301
|
-
return input.overlay ?? "internal";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* A pending conflict record held in `metadataConflictPendingRef` while
|
|
306
|
-
* the host decides how to resolve. Keyed by `conflictKey(...)`.
|
|
307
|
-
*/
|
|
308
|
-
interface PendingConflict {
|
|
309
|
-
scopeId?: string;
|
|
310
|
-
entryId?: string;
|
|
311
|
-
fieldKey?: string;
|
|
312
|
-
embedded: { value?: Record<string, unknown>; version?: number } | null;
|
|
313
|
-
external: { value?: Record<string, unknown>; version?: number } | null;
|
|
314
|
-
defaultPolicy: "prefer-latest";
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/** Emit a single `metadata_conflict_detected` event and register the pending conflict. */
|
|
318
|
-
function registerAndEmitConflict(args: {
|
|
319
|
-
onEvent: ((event: WordReviewEditorEvent) => void) | undefined;
|
|
320
|
-
documentId: string;
|
|
321
|
-
conflict: PendingConflict;
|
|
322
|
-
pendingConflicts: Map<string, PendingConflict>;
|
|
323
|
-
}): void {
|
|
324
|
-
const key = conflictKey(args.conflict);
|
|
325
|
-
// Guard: do not emit duplicate events for the same key in a single pass.
|
|
326
|
-
if (args.pendingConflicts.has(key)) return;
|
|
327
|
-
args.pendingConflicts.set(key, args.conflict);
|
|
328
|
-
args.onEvent?.({
|
|
329
|
-
type: "metadata_conflict_detected",
|
|
330
|
-
documentId: args.documentId,
|
|
331
|
-
scopeId: args.conflict.scopeId,
|
|
332
|
-
entryId: args.conflict.entryId,
|
|
333
|
-
fieldKey: args.conflict.fieldKey,
|
|
334
|
-
embedded: args.conflict.embedded,
|
|
335
|
-
external: args.conflict.external,
|
|
336
|
-
defaultPolicy: args.conflict.defaultPolicy,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async function runConvertScopesToInternal(args: {
|
|
341
|
-
runtime: WordReviewEditorRuntime;
|
|
342
|
-
scopeIds: string[];
|
|
343
|
-
resolver: ScopeMetadataResolver | null;
|
|
344
|
-
/** When provided, version mismatches emit `metadata_conflict_detected`. */
|
|
345
|
-
documentId?: string;
|
|
346
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
347
|
-
pendingConflicts?: Map<string, PendingConflict>;
|
|
348
|
-
}): Promise<void> {
|
|
349
|
-
if (!args.resolver) throw new MetadataResolverMissingError();
|
|
350
|
-
const snapshot = args.runtime.getWorkflowMetadataSnapshot();
|
|
351
|
-
const overlay = args.runtime.getWorkflowOverlay();
|
|
352
|
-
|
|
353
|
-
const nextEntries = await Promise.all(
|
|
354
|
-
snapshot.entries.map(async (entry) => {
|
|
355
|
-
if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
|
|
356
|
-
const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
|
|
357
|
-
const effective = resolveEffective({
|
|
358
|
-
overlay: overlay?.metadataPersistence,
|
|
359
|
-
scope: scope?.metadataPersistence,
|
|
360
|
-
field: entry.metadataPersistence,
|
|
361
|
-
});
|
|
362
|
-
if (effective !== "external" || !entry.storageRef) return entry;
|
|
363
|
-
const resolved = await args.resolver!.resolve(entry.storageRef);
|
|
364
|
-
if (!resolved) return entry;
|
|
365
|
-
|
|
366
|
-
// Conflict detection: compare embedded metadataVersion vs resolver version.
|
|
367
|
-
if (
|
|
368
|
-
args.pendingConflicts &&
|
|
369
|
-
args.documentId !== undefined &&
|
|
370
|
-
entry.metadataVersion !== undefined &&
|
|
371
|
-
resolved.version !== undefined &&
|
|
372
|
-
entry.metadataVersion !== resolved.version
|
|
373
|
-
) {
|
|
374
|
-
registerAndEmitConflict({
|
|
375
|
-
onEvent: args.onEvent,
|
|
376
|
-
documentId: args.documentId,
|
|
377
|
-
pendingConflicts: args.pendingConflicts,
|
|
378
|
-
conflict: {
|
|
379
|
-
scopeId: entry.scopeId,
|
|
380
|
-
entryId: entry.entryId,
|
|
381
|
-
// External entries have no inline value; embedded side carries version only.
|
|
382
|
-
embedded: { version: entry.metadataVersion },
|
|
383
|
-
external: { value: resolved.value, version: resolved.version },
|
|
384
|
-
defaultPolicy: "prefer-latest",
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Default behavior: always apply the resolved value (host can override via resolveMetadataConflict).
|
|
390
|
-
return {
|
|
391
|
-
...entry,
|
|
392
|
-
value: resolved.value,
|
|
393
|
-
metadataPersistence: "internal" as const,
|
|
394
|
-
storageRef: undefined,
|
|
395
|
-
metadataVersion: resolved.version ?? entry.metadataVersion,
|
|
396
|
-
};
|
|
397
|
-
}),
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
args.runtime.setWorkflowMetadataEntries(nextEntries);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async function runConvertScopesToExternal(args: {
|
|
404
|
-
runtime: WordReviewEditorRuntime;
|
|
405
|
-
scopeIds: string[];
|
|
406
|
-
resolver: ScopeMetadataResolver | null;
|
|
407
|
-
/** When provided, version-conflict publish errors emit `metadata_conflict_detected`. */
|
|
408
|
-
documentId?: string;
|
|
409
|
-
onEvent?: (event: WordReviewEditorEvent) => void;
|
|
410
|
-
pendingConflicts?: Map<string, PendingConflict>;
|
|
411
|
-
}): Promise<void> {
|
|
412
|
-
if (!args.resolver) throw new MetadataResolverMissingError();
|
|
413
|
-
const snapshot = args.runtime.getWorkflowMetadataSnapshot();
|
|
414
|
-
const overlay = args.runtime.getWorkflowOverlay();
|
|
415
|
-
|
|
416
|
-
const nextEntries = await Promise.all(
|
|
417
|
-
snapshot.entries.map(async (entry) => {
|
|
418
|
-
if (!entry.scopeId || !args.scopeIds.includes(entry.scopeId)) return entry;
|
|
419
|
-
const scope = overlay?.scopes.find((s) => s.scopeId === entry.scopeId);
|
|
420
|
-
const effective = resolveEffective({
|
|
421
|
-
overlay: overlay?.metadataPersistence,
|
|
422
|
-
scope: scope?.metadataPersistence,
|
|
423
|
-
field: entry.metadataPersistence,
|
|
424
|
-
});
|
|
425
|
-
if (effective === "external") return entry;
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
const { ref, version } = await args.resolver!.publish({
|
|
429
|
-
scopeId: entry.scopeId,
|
|
430
|
-
metadataId: entry.metadataId,
|
|
431
|
-
entryId: entry.entryId,
|
|
432
|
-
value: entry.value ?? {},
|
|
433
|
-
expectedVersion: entry.metadataVersion,
|
|
434
|
-
});
|
|
435
|
-
return {
|
|
436
|
-
...entry,
|
|
437
|
-
value: undefined,
|
|
438
|
-
metadataPersistence: "external" as const,
|
|
439
|
-
storageRef: ref,
|
|
440
|
-
metadataVersion: version,
|
|
441
|
-
};
|
|
442
|
-
} catch (err: unknown) {
|
|
443
|
-
// Duck-type version-conflict errors (HarnessVersionConflictError or compatible shapes).
|
|
444
|
-
if (
|
|
445
|
-
args.pendingConflicts &&
|
|
446
|
-
args.documentId !== undefined &&
|
|
447
|
-
err instanceof Error &&
|
|
448
|
-
(err.name === "HarnessVersionConflictError" ||
|
|
449
|
-
("ref" in err && "expected" in err && "actual" in err))
|
|
450
|
-
) {
|
|
451
|
-
const conflictErr = err as Error & { ref?: string; expected?: number; actual?: number };
|
|
452
|
-
registerAndEmitConflict({
|
|
453
|
-
onEvent: args.onEvent,
|
|
454
|
-
documentId: args.documentId,
|
|
455
|
-
pendingConflicts: args.pendingConflicts,
|
|
456
|
-
conflict: {
|
|
457
|
-
scopeId: entry.scopeId,
|
|
458
|
-
entryId: entry.entryId,
|
|
459
|
-
// Embedded side: the entry's current inline value and metadataVersion.
|
|
460
|
-
embedded: { value: entry.value, version: entry.metadataVersion },
|
|
461
|
-
// External side: only the rowstore's actual version (no value available from error).
|
|
462
|
-
external: { version: conflictErr.actual },
|
|
463
|
-
defaultPolicy: "prefer-latest",
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
// Skip this entry — do not publish; leave it unchanged.
|
|
467
|
-
return entry;
|
|
468
|
-
}
|
|
469
|
-
// Non-conflict errors propagate normally.
|
|
470
|
-
throw err;
|
|
471
|
-
}
|
|
472
|
-
}),
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
args.runtime.setWorkflowMetadataEntries(nextEntries);
|
|
476
|
-
}
|
|
291
|
+
import {
|
|
292
|
+
conflictKey,
|
|
293
|
+
convertScopesToInternal as runConvertScopesToInternal,
|
|
294
|
+
convertScopesToExternal as runConvertScopesToExternal,
|
|
295
|
+
type PendingConflict,
|
|
296
|
+
} from "../shell/ref-utilities.ts";
|
|
477
297
|
|
|
478
298
|
// ---------------------------------------------------------------------------
|
|
479
299
|
|
|
480
|
-
type CanonicalDocType = ReturnType<WordReviewEditorRuntime["getCanonicalDocument"]>;
|
|
481
|
-
|
|
482
|
-
function collectChartSnapshots(doc: CanonicalDocType): import("../api/public-types").ChartSnapshot[] {
|
|
483
|
-
const results: import("../api/public-types").ChartSnapshot[] = [];
|
|
484
|
-
collectChartSnapshotsFromBlocks(doc.content.children, results);
|
|
485
|
-
return results;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Walk the canonical document, compute each chart_preview's stableChartId,
|
|
490
|
-
* and project a snapshot for the first matching id. Short-circuits on
|
|
491
|
-
* match so the happy path is O(k) in blocks-until-match rather than the
|
|
492
|
-
* O(N) the `collect().find()` fallback incurred for every ref call. For
|
|
493
|
-
* hosts that call `getChartSnapshot` in a tight loop over many chartIds,
|
|
494
|
-
* this reduces the cost from O(N²) to O(N·k).
|
|
495
|
-
*/
|
|
496
|
-
function lookupChartSnapshot(
|
|
497
|
-
doc: CanonicalDocType,
|
|
498
|
-
chartId: string,
|
|
499
|
-
): import("../api/public-types").ChartSnapshot | null {
|
|
500
|
-
return lookupChartSnapshotInBlocks(doc.content.children, chartId);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function lookupChartSnapshotInBlocks(
|
|
504
|
-
blocks: CanonicalDocType["content"]["children"],
|
|
505
|
-
chartId: string,
|
|
506
|
-
): import("../api/public-types").ChartSnapshot | null {
|
|
507
|
-
for (const block of blocks) {
|
|
508
|
-
if (block.type === "paragraph") {
|
|
509
|
-
for (const inline of block.children) {
|
|
510
|
-
if (inline.type === "chart_preview" && inline.parsedData) {
|
|
511
|
-
const id = stableChartId(inline.rawXml);
|
|
512
|
-
if (id === chartId) {
|
|
513
|
-
return projectChartSnapshot(id, inline.parsedData);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
} else if (block.type === "table") {
|
|
518
|
-
for (const row of block.rows) {
|
|
519
|
-
for (const cell of row.cells) {
|
|
520
|
-
const found = lookupChartSnapshotInBlocks(cell.children, chartId);
|
|
521
|
-
if (found) return found;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
525
|
-
const found = lookupChartSnapshotInBlocks(block.children, chartId);
|
|
526
|
-
if (found) return found;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function collectChartSnapshotsFromBlocks(
|
|
533
|
-
blocks: CanonicalDocType["content"]["children"],
|
|
534
|
-
results: import("../api/public-types").ChartSnapshot[],
|
|
535
|
-
): void {
|
|
536
|
-
for (const block of blocks) {
|
|
537
|
-
if (block.type === "paragraph") {
|
|
538
|
-
for (const inline of block.children) {
|
|
539
|
-
if (inline.type === "chart_preview" && inline.parsedData) {
|
|
540
|
-
const chartId = stableChartId(inline.rawXml);
|
|
541
|
-
results.push(projectChartSnapshot(chartId, inline.parsedData));
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
} else if (block.type === "table") {
|
|
545
|
-
for (const row of block.rows) {
|
|
546
|
-
for (const cell of row.cells) {
|
|
547
|
-
collectChartSnapshotsFromBlocks(cell.children, results);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
551
|
-
collectChartSnapshotsFromBlocks(block.children, results);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
300
|
// ---------------------------------------------------------------------------
|
|
557
301
|
|
|
558
302
|
export function __createWordReviewEditorRefBridge(
|
|
@@ -579,7 +323,44 @@ export function __createWordReviewEditorRefBridge(
|
|
|
579
323
|
return r;
|
|
580
324
|
});
|
|
581
325
|
|
|
326
|
+
// L07 closure pass — cache the V3 surface per-bridge. `runtime` is stable
|
|
327
|
+
// across this bridge's lifetime; `createApiV3` freezes the result, so a
|
|
328
|
+
// single shared instance is safe and avoids re-running family factories
|
|
329
|
+
// on every `ref.getApiV3()` call.
|
|
330
|
+
//
|
|
331
|
+
// refactor/11 Slice 4 — wire the UI factory here too so the
|
|
332
|
+
// non-mounted ref path (headless harness, tests, services) returns an
|
|
333
|
+
// `api` with the `ui` namespace in lockstep with the mounted shell path.
|
|
334
|
+
let cachedApiV3: ApiV3 | null = null;
|
|
335
|
+
|
|
582
336
|
const refValue: WordReviewEditorRef = {
|
|
337
|
+
getApiV3: () => {
|
|
338
|
+
if (cachedApiV3 === null) {
|
|
339
|
+
const built = createApiV3(runtime, { ui: shellUiControllerFactory });
|
|
340
|
+
cachedApiV3 = built;
|
|
341
|
+
// L03 §5b activation — hand the runtime a callback that
|
|
342
|
+
// returns the composed effective markup mode (L06 policy ×
|
|
343
|
+
// L10 local preference). Closure captures the freshly-built
|
|
344
|
+
// api so subsequent `ui.viewport.setLocalMarkupMode` calls
|
|
345
|
+
// flip the value observed on the next projection. The paired
|
|
346
|
+
// subscribe below invalidates the surface cache when the
|
|
347
|
+
// composed mode changes due to a class-C preference flip,
|
|
348
|
+
// so the new posture reaches the render without waiting for
|
|
349
|
+
// the next mutation. Both hooks are guarded on the `ui`
|
|
350
|
+
// family being present — headless consumers that bypass the
|
|
351
|
+
// factory skip the wire.
|
|
352
|
+
const uiApi = built.ui;
|
|
353
|
+
if (uiApi) {
|
|
354
|
+
runtime.setEffectiveMarkupModeProvider(() =>
|
|
355
|
+
uiApi.viewport.getEffectiveMarkupMode(),
|
|
356
|
+
);
|
|
357
|
+
uiApi.viewport.subscribeEffectiveMarkupMode(() => {
|
|
358
|
+
runtime.invalidateForMarkupModeChange();
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return cachedApiV3;
|
|
363
|
+
},
|
|
583
364
|
focus: () => runtime.focus(),
|
|
584
365
|
blur: () => runtime.blur(),
|
|
585
366
|
undo: () => runtime.undo(),
|
|
@@ -929,6 +710,30 @@ export function __createWordReviewEditorRefBridge(
|
|
|
929
710
|
getWorkflowMetadataSnapshot: () => {
|
|
930
711
|
return clonePublicValue(runtime.getWorkflowMetadataSnapshot());
|
|
931
712
|
},
|
|
713
|
+
getVisibilityPolicy: (kind) => {
|
|
714
|
+
return clonePublicValue(runtime.getVisibilityPolicy(kind));
|
|
715
|
+
},
|
|
716
|
+
getVisibilityPolicies: () => {
|
|
717
|
+
return clonePublicValue(runtime.getVisibilityPolicies());
|
|
718
|
+
},
|
|
719
|
+
setVisibilityPolicy: (policy) => {
|
|
720
|
+
return runtime.setVisibilityPolicy(clonePublicValue(policy));
|
|
721
|
+
},
|
|
722
|
+
clearVisibilityPolicy: (kind) => {
|
|
723
|
+
return runtime.clearVisibilityPolicy(kind);
|
|
724
|
+
},
|
|
725
|
+
subscribeVisibilityPolicy: (listener) => {
|
|
726
|
+
return runtime.subscribeVisibilityPolicy(listener);
|
|
727
|
+
},
|
|
728
|
+
getMarkupModePolicy: () => {
|
|
729
|
+
return clonePublicValue(runtime.getMarkupModePolicy());
|
|
730
|
+
},
|
|
731
|
+
setMarkupModePolicy: (policy) => {
|
|
732
|
+
return runtime.setMarkupModePolicy(policy ? clonePublicValue(policy) : null);
|
|
733
|
+
},
|
|
734
|
+
subscribeMarkupModePolicy: (listener) => {
|
|
735
|
+
return runtime.subscribeMarkupModePolicy(listener);
|
|
736
|
+
},
|
|
932
737
|
queryScopes: (filter) => {
|
|
933
738
|
return clonePublicValue(runtime.queryScopes(filter));
|
|
934
739
|
},
|
|
@@ -1164,8 +969,15 @@ export function __createWordReviewEditorRefBridge(
|
|
|
1164
969
|
},
|
|
1165
970
|
...projections,
|
|
1166
971
|
};
|
|
1167
|
-
|
|
1168
|
-
|
|
972
|
+
// Phase 1 api-channel — Proxy wrap so every invocation emits when enabled.
|
|
973
|
+
const telemetryBus = runtime.debug?.bus;
|
|
974
|
+
if (!telemetryBus) {
|
|
975
|
+
refHolder.current = refValue;
|
|
976
|
+
return refValue;
|
|
977
|
+
}
|
|
978
|
+
const tracedRef = wrapRefForTelemetry(refValue, telemetryBus);
|
|
979
|
+
refHolder.current = tracedRef;
|
|
980
|
+
return tracedRef;
|
|
1169
981
|
}
|
|
1170
982
|
|
|
1171
983
|
export function __applyRuntimeTextCommand(
|
|
@@ -1218,7 +1030,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1218
1030
|
showReviewPanel = true,
|
|
1219
1031
|
chromeVisibility,
|
|
1220
1032
|
density,
|
|
1033
|
+
editorActionHost,
|
|
1034
|
+
chromeControllerRef,
|
|
1035
|
+
commandPaletteDisabled,
|
|
1221
1036
|
customSelectionTools,
|
|
1037
|
+
debugMode = "off",
|
|
1222
1038
|
} = props;
|
|
1223
1039
|
|
|
1224
1040
|
const [activeRailTab, setActiveRailTab] = useState<ReviewRailTab>("comments");
|
|
@@ -1239,6 +1055,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1239
1055
|
const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
|
|
1240
1056
|
const selectionToolbarElementRef = useRef<HTMLDivElement | null>(null);
|
|
1241
1057
|
const shellRef = useRef<HTMLDivElement | null>(null);
|
|
1058
|
+
// §3.1 — internal handle on the workspace chrome controller so the
|
|
1059
|
+
// PM surface's `onContextMenuRequested` can dispatch to it. Teed
|
|
1060
|
+
// to the host-supplied `chromeControllerRef` below so both the
|
|
1061
|
+
// internal wire and any external consumer see the same instance.
|
|
1062
|
+
const internalChromeControllerRef = useRef<TwWorkspaceChromeHostController | null>(null);
|
|
1242
1063
|
const editorRefForRepl = useRef<WordReviewEditorRef | null>(null);
|
|
1243
1064
|
const lastSelectionToolbarKeyRef = useRef<string | null>(null);
|
|
1244
1065
|
const lastAnnouncedErrorIdRef = useRef<string | null>(null);
|
|
@@ -1266,6 +1087,167 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1266
1087
|
lastSavedRevisionTokenRef,
|
|
1267
1088
|
runtimeViewStateSeedRef,
|
|
1268
1089
|
} = useEditorRuntimeBoundary(props);
|
|
1090
|
+
|
|
1091
|
+
// refactor/10 Task 1 — the mounted `UiController` reads live shell
|
|
1092
|
+
// state via refs. Refs hold a pointer to the current render's host
|
|
1093
|
+
// posture + preset input so the factory's closure getters always
|
|
1094
|
+
// return current values (React-native stable-getter pattern — the
|
|
1095
|
+
// factory is memoized on `activeRuntime`, the ref body mutates per
|
|
1096
|
+
// render).
|
|
1097
|
+
const shellUiStateRef = useRef<{
|
|
1098
|
+
hostPosture: ChromeHostPosture;
|
|
1099
|
+
chromePresetInput: {
|
|
1100
|
+
chromePreset: WordReviewEditorChromePreset;
|
|
1101
|
+
chromeOptions?: Partial<WordReviewEditorChromeOptions>;
|
|
1102
|
+
};
|
|
1103
|
+
}>({
|
|
1104
|
+
hostPosture: {},
|
|
1105
|
+
chromePresetInput: { chromePreset: "advanced" },
|
|
1106
|
+
});
|
|
1107
|
+
shellUiStateRef.current = {
|
|
1108
|
+
hostPosture: {
|
|
1109
|
+
reviewMode: reviewMode === "review" ? "reviewer" : "author",
|
|
1110
|
+
markupDisplay: normalizeHostMarkupDisplay(markupDisplay),
|
|
1111
|
+
debugMode,
|
|
1112
|
+
chromePreset,
|
|
1113
|
+
},
|
|
1114
|
+
chromePresetInput: {
|
|
1115
|
+
chromePreset: resolveChromePreset(chromePreset, reviewMode),
|
|
1116
|
+
chromeOptions,
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// refactor/11 Slice 4 — construct the v3 API with the UI namespace
|
|
1121
|
+
// wired so `<UiApiProvider>` can expose `api.ui` to chrome hooks that
|
|
1122
|
+
// read via `useUiApi()`. Memoized on `activeRuntime` (stable across
|
|
1123
|
+
// PM transactions per Performance Invariant 8); `createApiV3` freezes
|
|
1124
|
+
// the returned record so sharing it between the provider + the ref
|
|
1125
|
+
// (see useImperativeHandle below) is safe. Called once per runtime
|
|
1126
|
+
// bridge; re-runs only when the editor receives a new runtime handle.
|
|
1127
|
+
//
|
|
1128
|
+
// DS-C1 (designsystem.md §8.8.1 "Selection toolbar" row) — the shell
|
|
1129
|
+
// overlay-anchor bridge is a stable `useRef` box consulted by the
|
|
1130
|
+
// controller's `getOverlayAnchor` hook so
|
|
1131
|
+
// `ui.overlays.getAnchor({ kind: "selection" })` returns a real rect
|
|
1132
|
+
// without re-creating the API on every selection change. The factory
|
|
1133
|
+
// closes over the ref box identity (never changes); per-render updates
|
|
1134
|
+
// to the box contents land via `useShellSelectionAnchorBridge` inside
|
|
1135
|
+
// the review workspace.
|
|
1136
|
+
const overlayAnchorBridgeRef = useRef<ShellOverlayAnchorBridge>(
|
|
1137
|
+
createShellOverlayAnchorBridge(),
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
// refactor/10 chrome-contract Slice 2 (2026-04-23) — subscriber
|
|
1141
|
+
// channels for `ui.chrome.subscribe` / `ui.viewport.subscribe` /
|
|
1142
|
+
// `ui.overlays.subscribe`. Each is a stable `useRef` emitter the
|
|
1143
|
+
// factory closes over. Emission is driven from React effects below
|
|
1144
|
+
// on the appropriate state-source changes. Before this slice those
|
|
1145
|
+
// subscribe methods threw on mount ("controller lacks hook");
|
|
1146
|
+
// Playwright drivers + debug service couldn't observe posture /
|
|
1147
|
+
// viewport / overlay-invalidation over time on the mounted editor.
|
|
1148
|
+
const chromeChannelRef = useRef<UiSubscriberChannel<ChromePosture> | null>(
|
|
1149
|
+
null,
|
|
1150
|
+
);
|
|
1151
|
+
if (chromeChannelRef.current === null) {
|
|
1152
|
+
chromeChannelRef.current = createUiSubscriberChannel<ChromePosture>();
|
|
1153
|
+
}
|
|
1154
|
+
const viewportChannelRef = useRef<UiSubscriberChannel<ViewportState> | null>(
|
|
1155
|
+
null,
|
|
1156
|
+
);
|
|
1157
|
+
if (viewportChannelRef.current === null) {
|
|
1158
|
+
viewportChannelRef.current = createUiSubscriberChannel<ViewportState>();
|
|
1159
|
+
}
|
|
1160
|
+
const overlaysChannelRef = useRef<
|
|
1161
|
+
UiSubscriberChannel<OverlayAnchorQuery> | null
|
|
1162
|
+
>(null);
|
|
1163
|
+
if (overlaysChannelRef.current === null) {
|
|
1164
|
+
overlaysChannelRef.current = createUiSubscriberChannel<OverlayAnchorQuery>();
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// refactor/10 Task 1 — mounted factory populates getHostPosture +
|
|
1168
|
+
// getPinnedSurfaces + the DS-C1 selection bridge + the three
|
|
1169
|
+
// subscriber channels so every `api.ui.*.subscribe` on the mounted
|
|
1170
|
+
// path is a real subscription, not a throw.
|
|
1171
|
+
const api = useMemo(
|
|
1172
|
+
() =>
|
|
1173
|
+
createApiV3(activeRuntime, {
|
|
1174
|
+
ui: makeShellUiControllerFactory({
|
|
1175
|
+
getHostPosture: () => shellUiStateRef.current.hostPosture,
|
|
1176
|
+
getPinnedSurfaces: () =>
|
|
1177
|
+
deriveShellPinnedSurfaces(
|
|
1178
|
+
shellUiStateRef.current.chromePresetInput.chromePreset,
|
|
1179
|
+
shellUiStateRef.current.chromePresetInput.chromeOptions,
|
|
1180
|
+
),
|
|
1181
|
+
getOverlayAnchor: (query) => {
|
|
1182
|
+
if (query.kind === "selection") {
|
|
1183
|
+
return overlayAnchorBridgeRef.current.getSelectionAnchor?.() ?? null;
|
|
1184
|
+
}
|
|
1185
|
+
return null;
|
|
1186
|
+
},
|
|
1187
|
+
subscribeChrome: (listener) =>
|
|
1188
|
+
chromeChannelRef.current!.subscribe(listener),
|
|
1189
|
+
subscribeViewport: (listener) =>
|
|
1190
|
+
viewportChannelRef.current!.subscribe(listener),
|
|
1191
|
+
subscribeOverlays: (listener) =>
|
|
1192
|
+
overlaysChannelRef.current!.subscribe(listener),
|
|
1193
|
+
}),
|
|
1194
|
+
}),
|
|
1195
|
+
[activeRuntime],
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
// L03 §5b activation (mounted-shell path). Hand the runtime a
|
|
1199
|
+
// callback that returns the composed effective markup mode (L06
|
|
1200
|
+
// policy × L10 local preference). Subscribe to class-C preference
|
|
1201
|
+
// changes so the surface cache busts and the new posture reaches
|
|
1202
|
+
// the render without waiting for a mutation. Re-wires when
|
|
1203
|
+
// `activeRuntime` or `api` changes (effect cleanup clears the
|
|
1204
|
+
// previous provider + unsubscribes).
|
|
1205
|
+
useEffect(() => {
|
|
1206
|
+
const uiApi = api.ui;
|
|
1207
|
+
if (!uiApi) return;
|
|
1208
|
+
activeRuntime.setEffectiveMarkupModeProvider(() =>
|
|
1209
|
+
uiApi.viewport.getEffectiveMarkupMode(),
|
|
1210
|
+
);
|
|
1211
|
+
const unsubscribe = uiApi.viewport.subscribeEffectiveMarkupMode(() => {
|
|
1212
|
+
activeRuntime.invalidateForMarkupModeChange();
|
|
1213
|
+
});
|
|
1214
|
+
return () => {
|
|
1215
|
+
unsubscribe();
|
|
1216
|
+
activeRuntime.setEffectiveMarkupModeProvider(undefined);
|
|
1217
|
+
};
|
|
1218
|
+
}, [activeRuntime, api]);
|
|
1219
|
+
|
|
1220
|
+
// Chrome emit — fire posture subscribers whenever a host-posture
|
|
1221
|
+
// source changes. Runtime-owned posture fields (effectiveMode,
|
|
1222
|
+
// blockedReasons, documentMode, readOnly) are captured via the
|
|
1223
|
+
// snapshot-slice subscriptions below — they trigger re-renders that
|
|
1224
|
+
// in turn re-run this effect on dep changes.
|
|
1225
|
+
useEffect(() => {
|
|
1226
|
+
if (!api.ui) return;
|
|
1227
|
+
chromeChannelRef.current?.emit(api.ui.chrome.getPosture());
|
|
1228
|
+
}, [
|
|
1229
|
+
api,
|
|
1230
|
+
reviewMode,
|
|
1231
|
+
markupDisplay,
|
|
1232
|
+
debugMode,
|
|
1233
|
+
chromePreset,
|
|
1234
|
+
readOnly,
|
|
1235
|
+
]);
|
|
1236
|
+
|
|
1237
|
+
// Channel teardown on unmount — belt-and-suspenders vs stale
|
|
1238
|
+
// listeners after a later remount. The refs are per-instance so
|
|
1239
|
+
// lifetime is bounded, but clearing avoids a latent leak if a
|
|
1240
|
+
// host re-mounts the editor in the same tab.
|
|
1241
|
+
useEffect(() => {
|
|
1242
|
+
const chrome = chromeChannelRef.current;
|
|
1243
|
+
const viewport = viewportChannelRef.current;
|
|
1244
|
+
const overlays = overlaysChannelRef.current;
|
|
1245
|
+
return () => {
|
|
1246
|
+
chrome?.dispose();
|
|
1247
|
+
viewport?.dispose();
|
|
1248
|
+
overlays?.dispose();
|
|
1249
|
+
};
|
|
1250
|
+
}, []);
|
|
1269
1251
|
const metaSlice = useRuntimeSnapshotSlice(
|
|
1270
1252
|
runtime,
|
|
1271
1253
|
fallbackSnapshot,
|
|
@@ -1586,54 +1568,21 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1586
1568
|
[activeReviewQueueItemId, activeRuntime, reviewQueueSnapshot],
|
|
1587
1569
|
);
|
|
1588
1570
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
commandAppliedBridge,
|
|
1596
|
-
});
|
|
1597
|
-
return () => handle.destroy();
|
|
1598
|
-
}, [commandAppliedBridge, currentUser.userId, runtime, ydoc]);
|
|
1599
|
-
|
|
1600
|
-
useEffect(() => {
|
|
1601
|
-
if (!awareness) {
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
return () => clearLocalCursorState(awareness);
|
|
1605
|
-
}, [awareness]);
|
|
1606
|
-
|
|
1607
|
-
useEffect(() => {
|
|
1608
|
-
if (!awareness) {
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
if (!runtime) {
|
|
1612
|
-
clearLocalCursorState(awareness);
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1571
|
+
useRuntimeCollabSync({
|
|
1572
|
+
ydoc,
|
|
1573
|
+
runtime,
|
|
1574
|
+
authorId: currentUser.userId,
|
|
1575
|
+
commandAppliedBridge,
|
|
1576
|
+
});
|
|
1615
1577
|
|
|
1616
|
-
|
|
1617
|
-
suppressNextAwarenessPublishRef.current = false;
|
|
1618
|
-
} else {
|
|
1619
|
-
setLocalCursorState(awareness, {
|
|
1620
|
-
userId: currentUser.userId,
|
|
1621
|
-
displayName: currentUser.displayName,
|
|
1622
|
-
color: getCursorColorForUser(currentUser.userId),
|
|
1623
|
-
anchor: snapshot.selection.anchor,
|
|
1624
|
-
head: snapshot.selection.head,
|
|
1625
|
-
storyTarget: snapshot.activeStory,
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
}, [
|
|
1578
|
+
useRemoteCursorPublish({
|
|
1629
1579
|
awareness,
|
|
1630
|
-
currentUser.displayName,
|
|
1631
|
-
currentUser.userId,
|
|
1632
1580
|
runtime,
|
|
1633
|
-
|
|
1634
|
-
snapshot.selection.anchor,
|
|
1635
|
-
snapshot.
|
|
1636
|
-
|
|
1581
|
+
currentUser,
|
|
1582
|
+
selection: { anchor: snapshot.selection.anchor, head: snapshot.selection.head },
|
|
1583
|
+
activeStory: snapshot.activeStory,
|
|
1584
|
+
suppressNextPublishRef: suppressNextAwarenessPublishRef,
|
|
1585
|
+
});
|
|
1637
1586
|
|
|
1638
1587
|
useEffect(() => {
|
|
1639
1588
|
runtimeViewStateSeedRef.current = {
|
|
@@ -1659,7 +1608,14 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
1659
1608
|
if (!r) throw new Error("ref projection used before ref initialization");
|
|
1660
1609
|
return r;
|
|
1661
1610
|
});
|
|
1611
|
+
// L07 closure pass — share the v3 surface with the render-time
|
|
1612
|
+
// `api` memo (refactor/11 Slice 4). The provider mounts the same
|
|
1613
|
+
// frozen ApiV3 that `ref.getApiV3()` returns; consumers reading
|
|
1614
|
+
// through `useUiApi()` see the identical `api.ui` the host-side
|
|
1615
|
+
// ref exposes, so provider path and ref path cannot drift.
|
|
1616
|
+
const cachedApiV3: ApiV3 = api;
|
|
1662
1617
|
const refValue: WordReviewEditorRef = ({
|
|
1618
|
+
getApiV3: () => cachedApiV3,
|
|
1663
1619
|
focus: () => activeRuntime.focus(),
|
|
1664
1620
|
blur: () => activeRuntime.blur(),
|
|
1665
1621
|
undo: () => activeRuntime.undo(),
|
|
@@ -2081,6 +2037,30 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2081
2037
|
getWorkflowMetadataSnapshot: () => {
|
|
2082
2038
|
return clonePublicValue(activeRuntime.getWorkflowMetadataSnapshot());
|
|
2083
2039
|
},
|
|
2040
|
+
getVisibilityPolicy: (kind) => {
|
|
2041
|
+
return clonePublicValue(activeRuntime.getVisibilityPolicy(kind));
|
|
2042
|
+
},
|
|
2043
|
+
getVisibilityPolicies: () => {
|
|
2044
|
+
return clonePublicValue(activeRuntime.getVisibilityPolicies());
|
|
2045
|
+
},
|
|
2046
|
+
setVisibilityPolicy: (policy) => {
|
|
2047
|
+
return activeRuntime.setVisibilityPolicy(clonePublicValue(policy));
|
|
2048
|
+
},
|
|
2049
|
+
clearVisibilityPolicy: (kind) => {
|
|
2050
|
+
return activeRuntime.clearVisibilityPolicy(kind);
|
|
2051
|
+
},
|
|
2052
|
+
subscribeVisibilityPolicy: (listener) => {
|
|
2053
|
+
return activeRuntime.subscribeVisibilityPolicy(listener);
|
|
2054
|
+
},
|
|
2055
|
+
getMarkupModePolicy: () => {
|
|
2056
|
+
return clonePublicValue(activeRuntime.getMarkupModePolicy());
|
|
2057
|
+
},
|
|
2058
|
+
setMarkupModePolicy: (policy) => {
|
|
2059
|
+
return activeRuntime.setMarkupModePolicy(policy ? clonePublicValue(policy) : null);
|
|
2060
|
+
},
|
|
2061
|
+
subscribeMarkupModePolicy: (listener) => {
|
|
2062
|
+
return activeRuntime.subscribeMarkupModePolicy(listener);
|
|
2063
|
+
},
|
|
2084
2064
|
queryScopes: (filter) => {
|
|
2085
2065
|
return clonePublicValue(activeRuntime.queryScopes(filter));
|
|
2086
2066
|
},
|
|
@@ -2307,12 +2287,21 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2307
2287
|
},
|
|
2308
2288
|
...projections,
|
|
2309
2289
|
}) as WordReviewEditorRef;
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2290
|
+
// Phase 1 api-channel — Proxy wrap for trace events when enabled.
|
|
2291
|
+
const telemetryBus = activeRuntime.debug?.bus;
|
|
2292
|
+
if (!telemetryBus) {
|
|
2293
|
+
refHolder.current = refValue;
|
|
2294
|
+
editorRefForRepl.current = refValue;
|
|
2295
|
+
return refValue;
|
|
2296
|
+
}
|
|
2297
|
+
const tracedRef = wrapRefForTelemetry(refValue, telemetryBus);
|
|
2298
|
+
refHolder.current = tracedRef;
|
|
2299
|
+
editorRefForRepl.current = tracedRef;
|
|
2300
|
+
return tracedRef;
|
|
2313
2301
|
},
|
|
2314
2302
|
[
|
|
2315
2303
|
activeRuntime,
|
|
2304
|
+
api,
|
|
2316
2305
|
clearReviewSectionMarkById,
|
|
2317
2306
|
currentUser.userId,
|
|
2318
2307
|
documentId,
|
|
@@ -2515,38 +2504,10 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2515
2504
|
)
|
|
2516
2505
|
.sort()
|
|
2517
2506
|
.join("|");
|
|
2518
|
-
const mediaPreviews = useMemo(
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
try {
|
|
2523
|
-
const bytes = decodePersistedSourcePackageBytes(sourcePackage);
|
|
2524
|
-
if (!hasValidPersistedSourcePackageDigest(sourcePackage, bytes)) {
|
|
2525
|
-
return {} as Record<string, MediaPreviewDescriptor>;
|
|
2526
|
-
}
|
|
2527
|
-
const opc = readOpcPackage(bytes);
|
|
2528
|
-
const previews: Record<string, MediaPreviewDescriptor> = {};
|
|
2529
|
-
for (const item of Object.values(canonicalDocument.media.items)) {
|
|
2530
|
-
const contentType = item.contentType?.toLowerCase();
|
|
2531
|
-
const part = opc.parts.get(item.packagePartName);
|
|
2532
|
-
if (
|
|
2533
|
-
!part?.bytes ||
|
|
2534
|
-
!contentType ||
|
|
2535
|
-
!BROWSER_SAFE_PREVIEW_TYPES.has(contentType)
|
|
2536
|
-
) {
|
|
2537
|
-
continue;
|
|
2538
|
-
}
|
|
2539
|
-
previews[item.mediaId] = {
|
|
2540
|
-
src: createImageDataUrl(contentType, part.bytes),
|
|
2541
|
-
...(item.widthEmu !== undefined ? { widthEmu: item.widthEmu } : {}),
|
|
2542
|
-
...(item.heightEmu !== undefined ? { heightEmu: item.heightEmu } : {}),
|
|
2543
|
-
};
|
|
2544
|
-
}
|
|
2545
|
-
return previews;
|
|
2546
|
-
} catch {
|
|
2547
|
-
return {} as Record<string, MediaPreviewDescriptor>;
|
|
2548
|
-
}
|
|
2549
|
-
}, [mediaPreviewCatalogKey, sourcePackage?.sha256Hex]);
|
|
2507
|
+
const mediaPreviews = useMemo(
|
|
2508
|
+
() => buildMediaPreviews(sourcePackage, canonicalDocument),
|
|
2509
|
+
[mediaPreviewCatalogKey, sourcePackage?.sha256Hex],
|
|
2510
|
+
);
|
|
2550
2511
|
const activeObjectContext = useMemo(
|
|
2551
2512
|
() =>
|
|
2552
2513
|
viewState.activeObjectFrame && viewState.activeObjectFrame.kind !== "image"
|
|
@@ -2791,6 +2752,36 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
2791
2752
|
[],
|
|
2792
2753
|
);
|
|
2793
2754
|
|
|
2755
|
+
// §3.1 — merged ref that writes to the internal ref AND forwards to
|
|
2756
|
+
// the host-supplied `chromeControllerRef` (when present). Supports
|
|
2757
|
+
// both callback refs and mutable ref objects.
|
|
2758
|
+
const teedChromeControllerRef = useCallback(
|
|
2759
|
+
(instance: TwWorkspaceChromeHostController | null) => {
|
|
2760
|
+
internalChromeControllerRef.current = instance;
|
|
2761
|
+
if (!chromeControllerRef) return;
|
|
2762
|
+
if (typeof chromeControllerRef === "function") {
|
|
2763
|
+
chromeControllerRef(instance);
|
|
2764
|
+
} else {
|
|
2765
|
+
(chromeControllerRef as React.MutableRefObject<
|
|
2766
|
+
TwWorkspaceChromeHostController | null
|
|
2767
|
+
>).current = instance;
|
|
2768
|
+
}
|
|
2769
|
+
},
|
|
2770
|
+
[chromeControllerRef],
|
|
2771
|
+
);
|
|
2772
|
+
|
|
2773
|
+
// §3.1 — contextmenu dispatch: when `editorActionHost` is wired,
|
|
2774
|
+
// forward PM's `contextmenu` DOM events to the workspace chrome
|
|
2775
|
+
// host's controller, which resolves target kinds and mounts the
|
|
2776
|
+
// menu portal. Without `editorActionHost` the chrome host is
|
|
2777
|
+
// unmounted, so we leave the native menu alone (back-compat).
|
|
2778
|
+
const handleContextMenuRequested = useCallback(
|
|
2779
|
+
(event: { clientX: number; clientY: number; target: EventTarget | null }) => {
|
|
2780
|
+
internalChromeControllerRef.current?.handleContextMenuRequest(event);
|
|
2781
|
+
},
|
|
2782
|
+
[],
|
|
2783
|
+
);
|
|
2784
|
+
|
|
2794
2785
|
useEffect(() => {
|
|
2795
2786
|
if (!selectionToolbarSelectionKey) {
|
|
2796
2787
|
setSuppressedSuggestionRevisionId(null);
|
|
@@ -3327,6 +3318,9 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3327
3318
|
ref={surfaceRef}
|
|
3328
3319
|
currentUser={currentUser}
|
|
3329
3320
|
awareness={awareness}
|
|
3321
|
+
pasteFragmentParser={SHELL_PASTE_FRAGMENT_PARSER}
|
|
3322
|
+
runtimeSearchDocument={api.runtime.search.searchDocument}
|
|
3323
|
+
runtimeGetTableSelectionDescriptor={api.runtime.table.getSelectionDescriptor}
|
|
3330
3324
|
snapshot={snapshot}
|
|
3331
3325
|
canonicalDocument={canonicalDocument}
|
|
3332
3326
|
documentNavigation={documentNavigation}
|
|
@@ -3347,11 +3341,13 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3347
3341
|
activeWorkflowScopeIds={workflowScopeSnapshot?.activeWorkItem?.scopeIds ?? []}
|
|
3348
3342
|
workflowMetadata={workflowMarkupSnapshot?.metadata}
|
|
3349
3343
|
onSelectionToolbarAnchorChange={handleSelectionToolbarAnchorChange}
|
|
3344
|
+
{...(editorActionHost ? { onContextMenuRequested: handleContextMenuRequested } : {})}
|
|
3350
3345
|
{...editorCallbacks}
|
|
3351
3346
|
dispatchRuntimeCommand={(command) =>
|
|
3352
3347
|
activeRuntime.applyActiveStoryTextCommand(command as never)
|
|
3353
3348
|
}
|
|
3354
3349
|
layoutFacet={activeRuntime.layout}
|
|
3350
|
+
geometryFacet={activeRuntime.geometry}
|
|
3355
3351
|
pageChromeHeaderBandPx={isPageWorkspace ? 32 : 0}
|
|
3356
3352
|
pageChromeFooterBandPx={isPageWorkspace ? 32 : 0}
|
|
3357
3353
|
pageChromeInterGapPx={isPageWorkspace ? 24 : 16}
|
|
@@ -3372,7 +3368,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3372
3368
|
/>
|
|
3373
3369
|
);
|
|
3374
3370
|
|
|
3371
|
+
const shellChannels: UiShellChannels = {
|
|
3372
|
+
chrome: chromeChannelRef.current!,
|
|
3373
|
+
viewport: viewportChannelRef.current!,
|
|
3374
|
+
overlays: overlaysChannelRef.current!,
|
|
3375
|
+
};
|
|
3376
|
+
|
|
3375
3377
|
return (
|
|
3378
|
+
<UiApiProvider value={api.ui ?? null}>
|
|
3379
|
+
<UiShellChannelsProvider value={shellChannels}>
|
|
3380
|
+
<OverlayAnchorBridgeProvider value={overlayAnchorBridgeRef.current}>
|
|
3376
3381
|
<>
|
|
3377
3382
|
<EditorShellView
|
|
3378
3383
|
shellRef={shellRef}
|
|
@@ -3391,6 +3396,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3391
3396
|
onShellKeyDownCapture={handleShellKeyDownCapture}
|
|
3392
3397
|
shellHeader={shellHeader}
|
|
3393
3398
|
viewState={viewState}
|
|
3399
|
+
onEditorRoleChange={(role) => activeRuntime.setEditorRole(role)}
|
|
3394
3400
|
markupDisplay={liveMarkupDisplay}
|
|
3395
3401
|
currentUserId={currentUser.userId}
|
|
3396
3402
|
capabilities={capabilities}
|
|
@@ -3408,9 +3414,16 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3408
3414
|
showTrackedChanges={showTrackedChanges}
|
|
3409
3415
|
workflowScopeSnapshot={workflowScopeSnapshot}
|
|
3410
3416
|
layoutFacet={activeRuntime.layout}
|
|
3417
|
+
geometryFacet={activeRuntime.geometry}
|
|
3418
|
+
workflowFacet={activeRuntime.workflow}
|
|
3411
3419
|
interactionGuardSnapshot={interactionGuardSnapshot}
|
|
3412
3420
|
chromePreset={effectiveChromePreset}
|
|
3413
3421
|
chromeOptions={chromeOptions}
|
|
3422
|
+
{...(editorActionHost ? { editorActionHost } : {})}
|
|
3423
|
+
{...(editorActionHost ? { chromeControllerRef: teedChromeControllerRef } : {})}
|
|
3424
|
+
{...(commandPaletteDisabled !== undefined
|
|
3425
|
+
? { commandPaletteDisabled }
|
|
3426
|
+
: {})}
|
|
3414
3427
|
{...(props.collabSession ? { collabSession: props.collabSession } : {})}
|
|
3415
3428
|
{...(props.collabTransportStatus
|
|
3416
3429
|
? { collabTransportStatus: props.collabTransportStatus }
|
|
@@ -3511,13 +3524,11 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3511
3524
|
}}
|
|
3512
3525
|
onDeselectObject={() => activeRuntime.deselectObject()}
|
|
3513
3526
|
onScopeAskAgent={(payload) => {
|
|
3514
|
-
// Resolve the scope's anchor + story from the
|
|
3515
|
-
// model so the agent request carries the
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
? facet.getAllScopeCardModels()
|
|
3520
|
-
: [];
|
|
3527
|
+
// Resolve the scope's anchor + story from the workflow
|
|
3528
|
+
// facet's card model so the agent request carries the
|
|
3529
|
+
// canonical range. Layer-06 Slice 4 made `runtime.workflow`
|
|
3530
|
+
// the canonical source for scope-card data.
|
|
3531
|
+
const models = activeRuntime.workflow.getAllScopeCardModels();
|
|
3521
3532
|
const model = models.find((entry) => entry.scopeId === payload.scopeId);
|
|
3522
3533
|
if (!model) return;
|
|
3523
3534
|
const scopeSnapshot = activeRuntime.getWorkflowScopeSnapshot();
|
|
@@ -3546,1182 +3557,52 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
3546
3557
|
}}
|
|
3547
3558
|
/>
|
|
3548
3559
|
<TwRuntimeReplDialog runtime={activeRuntime} editorRef={editorRefForRepl} />
|
|
3560
|
+
<TwDebugPresentation
|
|
3561
|
+
mode={debugMode}
|
|
3562
|
+
sessionId={documentId}
|
|
3563
|
+
/>
|
|
3549
3564
|
</>
|
|
3565
|
+
</OverlayAnchorBridgeProvider>
|
|
3566
|
+
</UiShellChannelsProvider>
|
|
3567
|
+
</UiApiProvider>
|
|
3550
3568
|
);
|
|
3551
3569
|
},
|
|
3552
3570
|
);
|
|
3553
3571
|
|
|
3554
3572
|
/**
|
|
3555
|
-
*
|
|
3556
|
-
* the
|
|
3557
|
-
*
|
|
3558
|
-
*
|
|
3559
|
-
*
|
|
3560
|
-
* A future phase adds true atomicity at the runtime level.
|
|
3573
|
+
* Translate a public-API `TableOp` (kebab-case `kind` discriminator) to
|
|
3574
|
+
* the internal `TableStructureOperation` (`type` discriminator). Thin
|
|
3575
|
+
* re-export of the shell `publicTableOpToInternal` helper so legacy
|
|
3576
|
+
* tests keep their existing import path
|
|
3577
|
+
* (`test/api/table-op-translation.test.ts`).
|
|
3561
3578
|
*/
|
|
3562
|
-
function
|
|
3563
|
-
|
|
3564
|
-
groupId: string,
|
|
3565
|
-
action: "accept" | "reject",
|
|
3566
|
-
): void {
|
|
3567
|
-
const snapshot = runtime.getSuggestionsSnapshot();
|
|
3568
|
-
const group = snapshot.groups?.find((entry) => entry.groupId === groupId);
|
|
3569
|
-
const op = action === "accept" ? "acceptSuggestionGroup" : "rejectSuggestionGroup";
|
|
3570
|
-
if (!group) {
|
|
3571
|
-
runtime.emitTransientWarning({
|
|
3572
|
-
warningId: `suggestion-group-unknown-${groupId}-${Date.now()}`,
|
|
3573
|
-
code: "review_target_not_found",
|
|
3574
|
-
severity: "info",
|
|
3575
|
-
message: `${op}("${groupId}") skipped: unknown groupId.`,
|
|
3576
|
-
source: "review",
|
|
3577
|
-
details: { op, targetId: groupId, reason: "group_unknown" },
|
|
3578
|
-
});
|
|
3579
|
-
return;
|
|
3580
|
-
}
|
|
3581
|
-
const byId = new Map(
|
|
3582
|
-
snapshot.suggestions.map((entry) => [entry.suggestionId, entry]),
|
|
3583
|
-
);
|
|
3584
|
-
const skippedSuggestions: string[] = [];
|
|
3585
|
-
for (const suggestionId of group.suggestionIds) {
|
|
3586
|
-
const suggestion = byId.get(suggestionId);
|
|
3587
|
-
if (!suggestion) {
|
|
3588
|
-
skippedSuggestions.push(suggestionId);
|
|
3589
|
-
continue;
|
|
3590
|
-
}
|
|
3591
|
-
for (const changeId of suggestion.changeIds) {
|
|
3592
|
-
if (action === "accept") {
|
|
3593
|
-
runtime.acceptChange(changeId);
|
|
3594
|
-
} else {
|
|
3595
|
-
runtime.rejectChange(changeId);
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
if (skippedSuggestions.length > 0) {
|
|
3600
|
-
runtime.emitTransientWarning({
|
|
3601
|
-
warningId: `suggestion-group-stale-${groupId}-${Date.now()}`,
|
|
3602
|
-
code: "review_target_not_found",
|
|
3603
|
-
severity: "info",
|
|
3604
|
-
message: `${op}("${groupId}") partially skipped: ${skippedSuggestions.length} suggestion(s) no longer in snapshot.`,
|
|
3605
|
-
source: "review",
|
|
3606
|
-
details: {
|
|
3607
|
-
op,
|
|
3608
|
-
targetId: groupId,
|
|
3609
|
-
reason: "suggestion_stale",
|
|
3610
|
-
skippedSuggestionIds: skippedSuggestions,
|
|
3611
|
-
},
|
|
3612
|
-
});
|
|
3613
|
-
}
|
|
3579
|
+
export function __publicTableOpToInternal(op: TableOp): TableStructureOperation {
|
|
3580
|
+
return publicTableOpToInternal(op);
|
|
3614
3581
|
}
|
|
3615
3582
|
|
|
3616
|
-
|
|
3583
|
+
/**
|
|
3584
|
+
* Build the `ref.tables` facet: a typed dispatch boundary + capability
|
|
3585
|
+
* read. Delegates every op through the same `applyRuntimeTableStructureOperation`
|
|
3586
|
+
* helper the flat ref verbs use, so there is exactly one server-side path
|
|
3587
|
+
* for every table mutation.
|
|
3588
|
+
*/
|
|
3589
|
+
function buildTablesFacet(
|
|
3617
3590
|
runtime: WordReviewEditorRuntime,
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
}
|
|
3633
|
-
if (emitSuggestingUnsupportedMutation(runtime, getFormattingOperationCommandName(operation))) {
|
|
3634
|
-
return;
|
|
3635
|
-
}
|
|
3636
|
-
const context = getStoryMutationContext(runtime, getFormattingOperationCommandName(operation));
|
|
3637
|
-
if (!context) {
|
|
3638
|
-
return;
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
|
-
const result = applyFormattingOperationToDocument(
|
|
3642
|
-
context.localDocument,
|
|
3643
|
-
context.localSnapshot,
|
|
3644
|
-
operation,
|
|
3645
|
-
);
|
|
3646
|
-
dispatchStoryMutationResult(
|
|
3647
|
-
runtime,
|
|
3648
|
-
context,
|
|
3649
|
-
{
|
|
3650
|
-
...result,
|
|
3651
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3652
|
-
},
|
|
3653
|
-
context.timestamp,
|
|
3654
|
-
);
|
|
3655
|
-
}
|
|
3656
|
-
|
|
3657
|
-
function applySuggestingFormattingOperation(
|
|
3658
|
-
runtime: WordReviewEditorRuntime,
|
|
3659
|
-
operation:
|
|
3660
|
-
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
3661
|
-
| { type: "set-font-family"; fontFamily: string | null }
|
|
3662
|
-
| { type: "set-font-size"; size: number | null }
|
|
3663
|
-
| { type: "set-text-color"; color: string | null }
|
|
3664
|
-
| { type: "set-highlight-color"; color: string | null }
|
|
3665
|
-
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
3666
|
-
| { type: "indent" }
|
|
3667
|
-
| { type: "outdent" },
|
|
3668
|
-
): boolean {
|
|
3669
|
-
const commandName = getFormattingOperationCommandName(operation);
|
|
3670
|
-
const context = getStoryMutationContext(runtime, commandName);
|
|
3671
|
-
if (!context) {
|
|
3672
|
-
return true;
|
|
3673
|
-
}
|
|
3674
|
-
if (context.activeStory.kind !== "main") {
|
|
3675
|
-
runtime.emitBlockedCommand(commandName, [{
|
|
3676
|
-
code: "suggesting_unsupported",
|
|
3677
|
-
message: `"${commandName}" is not supported in suggesting mode for this story.`,
|
|
3678
|
-
}]);
|
|
3679
|
-
return true;
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
|
-
if (operation.type === "set-alignment" || operation.type === "indent" || operation.type === "outdent") {
|
|
3683
|
-
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
3684
|
-
if (!paragraphContext) {
|
|
3685
|
-
return true;
|
|
3686
|
-
}
|
|
3687
|
-
const beforeXml = buildParagraphPropertyBeforeXml(paragraphContext.paragraph);
|
|
3688
|
-
const result = applyFormattingOperationToDocument(
|
|
3689
|
-
context.localDocument,
|
|
3690
|
-
context.localSnapshot,
|
|
3691
|
-
operation,
|
|
3692
|
-
);
|
|
3693
|
-
if (!result.changed) {
|
|
3694
|
-
return true;
|
|
3695
|
-
}
|
|
3696
|
-
const nextDocument = appendPropertyChangeSuggestion(
|
|
3697
|
-
result.document,
|
|
3698
|
-
{
|
|
3699
|
-
from: paragraphContext.paragraph.from,
|
|
3700
|
-
to: paragraphContext.paragraph.to,
|
|
3701
|
-
},
|
|
3702
|
-
{
|
|
3703
|
-
originalRevisionType: "pPrChange",
|
|
3704
|
-
xmlTag: "pPrChange",
|
|
3705
|
-
beforeXml,
|
|
3706
|
-
semanticKind: "paragraph-property-change",
|
|
3707
|
-
storyTarget: context.activeStory,
|
|
3708
|
-
authorId: runtime.getDefaultAuthorId?.(),
|
|
3709
|
-
},
|
|
3710
|
-
context.timestamp,
|
|
3711
|
-
);
|
|
3712
|
-
dispatchStoryMutationResult(
|
|
3713
|
-
runtime,
|
|
3714
|
-
context,
|
|
3715
|
-
{
|
|
3716
|
-
changed: true,
|
|
3717
|
-
document: nextDocument,
|
|
3718
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3719
|
-
},
|
|
3720
|
-
context.timestamp,
|
|
3721
|
-
);
|
|
3722
|
-
return true;
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
|
-
const segment = findSingleSelectedTextSegment(context.localSnapshot);
|
|
3726
|
-
if (!segment) {
|
|
3727
|
-
runtime.emitBlockedCommand(commandName, [{
|
|
3728
|
-
code: "suggesting_unsupported",
|
|
3729
|
-
message: `"${commandName}" requires one bounded text segment in suggesting mode.`,
|
|
3730
|
-
}]);
|
|
3731
|
-
return true;
|
|
3732
|
-
}
|
|
3733
|
-
const beforeXml = buildRunPropertyBeforeXml(segment);
|
|
3734
|
-
const result = applyFormattingOperationToDocument(
|
|
3735
|
-
context.localDocument,
|
|
3736
|
-
context.localSnapshot,
|
|
3737
|
-
operation,
|
|
3738
|
-
);
|
|
3739
|
-
if (!result.changed) {
|
|
3740
|
-
return true;
|
|
3741
|
-
}
|
|
3742
|
-
const nextDocument = appendPropertyChangeSuggestion(
|
|
3743
|
-
result.document,
|
|
3744
|
-
{
|
|
3745
|
-
from: segment.from,
|
|
3746
|
-
to: segment.to,
|
|
3747
|
-
},
|
|
3748
|
-
{
|
|
3749
|
-
originalRevisionType: "rPrChange",
|
|
3750
|
-
xmlTag: "rPrChange",
|
|
3751
|
-
beforeXml,
|
|
3752
|
-
semanticKind: "formatting-change",
|
|
3753
|
-
storyTarget: context.activeStory,
|
|
3754
|
-
authorId: runtime.getDefaultAuthorId?.(),
|
|
3755
|
-
},
|
|
3756
|
-
context.timestamp,
|
|
3757
|
-
);
|
|
3758
|
-
dispatchStoryMutationResult(
|
|
3759
|
-
runtime,
|
|
3760
|
-
context,
|
|
3761
|
-
{
|
|
3762
|
-
changed: true,
|
|
3763
|
-
document: nextDocument,
|
|
3764
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3765
|
-
},
|
|
3766
|
-
context.timestamp,
|
|
3767
|
-
);
|
|
3768
|
-
return true;
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
function applyRuntimeListToggle(
|
|
3772
|
-
runtime: WordReviewEditorRuntime,
|
|
3773
|
-
kind: "bulleted" | "numbered",
|
|
3774
|
-
): void {
|
|
3775
|
-
const commandName =
|
|
3776
|
-
kind === "bulleted" ? "toggleBulletedList" : "toggleNumberedList";
|
|
3777
|
-
if (emitSuggestingUnsupportedMutation(runtime, commandName)) {
|
|
3778
|
-
return;
|
|
3779
|
-
}
|
|
3780
|
-
const context = getStoryMutationContext(runtime, commandName);
|
|
3781
|
-
if (!context) {
|
|
3782
|
-
return;
|
|
3783
|
-
}
|
|
3784
|
-
|
|
3785
|
-
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
3786
|
-
if (!paragraphContext) {
|
|
3787
|
-
return;
|
|
3788
|
-
}
|
|
3789
|
-
|
|
3790
|
-
const result =
|
|
3791
|
-
kind === "bulleted"
|
|
3792
|
-
? toggleBulletedList(
|
|
3793
|
-
context.localDocument,
|
|
3794
|
-
[paragraphContext.paragraphIndex],
|
|
3795
|
-
{ timestamp: context.timestamp },
|
|
3796
|
-
)
|
|
3797
|
-
: toggleNumberedList(
|
|
3798
|
-
context.localDocument,
|
|
3799
|
-
[paragraphContext.paragraphIndex],
|
|
3800
|
-
{ timestamp: context.timestamp },
|
|
3801
|
-
);
|
|
3802
|
-
dispatchStoryMutationResult(
|
|
3803
|
-
runtime,
|
|
3804
|
-
context,
|
|
3805
|
-
{
|
|
3806
|
-
changed: result.affectedParagraphIndexes.length > 0,
|
|
3807
|
-
document: result.document,
|
|
3808
|
-
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
3809
|
-
},
|
|
3810
|
-
context.timestamp,
|
|
3811
|
-
);
|
|
3812
|
-
}
|
|
3813
|
-
|
|
3814
|
-
function getRuntimeStyleCatalog(
|
|
3815
|
-
input:
|
|
3816
|
-
| WordReviewEditorRuntime
|
|
3817
|
-
| EditorSessionState["canonicalDocument"]["styles"],
|
|
3818
|
-
): StyleCatalogSnapshot {
|
|
3819
|
-
const styles =
|
|
3820
|
-
"getSessionState" in input
|
|
3821
|
-
? input.getSessionState().canonicalDocument.styles
|
|
3822
|
-
: input;
|
|
3823
|
-
const mapRecord = <
|
|
3824
|
-
T extends {
|
|
3825
|
-
styleId: string;
|
|
3826
|
-
displayName: string;
|
|
3827
|
-
kind: "paragraph" | "character" | "table";
|
|
3828
|
-
isDefault: boolean;
|
|
3829
|
-
basedOn?: string;
|
|
3830
|
-
nextStyle?: string;
|
|
3831
|
-
},
|
|
3832
|
-
>(
|
|
3833
|
-
record: Record<string, T>,
|
|
3834
|
-
) =>
|
|
3835
|
-
Object.values(record)
|
|
3836
|
-
.map((entry) => ({
|
|
3837
|
-
styleId: entry.styleId,
|
|
3838
|
-
displayName: entry.displayName,
|
|
3839
|
-
kind: entry.kind,
|
|
3840
|
-
isDefault: entry.isDefault,
|
|
3841
|
-
...(entry.basedOn ? { basedOn: entry.basedOn } : {}),
|
|
3842
|
-
...(entry.nextStyle ? { nextStyle: entry.nextStyle } : {}),
|
|
3843
|
-
}))
|
|
3844
|
-
.sort((left, right) =>
|
|
3845
|
-
left.displayName.localeCompare(right.displayName) ||
|
|
3846
|
-
left.styleId.localeCompare(right.styleId),
|
|
3847
|
-
);
|
|
3848
|
-
|
|
3849
|
-
return {
|
|
3850
|
-
paragraphs: mapRecord(styles.paragraphs),
|
|
3851
|
-
characters: mapRecord(styles.characters),
|
|
3852
|
-
tables: mapRecord(styles.tables),
|
|
3853
|
-
fromPackage: styles.fromPackage === true,
|
|
3854
|
-
};
|
|
3855
|
-
}
|
|
3856
|
-
|
|
3857
|
-
function applyRuntimeParagraphStyle(
|
|
3858
|
-
runtime: WordReviewEditorRuntime,
|
|
3859
|
-
styleId: string | null,
|
|
3860
|
-
): void {
|
|
3861
|
-
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphStyle")) {
|
|
3862
|
-
return;
|
|
3863
|
-
}
|
|
3864
|
-
const context = getStoryMutationContext(runtime, "setParagraphStyle");
|
|
3865
|
-
if (!context) {
|
|
3866
|
-
return;
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
|
-
const result = applyParagraphStyleToDocument(
|
|
3870
|
-
context.localDocument,
|
|
3871
|
-
context.localSnapshot,
|
|
3872
|
-
styleId,
|
|
3873
|
-
);
|
|
3874
|
-
dispatchStoryMutationResult(
|
|
3875
|
-
runtime,
|
|
3876
|
-
context,
|
|
3877
|
-
{
|
|
3878
|
-
...result,
|
|
3879
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3880
|
-
},
|
|
3881
|
-
context.timestamp,
|
|
3882
|
-
);
|
|
3883
|
-
}
|
|
3884
|
-
|
|
3885
|
-
function applyRuntimeTableStyle(
|
|
3886
|
-
runtime: WordReviewEditorRuntime,
|
|
3887
|
-
styleId: string | null,
|
|
3888
|
-
): void {
|
|
3889
|
-
if (emitSuggestingUnsupportedMutation(runtime, "setTableStyle")) {
|
|
3890
|
-
return;
|
|
3891
|
-
}
|
|
3892
|
-
const context = getStoryMutationContext(runtime, "setTableStyle");
|
|
3893
|
-
if (!context) {
|
|
3894
|
-
return;
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
const result = applyTableStyleToDocument(
|
|
3898
|
-
context.localDocument,
|
|
3899
|
-
context.localSnapshot,
|
|
3900
|
-
styleId,
|
|
3901
|
-
);
|
|
3902
|
-
dispatchStoryMutationResult(
|
|
3903
|
-
runtime,
|
|
3904
|
-
context,
|
|
3905
|
-
{
|
|
3906
|
-
...result,
|
|
3907
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3908
|
-
},
|
|
3909
|
-
context.timestamp,
|
|
3910
|
-
);
|
|
3911
|
-
}
|
|
3912
|
-
|
|
3913
|
-
function applyRuntimeParagraphIndentation(
|
|
3914
|
-
runtime: WordReviewEditorRuntime,
|
|
3915
|
-
indentation: {
|
|
3916
|
-
left?: number;
|
|
3917
|
-
right?: number;
|
|
3918
|
-
firstLine?: number;
|
|
3919
|
-
hanging?: number;
|
|
3920
|
-
},
|
|
3921
|
-
): void {
|
|
3922
|
-
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphIndentation")) {
|
|
3923
|
-
return;
|
|
3924
|
-
}
|
|
3925
|
-
const context = getStoryMutationContext(runtime, "setParagraphIndentation");
|
|
3926
|
-
if (!context) {
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
|
-
|
|
3930
|
-
const result = setActiveParagraphIndentation(
|
|
3931
|
-
context.localDocument,
|
|
3932
|
-
context.localSnapshot,
|
|
3933
|
-
indentation,
|
|
3934
|
-
{ timestamp: context.timestamp },
|
|
3935
|
-
);
|
|
3936
|
-
dispatchStoryMutationResult(
|
|
3937
|
-
runtime,
|
|
3938
|
-
context,
|
|
3939
|
-
{
|
|
3940
|
-
...result,
|
|
3941
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3942
|
-
},
|
|
3943
|
-
context.timestamp,
|
|
3944
|
-
);
|
|
3945
|
-
}
|
|
3946
|
-
|
|
3947
|
-
function applyRuntimeParagraphTabStops(
|
|
3948
|
-
runtime: WordReviewEditorRuntime,
|
|
3949
|
-
tabStops: Array<{ pos: number; val?: string; leader?: string }>,
|
|
3950
|
-
): void {
|
|
3951
|
-
if (emitSuggestingUnsupportedMutation(runtime, "setParagraphTabStops")) {
|
|
3952
|
-
return;
|
|
3953
|
-
}
|
|
3954
|
-
const context = getStoryMutationContext(runtime, "setParagraphTabStops");
|
|
3955
|
-
if (!context) {
|
|
3956
|
-
return;
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
|
-
const result = setActiveParagraphTabStops(
|
|
3960
|
-
context.localDocument,
|
|
3961
|
-
context.localSnapshot,
|
|
3962
|
-
tabStops,
|
|
3963
|
-
{ timestamp: context.timestamp },
|
|
3964
|
-
);
|
|
3965
|
-
dispatchStoryMutationResult(
|
|
3966
|
-
runtime,
|
|
3967
|
-
context,
|
|
3968
|
-
{
|
|
3969
|
-
...result,
|
|
3970
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
3971
|
-
},
|
|
3972
|
-
context.timestamp,
|
|
3973
|
-
);
|
|
3974
|
-
}
|
|
3975
|
-
|
|
3976
|
-
function applyRuntimeNumberingFlow(
|
|
3977
|
-
runtime: WordReviewEditorRuntime,
|
|
3978
|
-
operation: { type: "restart"; startAt?: number } | { type: "continue" },
|
|
3979
|
-
): void {
|
|
3980
|
-
if (
|
|
3981
|
-
emitSuggestingUnsupportedMutation(
|
|
3982
|
-
runtime,
|
|
3983
|
-
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
3984
|
-
)
|
|
3985
|
-
) {
|
|
3986
|
-
return;
|
|
3987
|
-
}
|
|
3988
|
-
const context = getStoryMutationContext(
|
|
3989
|
-
runtime,
|
|
3990
|
-
operation.type === "restart" ? "restartNumbering" : "continueNumbering",
|
|
3991
|
-
);
|
|
3992
|
-
if (!context) {
|
|
3993
|
-
return;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
const paragraphContext = resolveActiveParagraphContext(context.localSnapshot);
|
|
3997
|
-
if (!paragraphContext?.paragraph.numbering) {
|
|
3998
|
-
return;
|
|
3999
|
-
}
|
|
4000
|
-
|
|
4001
|
-
const result =
|
|
4002
|
-
operation.type === "restart"
|
|
4003
|
-
? restartListNumbering(
|
|
4004
|
-
context.localDocument,
|
|
4005
|
-
paragraphContext.paragraphIndex,
|
|
4006
|
-
{ timestamp: context.timestamp },
|
|
4007
|
-
operation.startAt,
|
|
4008
|
-
)
|
|
4009
|
-
: continueListNumbering(
|
|
4010
|
-
context.localDocument,
|
|
4011
|
-
paragraphContext.paragraphIndex,
|
|
4012
|
-
{ timestamp: context.timestamp },
|
|
4013
|
-
);
|
|
4014
|
-
|
|
4015
|
-
dispatchStoryMutationResult(
|
|
4016
|
-
runtime,
|
|
4017
|
-
context,
|
|
4018
|
-
{
|
|
4019
|
-
changed: result.affectedParagraphIndexes.length > 0,
|
|
4020
|
-
document: result.document,
|
|
4021
|
-
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
4022
|
-
},
|
|
4023
|
-
context.timestamp,
|
|
4024
|
-
);
|
|
4025
|
-
}
|
|
4026
|
-
|
|
4027
|
-
function applyRuntimeInsertSectionBreak(
|
|
4028
|
-
runtime: WordReviewEditorRuntime,
|
|
4029
|
-
breakType: SectionBreakType,
|
|
4030
|
-
options?: { afterSectionIndex?: number },
|
|
4031
|
-
): void {
|
|
4032
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4033
|
-
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
4034
|
-
return;
|
|
4035
|
-
}
|
|
4036
|
-
if (emitWorkflowBlockedMutation(runtime, "insertSectionBreak")) {
|
|
4037
|
-
return;
|
|
4038
|
-
}
|
|
4039
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4040
|
-
runtime.emitBlockedCommand("insertSectionBreak", [{
|
|
4041
|
-
code: "unsupported_surface",
|
|
4042
|
-
message: "Section break insertion is not supported in suggesting mode.",
|
|
4043
|
-
}]);
|
|
4044
|
-
return;
|
|
4045
|
-
}
|
|
4046
|
-
|
|
4047
|
-
const sessionState = runtime.getSessionState();
|
|
4048
|
-
const timestamp = new Date().toISOString();
|
|
4049
|
-
const result =
|
|
4050
|
-
typeof options?.afterSectionIndex === "number"
|
|
4051
|
-
? insertSectionBreakAfterSectionIndex(
|
|
4052
|
-
sessionState.canonicalDocument,
|
|
4053
|
-
options.afterSectionIndex,
|
|
4054
|
-
breakType,
|
|
4055
|
-
{ timestamp },
|
|
4056
|
-
)
|
|
4057
|
-
: insertSectionBreakAfterSectionIndex(
|
|
4058
|
-
sessionState.canonicalDocument,
|
|
4059
|
-
runtime.getDocumentNavigationSnapshot().activeSectionIndex,
|
|
4060
|
-
breakType,
|
|
4061
|
-
{ timestamp },
|
|
4062
|
-
);
|
|
4063
|
-
|
|
4064
|
-
dispatchRuntimeDocumentMutation(
|
|
4065
|
-
runtime,
|
|
4066
|
-
{
|
|
4067
|
-
changed: result.changed,
|
|
4068
|
-
document: result.document,
|
|
4069
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
4070
|
-
},
|
|
4071
|
-
timestamp,
|
|
4072
|
-
);
|
|
4073
|
-
}
|
|
4074
|
-
|
|
4075
|
-
function emitSuggestingUnsupportedMutation(
|
|
4076
|
-
runtime: WordReviewEditorRuntime,
|
|
4077
|
-
command: string,
|
|
4078
|
-
): boolean {
|
|
4079
|
-
if (!isSelectionSuggesting(runtime)) {
|
|
4080
|
-
return false;
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
runtime.emitBlockedCommand(command, [{
|
|
4084
|
-
code: "suggesting_unsupported",
|
|
4085
|
-
message: `"${command}" is not supported in suggesting mode.`,
|
|
4086
|
-
}]);
|
|
4087
|
-
return true;
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
|
-
function appendPropertyChangeSuggestion(
|
|
4091
|
-
document: EditorSessionState["canonicalDocument"],
|
|
4092
|
-
anchor: { from: number; to: number },
|
|
4093
|
-
input: {
|
|
4094
|
-
originalRevisionType: "rPrChange" | "pPrChange";
|
|
4095
|
-
xmlTag: "rPrChange" | "pPrChange";
|
|
4096
|
-
beforeXml: string;
|
|
4097
|
-
semanticKind: "formatting-change" | "paragraph-property-change";
|
|
4098
|
-
storyTarget: EditorStoryTarget;
|
|
4099
|
-
authorId?: string;
|
|
4100
|
-
},
|
|
4101
|
-
timestamp: string,
|
|
4102
|
-
): EditorSessionState["canonicalDocument"] {
|
|
4103
|
-
const existing = document.review.revisions;
|
|
4104
|
-
const changeId = createRuntimeSuggestionChangeId(existing, timestamp);
|
|
4105
|
-
const resolvedAuthorId = input.authorId ?? "unknown";
|
|
4106
|
-
return {
|
|
4107
|
-
...document,
|
|
4108
|
-
review: {
|
|
4109
|
-
...document.review,
|
|
4110
|
-
revisions: {
|
|
4111
|
-
...existing,
|
|
4112
|
-
[changeId]: {
|
|
4113
|
-
changeId,
|
|
4114
|
-
kind: "property-change",
|
|
4115
|
-
anchor: createRangeAnchor(anchor.from, anchor.to, { start: 1, end: -1 }),
|
|
4116
|
-
authorId: resolvedAuthorId,
|
|
4117
|
-
createdAt: timestamp,
|
|
4118
|
-
warningIds: [],
|
|
4119
|
-
metadata: {
|
|
4120
|
-
source: "runtime",
|
|
4121
|
-
storyTarget: input.storyTarget,
|
|
4122
|
-
suggestionId: changeId,
|
|
4123
|
-
semanticKind: input.semanticKind,
|
|
4124
|
-
originalRevisionType: input.originalRevisionType,
|
|
4125
|
-
propertyChangeData: {
|
|
4126
|
-
xmlTag: input.xmlTag,
|
|
4127
|
-
beforeXml: input.beforeXml,
|
|
4128
|
-
},
|
|
4129
|
-
},
|
|
4130
|
-
status: "open",
|
|
4131
|
-
},
|
|
4132
|
-
},
|
|
4133
|
-
},
|
|
4134
|
-
};
|
|
4135
|
-
}
|
|
4136
|
-
|
|
4137
|
-
function createRuntimeSuggestionChangeId(
|
|
4138
|
-
existing: EditorSessionState["canonicalDocument"]["review"]["revisions"],
|
|
4139
|
-
timestamp: string,
|
|
4140
|
-
): string {
|
|
4141
|
-
const base = `change-${timestamp.replace(/[^0-9]/gu, "")}`;
|
|
4142
|
-
let counter = Object.keys(existing).length + 1;
|
|
4143
|
-
let candidate = `${base}-p${counter}`;
|
|
4144
|
-
while (existing[candidate]) {
|
|
4145
|
-
counter += 1;
|
|
4146
|
-
candidate = `${base}-p${counter}`;
|
|
4147
|
-
}
|
|
4148
|
-
return candidate;
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
function findSingleSelectedTextSegment(
|
|
4152
|
-
snapshot: Pick<RuntimeRenderSnapshot, "surface" | "selection">,
|
|
4153
|
-
): Extract<SurfaceInlineSegment, { kind: "text" }> | null {
|
|
4154
|
-
if (!snapshot.surface || snapshot.selection.activeRange.kind !== "range" || snapshot.selection.isCollapsed) {
|
|
4155
|
-
return null;
|
|
4156
|
-
}
|
|
4157
|
-
const selectionFrom = Math.min(snapshot.selection.anchor, snapshot.selection.head);
|
|
4158
|
-
const selectionTo = Math.max(snapshot.selection.anchor, snapshot.selection.head);
|
|
4159
|
-
const segments = collectSelectedTextSegments(snapshot.surface.blocks, selectionFrom, selectionTo);
|
|
4160
|
-
if (segments.length !== 1) {
|
|
4161
|
-
return null;
|
|
4162
|
-
}
|
|
4163
|
-
const [segment] = segments;
|
|
4164
|
-
if (!segment || segment.from !== selectionFrom || segment.to !== selectionTo) {
|
|
4165
|
-
return null;
|
|
4166
|
-
}
|
|
4167
|
-
return segment;
|
|
4168
|
-
}
|
|
4169
|
-
|
|
4170
|
-
function collectSelectedTextSegments(
|
|
4171
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
4172
|
-
selectionFrom: number,
|
|
4173
|
-
selectionTo: number,
|
|
4174
|
-
output: Array<Extract<SurfaceInlineSegment, { kind: "text" }>> = [],
|
|
4175
|
-
): Array<Extract<SurfaceInlineSegment, { kind: "text" }>> {
|
|
4176
|
-
for (const block of blocks) {
|
|
4177
|
-
if (block.kind === "paragraph") {
|
|
4178
|
-
for (const segment of block.segments) {
|
|
4179
|
-
if (
|
|
4180
|
-
segment.kind === "text" &&
|
|
4181
|
-
rangesOverlap(selectionFrom, selectionTo, segment.from, segment.to)
|
|
4182
|
-
) {
|
|
4183
|
-
output.push(segment);
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
continue;
|
|
4187
|
-
}
|
|
4188
|
-
if (block.kind === "table") {
|
|
4189
|
-
for (const row of block.rows) {
|
|
4190
|
-
for (const cell of row.cells) {
|
|
4191
|
-
collectSelectedTextSegments(cell.content, selectionFrom, selectionTo, output);
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
continue;
|
|
4195
|
-
}
|
|
4196
|
-
if (block.kind === "sdt_block") {
|
|
4197
|
-
collectSelectedTextSegments(block.children, selectionFrom, selectionTo, output);
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
return output;
|
|
4201
|
-
}
|
|
4202
|
-
|
|
4203
|
-
function buildRunPropertyBeforeXml(
|
|
4204
|
-
segment: Extract<SurfaceInlineSegment, { kind: "text" }>,
|
|
4205
|
-
): string {
|
|
4206
|
-
const parts: string[] = [];
|
|
4207
|
-
const marks = new Set(segment.marks ?? []);
|
|
4208
|
-
if (marks.has("bold")) parts.push("<w:b/>");
|
|
4209
|
-
if (marks.has("italic")) parts.push("<w:i/>");
|
|
4210
|
-
if (marks.has("underline")) parts.push("<w:u w:val=\"single\"/>");
|
|
4211
|
-
if (marks.has("strikethrough")) parts.push("<w:strike/>");
|
|
4212
|
-
if (marks.has("superscript")) parts.push("<w:vertAlign w:val=\"superscript\"/>");
|
|
4213
|
-
if (marks.has("subscript")) parts.push("<w:vertAlign w:val=\"subscript\"/>");
|
|
4214
|
-
if (segment.markAttrs?.fontFamily) {
|
|
4215
|
-
parts.push(`<w:rFonts w:ascii="${escapeAttributeXml(segment.markAttrs.fontFamily)}" w:hAnsi="${escapeAttributeXml(segment.markAttrs.fontFamily)}"/>`);
|
|
4216
|
-
}
|
|
4217
|
-
if (segment.markAttrs?.fontSize !== undefined) {
|
|
4218
|
-
parts.push(`<w:sz w:val="${segment.markAttrs.fontSize}"/>`);
|
|
4219
|
-
}
|
|
4220
|
-
if (segment.markAttrs?.textColor) {
|
|
4221
|
-
parts.push(`<w:color w:val="${escapeAttributeXml(segment.markAttrs.textColor)}"/>`);
|
|
4222
|
-
}
|
|
4223
|
-
if (segment.markAttrs?.backgroundColor) {
|
|
4224
|
-
parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${escapeAttributeXml(segment.markAttrs.backgroundColor)}"/>`);
|
|
4225
|
-
}
|
|
4226
|
-
return `<w:rPr>${parts.join("")}</w:rPr>`;
|
|
4227
|
-
}
|
|
4228
|
-
|
|
4229
|
-
function buildParagraphPropertyBeforeXml(
|
|
4230
|
-
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
4231
|
-
): string {
|
|
4232
|
-
const parts: string[] = [];
|
|
4233
|
-
if (paragraph.styleId) {
|
|
4234
|
-
parts.push(`<w:pStyle w:val="${escapeAttributeXml(paragraph.styleId)}"/>`);
|
|
4235
|
-
}
|
|
4236
|
-
if (paragraph.numbering) {
|
|
4237
|
-
parts.push(
|
|
4238
|
-
`<w:numPr><w:ilvl w:val="${paragraph.numbering.level}"/><w:numId w:val="${escapeAttributeXml(
|
|
4239
|
-
paragraph.numbering.numberingInstanceId.replace(/^num:/u, ""),
|
|
4240
|
-
)}"/></w:numPr>`,
|
|
4241
|
-
);
|
|
4242
|
-
}
|
|
4243
|
-
if (paragraph.alignment) {
|
|
4244
|
-
parts.push(`<w:jc w:val="${escapeAttributeXml(paragraph.alignment)}"/>`);
|
|
4245
|
-
}
|
|
4246
|
-
if (paragraph.indentation) {
|
|
4247
|
-
const attrs: string[] = [];
|
|
4248
|
-
if (paragraph.indentation.left !== undefined) attrs.push(`w:left="${paragraph.indentation.left}"`);
|
|
4249
|
-
if (paragraph.indentation.right !== undefined) attrs.push(`w:right="${paragraph.indentation.right}"`);
|
|
4250
|
-
if (paragraph.indentation.firstLine !== undefined) attrs.push(`w:firstLine="${paragraph.indentation.firstLine}"`);
|
|
4251
|
-
if (paragraph.indentation.hanging !== undefined) attrs.push(`w:hanging="${paragraph.indentation.hanging}"`);
|
|
4252
|
-
if (attrs.length > 0) {
|
|
4253
|
-
parts.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
4257
|
-
}
|
|
4258
|
-
|
|
4259
|
-
function escapeAttributeXml(value: string): string {
|
|
4260
|
-
return value
|
|
4261
|
-
.replace(/&/g, "&")
|
|
4262
|
-
.replace(/</g, "<")
|
|
4263
|
-
.replace(/>/g, ">")
|
|
4264
|
-
.replace(/"/g, """);
|
|
4265
|
-
}
|
|
4266
|
-
|
|
4267
|
-
function isSelectionSuggesting(runtime: WordReviewEditorRuntime): boolean {
|
|
4268
|
-
return runtime.getInteractionGuardSnapshot().effectiveMode === "suggest";
|
|
4269
|
-
}
|
|
4270
|
-
|
|
4271
|
-
function getFormattingOperationCommandName(
|
|
4272
|
-
operation:
|
|
4273
|
-
| { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
|
|
4274
|
-
| { type: "set-font-family"; fontFamily: string | null }
|
|
4275
|
-
| { type: "set-font-size"; size: number | null }
|
|
4276
|
-
| { type: "set-text-color"; color: string | null }
|
|
4277
|
-
| { type: "set-highlight-color"; color: string | null }
|
|
4278
|
-
| { type: "set-alignment"; alignment: FormattingAlignment }
|
|
4279
|
-
| { type: "indent" }
|
|
4280
|
-
| { type: "outdent" },
|
|
4281
|
-
): string {
|
|
4282
|
-
switch (operation.type) {
|
|
4283
|
-
case "toggle":
|
|
4284
|
-
return `toggle${operation.mark.charAt(0).toUpperCase()}${operation.mark.slice(1)}`;
|
|
4285
|
-
case "set-font-family":
|
|
4286
|
-
return "setFontFamily";
|
|
4287
|
-
case "set-font-size":
|
|
4288
|
-
return "setFontSize";
|
|
4289
|
-
case "set-text-color":
|
|
4290
|
-
return "setTextColor";
|
|
4291
|
-
case "set-highlight-color":
|
|
4292
|
-
return "setHighlightColor";
|
|
4293
|
-
case "set-alignment":
|
|
4294
|
-
return "setAlignment";
|
|
4295
|
-
case "indent":
|
|
4296
|
-
return "indent";
|
|
4297
|
-
case "outdent":
|
|
4298
|
-
return "outdent";
|
|
4299
|
-
}
|
|
4300
|
-
}
|
|
4301
|
-
|
|
4302
|
-
function applyRuntimeDeleteSectionBreak(
|
|
4303
|
-
runtime: WordReviewEditorRuntime,
|
|
4304
|
-
sectionIndex: number,
|
|
4305
|
-
): void {
|
|
4306
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4307
|
-
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
4308
|
-
return;
|
|
4309
|
-
}
|
|
4310
|
-
if (emitWorkflowBlockedMutation(runtime, "deleteSectionBreak")) {
|
|
4311
|
-
return;
|
|
4312
|
-
}
|
|
4313
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4314
|
-
runtime.emitBlockedCommand("deleteSectionBreak", [{
|
|
4315
|
-
code: "unsupported_surface",
|
|
4316
|
-
message: "Section break deletion is not supported in suggesting mode.",
|
|
4317
|
-
}]);
|
|
4318
|
-
return;
|
|
4319
|
-
}
|
|
4320
|
-
|
|
4321
|
-
const sessionState = runtime.getSessionState();
|
|
4322
|
-
const timestamp = new Date().toISOString();
|
|
4323
|
-
const result = deleteSectionBreakAtSectionIndex(
|
|
4324
|
-
sessionState.canonicalDocument,
|
|
4325
|
-
sectionIndex,
|
|
4326
|
-
{ timestamp },
|
|
4327
|
-
);
|
|
4328
|
-
|
|
4329
|
-
dispatchRuntimeDocumentMutation(
|
|
4330
|
-
runtime,
|
|
4331
|
-
{
|
|
4332
|
-
changed: result.changed,
|
|
4333
|
-
document: result.document,
|
|
4334
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
4335
|
-
},
|
|
4336
|
-
timestamp,
|
|
4337
|
-
);
|
|
4338
|
-
}
|
|
4339
|
-
|
|
4340
|
-
function applyRuntimeUpdateSectionLayout(
|
|
4341
|
-
runtime: WordReviewEditorRuntime,
|
|
4342
|
-
sectionIndex: number,
|
|
4343
|
-
patch: SectionLayoutPatch,
|
|
4344
|
-
): void {
|
|
4345
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4346
|
-
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
4347
|
-
return;
|
|
4348
|
-
}
|
|
4349
|
-
if (emitWorkflowBlockedMutation(runtime, "updateSectionLayout")) {
|
|
4350
|
-
return;
|
|
4351
|
-
}
|
|
4352
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4353
|
-
runtime.emitBlockedCommand("updateSectionLayout", [{
|
|
4354
|
-
code: "unsupported_surface",
|
|
4355
|
-
message: "Section layout updates are not supported in suggesting mode.",
|
|
4356
|
-
}]);
|
|
4357
|
-
return;
|
|
4358
|
-
}
|
|
4359
|
-
|
|
4360
|
-
const sessionState = runtime.getSessionState();
|
|
4361
|
-
const timestamp = new Date().toISOString();
|
|
4362
|
-
const result = updateSectionLayoutAtSectionIndex(
|
|
4363
|
-
sessionState.canonicalDocument,
|
|
4364
|
-
sectionIndex,
|
|
4365
|
-
{
|
|
4366
|
-
...(patch.pageSize ? { pageSize: patch.pageSize } : {}),
|
|
4367
|
-
...(patch.pageMargins ? { pageMargins: patch.pageMargins } : {}),
|
|
4368
|
-
...(patch.columns ? { columns: patch.columns } : {}),
|
|
4369
|
-
...(patch.titlePage !== undefined ? { titlePage: patch.titlePage } : {}),
|
|
4370
|
-
...(patch.sectionType ? { sectionType: patch.sectionType } : {}),
|
|
4371
|
-
},
|
|
4372
|
-
{ timestamp },
|
|
4373
|
-
);
|
|
4374
|
-
|
|
4375
|
-
dispatchRuntimeDocumentMutation(
|
|
4376
|
-
runtime,
|
|
4377
|
-
{
|
|
4378
|
-
changed: result.changed,
|
|
4379
|
-
document: result.document,
|
|
4380
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
4381
|
-
},
|
|
4382
|
-
timestamp,
|
|
4383
|
-
);
|
|
4384
|
-
}
|
|
4385
|
-
|
|
4386
|
-
function applyRuntimeSetSectionPageNumbering(
|
|
4387
|
-
runtime: WordReviewEditorRuntime,
|
|
4388
|
-
sectionIndex: number,
|
|
4389
|
-
patch: SectionPageNumberingPatch | null,
|
|
4390
|
-
): void {
|
|
4391
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4392
|
-
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
4393
|
-
return;
|
|
4394
|
-
}
|
|
4395
|
-
if (emitWorkflowBlockedMutation(runtime, "setSectionPageNumbering")) {
|
|
4396
|
-
return;
|
|
4397
|
-
}
|
|
4398
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4399
|
-
runtime.emitBlockedCommand("setSectionPageNumbering", [{
|
|
4400
|
-
code: "unsupported_surface",
|
|
4401
|
-
message: "Section page numbering updates are not supported in suggesting mode.",
|
|
4402
|
-
}]);
|
|
4403
|
-
return;
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
|
-
const sessionState = runtime.getSessionState();
|
|
4407
|
-
const timestamp = new Date().toISOString();
|
|
4408
|
-
const normalizedPatch =
|
|
4409
|
-
patch === null
|
|
4410
|
-
? null
|
|
4411
|
-
: {
|
|
4412
|
-
...(patch.format !== undefined
|
|
4413
|
-
? { format: patch.format ?? undefined }
|
|
4414
|
-
: {}),
|
|
4415
|
-
...(patch.start !== undefined
|
|
4416
|
-
? { start: patch.start ?? undefined }
|
|
4417
|
-
: {}),
|
|
4418
|
-
...(patch.chapterStyle !== undefined
|
|
4419
|
-
? { chapStyle: patch.chapterStyle ?? undefined }
|
|
4420
|
-
: {}),
|
|
4421
|
-
...(patch.chapterSeparator !== undefined
|
|
4422
|
-
? { chapSep: patch.chapterSeparator ?? undefined }
|
|
4423
|
-
: {}),
|
|
4424
|
-
};
|
|
4425
|
-
const result = setSectionPageNumberingAtSectionIndex(
|
|
4426
|
-
sessionState.canonicalDocument,
|
|
4427
|
-
sectionIndex,
|
|
4428
|
-
normalizedPatch,
|
|
4429
|
-
{ timestamp },
|
|
4430
|
-
);
|
|
4431
|
-
|
|
4432
|
-
dispatchRuntimeDocumentMutation(
|
|
4433
|
-
runtime,
|
|
4434
|
-
{
|
|
4435
|
-
changed: result.changed,
|
|
4436
|
-
document: result.document,
|
|
4437
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
4438
|
-
},
|
|
4439
|
-
timestamp,
|
|
4440
|
-
);
|
|
4441
|
-
}
|
|
4442
|
-
|
|
4443
|
-
function applyRuntimeSetHeaderFooterLink(
|
|
4444
|
-
runtime: WordReviewEditorRuntime,
|
|
4445
|
-
sectionIndex: number,
|
|
4446
|
-
patch: HeaderFooterLinkPatch,
|
|
4447
|
-
): void {
|
|
4448
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4449
|
-
if (!canApplyRuntimeMutation(snapshot) || snapshot.activeStory.kind !== "main") {
|
|
4450
|
-
return;
|
|
4451
|
-
}
|
|
4452
|
-
if (emitWorkflowBlockedMutation(runtime, "setHeaderFooterLink")) {
|
|
4453
|
-
return;
|
|
4454
|
-
}
|
|
4455
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4456
|
-
runtime.emitBlockedCommand("setHeaderFooterLink", [{
|
|
4457
|
-
code: "unsupported_surface",
|
|
4458
|
-
message: "Header and footer linkage updates are not supported in suggesting mode.",
|
|
4459
|
-
}]);
|
|
4460
|
-
return;
|
|
4461
|
-
}
|
|
4462
|
-
|
|
4463
|
-
const sessionState = runtime.getSessionState();
|
|
4464
|
-
const timestamp = new Date().toISOString();
|
|
4465
|
-
const result = setHeaderFooterLinkAtSectionIndex(
|
|
4466
|
-
sessionState.canonicalDocument,
|
|
4467
|
-
sectionIndex,
|
|
4468
|
-
patch,
|
|
4469
|
-
{ timestamp },
|
|
4470
|
-
);
|
|
4471
|
-
|
|
4472
|
-
dispatchRuntimeDocumentMutation(
|
|
4473
|
-
runtime,
|
|
4474
|
-
{
|
|
4475
|
-
changed: result.changed,
|
|
4476
|
-
document: result.document,
|
|
4477
|
-
selection: toRuntimeSelectionSnapshot(result.selection),
|
|
4478
|
-
},
|
|
4479
|
-
timestamp,
|
|
4480
|
-
);
|
|
4481
|
-
}
|
|
4482
|
-
|
|
4483
|
-
function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
|
|
4484
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4485
|
-
runtime.emitBlockedCommand("insertPageBreak", [{
|
|
4486
|
-
code: "unsupported_surface",
|
|
4487
|
-
message: "Page break insertion is not supported in suggesting mode.",
|
|
4488
|
-
}]);
|
|
4489
|
-
return;
|
|
4490
|
-
}
|
|
4491
|
-
const context = getStoryMutationContext(runtime, "insertPageBreak");
|
|
4492
|
-
if (!context) {
|
|
4493
|
-
return;
|
|
4494
|
-
}
|
|
4495
|
-
|
|
4496
|
-
const result = insertPageBreakInDocument(
|
|
4497
|
-
context.localDocument,
|
|
4498
|
-
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
4499
|
-
{ timestamp: context.timestamp },
|
|
4500
|
-
);
|
|
4501
|
-
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
4502
|
-
}
|
|
4503
|
-
|
|
4504
|
-
function applyRuntimeInsertTable(
|
|
4505
|
-
runtime: WordReviewEditorRuntime,
|
|
4506
|
-
options: InsertTableOptions,
|
|
4507
|
-
): void {
|
|
4508
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4509
|
-
runtime.emitBlockedCommand("insertTable", [{
|
|
4510
|
-
code: "unsupported_surface",
|
|
4511
|
-
message: "Table insertion is not supported in suggesting mode.",
|
|
4512
|
-
}]);
|
|
4513
|
-
return;
|
|
4514
|
-
}
|
|
4515
|
-
const context = getStoryMutationContext(runtime, "insertTable");
|
|
4516
|
-
if (!context) {
|
|
4517
|
-
return;
|
|
4518
|
-
}
|
|
4519
|
-
|
|
4520
|
-
const result = insertTableInDocument(
|
|
4521
|
-
context.localDocument,
|
|
4522
|
-
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
4523
|
-
options,
|
|
4524
|
-
{ timestamp: context.timestamp },
|
|
4525
|
-
);
|
|
4526
|
-
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
4527
|
-
}
|
|
4528
|
-
|
|
4529
|
-
function applyRuntimeInsertImage(
|
|
4530
|
-
runtime: WordReviewEditorRuntime,
|
|
4531
|
-
options: InsertImageOptions,
|
|
4532
|
-
): void {
|
|
4533
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4534
|
-
runtime.emitBlockedCommand("insertImage", [{
|
|
4535
|
-
code: "unsupported_surface",
|
|
4536
|
-
message: "Image insertion is not supported in suggesting mode.",
|
|
4537
|
-
}]);
|
|
4538
|
-
return;
|
|
4539
|
-
}
|
|
4540
|
-
const context = getStoryMutationContext(runtime, "insertImage");
|
|
4541
|
-
if (!context) {
|
|
4542
|
-
return;
|
|
4543
|
-
}
|
|
4544
|
-
|
|
4545
|
-
try {
|
|
4546
|
-
const result = insertImageInDocument(
|
|
4547
|
-
context.localDocument,
|
|
4548
|
-
toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
4549
|
-
options.data,
|
|
4550
|
-
options.mimeType,
|
|
4551
|
-
options.width,
|
|
4552
|
-
options.height,
|
|
4553
|
-
{
|
|
4554
|
-
timestamp: context.timestamp,
|
|
4555
|
-
altText: options.altText,
|
|
4556
|
-
},
|
|
4557
|
-
);
|
|
4558
|
-
dispatchStoryMutationResult(runtime, context, {
|
|
4559
|
-
changed: true,
|
|
4560
|
-
document: result.document,
|
|
4561
|
-
selection: result.selection,
|
|
4562
|
-
mapping: result.mapping,
|
|
4563
|
-
}, context.timestamp);
|
|
4564
|
-
} catch {
|
|
4565
|
-
return;
|
|
4566
|
-
}
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
|
-
function applyRuntimeImageResize(
|
|
4570
|
-
runtime: WordReviewEditorRuntime,
|
|
4571
|
-
mediaId: string,
|
|
4572
|
-
dimensions: { widthEmu: number; heightEmu: number },
|
|
4573
|
-
): void {
|
|
4574
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4575
|
-
if (!canApplyRuntimeMutation(snapshot)) {
|
|
4576
|
-
return;
|
|
4577
|
-
}
|
|
4578
|
-
if (emitWorkflowBlockedMutation(runtime, "setImageLayout")) {
|
|
4579
|
-
return;
|
|
4580
|
-
}
|
|
4581
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4582
|
-
runtime.emitBlockedCommand("setImageLayout", [{
|
|
4583
|
-
code: "unsupported_surface",
|
|
4584
|
-
message: "Image resize is not supported in suggesting mode.",
|
|
4585
|
-
}]);
|
|
4586
|
-
return;
|
|
4587
|
-
}
|
|
4588
|
-
|
|
4589
|
-
try {
|
|
4590
|
-
const sessionState = runtime.getSessionState();
|
|
4591
|
-
const result = resizeImageInCatalog(
|
|
4592
|
-
sessionState.canonicalDocument,
|
|
4593
|
-
mediaId,
|
|
4594
|
-
dimensions,
|
|
4595
|
-
);
|
|
4596
|
-
runtime.dispatch({
|
|
4597
|
-
type: "document.replace",
|
|
4598
|
-
document: result.document,
|
|
4599
|
-
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
4600
|
-
origin: { source: "api", timestamp: new Date().toISOString() },
|
|
4601
|
-
});
|
|
4602
|
-
} catch {
|
|
4603
|
-
return;
|
|
4604
|
-
}
|
|
4605
|
-
}
|
|
4606
|
-
|
|
4607
|
-
function applyRuntimeImageReposition(
|
|
4608
|
-
runtime: WordReviewEditorRuntime,
|
|
4609
|
-
mediaId: string,
|
|
4610
|
-
offsets: { horizontalOffsetEmu?: number; verticalOffsetEmu?: number },
|
|
4611
|
-
): void {
|
|
4612
|
-
if (emitWorkflowBlockedMutation(runtime, "setImageFrame")) {
|
|
4613
|
-
return;
|
|
4614
|
-
}
|
|
4615
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4616
|
-
runtime.emitBlockedCommand("setImageFrame", [{
|
|
4617
|
-
code: "unsupported_surface",
|
|
4618
|
-
message: "Image reposition is not supported in suggesting mode.",
|
|
4619
|
-
}]);
|
|
4620
|
-
return;
|
|
4621
|
-
}
|
|
4622
|
-
const context = getStoryMutationContext(runtime, "setImageFrame");
|
|
4623
|
-
if (!context) {
|
|
4624
|
-
return;
|
|
4625
|
-
}
|
|
4626
|
-
|
|
4627
|
-
try {
|
|
4628
|
-
const result = repositionFloatingImageInDocument(
|
|
4629
|
-
context.localDocument,
|
|
4630
|
-
mediaId,
|
|
4631
|
-
offsets,
|
|
4632
|
-
context.timestamp,
|
|
4633
|
-
);
|
|
4634
|
-
dispatchStoryMutationResult(
|
|
4635
|
-
runtime,
|
|
4636
|
-
context,
|
|
4637
|
-
{
|
|
4638
|
-
changed: true,
|
|
4639
|
-
document: result.document,
|
|
4640
|
-
selection: toRuntimeSelectionSnapshot(context.localSnapshot.selection),
|
|
4641
|
-
},
|
|
4642
|
-
context.timestamp,
|
|
4643
|
-
);
|
|
4644
|
-
} catch {
|
|
4645
|
-
return;
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
4648
|
-
|
|
4649
|
-
// deriveViewState removed — the runtime's getViewState() is now the single
|
|
4650
|
-
// source of truth for EditorViewStateSnapshot, backed by view-state.ts.
|
|
4651
|
-
|
|
4652
|
-
function applyRuntimeTableStructureOperation(
|
|
4653
|
-
runtime: WordReviewEditorRuntime,
|
|
4654
|
-
mountedSurface: TwProseMirrorSurfaceRef | null | undefined,
|
|
4655
|
-
operation: TableStructureOperation,
|
|
4656
|
-
): { changed: boolean; coercedReason: string | null } {
|
|
4657
|
-
if (isSelectionSuggesting(runtime)) {
|
|
4658
|
-
const coercedReason = `Table operation "${operation.type}" is not supported in suggesting mode.`;
|
|
4659
|
-
runtime.emitBlockedCommand(`table.${operation.type}`, [{
|
|
4660
|
-
code: "unsupported_surface",
|
|
4661
|
-
message: coercedReason,
|
|
4662
|
-
}]);
|
|
4663
|
-
return { changed: false, coercedReason };
|
|
4664
|
-
}
|
|
4665
|
-
const context = getStoryMutationContext(runtime, `table.${operation.type}`);
|
|
4666
|
-
if (!context) {
|
|
4667
|
-
return { changed: false, coercedReason: "No active mutation context." };
|
|
4668
|
-
}
|
|
4669
|
-
|
|
4670
|
-
const result = applyTableStructureOperation(
|
|
4671
|
-
context.localDocument,
|
|
4672
|
-
context.localSnapshot,
|
|
4673
|
-
mountedSurface?.getTableSelection() ?? null,
|
|
4674
|
-
operation,
|
|
4675
|
-
);
|
|
4676
|
-
dispatchStoryMutationResult(runtime, context, result, context.timestamp);
|
|
4677
|
-
return {
|
|
4678
|
-
changed: result.changed,
|
|
4679
|
-
coercedReason: result.changed ? null : "Op was a no-op against the active selection.",
|
|
4680
|
-
};
|
|
4681
|
-
}
|
|
4682
|
-
|
|
4683
|
-
/**
|
|
4684
|
-
* Translate a public-API `TableOp` (kebab-case `kind` discriminator) to
|
|
4685
|
-
* the internal `TableStructureOperation` (`type` discriminator). The
|
|
4686
|
-
* shape values are identical aside from the discriminator name.
|
|
4687
|
-
*/
|
|
4688
|
-
export function __publicTableOpToInternal(op: TableOp): TableStructureOperation {
|
|
4689
|
-
return publicTableOpToInternal(op);
|
|
4690
|
-
}
|
|
4691
|
-
|
|
4692
|
-
function publicTableOpToInternal(op: TableOp): TableStructureOperation {
|
|
4693
|
-
const { kind, ...rest } = op as { kind: string } & Record<string, unknown>;
|
|
4694
|
-
if (kind === "insert") {
|
|
4695
|
-
throw new Error(
|
|
4696
|
-
"TableOp kind \"insert\" is not routed through ref.tables.apply; use ref.insertTable(...).",
|
|
4697
|
-
);
|
|
4698
|
-
}
|
|
4699
|
-
return { type: kind, ...rest } as TableStructureOperation;
|
|
4700
|
-
}
|
|
4701
|
-
|
|
4702
|
-
/**
|
|
4703
|
-
* Build the `ref.tables` facet: a typed dispatch boundary + capability
|
|
4704
|
-
* read. Delegates every op through the same `applyRuntimeTableStructureOperation`
|
|
4705
|
-
* helper the flat ref verbs use, so there is exactly one server-side path
|
|
4706
|
-
* for every table mutation.
|
|
4707
|
-
*/
|
|
4708
|
-
function buildTablesFacet(
|
|
4709
|
-
runtime: WordReviewEditorRuntime,
|
|
4710
|
-
mountedSurface: TwProseMirrorSurfaceRef | null,
|
|
4711
|
-
) {
|
|
4712
|
-
const getCapabilities = () => {
|
|
4713
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
4714
|
-
const document = runtime.getCanonicalDocument();
|
|
4715
|
-
return (
|
|
4716
|
-
clonePublicValue(
|
|
4717
|
-
getTableStructureContext(
|
|
4718
|
-
document,
|
|
4719
|
-
snapshot,
|
|
4720
|
-
mountedSurface?.getTableSelection() ?? null,
|
|
4721
|
-
),
|
|
4722
|
-
) ?? null
|
|
4723
|
-
);
|
|
4724
|
-
};
|
|
3591
|
+
mountedSurface: TwProseMirrorSurfaceRef | null,
|
|
3592
|
+
) {
|
|
3593
|
+
const getCapabilities = () => {
|
|
3594
|
+
const snapshot = runtime.getRenderSnapshot();
|
|
3595
|
+
const document = runtime.getCanonicalDocument();
|
|
3596
|
+
return (
|
|
3597
|
+
clonePublicValue(
|
|
3598
|
+
getTableStructureContext(
|
|
3599
|
+
document,
|
|
3600
|
+
snapshot,
|
|
3601
|
+
mountedSurface?.getTableSelection() ?? null,
|
|
3602
|
+
),
|
|
3603
|
+
) ?? null
|
|
3604
|
+
);
|
|
3605
|
+
};
|
|
4725
3606
|
|
|
4726
3607
|
const buildSummary = (
|
|
4727
3608
|
table: Extract<ReturnType<typeof runtime.getCanonicalDocument>["content"]["children"][number], { type: "table" }>,
|
|
@@ -4930,324 +3811,8 @@ function buildTablesFacet(
|
|
|
4930
3811
|
|
|
4931
3812
|
export { buildTablesFacet as __buildTablesFacet };
|
|
4932
3813
|
|
|
4933
|
-
const DISPATCH_CONTEXT: DispatchContext = {
|
|
4934
|
-
getStoryMutationContext,
|
|
4935
|
-
dispatchStoryMutationResult,
|
|
4936
|
-
resolveActiveParagraphContext,
|
|
4937
|
-
toRuntimeSelectionSnapshot,
|
|
4938
|
-
};
|
|
4939
|
-
|
|
4940
|
-
function resolveActiveParagraphContext(
|
|
4941
|
-
snapshot: Pick<RuntimeRenderSnapshot, "surface" | "selection">,
|
|
4942
|
-
): {
|
|
4943
|
-
paragraphIndex: number;
|
|
4944
|
-
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>;
|
|
4945
|
-
atParagraphStart: boolean;
|
|
4946
|
-
isEmpty: boolean;
|
|
4947
|
-
} | null {
|
|
4948
|
-
if (!snapshot.surface) {
|
|
4949
|
-
return null;
|
|
4950
|
-
}
|
|
4951
|
-
|
|
4952
|
-
const paragraphIndex = resolveActiveParagraphIndex(
|
|
4953
|
-
snapshot.surface.blocks,
|
|
4954
|
-
snapshot.selection,
|
|
4955
|
-
);
|
|
4956
|
-
if (paragraphIndex === null) {
|
|
4957
|
-
return null;
|
|
4958
|
-
}
|
|
4959
|
-
|
|
4960
|
-
const selectionPosition =
|
|
4961
|
-
snapshot.selection.activeRange.kind === "node"
|
|
4962
|
-
? snapshot.selection.activeRange.at
|
|
4963
|
-
: snapshot.selection.head;
|
|
4964
|
-
const paragraph = findSurfaceParagraphAtPosition(snapshot.surface.blocks, selectionPosition);
|
|
4965
|
-
if (!paragraph) {
|
|
4966
|
-
return null;
|
|
4967
|
-
}
|
|
4968
|
-
|
|
4969
|
-
return {
|
|
4970
|
-
paragraphIndex,
|
|
4971
|
-
paragraph,
|
|
4972
|
-
atParagraphStart:
|
|
4973
|
-
snapshot.selection.isCollapsed &&
|
|
4974
|
-
snapshot.selection.activeRange.kind !== "node" &&
|
|
4975
|
-
snapshot.selection.anchor === snapshot.selection.head &&
|
|
4976
|
-
snapshot.selection.head === paragraph.from,
|
|
4977
|
-
isEmpty: isSurfaceParagraphEmpty(paragraph),
|
|
4978
|
-
};
|
|
4979
|
-
}
|
|
4980
|
-
|
|
4981
|
-
function findSurfaceParagraphAtPosition(
|
|
4982
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
4983
|
-
position: number,
|
|
4984
|
-
): Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null {
|
|
4985
|
-
for (const block of blocks) {
|
|
4986
|
-
if (position < block.from || position > block.to) {
|
|
4987
|
-
continue;
|
|
4988
|
-
}
|
|
4989
|
-
if (block.kind === "paragraph") {
|
|
4990
|
-
return block;
|
|
4991
|
-
}
|
|
4992
|
-
if (block.kind === "table") {
|
|
4993
|
-
for (const row of block.rows) {
|
|
4994
|
-
for (const cell of row.cells) {
|
|
4995
|
-
const paragraph = findSurfaceParagraphAtPosition(cell.content, position);
|
|
4996
|
-
if (paragraph) {
|
|
4997
|
-
return paragraph;
|
|
4998
|
-
}
|
|
4999
|
-
}
|
|
5000
|
-
}
|
|
5001
|
-
continue;
|
|
5002
|
-
}
|
|
5003
|
-
if (block.kind === "sdt_block") {
|
|
5004
|
-
const paragraph = findSurfaceParagraphAtPosition(block.children, position);
|
|
5005
|
-
if (paragraph) {
|
|
5006
|
-
return paragraph;
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
}
|
|
5010
|
-
return null;
|
|
5011
|
-
}
|
|
5012
|
-
|
|
5013
|
-
function isSurfaceParagraphEmpty(
|
|
5014
|
-
paragraph: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
5015
|
-
): boolean {
|
|
5016
|
-
if (paragraph.segments.length === 0) {
|
|
5017
|
-
return true;
|
|
5018
|
-
}
|
|
5019
|
-
return paragraph.segments.every((segment) => segment.kind === "text" && segment.text.length === 0);
|
|
5020
|
-
}
|
|
5021
|
-
|
|
5022
|
-
function applyRuntimeSelection(
|
|
5023
|
-
runtime: WordReviewEditorRuntime,
|
|
5024
|
-
selection: PublicSelectionSnapshot,
|
|
5025
|
-
): void {
|
|
5026
|
-
const requestedStory = selection.storyTarget ?? { kind: "main" };
|
|
5027
|
-
if (requestedStory.kind === "main") {
|
|
5028
|
-
runtime.closeStory();
|
|
5029
|
-
} else if (!storyTargetsEqual(runtime.getActiveStory(), requestedStory)) {
|
|
5030
|
-
if (!runtime.openStory(requestedStory)) {
|
|
5031
|
-
return;
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5034
|
-
|
|
5035
|
-
runtime.dispatch({
|
|
5036
|
-
type: "selection.set",
|
|
5037
|
-
selection: toRuntimeSelectionSnapshot(stripStoryTarget(selection)),
|
|
5038
|
-
});
|
|
5039
|
-
}
|
|
5040
|
-
|
|
5041
|
-
function canApplyRuntimeMutation(snapshot: RuntimeRenderSnapshot): boolean {
|
|
5042
|
-
return snapshot.isReady && !snapshot.readOnly && !snapshot.fatalError;
|
|
5043
|
-
}
|
|
5044
|
-
|
|
5045
|
-
function emitWorkflowBlockedMutation(
|
|
5046
|
-
runtime: WordReviewEditorRuntime,
|
|
5047
|
-
command: string,
|
|
5048
|
-
): boolean {
|
|
5049
|
-
const interactionGuardSnapshot = runtime.getInteractionGuardSnapshot();
|
|
5050
|
-
if (interactionGuardSnapshot.blockedReasons.length === 0) {
|
|
5051
|
-
return false;
|
|
5052
|
-
}
|
|
5053
|
-
runtime.emitBlockedCommand(command, interactionGuardSnapshot.blockedReasons);
|
|
5054
|
-
return true;
|
|
5055
|
-
}
|
|
5056
|
-
|
|
5057
|
-
function getStoryMutationContext(
|
|
5058
|
-
runtime: WordReviewEditorRuntime,
|
|
5059
|
-
command?: string,
|
|
5060
|
-
): {
|
|
5061
|
-
timestamp: string;
|
|
5062
|
-
activeStory: EditorStoryTarget;
|
|
5063
|
-
persistedDocument: EditorSessionState["canonicalDocument"];
|
|
5064
|
-
localDocument: EditorSessionState["canonicalDocument"];
|
|
5065
|
-
localSnapshot: RuntimeRenderSnapshot;
|
|
5066
|
-
} | null {
|
|
5067
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
5068
|
-
if (!canApplyRuntimeMutation(snapshot)) {
|
|
5069
|
-
return null;
|
|
5070
|
-
}
|
|
5071
|
-
if (command && emitWorkflowBlockedMutation(runtime, command)) {
|
|
5072
|
-
return null;
|
|
5073
|
-
}
|
|
5074
|
-
|
|
5075
|
-
const persistedDocument = runtime.getSessionState().canonicalDocument;
|
|
5076
|
-
const activeStory = snapshot.activeStory;
|
|
5077
|
-
if (activeStory.kind === "main") {
|
|
5078
|
-
return {
|
|
5079
|
-
timestamp: new Date().toISOString(),
|
|
5080
|
-
activeStory,
|
|
5081
|
-
persistedDocument,
|
|
5082
|
-
localDocument: persistedDocument,
|
|
5083
|
-
localSnapshot: snapshot,
|
|
5084
|
-
};
|
|
5085
|
-
}
|
|
5086
|
-
|
|
5087
|
-
return {
|
|
5088
|
-
timestamp: new Date().toISOString(),
|
|
5089
|
-
activeStory,
|
|
5090
|
-
persistedDocument,
|
|
5091
|
-
localDocument: {
|
|
5092
|
-
...persistedDocument,
|
|
5093
|
-
content: {
|
|
5094
|
-
type: "doc",
|
|
5095
|
-
children: [...getStoryBlocks(persistedDocument, activeStory)],
|
|
5096
|
-
},
|
|
5097
|
-
},
|
|
5098
|
-
localSnapshot: {
|
|
5099
|
-
...snapshot,
|
|
5100
|
-
activeStory: { kind: "main" },
|
|
5101
|
-
selection: stripStoryTarget(snapshot.selection),
|
|
5102
|
-
},
|
|
5103
|
-
};
|
|
5104
|
-
}
|
|
5105
|
-
|
|
5106
|
-
function dispatchStoryMutationResult(
|
|
5107
|
-
runtime: WordReviewEditorRuntime,
|
|
5108
|
-
context: {
|
|
5109
|
-
activeStory: EditorStoryTarget;
|
|
5110
|
-
persistedDocument: EditorSessionState["canonicalDocument"];
|
|
5111
|
-
},
|
|
5112
|
-
result: {
|
|
5113
|
-
changed: boolean;
|
|
5114
|
-
document: EditorSessionState["canonicalDocument"];
|
|
5115
|
-
selection: InternalSelectionSnapshot;
|
|
5116
|
-
mapping?: TransactionMapping;
|
|
5117
|
-
},
|
|
5118
|
-
timestamp: string,
|
|
5119
|
-
): void {
|
|
5120
|
-
if (context.activeStory.kind === "main") {
|
|
5121
|
-
dispatchRuntimeDocumentMutation(runtime, result, timestamp);
|
|
5122
|
-
return;
|
|
5123
|
-
}
|
|
5124
|
-
|
|
5125
|
-
if (!result.changed) {
|
|
5126
|
-
return;
|
|
5127
|
-
}
|
|
5128
|
-
|
|
5129
|
-
const nextDocument = replaceStoryBlocks(
|
|
5130
|
-
context.persistedDocument,
|
|
5131
|
-
context.activeStory,
|
|
5132
|
-
result.document.content.children,
|
|
5133
|
-
);
|
|
5134
|
-
dispatchRuntimeDocumentMutation(
|
|
5135
|
-
runtime,
|
|
5136
|
-
{
|
|
5137
|
-
changed: true,
|
|
5138
|
-
document: nextDocument,
|
|
5139
|
-
selection: result.selection,
|
|
5140
|
-
},
|
|
5141
|
-
timestamp,
|
|
5142
|
-
);
|
|
5143
|
-
}
|
|
5144
|
-
|
|
5145
|
-
function dispatchRuntimeDocumentMutation(
|
|
5146
|
-
runtime: WordReviewEditorRuntime,
|
|
5147
|
-
result: {
|
|
5148
|
-
changed: boolean;
|
|
5149
|
-
document: EditorSessionState["canonicalDocument"];
|
|
5150
|
-
selection: InternalSelectionSnapshot;
|
|
5151
|
-
mapping?: TransactionMapping;
|
|
5152
|
-
},
|
|
5153
|
-
timestamp: string,
|
|
5154
|
-
): void {
|
|
5155
|
-
if (!result.changed) {
|
|
5156
|
-
return;
|
|
5157
|
-
}
|
|
5158
|
-
|
|
5159
|
-
runtime.dispatch({
|
|
5160
|
-
type: "document.replace",
|
|
5161
|
-
document: {
|
|
5162
|
-
...result.document,
|
|
5163
|
-
updatedAt: timestamp,
|
|
5164
|
-
},
|
|
5165
|
-
mapping: result.mapping,
|
|
5166
|
-
selection: result.selection,
|
|
5167
|
-
origin: {
|
|
5168
|
-
source: "api",
|
|
5169
|
-
timestamp,
|
|
5170
|
-
},
|
|
5171
|
-
});
|
|
5172
|
-
}
|
|
5173
|
-
|
|
5174
|
-
function stripStoryTarget(
|
|
5175
|
-
selection: PublicSelectionSnapshot,
|
|
5176
|
-
): PublicSelectionSnapshot {
|
|
5177
|
-
const { storyTarget: _storyTarget, ...rest } = selection;
|
|
5178
|
-
return rest;
|
|
5179
|
-
}
|
|
5180
|
-
|
|
5181
|
-
function applyRuntimeDeleteComment(
|
|
5182
|
-
runtime: WordReviewEditorRuntime,
|
|
5183
|
-
commentId: string,
|
|
5184
|
-
): void {
|
|
5185
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
5186
|
-
// Pre-ready / fatal states stay silent: the host called too early, and
|
|
5187
|
-
// there is no meaningful document yet to signal against. Emitting a
|
|
5188
|
-
// warning here would only add noise to load-time error handling.
|
|
5189
|
-
if (!snapshot.isReady || snapshot.fatalError) {
|
|
5190
|
-
return;
|
|
5191
|
-
}
|
|
5192
|
-
if (snapshot.readOnly) {
|
|
5193
|
-
runtime.emitTransientWarning({
|
|
5194
|
-
warningId: `delete-comment-readonly-${commentId}-${Date.now()}`,
|
|
5195
|
-
code: "review_target_not_found",
|
|
5196
|
-
severity: "info",
|
|
5197
|
-
message: `deleteComment("${commentId}") skipped: editor is read-only.`,
|
|
5198
|
-
source: "review",
|
|
5199
|
-
details: { op: "deleteComment", targetId: commentId, reason: "read_only" },
|
|
5200
|
-
});
|
|
5201
|
-
return;
|
|
5202
|
-
}
|
|
5203
|
-
|
|
5204
|
-
const sessionState = runtime.getSessionState();
|
|
5205
|
-
if (!sessionState.canonicalDocument.review.comments[commentId]) {
|
|
5206
|
-
runtime.emitTransientWarning({
|
|
5207
|
-
warningId: `delete-comment-unknown-${commentId}-${Date.now()}`,
|
|
5208
|
-
code: "review_target_not_found",
|
|
5209
|
-
severity: "info",
|
|
5210
|
-
message: `deleteComment("${commentId}") skipped: unknown commentId.`,
|
|
5211
|
-
source: "review",
|
|
5212
|
-
details: { op: "deleteComment", targetId: commentId, reason: "comment_unknown" },
|
|
5213
|
-
});
|
|
5214
|
-
return;
|
|
5215
|
-
}
|
|
5216
3814
|
|
|
5217
|
-
const nextComments = {
|
|
5218
|
-
...sessionState.canonicalDocument.review.comments,
|
|
5219
|
-
};
|
|
5220
|
-
delete nextComments[commentId];
|
|
5221
|
-
|
|
5222
|
-
runtime.dispatch({
|
|
5223
|
-
type: "document.replace",
|
|
5224
|
-
document: {
|
|
5225
|
-
...sessionState.canonicalDocument,
|
|
5226
|
-
review: {
|
|
5227
|
-
...sessionState.canonicalDocument.review,
|
|
5228
|
-
comments: nextComments,
|
|
5229
|
-
},
|
|
5230
|
-
},
|
|
5231
|
-
selection: toRuntimeSelectionSnapshot(snapshot.selection),
|
|
5232
|
-
origin: {
|
|
5233
|
-
source: "api",
|
|
5234
|
-
timestamp: new Date().toISOString(),
|
|
5235
|
-
},
|
|
5236
|
-
});
|
|
5237
|
-
}
|
|
5238
3815
|
|
|
5239
|
-
function normalizeRequestedSelection(
|
|
5240
|
-
snapshot: RuntimeRenderSnapshot,
|
|
5241
|
-
selection: PublicSelectionSnapshot | null,
|
|
5242
|
-
): PublicSelectionSnapshot {
|
|
5243
|
-
return (
|
|
5244
|
-
selection ??
|
|
5245
|
-
createCollapsedPublicSelection(
|
|
5246
|
-
snapshot.selection.head,
|
|
5247
|
-
snapshot.activeStory.kind === "main" ? undefined : snapshot.activeStory,
|
|
5248
|
-
)
|
|
5249
|
-
);
|
|
5250
|
-
}
|
|
5251
3816
|
|
|
5252
3817
|
export function __resolveLiveMarkupDisplay(
|
|
5253
3818
|
requested: MarkupDisplay | undefined,
|
|
@@ -5256,45 +3821,11 @@ export function __resolveLiveMarkupDisplay(
|
|
|
5256
3821
|
return requested ?? (isPageWorkspace ? "all" : "clean");
|
|
5257
3822
|
}
|
|
5258
3823
|
|
|
5259
|
-
function createCollapsedPublicSelection(
|
|
5260
|
-
position: number,
|
|
5261
|
-
storyTarget?: EditorStoryTarget,
|
|
5262
|
-
): PublicSelectionSnapshot {
|
|
5263
|
-
return {
|
|
5264
|
-
anchor: position,
|
|
5265
|
-
head: position,
|
|
5266
|
-
isCollapsed: true,
|
|
5267
|
-
activeRange: {
|
|
5268
|
-
kind: "range",
|
|
5269
|
-
from: position,
|
|
5270
|
-
to: position,
|
|
5271
|
-
assoc: {
|
|
5272
|
-
start: -1,
|
|
5273
|
-
end: 1,
|
|
5274
|
-
},
|
|
5275
|
-
},
|
|
5276
|
-
...(storyTarget ? { storyTarget } : {}),
|
|
5277
|
-
};
|
|
5278
|
-
}
|
|
5279
3824
|
|
|
5280
3825
|
function clonePublicValue<T>(value: T): T {
|
|
5281
3826
|
return structuredClone(value);
|
|
5282
3827
|
}
|
|
5283
3828
|
|
|
5284
|
-
function findTextMatchesForRuntime(
|
|
5285
|
-
runtime: WordReviewEditorRuntime,
|
|
5286
|
-
query: string,
|
|
5287
|
-
options: SearchOptions | undefined,
|
|
5288
|
-
): EditorAnchorProjection[] {
|
|
5289
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
5290
|
-
return findTextMatches(
|
|
5291
|
-
runtime.getSessionState().canonicalDocument,
|
|
5292
|
-
snapshot.selection,
|
|
5293
|
-
query,
|
|
5294
|
-
options ?? {},
|
|
5295
|
-
);
|
|
5296
|
-
}
|
|
5297
|
-
|
|
5298
3829
|
/**
|
|
5299
3830
|
* Open the correct header/footer story for a specific page. The page's
|
|
5300
3831
|
* resolved `stories.header` / `stories.footer` already carries the
|
|
@@ -5314,27 +3845,6 @@ function openStoryForPage(
|
|
|
5314
3845
|
runtime.openStory(target);
|
|
5315
3846
|
}
|
|
5316
3847
|
|
|
5317
|
-
function searchRuntimeDocument(
|
|
5318
|
-
runtime: WordReviewEditorRuntime,
|
|
5319
|
-
mountedSurface: TwProseMirrorSurfaceRef | null,
|
|
5320
|
-
query: string,
|
|
5321
|
-
options: SearchOptions = {},
|
|
5322
|
-
): SearchResultSnapshot[] {
|
|
5323
|
-
if (mountedSurface) {
|
|
5324
|
-
return mountedSurface.search(query, options);
|
|
5325
|
-
}
|
|
5326
|
-
|
|
5327
|
-
const snapshot = runtime.getRenderSnapshot();
|
|
5328
|
-
return searchDocument(
|
|
5329
|
-
runtime.getSessionState().canonicalDocument,
|
|
5330
|
-
snapshot.selection,
|
|
5331
|
-
snapshot.activeStory,
|
|
5332
|
-
runtime.getDocumentNavigationSnapshot(),
|
|
5333
|
-
query,
|
|
5334
|
-
options,
|
|
5335
|
-
);
|
|
5336
|
-
}
|
|
5337
|
-
|
|
5338
3848
|
function applyRegionAttributes(shell: HTMLElement): void {
|
|
5339
3849
|
const toolbar = shell.querySelector<HTMLElement>("header");
|
|
5340
3850
|
if (toolbar) {
|
|
@@ -5528,58 +4038,6 @@ function resolveWordReviewEditorChromeVisibility(
|
|
|
5528
4038
|
});
|
|
5529
4039
|
}
|
|
5530
4040
|
|
|
5531
|
-
function toRuntimeSelectionSnapshot(selection: PublicSelectionSnapshot) {
|
|
5532
|
-
return {
|
|
5533
|
-
anchor: selection.anchor,
|
|
5534
|
-
head: selection.head,
|
|
5535
|
-
isCollapsed: selection.isCollapsed,
|
|
5536
|
-
activeRange:
|
|
5537
|
-
selection.activeRange.kind === "range"
|
|
5538
|
-
? createRangeAnchor(
|
|
5539
|
-
selection.activeRange.from,
|
|
5540
|
-
selection.activeRange.to,
|
|
5541
|
-
selection.activeRange.assoc,
|
|
5542
|
-
)
|
|
5543
|
-
: selection.activeRange.kind === "node"
|
|
5544
|
-
? createNodeAnchor(selection.activeRange.at, selection.activeRange.assoc)
|
|
5545
|
-
: createDetachedAnchor(
|
|
5546
|
-
selection.activeRange.lastKnownRange,
|
|
5547
|
-
selection.activeRange.reason,
|
|
5548
|
-
),
|
|
5549
|
-
};
|
|
5550
|
-
}
|
|
5551
|
-
|
|
5552
|
-
function createSelectionFromAnchor(
|
|
5553
|
-
anchor: PublicSelectionSnapshot["activeRange"],
|
|
5554
|
-
storyTarget?: EditorStoryTarget,
|
|
5555
|
-
): PublicSelectionSnapshot {
|
|
5556
|
-
switch (anchor.kind) {
|
|
5557
|
-
case "range":
|
|
5558
|
-
return {
|
|
5559
|
-
anchor: anchor.from,
|
|
5560
|
-
head: anchor.to,
|
|
5561
|
-
isCollapsed: anchor.from === anchor.to,
|
|
5562
|
-
activeRange: anchor,
|
|
5563
|
-
...(storyTarget ? { storyTarget } : {}),
|
|
5564
|
-
};
|
|
5565
|
-
case "node":
|
|
5566
|
-
return {
|
|
5567
|
-
anchor: anchor.at,
|
|
5568
|
-
head: anchor.at,
|
|
5569
|
-
isCollapsed: true,
|
|
5570
|
-
activeRange: anchor,
|
|
5571
|
-
...(storyTarget ? { storyTarget } : {}),
|
|
5572
|
-
};
|
|
5573
|
-
case "detached":
|
|
5574
|
-
return {
|
|
5575
|
-
anchor: anchor.lastKnownRange.from,
|
|
5576
|
-
head: anchor.lastKnownRange.to,
|
|
5577
|
-
isCollapsed: anchor.lastKnownRange.from === anchor.lastKnownRange.to,
|
|
5578
|
-
activeRange: anchor,
|
|
5579
|
-
...(storyTarget ? { storyTarget } : {}),
|
|
5580
|
-
};
|
|
5581
|
-
}
|
|
5582
|
-
}
|
|
5583
4041
|
|
|
5584
4042
|
function estimateStoryLength(
|
|
5585
4043
|
sessionStateOrSnapshot?: EditorSessionState | PersistedEditorSnapshot,
|
|
@@ -6381,19 +4839,6 @@ function findImageNodeInValue(
|
|
|
6381
4839
|
return null;
|
|
6382
4840
|
}
|
|
6383
4841
|
|
|
6384
|
-
function createImageDataUrl(contentType: string, bytes: Uint8Array): string {
|
|
6385
|
-
const base64 = bytesToBase64(bytes);
|
|
6386
|
-
return `data:${contentType};base64,${base64}`;
|
|
6387
|
-
}
|
|
6388
|
-
|
|
6389
|
-
function bytesToBase64(bytes: Uint8Array): string {
|
|
6390
|
-
let binary = "";
|
|
6391
|
-
for (let index = 0; index < bytes.length; index += 1) {
|
|
6392
|
-
binary += String.fromCharCode(bytes[index] ?? 0);
|
|
6393
|
-
}
|
|
6394
|
-
return btoa(binary);
|
|
6395
|
-
}
|
|
6396
|
-
|
|
6397
4842
|
function deriveReviewQueueSnapshot(input: {
|
|
6398
4843
|
sections: Array<{
|
|
6399
4844
|
sectionIndex: number;
|