@beyondwork/docx-react-component 1.0.11 → 1.0.12
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 +8 -2
- package/package.json +6 -11
- package/src/api/public-types.ts +31 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/io/docx-session.ts +392 -22
- package/src/io/export/export-session.ts +55 -0
- package/src/io/export/serialize-footnotes.ts +5 -20
- package/src/io/export/serialize-headers-footers.ts +5 -31
- package/src/io/export/serialize-main-document.ts +78 -5
- package/src/io/normalize/normalize-text.ts +90 -1
- package/src/io/ooxml/parse-footnotes.ts +68 -5
- package/src/io/ooxml/parse-headers-footers.ts +67 -9
- package/src/io/ooxml/parse-main-document.ts +169 -6
- package/src/io/opc/package-reader.ts +3 -3
- package/src/io/source-package-provenance.ts +241 -0
- package/src/model/canonical-document.ts +450 -2
- package/src/model/cds-1.0.0.ts +5 -2
- package/src/model/snapshot.ts +190 -19
- package/src/preservation/package-preservation.ts +0 -7
- package/src/runtime/document-runtime.ts +7 -1
- package/src/runtime/read-only-diagnostics-runtime.ts +1 -1
- package/src/runtime/surface-projection.ts +199 -17
- package/src/ui/WordReviewEditor.tsx +189 -14
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +66 -2
- package/src/validation/compatibility-engine.ts +208 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/bwllaming/React-OOXML-Office/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
`@beyondwork/docx-react-component` is the shipped product in this repository: `WordReviewEditor`, a fidelity-first React editor for legal-review-safe `docx` workflows.
|
|
5
|
+
`@beyondwork/docx-react-component` is the shipped product in this repository: `WordReviewEditor`, a fidelity-first React editor for legal-review-safe `docx` workflows. Waves 20 through 23 land live table editing, package distribution, validator-backed CI, and legal workflow helpers. Wave 24 is the next production-hardening packet.
|
|
6
6
|
|
|
7
7
|
The broader repository is still evolving toward a layered `react-ooxml-office` platform, but the source reality is unchanged:
|
|
8
8
|
|
|
@@ -43,6 +43,11 @@ import { WordReviewEditor } from "@beyondwork/docx-react-component";
|
|
|
43
43
|
- runtime owns the live working session
|
|
44
44
|
- host receives events, warnings, errors, snapshots, and exported artifacts
|
|
45
45
|
|
|
46
|
+
Snapshot/export note:
|
|
47
|
+
|
|
48
|
+
- when a session starts from a real `.docx`, persisted snapshots now carry embedded source-package provenance so later snapshot-origin `.docx` export can use the same package-backed exporter
|
|
49
|
+
- legacy snapshots without that provenance still load, but `.docx` export is intentionally blocked rather than falling back to a lossy minimal package
|
|
50
|
+
|
|
46
51
|
The current public ESM exports are:
|
|
47
52
|
|
|
48
53
|
- `@beyondwork/docx-react-component` -> `WordReviewEditor`
|
|
@@ -87,7 +92,7 @@ Start here:
|
|
|
87
92
|
Current shipped docx contracts:
|
|
88
93
|
|
|
89
94
|
- `docs/reference/public-api.md`
|
|
90
|
-
This doc separates the shipped
|
|
95
|
+
This doc separates the shipped Waves 20-23 surface from the future Waves 25 through 27 ref expansion.
|
|
91
96
|
- `docs/reference/ooxml-compliance.md`
|
|
92
97
|
- `docs/reference/word-review-editor-frontend-architecture.md`
|
|
93
98
|
- `docs/reference/word-review-editor-ux-guide.md`
|
|
@@ -117,6 +122,7 @@ Shared platform and planned xlsx docs:
|
|
|
117
122
|
- do not silently drop unknown OOXML
|
|
118
123
|
- keep docs honest about shipped versus planned behavior
|
|
119
124
|
- add or extend fixtures for compatibility-critical changes
|
|
125
|
+
- `bash scripts/validate-fixtures.sh` now uses the Railway validator service and requires `OPENXML_VALIDATOR_AUTH_TOKEN` when hitting the public domain
|
|
120
126
|
|
|
121
127
|
## Guiding Principle
|
|
122
128
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.12",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
|
@@ -33,14 +33,6 @@
|
|
|
33
33
|
"types": "./src/api/public-types.ts",
|
|
34
34
|
"import": "./src/api/public-types.ts"
|
|
35
35
|
},
|
|
36
|
-
"./io/docx-session": {
|
|
37
|
-
"types": "./src/io/docx-session.ts",
|
|
38
|
-
"import": "./src/io/docx-session.ts"
|
|
39
|
-
},
|
|
40
|
-
"./runtime/document-runtime": {
|
|
41
|
-
"types": "./src/runtime/document-runtime.ts",
|
|
42
|
-
"import": "./src/runtime/document-runtime.ts"
|
|
43
|
-
},
|
|
44
36
|
"./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css",
|
|
45
37
|
"./package.json": "./package.json"
|
|
46
38
|
},
|
|
@@ -90,7 +82,7 @@
|
|
|
90
82
|
"tailwindcss": "^4.2.2"
|
|
91
83
|
},
|
|
92
84
|
"devDependencies": {
|
|
93
|
-
"@chllming/wave-orchestration": "^0.9.
|
|
85
|
+
"@chllming/wave-orchestration": "^0.9.13",
|
|
94
86
|
"@types/react": "19.2.14",
|
|
95
87
|
"@types/react-dom": "19.2.3",
|
|
96
88
|
"@typescript/native-preview": "7.0.0-dev.20260409.1",
|
|
@@ -110,7 +102,10 @@
|
|
|
110
102
|
"scripts": {
|
|
111
103
|
"build": "tsup",
|
|
112
104
|
"test": "bash scripts/run-workspace-tests.sh",
|
|
113
|
-
"test:repo": "
|
|
105
|
+
"test:repo": "node scripts/run-repo-tests.mjs core",
|
|
106
|
+
"test:repo:all": "node scripts/run-repo-tests.mjs all",
|
|
107
|
+
"test:repo:optional": "node scripts/run-repo-tests.mjs optional",
|
|
108
|
+
"test:wcag-audit": "node scripts/run-repo-tests.mjs wcag-audit",
|
|
114
109
|
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
115
110
|
"lint": "pnpm run lint:no-authored-js && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
|
|
116
111
|
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
package/src/api/public-types.ts
CHANGED
|
@@ -127,7 +127,16 @@ export type SurfaceTextMark =
|
|
|
127
127
|
| "bold"
|
|
128
128
|
| "italic"
|
|
129
129
|
| "underline"
|
|
130
|
-
| "strikethrough"
|
|
130
|
+
| "strikethrough"
|
|
131
|
+
| "doubleStrikethrough"
|
|
132
|
+
| "superscript"
|
|
133
|
+
| "subscript"
|
|
134
|
+
| "vanish"
|
|
135
|
+
| "emboss"
|
|
136
|
+
| "imprint"
|
|
137
|
+
| "shadow"
|
|
138
|
+
| "smallCaps"
|
|
139
|
+
| "allCaps";
|
|
131
140
|
|
|
132
141
|
export type SurfaceInlineSegment =
|
|
133
142
|
| {
|
|
@@ -137,6 +146,15 @@ export type SurfaceInlineSegment =
|
|
|
137
146
|
to: number;
|
|
138
147
|
text: string;
|
|
139
148
|
marks?: SurfaceTextMark[];
|
|
149
|
+
markAttrs?: {
|
|
150
|
+
backgroundColor?: string;
|
|
151
|
+
charSpacing?: number;
|
|
152
|
+
kerning?: number;
|
|
153
|
+
textFill?: string;
|
|
154
|
+
fontFamily?: string;
|
|
155
|
+
fontSize?: number;
|
|
156
|
+
textColor?: string;
|
|
157
|
+
};
|
|
140
158
|
hyperlinkHref?: string;
|
|
141
159
|
}
|
|
142
160
|
| {
|
|
@@ -192,6 +210,17 @@ export type SurfaceBlockSnapshot =
|
|
|
192
210
|
numberingInstanceId: string;
|
|
193
211
|
level: number;
|
|
194
212
|
};
|
|
213
|
+
alignment?: "left" | "center" | "right" | "both" | "distribute";
|
|
214
|
+
spacing?: { before?: number; after?: number; line?: number; lineRule?: string };
|
|
215
|
+
indentation?: { left?: number; right?: number; firstLine?: number; hanging?: number };
|
|
216
|
+
borders?: { top?: unknown; left?: unknown; bottom?: unknown; right?: unknown; bar?: unknown; between?: unknown };
|
|
217
|
+
shading?: { fill?: string; color?: string; val?: string };
|
|
218
|
+
tabStops?: Array<{ pos: number; val?: string; leader?: string }>;
|
|
219
|
+
keepNext?: boolean;
|
|
220
|
+
keepLines?: boolean;
|
|
221
|
+
pageBreakBefore?: boolean;
|
|
222
|
+
outlineLevel?: number;
|
|
223
|
+
bidi?: boolean;
|
|
195
224
|
segments: SurfaceInlineSegment[];
|
|
196
225
|
}
|
|
197
226
|
| {
|
|
@@ -346,6 +375,7 @@ export interface PersistedEditorSnapshot {
|
|
|
346
375
|
canonicalDocument: RuntimePersistedEditorSnapshot["canonicalDocument"];
|
|
347
376
|
compatibility: CompatibilityReport;
|
|
348
377
|
warningLog: EditorWarning[];
|
|
378
|
+
sourcePackage?: RuntimePersistedEditorSnapshot["sourcePackage"];
|
|
349
379
|
}
|
|
350
380
|
|
|
351
381
|
export interface AddCommentParams {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_BOUNDARY_ASSOC,
|
|
3
3
|
createDetachedAnchor,
|
|
4
|
+
createNodeAnchor,
|
|
4
5
|
createRangeAnchor,
|
|
5
6
|
type EditorAnchorProjection,
|
|
6
7
|
} from "../selection/mapping.ts";
|
|
8
|
+
import { isUuid } from "../../model/cds-1.0.0.ts";
|
|
7
9
|
import {
|
|
8
10
|
assertPersistedEditorSnapshot as assertModelPersistedEditorSnapshot,
|
|
9
11
|
createPersistedEditorSnapshot as createModelPersistedEditorSnapshot,
|
|
12
|
+
validatePersistedEditorSnapshot as validateModelPersistedEditorSnapshot,
|
|
10
13
|
type PersistedEditorSnapshot as ModelPersistedEditorSnapshot,
|
|
11
14
|
} from "../../model/snapshot.ts";
|
|
12
15
|
import {
|
|
@@ -175,6 +178,7 @@ export interface EditorState {
|
|
|
175
178
|
documentId: string;
|
|
176
179
|
sessionId: string;
|
|
177
180
|
sourceLabel?: string;
|
|
181
|
+
sourcePackage?: ModelPersistedEditorSnapshot["sourcePackage"];
|
|
178
182
|
revision: number;
|
|
179
183
|
revisionToken: string;
|
|
180
184
|
isDirty: boolean;
|
|
@@ -268,29 +272,32 @@ export function createEmptyCompatibilityReport(generatedAt: string): Compatibili
|
|
|
268
272
|
|
|
269
273
|
export function createEditorState(options: CreateEditorStateOptions): EditorState {
|
|
270
274
|
const timestamp = new Date(0).toISOString();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
const normalizedSnapshot = options.persistedSnapshot
|
|
276
|
+
? normalizePersistedSnapshotForRuntime(options.persistedSnapshot, options.documentId, timestamp)
|
|
277
|
+
: undefined;
|
|
274
278
|
if (options.canonicalDocument) {
|
|
275
279
|
assertCanonicalDocument(options.canonicalDocument as unknown);
|
|
276
280
|
}
|
|
277
281
|
|
|
278
|
-
const normalizedDocument =
|
|
279
|
-
? structuredClone(
|
|
282
|
+
const normalizedDocument = normalizedSnapshot
|
|
283
|
+
? structuredClone(normalizedSnapshot.canonicalDocument)
|
|
280
284
|
: options.canonicalDocument
|
|
281
285
|
? structuredClone(options.canonicalDocument)
|
|
282
286
|
: createDefaultCanonicalDocument(options.documentId, timestamp);
|
|
283
|
-
const warnings =
|
|
287
|
+
const warnings = normalizedSnapshot
|
|
288
|
+
? normalizeWarningLog(normalizedSnapshot.warningLog)
|
|
289
|
+
: options.warnings ?? [];
|
|
284
290
|
const compatibility =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
normalizedSnapshot
|
|
292
|
+
? normalizeCompatibilityReport(normalizedSnapshot.compatibility)
|
|
293
|
+
: options.compatibility ?? createEmptyCompatibilityReport(normalizedDocument.updatedAt);
|
|
288
294
|
|
|
289
295
|
return {
|
|
290
296
|
phase: options.fatalError ? "error" : "ready",
|
|
291
297
|
documentId: options.documentId,
|
|
292
298
|
sessionId: options.sessionId,
|
|
293
299
|
sourceLabel: options.sourceLabel,
|
|
300
|
+
sourcePackage: normalizedSnapshot?.sourcePackage,
|
|
294
301
|
revision: 0,
|
|
295
302
|
revisionToken: `${options.sessionId}:0`,
|
|
296
303
|
isDirty: false,
|
|
@@ -306,6 +313,265 @@ export function createEditorState(options: CreateEditorStateOptions): EditorStat
|
|
|
306
313
|
};
|
|
307
314
|
}
|
|
308
315
|
|
|
316
|
+
function normalizePersistedSnapshotForRuntime(
|
|
317
|
+
snapshot: PersistedEditorSnapshot,
|
|
318
|
+
documentId: string,
|
|
319
|
+
timestamp: string,
|
|
320
|
+
): PersistedEditorSnapshot {
|
|
321
|
+
const issues = validateModelPersistedEditorSnapshot(snapshot as unknown);
|
|
322
|
+
if (issues.length === 0) {
|
|
323
|
+
return snapshot;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!isRecord(snapshot) || !isRecord(snapshot.canonicalDocument)) {
|
|
327
|
+
assertModelPersistedEditorSnapshot(snapshot as unknown);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const normalizedDocument = normalizeCanonicalDocumentEnvelope(
|
|
331
|
+
snapshot.canonicalDocument,
|
|
332
|
+
documentId,
|
|
333
|
+
timestamp,
|
|
334
|
+
);
|
|
335
|
+
try {
|
|
336
|
+
assertCanonicalDocument(normalizedDocument as unknown);
|
|
337
|
+
} catch {
|
|
338
|
+
assertModelPersistedEditorSnapshot(snapshot as unknown);
|
|
339
|
+
}
|
|
340
|
+
const repairWarning = createSnapshotRepairWarning(issues.length);
|
|
341
|
+
const compatibility = coerceCompatibilityReport(
|
|
342
|
+
snapshot.compatibility,
|
|
343
|
+
normalizedDocument.updatedAt,
|
|
344
|
+
repairWarning,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
snapshotVersion:
|
|
349
|
+
snapshot.snapshotVersion === "persisted-editor-snapshot/1" ||
|
|
350
|
+
snapshot.snapshotVersion === "persisted-editor-snapshot/2"
|
|
351
|
+
? snapshot.snapshotVersion
|
|
352
|
+
: "persisted-editor-snapshot/2",
|
|
353
|
+
schemaVersion: "cds/1.0.0",
|
|
354
|
+
documentId:
|
|
355
|
+
typeof snapshot.documentId === "string" && snapshot.documentId.length > 0
|
|
356
|
+
? snapshot.documentId
|
|
357
|
+
: documentId,
|
|
358
|
+
docId: normalizedDocument.docId,
|
|
359
|
+
createdAt: normalizedDocument.createdAt,
|
|
360
|
+
updatedAt: normalizedDocument.updatedAt,
|
|
361
|
+
savedAt:
|
|
362
|
+
typeof snapshot.savedAt === "string" && snapshot.savedAt.length > 0
|
|
363
|
+
? snapshot.savedAt
|
|
364
|
+
: normalizedDocument.updatedAt,
|
|
365
|
+
editorBuild:
|
|
366
|
+
typeof snapshot.editorBuild === "string" && snapshot.editorBuild.length > 0
|
|
367
|
+
? snapshot.editorBuild
|
|
368
|
+
: "dev",
|
|
369
|
+
canonicalDocument: normalizedDocument,
|
|
370
|
+
compatibility,
|
|
371
|
+
warningLog: dedupeWarnings([
|
|
372
|
+
...coerceWarnings(snapshot.warningLog),
|
|
373
|
+
repairWarning,
|
|
374
|
+
]),
|
|
375
|
+
sourcePackage: snapshot.sourcePackage,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function normalizeCanonicalDocumentEnvelope(
|
|
380
|
+
value: unknown,
|
|
381
|
+
documentId: string,
|
|
382
|
+
timestamp: string,
|
|
383
|
+
): CanonicalDocumentEnvelope {
|
|
384
|
+
const base = createDefaultCanonicalDocument(documentId, timestamp);
|
|
385
|
+
const record = isRecord(value) ? value : {};
|
|
386
|
+
const metadata = isRecord(record.metadata) ? record.metadata : {};
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
...base,
|
|
390
|
+
...record,
|
|
391
|
+
schemaVersion: "cds/1.0.0",
|
|
392
|
+
docId: isUuid(record.docId) ? record.docId : createCanonicalDocumentId(documentId),
|
|
393
|
+
createdAt:
|
|
394
|
+
typeof record.createdAt === "string" && record.createdAt.length > 0
|
|
395
|
+
? record.createdAt
|
|
396
|
+
: base.createdAt,
|
|
397
|
+
updatedAt:
|
|
398
|
+
typeof record.updatedAt === "string" && record.updatedAt.length > 0
|
|
399
|
+
? record.updatedAt
|
|
400
|
+
: base.updatedAt,
|
|
401
|
+
metadata: {
|
|
402
|
+
...base.metadata,
|
|
403
|
+
...metadata,
|
|
404
|
+
customProperties: isRecord(metadata.customProperties)
|
|
405
|
+
? (Object.fromEntries(
|
|
406
|
+
Object.entries(metadata.customProperties).filter(([, entry]) => typeof entry === "string"),
|
|
407
|
+
) as Record<string, string>)
|
|
408
|
+
: {},
|
|
409
|
+
},
|
|
410
|
+
styles:
|
|
411
|
+
record.styles === undefined
|
|
412
|
+
? base.styles
|
|
413
|
+
: (record.styles as CanonicalDocumentEnvelope["styles"]),
|
|
414
|
+
numbering:
|
|
415
|
+
record.numbering === undefined
|
|
416
|
+
? base.numbering
|
|
417
|
+
: (record.numbering as CanonicalDocumentEnvelope["numbering"]),
|
|
418
|
+
media:
|
|
419
|
+
record.media === undefined
|
|
420
|
+
? base.media
|
|
421
|
+
: (record.media as CanonicalDocumentEnvelope["media"]),
|
|
422
|
+
content: isRecord(record.content)
|
|
423
|
+
? (record.content as unknown as CanonicalDocumentEnvelope["content"])
|
|
424
|
+
: base.content,
|
|
425
|
+
review:
|
|
426
|
+
record.review === undefined
|
|
427
|
+
? base.review
|
|
428
|
+
: (record.review as CanonicalDocumentEnvelope["review"]),
|
|
429
|
+
preservation:
|
|
430
|
+
record.preservation === undefined
|
|
431
|
+
? base.preservation
|
|
432
|
+
: (record.preservation as CanonicalDocumentEnvelope["preservation"]),
|
|
433
|
+
diagnostics:
|
|
434
|
+
record.diagnostics === undefined
|
|
435
|
+
? base.diagnostics
|
|
436
|
+
: (record.diagnostics as CanonicalDocumentEnvelope["diagnostics"]),
|
|
437
|
+
...(isRecord(record.subParts)
|
|
438
|
+
? { subParts: record.subParts as unknown as CanonicalDocumentEnvelope["subParts"] }
|
|
439
|
+
: {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function coerceCompatibilityReport(
|
|
444
|
+
value: unknown,
|
|
445
|
+
generatedAt: string,
|
|
446
|
+
repairWarning: EditorWarning,
|
|
447
|
+
): CompatibilityReport {
|
|
448
|
+
const record = isRecord(value) ? value : {};
|
|
449
|
+
const warnings = dedupeWarnings([
|
|
450
|
+
...coerceWarnings(record.warnings),
|
|
451
|
+
repairWarning,
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
reportVersion: "compatibility-report/1",
|
|
456
|
+
generatedAt:
|
|
457
|
+
typeof record.generatedAt === "string" && record.generatedAt.length > 0
|
|
458
|
+
? record.generatedAt
|
|
459
|
+
: generatedAt,
|
|
460
|
+
blockExport: typeof record.blockExport === "boolean" ? record.blockExport : false,
|
|
461
|
+
featureEntries: Array.isArray(record.featureEntries)
|
|
462
|
+
? record.featureEntries.flatMap((entry) => {
|
|
463
|
+
if (!isRecord(entry)) {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
if (
|
|
467
|
+
typeof entry.featureEntryId !== "string" ||
|
|
468
|
+
typeof entry.featureKey !== "string" ||
|
|
469
|
+
typeof entry.featureClass !== "string" ||
|
|
470
|
+
typeof entry.message !== "string"
|
|
471
|
+
) {
|
|
472
|
+
return [];
|
|
473
|
+
}
|
|
474
|
+
return [{
|
|
475
|
+
featureEntryId: entry.featureEntryId,
|
|
476
|
+
featureKey: entry.featureKey,
|
|
477
|
+
featureClass: entry.featureClass as CompatibilityFeatureEntry["featureClass"],
|
|
478
|
+
message: entry.message,
|
|
479
|
+
...(entry.affectedAnchor && isRecord(entry.affectedAnchor)
|
|
480
|
+
? { affectedAnchor: entry.affectedAnchor as unknown as EditorAnchorProjection }
|
|
481
|
+
: {}),
|
|
482
|
+
...(isRecord(entry.details) ? { details: entry.details } : {}),
|
|
483
|
+
}];
|
|
484
|
+
})
|
|
485
|
+
: [],
|
|
486
|
+
warnings,
|
|
487
|
+
errors: Array.isArray(record.errors)
|
|
488
|
+
? record.errors.flatMap((entry) => {
|
|
489
|
+
if (!isRecord(entry)) {
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
if (
|
|
493
|
+
typeof entry.errorId !== "string" ||
|
|
494
|
+
typeof entry.code !== "string" ||
|
|
495
|
+
typeof entry.message !== "string" ||
|
|
496
|
+
typeof entry.source !== "string" ||
|
|
497
|
+
typeof entry.isFatal !== "boolean"
|
|
498
|
+
) {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
return [{
|
|
502
|
+
errorId: entry.errorId,
|
|
503
|
+
code: entry.code as EditorError["code"],
|
|
504
|
+
message: entry.message,
|
|
505
|
+
source: entry.source as EditorError["source"],
|
|
506
|
+
isFatal: entry.isFatal,
|
|
507
|
+
...(isRecord(entry.details) ? { details: entry.details } : {}),
|
|
508
|
+
}];
|
|
509
|
+
})
|
|
510
|
+
: [],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function coerceWarnings(value: unknown): EditorWarning[] {
|
|
515
|
+
if (!Array.isArray(value)) {
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return value.flatMap((warning) => {
|
|
520
|
+
if (!isRecord(warning)) {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
if (
|
|
524
|
+
typeof warning.warningId !== "string" ||
|
|
525
|
+
typeof warning.code !== "string" ||
|
|
526
|
+
typeof warning.severity !== "string" ||
|
|
527
|
+
typeof warning.message !== "string" ||
|
|
528
|
+
typeof warning.source !== "string"
|
|
529
|
+
) {
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return [{
|
|
534
|
+
warningId: warning.warningId,
|
|
535
|
+
code: warning.code as EditorWarning["code"],
|
|
536
|
+
severity: warning.severity as EditorWarning["severity"],
|
|
537
|
+
message: warning.message,
|
|
538
|
+
source: warning.source as EditorWarning["source"],
|
|
539
|
+
...(warning.affectedAnchor && isRecord(warning.affectedAnchor)
|
|
540
|
+
? { affectedAnchor: warning.affectedAnchor as unknown as EditorAnchorProjection }
|
|
541
|
+
: {}),
|
|
542
|
+
...(typeof warning.featureEntryId === "string"
|
|
543
|
+
? { featureEntryId: warning.featureEntryId }
|
|
544
|
+
: {}),
|
|
545
|
+
...(isRecord(warning.details) ? { details: warning.details } : {}),
|
|
546
|
+
}];
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function createSnapshotRepairWarning(issueCount: number): EditorWarning {
|
|
551
|
+
return {
|
|
552
|
+
warningId: `warning:snapshot-repair:${issueCount}`,
|
|
553
|
+
code: "import_normalized",
|
|
554
|
+
severity: "warning",
|
|
555
|
+
message: `Loaded a persisted snapshot after repairing ${issueCount} validation issue${issueCount === 1 ? "" : "s"}.`,
|
|
556
|
+
source: "validation",
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function dedupeWarnings(warnings: EditorWarning[]): EditorWarning[] {
|
|
561
|
+
const seen = new Set<string>();
|
|
562
|
+
const result: EditorWarning[] = [];
|
|
563
|
+
|
|
564
|
+
for (const warning of warnings) {
|
|
565
|
+
if (!warning.warningId || seen.has(warning.warningId)) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
seen.add(warning.warningId);
|
|
569
|
+
result.push(warning);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
|
|
309
575
|
export function deriveDocumentStats(state: Pick<EditorState, "document">): DocumentStats {
|
|
310
576
|
const serializedContent = extractText(state.document.content);
|
|
311
577
|
|
|
@@ -381,6 +647,7 @@ export function createPersistedEditorSnapshot(
|
|
|
381
647
|
canonicalDocument: state.document,
|
|
382
648
|
compatibility: (options.compatibility ?? state.compatibility) as never,
|
|
383
649
|
warningLog: state.warnings as never,
|
|
650
|
+
sourcePackage: state.sourcePackage,
|
|
384
651
|
});
|
|
385
652
|
return snapshot as PersistedEditorSnapshot;
|
|
386
653
|
}
|
|
@@ -397,6 +664,48 @@ function estimateParagraphCount(content: unknown): number {
|
|
|
397
664
|
return extractText(content).length > 0 ? 1 : 0;
|
|
398
665
|
}
|
|
399
666
|
|
|
667
|
+
function normalizeWarningLog(warnings: EditorWarning[]): EditorWarning[] {
|
|
668
|
+
return warnings.map((warning) => ({
|
|
669
|
+
...warning,
|
|
670
|
+
affectedAnchor: warning.affectedAnchor
|
|
671
|
+
? normalizeAnchorProjection(warning.affectedAnchor)
|
|
672
|
+
: undefined,
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function normalizeCompatibilityReport(report: CompatibilityReport): CompatibilityReport {
|
|
677
|
+
return {
|
|
678
|
+
...report,
|
|
679
|
+
featureEntries: report.featureEntries.map((entry) => ({
|
|
680
|
+
...entry,
|
|
681
|
+
affectedAnchor: entry.affectedAnchor
|
|
682
|
+
? normalizeAnchorProjection(entry.affectedAnchor)
|
|
683
|
+
: undefined,
|
|
684
|
+
})),
|
|
685
|
+
warnings: normalizeWarningLog(report.warnings),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function normalizeAnchorProjection(anchor: EditorAnchorProjection): EditorAnchorProjection {
|
|
690
|
+
switch (anchor.kind) {
|
|
691
|
+
case "range": {
|
|
692
|
+
const rangeAnchor = anchor as EditorAnchorProjection & {
|
|
693
|
+
range?: { from: number; to: number };
|
|
694
|
+
from?: number;
|
|
695
|
+
to?: number;
|
|
696
|
+
assoc: { start: -1 | 1; end: -1 | 1 };
|
|
697
|
+
};
|
|
698
|
+
return rangeAnchor.range
|
|
699
|
+
? createRangeAnchor(rangeAnchor.range.from, rangeAnchor.range.to, rangeAnchor.assoc)
|
|
700
|
+
: createRangeAnchor(rangeAnchor.from ?? 0, rangeAnchor.to ?? 0, rangeAnchor.assoc);
|
|
701
|
+
}
|
|
702
|
+
case "node":
|
|
703
|
+
return createNodeAnchor(anchor.at, anchor.assoc);
|
|
704
|
+
case "detached":
|
|
705
|
+
return createDetachedAnchor(anchor.lastKnownRange, anchor.reason);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
400
709
|
function extractText(value: unknown): string {
|
|
401
710
|
if (typeof value === "string") {
|
|
402
711
|
return value;
|