@beyondwork/docx-react-component 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -104
- package/package.json +76 -46
- package/src/README.md +85 -0
- package/src/api/README.md +22 -0
- package/src/api/public-types.ts +525 -0
- package/src/compare/diff-engine.ts +530 -0
- package/src/compare/export-redlines.ts +162 -0
- package/src/compare/snapshot.ts +37 -0
- package/src/component-inventory.md +99 -0
- package/src/core/README.md +10 -0
- package/src/core/commands/README.md +3 -0
- package/src/core/commands/formatting-commands.ts +161 -0
- package/src/core/commands/image-commands.ts +144 -0
- package/src/core/commands/index.ts +1013 -0
- package/src/core/commands/list-commands.ts +370 -0
- package/src/core/commands/review-commands.ts +108 -0
- package/src/core/commands/text-commands.ts +119 -0
- package/src/core/schema/README.md +3 -0
- package/src/core/schema/text-schema.ts +512 -0
- package/src/core/selection/README.md +3 -0
- package/src/core/selection/mapping.ts +238 -0
- package/src/core/selection/review-anchors.ts +94 -0
- package/src/core/state/README.md +3 -0
- package/src/core/state/editor-state.ts +580 -0
- package/src/core/state/text-transaction.ts +276 -0
- package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
- package/src/formats/xlsx/io/parse-sheet.ts +289 -0
- package/src/formats/xlsx/io/parse-styles.ts +57 -0
- package/src/formats/xlsx/io/parse-workbook.ts +75 -0
- package/src/formats/xlsx/io/xlsx-session.ts +306 -0
- package/src/formats/xlsx/model/cell.ts +189 -0
- package/src/formats/xlsx/model/sheet.ts +244 -0
- package/src/formats/xlsx/model/styles.ts +118 -0
- package/src/formats/xlsx/model/workbook.ts +449 -0
- package/src/index.ts +45 -0
- package/src/io/README.md +10 -0
- package/src/io/docx-session.ts +1763 -0
- package/src/io/export/README.md +3 -0
- package/src/io/export/export-session.ts +165 -0
- package/src/io/export/minimal-docx.ts +115 -0
- package/src/io/export/reattach-preserved-parts.ts +54 -0
- package/src/io/export/serialize-comments.ts +876 -0
- package/src/io/export/serialize-footnotes.ts +217 -0
- package/src/io/export/serialize-headers-footers.ts +200 -0
- package/src/io/export/serialize-main-document.ts +982 -0
- package/src/io/export/serialize-numbering.ts +97 -0
- package/src/io/export/serialize-revisions.ts +389 -0
- package/src/io/export/serialize-runtime-revisions.ts +265 -0
- package/src/io/export/serialize-tables.ts +147 -0
- package/src/io/export/split-review-boundaries.ts +194 -0
- package/src/io/normalize/README.md +3 -0
- package/src/io/normalize/normalize-text.ts +437 -0
- package/src/io/ooxml/README.md +3 -0
- package/src/io/ooxml/parse-comments.ts +779 -0
- package/src/io/ooxml/parse-complex-content.ts +287 -0
- package/src/io/ooxml/parse-fields.ts +438 -0
- package/src/io/ooxml/parse-footnotes.ts +403 -0
- package/src/io/ooxml/parse-headers-footers.ts +483 -0
- package/src/io/ooxml/parse-inline-media.ts +431 -0
- package/src/io/ooxml/parse-main-document.ts +1846 -0
- package/src/io/ooxml/parse-numbering.ts +425 -0
- package/src/io/ooxml/parse-revisions.ts +658 -0
- package/src/io/ooxml/parse-shapes.ts +271 -0
- package/src/io/ooxml/parse-tables.ts +568 -0
- package/src/io/ooxml/parse-theme.ts +314 -0
- package/src/io/ooxml/part-manifest.ts +136 -0
- package/src/io/ooxml/revision-boundaries.ts +351 -0
- package/src/io/opc/README.md +3 -0
- package/src/io/opc/corrupt-package.ts +166 -0
- package/src/io/opc/docx-package.ts +74 -0
- package/src/io/opc/package-reader.ts +320 -0
- package/src/io/opc/package-writer.ts +273 -0
- package/src/legal/bookmarks.ts +196 -0
- package/src/legal/cross-references.ts +356 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/model/README.md +3 -0
- package/src/model/canonical-document.ts +1911 -0
- package/src/model/cds-1.0.0.ts +196 -0
- package/src/model/snapshot.ts +393 -0
- package/src/preservation/README.md +3 -0
- package/src/preservation/markup-compatibility.ts +48 -0
- package/src/preservation/opaque-fragment-store.ts +89 -0
- package/src/preservation/opaque-region.ts +233 -0
- package/src/preservation/package-preservation.ts +120 -0
- package/src/preservation/preserved-part-manifest.ts +56 -0
- package/src/preservation/relationship-retention.ts +57 -0
- package/src/preservation/store.ts +185 -0
- package/src/review/README.md +16 -0
- package/src/review/store/README.md +3 -0
- package/src/review/store/comment-anchors.ts +70 -0
- package/src/review/store/comment-remapping.ts +154 -0
- package/src/review/store/comment-store.ts +331 -0
- package/src/review/store/comment-thread.ts +109 -0
- package/src/review/store/revision-actions.ts +394 -0
- package/src/review/store/revision-store.ts +303 -0
- package/src/review/store/revision-types.ts +168 -0
- package/src/review/store/runtime-comment-store.ts +43 -0
- package/src/runtime/README.md +3 -0
- package/src/runtime/ai-action-policy.ts +764 -0
- package/src/runtime/document-runtime.ts +967 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
- package/src/runtime/review-runtime.ts +44 -0
- package/src/runtime/revision-runtime.ts +107 -0
- package/src/runtime/session-capabilities.ts +138 -0
- package/src/runtime/surface-projection.ts +570 -0
- package/src/runtime/table-commands.ts +87 -0
- package/src/runtime/table-schema.ts +140 -0
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/ui/README.md +30 -0
- package/src/ui/WordReviewEditor.tsx +1504 -0
- package/src/ui/comments/README.md +3 -0
- package/src/ui/compatibility/README.md +3 -0
- package/src/ui/editor-surface/README.md +3 -0
- package/src/ui/headless/comment-decoration-model.ts +124 -0
- package/src/ui/headless/revision-decoration-model.ts +128 -0
- package/src/ui/headless/selection-helpers.ts +34 -0
- package/src/ui/headless/use-editor-keyboard.ts +98 -0
- package/src/ui/review/README.md +3 -0
- package/src/ui/shared/revision-filters.ts +31 -0
- package/src/ui/status/README.md +3 -0
- package/src/ui/theme/README.md +3 -0
- package/src/ui/toolbar/README.md +3 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
- package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
- package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
- package/src/ui-tailwind/index.ts +61 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
- package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
- package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
- package/src/ui-tailwind/theme/editor-theme.css +190 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
- package/src/validation/README.md +3 -0
- package/src/validation/compatibility-engine.ts +317 -0
- package/src/validation/compatibility-report.ts +160 -0
- package/src/validation/diagnostics.ts +203 -0
- package/src/validation/import-diagnostics.ts +128 -0
- package/src/validation/low-priority-word-surfaces.ts +373 -0
- package/dist/chunk-32W6IVQE.js +0 -7725
- package/dist/chunk-32W6IVQE.js.map +0 -1
- package/dist/index.cjs +0 -23722
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -16011
- package/dist/index.js.map +0 -1
- package/dist/public-types-DqCURAz8.d.cts +0 -1152
- package/dist/public-types-DqCURAz8.d.ts +0 -1152
- package/dist/tailwind.cjs +0 -8295
- package/dist/tailwind.cjs.map +0 -1
- package/dist/tailwind.d.cts +0 -323
- package/dist/tailwind.d.ts +0 -323
- package/dist/tailwind.js +0 -553
- package/dist/tailwind.js.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export const CDS_SCHEMA_VERSION = "cds/1.0.0" as const;
|
|
2
|
+
export const PERSISTED_EDITOR_SNAPSHOT_VERSION =
|
|
3
|
+
"persisted-editor-snapshot/1" as const;
|
|
4
|
+
export const COMPATIBILITY_REPORT_VERSION = "compatibility-report/1" as const;
|
|
5
|
+
|
|
6
|
+
export type CDS_SchemaVersion = typeof CDS_SCHEMA_VERSION;
|
|
7
|
+
export type PersistedEditorSnapshotVersion =
|
|
8
|
+
typeof PERSISTED_EDITOR_SNAPSHOT_VERSION;
|
|
9
|
+
export type CompatibilityReportVersion = typeof COMPATIBILITY_REPORT_VERSION;
|
|
10
|
+
|
|
11
|
+
export type Id = string;
|
|
12
|
+
export type UUID = string;
|
|
13
|
+
export type ISO8601DateTime = string;
|
|
14
|
+
export type Base64 = string;
|
|
15
|
+
export type PartName = string;
|
|
16
|
+
export type RelationshipId = string;
|
|
17
|
+
|
|
18
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
19
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
20
|
+
|
|
21
|
+
export interface JsonObject {
|
|
22
|
+
[key: string]: JsonValue | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModelValidationIssue {
|
|
26
|
+
path: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ModelValidationError extends Error {
|
|
31
|
+
readonly issues: readonly ModelValidationIssue[];
|
|
32
|
+
|
|
33
|
+
constructor(message: string, issues: readonly ModelValidationIssue[]) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ModelValidationError";
|
|
36
|
+
this.issues = issues;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const UUID_PATTERN =
|
|
41
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
42
|
+
const ISO_8601_UTC_PATTERN =
|
|
43
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
44
|
+
|
|
45
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
46
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const prototype = Object.getPrototypeOf(value);
|
|
51
|
+
return prototype === Object.prototype || prototype === null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
55
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isUuid(value: unknown): value is UUID {
|
|
59
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isIso8601UtcTimestamp(
|
|
63
|
+
value: unknown,
|
|
64
|
+
): value is ISO8601DateTime {
|
|
65
|
+
return (
|
|
66
|
+
typeof value === "string" &&
|
|
67
|
+
ISO_8601_UTC_PATTERN.test(value) &&
|
|
68
|
+
!Number.isNaN(Date.parse(value))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function asPlainObject(
|
|
73
|
+
value: unknown,
|
|
74
|
+
path: string,
|
|
75
|
+
issues: ModelValidationIssue[],
|
|
76
|
+
): Record<string, unknown> | null {
|
|
77
|
+
if (!isPlainObject(value)) {
|
|
78
|
+
issues.push({ path, message: "Expected a plain object." });
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function expectString(
|
|
86
|
+
value: unknown,
|
|
87
|
+
path: string,
|
|
88
|
+
issues: ModelValidationIssue[],
|
|
89
|
+
): string | null {
|
|
90
|
+
if (!isNonEmptyString(value)) {
|
|
91
|
+
issues.push({ path, message: "Expected a non-empty string." });
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function expectExactString<T extends string>(
|
|
99
|
+
value: unknown,
|
|
100
|
+
expected: T,
|
|
101
|
+
path: string,
|
|
102
|
+
issues: ModelValidationIssue[],
|
|
103
|
+
): T | null {
|
|
104
|
+
if (value !== expected) {
|
|
105
|
+
issues.push({
|
|
106
|
+
path,
|
|
107
|
+
message: `Expected ${JSON.stringify(expected)}.`,
|
|
108
|
+
});
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return expected;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function expectIso8601UtcTimestamp(
|
|
116
|
+
value: unknown,
|
|
117
|
+
path: string,
|
|
118
|
+
issues: ModelValidationIssue[],
|
|
119
|
+
): ISO8601DateTime | null {
|
|
120
|
+
if (!isIso8601UtcTimestamp(value)) {
|
|
121
|
+
issues.push({
|
|
122
|
+
path,
|
|
123
|
+
message: "Expected an ISO 8601 UTC timestamp string.",
|
|
124
|
+
});
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function expectUuid(
|
|
132
|
+
value: unknown,
|
|
133
|
+
path: string,
|
|
134
|
+
issues: ModelValidationIssue[],
|
|
135
|
+
): UUID | null {
|
|
136
|
+
if (!isUuid(value)) {
|
|
137
|
+
issues.push({
|
|
138
|
+
path,
|
|
139
|
+
message: "Expected an RFC 4122 UUID string.",
|
|
140
|
+
});
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function sortRecordKeys<T>(
|
|
148
|
+
record: Record<string, T>,
|
|
149
|
+
): Array<[string, T]> {
|
|
150
|
+
return Object.entries(record).sort(([left], [right]) =>
|
|
151
|
+
left.localeCompare(right),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function assertValid(
|
|
156
|
+
issues: readonly ModelValidationIssue[],
|
|
157
|
+
message: string,
|
|
158
|
+
): void {
|
|
159
|
+
if (issues.length > 0) {
|
|
160
|
+
throw new ModelValidationError(message, issues);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function toStableJsonValue(value: unknown): JsonValue {
|
|
165
|
+
if (
|
|
166
|
+
value === null ||
|
|
167
|
+
typeof value === "string" ||
|
|
168
|
+
typeof value === "number" ||
|
|
169
|
+
typeof value === "boolean"
|
|
170
|
+
) {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
return value.map((item) => toStableJsonValue(item));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!isPlainObject(value)) {
|
|
179
|
+
throw new TypeError("Cannot serialize non-JSON value.");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const sortedEntries = sortRecordKeys(value).filter(
|
|
183
|
+
([, entryValue]) => entryValue !== undefined,
|
|
184
|
+
);
|
|
185
|
+
const result: JsonObject = {};
|
|
186
|
+
|
|
187
|
+
for (const [key, entryValue] of sortedEntries) {
|
|
188
|
+
result[key] = toStableJsonValue(entryValue);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function stableStringify(value: unknown): string {
|
|
195
|
+
return JSON.stringify(toStableJsonValue(value));
|
|
196
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CDS_SCHEMA_VERSION,
|
|
3
|
+
COMPATIBILITY_REPORT_VERSION,
|
|
4
|
+
PERSISTED_EDITOR_SNAPSHOT_VERSION,
|
|
5
|
+
type CompatibilityReportVersion,
|
|
6
|
+
type ISO8601DateTime,
|
|
7
|
+
type ModelValidationIssue,
|
|
8
|
+
type PersistedEditorSnapshotVersion,
|
|
9
|
+
asPlainObject,
|
|
10
|
+
assertValid,
|
|
11
|
+
expectExactString,
|
|
12
|
+
expectIso8601UtcTimestamp,
|
|
13
|
+
expectString,
|
|
14
|
+
stableStringify,
|
|
15
|
+
} from "./cds-1.0.0.ts";
|
|
16
|
+
import {
|
|
17
|
+
type CanonicalDocument,
|
|
18
|
+
assertCanonicalDocument,
|
|
19
|
+
validateCanonicalDocument,
|
|
20
|
+
} from "./canonical-document.ts";
|
|
21
|
+
|
|
22
|
+
export type EditorWarningCode =
|
|
23
|
+
| "unsupported_ooxml_preserved"
|
|
24
|
+
| "unsupported_ooxml_locked"
|
|
25
|
+
| "import_normalized"
|
|
26
|
+
| "export_roundtrip_risk"
|
|
27
|
+
| "comment_anchor_detached"
|
|
28
|
+
| "revision_anchor_detached"
|
|
29
|
+
| "large_document_degraded"
|
|
30
|
+
| "font_substitution"
|
|
31
|
+
| "image_missing";
|
|
32
|
+
|
|
33
|
+
export interface EditorWarning {
|
|
34
|
+
warningId: string;
|
|
35
|
+
code: EditorWarningCode;
|
|
36
|
+
severity: "info" | "warning";
|
|
37
|
+
message: string;
|
|
38
|
+
source:
|
|
39
|
+
| "import"
|
|
40
|
+
| "runtime"
|
|
41
|
+
| "review"
|
|
42
|
+
| "preservation"
|
|
43
|
+
| "validation"
|
|
44
|
+
| "export";
|
|
45
|
+
affectedAnchor?: Record<string, unknown>;
|
|
46
|
+
featureEntryId?: string;
|
|
47
|
+
details?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type EditorErrorCode =
|
|
51
|
+
| "import_failed"
|
|
52
|
+
| "export_failed"
|
|
53
|
+
| "package_corrupt"
|
|
54
|
+
| "validation_failed"
|
|
55
|
+
| "datastore_failed"
|
|
56
|
+
| "internal_invariant";
|
|
57
|
+
|
|
58
|
+
export interface EditorError {
|
|
59
|
+
errorId: string;
|
|
60
|
+
code: EditorErrorCode;
|
|
61
|
+
message: string;
|
|
62
|
+
isFatal: boolean;
|
|
63
|
+
source: "import" | "runtime" | "validation" | "datastore" | "export";
|
|
64
|
+
details?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type CompatibilityFeatureClass =
|
|
68
|
+
| "supported-roundtrip"
|
|
69
|
+
| "preserve-only"
|
|
70
|
+
| "unsupported-fatal";
|
|
71
|
+
|
|
72
|
+
export interface CompatibilityFeatureEntry {
|
|
73
|
+
featureEntryId: string;
|
|
74
|
+
featureKey: string;
|
|
75
|
+
featureClass: CompatibilityFeatureClass;
|
|
76
|
+
message: string;
|
|
77
|
+
affectedAnchor?: Record<string, unknown>;
|
|
78
|
+
details?: Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CompatibilityReport {
|
|
82
|
+
reportVersion: CompatibilityReportVersion;
|
|
83
|
+
generatedAt: ISO8601DateTime;
|
|
84
|
+
blockExport: boolean;
|
|
85
|
+
featureEntries: CompatibilityFeatureEntry[];
|
|
86
|
+
warnings: EditorWarning[];
|
|
87
|
+
errors: EditorError[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PersistedEditorSnapshot {
|
|
91
|
+
snapshotVersion: PersistedEditorSnapshotVersion;
|
|
92
|
+
schemaVersion: typeof CDS_SCHEMA_VERSION;
|
|
93
|
+
documentId: string;
|
|
94
|
+
docId: string;
|
|
95
|
+
createdAt: ISO8601DateTime;
|
|
96
|
+
updatedAt: ISO8601DateTime;
|
|
97
|
+
savedAt: ISO8601DateTime;
|
|
98
|
+
editorBuild: string;
|
|
99
|
+
canonicalDocument: CanonicalDocument;
|
|
100
|
+
compatibility: CompatibilityReport;
|
|
101
|
+
warningLog: EditorWarning[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const SNAPSHOT_TOP_LEVEL_KEYS = [
|
|
105
|
+
"snapshotVersion",
|
|
106
|
+
"schemaVersion",
|
|
107
|
+
"documentId",
|
|
108
|
+
"docId",
|
|
109
|
+
"createdAt",
|
|
110
|
+
"updatedAt",
|
|
111
|
+
"savedAt",
|
|
112
|
+
"editorBuild",
|
|
113
|
+
"canonicalDocument",
|
|
114
|
+
"compatibility",
|
|
115
|
+
"warningLog",
|
|
116
|
+
] as const;
|
|
117
|
+
|
|
118
|
+
export function serializePersistedEditorSnapshot(
|
|
119
|
+
snapshot: PersistedEditorSnapshot,
|
|
120
|
+
): string {
|
|
121
|
+
assertPersistedEditorSnapshot(snapshot);
|
|
122
|
+
return stableStringify(snapshot);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function parsePersistedEditorSnapshot(
|
|
126
|
+
json: string,
|
|
127
|
+
): PersistedEditorSnapshot {
|
|
128
|
+
const parsed = JSON.parse(json) as unknown;
|
|
129
|
+
assertPersistedEditorSnapshot(parsed);
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function assertPersistedEditorSnapshot(
|
|
134
|
+
snapshot: unknown,
|
|
135
|
+
): asserts snapshot is PersistedEditorSnapshot {
|
|
136
|
+
const issues = validatePersistedEditorSnapshot(snapshot);
|
|
137
|
+
assertValid(issues, "Invalid persisted editor snapshot.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function validatePersistedEditorSnapshot(
|
|
141
|
+
snapshot: unknown,
|
|
142
|
+
): ModelValidationIssue[] {
|
|
143
|
+
const issues: ModelValidationIssue[] = [];
|
|
144
|
+
const record = asPlainObject(snapshot, "$", issues);
|
|
145
|
+
if (!record) {
|
|
146
|
+
return issues;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
validateExactObjectKeys(record, SNAPSHOT_TOP_LEVEL_KEYS, "$", issues);
|
|
150
|
+
|
|
151
|
+
expectExactString(
|
|
152
|
+
record.snapshotVersion,
|
|
153
|
+
PERSISTED_EDITOR_SNAPSHOT_VERSION,
|
|
154
|
+
"$.snapshotVersion",
|
|
155
|
+
issues,
|
|
156
|
+
);
|
|
157
|
+
expectExactString(
|
|
158
|
+
record.schemaVersion,
|
|
159
|
+
CDS_SCHEMA_VERSION,
|
|
160
|
+
"$.schemaVersion",
|
|
161
|
+
issues,
|
|
162
|
+
);
|
|
163
|
+
expectString(record.documentId, "$.documentId", issues);
|
|
164
|
+
expectString(record.docId, "$.docId", issues);
|
|
165
|
+
expectIso8601UtcTimestamp(record.createdAt, "$.createdAt", issues);
|
|
166
|
+
expectIso8601UtcTimestamp(record.updatedAt, "$.updatedAt", issues);
|
|
167
|
+
expectIso8601UtcTimestamp(record.savedAt, "$.savedAt", issues);
|
|
168
|
+
expectString(record.editorBuild, "$.editorBuild", issues);
|
|
169
|
+
|
|
170
|
+
const canonicalDocumentIssues = validateCanonicalDocument(record.canonicalDocument);
|
|
171
|
+
issues.push(...canonicalDocumentIssues.map(prefixIssue("$.canonicalDocument")));
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
asPlainObject(record.canonicalDocument, "$.canonicalDocument", [])?.docId !==
|
|
175
|
+
record.docId
|
|
176
|
+
) {
|
|
177
|
+
issues.push({
|
|
178
|
+
path: "$.docId",
|
|
179
|
+
message: "Snapshot docId must match canonicalDocument.docId.",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
validateCompatibilityReport(record.compatibility, "$.compatibility", issues);
|
|
184
|
+
|
|
185
|
+
if (!Array.isArray(record.warningLog)) {
|
|
186
|
+
issues.push({
|
|
187
|
+
path: "$.warningLog",
|
|
188
|
+
message: "warningLog must be an array.",
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
record.warningLog.forEach((warning, index) => {
|
|
192
|
+
validateEditorWarning(warning, `$.warningLog[${index}]`, issues);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return issues;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function assertCompatibilityReport(
|
|
200
|
+
report: unknown,
|
|
201
|
+
): asserts report is CompatibilityReport {
|
|
202
|
+
const issues = validateCompatibilityReport(report);
|
|
203
|
+
assertValid(issues, "Invalid compatibility report.");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function validateCompatibilityReport(
|
|
207
|
+
report: unknown,
|
|
208
|
+
path = "$",
|
|
209
|
+
issues: ModelValidationIssue[] = [],
|
|
210
|
+
): ModelValidationIssue[] {
|
|
211
|
+
const record = asPlainObject(report, path, issues);
|
|
212
|
+
if (!record) {
|
|
213
|
+
return issues;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
expectExactString(
|
|
217
|
+
record.reportVersion,
|
|
218
|
+
COMPATIBILITY_REPORT_VERSION,
|
|
219
|
+
`${path}.reportVersion`,
|
|
220
|
+
issues,
|
|
221
|
+
);
|
|
222
|
+
expectIso8601UtcTimestamp(record.generatedAt, `${path}.generatedAt`, issues);
|
|
223
|
+
|
|
224
|
+
if (typeof record.blockExport !== "boolean") {
|
|
225
|
+
issues.push({
|
|
226
|
+
path: `${path}.blockExport`,
|
|
227
|
+
message: "blockExport must be a boolean.",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!Array.isArray(record.featureEntries)) {
|
|
232
|
+
issues.push({
|
|
233
|
+
path: `${path}.featureEntries`,
|
|
234
|
+
message: "featureEntries must be an array.",
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
record.featureEntries.forEach((featureEntry, index) => {
|
|
238
|
+
validateCompatibilityFeatureEntry(
|
|
239
|
+
featureEntry,
|
|
240
|
+
`${path}.featureEntries[${index}]`,
|
|
241
|
+
issues,
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!Array.isArray(record.warnings)) {
|
|
247
|
+
issues.push({
|
|
248
|
+
path: `${path}.warnings`,
|
|
249
|
+
message: "warnings must be an array.",
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
record.warnings.forEach((warning, index) => {
|
|
253
|
+
validateEditorWarning(warning, `${path}.warnings[${index}]`, issues);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!Array.isArray(record.errors)) {
|
|
258
|
+
issues.push({
|
|
259
|
+
path: `${path}.errors`,
|
|
260
|
+
message: "errors must be an array.",
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
record.errors.forEach((error, index) => {
|
|
264
|
+
validateEditorError(error, `${path}.errors[${index}]`, issues);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return issues;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function validateCompatibilityFeatureEntry(
|
|
272
|
+
featureEntry: unknown,
|
|
273
|
+
path: string,
|
|
274
|
+
issues: ModelValidationIssue[],
|
|
275
|
+
): void {
|
|
276
|
+
const record = asPlainObject(featureEntry, path, issues);
|
|
277
|
+
if (!record) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
expectString(record.featureEntryId, `${path}.featureEntryId`, issues);
|
|
282
|
+
expectString(record.featureKey, `${path}.featureKey`, issues);
|
|
283
|
+
expectString(record.featureClass, `${path}.featureClass`, issues);
|
|
284
|
+
expectString(record.message, `${path}.message`, issues);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function validateEditorWarning(
|
|
288
|
+
warning: unknown,
|
|
289
|
+
path: string,
|
|
290
|
+
issues: ModelValidationIssue[],
|
|
291
|
+
): void {
|
|
292
|
+
const record = asPlainObject(warning, path, issues);
|
|
293
|
+
if (!record) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
expectString(record.warningId, `${path}.warningId`, issues);
|
|
298
|
+
expectString(record.code, `${path}.code`, issues);
|
|
299
|
+
expectString(record.severity, `${path}.severity`, issues);
|
|
300
|
+
expectString(record.message, `${path}.message`, issues);
|
|
301
|
+
expectString(record.source, `${path}.source`, issues);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function validateEditorError(
|
|
305
|
+
error: unknown,
|
|
306
|
+
path: string,
|
|
307
|
+
issues: ModelValidationIssue[],
|
|
308
|
+
): void {
|
|
309
|
+
const record = asPlainObject(error, path, issues);
|
|
310
|
+
if (!record) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
expectString(record.errorId, `${path}.errorId`, issues);
|
|
315
|
+
expectString(record.code, `${path}.code`, issues);
|
|
316
|
+
expectString(record.message, `${path}.message`, issues);
|
|
317
|
+
expectString(record.source, `${path}.source`, issues);
|
|
318
|
+
|
|
319
|
+
if (typeof record.isFatal !== "boolean") {
|
|
320
|
+
issues.push({
|
|
321
|
+
path: `${path}.isFatal`,
|
|
322
|
+
message: "isFatal must be a boolean.",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function validateExactObjectKeys(
|
|
328
|
+
record: Record<string, unknown>,
|
|
329
|
+
expectedKeys: readonly string[],
|
|
330
|
+
path: string,
|
|
331
|
+
issues: ModelValidationIssue[],
|
|
332
|
+
): void {
|
|
333
|
+
const actualKeys = new Set(Object.keys(record));
|
|
334
|
+
const expected = new Set(expectedKeys);
|
|
335
|
+
|
|
336
|
+
for (const expectedKey of expectedKeys) {
|
|
337
|
+
if (!actualKeys.has(expectedKey)) {
|
|
338
|
+
issues.push({
|
|
339
|
+
path,
|
|
340
|
+
message: `Missing required key ${JSON.stringify(expectedKey)}.`,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const actualKey of actualKeys) {
|
|
346
|
+
if (!expected.has(actualKey)) {
|
|
347
|
+
issues.push({
|
|
348
|
+
path: `${path}.${actualKey}`,
|
|
349
|
+
message:
|
|
350
|
+
"Unexpected persisted snapshot key. Render/UI state is not part of the persistence contract.",
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function prefixIssue(basePath: string) {
|
|
357
|
+
return (issue: ModelValidationIssue): ModelValidationIssue => ({
|
|
358
|
+
path: `${basePath}${issue.path === "$" ? "" : issue.path.slice(1)}`,
|
|
359
|
+
message: issue.message,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function createPersistedEditorSnapshot(params: {
|
|
364
|
+
documentId: string;
|
|
365
|
+
savedAt: ISO8601DateTime;
|
|
366
|
+
editorBuild: string;
|
|
367
|
+
canonicalDocument: CanonicalDocument;
|
|
368
|
+
compatibility: CompatibilityReport;
|
|
369
|
+
warningLog?: EditorWarning[];
|
|
370
|
+
}): PersistedEditorSnapshot {
|
|
371
|
+
assertCanonicalDocument(params.canonicalDocument);
|
|
372
|
+
assertCompatibilityReport(params.compatibility);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
snapshotVersion: PERSISTED_EDITOR_SNAPSHOT_VERSION,
|
|
376
|
+
schemaVersion: CDS_SCHEMA_VERSION,
|
|
377
|
+
documentId: params.documentId,
|
|
378
|
+
docId: params.canonicalDocument.docId,
|
|
379
|
+
createdAt: params.canonicalDocument.createdAt,
|
|
380
|
+
updatedAt: params.canonicalDocument.updatedAt,
|
|
381
|
+
savedAt: params.savedAt,
|
|
382
|
+
editorBuild: params.editorBuild,
|
|
383
|
+
canonicalDocument: params.canonicalDocument,
|
|
384
|
+
compatibility: params.compatibility,
|
|
385
|
+
warningLog: params.warningLog ?? params.compatibility.warnings,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function projectAnchorToSnapshot(
|
|
390
|
+
anchor: Record<string, unknown>,
|
|
391
|
+
): Record<string, unknown> {
|
|
392
|
+
return JSON.parse(JSON.stringify(anchor)) as Record<string, unknown>;
|
|
393
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function extractDocumentRootAttributes(documentXml: string): Record<string, string> {
|
|
2
|
+
const match = documentXml.match(
|
|
3
|
+
/<(?:[A-Za-z_][A-Za-z0-9:._-]*:)?document\b([^>]*)>/u,
|
|
4
|
+
);
|
|
5
|
+
if (!match) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const attributes: Record<string, string> = {};
|
|
10
|
+
const pattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
11
|
+
for (const attributeMatch of (match[1] ?? "").matchAll(pattern)) {
|
|
12
|
+
const name = attributeMatch[1];
|
|
13
|
+
const value = attributeMatch[3] ?? attributeMatch[4] ?? "";
|
|
14
|
+
if (!name) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
attributes[name] = value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return attributes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function extractMarkupCompatibilityAttributes(
|
|
25
|
+
documentXml: string,
|
|
26
|
+
): Record<string, string> {
|
|
27
|
+
return filterMarkupCompatibilityAttributes(extractDocumentRootAttributes(documentXml));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function filterMarkupCompatibilityAttributes(
|
|
31
|
+
attributes: Record<string, string>,
|
|
32
|
+
): Record<string, string> {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(attributes).filter(([name]) =>
|
|
35
|
+
name.startsWith("xmlns:mc") ||
|
|
36
|
+
name === "mc:Ignorable" ||
|
|
37
|
+
name === "mc:ProcessContent" ||
|
|
38
|
+
name === "mc:PreserveAttributes" ||
|
|
39
|
+
name === "mc:PreserveElements" ||
|
|
40
|
+
name.startsWith("xmlns:w1") ||
|
|
41
|
+
name.startsWith("xmlns:wsp") ||
|
|
42
|
+
name.startsWith("xmlns:wpg") ||
|
|
43
|
+
name.startsWith("xmlns:wpi") ||
|
|
44
|
+
name.startsWith("xmlns:wne") ||
|
|
45
|
+
name.startsWith("xmlns:wps"),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
}
|