@stll/folio-core 0.36.0 → 0.37.1
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 +689 -367
- 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 +133 -71
- 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 +99 -19
- 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 +60 -57
- 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 +41 -5
- 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 +201 -569
- package/dist/compare/scenario.d.ts +2 -2
- package/dist/compare/types.d.ts +3 -3
- package/dist/compare/verification.d.ts +2 -2
- package/dist/compare/verification.js +25 -8
- package/dist/compat/eigenpal.d.ts +5 -3
- package/dist/compat/eigenpal.js +3 -2
- package/dist/controller/headerFooterEditorManager.js +14 -6
- 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 -6
- package/dist/document-operations.js +121 -26
- package/dist/docx/blockContentParser.js +6 -27
- package/dist/docx/ensureParaIds.js +9 -4
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +4 -1
- package/dist/docx/paragraphParser.js +7 -6
- 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/runParser.js +4 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/runSerializer.js +5 -3
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +3 -1
- package/dist/docx/serializer/trackedChangeAttributes.js +11 -1
- package/dist/docx/server/build.d.ts +1 -1
- 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 +176 -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 +48 -5
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +379 -75
- 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/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 +113 -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 +167 -59
- 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 +7 -5
- package/dist/prosemirror/commands/propertyChangeScope.js +27 -18
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +7 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +809 -209
- package/dist/prosemirror/conversion/runShadingMark.js +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +26 -1
- package/dist/prosemirror/conversion/toProseDoc.js +416 -283
- package/dist/prosemirror/extensions/StarterKit.js +4 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -8
- package/dist/prosemirror/extensions/features/ListExtension.js +95 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -1
- 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/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/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 +8 -10
- 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 +82 -9
- package/dist/prosemirror/schema/marks.js +73 -1
- package/dist/prosemirror/schema/nodes.d.ts +18 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +14 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +44 -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/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/version-comparison.d.ts +38 -44
- package/dist/version-comparison.js +270 -401
- package/package.json +3 -2
package/dist/ai-edits/types.d.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { FolioContentBlock, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
|
|
2
3
|
//#region src/ai-edits/types.d.ts
|
|
3
4
|
type FolioAIBlockKind = "heading" | "listItem" | "paragraph";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
/** Boolean run properties supported by snapshots and range-formatting mutations. */
|
|
6
|
+
type FolioAIInlineBooleanProperty = FolioContentInlineBooleanProperty;
|
|
7
|
+
type FolioAIInlineFormatting = FolioContentInlineFormatting;
|
|
8
|
+
/**
|
|
9
|
+
* A run-formatting mutation: `false` authors an explicit off value, while
|
|
10
|
+
* `null` removes the direct property so its inherited value becomes effective.
|
|
11
|
+
*/
|
|
12
|
+
type FolioAIInlineFormattingPatch = FolioContentInlineFormattingPatch;
|
|
13
|
+
type FolioAIBlockPreviewRun = FolioContentRun;
|
|
14
|
+
/**
|
|
15
|
+
* A zero-width inline structure at one clean-text boundary. `pageBreak`
|
|
16
|
+
* always represents an authored `<w:br w:type="page"/>`; an omitted `clear`
|
|
17
|
+
* remains distinct from any explicit value even though it does not affect
|
|
18
|
+
* layout for this break type.
|
|
19
|
+
*/
|
|
20
|
+
type FolioAIBlockStructuralBoundary = {
|
|
21
|
+
type: "pageBreak";
|
|
22
|
+
offset: number;
|
|
23
|
+
clear?: document_d_exports.BreakContent["clear"];
|
|
19
24
|
};
|
|
20
25
|
/**
|
|
21
26
|
* Where a block sits inside its innermost enclosing table. Every index is
|
|
@@ -25,45 +30,16 @@ type FolioAIBlockPreviewRun = {
|
|
|
25
30
|
* not a grid column), and `paragraphIndex` orders the block among the cell's
|
|
26
31
|
* own blocks. Absent on a block that is not inside a table.
|
|
27
32
|
*/
|
|
28
|
-
type FolioAIBlockTableLocation =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* as `tableIndex` unless tables nest. A comparison aligns on this: a table
|
|
32
|
-
* inside a cell is part of its parent, not a structure of its own that can
|
|
33
|
-
* be paired against one somewhere else.
|
|
34
|
-
*/
|
|
35
|
-
outerTableIndex: number;
|
|
36
|
-
tableIndex: number;
|
|
37
|
-
rowIndex: number;
|
|
38
|
-
cellIndex: number;
|
|
39
|
-
/** Grid column occupied by the cell's left edge, derived from `TableMap`. */
|
|
40
|
-
gridColumnIndex: number;
|
|
41
|
-
/** Number of grid columns occupied by this physical cell. */
|
|
42
|
-
columnSpan: number;
|
|
43
|
-
/** Number of grid rows occupied by this physical cell. */
|
|
44
|
-
rowSpan: number;
|
|
45
|
-
paragraphIndex: number;
|
|
46
|
-
};
|
|
47
|
-
type FolioAIBlock = {
|
|
48
|
-
id: string;
|
|
49
|
-
kind: FolioAIBlockKind;
|
|
50
|
-
text: string;
|
|
51
|
-
/** One-based heading depth when the block has outline semantics. */
|
|
52
|
-
headingLevel?: number;
|
|
53
|
-
displayLabel?: string;
|
|
54
|
-
styleId?: string;
|
|
55
|
-
/** Direct `w:pPr/w:jc`; absent when alignment comes only from a style. */
|
|
56
|
-
directAlignment?: document_d_exports.ParagraphAlignment;
|
|
57
|
-
/**
|
|
58
|
-
* `w:numPr/w:ilvl`: the block's list indent level. Present only on a block
|
|
59
|
-
* that carries numbering, and the only pPr property a redline can move
|
|
60
|
-
* without touching a word — a demoted list item reads as unchanged text and
|
|
61
|
-
* is not.
|
|
62
|
-
*/
|
|
63
|
-
listLevel?: number;
|
|
64
|
-
previewRuns?: FolioAIBlockPreviewRun[];
|
|
65
|
-
table?: FolioAIBlockTableLocation;
|
|
33
|
+
type FolioAIBlockTableLocation = FolioContentTableLocation;
|
|
34
|
+
type FolioAIBlock = FolioContentBlock<FolioAIBlockKind> & {
|
|
35
|
+
structuralBoundaries?: readonly FolioAIBlockStructuralBoundary[];
|
|
66
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* The complete modeled attribute set of one direct `w:pPr/w:spacing` child.
|
|
39
|
+
* Optional fields preserve the distinction between an absent attribute and
|
|
40
|
+
* an explicit zero or false value.
|
|
41
|
+
*/
|
|
42
|
+
type FolioAIParagraphSpacing = FolioContentParagraphSpacing;
|
|
67
43
|
/**
|
|
68
44
|
* The paragraph properties an operation may set. A subset of `w:pPrChange`'s
|
|
69
45
|
* scope: properties a comparison can see in a block projection and an agent
|
|
@@ -79,6 +55,8 @@ type FolioAIBlockParagraphProperties = {
|
|
|
79
55
|
listLevel?: number | null;
|
|
80
56
|
/** Direct `w:jc`. `null` clears the override and restores style inheritance. */
|
|
81
57
|
alignment?: document_d_exports.ParagraphAlignment | null;
|
|
58
|
+
/** Direct `w:spacing` attributes. `null` removes the whole direct child. */
|
|
59
|
+
spacing?: FolioAIParagraphSpacing | null;
|
|
82
60
|
};
|
|
83
61
|
/**
|
|
84
62
|
* Every paragraph of one story, blank ones included.
|
|
@@ -89,8 +67,7 @@ type FolioAIBlockParagraphProperties = {
|
|
|
89
67
|
* document for a person or a model wants only the paragraphs that carry text,
|
|
90
68
|
* and says so with `isFolioAIContentBlock`.
|
|
91
69
|
*/
|
|
92
|
-
type FolioAIEditSnapshot = {
|
|
93
|
-
blocks: FolioAIBlock[];
|
|
70
|
+
type FolioAIEditSnapshot = FolioContentSnapshot<FolioAIBlock> & {
|
|
94
71
|
anchors: Record<string, FolioAIBlockAnchor>;
|
|
95
72
|
};
|
|
96
73
|
type FolioAIBlockAnchor = {
|
|
@@ -100,6 +77,8 @@ type FolioAIBlockAnchor = {
|
|
|
100
77
|
text: string;
|
|
101
78
|
normalizedText: string;
|
|
102
79
|
textHash: string;
|
|
80
|
+
/** Hash of zero-width structural boundaries in the clean block view. */
|
|
81
|
+
structuralBoundaryHash: string;
|
|
103
82
|
hashOccurrenceCount: number;
|
|
104
83
|
};
|
|
105
84
|
type FolioAIComment = {
|
|
@@ -218,7 +197,7 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
218
197
|
id: string;
|
|
219
198
|
type: "formatRange";
|
|
220
199
|
range: FolioAITextRangeHandle;
|
|
221
|
-
formatting:
|
|
200
|
+
formatting: FolioAIInlineFormattingPatch;
|
|
222
201
|
} | {
|
|
223
202
|
id: string;
|
|
224
203
|
type: "insertAfterBlock" | "insertBeforeBlock";
|
|
@@ -272,6 +251,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
272
251
|
* from the anchor and lets the inserted paragraph's style decide.
|
|
273
252
|
*/
|
|
274
253
|
alignment?: document_d_exports.ParagraphAlignment | null;
|
|
254
|
+
/**
|
|
255
|
+
* Direct `w:spacing` for the inserted block. `null` clears spacing
|
|
256
|
+
* copied from the anchor and lets the inserted paragraph's style decide.
|
|
257
|
+
*/
|
|
258
|
+
spacing?: FolioAIParagraphSpacing | null;
|
|
275
259
|
comment?: FolioAIComment;
|
|
276
260
|
} | {
|
|
277
261
|
id: string;
|
|
@@ -328,6 +312,16 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
328
312
|
*/
|
|
329
313
|
separator?: string;
|
|
330
314
|
blockId: string;
|
|
315
|
+
/**
|
|
316
|
+
* Paragraph properties for the first result. Omitted properties keep
|
|
317
|
+
* the source paragraph's value.
|
|
318
|
+
*/
|
|
319
|
+
firstParagraphProperties?: FolioAIBlockParagraphProperties;
|
|
320
|
+
/**
|
|
321
|
+
* Paragraph properties for the second result. Omitted properties keep
|
|
322
|
+
* the source paragraph's value.
|
|
323
|
+
*/
|
|
324
|
+
secondParagraphProperties?: FolioAIBlockParagraphProperties;
|
|
331
325
|
} |
|
|
332
326
|
/**
|
|
333
327
|
* Add a whole table next to the anchor block, its rows marked inserted in
|
|
@@ -393,6 +387,11 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
393
387
|
*/
|
|
394
388
|
separator?: string;
|
|
395
389
|
blockId: string;
|
|
390
|
+
/**
|
|
391
|
+
* Paragraph properties for the joined result. Omitted properties keep
|
|
392
|
+
* the first paragraph's value.
|
|
393
|
+
*/
|
|
394
|
+
mergedParagraphProperties?: FolioAIBlockParagraphProperties;
|
|
396
395
|
} | {
|
|
397
396
|
id: string;
|
|
398
397
|
type: "commentOnBlock";
|
|
@@ -483,6 +482,10 @@ type FolioAIEditOperation = FolioAIEditReviewMeta & {
|
|
|
483
482
|
*/
|
|
484
483
|
type FolioAIEditApplyMode = "direct" | "tracked-changes" | "suggested";
|
|
485
484
|
type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" | "missingFind" | "unsupportedBlock" | "unsupportedMode" | "atomicBatchRejected" | "preconditionFailed" | "staleRange" | "emptyOperation" |
|
|
485
|
+
/** The paragraph already owns the one `w:pPrChange` OOXML permits. */
|
|
486
|
+
"pendingParagraphPropertyChange" |
|
|
487
|
+
/** The affected run already owns the one `w:rPrChange` OOXML permits. */
|
|
488
|
+
"pendingRunPropertyChange" |
|
|
486
489
|
/**
|
|
487
490
|
* The operation would not change the document — find equals
|
|
488
491
|
* replace, or replaceBlock's `text` matches the live block.
|
|
@@ -571,4 +574,4 @@ type FolioAIEditApplyResult = {
|
|
|
571
574
|
normalizations?: FolioAIEditNormalization[];
|
|
572
575
|
};
|
|
573
576
|
//#endregion
|
|
574
|
-
export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };
|
|
577
|
+
export { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockParagraphProperties, FolioAIBlockPreviewRun, FolioAIBlockStructuralBoundary, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineBooleanProperty, FolioAIInlineFormatting, FolioAIInlineFormattingPatch, FolioAIParagraphSpacing, FolioAISignatureParty, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult };
|
|
@@ -53,10 +53,7 @@ function resolveSuggestionAnchor(doc, suggestion) {
|
|
|
53
53
|
to: point
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
return
|
|
57
|
-
from: positional.pmPositionAt(originalTextStart),
|
|
58
|
-
to: positional.pmPositionAt(originalTextEnd - 1) + 1
|
|
59
|
-
};
|
|
56
|
+
return positional.pmRangeAt(originalTextStart, originalTextEnd);
|
|
60
57
|
}
|
|
61
58
|
return null;
|
|
62
59
|
}
|
|
@@ -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,9 +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";
|
|
4
5
|
import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
|
|
5
6
|
import "../document-operations.js";
|
|
6
7
|
import { pairFolioDocumentStories } from "../document-stories.js";
|
|
8
|
+
import { createContentComparisonWorkSession } from "./content.js";
|
|
7
9
|
import { planStoryCompare } from "./plan.js";
|
|
8
10
|
import { withFixedPackageDates } from "./reproducible-package.js";
|
|
9
11
|
import { CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "./types.js";
|
|
@@ -215,19 +217,49 @@ const parseComparison = async (base, target, options) => {
|
|
|
215
217
|
unsupported
|
|
216
218
|
});
|
|
217
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
|
+
};
|
|
218
228
|
/**
|
|
219
229
|
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
220
230
|
* parsing, no serialization, no clock.
|
|
221
231
|
*/
|
|
222
|
-
const planComparison = ({ pairs }) => {
|
|
232
|
+
const planComparison = ({ pairs, targetReviewer }) => {
|
|
223
233
|
const planned = [];
|
|
234
|
+
const workSession = createContentComparisonWorkSession();
|
|
235
|
+
let remainingOperations = MAX_COMPARE_OPERATIONS;
|
|
224
236
|
for (const pair of pairs) {
|
|
225
|
-
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({
|
|
226
242
|
story: pair.baseStory,
|
|
227
243
|
baseSnapshot: pair.baseSnapshot,
|
|
228
244
|
targetSnapshot: pair.targetSnapshot,
|
|
229
|
-
maxOperations:
|
|
245
|
+
maxOperations: remainingOperations,
|
|
246
|
+
wholeTableReplacement: "allow",
|
|
247
|
+
workSession
|
|
230
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
|
+
}
|
|
231
263
|
if (plan === null) return Result.err(new CompareDocxOperationLimitError({
|
|
232
264
|
message: "The comparison needs more operations than the engine generates.",
|
|
233
265
|
limit: MAX_COMPARE_OPERATIONS
|
|
@@ -236,6 +268,7 @@ const planComparison = ({ pairs }) => {
|
|
|
236
268
|
pair,
|
|
237
269
|
plan
|
|
238
270
|
});
|
|
271
|
+
remainingOperations -= plan.operations.length;
|
|
239
272
|
}
|
|
240
273
|
return Result.ok(planned);
|
|
241
274
|
};
|
|
@@ -266,10 +299,13 @@ const COMPARE_SKIP_DISPOSITION = {
|
|
|
266
299
|
preconditionFailed: "fatal",
|
|
267
300
|
staleRange: "fatal",
|
|
268
301
|
emptyOperation: "unwritable",
|
|
302
|
+
pendingParagraphPropertyChange: "unwritable",
|
|
303
|
+
pendingRunPropertyChange: "unwritable",
|
|
269
304
|
noopOperation: "unwritable",
|
|
270
305
|
documentVersionMismatch: "fatal",
|
|
271
306
|
documentNotEditable: "fatal"
|
|
272
307
|
};
|
|
308
|
+
const getCompareSkipDisposition = (reason) => COMPARE_SKIP_DISPOSITION[reason];
|
|
273
309
|
/**
|
|
274
310
|
* The table each `insertTable` / `insertTableRow` in the plan should place,
|
|
275
311
|
* resolved against the target document the plan named it in.
|
|
@@ -354,7 +390,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
354
390
|
if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
|
|
355
391
|
idSeed = nextRevisionId;
|
|
356
392
|
documentChanged = true;
|
|
357
|
-
const refused = skipped.filter(({ reason }) =>
|
|
393
|
+
const refused = skipped.filter(({ reason }) => getCompareSkipDisposition(reason) === "fatal");
|
|
358
394
|
if (refused.length > 0) return Result.err(new CompareDocxApplyError({
|
|
359
395
|
message: "Some derived operations were refused, so the result would not match the target.",
|
|
360
396
|
skipped: refused
|
|
@@ -510,4 +546,4 @@ const compareDocx = async (base, target, options) => {
|
|
|
510
546
|
});
|
|
511
547
|
};
|
|
512
548
|
//#endregion
|
|
513
|
-
export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
|
|
549
|
+
export { MAX_COMPARE_OPERATIONS, applyComparison, compareDocx, getCompareSkipDisposition, parseComparison, planComparison, serializeComparison };
|