@beyondwork/docx-react-component 1.0.11 → 1.0.13
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 +35 -21
- package/src/api/public-types.ts +103 -1
- package/src/core/commands/formatting-commands.ts +742 -0
- package/src/core/commands/image-commands.ts +84 -2
- package/src/core/commands/structural-helpers.ts +309 -0
- package/src/core/commands/table-structure-commands.ts +721 -0
- package/src/core/commands/text-commands.ts +166 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/formats/xlsx/io/parse-sheet.ts +177 -7
- package/src/formats/xlsx/io/parse-styles.ts +2 -0
- package/src/formats/xlsx/io/xlsx-session.ts +18 -12
- package/src/formats/xlsx/model/sheet.ts +81 -1
- package/src/formats/xlsx/model/workbook.ts +10 -6
- 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 +200 -17
- package/src/runtime/table-commands.ts +79 -0
- package/src/runtime/table-schema.ts +9 -0
- package/src/ui/WordReviewEditor.tsx +708 -16
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +73 -7
- package/src/ui-tailwind/editor-surface/search-plugin.ts +76 -16
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +162 -14
- package/src/validation/compatibility-engine.ts +208 -0
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InsertTableOptions } from "../../api/public-types";
|
|
2
|
+
import type { ParagraphNode } from "../../model/canonical-document.ts";
|
|
3
|
+
import {
|
|
4
|
+
createSelectionSnapshot,
|
|
5
|
+
type CanonicalDocumentEnvelope,
|
|
6
|
+
type SelectionSnapshot,
|
|
7
|
+
} from "../state/editor-state.ts";
|
|
2
8
|
import {
|
|
3
9
|
applyTextTransaction,
|
|
4
10
|
type TextTransactionResult,
|
|
5
11
|
} from "../state/text-transaction.ts";
|
|
12
|
+
import {
|
|
13
|
+
createInsertedTableBlock,
|
|
14
|
+
createNoopStructuralMutation,
|
|
15
|
+
findTableCellParagraphSelection,
|
|
16
|
+
replaceParagraphScope,
|
|
17
|
+
resolveInsertedTableStyleId,
|
|
18
|
+
resolveParagraphScope,
|
|
19
|
+
type StructuralMutationResult,
|
|
20
|
+
} from "./structural-helpers.ts";
|
|
6
21
|
|
|
7
22
|
export interface TextCommandContext {
|
|
8
23
|
timestamp: string;
|
|
@@ -117,3 +132,153 @@ export function splitParagraph(
|
|
|
117
132
|
context,
|
|
118
133
|
);
|
|
119
134
|
}
|
|
135
|
+
|
|
136
|
+
export function insertPageBreak(
|
|
137
|
+
document: CanonicalDocumentEnvelope,
|
|
138
|
+
selection: SelectionSnapshot,
|
|
139
|
+
context: TextCommandContext,
|
|
140
|
+
): StructuralMutationResult {
|
|
141
|
+
const scope = resolveParagraphScope(document, selection);
|
|
142
|
+
if (!scope) {
|
|
143
|
+
return createNoopStructuralMutation(document, selection);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const localDocument: CanonicalDocumentEnvelope = {
|
|
147
|
+
...document,
|
|
148
|
+
content: {
|
|
149
|
+
type: "doc",
|
|
150
|
+
children: [scope.paragraph],
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const localSelection = createSelectionSnapshot(
|
|
154
|
+
selection.anchor - scope.paragraphStart,
|
|
155
|
+
selection.head - scope.paragraphStart,
|
|
156
|
+
);
|
|
157
|
+
const splitResult = splitParagraph(localDocument, localSelection, context);
|
|
158
|
+
const splitRoot = splitResult.document.content;
|
|
159
|
+
if (!splitRoot || splitRoot.type !== "doc") {
|
|
160
|
+
return createNoopStructuralMutation(document, selection);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const replacementParagraphs = splitRoot.children
|
|
164
|
+
.filter((block): block is ParagraphNode => block.type === "paragraph")
|
|
165
|
+
.map((block, index) =>
|
|
166
|
+
index === 1
|
|
167
|
+
? {
|
|
168
|
+
...block,
|
|
169
|
+
pageBreakBefore: true,
|
|
170
|
+
}
|
|
171
|
+
: block,
|
|
172
|
+
);
|
|
173
|
+
if (replacementParagraphs.length < 2) {
|
|
174
|
+
return createNoopStructuralMutation(document, selection);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const nextDocument = replaceParagraphScope(document, scope, replacementParagraphs);
|
|
178
|
+
return {
|
|
179
|
+
changed: true,
|
|
180
|
+
document: {
|
|
181
|
+
...nextDocument,
|
|
182
|
+
updatedAt: context.timestamp,
|
|
183
|
+
},
|
|
184
|
+
selection: createSelectionSnapshot(
|
|
185
|
+
splitResult.selection.anchor + scope.paragraphStart,
|
|
186
|
+
splitResult.selection.head + scope.paragraphStart,
|
|
187
|
+
),
|
|
188
|
+
mapping: {
|
|
189
|
+
...splitResult.mapping,
|
|
190
|
+
steps: splitResult.mapping.steps.map((step) => ({
|
|
191
|
+
...step,
|
|
192
|
+
from: step.from + scope.paragraphStart,
|
|
193
|
+
to: step.to + scope.paragraphStart,
|
|
194
|
+
})),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function insertTable(
|
|
200
|
+
document: CanonicalDocumentEnvelope,
|
|
201
|
+
selection: SelectionSnapshot,
|
|
202
|
+
options: InsertTableOptions,
|
|
203
|
+
context: TextCommandContext,
|
|
204
|
+
): StructuralMutationResult {
|
|
205
|
+
const scope = resolveParagraphScope(document, selection);
|
|
206
|
+
if (!scope || scope.kind !== "top-level") {
|
|
207
|
+
return createNoopStructuralMutation(document, selection);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const safeRows = Math.max(1, Math.floor(options.rows));
|
|
211
|
+
const safeColumns = Math.max(1, Math.floor(options.columns));
|
|
212
|
+
if (!Number.isFinite(safeRows) || !Number.isFinite(safeColumns)) {
|
|
213
|
+
return createNoopStructuralMutation(document, selection);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const localDocument: CanonicalDocumentEnvelope = {
|
|
217
|
+
...document,
|
|
218
|
+
content: {
|
|
219
|
+
type: "doc",
|
|
220
|
+
children: [scope.paragraph],
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
const localSelection = createSelectionSnapshot(
|
|
224
|
+
selection.anchor - scope.paragraphStart,
|
|
225
|
+
selection.head - scope.paragraphStart,
|
|
226
|
+
);
|
|
227
|
+
const splitResult = splitParagraph(localDocument, localSelection, context);
|
|
228
|
+
const splitRoot = splitResult.document.content;
|
|
229
|
+
if (!splitRoot || splitRoot.type !== "doc") {
|
|
230
|
+
return createNoopStructuralMutation(document, selection);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const replacementParagraphs = splitRoot.children.filter(
|
|
234
|
+
(block): block is ParagraphNode => block.type === "paragraph",
|
|
235
|
+
);
|
|
236
|
+
if (replacementParagraphs.length < 2) {
|
|
237
|
+
return createNoopStructuralMutation(document, selection);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const nextRoot = document.content;
|
|
241
|
+
if (!nextRoot || nextRoot.type !== "doc") {
|
|
242
|
+
return createNoopStructuralMutation(document, selection);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const insertedTable = createInsertedTableBlock(
|
|
246
|
+
safeRows,
|
|
247
|
+
safeColumns,
|
|
248
|
+
resolveInsertedTableStyleId(document),
|
|
249
|
+
);
|
|
250
|
+
const nextDocument: CanonicalDocumentEnvelope = {
|
|
251
|
+
...document,
|
|
252
|
+
updatedAt: context.timestamp,
|
|
253
|
+
content: {
|
|
254
|
+
...nextRoot,
|
|
255
|
+
children: [
|
|
256
|
+
...nextRoot.children.slice(0, scope.blockIndex),
|
|
257
|
+
replacementParagraphs[0],
|
|
258
|
+
insertedTable,
|
|
259
|
+
replacementParagraphs[1],
|
|
260
|
+
...nextRoot.children.slice(scope.blockIndex + 1),
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
changed: true,
|
|
267
|
+
document: nextDocument,
|
|
268
|
+
selection:
|
|
269
|
+
findTableCellParagraphSelection(
|
|
270
|
+
nextDocument,
|
|
271
|
+
scope.blockIndex + 1,
|
|
272
|
+
0,
|
|
273
|
+
0,
|
|
274
|
+
) ?? selection,
|
|
275
|
+
mapping: {
|
|
276
|
+
...splitResult.mapping,
|
|
277
|
+
steps: splitResult.mapping.steps.map((step) => ({
|
|
278
|
+
...step,
|
|
279
|
+
from: step.from + scope.paragraphStart,
|
|
280
|
+
to: step.to + scope.paragraphStart,
|
|
281
|
+
})),
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -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;
|