@beyondwork/docx-react-component 1.0.106 → 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 +2 -1
- 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-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +38 -0
- package/src/api/v3/ai/attach.ts +7 -2
- package/src/api/v3/ai/replacement.ts +101 -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 +1 -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 +149 -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 +13 -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 +22 -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 +118 -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 +240 -97
- 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 +591 -20
- 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 +181 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +21 -4
- package/src/runtime/layout/paginated-layout-engine.ts +139 -15
- package/src/runtime/layout/project-block-fragments.ts +265 -7
- package/src/runtime/layout/public-facet.ts +78 -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 +3 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +168 -10
- 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 +0 -1
- package/src/ui/ui-controller-factory.ts +7 -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
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
import type { UiApiContext } from "./_context.ts";
|
|
35
35
|
import { readComposedViewport } from "./_context.ts";
|
|
36
36
|
import { emitUxResponse } from "../_ux-response.ts";
|
|
37
|
+
import { buildPageAnchorElementId } from "../_page-anchor-id.ts";
|
|
37
38
|
|
|
38
39
|
export const getMetadata: ApiV3FnMetadata = {
|
|
39
40
|
name: "ui.viewport.get",
|
|
@@ -159,7 +160,7 @@ export const scrollToPageMetadata: ApiV3FnMetadata = {
|
|
|
159
160
|
stateClass: "C-local",
|
|
160
161
|
persistsTo: "none",
|
|
161
162
|
rwdReference:
|
|
162
|
-
"§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.",
|
|
163
164
|
};
|
|
164
165
|
|
|
165
166
|
// ----- X5 markup-mode metadata (state-classes cross-cutting Slice X5) -----
|
|
@@ -392,10 +393,10 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
392
393
|
* the document's page count returns the last valid page's scrollY;
|
|
393
394
|
* `actualPage` reflects the clamp so callers can detect it.
|
|
394
395
|
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
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.
|
|
399
400
|
*/
|
|
400
401
|
async scrollToPage(
|
|
401
402
|
pageNumber: number,
|
|
@@ -425,11 +426,15 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
425
426
|
// Try the requested page; if null, scan downward through lower
|
|
426
427
|
// indices to land on the largest resolvable page (the doc's last
|
|
427
428
|
// populated page). If nothing resolves, return null.
|
|
428
|
-
let resolved: { pageIndex: number; scrollY: number } | null = null;
|
|
429
|
+
let resolved: { pageIndex: number; scrollY: number; elementId: string } | null = null;
|
|
429
430
|
for (let i = requestedClampedLow - 1; i >= 0; i--) {
|
|
430
431
|
const page = getPage.call(ctx.handle.geometry, i);
|
|
431
432
|
if (page) {
|
|
432
|
-
resolved = {
|
|
433
|
+
resolved = {
|
|
434
|
+
pageIndex: i,
|
|
435
|
+
scrollY: page.frame.topPx,
|
|
436
|
+
elementId: buildPageAnchorElementId(page.pageId, i),
|
|
437
|
+
};
|
|
433
438
|
break;
|
|
434
439
|
}
|
|
435
440
|
}
|
|
@@ -451,11 +456,19 @@ export function createViewportFamily(ctx: UiApiContext) {
|
|
|
451
456
|
expectedDelta: scrollToPageMetadata.uxIntent.expectedDelta,
|
|
452
457
|
actualDelta: {
|
|
453
458
|
kind: "surface-refresh",
|
|
454
|
-
payload: {
|
|
459
|
+
payload: {
|
|
460
|
+
page: actualPage,
|
|
461
|
+
scrollY: resolved.scrollY,
|
|
462
|
+
elementId: resolved.elementId,
|
|
463
|
+
},
|
|
455
464
|
},
|
|
456
465
|
});
|
|
457
466
|
|
|
458
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
actualPage,
|
|
469
|
+
scrollY: resolved.scrollY,
|
|
470
|
+
elementId: resolved.elementId,
|
|
471
|
+
};
|
|
459
472
|
},
|
|
460
473
|
|
|
461
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
|
}
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* 3. A `TOC` field anywhere in the doc — TOC fields produce
|
|
14
14
|
* hyperlinks to generated `_Toc####` anchors at render time, so
|
|
15
15
|
* we blanket-retain `_Toc*` whenever a TOC field is present.
|
|
16
|
+
* 4. A bookmark range that encloses active revision markup. Those
|
|
17
|
+
* bookmarks are part of the review round-trip surface; stripping them
|
|
18
|
+
* after runtime revision export turns structured re-import into plain
|
|
19
|
+
* text.
|
|
16
20
|
*
|
|
17
21
|
* Produces a typed `BookmarkReferenceScan` that the parser consults
|
|
18
22
|
* at every `<w:bookmarkStart>` / `<w:bookmarkEnd>` emission site to
|
|
@@ -64,6 +68,11 @@ const TOC_FIELD_RE = /\bTOC\b/;
|
|
|
64
68
|
const REFLIKE_FIELD_RE =
|
|
65
69
|
/\b(?:HYPERLINK|REF|PAGEREF|NOTEREF)\s+([A-Za-z0-9_:.\-]+)/g;
|
|
66
70
|
const DATA_BINDING_RE = /<(?:\w+:)?dataBinding\b/i;
|
|
71
|
+
const BOOKMARK_START_RE =
|
|
72
|
+
/<(?:\w+:)?bookmarkStart\b(?=[^>]*\bw:id\s*=\s*"([^"]*)")(?=[^>]*\bw:name\s*=\s*"([^"]*)")[^>]*\/>/gi;
|
|
73
|
+
const BOOKMARK_END_RE =
|
|
74
|
+
/<(?:\w+:)?bookmarkEnd\b[^>]*\bw:id\s*=\s*"([^"]*)"[^>]*\/>/gi;
|
|
75
|
+
const ACTIVE_REVISION_RE = /<(?:\w+:)?(?:ins|del)\b/i;
|
|
67
76
|
|
|
68
77
|
/**
|
|
69
78
|
* Always-retain prefix check — bookmarks whose name starts with
|
|
@@ -102,6 +111,8 @@ export function scanBookmarkReferences(
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
retainRevisionBoundedBookmarks(documentXml, retained);
|
|
115
|
+
|
|
105
116
|
return {
|
|
106
117
|
retainedNames: retained,
|
|
107
118
|
retainAllTocPattern: retainAllToc,
|
|
@@ -109,6 +120,50 @@ export function scanBookmarkReferences(
|
|
|
109
120
|
};
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
function retainRevisionBoundedBookmarks(
|
|
124
|
+
documentXml: string,
|
|
125
|
+
retained: Set<string>,
|
|
126
|
+
): void {
|
|
127
|
+
const starts = new Map<
|
|
128
|
+
string,
|
|
129
|
+
{
|
|
130
|
+
name: string;
|
|
131
|
+
endOffset: number;
|
|
132
|
+
}
|
|
133
|
+
>();
|
|
134
|
+
|
|
135
|
+
BOOKMARK_START_RE.lastIndex = 0;
|
|
136
|
+
let startMatch: RegExpExecArray | null;
|
|
137
|
+
while ((startMatch = BOOKMARK_START_RE.exec(documentXml)) !== null) {
|
|
138
|
+
const id = startMatch[1];
|
|
139
|
+
const name = startMatch[2];
|
|
140
|
+
if (!id || !name) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
starts.set(id, {
|
|
144
|
+
name,
|
|
145
|
+
endOffset: startMatch.index + startMatch[0].length,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
BOOKMARK_END_RE.lastIndex = 0;
|
|
150
|
+
let endMatch: RegExpExecArray | null;
|
|
151
|
+
while ((endMatch = BOOKMARK_END_RE.exec(documentXml)) !== null) {
|
|
152
|
+
const id = endMatch[1];
|
|
153
|
+
if (!id) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const start = starts.get(id);
|
|
157
|
+
if (!start || start.endOffset > endMatch.index) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const enclosedXml = documentXml.slice(start.endOffset, endMatch.index);
|
|
161
|
+
if (ACTIVE_REVISION_RE.test(enclosedXml)) {
|
|
162
|
+
retained.add(start.name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
112
167
|
export function isRetainedBookmarkName(
|
|
113
168
|
name: string,
|
|
114
169
|
scan: BookmarkReferenceScan,
|
|
@@ -332,13 +332,14 @@ import type {
|
|
|
332
332
|
} from "../../model/canonical-document.ts";
|
|
333
333
|
import {
|
|
334
334
|
MAIN_STORY_KEY,
|
|
335
|
+
createCanonicalFieldId,
|
|
335
336
|
createHeaderFooterStoryKey,
|
|
336
337
|
createNoteStoryKey,
|
|
337
338
|
} from "../../model/canonical-layout-inputs.ts";
|
|
338
339
|
import { parseFieldSwitches } from "./parse-field-switches.ts";
|
|
339
340
|
|
|
340
341
|
const FIELD_FAMILY_PATTERN =
|
|
341
|
-
/^\s*(REF|PAGEREF|NOTEREF|TOC|PAGE|NUMPAGES|DATE|TIME|AUTHOR|FILENAME|MERGEFIELD|IF|SEQ|INDEX|TC|FORMTEXT|FORMCHECKBOX|FORMDROPDOWN|STYLEREF|SECTIONPAGES)\b/i;
|
|
342
|
+
/^\s*(REF|PAGEREF|NOTEREF|TOC|PAGE|NUMPAGES|DATE|TIME|AUTHOR|FILENAME|HYPERLINK|MERGEFIELD|IF|SEQ|INDEX|TC|FORMTEXT|FORMCHECKBOX|FORMDROPDOWN|STYLEREF|SECTIONPAGES)\b/i;
|
|
342
343
|
|
|
343
344
|
const SUPPORTED_FAMILIES = new Set<string>([
|
|
344
345
|
"REF",
|
|
@@ -432,20 +433,31 @@ export function buildFieldRegistry(
|
|
|
432
433
|
walkFieldDocument(root, (node, pIdx) => {
|
|
433
434
|
paragraphIndex = pIdx;
|
|
434
435
|
if (node.type === "field") {
|
|
436
|
+
const storyKey = MAIN_STORY_KEY;
|
|
435
437
|
const classification = node.fieldFamily
|
|
436
438
|
? { family: node.fieldFamily, supported: isSupportedFieldFamily(node.fieldFamily), target: node.fieldTarget, switches: node.switches }
|
|
437
439
|
: classifyFieldInstruction(node.instruction);
|
|
438
440
|
const displayText = flattenFieldText(node.children);
|
|
439
441
|
const entry: Mutable<FieldRegistryEntry> = {
|
|
440
442
|
fieldIndex,
|
|
443
|
+
canonicalFieldId:
|
|
444
|
+
node.canonicalFieldId ??
|
|
445
|
+
createCanonicalFieldId({
|
|
446
|
+
fieldIndex,
|
|
447
|
+
storyKey,
|
|
448
|
+
sourceRef: node.sourceRef,
|
|
449
|
+
}),
|
|
441
450
|
fieldFamily: classification.family,
|
|
442
451
|
supported: classification.supported,
|
|
443
452
|
instruction: node.instruction,
|
|
444
453
|
...(classification.target ? { fieldTarget: classification.target } : {}),
|
|
445
454
|
displayText,
|
|
446
455
|
paragraphIndex,
|
|
447
|
-
storyKey
|
|
456
|
+
storyKey,
|
|
457
|
+
...(node.sourceRef !== undefined ? { sourceRef: node.sourceRef } : {}),
|
|
448
458
|
refreshStatus: node.refreshStatus ?? (classification.supported ? "stale" : "preserve-only"),
|
|
459
|
+
...(node.locked !== undefined ? { locked: node.locked } : {}),
|
|
460
|
+
...(node.dirty !== undefined ? { dirty: node.dirty } : {}),
|
|
449
461
|
...(classification.switches ? { switches: classification.switches } : {}),
|
|
450
462
|
};
|
|
451
463
|
if (classification.supported) {
|
|
@@ -469,6 +481,13 @@ export function buildFieldRegistry(
|
|
|
469
481
|
const displayText = flattenFieldText(node.children);
|
|
470
482
|
const entry: Mutable<FieldRegistryEntry> = {
|
|
471
483
|
fieldIndex,
|
|
484
|
+
canonicalFieldId:
|
|
485
|
+
node.canonicalFieldId ??
|
|
486
|
+
createCanonicalFieldId({
|
|
487
|
+
fieldIndex,
|
|
488
|
+
storyKey,
|
|
489
|
+
sourceRef: node.sourceRef,
|
|
490
|
+
}),
|
|
472
491
|
fieldFamily: classification.family,
|
|
473
492
|
supported: classification.supported,
|
|
474
493
|
instruction: node.instruction,
|
|
@@ -476,7 +495,10 @@ export function buildFieldRegistry(
|
|
|
476
495
|
displayText,
|
|
477
496
|
paragraphIndex,
|
|
478
497
|
storyKey,
|
|
498
|
+
...(node.sourceRef !== undefined ? { sourceRef: node.sourceRef } : {}),
|
|
479
499
|
refreshStatus: node.refreshStatus ?? (classification.supported ? "stale" : "preserve-only"),
|
|
500
|
+
...(node.locked !== undefined ? { locked: node.locked } : {}),
|
|
501
|
+
...(node.dirty !== undefined ? { dirty: node.dirty } : {}),
|
|
480
502
|
...(classification.switches ? { switches: classification.switches } : {}),
|
|
481
503
|
};
|
|
482
504
|
if (classification.supported) {
|