@stll/folio-core 0.58.5 → 0.59.0
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/dist/ai-edits/apply.d.ts +11 -12
- package/dist/ai-edits/apply.js +179 -168
- package/dist/ai-edits/headless.d.ts +15 -13
- package/dist/ai-edits/headless.js +40 -13
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/newListNumbering.d.ts +3 -9
- package/dist/ai-edits/newListNumbering.js +36 -41
- package/dist/ai-edits/referenceClassification.d.ts +22 -0
- package/dist/ai-edits/referenceClassification.js +21 -0
- package/dist/ai-edits/snapshot.js +39 -40
- package/dist/ai-edits/types.d.ts +24 -32
- package/dist/compare/column-alignment.d.ts +4 -4
- package/dist/compare/compare.d.ts +3 -7
- package/dist/compare/compare.js +25 -27
- package/dist/compare/content-alignment.d.ts +9 -9
- package/dist/compare/content-types.d.ts +23 -10
- package/dist/compare/content-types.js +14 -0
- package/dist/compare/content.d.ts +16 -17
- package/dist/compare/content.js +30 -7
- package/dist/compare/formatting.d.ts +3 -3
- package/dist/compare/import-style-closure.d.ts +22 -0
- package/dist/compare/import-style-closure.js +33 -0
- package/dist/compare/inserted-resource-snapshot.d.ts +15 -0
- package/dist/compare/inserted-resource-snapshot.js +63 -0
- package/dist/compare/plan.js +20 -13
- package/dist/compare/scenario.js +12 -2
- package/dist/compare/style-resources.d.ts +8 -2
- package/dist/compare/style-resources.js +63 -14
- package/dist/compare/verification.d.ts +15 -3
- package/dist/compare/verification.js +47 -8
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/controller/canonicalListRendering.js +13 -22
- package/dist/controller/canonicalPublicOperations.js +1 -1
- package/dist/controller/canonicalStructure.js +45 -21
- package/dist/document-operations.d.ts +4 -9
- package/dist/document-operations.js +45 -41
- package/dist/docx/attributeRemainder.d.ts +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/listMarkerComputation.js +9 -14
- package/dist/docx/listTemplate.d.ts +22 -0
- package/dist/docx/listTemplate.js +40 -0
- package/dist/docx/numberingParser.js +2 -2
- package/dist/docx/numberingReferenceNormalization.js +3 -3
- package/dist/docx/paragraphParser.js +3 -4
- package/dist/docx/rezip.js +2 -2
- package/dist/docx/server/materializeYjsDocx.js +2 -2
- package/dist/docx/server/migrateYjsAttrSchema.js +5 -5
- package/dist/generated/text_shaper.js +8 -4
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/index.d.ts +4 -4
- package/dist/internal/paragraphFormattingSerialization.d.ts +1 -8
- package/dist/internal/paragraphFormattingSerialization.js +6 -20
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +1 -1
- package/dist/layout-bridge/convert/listMarkers.d.ts +1 -2
- package/dist/layout-bridge/convert/listMarkers.js +3 -11
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +1 -1
- package/dist/layout-bridge/convert/paragraphAttrs.js +8 -4
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -1
- package/dist/markdown/renderParagraph.js +29 -3
- package/dist/prosemirror/commands/comments.js +5 -32
- package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +7 -1
- package/dist/prosemirror/commands/paragraphBookmarkJoin.js +35 -1
- package/dist/prosemirror/commands/propertyChangeScope.js +6 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.js +10 -5
- package/dist/prosemirror/conversion/fromProseDoc.js +23 -9
- package/dist/prosemirror/conversion/toProseDoc.js +31 -7
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +29 -14
- package/dist/prosemirror/extensions/features/ListExtension.js +51 -21
- package/dist/prosemirror/listInstanceReferences.js +21 -15
- package/dist/prosemirror/listLabels.d.ts +4 -3
- package/dist/prosemirror/listLabels.js +18 -51
- package/dist/prosemirror/listMarker.d.ts +2 -19
- package/dist/prosemirror/listMarker.js +28 -57
- package/dist/prosemirror/listNumbering.js +16 -6
- package/dist/prosemirror/listRendering.d.ts +4 -3
- package/dist/prosemirror/listRendering.js +22 -3
- package/dist/prosemirror/listRenderingAttrs.d.ts +2 -1
- package/dist/prosemirror/listRenderingAttrs.js +6 -11
- package/dist/prosemirror/numberedRefFields.js +9 -5
- package/dist/prosemirror/numberingAttr.d.ts +19 -1
- package/dist/prosemirror/numberingAttr.js +16 -2
- package/dist/prosemirror/paragraphIndentation.js +5 -2
- package/dist/prosemirror/paragraphMarkJoin.js +17 -3
- package/dist/prosemirror/paragraphPropertyCarry.js +5 -6
- package/dist/prosemirror/plugins/transactionInvariants.js +6 -2
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/prosemirror/selectionState.js +5 -1
- package/dist/prosemirror/storyListNumbering.js +11 -12
- package/dist/prosemirror/styles/paragraphStyleCascade.js +2 -6
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +11 -4
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +28 -18
- package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
- package/dist/prosemirror/yjsDocumentMetadata.js +42 -10
- package/dist/redline.d.ts +17 -1
- package/dist/redline.js +118 -57
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/version-comparison.d.ts +1 -2
- package/dist/version-comparison.js +1 -2
- package/package.json +4 -3
package/dist/compare/content.js
CHANGED
|
@@ -3,8 +3,10 @@ import { outlineLevelFromAttrValue } from "../prosemirror/outlineLevelAttr.js";
|
|
|
3
3
|
import { paragraphIndentationEqual } from "../prosemirror/paragraphIndentation.js";
|
|
4
4
|
import { PARAGRAPH_ALIGNMENT_VALUES } from "../types/documentEnumValues.js";
|
|
5
5
|
import { alignFolioContentStructure, contentBlocksShareContainer, createFolioContentAlignmentWorkSession } from "./content-alignment.js";
|
|
6
|
+
import { sameFolioContentStatedNumbering } from "./content-types.js";
|
|
6
7
|
import { inlineFormattingSegments } from "./formatting.js";
|
|
7
8
|
import { Result, TaggedError, panic } from "better-result";
|
|
9
|
+
import { isNumberingReference } from "@stll/docx-core/model";
|
|
8
10
|
//#region src/compare/content.ts
|
|
9
11
|
/**
|
|
10
12
|
* Pure comparison of two ordered, representation-neutral content snapshots.
|
|
@@ -68,10 +70,29 @@ const limitExceeded = ({ input, limit, maximum, actual, blockIndex, field }) =>
|
|
|
68
70
|
...field !== void 0 && { field }
|
|
69
71
|
});
|
|
70
72
|
const isFiniteInteger = (value) => typeof value === "number" && Number.isSafeInteger(value);
|
|
73
|
+
const isNonNegativeInteger = (value) => isFiniteInteger(value) && value >= 0;
|
|
71
74
|
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
72
75
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
76
|
+
const validStatedNumbering = (value) => {
|
|
77
|
+
if (!isRecord(value) || typeof value["kind"] !== "string") return false;
|
|
78
|
+
switch (value["kind"]) {
|
|
79
|
+
case "inherit":
|
|
80
|
+
case "none": return true;
|
|
81
|
+
case "levelOnly": return isFiniteInteger(value["ilvl"]) && value["ilvl"] >= 0;
|
|
82
|
+
case "reference": return isNonNegativeInteger(value["numId"]) && isNumberingReference(value["numId"]) && (value["ilvl"] === void 0 || isFiniteInteger(value["ilvl"]) && value["ilvl"] >= 0);
|
|
83
|
+
default: return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
73
86
|
const hasRecordShape = (value) => isRecord(value);
|
|
74
|
-
const PARAGRAPH_ALIGNMENTS = new Set(PARAGRAPH_ALIGNMENT_VALUES);
|
|
87
|
+
const PARAGRAPH_ALIGNMENTS$1 = new Set(PARAGRAPH_ALIGNMENT_VALUES);
|
|
88
|
+
const isParagraphContentBlock = (block) => {
|
|
89
|
+
switch (block.kind) {
|
|
90
|
+
case "paragraph":
|
|
91
|
+
case "heading":
|
|
92
|
+
case "listItem": return "statedNumbering" in block;
|
|
93
|
+
default: return false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
75
96
|
const validateRunFormatting = (block, side, blockIndex) => {
|
|
76
97
|
for (const [runIndex, run] of (block.previewRuns ?? []).entries()) {
|
|
77
98
|
if (run.directFormatting !== void 0 && !isRecord(run.directFormatting)) return invalidInput(side, `blocks[${String(blockIndex)}].previewRuns[${String(runIndex)}].directFormatting`, "Direct inline formatting must be an object.", blockIndex);
|
|
@@ -100,8 +121,12 @@ const validateRunFormatting = (block, side, blockIndex) => {
|
|
|
100
121
|
const validateParagraphFormatting = (block, side, blockIndex) => {
|
|
101
122
|
for (const property of ["styleId", "displayLabel"]) if (block[property] !== void 0 && typeof block[property] !== "string") return invalidInput(side, `blocks[${String(blockIndex)}].${property}`, "Optional block labels and style ids must be strings.", blockIndex);
|
|
102
123
|
if (block.headingLevel !== void 0 && (!isFiniteInteger(block.headingLevel) || block.headingLevel < 1)) return invalidInput(side, `blocks[${String(blockIndex)}].headingLevel`, "Heading levels must be positive integers.", blockIndex);
|
|
103
|
-
if (block.
|
|
104
|
-
|
|
124
|
+
if (block.kind === "diagnostic") {
|
|
125
|
+
for (const field of ["statedNumbering", "listReference"]) if (Object.hasOwn(block, field)) return invalidInput(side, `blocks[${String(blockIndex)}].${field}`, "Diagnostics cannot state paragraph numbering.", blockIndex);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (!isParagraphContentBlock(block) || !validStatedNumbering(block.statedNumbering)) return invalidInput(side, `blocks[${String(blockIndex)}].statedNumbering`, "Stated paragraph numbering must be a recognized authored override or inherit.", blockIndex);
|
|
129
|
+
if (block.directAlignment !== void 0 && !PARAGRAPH_ALIGNMENTS$1.has(block.directAlignment)) return invalidInput(side, `blocks[${String(blockIndex)}].directAlignment`, "Paragraph alignment is not recognized.", blockIndex);
|
|
105
130
|
if (block.directOutlineLevel !== void 0 && outlineLevelFromAttrValue(block.directOutlineLevel) === null) return invalidInput(side, `blocks[${String(blockIndex)}].directOutlineLevel`, "Direct outline level is not recognized.", blockIndex);
|
|
106
131
|
const spacing = block.directSpacing;
|
|
107
132
|
if (spacing === void 0) return null;
|
|
@@ -411,13 +436,11 @@ const withTextOffsets = (segments) => {
|
|
|
411
436
|
};
|
|
412
437
|
const paragraphSpacingEqual = (base, revised) => base?.spaceBefore === revised?.spaceBefore && base?.spaceAfter === revised?.spaceAfter && base?.lineSpacing === revised?.lineSpacing && base?.lineSpacingRule === revised?.lineSpacingRule && base?.beforeAutospacing === revised?.beforeAutospacing && base?.afterAutospacing === revised?.afterAutospacing;
|
|
413
438
|
const changedFolioContentParagraphFormatting = (base, revised) => {
|
|
439
|
+
if (!isParagraphContentBlock(base) || !isParagraphContentBlock(revised)) return null;
|
|
414
440
|
const patch = {};
|
|
415
441
|
if ((base.styleId ?? null) !== (revised.styleId ?? null)) patch.styleId = revised.styleId ?? null;
|
|
416
442
|
if (!sameFolioContentOutlineLevel(base.directOutlineLevel, revised.directOutlineLevel)) patch.outlineLevel = revised.directOutlineLevel ?? null;
|
|
417
|
-
|
|
418
|
-
const targetOmitsLevelOnChangedReference = listReferenceChanged && revised.listReference !== void 0 && revised.listLevel === void 0;
|
|
419
|
-
if (base.listLevel !== revised.listLevel || targetOmitsLevelOnChangedReference) patch.listLevel = revised.listLevel ?? null;
|
|
420
|
-
if (listReferenceChanged || base.listLevel !== revised.listLevel && revised.listLevel === void 0 && revised.listReference !== void 0) patch.listReference = revised.listReference ?? null;
|
|
443
|
+
if (!sameFolioContentStatedNumbering(base.statedNumbering, revised.statedNumbering)) patch.numbering = revised.statedNumbering;
|
|
421
444
|
if (base.directAlignment !== revised.directAlignment) patch.alignment = revised.directAlignment ?? null;
|
|
422
445
|
if (!paragraphSpacingEqual(base.directSpacing, revised.directSpacing)) patch.spacing = revised.directSpacing ?? null;
|
|
423
446
|
if (!paragraphIndentationEqual(base.directIndentation, revised.directIndentation)) patch.indentation = revised.directIndentation ?? null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FolioContentBlockIdentity, FolioContentInlineComparisonResult } from "./content-types.js";
|
|
2
2
|
//#region src/compare/formatting.d.ts
|
|
3
3
|
type InlineFormattingSegmentsOptions = {
|
|
4
|
-
baseBlock:
|
|
5
|
-
targetBlock:
|
|
4
|
+
baseBlock: FolioContentBlockIdentity;
|
|
5
|
+
targetBlock: FolioContentBlockIdentity;
|
|
6
6
|
maxSegments: number;
|
|
7
7
|
};
|
|
8
8
|
/** Malformed run streams take precedence over the output budget. */
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
|
+
import { ImportReferencedStyleDefinitionsResult } from "./style-resources.js";
|
|
3
|
+
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
4
|
+
//#region src/compare/import-style-closure.d.ts
|
|
5
|
+
type ImportStyleClosureWithNumberingOptions = {
|
|
6
|
+
destination: FolioDocxReviewer;
|
|
7
|
+
source: FolioDocxReviewer;
|
|
8
|
+
snapshots: readonly FolioAIEditSnapshot[];
|
|
9
|
+
importedHeaderFooterSnapshots: readonly FolioAIEditSnapshot[];
|
|
10
|
+
numberingReferences: readonly {
|
|
11
|
+
numId: number;
|
|
12
|
+
level: number;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
/** Import numbering before styles bind to it, then rebind the source snapshots once. */
|
|
16
|
+
declare const importStyleClosureWithNumbering: ({ destination, source, snapshots, importedHeaderFooterSnapshots, numberingReferences }: ImportStyleClosureWithNumberingOptions) => {
|
|
17
|
+
styleImport: ImportReferencedStyleDefinitionsResult;
|
|
18
|
+
numberingStage: "unchanged" | "staged" | "conflict";
|
|
19
|
+
snapshots: FolioAIEditSnapshot[];
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { importStyleClosureWithNumbering };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getFolioDocxComparisonAccess } from "../ai-edits/headless.js";
|
|
2
|
+
import { remapFolioAIEditSnapshotNumberingReferences, remapFolioAIEditSnapshotStyleReferences } from "../ai-edits/snapshot.js";
|
|
3
|
+
import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
|
|
4
|
+
//#region src/compare/import-style-closure.ts
|
|
5
|
+
/** Import numbering before styles bind to it, then rebind the source snapshots once. */
|
|
6
|
+
const importStyleClosureWithNumbering = ({ destination, source, snapshots, importedHeaderFooterSnapshots, numberingReferences }) => {
|
|
7
|
+
const access = getFolioDocxComparisonAccess(destination);
|
|
8
|
+
const numbering = getFolioDocxComparisonAccess(source).numberingDefinitions();
|
|
9
|
+
const numberingReferenceMap = access.planTargetNumberingReferences(numbering, numberingReferences);
|
|
10
|
+
const numberingStage = numberingReferenceMap === null ? "conflict" : access.stageTargetNumbering(numbering, numberingReferences, numberingReferenceMap);
|
|
11
|
+
const styleImport = access.stageTargetStyles({
|
|
12
|
+
source,
|
|
13
|
+
snapshots,
|
|
14
|
+
importedHeaderFooterSnapshots,
|
|
15
|
+
...numberingReferenceMap !== null && { numberingReferenceMap }
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
styleImport,
|
|
19
|
+
numberingStage,
|
|
20
|
+
snapshots: snapshots.map((snapshot) => {
|
|
21
|
+
const styleRebound = styleImport.status === "unalignable" ? snapshot : remapFolioAIEditSnapshotStyleReferences({
|
|
22
|
+
snapshot,
|
|
23
|
+
styleIdMap: styleImport.styleIdMap,
|
|
24
|
+
defaultParagraphStyleId: styleImport.defaultParagraphStyleId,
|
|
25
|
+
importedStyleResolver: createStyleResolver(styleImport.styles),
|
|
26
|
+
reconcileAuthoredFormatting: true
|
|
27
|
+
});
|
|
28
|
+
return numberingReferenceMap === null ? styleRebound : remapFolioAIEditSnapshotNumberingReferences(styleRebound, numberingReferenceMap);
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { importStyleClosureWithNumbering };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FolioAIBlock, FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
|
+
//#region src/compare/inserted-resource-snapshot.d.ts
|
|
3
|
+
type CreateInsertedResourceProjectionOptions = {
|
|
4
|
+
base: FolioAIEditSnapshot;
|
|
5
|
+
revised: FolioAIEditSnapshot;
|
|
6
|
+
};
|
|
7
|
+
/** Select resource nodes with their identities from the full revised snapshot. */
|
|
8
|
+
declare const createInsertedResourceProjection: ({ base, revised }: CreateInsertedResourceProjectionOptions) => {
|
|
9
|
+
snapshot: FolioAIEditSnapshot;
|
|
10
|
+
revisedBlockIds: string[];
|
|
11
|
+
};
|
|
12
|
+
/** Subset projection IDs never participate in rebinding the full revised snapshot. */
|
|
13
|
+
declare const rebindInsertedResourceBlocks: (projection: ReturnType<typeof createInsertedResourceProjection>, rebound: FolioAIEditSnapshot) => Map<string, FolioAIBlock>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createInsertedResourceProjection, rebindInsertedResourceBlocks };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createFolioAIEditSnapshotWithStyleResolver, sourceDocumentOf, styleResolverOf } from "../ai-edits/snapshot.js";
|
|
2
|
+
import { alignFolioBlocks } from "../version-comparison.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
import { Fragment } from "prosemirror-model";
|
|
5
|
+
//#region src/compare/inserted-resource-snapshot.ts
|
|
6
|
+
/** Select resource nodes with their identities from the full revised snapshot. */
|
|
7
|
+
const createInsertedResourceProjection = ({ base, revised }) => {
|
|
8
|
+
const document = sourceDocumentOf(revised);
|
|
9
|
+
const insertions = alignFolioBlocks(base.blocks, revised.blocks).flatMap((event) => {
|
|
10
|
+
if (event.type === "pair" || event.type === "baseOnly") return [];
|
|
11
|
+
const anchor = revised.anchors[event.block.id] ?? panic("An inserted block lost its source anchor");
|
|
12
|
+
const node = document.nodeAt(anchor.from) ?? panic("An inserted block lost its source paragraph");
|
|
13
|
+
return [{
|
|
14
|
+
from: anchor.from,
|
|
15
|
+
to: anchor.to,
|
|
16
|
+
node
|
|
17
|
+
}];
|
|
18
|
+
}).toSorted((left, right) => left.from - right.from);
|
|
19
|
+
const carriers = [];
|
|
20
|
+
let projectionFrom = 0;
|
|
21
|
+
for (const insertion of insertions) {
|
|
22
|
+
if (insertion.from < (carriers.at(-1)?.to ?? -1)) continue;
|
|
23
|
+
carriers.push({
|
|
24
|
+
...insertion,
|
|
25
|
+
projectionFrom
|
|
26
|
+
});
|
|
27
|
+
projectionFrom += insertion.node.nodeSize;
|
|
28
|
+
}
|
|
29
|
+
const revisedIdsByProjectionPosition = /* @__PURE__ */ new Map();
|
|
30
|
+
let carrierIndex = 0;
|
|
31
|
+
for (const block of revised.blocks) {
|
|
32
|
+
const anchor = revised.anchors[block.id];
|
|
33
|
+
if (anchor === void 0) continue;
|
|
34
|
+
while (anchor.from >= (carriers.at(carrierIndex)?.to ?? Number.POSITIVE_INFINITY)) carrierIndex++;
|
|
35
|
+
const carrier = carriers.at(carrierIndex);
|
|
36
|
+
if (carrier === void 0 || anchor.from < carrier.from || anchor.to > carrier.to) continue;
|
|
37
|
+
revisedIdsByProjectionPosition.set(carrier.projectionFrom + anchor.from - carrier.from, block.id);
|
|
38
|
+
}
|
|
39
|
+
const snapshot = createFolioAIEditSnapshotWithStyleResolver(document.copy(Fragment.from(carriers.map(({ node }) => node))), styleResolverOf(revised));
|
|
40
|
+
if (snapshot.blocks.length !== revisedIdsByProjectionPosition.size) return panic("A redline resource projection must preserve its maximal carriers' blocks");
|
|
41
|
+
return {
|
|
42
|
+
snapshot,
|
|
43
|
+
revisedBlockIds: snapshot.blocks.map((block) => {
|
|
44
|
+
const anchor = snapshot.anchors[block.id] ?? panic("A projected resource block lost its anchor");
|
|
45
|
+
return revisedIdsByProjectionPosition.get(anchor.from) ?? panic("A projected resource block lost its carried source identity");
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/** Subset projection IDs never participate in rebinding the full revised snapshot. */
|
|
50
|
+
const rebindInsertedResourceBlocks = (projection, rebound) => {
|
|
51
|
+
if (rebound.blocks.length !== projection.revisedBlockIds.length) return panic("A redline resource import changed the carried insertion count");
|
|
52
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const [index, id] of projection.revisedBlockIds.entries()) {
|
|
54
|
+
const block = rebound.blocks.at(index) ?? panic("A redline import lost a carried insertion");
|
|
55
|
+
blocks.set(id, {
|
|
56
|
+
...block,
|
|
57
|
+
id
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return blocks;
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { createInsertedResourceProjection, rebindInsertedResourceBlocks };
|
package/dist/compare/plan.js
CHANGED
|
@@ -224,8 +224,7 @@ const shareAContainer = (left, right) => {
|
|
|
224
224
|
const toFolioAIBlockParagraphProperties = (properties) => ({
|
|
225
225
|
...properties.styleId !== void 0 && { styleId: properties.styleId },
|
|
226
226
|
...properties.outlineLevel !== void 0 && { outlineLevel: properties.outlineLevel },
|
|
227
|
-
...properties.
|
|
228
|
-
...properties.listReference !== void 0 && { numbering: properties.listReference },
|
|
227
|
+
...properties.numbering !== void 0 && { numbering: properties.numbering },
|
|
229
228
|
...properties.alignment !== void 0 && { alignment: properties.alignment },
|
|
230
229
|
...properties.spacing !== void 0 && { spacing: properties.spacing },
|
|
231
230
|
...properties.indentation !== void 0 && { indentation: properties.indentation }
|
|
@@ -489,8 +488,7 @@ const withTrailingDeletionRules = ({ baseSnapshot, targetSnapshot, operations, t
|
|
|
489
488
|
text: insert.text,
|
|
490
489
|
...insert.moveId !== void 0 && { moveId: insert.moveId },
|
|
491
490
|
styleId: insert.styleId ?? null,
|
|
492
|
-
|
|
493
|
-
numbering: insert.numbering ?? null,
|
|
491
|
+
...insert.numbering !== void 0 && { numbering: insert.numbering },
|
|
494
492
|
indentation: insert.indentation ?? null,
|
|
495
493
|
...insert.lineBreakMode !== void 0 && { lineBreakMode: insert.lineBreakMode },
|
|
496
494
|
...insert.hardPageBreak !== void 0 && { hardPageBreak: insert.hardPageBreak },
|
|
@@ -592,17 +590,26 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
|
|
|
592
590
|
*/
|
|
593
591
|
const moveIdOf = (baseBlockId) => `move-${baseBlockId}`;
|
|
594
592
|
const pushInsertOperation = (block, anchorId) => {
|
|
593
|
+
let paragraph;
|
|
594
|
+
switch (block.kind) {
|
|
595
|
+
case "paragraph":
|
|
596
|
+
case "heading":
|
|
597
|
+
case "listItem":
|
|
598
|
+
paragraph = block;
|
|
599
|
+
break;
|
|
600
|
+
case "diagnostic": return;
|
|
601
|
+
default: return panic("Unhandled comparison block kind", { block });
|
|
602
|
+
}
|
|
595
603
|
const moveSourceId = moveSourceByTargetBlockId.get(block.id);
|
|
596
|
-
const structuralBoundary =
|
|
597
|
-
const hardPageBreak = block.text.length === 0 &&
|
|
604
|
+
const structuralBoundary = paragraph.structuralBoundaries?.at(0);
|
|
605
|
+
const hardPageBreak = block.text.length === 0 && paragraph.structuralBoundaries?.length === 1 && structuralBoundary?.type === "pageBreak" && structuralBoundary.offset === 0 ? { ...structuralBoundary.clear !== void 0 && { clear: structuralBoundary.clear } } : void 0;
|
|
598
606
|
const paragraphProperties = {
|
|
599
|
-
styleId:
|
|
600
|
-
outlineLevel:
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
indentation: block.directIndentation ?? null
|
|
607
|
+
styleId: paragraph.styleId ?? null,
|
|
608
|
+
outlineLevel: paragraph.directOutlineLevel ?? null,
|
|
609
|
+
numbering: paragraph.statedNumbering,
|
|
610
|
+
alignment: paragraph.directAlignment ?? null,
|
|
611
|
+
spacing: paragraph.directSpacing ?? null,
|
|
612
|
+
indentation: paragraph.directIndentation ?? null
|
|
606
613
|
};
|
|
607
614
|
const shared = {
|
|
608
615
|
text: block.text,
|
package/dist/compare/scenario.js
CHANGED
|
@@ -38,6 +38,15 @@ const EDIT_SCRIPT_UNRESOLVED_REASONS = Object.freeze([
|
|
|
38
38
|
"refused"
|
|
39
39
|
]);
|
|
40
40
|
const blockAt = (blocks, index) => Number.isInteger(index) && index >= 0 ? blocks.at(index) : void 0;
|
|
41
|
+
const statedNumberingOf = (block) => {
|
|
42
|
+
switch (block.kind) {
|
|
43
|
+
case "paragraph":
|
|
44
|
+
case "heading":
|
|
45
|
+
case "listItem": return block.statedNumbering;
|
|
46
|
+
case "diagnostic": return;
|
|
47
|
+
default: return panic("Unhandled scenario block kind", { block });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
41
50
|
const planStep = ({ step, blocks, nextOperationId }) => {
|
|
42
51
|
const block = blockAt(blocks, step.blockIndex);
|
|
43
52
|
if (!block) return {
|
|
@@ -81,6 +90,7 @@ const planStep = ({ step, blocks, nextOperationId }) => {
|
|
|
81
90
|
status: "unresolved",
|
|
82
91
|
reason: "block-out-of-range"
|
|
83
92
|
};
|
|
93
|
+
const statedNumbering = statedNumberingOf(block);
|
|
84
94
|
return {
|
|
85
95
|
status: "planned",
|
|
86
96
|
operations: [{
|
|
@@ -93,7 +103,7 @@ const planStep = ({ step, blocks, nextOperationId }) => {
|
|
|
93
103
|
blockId: destination.id,
|
|
94
104
|
text: block.text,
|
|
95
105
|
styleId: block.styleId ?? null,
|
|
96
|
-
|
|
106
|
+
...statedNumbering !== void 0 && { numbering: statedNumbering }
|
|
97
107
|
}]
|
|
98
108
|
};
|
|
99
109
|
}
|
|
@@ -243,7 +253,7 @@ const applyEditScript = async (base, script) => {
|
|
|
243
253
|
operations: planned.flatMap(({ operations }) => [...operations])
|
|
244
254
|
},
|
|
245
255
|
snapshot,
|
|
246
|
-
|
|
256
|
+
undefinedReferences: "keep"
|
|
247
257
|
});
|
|
248
258
|
const skippedIds = new Set(skipped.map(({ id }) => id));
|
|
249
259
|
const applied = [];
|
|
@@ -8,6 +8,7 @@ type ImportReferencedStyleDefinitionsOptions = {
|
|
|
8
8
|
referencedStyleIds: readonly string[];
|
|
9
9
|
reservedStyleIds?: readonly string[];
|
|
10
10
|
materializeDefaultParagraphStyle?: boolean;
|
|
11
|
+
numberingReferenceMap?: ReadonlyMap<number, number>;
|
|
11
12
|
};
|
|
12
13
|
type ImportReferencedStyleDefinitionsResult = {
|
|
13
14
|
status: "unchanged" | "imported";
|
|
@@ -19,12 +20,17 @@ type ImportReferencedStyleDefinitionsResult = {
|
|
|
19
20
|
status: "unalignable";
|
|
20
21
|
detail: string;
|
|
21
22
|
};
|
|
23
|
+
/** Numbering resources in the same transitive closure the style importer owns. */
|
|
24
|
+
declare const styleClosureNumberingReferences: (styles: document_d_exports.StyleDefinitions | undefined, referencedStyleIds: readonly string[]) => {
|
|
25
|
+
numId: number;
|
|
26
|
+
level: number;
|
|
27
|
+
}[];
|
|
22
28
|
/**
|
|
23
29
|
* Add the target style definitions required by a comparison without changing
|
|
24
30
|
* existing package resources. A referenced style is only portable when both
|
|
25
31
|
* packages have the same document defaults and theme: those resources take
|
|
26
32
|
* part in OOXML style resolution even when a style definition itself is new.
|
|
27
33
|
*/
|
|
28
|
-
declare const importReferencedStyleDefinitions: ({ sourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds, materializeDefaultParagraphStyle }: ImportReferencedStyleDefinitionsOptions) => ImportReferencedStyleDefinitionsResult;
|
|
34
|
+
declare const importReferencedStyleDefinitions: ({ sourceStyles: originalSourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds, materializeDefaultParagraphStyle, numberingReferenceMap }: ImportReferencedStyleDefinitionsOptions) => ImportReferencedStyleDefinitionsResult;
|
|
29
35
|
//#endregion
|
|
30
|
-
export { ImportReferencedStyleDefinitionsOptions, ImportReferencedStyleDefinitionsResult, importReferencedStyleDefinitions };
|
|
36
|
+
export { ImportReferencedStyleDefinitionsOptions, ImportReferencedStyleDefinitionsResult, importReferencedStyleDefinitions, styleClosureNumberingReferences };
|
|
@@ -2,12 +2,34 @@ import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
|
|
|
2
2
|
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
3
3
|
import { resolveColorToHex } from "../utils/colorResolver.js";
|
|
4
4
|
import { resolveThemeFont } from "../utils/fontResolver.js";
|
|
5
|
+
import { paragraphNumberingReference } from "@stll/docx-core/model";
|
|
5
6
|
//#region src/compare/style-resources.ts
|
|
6
7
|
const styleDependencies = ({ basedOn, link, next }) => [
|
|
7
8
|
basedOn,
|
|
8
9
|
link,
|
|
9
10
|
next
|
|
10
11
|
].filter((styleId) => styleId !== void 0);
|
|
12
|
+
/** Numbering resources in the same transitive closure the style importer owns. */
|
|
13
|
+
const styleClosureNumberingReferences = (styles, referencedStyleIds) => {
|
|
14
|
+
const definitions = new Map(styles?.styles.map((style) => [style.styleId, style]));
|
|
15
|
+
const pending = [...referencedStyleIds];
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
const references = [];
|
|
18
|
+
while (pending.length > 0) {
|
|
19
|
+
const id = pending.pop();
|
|
20
|
+
if (id === void 0 || seen.has(id)) continue;
|
|
21
|
+
seen.add(id);
|
|
22
|
+
const style = definitions.get(id);
|
|
23
|
+
if (style === void 0) continue;
|
|
24
|
+
const numbering = style.pPr?.numPr;
|
|
25
|
+
if (numbering?.kind === "reference") references.push({
|
|
26
|
+
numId: numbering.numId,
|
|
27
|
+
level: numbering.ilvl ?? 0
|
|
28
|
+
});
|
|
29
|
+
pending.push(...styleDependencies(style));
|
|
30
|
+
}
|
|
31
|
+
return references;
|
|
32
|
+
};
|
|
11
33
|
const themesMatch = (sourceTheme, destinationTheme) => canonicalJson(sourceTheme) === canonicalJson(destinationTheme);
|
|
12
34
|
const hasThemeReference = (value) => {
|
|
13
35
|
if (Array.isArray(value)) return value.some(hasThemeReference);
|
|
@@ -205,7 +227,25 @@ const remapImportedStyle = ({ style, styleIdMap, isolatedDefaultStyleIds }) => {
|
|
|
205
227
|
* packages have the same document defaults and theme: those resources take
|
|
206
228
|
* part in OOXML style resolution even when a style definition itself is new.
|
|
207
229
|
*/
|
|
208
|
-
const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds = [], materializeDefaultParagraphStyle = false }) => {
|
|
230
|
+
const importReferencedStyleDefinitions = ({ sourceStyles: originalSourceStyles, destinationStyles, sourceTheme, destinationTheme, referencedStyleIds, reservedStyleIds = [], materializeDefaultParagraphStyle = false, numberingReferenceMap }) => {
|
|
231
|
+
const sourceStyles = originalSourceStyles && numberingReferenceMap && numberingReferenceMap.size > 0 ? {
|
|
232
|
+
...originalSourceStyles,
|
|
233
|
+
styles: originalSourceStyles.styles.map((style) => {
|
|
234
|
+
const numbering = style.pPr?.numPr;
|
|
235
|
+
const numId = numbering?.kind === "reference" ? numberingReferenceMap.get(numbering.numId) : void 0;
|
|
236
|
+
if (numId === void 0 || numbering?.kind !== "reference") return style;
|
|
237
|
+
return {
|
|
238
|
+
...style,
|
|
239
|
+
pPr: {
|
|
240
|
+
...style.pPr,
|
|
241
|
+
numPr: paragraphNumberingReference({
|
|
242
|
+
numId,
|
|
243
|
+
...numbering.ilvl !== void 0 && { ilvl: numbering.ilvl }
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
})
|
|
248
|
+
} : originalSourceStyles;
|
|
209
249
|
const requested = [...new Set(referencedStyleIds)].toSorted();
|
|
210
250
|
if (requested.length === 0 && !materializeDefaultParagraphStyle) return {
|
|
211
251
|
status: "unchanged",
|
|
@@ -238,7 +278,7 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
238
278
|
const source = sourceById.get(styleId);
|
|
239
279
|
if (!source) return {
|
|
240
280
|
status: "unalignable",
|
|
241
|
-
detail:
|
|
281
|
+
detail: `referenced target style ${styleId} is missing`
|
|
242
282
|
};
|
|
243
283
|
required.add(styleId);
|
|
244
284
|
pending.push(...styleDependencies(source));
|
|
@@ -257,7 +297,7 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
257
297
|
const destination = destinationById.get(styleId);
|
|
258
298
|
if (!source) return {
|
|
259
299
|
status: "unalignable",
|
|
260
|
-
detail:
|
|
300
|
+
detail: `referenced target style ${styleId} is missing`
|
|
261
301
|
};
|
|
262
302
|
if (destination !== void 0 && canonicalJson(source) !== canonicalJson(destination) || !destination && (reserved.has(styleId) || source.default)) {
|
|
263
303
|
const alias = nextAvailableStyleAlias({
|
|
@@ -275,7 +315,14 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
275
315
|
const contextMatches = canonicalJson(sourceStyles.docDefaults) === canonicalJson(destinationStyles.docDefaults) && themesMatch(sourceTheme, destinationTheme);
|
|
276
316
|
const materializeThemeReferences = !themesMatch(sourceTheme, destinationTheme);
|
|
277
317
|
const additions = sourceStyles.styles.filter(({ styleId }) => required.has(styleId) && (!destinationById.has(styleId) || styleIdMap.get(styleId) !== styleId));
|
|
278
|
-
|
|
318
|
+
const needsDefaultParagraphStyle = materializeDefaultParagraphStyle && !effectiveDefaultParagraphFormattingMatches({
|
|
319
|
+
sourceStyles,
|
|
320
|
+
destinationStyles,
|
|
321
|
+
sourceTheme,
|
|
322
|
+
destinationTheme,
|
|
323
|
+
materializeThemeReferences
|
|
324
|
+
});
|
|
325
|
+
if (contextMatches && !needsDefaultParagraphStyle) {
|
|
279
326
|
if (additions.length === 0) return {
|
|
280
327
|
status: "unchanged",
|
|
281
328
|
styles: destinationStyles,
|
|
@@ -298,6 +345,14 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
298
345
|
}
|
|
299
346
|
const materializedStyles = [];
|
|
300
347
|
for (const style of additions) {
|
|
348
|
+
if (contextMatches) {
|
|
349
|
+
materializedStyles.push(remapImportedStyle({
|
|
350
|
+
style,
|
|
351
|
+
styleIdMap,
|
|
352
|
+
isolatedDefaultStyleIds
|
|
353
|
+
}));
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
301
356
|
const result = materializeEffectiveStyle({
|
|
302
357
|
style,
|
|
303
358
|
styles: sourceStyles,
|
|
@@ -306,7 +361,7 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
306
361
|
});
|
|
307
362
|
if (result.status === "unalignable") return {
|
|
308
363
|
status: "unalignable",
|
|
309
|
-
detail:
|
|
364
|
+
detail: `referenced style ${style.styleId} has formatting that cannot be materialized`
|
|
310
365
|
};
|
|
311
366
|
materializedStyles.push(remapImportedStyle({
|
|
312
367
|
style: result.style,
|
|
@@ -316,13 +371,7 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
316
371
|
}
|
|
317
372
|
let defaultParagraphStyleId;
|
|
318
373
|
let materializedDefaultParagraphStyle;
|
|
319
|
-
if (
|
|
320
|
-
sourceStyles,
|
|
321
|
-
destinationStyles,
|
|
322
|
-
sourceTheme,
|
|
323
|
-
destinationTheme,
|
|
324
|
-
materializeThemeReferences
|
|
325
|
-
})) {
|
|
374
|
+
if (needsDefaultParagraphStyle) {
|
|
326
375
|
defaultParagraphStyleId = nextAvailableStyleAlias({
|
|
327
376
|
taken,
|
|
328
377
|
sequence: aliasSequence
|
|
@@ -373,7 +422,7 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
373
422
|
rPr: candidateRunFormatting.formatting
|
|
374
423
|
})) return {
|
|
375
424
|
status: "unalignable",
|
|
376
|
-
detail:
|
|
425
|
+
detail: `referenced style ${style.styleId} differs after materializing its formatting`
|
|
377
426
|
};
|
|
378
427
|
}
|
|
379
428
|
if (defaultParagraphStyleId !== void 0) {
|
|
@@ -407,4 +456,4 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
|
|
|
407
456
|
};
|
|
408
457
|
};
|
|
409
458
|
//#endregion
|
|
410
|
-
export { importReferencedStyleDefinitions };
|
|
459
|
+
export { importReferencedStyleDefinitions, styleClosureNumberingReferences };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FolioAIDiagnosticBlock, FolioAIParagraphBlock } from "../ai-edits/types.js";
|
|
2
2
|
import { FolioDocumentStoryHandle } from "../ai-edits/headless.js";
|
|
3
3
|
import { ParagraphMarkChangeKind } from "@stll/docx-core/model";
|
|
4
4
|
//#region src/compare/verification.d.ts
|
|
@@ -10,7 +10,7 @@ type CompareVerificationInvariant = (typeof COMPARE_VERIFICATION_INVARIANTS)[num
|
|
|
10
10
|
* that fits is the one reported, because a block in the wrong container is a
|
|
11
11
|
* different finding from a block whose words are wrong even when both are true.
|
|
12
12
|
*/
|
|
13
|
-
declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "inline-structure", "table-geometry", "section-properties", "style", "list-level", "alignment", "spacing", "indentation", "inline-formatting", "whitespace", "text"];
|
|
13
|
+
declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "inline-structure", "table-geometry", "section-properties", "style", "list-level", "numbering-source", "alignment", "spacing", "indentation", "inline-formatting", "whitespace", "text"];
|
|
14
14
|
type CompareVerificationCause = (typeof COMPARE_VERIFICATION_CAUSES)[number];
|
|
15
15
|
/** One invariant that did not hold, in one story. */
|
|
16
16
|
type CompareVerificationFailure = {
|
|
@@ -33,7 +33,19 @@ type CompareVerification = {
|
|
|
33
33
|
status: "unverified";
|
|
34
34
|
failures: readonly CompareVerificationFailure[];
|
|
35
35
|
};
|
|
36
|
-
type
|
|
36
|
+
type ProjectedBlockFields = Omit<Pick<FolioAIParagraphBlock, "kind" | "headingLevel" | "text" | "table" | "styleId">, "kind">;
|
|
37
|
+
type ProjectedBlock = ProjectedBlockFields & ({
|
|
38
|
+
kind: FolioAIDiagnosticBlock["kind"];
|
|
39
|
+
} | {
|
|
40
|
+
kind: FolioAIParagraphBlock["kind"];
|
|
41
|
+
statedNumbering: FolioAIParagraphBlock["statedNumbering"];
|
|
42
|
+
listReference?: FolioAIParagraphBlock["listReference"];
|
|
43
|
+
structuralBoundaries?: FolioAIParagraphBlock["structuralBoundaries"];
|
|
44
|
+
directOutlineLevel?: FolioAIParagraphBlock["directOutlineLevel"];
|
|
45
|
+
directAlignment?: FolioAIParagraphBlock["directAlignment"];
|
|
46
|
+
directSpacing?: FolioAIParagraphBlock["directSpacing"];
|
|
47
|
+
directIndentation?: FolioAIParagraphBlock["directIndentation"];
|
|
48
|
+
});
|
|
37
49
|
type ClassifyOptions<T> = {
|
|
38
50
|
invariant: CompareVerificationInvariant;
|
|
39
51
|
story: FolioDocumentStoryHandle;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { paragraphIndentationEqual } from "../prosemirror/paragraphIndentation.js";
|
|
2
2
|
import { paragraphSpacingEqual } from "../prosemirror/paragraphSpacing.js";
|
|
3
|
+
import { sameFolioContentStatedNumbering } from "./content-types.js";
|
|
3
4
|
import { sameFolioContentOutlineLevel } from "./content.js";
|
|
5
|
+
import { panic } from "better-result";
|
|
4
6
|
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
5
7
|
//#region src/compare/verification.ts
|
|
6
8
|
/**
|
|
@@ -38,6 +40,7 @@ const COMPARE_VERIFICATION_CAUSES = Object.freeze([
|
|
|
38
40
|
"section-properties",
|
|
39
41
|
"style",
|
|
40
42
|
"list-level",
|
|
43
|
+
"numbering-source",
|
|
41
44
|
"alignment",
|
|
42
45
|
"spacing",
|
|
43
46
|
"indentation",
|
|
@@ -65,7 +68,43 @@ const sameStructuralBoundaries = (left, right) => {
|
|
|
65
68
|
return true;
|
|
66
69
|
};
|
|
67
70
|
const sameClassification = (left, right) => left.kind === right.kind && left.headingLevel === right.headingLevel;
|
|
68
|
-
const
|
|
71
|
+
const sameProjectedNumbering = (left, right) => {
|
|
72
|
+
switch (left.kind) {
|
|
73
|
+
case "diagnostic": return right.kind === "diagnostic";
|
|
74
|
+
case "paragraph":
|
|
75
|
+
case "heading":
|
|
76
|
+
case "listItem": return right.kind !== "diagnostic" && sameFolioContentStatedNumbering(left.statedNumbering, right.statedNumbering);
|
|
77
|
+
default: return panic("Unhandled projected block kind", { block: left });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const sameProjectedListReference = (left, right) => {
|
|
81
|
+
switch (left.kind) {
|
|
82
|
+
case "diagnostic": return right.kind === "diagnostic";
|
|
83
|
+
case "paragraph":
|
|
84
|
+
case "heading":
|
|
85
|
+
case "listItem": return right.kind !== "diagnostic" && left.listReference?.numId === right.listReference?.numId && left.listReference?.level === right.listReference?.level;
|
|
86
|
+
default: return panic("Unhandled projected block kind", { block: left });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const sameProjectedStructuralBoundaries = (left, right) => {
|
|
90
|
+
switch (left.kind) {
|
|
91
|
+
case "diagnostic": return right.kind === "diagnostic";
|
|
92
|
+
case "paragraph":
|
|
93
|
+
case "heading":
|
|
94
|
+
case "listItem": return right.kind !== "diagnostic" && sameStructuralBoundaries(left.structuralBoundaries, right.structuralBoundaries);
|
|
95
|
+
default: return panic("Unhandled projected block kind", { block: left });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const sameProjectedParagraphFormatting = (left, right, compare) => {
|
|
99
|
+
switch (left.kind) {
|
|
100
|
+
case "diagnostic": return right.kind === "diagnostic";
|
|
101
|
+
case "paragraph":
|
|
102
|
+
case "heading":
|
|
103
|
+
case "listItem": return right.kind !== "diagnostic" && compare(left, right);
|
|
104
|
+
default: return panic("Unhandled projected block kind", { block: left });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const sameProjectedBlock = (left, right) => sameContainer(left.table, right.table) && sameClassification(left, right) && sameProjectedStructuralBoundaries(left, right) && left.styleId === right.styleId && sameProjectedParagraphFormatting(left, right, (a, b) => sameFolioContentOutlineLevel(a.directOutlineLevel, b.directOutlineLevel)) && sameProjectedListReference(left, right) && sameProjectedNumbering(left, right) && sameProjectedParagraphFormatting(left, right, (a, b) => a.directAlignment === b.directAlignment) && sameProjectedParagraphFormatting(left, right, (a, b) => paragraphSpacingEqual(a.directSpacing, b.directSpacing)) && sameProjectedParagraphFormatting(left, right, (a, b) => paragraphIndentationEqual(a.directIndentation, b.directIndentation)) && left.text === right.text;
|
|
69
108
|
const containerKind = (container) => container ? "cell" : "body";
|
|
70
109
|
const collapseWhitespace = (text) => text.replace(/\s+/gu, " ").trim();
|
|
71
110
|
const sameBlockProjection = (left, right) => left.length === right.length && left.every((entry, index) => {
|
|
@@ -180,15 +219,15 @@ const classifyProjectionMismatch = ({ invariant, story, actual, expected }) => {
|
|
|
180
219
|
if (divergence.actual === null || divergence.expected === null) return failure("block-count", `${actual.length > expected.length ? "more" : "fewer"} blocks than expected (${counts}), diverging ${at}`);
|
|
181
220
|
const { actual: left, expected: right } = divergence;
|
|
182
221
|
if (!sameContainer(left.table, right.table)) return failure("container", `a block sits in a ${containerKind(left.table)} where it is expected in a ${containerKind(right.table)}, ${at} (${counts})`);
|
|
183
|
-
if (!
|
|
222
|
+
if (left.kind === right.kind && !sameProjectedStructuralBoundaries(left, right)) return failure("inline-structure", `a block's zero-width inline structure does not match ${at} (${counts})`);
|
|
184
223
|
if (left.text === right.text && left.styleId !== right.styleId) return failure("style", `the paragraph style did not move ${at} (${counts})`);
|
|
185
|
-
if (left.text === right.text && !sameFolioContentOutlineLevel(
|
|
186
|
-
if (left.
|
|
187
|
-
if (left.
|
|
224
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedParagraphFormatting(left, right, (a, b) => sameFolioContentOutlineLevel(a.directOutlineLevel, b.directOutlineLevel))) return failure("style", `the direct outline level did not move ${at} (${counts})`);
|
|
225
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedListReference(left, right)) return failure("list-level", `the numbering reference did not move ${at} (${counts})`);
|
|
226
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedNumbering(left, right)) return failure("numbering-source", `the authored numbering state did not move ${at} (${counts})`);
|
|
188
227
|
if (left.text === right.text && !sameClassification(left, right)) return left.kind === "heading" || right.kind === "heading" ? failure("style", `the heading classification did not move ${at} (${counts})`) : failure("list-level", `the list membership did not move ${at} (${counts})`);
|
|
189
|
-
if (left.text === right.text && left.directAlignment
|
|
190
|
-
if (left.text === right.text && !paragraphSpacingEqual(
|
|
191
|
-
if (left.text === right.text && !paragraphIndentationEqual(
|
|
228
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedParagraphFormatting(left, right, (a, b) => a.directAlignment === b.directAlignment)) return failure("alignment", `the direct paragraph alignment did not move ${at} (${counts})`);
|
|
229
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedParagraphFormatting(left, right, (a, b) => paragraphSpacingEqual(a.directSpacing, b.directSpacing))) return failure("spacing", `the direct paragraph spacing did not move ${at} (${counts})`);
|
|
230
|
+
if (left.kind === right.kind && left.text === right.text && !sameProjectedParagraphFormatting(left, right, (a, b) => paragraphIndentationEqual(a.directIndentation, b.directIndentation))) return failure("indentation", `the direct paragraph indentation did not move ${at} (${counts})`);
|
|
192
231
|
if (collapseWhitespace(left.text) === collapseWhitespace(right.text)) return failure("whitespace", `a block's text differs only in whitespace ${at} (${counts})`);
|
|
193
232
|
if (actual.length !== expected.length) return failure("block-count", `${counts}, first differing ${at}`);
|
|
194
233
|
return failure("text", `a ${containerKind(left.table)} block's text does not match ${at}, length ${String(left.text.length)} against ${String(right.text.length)} (${counts})`);
|