@beyondwork/docx-react-component 1.0.105 → 1.0.108
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/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +10 -2
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +310 -6
- package/src/api/v3/ai/attach.ts +29 -4
- package/src/api/v3/ai/bundle.ts +6 -2
- package/src/api/v3/ai/inspect.ts +6 -2
- package/src/api/v3/ai/replacement.ts +112 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +8 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +202 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +17 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +119 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +147 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +279 -115
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +661 -16
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +188 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +23 -4
- package/src/runtime/layout/paginated-layout-engine.ts +149 -15
- package/src/runtime/layout/project-block-fragments.ts +351 -14
- package/src/runtime/layout/public-facet.ts +162 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +4 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +386 -0
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +130 -0
- package/src/ui/ui-controller-factory.ts +17 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
package/src/api/v3/ui/surface.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { SelectionSnapshot } from "../../public-types.ts";
|
|
|
24
24
|
import type {
|
|
25
25
|
SelectionRangeInput,
|
|
26
26
|
ScrollTarget,
|
|
27
|
+
UiObjectDragStartInput,
|
|
27
28
|
ViewportState,
|
|
28
29
|
} from "./_types.ts";
|
|
29
30
|
import type { UiApiContext } from "./_context.ts";
|
|
@@ -105,6 +106,31 @@ export const scrollToMetadata: ApiV3FnMetadata = {
|
|
|
105
106
|
rwdReference: "§UI API § ui.surface.scrollTo. Adapter delegates to UiController.dispatchScroll. Slice 3 adds geometry-backed target resolution for block/scope/comment/revision kinds prior to dispatch.",
|
|
106
107
|
};
|
|
107
108
|
|
|
109
|
+
export const beginObjectDragMetadata: ApiV3FnMetadata = {
|
|
110
|
+
name: "ui.surface.beginObjectDrag",
|
|
111
|
+
status: "live-with-adapter",
|
|
112
|
+
sourceLayer: "presentation",
|
|
113
|
+
liveEvidence: {
|
|
114
|
+
runnerTest: "test/api/v3/ui/object-drag.test.ts",
|
|
115
|
+
commit: "refactor-10-object-drag-lifecycle",
|
|
116
|
+
},
|
|
117
|
+
uxIntent: {
|
|
118
|
+
uiVisible: true,
|
|
119
|
+
expectsUxResponse: "surface-refresh",
|
|
120
|
+
expectedDelta: "object drag lifecycle starts for a mounted object handle",
|
|
121
|
+
},
|
|
122
|
+
agentMetadata: {
|
|
123
|
+
readOrMutate: "mutate",
|
|
124
|
+
boundedScope: "selection",
|
|
125
|
+
auditCategory: "ui-surface-object-drag",
|
|
126
|
+
},
|
|
127
|
+
stateClass: "C-local",
|
|
128
|
+
persistsTo: "none",
|
|
129
|
+
bidirectional: true,
|
|
130
|
+
rwdReference:
|
|
131
|
+
"§UI API § object handles. Adapter delegates mounted object drag to UiController.beginObjectDrag; commit routes through host-owned runtime mutation and layout invalidation paths.",
|
|
132
|
+
};
|
|
133
|
+
|
|
108
134
|
export function createSurfaceFamily(ctx: UiApiContext) {
|
|
109
135
|
return {
|
|
110
136
|
getSelection(): SelectionSnapshot | null {
|
|
@@ -166,5 +192,35 @@ export function createSurfaceFamily(ctx: UiApiContext) {
|
|
|
166
192
|
actualDelta: { kind: "surface-refresh", payload: { kind: target.kind, value: target.kind === "scope" || target.kind === "comment" || target.kind === "revision" || target.kind === "block" ? target.value : undefined } },
|
|
167
193
|
});
|
|
168
194
|
},
|
|
195
|
+
beginObjectDrag(input: UiObjectDragStartInput) {
|
|
196
|
+
const controller = ctx.binding?.controller;
|
|
197
|
+
if (!controller) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"ui.surface.beginObjectDrag: no controller bound — call ui.session.bind(controller) first",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (!controller.beginObjectDrag) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`ui.surface.beginObjectDrag: controller of kind "${controller.kind}" did not provide a beginObjectDrag hook`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const session = controller.beginObjectDrag(input);
|
|
208
|
+
emitUxResponse(ctx.handle, {
|
|
209
|
+
apiFn: beginObjectDragMetadata.name,
|
|
210
|
+
intent: beginObjectDragMetadata.uxIntent.expectedDelta ?? "",
|
|
211
|
+
mockOrLive: "live-with-adapter",
|
|
212
|
+
uiVisible: true,
|
|
213
|
+
expectedDelta: beginObjectDragMetadata.uxIntent.expectedDelta,
|
|
214
|
+
actualDelta: {
|
|
215
|
+
kind: "surface-refresh",
|
|
216
|
+
payload: {
|
|
217
|
+
objectId: input.objectId,
|
|
218
|
+
handle: input.handle,
|
|
219
|
+
sessionId: session.id,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
return session;
|
|
224
|
+
},
|
|
169
225
|
};
|
|
170
226
|
}
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
25
25
|
import type {
|
|
26
26
|
ViewportState,
|
|
27
|
+
PageResidencySnapshot,
|
|
27
28
|
UiListener,
|
|
28
29
|
UiUnsubscribe,
|
|
29
30
|
WorkflowMarkupMode,
|
|
@@ -33,6 +34,7 @@ import type {
|
|
|
33
34
|
import type { UiApiContext } from "./_context.ts";
|
|
34
35
|
import { readComposedViewport } from "./_context.ts";
|
|
35
36
|
import { emitUxResponse } from "../_ux-response.ts";
|
|
37
|
+
import { buildPageAnchorElementId } from "../_page-anchor-id.ts";
|
|
36
38
|
|
|
37
39
|
export const getMetadata: ApiV3FnMetadata = {
|
|
38
40
|
name: "ui.viewport.get",
|
|
@@ -86,6 +88,52 @@ export const subscribeMetadata: ApiV3FnMetadata = {
|
|
|
86
88
|
rwdReference: "§UI API § ui.viewport.subscribe. Adapter delegates to UiController.subscribeViewport; throws when the active binding has no hook. Subscribe call emits one `ux.response.ui.viewport.subscribe` acknowledgement; per-tick ViewportState deliveries flow through the listener (rAF-coalesced, U7).",
|
|
87
89
|
};
|
|
88
90
|
|
|
91
|
+
export const getPageResidencyMetadata: ApiV3FnMetadata = {
|
|
92
|
+
name: "ui.viewport.getPageResidency",
|
|
93
|
+
status: "live-with-adapter",
|
|
94
|
+
sourceLayer: "presentation",
|
|
95
|
+
liveEvidence: {
|
|
96
|
+
runnerTest: "test/api/v3/ui/viewport-residency.test.ts",
|
|
97
|
+
commit: "refactor-10-pe2-residency",
|
|
98
|
+
},
|
|
99
|
+
mockShape: {
|
|
100
|
+
deterministic: true,
|
|
101
|
+
seededFrom: "fixed",
|
|
102
|
+
shapeDescription: "Mock PageResidencySnapshot with residency='evicted' and status='unavailable' when no mounted controller exposes L10 residency policy.",
|
|
103
|
+
carriesMockFlag: true,
|
|
104
|
+
},
|
|
105
|
+
uxIntent: { uiVisible: false },
|
|
106
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-read" },
|
|
107
|
+
stateClass: "C-local",
|
|
108
|
+
persistsTo: "none",
|
|
109
|
+
rwdReference: "§PE2 § Virtual Page Windowing. Reads L10 page residency policy (realized/cold/evicted) without probing L05 geometry or realizing L11 DOM.",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const subscribePageResidencyMetadata: ApiV3FnMetadata = {
|
|
113
|
+
name: "ui.viewport.subscribePageResidency",
|
|
114
|
+
status: "live-with-adapter",
|
|
115
|
+
sourceLayer: "presentation",
|
|
116
|
+
liveEvidence: {
|
|
117
|
+
runnerTest: "test/api/v3/ui/viewport-residency.test.ts",
|
|
118
|
+
commit: "refactor-10-pe2-residency",
|
|
119
|
+
},
|
|
120
|
+
uxIntent: {
|
|
121
|
+
uiVisible: true,
|
|
122
|
+
expectsUxResponse: "surface-refresh",
|
|
123
|
+
expectedDelta: "page-residency subscriber attached; future realized/cold/evicted changes propagate through the listener",
|
|
124
|
+
},
|
|
125
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-subscribe" },
|
|
126
|
+
stateClass: "C-local",
|
|
127
|
+
persistsTo: "none",
|
|
128
|
+
bidirectional: true,
|
|
129
|
+
subscriptionShape: {
|
|
130
|
+
eventType: "ui.viewport.page_residency_changed",
|
|
131
|
+
payloadType: "PageResidencySnapshot",
|
|
132
|
+
coalescing: "raf",
|
|
133
|
+
},
|
|
134
|
+
rwdReference: "§PE2 § Virtual Page Windowing. Adapter delegates to UiController.subscribePageResidency; emissions are plain snapshots and do not dispatch PM transactions or force geometry rehydration.",
|
|
135
|
+
};
|
|
136
|
+
|
|
89
137
|
// ----- scrollToPage (coord-10 §γ — visual-fidelity / Go-to-page UX) -----
|
|
90
138
|
|
|
91
139
|
export const scrollToPageMetadata: ApiV3FnMetadata = {
|
|
@@ -112,7 +160,7 @@ export const scrollToPageMetadata: ApiV3FnMetadata = {
|
|
|
112
160
|
stateClass: "C-local",
|
|
113
161
|
persistsTo: "none",
|
|
114
162
|
rwdReference:
|
|
115
|
-
"§UI API § ui.viewport.scrollToPage. Resolves pageNumber → scrollY via handle.geometry.getPage(pageIndex); dispatches through controller.dispatchScroll({ kind:'page', value, behavior }); returns the settled {actualPage, scrollY}. 1-based page numbers; clamps to [1, pageCount]. First-class API for visual-fidelity harness + 'Go to page N' UX — replaces DOM-scrape fallback (coord-10 §γ). Parity note: reads the same `handle.geometry.getPage(i).frame.topPx` source as `runtime.viewport.getPageAnchor` (L07 coord-07 §2.9, shipped 2026-04-24 in `src/api/v3/runtime/viewport.ts`), so `actualPage + scrollY` here and `{scrollY, pageRect}` on the runtime side stay consistent by construction. No direct delegation today because `scripts/ci-check-ui-api-layer-purity.mjs` restricts `src/api/v3/ui/**` from importing `src/api/v3/runtime/**`; both surfaces are thin wrappers over the shared geometry facet.",
|
|
163
|
+
"§UI API § ui.viewport.scrollToPage. Resolves pageNumber → scrollY + elementId via handle.geometry.getPage(pageIndex); dispatches through controller.dispatchScroll({ kind:'page', value, behavior }); returns the settled {actualPage, scrollY, elementId}. 1-based page numbers; clamps to [1, pageCount]. First-class API for visual-fidelity harness + 'Go to page N' UX — replaces DOM-scrape fallback (coord-10 §γ). Parity note: reads the same `handle.geometry.getPage(i).frame.topPx` source as `runtime.viewport.getPageAnchor` (L07 coord-07 §2.9, shipped 2026-04-24 in `src/api/v3/runtime/viewport.ts`), so `actualPage + scrollY + elementId` here and `{scrollY, pageRect, elementId}` on the runtime side stay consistent by construction. No direct delegation today because `scripts/ci-check-ui-api-layer-purity.mjs` restricts `src/api/v3/ui/**` from importing `src/api/v3/runtime/**`; both surfaces are thin wrappers over the shared geometry facet.",
|
|
116
164
|
};
|
|
117
165
|
|
|
118
166
|
// ----- X5 markup-mode metadata (state-classes cross-cutting Slice X5) -----
|
|
@@ -245,6 +293,23 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
245
293
|
notifyMarkupModeSubscribers();
|
|
246
294
|
});
|
|
247
295
|
|
|
296
|
+
function normalizePageIndex(pageIndex: number): number {
|
|
297
|
+
if (!Number.isFinite(pageIndex)) return 0;
|
|
298
|
+
return Math.max(0, Math.floor(pageIndex));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function unavailableResidency(pageIndex: number): PageResidencySnapshot {
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
__mock: true,
|
|
304
|
+
pageIndex: normalizePageIndex(pageIndex),
|
|
305
|
+
residency: "evicted",
|
|
306
|
+
status: "unavailable",
|
|
307
|
+
revision: 0,
|
|
308
|
+
source: "unavailable",
|
|
309
|
+
reason: "page-residency policy is not wired on the active controller",
|
|
310
|
+
} as const);
|
|
311
|
+
}
|
|
312
|
+
|
|
248
313
|
return {
|
|
249
314
|
// Slice 11 — composes scroll/dpr/zoom from `handle.geometry` with
|
|
250
315
|
// width/height from the bound controller (see
|
|
@@ -279,6 +344,39 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
279
344
|
});
|
|
280
345
|
return unsubscribe;
|
|
281
346
|
},
|
|
347
|
+
getPageResidency(pageIndex: number): PageResidencySnapshot {
|
|
348
|
+
const normalized = normalizePageIndex(pageIndex);
|
|
349
|
+
const resolver = ctx.binding?.controller.getPageResidency;
|
|
350
|
+
if (!resolver) return unavailableResidency(normalized);
|
|
351
|
+
return resolver(normalized);
|
|
352
|
+
},
|
|
353
|
+
subscribePageResidency(
|
|
354
|
+
pageIndex: number,
|
|
355
|
+
listener: UiListener<PageResidencySnapshot>,
|
|
356
|
+
): UiUnsubscribe {
|
|
357
|
+
const controller = ctx.binding?.controller;
|
|
358
|
+
if (!controller) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"ui.viewport.subscribePageResidency: no controller bound — call ui.session.bind(controller) first",
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!controller.subscribePageResidency) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`ui.viewport.subscribePageResidency: controller of kind "${controller.kind}" did not provide a subscribePageResidency hook`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const normalized = normalizePageIndex(pageIndex);
|
|
369
|
+
const unsubscribe = controller.subscribePageResidency(normalized, listener);
|
|
370
|
+
emitUxResponse(ctx.handle, {
|
|
371
|
+
apiFn: subscribePageResidencyMetadata.name,
|
|
372
|
+
intent: subscribePageResidencyMetadata.uxIntent.expectedDelta ?? "",
|
|
373
|
+
mockOrLive: "live-with-adapter",
|
|
374
|
+
uiVisible: true,
|
|
375
|
+
expectedDelta: subscribePageResidencyMetadata.uxIntent.expectedDelta,
|
|
376
|
+
actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.viewport.pageResidency", pageIndex: normalized } },
|
|
377
|
+
});
|
|
378
|
+
return unsubscribe;
|
|
379
|
+
},
|
|
282
380
|
|
|
283
381
|
// ----- scrollToPage (coord-10 §γ) -----
|
|
284
382
|
|
|
@@ -295,10 +393,10 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
295
393
|
* the document's page count returns the last valid page's scrollY;
|
|
296
394
|
* `actualPage` reflects the clamp so callers can detect it.
|
|
297
395
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
396
|
+
* Throws when controller wiring is missing. Returns `null` only when
|
|
397
|
+
* geometry cannot resolve any page (pre-paint / empty doc). Callers
|
|
398
|
+
* that need explicit failure handling check `result !== null` before
|
|
399
|
+
* trusting the scroll.
|
|
302
400
|
*/
|
|
303
401
|
async scrollToPage(
|
|
304
402
|
pageNumber: number,
|
|
@@ -328,11 +426,15 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
328
426
|
// Try the requested page; if null, scan downward through lower
|
|
329
427
|
// indices to land on the largest resolvable page (the doc's last
|
|
330
428
|
// populated page). If nothing resolves, return null.
|
|
331
|
-
let resolved: { pageIndex: number; scrollY: number } | null = null;
|
|
429
|
+
let resolved: { pageIndex: number; scrollY: number; elementId: string } | null = null;
|
|
332
430
|
for (let i = requestedClampedLow - 1; i >= 0; i--) {
|
|
333
431
|
const page = getPage.call(ctx.handle.geometry, i);
|
|
334
432
|
if (page) {
|
|
335
|
-
resolved = {
|
|
433
|
+
resolved = {
|
|
434
|
+
pageIndex: i,
|
|
435
|
+
scrollY: page.frame.topPx,
|
|
436
|
+
elementId: buildPageAnchorElementId(page.pageId, i),
|
|
437
|
+
};
|
|
336
438
|
break;
|
|
337
439
|
}
|
|
338
440
|
}
|
|
@@ -354,11 +456,19 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
354
456
|
expectedDelta: scrollToPageMetadata.uxIntent.expectedDelta,
|
|
355
457
|
actualDelta: {
|
|
356
458
|
kind: "surface-refresh",
|
|
357
|
-
payload: {
|
|
459
|
+
payload: {
|
|
460
|
+
page: actualPage,
|
|
461
|
+
scrollY: resolved.scrollY,
|
|
462
|
+
elementId: resolved.elementId,
|
|
463
|
+
},
|
|
358
464
|
},
|
|
359
465
|
});
|
|
360
466
|
|
|
361
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
actualPage,
|
|
469
|
+
scrollY: resolved.scrollY,
|
|
470
|
+
elementId: resolved.elementId,
|
|
471
|
+
};
|
|
362
472
|
},
|
|
363
473
|
|
|
364
474
|
// ----- X5 markup-mode (state-classes cross-cutting Slice X5) -----
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
createNodeAnchor,
|
|
16
16
|
createRangeAnchor,
|
|
17
17
|
DEFAULT_BOUNDARY_ASSOC,
|
|
18
|
+
MAIN_STORY_TARGET,
|
|
18
19
|
mapAnchor,
|
|
20
|
+
storyTargetsEqual,
|
|
19
21
|
type BoundaryAssoc,
|
|
20
22
|
type EditorAnchorProjection,
|
|
21
23
|
type MappingStep,
|
|
@@ -1925,6 +1927,10 @@ function applyReviewCommand(
|
|
|
1925
1927
|
continue;
|
|
1926
1928
|
}
|
|
1927
1929
|
|
|
1930
|
+
if (!storyTargetsEqual(entry.storyTarget, MAIN_STORY_TARGET)) {
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1928
1934
|
selection = remapSelection(selection, entry.mapping);
|
|
1929
1935
|
mappingSteps.push(...entry.mapping.steps);
|
|
1930
1936
|
|
|
@@ -8,6 +8,11 @@ import type {
|
|
|
8
8
|
TextMark,
|
|
9
9
|
} from "../../model/canonical-document.ts";
|
|
10
10
|
|
|
11
|
+
type PreservedStructuralBlockNode = Exclude<
|
|
12
|
+
DocumentRootNode["children"][number],
|
|
13
|
+
ParagraphNode | OpaqueBlockNode
|
|
14
|
+
>;
|
|
15
|
+
|
|
11
16
|
export interface ParagraphProperties {
|
|
12
17
|
styleId?: string;
|
|
13
18
|
numbering?: ParagraphNode["numbering"];
|
|
@@ -33,6 +38,7 @@ export type StoryUnit =
|
|
|
33
38
|
| ImageUnit
|
|
34
39
|
| OpaqueInlineUnit
|
|
35
40
|
| OpaqueBlockUnit
|
|
41
|
+
| PreservedStructuralBlockUnit
|
|
36
42
|
| ScopeMarkerUnit
|
|
37
43
|
| ParagraphBreakUnit;
|
|
38
44
|
|
|
@@ -72,6 +78,12 @@ export interface OpaqueBlockUnit {
|
|
|
72
78
|
nextParagraph?: ParagraphProperties;
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
export interface PreservedStructuralBlockUnit {
|
|
82
|
+
kind: "structural_block";
|
|
83
|
+
block: PreservedStructuralBlockNode;
|
|
84
|
+
nextParagraph?: ParagraphProperties;
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
export interface ParagraphBreakUnit {
|
|
76
88
|
kind: "paragraph_break";
|
|
77
89
|
nextParagraph: ParagraphProperties;
|
|
@@ -114,14 +126,21 @@ export function parseTextStory(content: unknown): TextStory {
|
|
|
114
126
|
continue;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
if (block.type
|
|
129
|
+
if (block.type === "opaque_block") {
|
|
130
|
+
units.push({
|
|
131
|
+
kind: "opaque_block",
|
|
132
|
+
fragmentId: block.fragmentId,
|
|
133
|
+
warningId: block.warningId,
|
|
134
|
+
...(isParagraphNode(nextBlock)
|
|
135
|
+
? { nextParagraph: extractParagraphProperties(nextBlock) }
|
|
136
|
+
: {}),
|
|
137
|
+
});
|
|
118
138
|
continue;
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
units.push({
|
|
122
|
-
kind: "
|
|
123
|
-
|
|
124
|
-
warningId: block.warningId,
|
|
142
|
+
kind: "structural_block",
|
|
143
|
+
block,
|
|
125
144
|
...(isParagraphNode(nextBlock)
|
|
126
145
|
? { nextParagraph: extractParagraphProperties(nextBlock) }
|
|
127
146
|
: {}),
|
|
@@ -186,7 +205,7 @@ export function logicalPositionToUnitIndex(
|
|
|
186
205
|
}
|
|
187
206
|
|
|
188
207
|
export function serializeTextStory(story: TextStory): DocumentRootNode {
|
|
189
|
-
const blocks:
|
|
208
|
+
const blocks: DocumentRootNode["children"] = [];
|
|
190
209
|
let currentParagraph: ParagraphNode | undefined = createParagraph(story.firstParagraph);
|
|
191
210
|
let currentHyperlink: HyperlinkNode | undefined;
|
|
192
211
|
let activeTextBuffer:
|
|
@@ -301,6 +320,15 @@ export function serializeTextStory(story: TextStory): DocumentRootNode {
|
|
|
301
320
|
continue;
|
|
302
321
|
}
|
|
303
322
|
|
|
323
|
+
if (unit.kind === "structural_block") {
|
|
324
|
+
flushParagraph();
|
|
325
|
+
blocks.push(unit.block);
|
|
326
|
+
currentParagraph = unit.nextParagraph
|
|
327
|
+
? createParagraph(unit.nextParagraph)
|
|
328
|
+
: undefined;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
304
332
|
if (unit.kind === "text") {
|
|
305
333
|
const shouldExtendBuffer =
|
|
306
334
|
activeTextBuffer &&
|
|
@@ -384,6 +412,8 @@ export function createPlainText(story: TextStory): string {
|
|
|
384
412
|
return "\uFFF9";
|
|
385
413
|
case "opaque_block":
|
|
386
414
|
return "\uFFFA";
|
|
415
|
+
case "structural_block":
|
|
416
|
+
return "\uFFFA";
|
|
387
417
|
case "scope_marker":
|
|
388
418
|
return "";
|
|
389
419
|
}
|
|
@@ -431,6 +461,14 @@ export function cloneStoryUnit(unit: StoryUnit): StoryUnit {
|
|
|
431
461
|
? { nextParagraph: cloneParagraphProperties(unit.nextParagraph) }
|
|
432
462
|
: {}),
|
|
433
463
|
};
|
|
464
|
+
case "structural_block":
|
|
465
|
+
return {
|
|
466
|
+
kind: "structural_block",
|
|
467
|
+
block: structuredClone(unit.block) as PreservedStructuralBlockNode,
|
|
468
|
+
...(unit.nextParagraph
|
|
469
|
+
? { nextParagraph: cloneParagraphProperties(unit.nextParagraph) }
|
|
470
|
+
: {}),
|
|
471
|
+
};
|
|
434
472
|
case "paragraph_break":
|
|
435
473
|
return {
|
|
436
474
|
kind: "paragraph_break",
|
|
@@ -67,12 +67,19 @@ export function createRangeAnchor(
|
|
|
67
67
|
assoc: BoundaryAssoc = DEFAULT_BOUNDARY_ASSOC,
|
|
68
68
|
): RangeAnchor {
|
|
69
69
|
const normalized = normalizeRange({ from, to });
|
|
70
|
-
|
|
70
|
+
const anchor = {
|
|
71
71
|
kind: "range",
|
|
72
72
|
from: normalized.from,
|
|
73
73
|
to: normalized.to,
|
|
74
74
|
assoc,
|
|
75
75
|
};
|
|
76
|
+
Object.defineProperty(anchor, "range", {
|
|
77
|
+
value: normalized,
|
|
78
|
+
enumerable: false,
|
|
79
|
+
configurable: false,
|
|
80
|
+
writable: false,
|
|
81
|
+
});
|
|
82
|
+
return anchor as RangeAnchor;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
export function createNodeAnchor(at: Position, assoc: Assoc = 1): NodeAnchor {
|
|
@@ -95,7 +95,11 @@ export function rangeStaysWithinSingleParagraph(
|
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
if (
|
|
98
|
+
if (
|
|
99
|
+
unit.kind === "paragraph_break" ||
|
|
100
|
+
unit.kind === "opaque_block" ||
|
|
101
|
+
unit.kind === "structural_block"
|
|
102
|
+
) {
|
|
99
103
|
return false;
|
|
100
104
|
}
|
|
101
105
|
}
|
|
@@ -914,6 +914,8 @@ function resolveParagraphPropertiesAtPosition(
|
|
|
914
914
|
current = cloneParagraphProperties(unit.nextParagraph);
|
|
915
915
|
} else if (unit.kind === "opaque_block" && unit.nextParagraph) {
|
|
916
916
|
current = cloneParagraphProperties(unit.nextParagraph);
|
|
917
|
+
} else if (unit.kind === "structural_block" && unit.nextParagraph) {
|
|
918
|
+
current = cloneParagraphProperties(unit.nextParagraph);
|
|
917
919
|
}
|
|
918
920
|
}
|
|
919
921
|
|
|
@@ -947,7 +949,11 @@ function normalizeStoryUnits(units: StoryUnit[]): StoryUnit[] {
|
|
|
947
949
|
|
|
948
950
|
function ensureEditableRange(units: StoryUnit[]): void {
|
|
949
951
|
const protectedUnit = units.find(
|
|
950
|
-
(unit) =>
|
|
952
|
+
(unit) =>
|
|
953
|
+
unit.kind === "opaque_inline" ||
|
|
954
|
+
unit.kind === "opaque_block" ||
|
|
955
|
+
unit.kind === "structural_block" ||
|
|
956
|
+
unit.kind === "image",
|
|
951
957
|
);
|
|
952
958
|
|
|
953
959
|
if (!protectedUnit) {
|
|
@@ -971,5 +977,5 @@ function containsParagraphBoundaryChange(
|
|
|
971
977
|
|
|
972
978
|
return story.units
|
|
973
979
|
.slice(range.from, range.to)
|
|
974
|
-
.some((unit) => unit.kind === "paragraph_break");
|
|
980
|
+
.some((unit) => unit.kind === "paragraph_break" || unit.kind === "structural_block");
|
|
975
981
|
}
|
|
@@ -40,12 +40,22 @@ export function serializeRevisionsIntoDocumentXml(
|
|
|
40
40
|
const boundaries = options.boundaries ?? mapRevisionBoundaries(documentXml);
|
|
41
41
|
const replacements: XmlReplacement[] = [];
|
|
42
42
|
const consumedRevisionIds = new Set<string>();
|
|
43
|
+
const consumedMarkupRevisionIds = new Set<string>();
|
|
43
44
|
const paragraphDecisions = collectParagraphMarkupDecisions(
|
|
44
45
|
preservedMarkup,
|
|
45
46
|
revisionById,
|
|
46
47
|
boundaries,
|
|
47
48
|
);
|
|
48
49
|
|
|
50
|
+
replacements.push(
|
|
51
|
+
...createMoveRangeMarkerReplacements(
|
|
52
|
+
preservedMarkup,
|
|
53
|
+
revisionById,
|
|
54
|
+
consumedRevisionIds,
|
|
55
|
+
consumedMarkupRevisionIds,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
|
|
49
59
|
replacements.push(
|
|
50
60
|
...createParagraphStructuralReplacements(
|
|
51
61
|
documentXml,
|
|
@@ -56,7 +66,10 @@ export function serializeRevisionsIntoDocumentXml(
|
|
|
56
66
|
);
|
|
57
67
|
|
|
58
68
|
for (const markup of preservedMarkup) {
|
|
59
|
-
if (
|
|
69
|
+
if (
|
|
70
|
+
consumedRevisionIds.has(markup.revisionId) ||
|
|
71
|
+
consumedMarkupRevisionIds.has(markup.revisionId)
|
|
72
|
+
) {
|
|
60
73
|
continue;
|
|
61
74
|
}
|
|
62
75
|
|
|
@@ -87,6 +100,141 @@ export function serializeRevisionsIntoDocumentXml(
|
|
|
87
100
|
return applyReplacements(documentXml, replacements);
|
|
88
101
|
}
|
|
89
102
|
|
|
103
|
+
function createMoveRangeMarkerReplacements(
|
|
104
|
+
preservedMarkup: readonly PreservedRevisionMarkup[],
|
|
105
|
+
revisionById: ReadonlyMap<string, RevisionRecord>,
|
|
106
|
+
consumedRevisionIds: Set<string>,
|
|
107
|
+
consumedMarkupRevisionIds: Set<string>,
|
|
108
|
+
): XmlReplacement[] {
|
|
109
|
+
const replacements: XmlReplacement[] = [];
|
|
110
|
+
const rangeMarkers = collectMoveRangeMarkers(preservedMarkup);
|
|
111
|
+
|
|
112
|
+
for (const marker of rangeMarkers) {
|
|
113
|
+
const revision = revisionById.get(marker.start.revisionId);
|
|
114
|
+
if (
|
|
115
|
+
!revision ||
|
|
116
|
+
revision.status === "active" ||
|
|
117
|
+
revision.status === "detached" ||
|
|
118
|
+
revision.kind !== "move"
|
|
119
|
+
) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const direction = revision.metadata.moveData?.direction;
|
|
124
|
+
if (direction !== marker.direction) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const shouldDropSpan =
|
|
129
|
+
(revision.status === "accepted" && marker.direction === "from") ||
|
|
130
|
+
(revision.status === "rejected" && marker.direction === "to");
|
|
131
|
+
|
|
132
|
+
if (shouldDropSpan) {
|
|
133
|
+
replacements.push({
|
|
134
|
+
start: marker.start.xmlStart,
|
|
135
|
+
end: marker.end.xmlEnd,
|
|
136
|
+
replacement: "",
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
replacements.push(
|
|
140
|
+
{
|
|
141
|
+
start: marker.start.xmlStart,
|
|
142
|
+
end: marker.start.xmlEnd,
|
|
143
|
+
replacement: "",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
start: marker.end.xmlStart,
|
|
147
|
+
end: marker.end.xmlEnd,
|
|
148
|
+
replacement: "",
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
consumedRevisionIds.add(marker.start.revisionId);
|
|
154
|
+
consumedMarkupRevisionIds.add(marker.end.revisionId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return replacements;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectMoveRangeMarkers(
|
|
161
|
+
preservedMarkup: readonly PreservedRevisionMarkup[],
|
|
162
|
+
): Array<{
|
|
163
|
+
direction: "from" | "to";
|
|
164
|
+
moveId: string;
|
|
165
|
+
start: PreservedRevisionMarkup;
|
|
166
|
+
end: PreservedRevisionMarkup;
|
|
167
|
+
}> {
|
|
168
|
+
const starts = new Map<string, PreservedRevisionMarkup>();
|
|
169
|
+
const ends = new Map<string, PreservedRevisionMarkup>();
|
|
170
|
+
|
|
171
|
+
for (const markup of preservedMarkup) {
|
|
172
|
+
const direction = getMoveRangeMarkerDirection(markup.originalRevisionType);
|
|
173
|
+
if (!direction) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const moveId = readMoveRangeMarkerId(markup);
|
|
177
|
+
if (!moveId) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const key = `${direction}:${moveId}`;
|
|
181
|
+
if (isMoveRangeStart(markup.originalRevisionType)) {
|
|
182
|
+
starts.set(key, markup);
|
|
183
|
+
} else if (isMoveRangeEnd(markup.originalRevisionType)) {
|
|
184
|
+
ends.set(key, markup);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const markers: Array<{
|
|
189
|
+
direction: "from" | "to";
|
|
190
|
+
moveId: string;
|
|
191
|
+
start: PreservedRevisionMarkup;
|
|
192
|
+
end: PreservedRevisionMarkup;
|
|
193
|
+
}> = [];
|
|
194
|
+
|
|
195
|
+
for (const [key, start] of starts.entries()) {
|
|
196
|
+
const end = ends.get(key);
|
|
197
|
+
if (!end) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const [direction, moveId] = key.split(":");
|
|
201
|
+
if ((direction === "from" || direction === "to") && moveId) {
|
|
202
|
+
markers.push({ direction, moveId, start, end });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return markers;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function isMoveRangeStart(originalRevisionType: string): boolean {
|
|
210
|
+
return originalRevisionType === "moveFromRangeStart" ||
|
|
211
|
+
originalRevisionType === "moveToRangeStart";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isMoveRangeEnd(originalRevisionType: string): boolean {
|
|
215
|
+
return originalRevisionType === "moveFromRangeEnd" ||
|
|
216
|
+
originalRevisionType === "moveToRangeEnd";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getMoveRangeMarkerDirection(
|
|
220
|
+
originalRevisionType: string,
|
|
221
|
+
): "from" | "to" | undefined {
|
|
222
|
+
if (originalRevisionType === "moveFromRangeStart" ||
|
|
223
|
+
originalRevisionType === "moveFromRangeEnd") {
|
|
224
|
+
return "from";
|
|
225
|
+
}
|
|
226
|
+
if (originalRevisionType === "moveToRangeStart" ||
|
|
227
|
+
originalRevisionType === "moveToRangeEnd") {
|
|
228
|
+
return "to";
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function readMoveRangeMarkerId(markup: PreservedRevisionMarkup): string | undefined {
|
|
234
|
+
const match = /\b(?:w:)?id=(["'])([^"']+)\1/.exec(markup.rawXml);
|
|
235
|
+
return match?.[2];
|
|
236
|
+
}
|
|
237
|
+
|
|
90
238
|
function serializeMarkup(
|
|
91
239
|
markup: PreservedRevisionMarkup,
|
|
92
240
|
revision: RevisionRecord | undefined,
|
|
@@ -245,6 +245,7 @@ function normalizeParagraph(
|
|
|
245
245
|
const children = normalizeInlineChildren(paragraph.children, state, packagePartName);
|
|
246
246
|
return {
|
|
247
247
|
type: "paragraph",
|
|
248
|
+
...(paragraph.sourceRef ? { sourceRef: paragraph.sourceRef } : {}),
|
|
248
249
|
...(paragraph.styleId ? { styleId: paragraph.styleId } : {}),
|
|
249
250
|
...(paragraph.numbering ? { numbering: paragraph.numbering } : {}),
|
|
250
251
|
...(paragraph.alignment ? { alignment: paragraph.alignment } : {}),
|
|
@@ -603,6 +604,7 @@ function normalizeInlineChildren(
|
|
|
603
604
|
...(classification.target ? { fieldTarget: classification.target } : {}),
|
|
604
605
|
...(classification.switches ? { switches: classification.switches } : {}),
|
|
605
606
|
refreshStatus: classification.supported ? "stale" : "preserve-only",
|
|
607
|
+
...(node.sourceRef ? { sourceRef: node.sourceRef } : {}),
|
|
606
608
|
...(node.legacyFormField ? { legacyFormField: node.legacyFormField } : {}),
|
|
607
609
|
});
|
|
608
610
|
state.cursor += renderedLength > 0 ? renderedLength : 1;
|
|
@@ -720,6 +722,8 @@ function registerComplexPreviewMedia(
|
|
|
720
722
|
function normalizeHyperlink(node: ParsedHyperlinkNode): {
|
|
721
723
|
type: "hyperlink";
|
|
722
724
|
href: string;
|
|
725
|
+
sourceRef?: ParsedHyperlinkNode["sourceRef"];
|
|
726
|
+
fieldCarrier?: ParsedHyperlinkNode["fieldCarrier"];
|
|
723
727
|
children: Array<
|
|
724
728
|
| TextNode
|
|
725
729
|
| { type: "hard_break" }
|
|
@@ -789,6 +793,8 @@ function normalizeHyperlink(node: ParsedHyperlinkNode): {
|
|
|
789
793
|
return {
|
|
790
794
|
type: "hyperlink",
|
|
791
795
|
href: node.href,
|
|
796
|
+
...(node.sourceRef ? { sourceRef: node.sourceRef } : {}),
|
|
797
|
+
...(node.fieldCarrier ? { fieldCarrier: node.fieldCarrier } : {}),
|
|
792
798
|
children,
|
|
793
799
|
};
|
|
794
800
|
}
|