@stll/folio-core 0.35.1 → 0.37.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/README.md +59 -1
- package/dist/ai-edits/apply.js +878 -377
- package/dist/ai-edits/block-identity.d.ts +4 -1
- package/dist/ai-edits/block-identity.js +4 -1
- package/dist/ai-edits/blockRange.js +14 -14
- package/dist/ai-edits/clean-text.d.ts +40 -1
- package/dist/ai-edits/clean-text.js +52 -2
- package/dist/ai-edits/headless.d.ts +3 -1
- package/dist/ai-edits/headless.js +141 -73
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.js +38 -20
- package/dist/ai-edits/snapshot.d.ts +14 -6
- package/dist/ai-edits/snapshot.js +105 -20
- package/dist/ai-edits/table-cell-mutations.js +1 -1
- package/dist/ai-edits/table-row-column-mutations.js +7 -0
- package/dist/ai-edits/table-template.d.ts +11 -1
- package/dist/ai-edits/table-template.js +49 -17
- package/dist/ai-edits/types.d.ts +62 -61
- package/dist/ai-edits/word-diff.d.ts +18 -3
- package/dist/ai-edits/word-diff.js +556 -67
- package/dist/ai-suggestions/conflict.js +1 -4
- package/dist/ai-suggestions/text-positions.d.ts +8 -0
- package/dist/ai-suggestions/text-positions.js +27 -5
- package/dist/compare/__fixtures__/body-sequence.js +3 -4
- package/dist/compare/column-alignment.d.ts +13 -13
- package/dist/compare/column-alignment.js +78 -39
- package/dist/compare/compare.d.ts +4 -3
- package/dist/compare/compare.js +48 -21
- package/dist/compare/content-alignment.d.ts +95 -0
- package/dist/compare/content-alignment.js +1450 -0
- package/dist/compare/content-types.d.ts +100 -0
- package/dist/compare/content-types.js +0 -0
- package/dist/compare/content.d.ts +209 -0
- package/dist/compare/content.js +885 -0
- package/dist/compare/formatting.d.ts +5 -5
- package/dist/compare/formatting.js +6 -4
- package/dist/compare/plan.d.ts +6 -1
- package/dist/compare/plan.js +171 -566
- package/dist/compare/scenario.d.ts +2 -2
- package/dist/compare/types.d.ts +3 -3
- package/dist/compare/verification.d.ts +8 -9
- package/dist/compare/verification.js +72 -50
- package/dist/compat/eigenpal.d.ts +5 -3
- package/dist/compat/eigenpal.js +3 -2
- package/dist/controller/headerFooterEditorManager.js +18 -10
- package/dist/controller/hiddenEditorApi.js +1 -1
- package/dist/controller/hiddenEditorManager.d.ts +7 -2
- package/dist/controller/hiddenEditorManager.js +15 -6
- package/dist/controller/layoutPipeline.js +5 -3
- package/dist/controller/noteEditorManager.js +12 -7
- package/dist/display-list/build/headerFooterPrimitives.js +1 -0
- package/dist/display-list/build/storyPrimitives.d.ts +5 -2
- package/dist/display-list/build/storyPrimitives.js +60 -3
- package/dist/display-list/build/tablePrimitives.js +30 -0
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +8 -4
- package/dist/document-operations.js +104 -12
- package/dist/docx/blockContentParser.js +6 -27
- package/dist/docx/commentParser.js +2 -4
- package/dist/docx/commentRangeIntegrity.js +2 -4
- package/dist/docx/documentClone.d.ts +2 -0
- package/dist/docx/documentClone.js +2 -0
- package/dist/docx/ensureParaIds.js +9 -4
- package/dist/docx/headerFooterParser.js +2 -4
- package/dist/docx/normalizeBaseDirection.js +5 -7
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +4 -1
- package/dist/docx/paragraphParser.js +34 -35
- package/dist/docx/paragraphPropertySource.d.ts +49 -0
- package/dist/docx/paragraphPropertySource.js +113 -0
- package/dist/docx/paragraphTraversal.js +30 -34
- package/dist/docx/rezip.d.ts +0 -8
- package/dist/docx/rezip.js +41 -16
- package/dist/docx/runConsolidator.js +2 -4
- package/dist/docx/runParser.js +6 -17
- package/dist/docx/sectionParser.js +1 -15
- package/dist/docx/serializer/paragraphSerializer.js +289 -30
- package/dist/docx/serializer/runSerializer.js +6 -12
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
- package/dist/docx/serializer/tableSerializer.js +4 -23
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +11 -0
- package/dist/docx/serializer/trackedChangeAttributes.js +31 -0
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/server/createBilingualDocument.js +8 -10
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
- package/dist/docx/server/validateDocxConformance.js +17 -13
- package/dist/docx/tableParser.js +1 -37
- package/dist/docx/trackedChangeInfo.d.ts +11 -0
- package/dist/docx/trackedChangeInfo.js +38 -0
- package/dist/docx/verbatimCapture.d.ts +20 -9
- package/dist/docx/verbatimCapture.js +208 -8
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +6 -1
- package/dist/docx/xmlSafety.d.ts +1 -1
- package/dist/docx/xmlSafety.js +157 -0
- package/dist/headless-layout.js +5 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +23 -0
- package/dist/internal/headlessRevisionResolution.js +155 -0
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +12 -0
- package/dist/internal/headlessRevisionResolutionGuard.js +28 -0
- package/dist/internal/pageBreakRunDescendantIndex.d.ts +9 -0
- package/dist/internal/pageBreakRunDescendantIndex.js +27 -0
- package/dist/internal/pageBreakRunPartition.d.ts +26 -0
- package/dist/internal/pageBreakRunPartition.js +35 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +15 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +126 -0
- package/dist/internal/sectionEndpointResolution.d.ts +32 -0
- package/dist/internal/sectionEndpointResolution.js +38 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +16 -4
- package/dist/layout-bridge/convert/headerFooterLayout.js +52 -9
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +391 -82
- package/dist/layout-bridge/dom/clickToPositionDom.d.ts +8 -1
- package/dist/layout-bridge/dom/clickToPositionDom.js +114 -40
- package/dist/layout-bridge/dom/textStreamDom.d.ts +24 -0
- package/dist/layout-bridge/dom/textStreamDom.js +75 -0
- package/dist/layout-bridge/headerFooterLayout.js +16 -30
- package/dist/layout-engine/index.js +12 -3
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.d.ts +17 -0
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +24 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +7 -0
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +20 -0
- package/dist/markdown/renderBlock.js +7 -10
- package/dist/model.d.ts +1 -1
- package/dist/paged-layout/rangeProjection.js +6 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +3 -1
- package/dist/paged-layout/transactionDirtyRange.js +23 -1
- package/dist/prosemirror/attrs/index.d.ts +7 -3
- package/dist/prosemirror/attrs/index.js +128 -7
- package/dist/prosemirror/bookmarkBoundaryIntegrity.d.ts +9 -0
- package/dist/prosemirror/bookmarkBoundaryIntegrity.js +22 -0
- package/dist/prosemirror/commands/comments.d.ts +10 -1
- package/dist/prosemirror/commands/comments.js +551 -87
- package/dist/prosemirror/commands/formatPainter.d.ts +45 -10
- package/dist/prosemirror/commands/formatPainter.js +319 -63
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/pageBreak.d.ts +5 -2
- package/dist/prosemirror/commands/pageBreak.js +18 -36
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +27 -3
- package/dist/prosemirror/commands/propertyChangeScope.js +85 -14
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -5
- package/dist/prosemirror/conversion/fromProseDoc.js +935 -223
- package/dist/prosemirror/conversion/runShadingMark.js +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
- package/dist/prosemirror/conversion/toProseDoc.js +439 -291
- package/dist/prosemirror/extensions/StarterKit.js +4 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +54 -6
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +19 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +27 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +134 -30
- package/dist/prosemirror/extensions/features/pasteCleanup.js +9 -15
- package/dist/prosemirror/extensions/marks/BoldExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.d.ts +4 -7
- package/dist/prosemirror/extensions/marks/CharacterStyleExtension.js +5 -11
- package/dist/prosemirror/extensions/marks/FontSizeExtension.js +3 -3
- package/dist/prosemirror/extensions/marks/ItalicExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/marks/PageBreakRunOwnerExtension.js +39 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +67 -21
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +88 -2
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +44 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +15 -4
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/PageBreakRunExtension.js +36 -0
- package/dist/prosemirror/findReplaceSelection.js +8 -3
- package/dist/prosemirror/index.d.ts +3 -3
- package/dist/prosemirror/listMarker.d.ts +26 -1
- package/dist/prosemirror/listMarker.js +108 -14
- package/dist/prosemirror/pageBreakRunProjection.d.ts +28 -0
- package/dist/prosemirror/pageBreakRunProjection.js +47 -0
- package/dist/prosemirror/paraText.js +6 -0
- package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
- package/dist/prosemirror/paragraphAlignment.js +13 -0
- package/dist/prosemirror/paragraphSpacing.d.ts +29 -0
- package/dist/prosemirror/paragraphSpacing.js +125 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +8 -0
- package/dist/prosemirror/plugins/documentNumbering.js +18 -0
- package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -2
- package/dist/prosemirror/plugins/pmTextScan.js +14 -4
- package/dist/prosemirror/plugins/suggestionMode.js +9 -11
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +16 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +61 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +64 -0
- package/dist/prosemirror/runFormattingInlineCarriers.js +121 -0
- package/dist/prosemirror/runFormattingProvenance.d.ts +12 -0
- package/dist/prosemirror/runFormattingProvenance.js +48 -0
- package/dist/prosemirror/runFormattingReconciliation.d.ts +25 -0
- package/dist/prosemirror/runFormattingReconciliation.js +54 -0
- package/dist/prosemirror/runPropertyChangeResolution.d.ts +18 -0
- package/dist/prosemirror/runPropertyChangeResolution.js +18 -0
- package/dist/prosemirror/runStyleFormatting.d.ts +50 -0
- package/dist/prosemirror/runStyleFormatting.js +105 -0
- package/dist/prosemirror/schema/index.d.ts +3 -3
- package/dist/prosemirror/schema/marks.d.ts +84 -9
- package/dist/prosemirror/schema/marks.js +73 -1
- package/dist/prosemirror/schema/nodes.d.ts +30 -6
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +45 -23
- package/dist/prosemirror/styles/styleResolver.d.ts +10 -13
- package/dist/prosemirror/styles/styleResolver.js +3 -3
- package/dist/prosemirror/styles/styleToggleCascade.d.ts +8 -1
- package/dist/prosemirror/styles/styleToggleCascade.js +24 -1
- package/dist/prosemirror/tableGridMutation.d.ts +20 -0
- package/dist/prosemirror/tableGridMutation.js +30 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +31 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +39 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +27 -31
- package/dist/prosemirror/validation.js +23 -4
- package/dist/redline.js +4 -1
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +20 -20
- package/dist/render-dom/RenderedDomContext.js +17 -34
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/style-engine/styleEngine.d.ts +1 -1
- package/dist/utils/clipboard.js +1 -1
- package/dist/utils/findReplace.js +38 -21
- package/dist/utils/mergeDocumentContent.js +3 -3
- package/dist/utils/replaceText.js +2 -1
- package/dist/version-comparison.d.ts +38 -44
- package/dist/version-comparison.js +270 -399
- package/dist/watermark/index.js +2 -4
- package/package.json +8 -2
|
@@ -10,6 +10,14 @@ type PositionalText = {
|
|
|
10
10
|
* preceding block.
|
|
11
11
|
*/
|
|
12
12
|
pmPositionAt: (textIndex: number) => number;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a text span without absorbing an explicit zero-width structure.
|
|
15
|
+
* Returns `null` when the span crosses one.
|
|
16
|
+
*/
|
|
17
|
+
pmRangeAt: (startTextIndex: number, endTextIndex: number) => {
|
|
18
|
+
from: number;
|
|
19
|
+
to: number;
|
|
20
|
+
} | null;
|
|
13
21
|
};
|
|
14
22
|
/**
|
|
15
23
|
* Walk a slice of the document, returning its text plus a position
|
|
@@ -8,9 +8,18 @@ const BLOCK_SEPARATOR = "\n";
|
|
|
8
8
|
function buildPositionalText(doc, from = 0, to = doc.content.size) {
|
|
9
9
|
const chunks = [];
|
|
10
10
|
const offsets = [];
|
|
11
|
+
const structuralBoundaries = [];
|
|
11
12
|
let textLength = 0;
|
|
12
13
|
let lastBlockEnd = null;
|
|
13
14
|
doc.nodesBetween(from, to, (node, pos) => {
|
|
15
|
+
if (node.type.name === "pageBreakRun") {
|
|
16
|
+
structuralBoundaries.push({
|
|
17
|
+
textOffset: textLength,
|
|
18
|
+
from: pos,
|
|
19
|
+
to: pos + node.nodeSize
|
|
20
|
+
});
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
14
23
|
if (node.isText) {
|
|
15
24
|
const text = node.text ?? "";
|
|
16
25
|
const startInNode = Math.max(from, pos);
|
|
@@ -35,12 +44,25 @@ function buildPositionalText(doc, from = 0, to = doc.content.size) {
|
|
|
35
44
|
}
|
|
36
45
|
return true;
|
|
37
46
|
});
|
|
47
|
+
const text = chunks.join("");
|
|
48
|
+
const pmPositionAt = (textIndex) => {
|
|
49
|
+
if (textIndex < 0) return offsets[0] ?? from;
|
|
50
|
+
if (textIndex >= offsets.length) return (offsets.at(-1) ?? from) + 1;
|
|
51
|
+
return offsets[textIndex] ?? from;
|
|
52
|
+
};
|
|
38
53
|
return {
|
|
39
|
-
text
|
|
40
|
-
pmPositionAt
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
54
|
+
text,
|
|
55
|
+
pmPositionAt,
|
|
56
|
+
pmRangeAt: (startTextIndex, endTextIndex) => {
|
|
57
|
+
if (!Number.isInteger(startTextIndex) || !Number.isInteger(endTextIndex) || startTextIndex < 0 || endTextIndex <= startTextIndex || endTextIndex > text.length || structuralBoundaries.some(({ textOffset }) => textOffset > startTextIndex && textOffset < endTextIndex)) return null;
|
|
58
|
+
const rangeFrom = pmPositionAt(startTextIndex);
|
|
59
|
+
const finalCharacter = offsets[endTextIndex - 1];
|
|
60
|
+
if (finalCharacter === void 0) return null;
|
|
61
|
+
const rangeTo = finalCharacter + 1;
|
|
62
|
+
return rangeFrom <= rangeTo ? {
|
|
63
|
+
from: rangeFrom,
|
|
64
|
+
to: rangeTo
|
|
65
|
+
} : null;
|
|
44
66
|
}
|
|
45
67
|
};
|
|
46
68
|
}
|
|
@@ -88,10 +88,9 @@ const EMPTY_PARAGRAPH = {
|
|
|
88
88
|
text: ""
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* the body would be measuring two different things.
|
|
91
|
+
* Most fixtures add a paragraph after a table so later paragraph insertions
|
|
92
|
+
* have an explicit anchor. Tests for a schema-valid terminal table remove that
|
|
93
|
+
* convenience paragraph deliberately.
|
|
95
94
|
*/
|
|
96
95
|
const closedSequence = (items) => {
|
|
97
96
|
const last = items.at(-1);
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FolioContentBlock, FolioContentTableLocation } from "./content-types.js";
|
|
2
2
|
//#region src/compare/column-alignment.d.ts
|
|
3
|
-
type TableColumnAlignmentStep = {
|
|
3
|
+
type TableColumnAlignmentStep<Block extends FolioContentBlock = FolioContentBlock> = {
|
|
4
4
|
type: "baseColumn";
|
|
5
|
-
blocks: readonly
|
|
6
|
-
location:
|
|
5
|
+
blocks: readonly Block[];
|
|
6
|
+
location: FolioContentTableLocation;
|
|
7
7
|
columnIndex: number;
|
|
8
8
|
} | {
|
|
9
|
-
type: "
|
|
10
|
-
blocks: readonly
|
|
11
|
-
location:
|
|
9
|
+
type: "revisedColumn";
|
|
10
|
+
blocks: readonly Block[];
|
|
11
|
+
location: FolioContentTableLocation;
|
|
12
12
|
columnIndex: number;
|
|
13
13
|
anchor: {
|
|
14
14
|
blockId: string;
|
|
15
15
|
position: "after" | "before";
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
-
type TableColumnAlignment = {
|
|
19
|
-
steps: TableColumnAlignmentStep[];
|
|
20
|
-
baseBlocks:
|
|
21
|
-
|
|
18
|
+
type TableColumnAlignment<Block extends FolioContentBlock = FolioContentBlock> = {
|
|
19
|
+
steps: TableColumnAlignmentStep<Block>[];
|
|
20
|
+
baseBlocks: Block[];
|
|
21
|
+
revisedBlocks: Block[];
|
|
22
22
|
baseColumnKeys: ReadonlyMap<number, number>;
|
|
23
|
-
|
|
23
|
+
revisedColumnKeys: ReadonlyMap<number, number>;
|
|
24
24
|
};
|
|
25
|
-
declare const alignTableColumns: (baseBlocks: readonly
|
|
25
|
+
declare const alignTableColumns: <Block extends FolioContentBlock>(baseBlocks: readonly Block[], revisedBlocks: readonly Block[]) => TableColumnAlignment<Block> | null;
|
|
26
26
|
//#endregion
|
|
27
27
|
export { TableColumnAlignment, TableColumnAlignmentStep, alignTableColumns };
|
|
@@ -2,7 +2,7 @@ import { panic } from "better-result";
|
|
|
2
2
|
//#region src/compare/column-alignment.ts
|
|
3
3
|
const MAX_TABLE_GRID_COLUMNS = 63;
|
|
4
4
|
const MAX_TABLE_GRID_AREA = 1e6;
|
|
5
|
-
const
|
|
5
|
+
const extractTableGrid = (blocks) => {
|
|
6
6
|
const cellsByPhysicalLocation = /* @__PURE__ */ new Map();
|
|
7
7
|
let width = 0;
|
|
8
8
|
let height = 0;
|
|
@@ -17,7 +17,7 @@ const tableGridColumns = (blocks) => {
|
|
|
17
17
|
}
|
|
18
18
|
const right = location.gridColumnIndex + location.columnSpan;
|
|
19
19
|
const bottom = location.rowIndex + location.rowSpan;
|
|
20
|
-
if (location.gridColumnIndex < 0 || location.rowIndex < 0 || location.columnSpan < 1 || location.rowSpan < 1 || right > MAX_TABLE_GRID_COLUMNS) return null;
|
|
20
|
+
if (!Number.isSafeInteger(location.gridColumnIndex) || !Number.isSafeInteger(location.rowIndex) || !Number.isSafeInteger(location.columnSpan) || !Number.isSafeInteger(location.rowSpan) || !Number.isSafeInteger(right) || !Number.isSafeInteger(bottom) || location.gridColumnIndex < 0 || location.rowIndex < 0 || location.columnSpan < 1 || location.rowSpan < 1 || right > MAX_TABLE_GRID_COLUMNS) return null;
|
|
21
21
|
cellsByPhysicalLocation.set(key, {
|
|
22
22
|
blocks: [block],
|
|
23
23
|
rowIndex: location.rowIndex,
|
|
@@ -29,30 +29,58 @@ const tableGridColumns = (blocks) => {
|
|
|
29
29
|
height = Math.max(height, bottom);
|
|
30
30
|
}
|
|
31
31
|
if (width === 0 || height === 0 || width * height > MAX_TABLE_GRID_AREA) return null;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const occupancyEvents = [...cellsByPhysicalLocation.values()].flatMap((cell) => {
|
|
33
|
+
const mask = (1n << BigInt(cell.columnSpan)) - 1n << BigInt(cell.gridColumnIndex);
|
|
34
|
+
return [{
|
|
35
|
+
rowIndex: cell.rowIndex,
|
|
36
|
+
type: "enter",
|
|
37
|
+
mask
|
|
38
|
+
}, {
|
|
39
|
+
rowIndex: cell.rowIndex + cell.rowSpan,
|
|
40
|
+
type: "leave",
|
|
41
|
+
mask
|
|
42
|
+
}];
|
|
43
|
+
});
|
|
44
|
+
occupancyEvents.sort((left, right) => {
|
|
45
|
+
const rowOrder = left.rowIndex - right.rowIndex;
|
|
46
|
+
if (rowOrder !== 0 || left.type === right.type) return rowOrder;
|
|
47
|
+
return left.type === "leave" ? -1 : 1;
|
|
48
|
+
});
|
|
49
|
+
let occupiedColumns = 0n;
|
|
50
|
+
for (const event of occupancyEvents) {
|
|
51
|
+
if (event.type === "leave") {
|
|
52
|
+
occupiedColumns &= ~event.mask;
|
|
53
|
+
continue;
|
|
40
54
|
}
|
|
55
|
+
if ((occupiedColumns & event.mask) !== 0n) return null;
|
|
56
|
+
occupiedColumns |= event.mask;
|
|
41
57
|
}
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
return {
|
|
59
|
+
cells: [...cellsByPhysicalLocation.values()],
|
|
60
|
+
width,
|
|
61
|
+
height
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const tableGridColumns = (grid, internText) => {
|
|
65
|
+
const coveringCellsByColumn = Array.from({ length: grid.width }, () => []);
|
|
66
|
+
const ownedCellsByColumn = Array.from({ length: grid.width }, () => []);
|
|
67
|
+
const textKeysByCell = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const cell of grid.cells) {
|
|
69
|
+
textKeysByCell.set(cell, cell.blocks.map(({ text }) => internText(text)));
|
|
70
|
+
ownedCellsByColumn[cell.gridColumnIndex]?.push(cell);
|
|
71
|
+
for (let column = cell.gridColumnIndex; column < cell.gridColumnIndex + cell.columnSpan; column++) coveringCellsByColumn[column]?.push(cell);
|
|
72
|
+
}
|
|
73
|
+
return Array.from({ length: grid.width }, (_unused, columnIndex) => {
|
|
74
|
+
const structuralCells = [...coveringCellsByColumn[columnIndex] ?? []].toSorted((left, right) => left.rowIndex - right.rowIndex).map((cell) => [
|
|
75
|
+
columnIndex - cell.gridColumnIndex,
|
|
76
|
+
cell.columnSpan,
|
|
77
|
+
cell.rowIndex,
|
|
78
|
+
cell.rowSpan,
|
|
79
|
+
textKeysByCell.get(cell) ?? panic("A table cell has no interned text signature")
|
|
80
|
+
]);
|
|
53
81
|
return {
|
|
54
82
|
index: columnIndex,
|
|
55
|
-
signature: JSON.stringify(
|
|
83
|
+
signature: JSON.stringify([grid.height, structuralCells]),
|
|
56
84
|
ownedCells: ownedCellsByColumn[columnIndex] ?? []
|
|
57
85
|
};
|
|
58
86
|
});
|
|
@@ -90,14 +118,25 @@ const columnOwnedBlocks = ({ ownedCells }) => {
|
|
|
90
118
|
if (ownedCells.length === 0 || ownedCells.some(({ columnSpan }) => columnSpan !== 1)) return null;
|
|
91
119
|
return ownedCells.flatMap(({ blocks }) => blocks);
|
|
92
120
|
};
|
|
93
|
-
const alignTableColumns = (baseBlocks,
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
97
|
-
const
|
|
98
|
-
|
|
121
|
+
const alignTableColumns = (baseBlocks, revisedBlocks) => {
|
|
122
|
+
const baseGrid = extractTableGrid(baseBlocks);
|
|
123
|
+
const revisedGrid = extractTableGrid(revisedBlocks);
|
|
124
|
+
if (!baseGrid || !revisedGrid || baseGrid.width === revisedGrid.width) return null;
|
|
125
|
+
const textKeys = /* @__PURE__ */ new Map();
|
|
126
|
+
let nextTextKey = 0;
|
|
127
|
+
const internText = (text) => {
|
|
128
|
+
const existing = textKeys.get(text);
|
|
129
|
+
if (existing !== void 0) return existing;
|
|
130
|
+
const key = nextTextKey++;
|
|
131
|
+
textKeys.set(text, key);
|
|
132
|
+
return key;
|
|
133
|
+
};
|
|
134
|
+
const baseColumns = tableGridColumns(baseGrid, internText);
|
|
135
|
+
const revisedColumns = tableGridColumns(revisedGrid, internText);
|
|
136
|
+
const revisedIsWider = revisedColumns.length > baseColumns.length;
|
|
137
|
+
const mapping = uniqueColumnEmbedding(revisedIsWider ? baseColumns : revisedColumns, revisedIsWider ? revisedColumns : baseColumns);
|
|
99
138
|
if (!mapping) return null;
|
|
100
|
-
const widerColumns =
|
|
139
|
+
const widerColumns = revisedIsWider ? revisedColumns : baseColumns;
|
|
101
140
|
const mapped = new Set(mapping);
|
|
102
141
|
const unmatchedColumns = widerColumns.filter((_column, index) => !mapped.has(index));
|
|
103
142
|
const ownedColumns = [];
|
|
@@ -109,21 +148,21 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
|
|
|
109
148
|
blocks
|
|
110
149
|
});
|
|
111
150
|
}
|
|
112
|
-
if (ownedColumns.length === 0 ||
|
|
151
|
+
if (ownedColumns.length === 0 || revisedIsWider && unmatchedColumns.some(({ ownedCells }) => ownedCells.some(({ rowSpan }) => rowSpan !== 1))) return null;
|
|
113
152
|
const unmatchedIds = new Set(ownedColumns.flatMap(({ blocks }) => blocks.map(({ id }) => id)));
|
|
114
153
|
const baseColumnKeys = /* @__PURE__ */ new Map();
|
|
115
|
-
const
|
|
154
|
+
const revisedColumnKeys = /* @__PURE__ */ new Map();
|
|
116
155
|
mapping.forEach((wideIndex, shortIndex) => {
|
|
117
|
-
if (
|
|
156
|
+
if (revisedIsWider) {
|
|
118
157
|
baseColumnKeys.set(shortIndex, shortIndex);
|
|
119
|
-
|
|
158
|
+
revisedColumnKeys.set(wideIndex, shortIndex);
|
|
120
159
|
} else {
|
|
121
160
|
baseColumnKeys.set(wideIndex, shortIndex);
|
|
122
|
-
|
|
161
|
+
revisedColumnKeys.set(shortIndex, shortIndex);
|
|
123
162
|
}
|
|
124
163
|
});
|
|
125
164
|
const steps = [];
|
|
126
|
-
if (!
|
|
165
|
+
if (!revisedIsWider) for (const { column, blocks } of ownedColumns) {
|
|
127
166
|
const location = blocks.at(0)?.table;
|
|
128
167
|
if (!location) return null;
|
|
129
168
|
steps.push({
|
|
@@ -140,7 +179,7 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
|
|
|
140
179
|
const anchorBlock = (rightBaseIndex >= 0 ? baseColumns[rightBaseIndex] : baseColumns[leftBaseIndex])?.ownedCells.at(0)?.blocks.at(0);
|
|
141
180
|
if (!location || !anchorBlock) return null;
|
|
142
181
|
steps.push({
|
|
143
|
-
type: "
|
|
182
|
+
type: "revisedColumn",
|
|
144
183
|
blocks,
|
|
145
184
|
location,
|
|
146
185
|
columnIndex: column.index,
|
|
@@ -152,10 +191,10 @@ const alignTableColumns = (baseBlocks, targetBlocks) => {
|
|
|
152
191
|
}
|
|
153
192
|
return {
|
|
154
193
|
steps,
|
|
155
|
-
baseBlocks: baseBlocks.filter(({ id }) =>
|
|
156
|
-
|
|
194
|
+
baseBlocks: baseBlocks.filter(({ id }) => revisedIsWider || !unmatchedIds.has(id)),
|
|
195
|
+
revisedBlocks: revisedBlocks.filter(({ id }) => !revisedIsWider || !unmatchedIds.has(id)),
|
|
157
196
|
baseColumnKeys,
|
|
158
|
-
|
|
197
|
+
revisedColumnKeys
|
|
159
198
|
};
|
|
160
199
|
};
|
|
161
200
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
1
|
+
import { FolioAIEditSkipReason, FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
2
|
import { WordDiffGranularity } from "../ai-edits/word-diff.js";
|
|
3
3
|
import { FolioRevisionStamp } from "../ai-edits/apply.js";
|
|
4
4
|
import { FolioDocumentStoryHandle, FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
@@ -50,7 +50,8 @@ type PlannedStoryComparison = {
|
|
|
50
50
|
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
51
51
|
* parsing, no serialization, no clock.
|
|
52
52
|
*/
|
|
53
|
-
declare const planComparison: ({ pairs }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
|
|
53
|
+
declare const planComparison: ({ pairs, targetReviewer }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
|
|
54
|
+
declare const getCompareSkipDisposition: (reason: FolioAIEditSkipReason) => "fatal" | "unwritable";
|
|
54
55
|
/** What stage 3 produced: the change list, whether it was proven, and whether it wrote anything. */
|
|
55
56
|
type AppliedComparison = {
|
|
56
57
|
changes: readonly CompareChange[];
|
|
@@ -111,4 +112,4 @@ declare const serializeComparison: ({ baseBuffer, baseCarriedRevisions, reviewer
|
|
|
111
112
|
*/
|
|
112
113
|
declare const compareDocx: (base: ArrayBuffer, target: ArrayBuffer, options: CompareDocxOptions) => Promise<Result<CompareResult, CompareDocxError>>;
|
|
113
114
|
//#endregion
|
|
114
|
-
export { AppliedComparison, ComparedStoryPair, MAX_COMPARE_OPERATIONS, ParsedComparison, PlannedStoryComparison, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
|
|
115
|
+
export { AppliedComparison, ComparedStoryPair, MAX_COMPARE_OPERATIONS, ParsedComparison, PlannedStoryComparison, applyComparison, compareDocx, getCompareSkipDisposition, parseComparison, planComparison, serializeComparison };
|
package/dist/compare/compare.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
2
2
|
import { numberingReferenceKeysOf } from "../ai-edits/snapshot.js";
|
|
3
3
|
import { projectTableGeometry } from "../ai-edits/table-geometry.js";
|
|
4
|
+
import { tableTemplateCanCrossPackageLosslessly } from "../ai-edits/table-template.js";
|
|
5
|
+
import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
|
|
4
6
|
import "../document-operations.js";
|
|
5
7
|
import { pairFolioDocumentStories } from "../document-stories.js";
|
|
8
|
+
import { createContentComparisonWorkSession } from "./content.js";
|
|
6
9
|
import { planStoryCompare } from "./plan.js";
|
|
7
10
|
import { withFixedPackageDates } from "./reproducible-package.js";
|
|
8
11
|
import { CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "./types.js";
|
|
@@ -76,16 +79,6 @@ const existingRevisionsOf = (reviewer) => {
|
|
|
76
79
|
present
|
|
77
80
|
};
|
|
78
81
|
};
|
|
79
|
-
/**
|
|
80
|
-
* One story's text-and-structure projection: every block's text tagged with
|
|
81
|
-
* the table cell it sits in. The tag is what makes the self-check below see a
|
|
82
|
-
* paragraph that landed beside a table instead of inside it.
|
|
83
|
-
*/
|
|
84
|
-
const projectBlocks = (blocks) => {
|
|
85
|
-
return blocks.map(({ text, table, styleId, listLevel }) => {
|
|
86
|
-
return `${table ? `t${String(table.tableIndex)}r${String(table.rowIndex)}c${String(table.cellIndex)}g${String(table.gridColumnIndex)}x${String(table.columnSpan)}y${String(table.rowSpan)}p${String(table.paragraphIndex)}` : "body"}|${styleId ?? ""}|${listLevel ?? ""}|${text}`;
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
82
|
/** Verify formatting only where the plan claims a text-equal formatting change. */
|
|
90
83
|
const formattingRoundTripFailure = ({ invariant, story, changes, actualBlocks, expectedBlocks, expectedBlockId }) => {
|
|
91
84
|
const expectedIndexById = new Map(expectedBlocks.map(({ id }, index) => [id, index]));
|
|
@@ -224,19 +217,49 @@ const parseComparison = async (base, target, options) => {
|
|
|
224
217
|
unsupported
|
|
225
218
|
});
|
|
226
219
|
};
|
|
220
|
+
const planCopiesNonPortableWholeTable = (targetReviewer, pair, plan) => {
|
|
221
|
+
const targetTables = new Map(targetReviewer.storyTables({ story: pair.targetStory }).map(({ index, node }) => [index, node]));
|
|
222
|
+
return plan.tableTemplates.some(({ targetRowIndex, targetTableIndex }) => {
|
|
223
|
+
if (targetRowIndex !== void 0) return false;
|
|
224
|
+
const table = targetTables.get(targetTableIndex);
|
|
225
|
+
return table === void 0 || !tableTemplateCanCrossPackageLosslessly(table);
|
|
226
|
+
});
|
|
227
|
+
};
|
|
227
228
|
/**
|
|
228
229
|
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
229
230
|
* parsing, no serialization, no clock.
|
|
230
231
|
*/
|
|
231
|
-
const planComparison = ({ pairs }) => {
|
|
232
|
+
const planComparison = ({ pairs, targetReviewer }) => {
|
|
232
233
|
const planned = [];
|
|
234
|
+
const workSession = createContentComparisonWorkSession();
|
|
235
|
+
let remainingOperations = MAX_COMPARE_OPERATIONS;
|
|
233
236
|
for (const pair of pairs) {
|
|
234
|
-
const
|
|
237
|
+
const remainingLcsCells = workSession.alignment.remainingLcsCells;
|
|
238
|
+
const remainingStructuralTokenLookups = workSession.alignment.remainingStructuralTokenLookups;
|
|
239
|
+
const remainingMoveComparisons = workSession.remainingMoveComparisons;
|
|
240
|
+
const remainingMoveTokenLookups = workSession.remainingMoveTokenLookups;
|
|
241
|
+
let plan = planStoryCompare({
|
|
235
242
|
story: pair.baseStory,
|
|
236
243
|
baseSnapshot: pair.baseSnapshot,
|
|
237
244
|
targetSnapshot: pair.targetSnapshot,
|
|
238
|
-
maxOperations:
|
|
245
|
+
maxOperations: remainingOperations,
|
|
246
|
+
wholeTableReplacement: "allow",
|
|
247
|
+
workSession
|
|
239
248
|
});
|
|
249
|
+
if (plan && planCopiesNonPortableWholeTable(targetReviewer, pair, plan)) {
|
|
250
|
+
workSession.alignment.remainingLcsCells = remainingLcsCells;
|
|
251
|
+
workSession.alignment.remainingStructuralTokenLookups = remainingStructuralTokenLookups;
|
|
252
|
+
workSession.remainingMoveComparisons = remainingMoveComparisons;
|
|
253
|
+
workSession.remainingMoveTokenLookups = remainingMoveTokenLookups;
|
|
254
|
+
plan = planStoryCompare({
|
|
255
|
+
story: pair.baseStory,
|
|
256
|
+
baseSnapshot: pair.baseSnapshot,
|
|
257
|
+
targetSnapshot: pair.targetSnapshot,
|
|
258
|
+
maxOperations: remainingOperations,
|
|
259
|
+
wholeTableReplacement: "avoid",
|
|
260
|
+
workSession
|
|
261
|
+
});
|
|
262
|
+
}
|
|
240
263
|
if (plan === null) return Result.err(new CompareDocxOperationLimitError({
|
|
241
264
|
message: "The comparison needs more operations than the engine generates.",
|
|
242
265
|
limit: MAX_COMPARE_OPERATIONS
|
|
@@ -245,6 +268,7 @@ const planComparison = ({ pairs }) => {
|
|
|
245
268
|
pair,
|
|
246
269
|
plan
|
|
247
270
|
});
|
|
271
|
+
remainingOperations -= plan.operations.length;
|
|
248
272
|
}
|
|
249
273
|
return Result.ok(planned);
|
|
250
274
|
};
|
|
@@ -275,10 +299,13 @@ const COMPARE_SKIP_DISPOSITION = {
|
|
|
275
299
|
preconditionFailed: "fatal",
|
|
276
300
|
staleRange: "fatal",
|
|
277
301
|
emptyOperation: "unwritable",
|
|
302
|
+
pendingParagraphPropertyChange: "unwritable",
|
|
303
|
+
pendingRunPropertyChange: "unwritable",
|
|
278
304
|
noopOperation: "unwritable",
|
|
279
305
|
documentVersionMismatch: "fatal",
|
|
280
306
|
documentNotEditable: "fatal"
|
|
281
307
|
};
|
|
308
|
+
const getCompareSkipDisposition = (reason) => COMPARE_SKIP_DISPOSITION[reason];
|
|
282
309
|
/**
|
|
283
310
|
* The table each `insertTable` / `insertTableRow` in the plan should place,
|
|
284
311
|
* resolved against the target document the plan named it in.
|
|
@@ -321,14 +348,14 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
321
348
|
const failures = [];
|
|
322
349
|
let idSeed = revisionStamp.idSeed;
|
|
323
350
|
let documentChanged = false;
|
|
351
|
+
const wordDiff = createScopedWordDiffOptions({ granularity });
|
|
324
352
|
for (const { pair, plan } of planned) {
|
|
325
353
|
changes.push(...plan.changes);
|
|
326
354
|
if (plan.operations.length === 0 && plan.tableGeometryPairings.length === 0) continue;
|
|
327
|
-
const
|
|
355
|
+
const baseBefore = reviewer.readReviewedStory({
|
|
328
356
|
story: pair.baseStory,
|
|
329
357
|
view: "final"
|
|
330
358
|
})?.snapshot.blocks ?? [];
|
|
331
|
-
const baseBefore = projectBlocks(baseBeforeBlocks);
|
|
332
359
|
const baseBeforeGeometry = projectTableGeometry(reviewer.storyTables({ story: pair.baseStory }));
|
|
333
360
|
const targetTables = new Map(targetReviewer.storyTables({ story: pair.targetStory }).map(({ index, node }) => [index, node]));
|
|
334
361
|
const afterGeometry = reviewer.matchStoryTableGeometry({
|
|
@@ -352,7 +379,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
352
379
|
date: revisionStamp.date,
|
|
353
380
|
idSeed
|
|
354
381
|
},
|
|
355
|
-
wordDiff
|
|
382
|
+
wordDiff,
|
|
356
383
|
batch: {
|
|
357
384
|
version: 1,
|
|
358
385
|
mode: "tracked-changes",
|
|
@@ -363,7 +390,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
363
390
|
if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
|
|
364
391
|
idSeed = nextRevisionId;
|
|
365
392
|
documentChanged = true;
|
|
366
|
-
const refused = skipped.filter(({ reason }) =>
|
|
393
|
+
const refused = skipped.filter(({ reason }) => getCompareSkipDisposition(reason) === "fatal");
|
|
367
394
|
if (refused.length > 0) return Result.err(new CompareDocxApplyError({
|
|
368
395
|
message: "Some derived operations were refused, so the result would not match the target.",
|
|
369
396
|
skipped: refused
|
|
@@ -376,8 +403,8 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
376
403
|
const acceptFailure = classifyProjectionMismatch({
|
|
377
404
|
invariant: "accept-reproduces-target",
|
|
378
405
|
story: pair.baseStory,
|
|
379
|
-
actual:
|
|
380
|
-
expected:
|
|
406
|
+
actual: acceptedStory?.snapshot.blocks ?? [],
|
|
407
|
+
expected: pair.targetSnapshot.blocks
|
|
381
408
|
});
|
|
382
409
|
if (acceptFailure) failures.push(acceptFailure);
|
|
383
410
|
else {
|
|
@@ -398,7 +425,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
398
425
|
const rejectFailure = classifyProjectionMismatch({
|
|
399
426
|
invariant: "reject-reproduces-base",
|
|
400
427
|
story: pair.baseStory,
|
|
401
|
-
actual:
|
|
428
|
+
actual: rejectedStory?.snapshot.blocks ?? [],
|
|
402
429
|
expected: baseBefore
|
|
403
430
|
});
|
|
404
431
|
if (rejectFailure) failures.push(rejectFailure);
|
|
@@ -519,4 +546,4 @@ const compareDocx = async (base, target, options) => {
|
|
|
519
546
|
});
|
|
520
547
|
};
|
|
521
548
|
//#endregion
|
|
522
|
-
export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
|
|
549
|
+
export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, getCompareSkipDisposition, parseComparison, planComparison, serializeComparison };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FolioContentBlock, FolioContentIdStability, FolioContentTableLocation } from "./content-types.js";
|
|
2
|
+
import { TableColumnAlignmentStep } from "./column-alignment.js";
|
|
3
|
+
//#region src/compare/content-alignment.d.ts
|
|
4
|
+
type FolioContentAlignmentWorkSession = {
|
|
5
|
+
remainingLcsCells: number;
|
|
6
|
+
remainingStructuralTokenLookups: number;
|
|
7
|
+
};
|
|
8
|
+
type CreateFolioContentAlignmentWorkSessionOptions = {
|
|
9
|
+
/** @internal A lower allowance used by focused boundary tests. */
|
|
10
|
+
lcsCells?: number;
|
|
11
|
+
/** @internal A lower allowance used by focused boundary tests. */
|
|
12
|
+
structuralTokenLookups?: number;
|
|
13
|
+
};
|
|
14
|
+
declare const createFolioContentAlignmentWorkSession: (options?: CreateFolioContentAlignmentWorkSessionOptions) => FolioContentAlignmentWorkSession;
|
|
15
|
+
declare const exceedsFolioContentLcsBudget: (baseCount: number, revisedCount: number) => boolean;
|
|
16
|
+
type FolioContentBlockPair = {
|
|
17
|
+
baseIndex: number;
|
|
18
|
+
revisedIndex: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Longest strictly increasing subsequence by revised index.
|
|
22
|
+
*
|
|
23
|
+
* The Fenwick tree stores the earliest end index for equal-length candidates,
|
|
24
|
+
* preserving the historical deterministic tie break while reducing the
|
|
25
|
+
* stable-anchor pass from quadratic to O(n log n).
|
|
26
|
+
*/
|
|
27
|
+
declare const longestIncreasingFolioContentPairs: (pairs: readonly FolioContentBlockPair[]) => FolioContentBlockPair[];
|
|
28
|
+
type FolioContentAlignedBlockEvent<Block extends FolioContentBlock = FolioContentBlock> = {
|
|
29
|
+
type: "pair";
|
|
30
|
+
baseBlock: Block;
|
|
31
|
+
revisedBlock: Block;
|
|
32
|
+
} | {
|
|
33
|
+
type: "baseOnly";
|
|
34
|
+
block: Block;
|
|
35
|
+
} | {
|
|
36
|
+
type: "revisedOnly";
|
|
37
|
+
block: Block;
|
|
38
|
+
};
|
|
39
|
+
type AlignFolioContentBlocksOptions<Block extends FolioContentBlock> = {
|
|
40
|
+
workSession?: FolioContentAlignmentWorkSession;
|
|
41
|
+
/** Let an already-aligned structural slot outrank differing authored IDs. */
|
|
42
|
+
stableIdMismatch?: "pair" | "separate";
|
|
43
|
+
/** @internal Compatibility hook for adapters whose source model predates `idStability`. */
|
|
44
|
+
idStability?: ((block: Block) => FolioContentIdStability) | undefined;
|
|
45
|
+
};
|
|
46
|
+
declare const alignFolioContentBlocks: <Block extends FolioContentBlock>(baseBlocks: readonly Block[], revisedBlocks: readonly Block[], options?: AlignFolioContentBlocksOptions<Block>) => FolioContentAlignedBlockEvent<Block>[];
|
|
47
|
+
type FolioContentAlignmentStep<Block extends FolioContentBlock = FolioContentBlock> = {
|
|
48
|
+
type: "pair";
|
|
49
|
+
baseBlock: Block;
|
|
50
|
+
revisedBlock: Block;
|
|
51
|
+
} | {
|
|
52
|
+
type: "baseOnly";
|
|
53
|
+
block: Block;
|
|
54
|
+
moveScope: FolioContentMoveScope;
|
|
55
|
+
} | {
|
|
56
|
+
type: "revisedOnly";
|
|
57
|
+
block: Block;
|
|
58
|
+
moveScope: FolioContentMoveScope;
|
|
59
|
+
} | {
|
|
60
|
+
type: "baseRow";
|
|
61
|
+
blocks: readonly Block[];
|
|
62
|
+
location: FolioContentTableLocation;
|
|
63
|
+
} | {
|
|
64
|
+
type: "revisedRow";
|
|
65
|
+
blocks: readonly Block[];
|
|
66
|
+
location: FolioContentTableLocation;
|
|
67
|
+
} | {
|
|
68
|
+
type: "baseTable";
|
|
69
|
+
blocks: readonly Block[];
|
|
70
|
+
location: FolioContentTableLocation;
|
|
71
|
+
} | {
|
|
72
|
+
type: "revisedTable";
|
|
73
|
+
blocks: readonly Block[];
|
|
74
|
+
location: FolioContentTableLocation;
|
|
75
|
+
} | TableColumnAlignmentStep<Block>;
|
|
76
|
+
/** @internal Legal move bucket and alignment gap for one unpaired block. */
|
|
77
|
+
type FolioContentMoveScope = {
|
|
78
|
+
readonly bucket: number;
|
|
79
|
+
readonly gap: number;
|
|
80
|
+
};
|
|
81
|
+
declare const groupFolioContentTableRows: <Block extends FolioContentBlock>(blocks: readonly Block[]) => Block[][];
|
|
82
|
+
type AlignFolioContentStructureOptions<Block extends FolioContentBlock> = {
|
|
83
|
+
baseBlocks: readonly Block[];
|
|
84
|
+
revisedBlocks: readonly Block[];
|
|
85
|
+
workSession?: FolioContentAlignmentWorkSession;
|
|
86
|
+
wholeTableReplacement?: "allow" | "avoid";
|
|
87
|
+
/** Let a structurally aligned slot outrank different stable identifiers. */
|
|
88
|
+
stableIdMismatch?: "pair" | "separate";
|
|
89
|
+
/** @internal Compatibility hook for adapters whose source model predates `idStability`. */
|
|
90
|
+
idStability?: ((block: Block) => FolioContentIdStability) | undefined;
|
|
91
|
+
};
|
|
92
|
+
declare const alignFolioContentStructure: <Block extends FolioContentBlock>({ baseBlocks, revisedBlocks, workSession, wholeTableReplacement, stableIdMismatch, idStability }: AlignFolioContentStructureOptions<Block>) => FolioContentAlignmentStep<Block>[];
|
|
93
|
+
declare const contentBlocksShareContainer: (left: FolioContentBlock, right: FolioContentBlock) => boolean;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { AlignFolioContentBlocksOptions, AlignFolioContentStructureOptions, CreateFolioContentAlignmentWorkSessionOptions, FolioContentAlignedBlockEvent, FolioContentAlignmentStep, FolioContentAlignmentWorkSession, FolioContentBlockPair, FolioContentMoveScope, alignFolioContentBlocks, alignFolioContentStructure, contentBlocksShareContainer, createFolioContentAlignmentWorkSession, exceedsFolioContentLcsBudget, groupFolioContentTableRows, longestIncreasingFolioContentPairs };
|