@beyondwork/docx-react-component 1.0.86 → 1.0.88
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 +1 -1
- package/src/api/public-types.ts +49 -0
- package/src/api/v3/ui/chrome-composition.ts +2 -11
- package/src/api/v3/ui/chrome.ts +6 -8
- package/src/index.ts +5 -0
- package/src/io/export/serialize-main-document.ts +215 -6
- package/src/io/ooxml/parse-drawing.ts +15 -1
- package/src/io/ooxml/parse-fields.ts +410 -12
- package/src/model/canonical-document.ts +177 -2
- package/src/model/layout/page-layout-snapshot.ts +2 -0
- package/src/model/layout/runtime-page-graph-types.ts +6 -0
- package/src/preservation/store.ts +4 -5
- package/src/runtime/document-outline.ts +80 -0
- package/src/runtime/document-runtime.ts +580 -40
- package/src/runtime/formatting/field/page-number-format.ts +49 -0
- package/src/runtime/formatting/field/resolver.ts +61 -40
- package/src/runtime/layout/layout-engine-instance.ts +18 -1
- package/src/runtime/layout/layout-engine-version.ts +19 -1
- package/src/runtime/layout/measurement-backend-canvas.ts +21 -9
- package/src/runtime/layout/measurement-backend-empirical.ts +18 -4
- package/src/runtime/layout/page-graph.ts +13 -2
- package/src/runtime/layout/paginated-layout-engine.ts +440 -117
- package/src/runtime/layout/project-block-fragments.ts +87 -4
- package/src/runtime/layout/resolve-page-fields.ts +8 -5
- package/src/runtime/layout/table-row-split.ts +97 -23
- package/src/runtime/surface-projection.ts +227 -27
- package/src/shell/session-bootstrap.ts +6 -1
- package/src/ui/WordReviewEditor.tsx +8 -0
- package/src/ui/editor-surface-controller.tsx +1 -0
- package/src/ui/headless/revision-decoration-model.ts +11 -13
- package/src/ui-tailwind/chrome/responsive-chrome.ts +2 -2
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +27 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +57 -6
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +17 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +5 -0
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +34 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +146 -20
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +8 -2
- package/src/ui-tailwind/editor-surface/preserve-position.ts +31 -6
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +20 -5
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +92 -50
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +1 -1
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +32 -3
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +1 -1
- package/src/ui-tailwind/tw-review-workspace.tsx +18 -0
|
@@ -1339,6 +1339,9 @@ export type PreserveOnlyFieldFamily =
|
|
|
1339
1339
|
| "SEQ"
|
|
1340
1340
|
| "INDEX"
|
|
1341
1341
|
| "TC"
|
|
1342
|
+
| "FORMTEXT"
|
|
1343
|
+
| "FORMCHECKBOX"
|
|
1344
|
+
| "FORMDROPDOWN"
|
|
1342
1345
|
| "FORMULA"
|
|
1343
1346
|
| "UNKNOWN";
|
|
1344
1347
|
|
|
@@ -1424,6 +1427,8 @@ export interface FieldRegistry {
|
|
|
1424
1427
|
supported: FieldRegistryEntry[];
|
|
1425
1428
|
/** Preserve-only field instances cataloged for round-trip safety. */
|
|
1426
1429
|
preserveOnly: FieldRegistryEntry[];
|
|
1430
|
+
/** First-class TOC regions, including cached field-result rows and generated heading rows. */
|
|
1431
|
+
tocRegions?: TocRegion[];
|
|
1427
1432
|
/** Generated TOC structure extracted from heading-driven TOC fields. */
|
|
1428
1433
|
tocStructure?: TocStructure;
|
|
1429
1434
|
}
|
|
@@ -1464,6 +1469,78 @@ export interface TocStructure {
|
|
|
1464
1469
|
status: "current" | "stale";
|
|
1465
1470
|
}
|
|
1466
1471
|
|
|
1472
|
+
export interface TocInstructionModel {
|
|
1473
|
+
/** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
|
|
1474
|
+
raw: string;
|
|
1475
|
+
/** Heading level range selected by \\o, or defaulted to 1-9. */
|
|
1476
|
+
outlineRange: { from: number; to: number };
|
|
1477
|
+
/** \\h — result entries should hyperlink to their anchors. */
|
|
1478
|
+
hyperlink?: boolean;
|
|
1479
|
+
/** \\z — hide tab leader and page numbers in web layout. */
|
|
1480
|
+
hidePageNumbersInWeb?: boolean;
|
|
1481
|
+
/** \\u — include paragraphs with outline levels. */
|
|
1482
|
+
useOutlineLevels?: boolean;
|
|
1483
|
+
/** \\t "Style,Level,..." — style-driven TOC mapping. */
|
|
1484
|
+
styleMap?: TocStyleMapEntry[];
|
|
1485
|
+
/** \\b bookmark — bound source content to a bookmark. */
|
|
1486
|
+
bookmarkName?: string;
|
|
1487
|
+
/** \\c identifier — table-of-figures / sequence identifier. */
|
|
1488
|
+
sequenceIdentifier?: string;
|
|
1489
|
+
/** \\f identifier — TC-entry identifier. */
|
|
1490
|
+
tcIdentifier?: string;
|
|
1491
|
+
/** \\p separator — custom entry/page separator. */
|
|
1492
|
+
entryPageSeparator?: string;
|
|
1493
|
+
/** \\d separator — custom sequence separator. */
|
|
1494
|
+
sequenceSeparator?: string;
|
|
1495
|
+
/** \\n optional range — omit page numbers. */
|
|
1496
|
+
omitPageNumbers?: true | { from: number; to: number };
|
|
1497
|
+
/** Switches parsed but not yet semantically implemented. */
|
|
1498
|
+
unsupportedSwitches?: string[];
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
export interface TocStyleMapEntry {
|
|
1502
|
+
styleName: string;
|
|
1503
|
+
level: number;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
export interface TocCachedEntry {
|
|
1507
|
+
/** Text extracted from the cached field-result row before the page-number tab. */
|
|
1508
|
+
text: string;
|
|
1509
|
+
/** TOC level inferred from TOC1/TOC2/etc. paragraph style. */
|
|
1510
|
+
level: number;
|
|
1511
|
+
/** Paragraph index of the cached result row in document order. */
|
|
1512
|
+
paragraphIndex: number;
|
|
1513
|
+
/** Style ID of the cached result paragraph, if available. */
|
|
1514
|
+
styleId?: string;
|
|
1515
|
+
/** Bookmark/hyperlink anchor used by the cached result row, if present. */
|
|
1516
|
+
bookmarkName?: string;
|
|
1517
|
+
/** Page label extracted from the cached field result. */
|
|
1518
|
+
pageText?: string;
|
|
1519
|
+
/** Full visible row text, including tabs/page labels. */
|
|
1520
|
+
displayText: string;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
export interface TocRegion {
|
|
1524
|
+
/** Stable document-order id for this imported TOC region. */
|
|
1525
|
+
tocId: string;
|
|
1526
|
+
/** Source TOC field index in FieldRegistry.supported. */
|
|
1527
|
+
sourceFieldIndex: number;
|
|
1528
|
+
/** Parsed instruction metadata and switches. */
|
|
1529
|
+
instruction: TocInstructionModel;
|
|
1530
|
+
/** Inclusive paragraph range containing the cached/generated result rows. */
|
|
1531
|
+
resultRange: { fromParagraphIndex: number; toParagraphIndex: number };
|
|
1532
|
+
/** Parent container kind where the region was found. */
|
|
1533
|
+
parentKind: "body" | "sdt" | "custom_xml" | "table_cell";
|
|
1534
|
+
/** Structural path to the parent block sequence, for diagnostics. */
|
|
1535
|
+
sourcePath: string;
|
|
1536
|
+
/** Cached rows imported from the DOCX field result. */
|
|
1537
|
+
cachedEntries: TocCachedEntry[];
|
|
1538
|
+
/** Heading-derived rows generated from the canonical document. */
|
|
1539
|
+
generatedEntries: TocEntry[];
|
|
1540
|
+
/** Whether cached entries match the generated heading model. */
|
|
1541
|
+
status: "current" | "stale";
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1467
1544
|
export interface TocEntry {
|
|
1468
1545
|
/** Heading text. */
|
|
1469
1546
|
text: string;
|
|
@@ -2009,8 +2086,8 @@ export interface DrawingFrameNode {
|
|
|
2009
2086
|
content:
|
|
2010
2087
|
| PictureContent
|
|
2011
2088
|
| ShapeContent
|
|
2012
|
-
|
|
|
2013
|
-
|
|
|
2089
|
+
| ChartPreviewNode
|
|
2090
|
+
| SmartArtPreviewNode
|
|
2014
2091
|
| { type: "opaque"; rawXml: string };
|
|
2015
2092
|
}
|
|
2016
2093
|
|
|
@@ -4069,6 +4146,9 @@ const PRESERVE_ONLY_FIELD_FAMILIES: ReadonlySet<PreserveOnlyFieldFamily> = new S
|
|
|
4069
4146
|
"SEQ",
|
|
4070
4147
|
"INDEX",
|
|
4071
4148
|
"TC",
|
|
4149
|
+
"FORMTEXT",
|
|
4150
|
+
"FORMCHECKBOX",
|
|
4151
|
+
"FORMDROPDOWN",
|
|
4072
4152
|
"FORMULA",
|
|
4073
4153
|
"UNKNOWN",
|
|
4074
4154
|
]);
|
|
@@ -4123,6 +4203,16 @@ function validateFieldRegistry(
|
|
|
4123
4203
|
if (record.tocStructure !== undefined) {
|
|
4124
4204
|
validateTocStructure(record.tocStructure, `${path}.tocStructure`, issues);
|
|
4125
4205
|
}
|
|
4206
|
+
|
|
4207
|
+
if (record.tocRegions !== undefined) {
|
|
4208
|
+
if (!Array.isArray(record.tocRegions)) {
|
|
4209
|
+
issues.push({ path: `${path}.tocRegions`, message: "tocRegions must be an array." });
|
|
4210
|
+
} else {
|
|
4211
|
+
record.tocRegions.forEach((region, index) => {
|
|
4212
|
+
validateTocRegion(region, `${path}.tocRegions[${index}]`, issues);
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4126
4216
|
}
|
|
4127
4217
|
|
|
4128
4218
|
function validateFieldRegistryEntry(
|
|
@@ -4265,6 +4355,91 @@ function validateTocStructure(
|
|
|
4265
4355
|
}
|
|
4266
4356
|
}
|
|
4267
4357
|
|
|
4358
|
+
function validateTocRegion(
|
|
4359
|
+
value: unknown,
|
|
4360
|
+
path: string,
|
|
4361
|
+
issues: ModelValidationIssue[],
|
|
4362
|
+
): void {
|
|
4363
|
+
const record = asPlainObject(value, path, issues);
|
|
4364
|
+
if (!record) {
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
expectString(record.tocId, `${path}.tocId`, issues);
|
|
4369
|
+
if (!Number.isInteger(record.sourceFieldIndex) || (record.sourceFieldIndex as number) < 0) {
|
|
4370
|
+
issues.push({
|
|
4371
|
+
path: `${path}.sourceFieldIndex`,
|
|
4372
|
+
message: "sourceFieldIndex must be a non-negative integer.",
|
|
4373
|
+
});
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
const instruction = asPlainObject(record.instruction, `${path}.instruction`, issues);
|
|
4377
|
+
if (instruction) {
|
|
4378
|
+
expectString(instruction.raw, `${path}.instruction.raw`, issues);
|
|
4379
|
+
const outlineRange = asPlainObject(
|
|
4380
|
+
instruction.outlineRange,
|
|
4381
|
+
`${path}.instruction.outlineRange`,
|
|
4382
|
+
issues,
|
|
4383
|
+
);
|
|
4384
|
+
if (outlineRange) {
|
|
4385
|
+
if (!Number.isInteger(outlineRange.from) || (outlineRange.from as number) < 1) {
|
|
4386
|
+
issues.push({
|
|
4387
|
+
path: `${path}.instruction.outlineRange.from`,
|
|
4388
|
+
message: "outlineRange.from must be an integer >= 1.",
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
4391
|
+
if (!Number.isInteger(outlineRange.to) || (outlineRange.to as number) < 1) {
|
|
4392
|
+
issues.push({
|
|
4393
|
+
path: `${path}.instruction.outlineRange.to`,
|
|
4394
|
+
message: "outlineRange.to must be an integer >= 1.",
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
const resultRange = asPlainObject(record.resultRange, `${path}.resultRange`, issues);
|
|
4401
|
+
if (resultRange) {
|
|
4402
|
+
if (!Number.isInteger(resultRange.fromParagraphIndex)) {
|
|
4403
|
+
issues.push({
|
|
4404
|
+
path: `${path}.resultRange.fromParagraphIndex`,
|
|
4405
|
+
message: "fromParagraphIndex must be an integer.",
|
|
4406
|
+
});
|
|
4407
|
+
}
|
|
4408
|
+
if (!Number.isInteger(resultRange.toParagraphIndex)) {
|
|
4409
|
+
issues.push({
|
|
4410
|
+
path: `${path}.resultRange.toParagraphIndex`,
|
|
4411
|
+
message: "toParagraphIndex must be an integer.",
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
if (
|
|
4417
|
+
record.parentKind !== "body" &&
|
|
4418
|
+
record.parentKind !== "sdt" &&
|
|
4419
|
+
record.parentKind !== "custom_xml" &&
|
|
4420
|
+
record.parentKind !== "table_cell"
|
|
4421
|
+
) {
|
|
4422
|
+
issues.push({
|
|
4423
|
+
path: `${path}.parentKind`,
|
|
4424
|
+
message: "parentKind must be body, sdt, custom_xml, or table_cell.",
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
expectString(record.sourcePath, `${path}.sourcePath`, issues);
|
|
4428
|
+
|
|
4429
|
+
if (!Array.isArray(record.cachedEntries)) {
|
|
4430
|
+
issues.push({ path: `${path}.cachedEntries`, message: "cachedEntries must be an array." });
|
|
4431
|
+
}
|
|
4432
|
+
if (!Array.isArray(record.generatedEntries)) {
|
|
4433
|
+
issues.push({ path: `${path}.generatedEntries`, message: "generatedEntries must be an array." });
|
|
4434
|
+
}
|
|
4435
|
+
if (record.status !== "current" && record.status !== "stale") {
|
|
4436
|
+
issues.push({
|
|
4437
|
+
path: `${path}.status`,
|
|
4438
|
+
message: "status must be 'current' or 'stale'.",
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4268
4443
|
function validateCanonicalFontTable(
|
|
4269
4444
|
value: unknown,
|
|
4270
4445
|
path: string,
|
|
@@ -38,6 +38,8 @@ export interface PageLayoutSnapshot {
|
|
|
38
38
|
start?: number;
|
|
39
39
|
chapterStyle?: string;
|
|
40
40
|
chapterSeparator?: string;
|
|
41
|
+
/** Runtime-derived chapter marker for chapter-prefixed PAGE/PAGEREF display. */
|
|
42
|
+
chapterNumber?: string;
|
|
41
43
|
};
|
|
42
44
|
lineNumbering?: {
|
|
43
45
|
countBy?: number;
|
|
@@ -90,6 +90,12 @@ export interface BuildPageGraphInput {
|
|
|
90
90
|
>;
|
|
91
91
|
/** Optional per-page line boxes. */
|
|
92
92
|
lineBoxes?: ReadonlyMap<string, RuntimeLineBox[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Optional per-page line boxes keyed by pageIndex. This mirrors
|
|
95
|
+
* `fragmentsByPageIndex` for callers that cannot know graph-internal
|
|
96
|
+
* `page-${revision}-${index}` ids before `buildPageGraph` runs.
|
|
97
|
+
*/
|
|
98
|
+
lineBoxesByPageIndex?: ReadonlyMap<number, RuntimeLineBox[]>;
|
|
93
99
|
/** Optional per-page note allocations keyed by graph-assigned pageId. */
|
|
94
100
|
noteAllocations?: ReadonlyMap<string, RuntimeNoteAllocation[]>;
|
|
95
101
|
/**
|
|
@@ -163,11 +163,10 @@ export function describeStructuredWrapperBlock(
|
|
|
163
163
|
): OpaqueFragmentDescriptor | null {
|
|
164
164
|
if (block.type === "sdt") {
|
|
165
165
|
if (isTocContentControl(block)) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
166
|
+
// CCEP SOW templates wrap the visible cached TOC result paragraphs in a
|
|
167
|
+
// docPartObj SDT. Collapsing that wrapper hides LibreOffice-visible
|
|
168
|
+
// content, so keep the wrapper in the surface tree and project children.
|
|
169
|
+
return null;
|
|
171
170
|
}
|
|
172
171
|
if (block.properties.sdtType === "docPartObj") {
|
|
173
172
|
// coord-02 §11 P0 — a Template content control that wraps a
|
|
@@ -211,6 +211,62 @@ export function createTocSnapshot(
|
|
|
211
211
|
navigation?: DocumentNavigationSnapshot,
|
|
212
212
|
): TocSnapshot | null {
|
|
213
213
|
const registry = document.fieldRegistry;
|
|
214
|
+
const tocRegions = registry?.tocRegions ?? [];
|
|
215
|
+
const primaryRegion = tocRegions[0];
|
|
216
|
+
if (primaryRegion) {
|
|
217
|
+
const resolvedNavigation =
|
|
218
|
+
navigation ?? createDocumentNavigationSnapshot(document, 0, MAIN_STORY_TARGET);
|
|
219
|
+
const headingRefs = buildHeadingRefs(document, resolvedNavigation);
|
|
220
|
+
const useCachedEntries = primaryRegion.cachedEntries.length > 0;
|
|
221
|
+
const entries: TocEntrySnapshot[] = (useCachedEntries
|
|
222
|
+
? primaryRegion.cachedEntries
|
|
223
|
+
: primaryRegion.generatedEntries
|
|
224
|
+
).map((entry, index) => {
|
|
225
|
+
const pageText = useCachedEntries
|
|
226
|
+
? primaryRegion.cachedEntries[index]?.pageText
|
|
227
|
+
: undefined;
|
|
228
|
+
const heading = useCachedEntries
|
|
229
|
+
? resolveHeadingForCachedTocEntry(entry, headingRefs)
|
|
230
|
+
: resolveHeadingForTocEntry(primaryRegion.generatedEntries[index]!, headingRefs);
|
|
231
|
+
const bookmarkName =
|
|
232
|
+
entry.bookmarkName ??
|
|
233
|
+
headingRefs.find((ref) => ref.heading.headingId === heading?.headingId)?.bookmarkName;
|
|
234
|
+
return {
|
|
235
|
+
tocEntryId: `${primaryRegion.tocId}-entry-${index}`,
|
|
236
|
+
level: entry.level,
|
|
237
|
+
text: entry.text,
|
|
238
|
+
source: useCachedEntries ? "cached" : "generated",
|
|
239
|
+
...(pageText ? { pageText } : {}),
|
|
240
|
+
...(heading
|
|
241
|
+
? {
|
|
242
|
+
pageIndex: heading.pageIndex,
|
|
243
|
+
anchor: createPublicRangeAnchor(heading.offset, heading.offset),
|
|
244
|
+
headingId: heading.headingId,
|
|
245
|
+
}
|
|
246
|
+
: {}),
|
|
247
|
+
...(bookmarkName ? { bookmarkName } : {}),
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
status: primaryRegion.status,
|
|
253
|
+
tocId: primaryRegion.tocId,
|
|
254
|
+
sourceFieldIndex: primaryRegion.sourceFieldIndex,
|
|
255
|
+
instruction: primaryRegion.instruction.raw,
|
|
256
|
+
source: useCachedEntries ? "cached" : "generated",
|
|
257
|
+
regionCount: tocRegions.length,
|
|
258
|
+
regions: tocRegions.map((region) => ({
|
|
259
|
+
tocId: region.tocId,
|
|
260
|
+
status: region.status,
|
|
261
|
+
sourceFieldIndex: region.sourceFieldIndex,
|
|
262
|
+
instruction: region.instruction.raw,
|
|
263
|
+
cachedEntryCount: region.cachedEntries.length,
|
|
264
|
+
generatedEntryCount: region.generatedEntries.length,
|
|
265
|
+
})),
|
|
266
|
+
entries,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
214
270
|
const tocStructure = registry?.tocStructure;
|
|
215
271
|
const fallbackFields = document.fieldRegistry?.supported ?? [];
|
|
216
272
|
const tocFieldIndex = tocStructure
|
|
@@ -331,6 +387,30 @@ function resolveHeadingForTocEntry(
|
|
|
331
387
|
return paragraphMatch?.heading;
|
|
332
388
|
}
|
|
333
389
|
|
|
390
|
+
function resolveHeadingForCachedTocEntry(
|
|
391
|
+
entry: { text: string; bookmarkName?: string },
|
|
392
|
+
headingRefs: ReadonlyArray<{
|
|
393
|
+
heading: DocumentHeadingSnapshot;
|
|
394
|
+
paragraphIndex?: number;
|
|
395
|
+
bookmarkName?: string;
|
|
396
|
+
}>,
|
|
397
|
+
): DocumentHeadingSnapshot | undefined {
|
|
398
|
+
if (entry.bookmarkName) {
|
|
399
|
+
const bookmarkMatch = headingRefs.find((ref) => ref.bookmarkName === entry.bookmarkName);
|
|
400
|
+
if (bookmarkMatch) {
|
|
401
|
+
return bookmarkMatch.heading;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const normalizedEntryText = normalizeTocSnapshotText(entry.text);
|
|
405
|
+
return headingRefs.find(
|
|
406
|
+
(ref) => normalizeTocSnapshotText(ref.heading.text) === normalizedEntryText,
|
|
407
|
+
)?.heading;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function normalizeTocSnapshotText(text: string): string {
|
|
411
|
+
return text.replace(/\s+/gu, " ").trim();
|
|
412
|
+
}
|
|
413
|
+
|
|
334
414
|
function findFirstTocField(blocks: readonly BlockNode[]): Extract<InlineNode, { type: "field" }> | undefined {
|
|
335
415
|
for (const block of blocks) {
|
|
336
416
|
if (block.type === "paragraph") {
|