@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
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
EditorWarning,
|
|
8
8
|
} from "../core/state/editor-state.ts";
|
|
9
9
|
import type {
|
|
10
|
+
BlockNode,
|
|
10
11
|
DocumentRootNode,
|
|
11
12
|
InlineNode,
|
|
12
13
|
ParagraphNode,
|
|
@@ -221,6 +222,9 @@ function collectPreservationFeatures(
|
|
|
221
222
|
document: CanonicalDocumentEnvelope,
|
|
222
223
|
): CompatibilityFeatureEntry[] {
|
|
223
224
|
const entries: CompatibilityFeatureEntry[] = [];
|
|
225
|
+
const structuredSubPartPaths = collectStructuredSubPartPaths(document);
|
|
226
|
+
|
|
227
|
+
entries.push(...collectSubPartFeatures(document));
|
|
224
228
|
|
|
225
229
|
for (const fragment of listOpaqueFragments(document.preservation as never)) {
|
|
226
230
|
const descriptor = describeOpaqueFragment(fragment);
|
|
@@ -242,6 +246,10 @@ function collectPreservationFeatures(
|
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
for (const packagePart of listPreservedPackageParts(document.preservation as never)) {
|
|
249
|
+
if (structuredSubPartPaths.has(packagePart.packagePartName)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
245
253
|
entries.push({
|
|
246
254
|
featureEntryId: `feature:package:${packagePart.packagePartName}`,
|
|
247
255
|
featureKey: "unknown-package-parts",
|
|
@@ -258,6 +266,206 @@ function collectPreservationFeatures(
|
|
|
258
266
|
return entries;
|
|
259
267
|
}
|
|
260
268
|
|
|
269
|
+
function collectStructuredSubPartPaths(
|
|
270
|
+
document: CanonicalDocumentEnvelope,
|
|
271
|
+
): ReadonlySet<string> {
|
|
272
|
+
const subParts = document.subParts;
|
|
273
|
+
if (!subParts) {
|
|
274
|
+
return new Set<string>();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const paths = new Set<string>();
|
|
278
|
+
|
|
279
|
+
for (const header of subParts.headers ?? []) {
|
|
280
|
+
paths.add(header.partPath);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const footer of subParts.footers ?? []) {
|
|
284
|
+
paths.add(footer.partPath);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (subParts.footnoteCollection) {
|
|
288
|
+
paths.add("/word/footnotes.xml");
|
|
289
|
+
paths.add("/word/endnotes.xml");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (subParts.theme) {
|
|
293
|
+
paths.add("/word/theme/theme1.xml");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return paths;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function collectSubPartFeatures(
|
|
300
|
+
document: CanonicalDocumentEnvelope,
|
|
301
|
+
): CompatibilityFeatureEntry[] {
|
|
302
|
+
const subParts = document.subParts;
|
|
303
|
+
if (!subParts) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const entries: CompatibilityFeatureEntry[] = collectLossySubPartFeatures(subParts);
|
|
308
|
+
const hasHeaderFooterContent = (subParts.headers?.length ?? 0) > 0 || (subParts.footers?.length ?? 0) > 0;
|
|
309
|
+
const noteCount =
|
|
310
|
+
Object.keys(subParts.footnoteCollection?.footnotes ?? {}).length +
|
|
311
|
+
Object.keys(subParts.footnoteCollection?.endnotes ?? {}).length;
|
|
312
|
+
|
|
313
|
+
if (hasHeaderFooterContent) {
|
|
314
|
+
entries.push({
|
|
315
|
+
featureEntryId: "feature:subparts:headers-footers",
|
|
316
|
+
featureKey: "headers-footers",
|
|
317
|
+
featureClass: "preserve-only",
|
|
318
|
+
message: "Headers and footers are preserved through structured sub-part ownership.",
|
|
319
|
+
details: {
|
|
320
|
+
headerCount: subParts.headers?.length ?? 0,
|
|
321
|
+
footerCount: subParts.footers?.length ?? 0,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (noteCount > 0) {
|
|
327
|
+
entries.push({
|
|
328
|
+
featureEntryId: "feature:subparts:notes",
|
|
329
|
+
featureKey: "notes",
|
|
330
|
+
featureClass: "preserve-only",
|
|
331
|
+
message: "Footnotes and endnotes are preserved through structured sub-part ownership.",
|
|
332
|
+
details: {
|
|
333
|
+
footnoteCount: Object.keys(subParts.footnoteCollection?.footnotes ?? {}).length,
|
|
334
|
+
endnoteCount: Object.keys(subParts.footnoteCollection?.endnotes ?? {}).length,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (subParts.theme) {
|
|
340
|
+
entries.push({
|
|
341
|
+
featureEntryId: "feature:subparts:theme",
|
|
342
|
+
featureKey: "unknown-package-parts",
|
|
343
|
+
featureClass: "preserve-only",
|
|
344
|
+
message: "Theme metadata is preserved through structured sub-part ownership.",
|
|
345
|
+
details: {
|
|
346
|
+
surface: "theme-subpart",
|
|
347
|
+
themeName: subParts.theme.name,
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return entries;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function collectLossySubPartFeatures(
|
|
356
|
+
subParts: NonNullable<CanonicalDocumentEnvelope["subParts"]>,
|
|
357
|
+
): CompatibilityFeatureEntry[] {
|
|
358
|
+
const entries: CompatibilityFeatureEntry[] = [];
|
|
359
|
+
|
|
360
|
+
const headerFooterLossy = [
|
|
361
|
+
...(subParts.headers ?? []).flatMap((header) =>
|
|
362
|
+
collectLossyBlocks(header.blocks, `header:${header.partPath}`),
|
|
363
|
+
),
|
|
364
|
+
...(subParts.footers ?? []).flatMap((footer) =>
|
|
365
|
+
collectLossyBlocks(footer.blocks, `footer:${footer.partPath}`),
|
|
366
|
+
),
|
|
367
|
+
];
|
|
368
|
+
if (headerFooterLossy.length > 0) {
|
|
369
|
+
entries.push({
|
|
370
|
+
featureEntryId: "feature:subparts:headers-footers-lossy",
|
|
371
|
+
featureKey: "headers-footers-lossy",
|
|
372
|
+
featureClass: "unsupported-fatal",
|
|
373
|
+
message: "Headers or footers contain content the current sub-part serializer cannot re-emit safely.",
|
|
374
|
+
details: {
|
|
375
|
+
issues: headerFooterLossy,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const noteLossy = [
|
|
381
|
+
...Object.values(subParts.footnoteCollection?.footnotes ?? {}).flatMap((note) =>
|
|
382
|
+
collectLossyBlocks(note.blocks, `footnote:${note.noteId}`),
|
|
383
|
+
),
|
|
384
|
+
...Object.values(subParts.footnoteCollection?.endnotes ?? {}).flatMap((note) =>
|
|
385
|
+
collectLossyBlocks(note.blocks, `endnote:${note.noteId}`),
|
|
386
|
+
),
|
|
387
|
+
];
|
|
388
|
+
if (noteLossy.length > 0) {
|
|
389
|
+
entries.push({
|
|
390
|
+
featureEntryId: "feature:subparts:notes-lossy",
|
|
391
|
+
featureKey: "notes-lossy",
|
|
392
|
+
featureClass: "unsupported-fatal",
|
|
393
|
+
message: "Footnotes or endnotes contain content the current sub-part serializer cannot re-emit safely.",
|
|
394
|
+
details: {
|
|
395
|
+
issues: noteLossy,
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return entries;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function collectLossyBlocks(
|
|
404
|
+
blocks: readonly BlockNode[],
|
|
405
|
+
surface: string,
|
|
406
|
+
): string[] {
|
|
407
|
+
const issues: string[] = [];
|
|
408
|
+
|
|
409
|
+
for (const block of blocks) {
|
|
410
|
+
if (block.type !== "paragraph") {
|
|
411
|
+
issues.push(`${surface}:${block.type}`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
block.numbering !== undefined ||
|
|
417
|
+
block.spacing !== undefined ||
|
|
418
|
+
block.indentation !== undefined ||
|
|
419
|
+
block.tabStops !== undefined ||
|
|
420
|
+
block.keepNext !== undefined ||
|
|
421
|
+
block.keepLines !== undefined ||
|
|
422
|
+
block.outlineLevel !== undefined ||
|
|
423
|
+
block.pageBreakBefore !== undefined ||
|
|
424
|
+
block.widowControl !== undefined ||
|
|
425
|
+
block.borders !== undefined ||
|
|
426
|
+
block.shading !== undefined ||
|
|
427
|
+
block.bidi !== undefined ||
|
|
428
|
+
block.suppressLineNumbers !== undefined ||
|
|
429
|
+
block.cnfStyle !== undefined
|
|
430
|
+
) {
|
|
431
|
+
issues.push(`${surface}:paragraph-properties`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
for (const child of block.children) {
|
|
435
|
+
issues.push(...collectLossyInlineContent(child, surface));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return [...new Set(issues)];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function collectLossyInlineContent(
|
|
443
|
+
node: InlineNode,
|
|
444
|
+
surface: string,
|
|
445
|
+
): string[] {
|
|
446
|
+
switch (node.type) {
|
|
447
|
+
case "text": {
|
|
448
|
+
const unsupportedMarks = (node.marks ?? [])
|
|
449
|
+
.filter(
|
|
450
|
+
(mark) =>
|
|
451
|
+
mark.type !== "bold" &&
|
|
452
|
+
mark.type !== "italic" &&
|
|
453
|
+
mark.type !== "underline" &&
|
|
454
|
+
mark.type !== "strikethrough" &&
|
|
455
|
+
mark.type !== "doubleStrikethrough",
|
|
456
|
+
)
|
|
457
|
+
.map((mark) => `${surface}:mark:${mark.type}`);
|
|
458
|
+
return unsupportedMarks;
|
|
459
|
+
}
|
|
460
|
+
case "tab":
|
|
461
|
+
case "hard_break":
|
|
462
|
+
case "footnote_ref":
|
|
463
|
+
return [];
|
|
464
|
+
default:
|
|
465
|
+
return [`${surface}:${node.type}`];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
261
469
|
function collectDiagnosticWarnings(
|
|
262
470
|
document: CanonicalDocumentEnvelope,
|
|
263
471
|
): EditorWarning[] {
|