@beyondwork/docx-react-component 1.0.19 → 1.0.21
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 +44 -25
- package/src/api/public-types.ts +336 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +14 -2
- package/src/core/search/search-text.ts +28 -0
- package/src/core/state/editor-state.ts +3 -0
- package/src/index.ts +21 -0
- package/src/io/docx-session.ts +363 -17
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +83 -3
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +82 -8
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +240 -2
- package/src/io/ooxml/parse-headers-footers.ts +431 -7
- package/src/io/ooxml/parse-inline-media.ts +15 -1
- package/src/io/ooxml/parse-main-document.ts +396 -14
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +117 -1
- package/src/model/snapshot.ts +85 -1
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-navigation.ts +52 -13
- package/src/runtime/document-runtime.ts +1521 -75
- package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
- package/src/runtime/session-capabilities.ts +33 -3
- package/src/runtime/surface-projection.ts +86 -25
- package/src/runtime/table-schema.ts +2 -2
- package/src/runtime/view-state.ts +24 -6
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +915 -1314
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1448 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +55 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +27 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +237 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +55 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +190 -48
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +130 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
- package/src/validation/compatibility-engine.ts +27 -4
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/docx-comment-proof.ts +220 -0
|
@@ -28,7 +28,7 @@ const CANONICAL_DOCUMENT_TOP_LEVEL_KEYS = [
|
|
|
28
28
|
"diagnostics",
|
|
29
29
|
] as const;
|
|
30
30
|
|
|
31
|
-
const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts"] as const;
|
|
31
|
+
const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts", "fieldRegistry"] as const;
|
|
32
32
|
|
|
33
33
|
const ID_PATTERNS = {
|
|
34
34
|
styleId: /^[A-Za-z_][A-Za-z0-9._-]{0,127}$/,
|
|
@@ -62,6 +62,8 @@ export interface CanonicalDocument {
|
|
|
62
62
|
preservation: PreservationStore;
|
|
63
63
|
diagnostics: DiagnosticStore;
|
|
64
64
|
subParts?: SubPartsCatalog;
|
|
65
|
+
/** Package-backed field registry for supported field families. */
|
|
66
|
+
fieldRegistry?: FieldRegistry;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
export interface DocumentMetadata {
|
|
@@ -329,6 +331,7 @@ export interface ParagraphNode {
|
|
|
329
331
|
};
|
|
330
332
|
alignment?: "left" | "center" | "right" | "both" | "distribute";
|
|
331
333
|
spacing?: ParagraphSpacing;
|
|
334
|
+
contextualSpacing?: boolean;
|
|
332
335
|
indentation?: ParagraphIndentation;
|
|
333
336
|
tabStops?: TabStop[];
|
|
334
337
|
keepNext?: boolean;
|
|
@@ -477,11 +480,119 @@ export interface AltChunkNode {
|
|
|
477
480
|
relationshipId: string;
|
|
478
481
|
}
|
|
479
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Supported field families that receive first-class canonical treatment.
|
|
485
|
+
* These families have stable registry IDs, dependency metadata, and
|
|
486
|
+
* runtime-owned refresh behavior.
|
|
487
|
+
*/
|
|
488
|
+
export type SupportedFieldFamily = "REF" | "PAGEREF" | "NOTEREF" | "TOC";
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Unsupported field families that remain preserve-only.
|
|
492
|
+
* They survive round-trip but do not participate in runtime refresh.
|
|
493
|
+
*/
|
|
494
|
+
export type PreserveOnlyFieldFamily =
|
|
495
|
+
| "PAGE"
|
|
496
|
+
| "NUMPAGES"
|
|
497
|
+
| "DATE"
|
|
498
|
+
| "TIME"
|
|
499
|
+
| "AUTHOR"
|
|
500
|
+
| "FILENAME"
|
|
501
|
+
| "MERGEFIELD"
|
|
502
|
+
| "IF"
|
|
503
|
+
| "SEQ"
|
|
504
|
+
| "INDEX"
|
|
505
|
+
| "TC"
|
|
506
|
+
| "STYLEREF"
|
|
507
|
+
| "FORMULA"
|
|
508
|
+
| "UNKNOWN";
|
|
509
|
+
|
|
510
|
+
export type FieldFamily = SupportedFieldFamily | PreserveOnlyFieldFamily;
|
|
511
|
+
|
|
512
|
+
/** Runtime refresh status for a field instance. */
|
|
513
|
+
export type FieldRefreshStatus =
|
|
514
|
+
| "current"
|
|
515
|
+
| "stale"
|
|
516
|
+
| "unresolvable"
|
|
517
|
+
| "preserve-only";
|
|
518
|
+
|
|
480
519
|
export interface FieldNode {
|
|
481
520
|
type: "field";
|
|
482
521
|
fieldType: "simple" | "complex";
|
|
483
522
|
instruction: string;
|
|
484
523
|
children: InlineNode[];
|
|
524
|
+
/** Classified field family. Undefined for legacy snapshots. */
|
|
525
|
+
fieldFamily?: FieldFamily;
|
|
526
|
+
/** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
|
|
527
|
+
fieldTarget?: string;
|
|
528
|
+
/** Runtime refresh status. Undefined for legacy or preserve-only fields. */
|
|
529
|
+
refreshStatus?: FieldRefreshStatus;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ─── Field registry ─────────────────────────────────────────────────────────
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Package-backed field registry that catalogs every field instance in the
|
|
536
|
+
* document, grouped by supported vs preserve-only families.
|
|
537
|
+
*
|
|
538
|
+
* Supported field entries carry dependency metadata (bookmark targets) and
|
|
539
|
+
* participate in deterministic refresh. Preserve-only entries survive
|
|
540
|
+
* round-trip but are not refreshable.
|
|
541
|
+
*/
|
|
542
|
+
export interface FieldRegistry {
|
|
543
|
+
/** Supported field instances that participate in refresh. */
|
|
544
|
+
supported: FieldRegistryEntry[];
|
|
545
|
+
/** Preserve-only field instances cataloged for round-trip safety. */
|
|
546
|
+
preserveOnly: FieldRegistryEntry[];
|
|
547
|
+
/** Generated TOC structure extracted from heading-driven TOC fields. */
|
|
548
|
+
tocStructure?: TocStructure;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export interface FieldRegistryEntry {
|
|
552
|
+
/** Stable document-order index of this field (0-based). */
|
|
553
|
+
fieldIndex: number;
|
|
554
|
+
/** Classified field family. */
|
|
555
|
+
fieldFamily: FieldFamily;
|
|
556
|
+
/** Whether the field is in the supported refresh slice. */
|
|
557
|
+
supported: boolean;
|
|
558
|
+
/** Field instruction text. */
|
|
559
|
+
instruction: string;
|
|
560
|
+
/** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
|
|
561
|
+
fieldTarget?: string;
|
|
562
|
+
/** Current display text extracted from field content. */
|
|
563
|
+
displayText: string;
|
|
564
|
+
/** Paragraph index in document order where this field appears. */
|
|
565
|
+
paragraphIndex: number;
|
|
566
|
+
/** Runtime refresh status. */
|
|
567
|
+
refreshStatus: FieldRefreshStatus;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Generated table-of-contents structure extracted from TOC fields and
|
|
572
|
+
* heading-styled paragraphs in the document.
|
|
573
|
+
*/
|
|
574
|
+
export interface TocStructure {
|
|
575
|
+
/** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
|
|
576
|
+
instruction: string;
|
|
577
|
+
/** Heading level range the TOC covers. */
|
|
578
|
+
levelRange: { from: number; to: number };
|
|
579
|
+
/** Ordered TOC entries derived from heading paragraphs. */
|
|
580
|
+
entries: TocEntry[];
|
|
581
|
+
/** Whether the TOC content is current with the heading structure. */
|
|
582
|
+
status: "current" | "stale";
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export interface TocEntry {
|
|
586
|
+
/** Heading text. */
|
|
587
|
+
text: string;
|
|
588
|
+
/** Heading outline level (1-9). */
|
|
589
|
+
level: number;
|
|
590
|
+
/** Paragraph index of the heading in document order. */
|
|
591
|
+
paragraphIndex: number;
|
|
592
|
+
/** Style ID of the heading paragraph, if available. */
|
|
593
|
+
styleId?: string;
|
|
594
|
+
/** Bookmark name anchoring this heading, if present. */
|
|
595
|
+
bookmarkName?: string;
|
|
485
596
|
}
|
|
486
597
|
|
|
487
598
|
export interface BookmarkStartNode {
|
|
@@ -612,6 +723,7 @@ export type TextMark =
|
|
|
612
723
|
| { type: "doubleStrikethrough" }
|
|
613
724
|
| { type: "vanish" }
|
|
614
725
|
| { type: "lang"; val: string }
|
|
726
|
+
| { type: "highlight"; color: string; val: string }
|
|
615
727
|
| { type: "backgroundColor"; color: string }
|
|
616
728
|
| { type: "charSpacing"; val: number }
|
|
617
729
|
| { type: "kerning"; val: number }
|
|
@@ -743,6 +855,7 @@ export interface OpaqueBlockNode {
|
|
|
743
855
|
type: "opaque_block";
|
|
744
856
|
fragmentId: string;
|
|
745
857
|
warningId: string;
|
|
858
|
+
rawXml?: string;
|
|
746
859
|
}
|
|
747
860
|
|
|
748
861
|
export interface DocRange {
|
|
@@ -1272,6 +1385,9 @@ function validateDocumentNode(
|
|
|
1272
1385
|
case "opaque_block":
|
|
1273
1386
|
expectDomainString(record.fragmentId, "fragmentId", `${path}.fragmentId`, issues);
|
|
1274
1387
|
expectDomainString(record.warningId, "warningId", `${path}.warningId`, issues);
|
|
1388
|
+
if (record.rawXml !== undefined) {
|
|
1389
|
+
expectString(record.rawXml, `${path}.rawXml`, issues);
|
|
1390
|
+
}
|
|
1275
1391
|
return;
|
|
1276
1392
|
case "table":
|
|
1277
1393
|
if (!Array.isArray(record.gridColumns)) {
|
package/src/model/snapshot.ts
CHANGED
|
@@ -110,6 +110,7 @@ export interface PersistedEditorSnapshot {
|
|
|
110
110
|
canonicalDocument: CanonicalDocument;
|
|
111
111
|
compatibility: CompatibilityReport;
|
|
112
112
|
warningLog: EditorWarning[];
|
|
113
|
+
protectionSnapshot?: ProtectionSnapshotRecord;
|
|
113
114
|
sourcePackage?: PersistedSourcePackage;
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -127,7 +128,26 @@ const SNAPSHOT_REQUIRED_TOP_LEVEL_KEYS = [
|
|
|
127
128
|
"warningLog",
|
|
128
129
|
] as const;
|
|
129
130
|
|
|
130
|
-
const SNAPSHOT_OPTIONAL_TOP_LEVEL_KEYS = ["sourcePackage"] as const;
|
|
131
|
+
const SNAPSHOT_OPTIONAL_TOP_LEVEL_KEYS = ["sourcePackage", "protectionSnapshot"] as const;
|
|
132
|
+
|
|
133
|
+
export interface ProtectionRangeRecord {
|
|
134
|
+
rangeId: string;
|
|
135
|
+
start?: number;
|
|
136
|
+
end?: number;
|
|
137
|
+
editorGroup?: string;
|
|
138
|
+
editor?: string;
|
|
139
|
+
enforced: boolean;
|
|
140
|
+
enforcementReason: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ProtectionSnapshotRecord {
|
|
144
|
+
hasDocumentProtection: boolean;
|
|
145
|
+
editType?: string;
|
|
146
|
+
enforcementActive: boolean;
|
|
147
|
+
ranges: ProtectionRangeRecord[];
|
|
148
|
+
enforcedRangeCount: number;
|
|
149
|
+
preservedRangeCount: number;
|
|
150
|
+
}
|
|
131
151
|
|
|
132
152
|
const PERSISTED_EDITOR_SNAPSHOT_VERSIONS = new Set<PersistedEditorSnapshotVersion>([
|
|
133
153
|
LEGACY_PERSISTED_EDITOR_SNAPSHOT_VERSION,
|
|
@@ -260,6 +280,9 @@ export function validatePersistedEditorSnapshot(
|
|
|
260
280
|
if (record.sourcePackage !== undefined) {
|
|
261
281
|
validatePersistedSourcePackage(record.sourcePackage, "$.sourcePackage", issues);
|
|
262
282
|
}
|
|
283
|
+
if (record.protectionSnapshot !== undefined) {
|
|
284
|
+
validateProtectionSnapshot(record.protectionSnapshot, "$.protectionSnapshot", issues);
|
|
285
|
+
}
|
|
263
286
|
|
|
264
287
|
return issues;
|
|
265
288
|
}
|
|
@@ -537,6 +560,7 @@ export function createPersistedEditorSnapshot(params: {
|
|
|
537
560
|
canonicalDocument: CanonicalDocument;
|
|
538
561
|
compatibility: CompatibilityReport;
|
|
539
562
|
warningLog?: EditorWarning[];
|
|
563
|
+
protectionSnapshot?: ProtectionSnapshotRecord;
|
|
540
564
|
sourcePackage?: PersistedSourcePackage;
|
|
541
565
|
}): PersistedEditorSnapshot {
|
|
542
566
|
assertCanonicalDocument(params.canonicalDocument);
|
|
@@ -554,10 +578,70 @@ export function createPersistedEditorSnapshot(params: {
|
|
|
554
578
|
canonicalDocument: params.canonicalDocument,
|
|
555
579
|
compatibility: params.compatibility,
|
|
556
580
|
warningLog: params.warningLog ?? params.compatibility.warnings,
|
|
581
|
+
protectionSnapshot: params.protectionSnapshot,
|
|
557
582
|
sourcePackage: params.sourcePackage,
|
|
558
583
|
};
|
|
559
584
|
}
|
|
560
585
|
|
|
586
|
+
function validateProtectionSnapshot(
|
|
587
|
+
value: unknown,
|
|
588
|
+
path: string,
|
|
589
|
+
issues: ModelValidationIssue[],
|
|
590
|
+
): void {
|
|
591
|
+
const record = asPlainObject(value, path, issues);
|
|
592
|
+
if (!record) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (typeof record.hasDocumentProtection !== "boolean") {
|
|
596
|
+
issues.push({ path: `${path}.hasDocumentProtection`, message: "hasDocumentProtection must be a boolean." });
|
|
597
|
+
}
|
|
598
|
+
if (record.editType !== undefined) {
|
|
599
|
+
expectString(record.editType, `${path}.editType`, issues);
|
|
600
|
+
}
|
|
601
|
+
if (typeof record.enforcementActive !== "boolean") {
|
|
602
|
+
issues.push({ path: `${path}.enforcementActive`, message: "enforcementActive must be a boolean." });
|
|
603
|
+
}
|
|
604
|
+
if (!Array.isArray(record.ranges)) {
|
|
605
|
+
issues.push({ path: `${path}.ranges`, message: "ranges must be an array." });
|
|
606
|
+
} else {
|
|
607
|
+
record.ranges.forEach((range, index) => validateProtectionRange(range, `${path}.ranges[${index}]`, issues));
|
|
608
|
+
}
|
|
609
|
+
if (!Number.isInteger(record.enforcedRangeCount)) {
|
|
610
|
+
issues.push({ path: `${path}.enforcedRangeCount`, message: "enforcedRangeCount must be an integer." });
|
|
611
|
+
}
|
|
612
|
+
if (!Number.isInteger(record.preservedRangeCount)) {
|
|
613
|
+
issues.push({ path: `${path}.preservedRangeCount`, message: "preservedRangeCount must be an integer." });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function validateProtectionRange(
|
|
618
|
+
value: unknown,
|
|
619
|
+
path: string,
|
|
620
|
+
issues: ModelValidationIssue[],
|
|
621
|
+
): void {
|
|
622
|
+
const record = asPlainObject(value, path, issues);
|
|
623
|
+
if (!record) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
expectString(record.rangeId, `${path}.rangeId`, issues);
|
|
627
|
+
if (record.start !== undefined && !Number.isInteger(record.start)) {
|
|
628
|
+
issues.push({ path: `${path}.start`, message: "start must be an integer." });
|
|
629
|
+
}
|
|
630
|
+
if (record.end !== undefined && !Number.isInteger(record.end)) {
|
|
631
|
+
issues.push({ path: `${path}.end`, message: "end must be an integer." });
|
|
632
|
+
}
|
|
633
|
+
if (record.editorGroup !== undefined) {
|
|
634
|
+
expectString(record.editorGroup, `${path}.editorGroup`, issues);
|
|
635
|
+
}
|
|
636
|
+
if (record.editor !== undefined) {
|
|
637
|
+
expectString(record.editor, `${path}.editor`, issues);
|
|
638
|
+
}
|
|
639
|
+
if (typeof record.enforced !== "boolean") {
|
|
640
|
+
issues.push({ path: `${path}.enforced`, message: "enforced must be a boolean." });
|
|
641
|
+
}
|
|
642
|
+
expectString(record.enforcementReason, `${path}.enforcementReason`, issues);
|
|
643
|
+
}
|
|
644
|
+
|
|
561
645
|
export function projectAnchorToSnapshot(
|
|
562
646
|
anchor: Record<string, unknown>,
|
|
563
647
|
): Record<string, unknown> {
|
|
@@ -44,6 +44,8 @@ export interface RevisionSidebarEntry {
|
|
|
44
44
|
canAccept: boolean;
|
|
45
45
|
canReject: boolean;
|
|
46
46
|
preserveOnlyReason?: string;
|
|
47
|
+
linkedMoveRevisionId?: string;
|
|
48
|
+
moveDirection?: "from" | "to";
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface RevisionSidebarProjection {
|
|
@@ -212,6 +214,8 @@ function toSidebarEntry(revision: RevisionRecord): RevisionSidebarEntry {
|
|
|
212
214
|
const anchorSummary = summarizeRevisionAnchor(revision.anchor);
|
|
213
215
|
const actionability = getRevisionActionability(revision);
|
|
214
216
|
|
|
217
|
+
const moveData = revision.metadata.moveData;
|
|
218
|
+
|
|
215
219
|
return {
|
|
216
220
|
revisionId: revision.revisionId,
|
|
217
221
|
kind: revision.kind,
|
|
@@ -228,6 +232,8 @@ function toSidebarEntry(revision: RevisionRecord): RevisionSidebarEntry {
|
|
|
228
232
|
canAccept: isRevisionActionable(revision),
|
|
229
233
|
canReject: isRevisionActionable(revision),
|
|
230
234
|
preserveOnlyReason: revision.metadata.preserveOnlyReason,
|
|
235
|
+
linkedMoveRevisionId: moveData?.linkedRevisionId,
|
|
236
|
+
moveDirection: moveData?.direction,
|
|
231
237
|
};
|
|
232
238
|
}
|
|
233
239
|
|
|
@@ -45,6 +45,21 @@ import { findNoteReferencePosition } from "./view-state.ts";
|
|
|
45
45
|
const MIN_BLOCK_HEIGHT_TWIPS = 240;
|
|
46
46
|
const FOOTNOTE_REFERENCE_RESERVATION_TWIPS = 180;
|
|
47
47
|
|
|
48
|
+
interface NavigationBaseSnapshot {
|
|
49
|
+
mainSurface: EditorSurfaceSnapshot;
|
|
50
|
+
sections: ResolvedDocumentSection[];
|
|
51
|
+
pages: DocumentPageSnapshot[];
|
|
52
|
+
headings: DocumentHeadingSnapshot[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const navigationBaseCache = new WeakMap<
|
|
56
|
+
CanonicalDocumentEnvelope["content"],
|
|
57
|
+
NavigationBaseSnapshot & {
|
|
58
|
+
styles: CanonicalDocumentEnvelope["styles"];
|
|
59
|
+
subParts: CanonicalDocumentEnvelope["subParts"];
|
|
60
|
+
}
|
|
61
|
+
>();
|
|
62
|
+
|
|
48
63
|
// ---------------------------------------------------------------------------
|
|
49
64
|
// Public API
|
|
50
65
|
// ---------------------------------------------------------------------------
|
|
@@ -61,31 +76,55 @@ export function createDocumentNavigationSnapshot(
|
|
|
61
76
|
selectionHead: number,
|
|
62
77
|
activeStory: EditorStoryTarget,
|
|
63
78
|
): DocumentNavigationSnapshot {
|
|
79
|
+
const base = getNavigationBaseSnapshot(document);
|
|
80
|
+
const navigationContext = resolveActiveNavigationContext(
|
|
81
|
+
document,
|
|
82
|
+
base.pages,
|
|
83
|
+
base.sections,
|
|
84
|
+
base.mainSurface,
|
|
85
|
+
selectionHead,
|
|
86
|
+
activeStory,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
pageCount: base.pages.length,
|
|
91
|
+
pages: base.pages,
|
|
92
|
+
headings: base.headings,
|
|
93
|
+
activePageIndex: navigationContext.activePageIndex,
|
|
94
|
+
activeSectionIndex: navigationContext.activeSectionIndex,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getNavigationBaseSnapshot(
|
|
99
|
+
document: CanonicalDocumentEnvelope,
|
|
100
|
+
): NavigationBaseSnapshot {
|
|
101
|
+
const cached = navigationBaseCache.get(document.content);
|
|
102
|
+
if (
|
|
103
|
+
cached &&
|
|
104
|
+
cached.styles === document.styles &&
|
|
105
|
+
cached.subParts === document.subParts
|
|
106
|
+
) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
|
|
64
110
|
const mainSurface = createEditorSurfaceSnapshot(
|
|
65
111
|
document,
|
|
66
112
|
createSelectionSnapshot(0, 0),
|
|
67
113
|
MAIN_STORY_TARGET,
|
|
68
114
|
);
|
|
69
|
-
|
|
70
115
|
const sections = buildResolvedSections(document);
|
|
71
116
|
const pages = buildPageStack(document, sections, mainSurface);
|
|
72
117
|
const headings = buildHeadingOutline(document, mainSurface, sections, pages);
|
|
73
|
-
const
|
|
74
|
-
document,
|
|
75
|
-
pages,
|
|
76
|
-
sections,
|
|
118
|
+
const next = {
|
|
77
119
|
mainSurface,
|
|
78
|
-
|
|
79
|
-
activeStory,
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
pageCount: pages.length,
|
|
120
|
+
sections,
|
|
84
121
|
pages,
|
|
85
122
|
headings,
|
|
86
|
-
|
|
87
|
-
|
|
123
|
+
styles: document.styles,
|
|
124
|
+
subParts: document.subParts,
|
|
88
125
|
};
|
|
126
|
+
navigationBaseCache.set(document.content, next);
|
|
127
|
+
return next;
|
|
89
128
|
}
|
|
90
129
|
|
|
91
130
|
/**
|