@stll/folio-core 0.37.1 → 0.37.3
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/headless.d.ts +24 -1
- package/dist/ai-edits/headless.js +108 -71
- package/dist/ai-edits/read.d.ts +9 -1
- package/dist/ai-edits/read.js +34 -16
- package/dist/ai-edits/snapshot.d.ts +7 -5
- package/dist/ai-edits/snapshot.js +39 -12
- package/dist/ai-edits/table-cell-mutations.js +10 -5
- package/dist/ai-edits/table-template.js +19 -5
- package/dist/compare/compare.d.ts +2 -3
- package/dist/compare/compare.js +41 -64
- package/dist/compare/content-alignment.js +0 -1
- package/dist/compare/scenario.d.ts +11 -2
- package/dist/compare/scenario.js +6 -2
- package/dist/controller/hiddenEditorManager.js +11 -0
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/display-list/build/textBoxPrimitives.js +34 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +5 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/display-list/types.d.ts +8 -4
- package/dist/docx/paragraphPropertySource.d.ts +82 -2
- package/dist/docx/paragraphPropertySource.js +569 -3
- package/dist/docx/parser.js +9 -0
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +21 -2
- package/dist/headless-layout.js +1 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +7 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +20 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +43 -12
- package/dist/layout-engine/index.js +20 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +7 -3
- package/dist/layout-engine/measure/measureBlocks.js +10 -7
- package/dist/layout-engine/measure/measureParagraph.d.ts +2 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -1
- package/dist/layout-engine/paginator.d.ts +1 -1
- package/dist/layout-engine/paginator.js +41 -4
- package/dist/layout-engine/textBoxFlow.d.ts +2 -0
- package/dist/layout-engine/textBoxFlow.js +9 -5
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/documentColors.d.ts +10 -1
- package/dist/layout-painter/documentColors.js +16 -1
- package/dist/layout-painter/renderParagraph.js +9 -24
- package/dist/layout-painter/renderTable.js +3 -3
- package/dist/layout-painter/renderTextBox.js +2 -1
- package/dist/pdf/pageSpace.d.ts +12 -1
- package/dist/pdf/pageSpace.js +24 -1
- package/dist/pdf/paint.js +2 -2
- package/dist/prosemirror/attrs/index.js +19 -8
- package/dist/prosemirror/commands/comments.js +12 -3
- package/dist/prosemirror/commands/tableCellMergeResolution.js +18 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +94 -21
- package/dist/prosemirror/conversion/toProseDoc.js +100 -8
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +5 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +132 -41
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -1
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +9 -0
- package/dist/prosemirror/yjsParagraphSourceContract.js +26 -0
- package/dist/utils/rotationBoundingBox.d.ts +5 -1
- package/dist/utils/rotationBoundingBox.js +9 -1
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeTableCellParagraphSourcePayload, restoreTableCellsWithParagraphPropertySources, transportTableCellsWithParagraphPropertySources } from "../docx/paragraphPropertySource.js";
|
|
1
2
|
import { expectTableCellAttrs } from "../prosemirror/attrs/index.js";
|
|
2
3
|
import { standaloneTableCellFromProseMirror } from "../prosemirror/conversion/fromProseDoc.js";
|
|
3
4
|
import { standaloneTableCellToProseMirror } from "../prosemirror/conversion/toProseDoc.js";
|
|
@@ -105,7 +106,7 @@ const mergeTrackedVerticalTableCells = ({ tr, tablePosition, table, rectangle, r
|
|
|
105
106
|
bottom: row + 1
|
|
106
107
|
})) return null;
|
|
107
108
|
const attrs = expectTableCellAttrs(cell);
|
|
108
|
-
if (attrs.colspan !== 1 || attrs.rowspan !== 1 || attrs.cellMarker !== void 0 || attrs._docxVMergeContinuationCells !== void 0 || attrs._preserveVMergeRestart === true || attrs._originalFormatting?.vMerge !== void 0) return null;
|
|
109
|
+
if (attrs.colspan !== 1 || attrs.rowspan !== 1 || attrs.cellMarker !== void 0 || attrs._docxVMergeContinuationCells !== void 0 && attrs._docxVMergeContinuationCells !== null || attrs._preserveVMergeRestart === true || attrs._originalFormatting?.vMerge !== void 0) return null;
|
|
109
110
|
cells.push({
|
|
110
111
|
position,
|
|
111
112
|
cell
|
|
@@ -139,7 +140,7 @@ const mergeTrackedVerticalTableCells = ({ tr, tablePosition, table, rectangle, r
|
|
|
139
140
|
rectangle
|
|
140
141
|
});
|
|
141
142
|
if (!nextTr) return null;
|
|
142
|
-
nextTr.setNodeAttribute(tablePosition + 1 + origin.position, "_docxVMergeContinuationCells", continuationCells);
|
|
143
|
+
nextTr.setNodeAttribute(tablePosition + 1 + origin.position, "_docxVMergeContinuationCells", transportTableCellsWithParagraphPropertySources(continuationCells));
|
|
143
144
|
markStructuralChange(nextTr);
|
|
144
145
|
return nextTr;
|
|
145
146
|
};
|
|
@@ -196,8 +197,10 @@ const splitTrackedVerticalTableCell = ({ tr, tablePosition, table, rectangle, re
|
|
|
196
197
|
if (!cell) return null;
|
|
197
198
|
const attrs = expectTableCellAttrs(cell);
|
|
198
199
|
if (attrs.colspan !== 1 || attrs.rowspan !== rectangle.bottom - rectangle.top || attrs.cellMarker !== void 0) return null;
|
|
199
|
-
const
|
|
200
|
+
const continuationPayload = attrs._docxVMergeContinuationCells === void 0 || attrs._docxVMergeContinuationCells === null ? null : decodeTableCellParagraphSourcePayload(attrs._docxVMergeContinuationCells);
|
|
201
|
+
const continuationCells = continuationPayload?.cells;
|
|
200
202
|
if (continuationCells !== void 0 && continuationCells.length !== rectangle.bottom - rectangle.top - 1) return null;
|
|
203
|
+
const restoredContinuationCells = continuationPayload ? restoreTableCellsWithParagraphPropertySources(continuationPayload) : void 0;
|
|
201
204
|
const marker = {
|
|
202
205
|
kind: "merge",
|
|
203
206
|
info: {
|
|
@@ -211,9 +214,11 @@ const splitTrackedVerticalTableCell = ({ tr, tablePosition, table, rectangle, re
|
|
|
211
214
|
for (let index = 0; index < rectangle.bottom - rectangle.top - 1; index++) {
|
|
212
215
|
const source = continuationCells?.[index];
|
|
213
216
|
if (source?.structuralChange !== void 0) return null;
|
|
214
|
-
const
|
|
217
|
+
const restoredSource = restoredContinuationCells?.[index];
|
|
218
|
+
if (source && !restoredSource) return null;
|
|
219
|
+
const insertedCell = restoredSource ? trackedSplitCellFromStoredSource({
|
|
215
220
|
origin: cell,
|
|
216
|
-
source,
|
|
221
|
+
source: restoredSource,
|
|
217
222
|
marker
|
|
218
223
|
}) : cell.type.createAndFill({
|
|
219
224
|
...cell.attrs,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cloneTableCellsWithParagraphPropertyCaptures, decodeTableCellParagraphSourcePayload, recreateProseNodeWithDetachedParagraphPropertySource } from "../docx/paragraphPropertySource.js";
|
|
2
|
+
import { expectTableCellAttrs } from "../prosemirror/attrs/index.js";
|
|
1
3
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
2
4
|
import { Fragment } from "prosemirror-model";
|
|
3
5
|
//#region src/ai-edits/table-template.ts
|
|
@@ -137,10 +139,18 @@ const copiedAttrs = (node, context) => {
|
|
|
137
139
|
trIns: context.revision
|
|
138
140
|
} : attrs;
|
|
139
141
|
case "cell":
|
|
140
|
-
case "header_cell":
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
case "header_cell": {
|
|
143
|
+
if (context.clampRowSpan) return {
|
|
144
|
+
...attrs,
|
|
145
|
+
rowspan: 1,
|
|
146
|
+
_docxVMergeContinuationCells: null
|
|
147
|
+
};
|
|
148
|
+
const continuationCells = expectTableCellAttrs(node)._docxVMergeContinuationCells;
|
|
149
|
+
return continuationCells !== void 0 && continuationCells !== null ? {
|
|
150
|
+
...attrs,
|
|
151
|
+
_docxVMergeContinuationCells: cloneTableCellsWithParagraphPropertyCaptures(decodeTableCellParagraphSourcePayload(continuationCells))
|
|
152
|
+
} : attrs;
|
|
153
|
+
}
|
|
144
154
|
default: return attrs;
|
|
145
155
|
}
|
|
146
156
|
};
|
|
@@ -162,7 +172,11 @@ const copyNode = (node, context) => {
|
|
|
162
172
|
const copied = copyNode(child, inner);
|
|
163
173
|
if (copied) content.push(copied);
|
|
164
174
|
});
|
|
165
|
-
return
|
|
175
|
+
return recreateProseNodeWithDetachedParagraphPropertySource(node, {
|
|
176
|
+
attrs: copiedAttrs(node, context),
|
|
177
|
+
content: Fragment.fromArray(content),
|
|
178
|
+
marks
|
|
179
|
+
});
|
|
166
180
|
};
|
|
167
181
|
/**
|
|
168
182
|
* The template table, ready to insert: its properties, grid, rows and cells
|
|
@@ -31,7 +31,6 @@ type ParsedComparison = {
|
|
|
31
31
|
baseBuffer: ArrayBuffer;
|
|
32
32
|
baseCarriedRevisions: boolean;
|
|
33
33
|
reviewer: FolioDocxReviewer;
|
|
34
|
-
targetReviewer: FolioDocxReviewer;
|
|
35
34
|
revisionStamp: FolioRevisionStamp;
|
|
36
35
|
packageDate: Date;
|
|
37
36
|
pairs: readonly ComparedStoryPair[];
|
|
@@ -50,7 +49,7 @@ type PlannedStoryComparison = {
|
|
|
50
49
|
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
51
50
|
* parsing, no serialization, no clock.
|
|
52
51
|
*/
|
|
53
|
-
declare const planComparison: ({ pairs
|
|
52
|
+
declare const planComparison: ({ pairs }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
|
|
54
53
|
declare const getCompareSkipDisposition: (reason: FolioAIEditSkipReason) => "fatal" | "unwritable";
|
|
55
54
|
/** What stage 3 produced: the change list, whether it was proven, and whether it wrote anything. */
|
|
56
55
|
type AppliedComparison = {
|
|
@@ -77,7 +76,7 @@ type AppliedComparison = {
|
|
|
77
76
|
* what you could not represent" and "give me nothing unless you can prove it"
|
|
78
77
|
* are both legitimate asks and only the caller knows which one it is making.
|
|
79
78
|
*/
|
|
80
|
-
declare const applyComparison: ({ reviewer,
|
|
79
|
+
declare const applyComparison: ({ reviewer, revisionStamp, granularity, numberingChanges }: ParsedComparison, planned: readonly PlannedStoryComparison[]) => Result<AppliedComparison, CompareDocxApplyError>;
|
|
81
80
|
/**
|
|
82
81
|
* Stage 4: the result package, with every ZIP entry date pinned.
|
|
83
82
|
*
|
package/dist/compare/compare.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
2
|
-
import { numberingReferenceKeysOf } from "../ai-edits/snapshot.js";
|
|
1
|
+
import { FolioDocxReviewer, getFolioDocxComparisonAccess } from "../ai-edits/headless.js";
|
|
2
|
+
import { numberingReferenceKeysOf, storyTablesOf } from "../ai-edits/snapshot.js";
|
|
3
3
|
import { projectTableGeometry } from "../ai-edits/table-geometry.js";
|
|
4
4
|
import { tableTemplateCanCrossPackageLosslessly } from "../ai-edits/table-template.js";
|
|
5
5
|
import { createScopedWordDiffOptions } from "../ai-edits/word-diff.js";
|
|
@@ -60,25 +60,6 @@ const parseSide = async (buffer, side, author) => await Result.tryPromise({
|
|
|
60
60
|
cause
|
|
61
61
|
})
|
|
62
62
|
});
|
|
63
|
-
/** Read before either side is resolved, so it describes the package as it arrived. */
|
|
64
|
-
const existingRevisionsOf = (reviewer) => {
|
|
65
|
-
let highest = 0;
|
|
66
|
-
let present = false;
|
|
67
|
-
for (const { handle } of reviewer.listStories()) {
|
|
68
|
-
const story = reviewer.readReviewedStory({
|
|
69
|
-
story: handle,
|
|
70
|
-
view: "current-markup"
|
|
71
|
-
});
|
|
72
|
-
for (const change of story?.changes ?? []) {
|
|
73
|
-
highest = Math.max(highest, change.id);
|
|
74
|
-
present = true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
idSeed: highest + 1,
|
|
79
|
-
present
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
63
|
/** Verify formatting only where the plan claims a text-equal formatting change. */
|
|
83
64
|
const formattingRoundTripFailure = ({ invariant, story, changes, actualBlocks, expectedBlocks, expectedBlockId }) => {
|
|
84
65
|
const expectedIndexById = new Map(expectedBlocks.map(({ id }, index) => [id, index]));
|
|
@@ -146,15 +127,20 @@ const parseComparison = async (base, target, options) => {
|
|
|
146
127
|
if (targetParse.isErr()) return Result.err(targetParse.error);
|
|
147
128
|
const reviewer = baseParse.value;
|
|
148
129
|
const targetReviewer = targetParse.value;
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
130
|
+
const baseProjection = getFolioDocxComparisonAccess(reviewer).projectStories("with-revision-census");
|
|
131
|
+
const targetProjection = getFolioDocxComparisonAccess(targetReviewer).projectStories("without-revision-census");
|
|
132
|
+
const baseStories = [];
|
|
133
|
+
const targetStories = [];
|
|
134
|
+
const baseSnapshots = /* @__PURE__ */ new Map();
|
|
135
|
+
const targetSnapshots = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const { handle, snapshot } of baseProjection.stories) {
|
|
137
|
+
baseStories.push(handle);
|
|
138
|
+
baseSnapshots.set(handle, snapshot);
|
|
139
|
+
}
|
|
140
|
+
for (const { handle, snapshot } of targetProjection.stories) {
|
|
141
|
+
targetStories.push(handle);
|
|
142
|
+
targetSnapshots.set(handle, snapshot);
|
|
143
|
+
}
|
|
158
144
|
const pairs = [];
|
|
159
145
|
const unsupported = [];
|
|
160
146
|
const referencedNumberingLevels = /* @__PURE__ */ new Set();
|
|
@@ -162,10 +148,10 @@ const parseComparison = async (base, target, options) => {
|
|
|
162
148
|
if (!snapshot) return;
|
|
163
149
|
for (const referenceKey of numberingReferenceKeysOf(snapshot)) referencedNumberingLevels.add(referenceKey);
|
|
164
150
|
};
|
|
165
|
-
for (const { baseStory, revisedStory: targetStory } of pairFolioDocumentStories(
|
|
151
|
+
for (const { baseStory, revisedStory: targetStory } of pairFolioDocumentStories(baseStories, targetStories)) {
|
|
166
152
|
if (!baseStory) {
|
|
167
153
|
if (!targetStory) panic("A story pair contained neither a base nor a target story");
|
|
168
|
-
collectNumberingReferences(
|
|
154
|
+
collectNumberingReferences(targetSnapshots.get(targetStory));
|
|
169
155
|
unsupported.push({
|
|
170
156
|
reason: "story-missing-in-base",
|
|
171
157
|
baseStory: null,
|
|
@@ -174,7 +160,7 @@ const parseComparison = async (base, target, options) => {
|
|
|
174
160
|
continue;
|
|
175
161
|
}
|
|
176
162
|
if (!targetStory) {
|
|
177
|
-
collectNumberingReferences(
|
|
163
|
+
collectNumberingReferences(baseSnapshots.get(baseStory));
|
|
178
164
|
unsupported.push({
|
|
179
165
|
reason: "story-missing-in-target",
|
|
180
166
|
baseStory,
|
|
@@ -182,8 +168,8 @@ const parseComparison = async (base, target, options) => {
|
|
|
182
168
|
});
|
|
183
169
|
continue;
|
|
184
170
|
}
|
|
185
|
-
const baseSnapshot =
|
|
186
|
-
const targetSnapshot =
|
|
171
|
+
const baseSnapshot = baseSnapshots.get(baseStory);
|
|
172
|
+
const targetSnapshot = targetSnapshots.get(targetStory);
|
|
187
173
|
collectNumberingReferences(baseSnapshot);
|
|
188
174
|
collectNumberingReferences(targetSnapshot);
|
|
189
175
|
if (!baseSnapshot || !targetSnapshot) {
|
|
@@ -204,12 +190,11 @@ const parseComparison = async (base, target, options) => {
|
|
|
204
190
|
return Result.ok({
|
|
205
191
|
granularity: options.granularity ?? "word",
|
|
206
192
|
baseBuffer: base,
|
|
207
|
-
baseCarriedRevisions:
|
|
193
|
+
baseCarriedRevisions: baseProjection.revisions.present,
|
|
208
194
|
reviewer,
|
|
209
|
-
targetReviewer,
|
|
210
195
|
revisionStamp: {
|
|
211
196
|
date: options.timestamp,
|
|
212
|
-
idSeed:
|
|
197
|
+
idSeed: baseProjection.revisions.highestId + 1
|
|
213
198
|
},
|
|
214
199
|
packageDate,
|
|
215
200
|
pairs,
|
|
@@ -217,8 +202,8 @@ const parseComparison = async (base, target, options) => {
|
|
|
217
202
|
unsupported
|
|
218
203
|
});
|
|
219
204
|
};
|
|
220
|
-
const planCopiesNonPortableWholeTable = (
|
|
221
|
-
const targetTables = new Map(
|
|
205
|
+
const planCopiesNonPortableWholeTable = (pair, plan) => {
|
|
206
|
+
const targetTables = new Map(storyTablesOf(pair.targetSnapshot).map(({ index, node }) => [index, node]));
|
|
222
207
|
return plan.tableTemplates.some(({ targetRowIndex, targetTableIndex }) => {
|
|
223
208
|
if (targetRowIndex !== void 0) return false;
|
|
224
209
|
const table = targetTables.get(targetTableIndex);
|
|
@@ -229,7 +214,7 @@ const planCopiesNonPortableWholeTable = (targetReviewer, pair, plan) => {
|
|
|
229
214
|
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
230
215
|
* parsing, no serialization, no clock.
|
|
231
216
|
*/
|
|
232
|
-
const planComparison = ({ pairs
|
|
217
|
+
const planComparison = ({ pairs }) => {
|
|
233
218
|
const planned = [];
|
|
234
219
|
const workSession = createContentComparisonWorkSession();
|
|
235
220
|
let remainingOperations = MAX_COMPARE_OPERATIONS;
|
|
@@ -246,7 +231,7 @@ const planComparison = ({ pairs, targetReviewer }) => {
|
|
|
246
231
|
wholeTableReplacement: "allow",
|
|
247
232
|
workSession
|
|
248
233
|
});
|
|
249
|
-
if (plan && planCopiesNonPortableWholeTable(
|
|
234
|
+
if (plan && planCopiesNonPortableWholeTable(pair, plan)) {
|
|
250
235
|
workSession.alignment.remainingLcsCells = remainingLcsCells;
|
|
251
236
|
workSession.alignment.remainingStructuralTokenLookups = remainingStructuralTokenLookups;
|
|
252
237
|
workSession.remainingMoveComparisons = remainingMoveComparisons;
|
|
@@ -343,7 +328,8 @@ const resolveTableTemplates = (targetTables, requests) => {
|
|
|
343
328
|
* what you could not represent" and "give me nothing unless you can prove it"
|
|
344
329
|
* are both legitimate asks and only the caller knows which one it is making.
|
|
345
330
|
*/
|
|
346
|
-
const applyComparison = ({ reviewer,
|
|
331
|
+
const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChanges }, planned) => {
|
|
332
|
+
const comparisonAccess = getFolioDocxComparisonAccess(reviewer);
|
|
347
333
|
const changes = [...numberingChanges];
|
|
348
334
|
const failures = [];
|
|
349
335
|
let idSeed = revisionStamp.idSeed;
|
|
@@ -352,12 +338,9 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
352
338
|
for (const { pair, plan } of planned) {
|
|
353
339
|
changes.push(...plan.changes);
|
|
354
340
|
if (plan.operations.length === 0 && plan.tableGeometryPairings.length === 0) continue;
|
|
355
|
-
const baseBefore =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
})?.snapshot.blocks ?? [];
|
|
359
|
-
const baseBeforeGeometry = projectTableGeometry(reviewer.storyTables({ story: pair.baseStory }));
|
|
360
|
-
const targetTables = new Map(targetReviewer.storyTables({ story: pair.targetStory }).map(({ index, node }) => [index, node]));
|
|
341
|
+
const baseBefore = pair.baseSnapshot.blocks;
|
|
342
|
+
const baseBeforeGeometry = projectTableGeometry(storyTablesOf(pair.baseSnapshot));
|
|
343
|
+
const targetTables = new Map(storyTablesOf(pair.targetSnapshot).map(({ index, node }) => [index, node]));
|
|
361
344
|
const afterGeometry = reviewer.matchStoryTableGeometry({
|
|
362
345
|
story: pair.baseStory,
|
|
363
346
|
targetTables,
|
|
@@ -396,14 +379,14 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
396
379
|
skipped: refused
|
|
397
380
|
}));
|
|
398
381
|
}
|
|
399
|
-
const
|
|
382
|
+
const acceptedSnapshot = comparisonAccess.snapshotReviewedStory({
|
|
400
383
|
story: pair.baseStory,
|
|
401
384
|
view: "final"
|
|
402
385
|
});
|
|
403
386
|
const acceptFailure = classifyProjectionMismatch({
|
|
404
387
|
invariant: "accept-reproduces-target",
|
|
405
388
|
story: pair.baseStory,
|
|
406
|
-
actual:
|
|
389
|
+
actual: acceptedSnapshot?.blocks ?? [],
|
|
407
390
|
expected: pair.targetSnapshot.blocks
|
|
408
391
|
});
|
|
409
392
|
if (acceptFailure) failures.push(acceptFailure);
|
|
@@ -412,20 +395,20 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
412
395
|
invariant: "accept-reproduces-target",
|
|
413
396
|
story: pair.baseStory,
|
|
414
397
|
changes: plan.changes,
|
|
415
|
-
actualBlocks:
|
|
398
|
+
actualBlocks: acceptedSnapshot?.blocks ?? [],
|
|
416
399
|
expectedBlocks: pair.targetSnapshot.blocks,
|
|
417
400
|
expectedBlockId: ({ targetBlockId }) => targetBlockId
|
|
418
401
|
});
|
|
419
402
|
if (formattingFailure) failures.push(formattingFailure);
|
|
420
403
|
}
|
|
421
|
-
const
|
|
404
|
+
const rejectedSnapshot = comparisonAccess.snapshotReviewedStory({
|
|
422
405
|
story: pair.baseStory,
|
|
423
406
|
view: "original"
|
|
424
407
|
});
|
|
425
408
|
const rejectFailure = classifyProjectionMismatch({
|
|
426
409
|
invariant: "reject-reproduces-base",
|
|
427
410
|
story: pair.baseStory,
|
|
428
|
-
actual:
|
|
411
|
+
actual: rejectedSnapshot?.blocks ?? [],
|
|
429
412
|
expected: baseBefore
|
|
430
413
|
});
|
|
431
414
|
if (rejectFailure) failures.push(rejectFailure);
|
|
@@ -434,7 +417,7 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
434
417
|
invariant: "reject-reproduces-base",
|
|
435
418
|
story: pair.baseStory,
|
|
436
419
|
changes: plan.changes,
|
|
437
|
-
actualBlocks:
|
|
420
|
+
actualBlocks: rejectedSnapshot?.blocks ?? [],
|
|
438
421
|
expectedBlocks: pair.baseSnapshot.blocks,
|
|
439
422
|
expectedBlockId: ({ baseBlockId }) => baseBlockId
|
|
440
423
|
});
|
|
@@ -443,20 +426,14 @@ const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity,
|
|
|
443
426
|
const geometryAcceptFailure = classifyGeometryMismatch({
|
|
444
427
|
invariant: "accept-reproduces-target",
|
|
445
428
|
story: pair.baseStory,
|
|
446
|
-
actual: projectTableGeometry(
|
|
447
|
-
|
|
448
|
-
view: "final"
|
|
449
|
-
})),
|
|
450
|
-
expected: projectTableGeometry(targetReviewer.storyTables({ story: pair.targetStory }))
|
|
429
|
+
actual: projectTableGeometry(acceptedSnapshot ? storyTablesOf(acceptedSnapshot) : []),
|
|
430
|
+
expected: projectTableGeometry(storyTablesOf(pair.targetSnapshot))
|
|
451
431
|
});
|
|
452
432
|
if (geometryAcceptFailure) failures.push(geometryAcceptFailure);
|
|
453
433
|
const geometryRejectFailure = classifyGeometryMismatch({
|
|
454
434
|
invariant: "reject-reproduces-base",
|
|
455
435
|
story: pair.baseStory,
|
|
456
|
-
actual: projectTableGeometry(
|
|
457
|
-
story: pair.baseStory,
|
|
458
|
-
view: "original"
|
|
459
|
-
})),
|
|
436
|
+
actual: projectTableGeometry(rejectedSnapshot ? storyTablesOf(rejectedSnapshot) : []),
|
|
460
437
|
expected: baseBeforeGeometry
|
|
461
438
|
});
|
|
462
439
|
if (geometryRejectFailure) failures.push(geometryRejectFailure);
|
|
@@ -601,7 +601,6 @@ const trustedContentSequencePairs = ({ exactPairs, stablePairs, revisedLength, p
|
|
|
601
601
|
return [...primary, ...secondary].toSorted((left, right) => left.baseIndex - right.baseIndex || left.revisedIndex - right.revisedIndex);
|
|
602
602
|
};
|
|
603
603
|
const persistedContentSequencePairs = ({ base, revised, anchors, canPair }) => {
|
|
604
|
-
if (anchors.length === 0) return /* @__PURE__ */ new Set();
|
|
605
604
|
const uniqueIndexesByFirstId = (items) => {
|
|
606
605
|
const indexes = /* @__PURE__ */ new Map();
|
|
607
606
|
items.forEach(({ profile }, itemIndex) => {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { FolioAIInlineFormattingPatch } from "../ai-edits/types.js";
|
|
1
|
+
import { FolioAIEditOperation, FolioAIInlineFormattingPatch } from "../ai-edits/types.js";
|
|
2
2
|
import { CompareDocxParseError, CompareDocxSerializeError } from "./types.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
//#region src/compare/scenario.d.ts
|
|
5
|
+
type InsertTableRowOperation = Extract<FolioAIEditOperation, {
|
|
6
|
+
type: "insertTableRow";
|
|
7
|
+
}>;
|
|
8
|
+
type TableRowPosition = NonNullable<InsertTableRowOperation["position"]>;
|
|
9
|
+
declare const EDIT_SCRIPT_TABLE_ROW_POSITION: {
|
|
10
|
+
readonly after: "after";
|
|
11
|
+
readonly before: "before";
|
|
12
|
+
};
|
|
5
13
|
type EditScriptStep = {
|
|
6
14
|
type: "insertParagraphAfter";
|
|
7
15
|
blockIndex: number;
|
|
@@ -46,6 +54,7 @@ type EditScriptStep = {
|
|
|
46
54
|
} | {
|
|
47
55
|
type: "insertTableRow";
|
|
48
56
|
blockIndex: number;
|
|
57
|
+
position?: TableRowPosition;
|
|
49
58
|
cellTexts: readonly string[];
|
|
50
59
|
} | {
|
|
51
60
|
type: "deleteTableRow";
|
|
@@ -74,4 +83,4 @@ type EditScriptResult = {
|
|
|
74
83
|
*/
|
|
75
84
|
declare const applyEditScript: (base: ArrayBuffer, script: EditScript) => Promise<Result<EditScriptResult, CompareDocxParseError | CompareDocxSerializeError>>;
|
|
76
85
|
//#endregion
|
|
77
|
-
export { EDIT_SCRIPT_UNRESOLVED_REASONS, EditScript, EditScriptResult, EditScriptStep, EditScriptUnresolvedReason, EditScriptUnresolvedStep, applyEditScript };
|
|
86
|
+
export { EDIT_SCRIPT_TABLE_ROW_POSITION, EDIT_SCRIPT_UNRESOLVED_REASONS, EditScript, EditScriptResult, EditScriptStep, EditScriptUnresolvedReason, EditScriptUnresolvedStep, applyEditScript };
|
package/dist/compare/scenario.js
CHANGED
|
@@ -24,6 +24,10 @@ import { Result, panic } from "better-result";
|
|
|
24
24
|
* dropped: a script silently doing less than it says would weaken every
|
|
25
25
|
* property built on it.
|
|
26
26
|
*/
|
|
27
|
+
const EDIT_SCRIPT_TABLE_ROW_POSITION = {
|
|
28
|
+
after: "after",
|
|
29
|
+
before: "before"
|
|
30
|
+
};
|
|
27
31
|
const EDIT_SCRIPT_UNRESOLVED_REASONS = Object.freeze([
|
|
28
32
|
"block-out-of-range",
|
|
29
33
|
"block-not-in-table",
|
|
@@ -160,7 +164,7 @@ const planStep = ({ step, blocks, nextOperationId }) => {
|
|
|
160
164
|
id: nextOperationId(),
|
|
161
165
|
type: "insertTableRow",
|
|
162
166
|
blockId: block.id,
|
|
163
|
-
position:
|
|
167
|
+
position: step.position ?? EDIT_SCRIPT_TABLE_ROW_POSITION.after,
|
|
164
168
|
cellTexts: [...step.cellTexts]
|
|
165
169
|
}]
|
|
166
170
|
} : {
|
|
@@ -260,4 +264,4 @@ const applyEditScript = async (base, script) => {
|
|
|
260
264
|
});
|
|
261
265
|
};
|
|
262
266
|
//#endregion
|
|
263
|
-
export { EDIT_SCRIPT_UNRESOLVED_REASONS, applyEditScript };
|
|
267
|
+
export { EDIT_SCRIPT_TABLE_ROW_POSITION, EDIT_SCRIPT_UNRESOLVED_REASONS, applyEditScript };
|
|
@@ -7,6 +7,7 @@ import { ensureParaIdsInDoc, ensureParaIdsInState } from "../prosemirror/extensi
|
|
|
7
7
|
import { createDocumentNumberingPlugin } from "../prosemirror/plugins/documentNumbering.js";
|
|
8
8
|
import { createDocumentStylesPlugin } from "../prosemirror/plugins/documentStyles.js";
|
|
9
9
|
import { schema } from "../prosemirror/schema/index.js";
|
|
10
|
+
import { proseDocumentParagraphSourceContract, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsParagraphSourceContract } from "../prosemirror/yjsParagraphSourceContract.js";
|
|
10
11
|
import { createHiddenEditorApi } from "./hiddenEditorApi.js";
|
|
11
12
|
import { panic } from "better-result";
|
|
12
13
|
import { EditorState } from "prosemirror-state";
|
|
@@ -125,9 +126,18 @@ function createHiddenEditorState(options) {
|
|
|
125
126
|
plugins
|
|
126
127
|
}));
|
|
127
128
|
collaborationModules.yProseMirror.prosemirrorToYXmlFragment(seedState.doc, collaboration.yXmlFragment);
|
|
129
|
+
const collaborationDocument = collaboration.yXmlFragment.doc;
|
|
130
|
+
if (!collaborationDocument) panic("A collaboration fragment must belong to a Yjs document before seeding.");
|
|
131
|
+
if (proseDocumentParagraphSourceContract(seedState.doc)) writeYjsParagraphSourceContract(collaborationDocument, seedState.doc);
|
|
128
132
|
collaboration.onSeeded?.();
|
|
129
133
|
}
|
|
130
134
|
let { doc } = collaborationModules.yProseMirror.initProseMirrorDoc(collaboration.yXmlFragment, activeSchema);
|
|
135
|
+
const collaborationDocument = collaboration.yXmlFragment.doc;
|
|
136
|
+
if (!collaborationDocument) panic("A collaboration fragment must belong to a Yjs document before loading.");
|
|
137
|
+
const collaborationContract = readYjsParagraphSourceContract(collaborationDocument);
|
|
138
|
+
const localContract = proseDocumentParagraphSourceContract(localDoc);
|
|
139
|
+
if (localContract && collaborationContract !== localContract) panic("The collaboration state belongs to a different paragraph-property source.");
|
|
140
|
+
if (collaborationContract) doc = withParagraphSourceContract(doc, collaborationContract);
|
|
131
141
|
const initializedState = normalizeSeedState(EditorState.create({
|
|
132
142
|
doc,
|
|
133
143
|
schema: activeSchema,
|
|
@@ -136,6 +146,7 @@ function createHiddenEditorState(options) {
|
|
|
136
146
|
if (!initializedState.doc.eq(doc)) {
|
|
137
147
|
collaborationModules.yProseMirror.prosemirrorToYXmlFragment(initializedState.doc, collaboration.yXmlFragment);
|
|
138
148
|
({doc} = collaborationModules.yProseMirror.initProseMirrorDoc(collaboration.yXmlFragment, activeSchema));
|
|
149
|
+
if (collaborationContract) doc = withParagraphSourceContract(doc, collaborationContract);
|
|
139
150
|
}
|
|
140
151
|
const startedAt = performance.now();
|
|
141
152
|
const state = normalizeSeedState(EditorState.create({
|
|
@@ -181,7 +181,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
181
181
|
const hfPageCountEstimate = layout?.pages.length ?? 1;
|
|
182
182
|
const hfClock = /* @__PURE__ */ new Date();
|
|
183
183
|
const buildHfOptions = (pageCount, fieldInputs) => {
|
|
184
|
-
const hfMeasureBlocks = (hfBlocks, hfWidth) => measureBlocks(hfBlocks, hfWidth, void 0, void 0, buildHeaderFooterFieldValues(hfBlocks, pageCount, hfClock, fieldInputs));
|
|
184
|
+
const hfMeasureBlocks = (hfBlocks, hfWidth) => measureBlocks(hfBlocks, hfWidth, void 0, void 0, buildHeaderFooterFieldValues(hfBlocks, pageCount, hfClock, fieldInputs), { allowEndTabOverflow: true });
|
|
185
185
|
return {
|
|
186
186
|
...flowOpts.styles ? { styles: flowOpts.styles } : {},
|
|
187
187
|
..._theme !== void 0 ? { theme: _theme } : {},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { layoutTextBoxContent } from "../../layout-engine/measure/textBoxParagraphLayout.js";
|
|
2
2
|
import { DEFAULT_TEXTBOX_MARGINS } from "../../layout-engine/types.js";
|
|
3
|
+
import { hasHorizontalFlip, hasVerticalFlip, parseRotationDegrees } from "../../utils/rotationBoundingBox.js";
|
|
3
4
|
import { HIT_REGION_KINDS } from "../primitives.js";
|
|
4
5
|
import { parseDisplayColor } from "./colors.js";
|
|
5
6
|
import { paintParagraphFragment } from "./paragraphPrimitives.js";
|
|
6
|
-
import { blockRegion } from "./regions.js";
|
|
7
|
+
import { blockRegion, createPageComposer } from "./regions.js";
|
|
7
8
|
import { resolveBorderStroke } from "./strokes.js";
|
|
8
9
|
import { paintTableBlock } from "./tablePrimitives.js";
|
|
9
10
|
import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
|
|
@@ -15,6 +16,38 @@ import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
|
|
|
15
16
|
* measurer used, so the paragraphs land where their heights were reserved.
|
|
16
17
|
*/
|
|
17
18
|
const paintTextBoxFragment = ({ composer, fragment, block, measure, context }) => {
|
|
19
|
+
const rotation = parseRotationDegrees(block.transform);
|
|
20
|
+
const flipH = hasHorizontalFlip(block.transform);
|
|
21
|
+
const flipV = hasVerticalFlip(block.transform);
|
|
22
|
+
if (rotation === 0 && !flipH && !flipV) {
|
|
23
|
+
paintUnrotatedTextBoxFragment({
|
|
24
|
+
composer,
|
|
25
|
+
fragment,
|
|
26
|
+
block,
|
|
27
|
+
measure,
|
|
28
|
+
context
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const inner = createPageComposer();
|
|
33
|
+
paintUnrotatedTextBoxFragment({
|
|
34
|
+
composer: inner,
|
|
35
|
+
fragment,
|
|
36
|
+
block,
|
|
37
|
+
measure,
|
|
38
|
+
context
|
|
39
|
+
});
|
|
40
|
+
composer.push([{
|
|
41
|
+
kind: "rotateGroup",
|
|
42
|
+
degrees: rotation,
|
|
43
|
+
originXPx: fragment.x + fragment.width / 2,
|
|
44
|
+
originYPx: fragment.y + fragment.height / 2,
|
|
45
|
+
...flipH ? { scaleX: -1 } : {},
|
|
46
|
+
...flipV ? { scaleY: -1 } : {},
|
|
47
|
+
children: inner.primitives()
|
|
48
|
+
}]);
|
|
49
|
+
};
|
|
50
|
+
const paintUnrotatedTextBoxFragment = ({ composer, fragment, block, measure, context }) => {
|
|
18
51
|
const primitives = [];
|
|
19
52
|
const box = {
|
|
20
53
|
xPx: fragment.x,
|
|
@@ -434,10 +434,13 @@ const paintClipGroup = ({ rect, children, regions }, context) => {
|
|
|
434
434
|
regions
|
|
435
435
|
}, inner);
|
|
436
436
|
};
|
|
437
|
-
const paintRotateGroup = ({ degrees, originXPx, originYPx, children }, context) => {
|
|
437
|
+
const paintRotateGroup = ({ degrees, originXPx, originYPx, scaleX, scaleY, children }, context) => {
|
|
438
438
|
const element = createTransparentGroupDiv(context);
|
|
439
439
|
element.style.transformOrigin = `${px(originXPx - context.originXPx)} ${px(originYPx - context.originYPx)}`;
|
|
440
|
-
|
|
440
|
+
const transforms = [`rotate(${degrees}deg)`];
|
|
441
|
+
if (scaleX === -1) transforms.push("scaleX(-1)");
|
|
442
|
+
if (scaleY === -1) transforms.push("scaleY(-1)");
|
|
443
|
+
element.style.transform = transforms.join(" ");
|
|
441
444
|
context.parent.append(element);
|
|
442
445
|
const inner = {
|
|
443
446
|
...context,
|
|
@@ -15,7 +15,7 @@ import { hasComplexScript, isComplexScriptCodePoint } from "../utils/scriptSegme
|
|
|
15
15
|
* advances rightward from its origin whatever the paragraph does.
|
|
16
16
|
*/
|
|
17
17
|
declare const glyphCellOffsetsPx: (run: DisplayGlyphRun) => readonly number[];
|
|
18
|
-
declare const DISPLAY_PRIMITIVE_KINDS: ("
|
|
18
|
+
declare const DISPLAY_PRIMITIVE_KINDS: ("glyphRun" | "rect" | "line" | "image" | "clipGroup" | "rotateGroup" | "opacityGroup")[];
|
|
19
19
|
/**
|
|
20
20
|
* Dash geometry of each stroke pattern, as multiples of the stroke thickness.
|
|
21
21
|
*
|
|
@@ -309,16 +309,20 @@ type DisplayClipGroup = {
|
|
|
309
309
|
readonly regions: readonly DisplayHitRegion[];
|
|
310
310
|
};
|
|
311
311
|
/**
|
|
312
|
-
* Rotates its children about `originXPx`/`originYPx`.
|
|
313
|
-
* watermark text and vertical (`w:textDirection`)
|
|
314
|
-
*
|
|
315
|
-
*
|
|
312
|
+
* Rotates and optionally reflects its children about `originXPx`/`originYPx`.
|
|
313
|
+
* Rotated images, text boxes, watermark text and vertical (`w:textDirection`)
|
|
314
|
+
* table text are all this. A scale of `-1` is a DrawingML flip; the scale is
|
|
315
|
+
* applied before the rotation, matching CSS and OOXML transform order.
|
|
316
316
|
*/
|
|
317
317
|
type DisplayRotateGroup = {
|
|
318
318
|
readonly kind: "rotateGroup";
|
|
319
319
|
readonly degrees: number;
|
|
320
320
|
readonly originXPx: number;
|
|
321
321
|
readonly originYPx: number;
|
|
322
|
+
/** Optional horizontal reflection about the transform origin. */
|
|
323
|
+
readonly scaleX?: -1;
|
|
324
|
+
/** Optional vertical reflection about the transform origin. */
|
|
325
|
+
readonly scaleY?: -1;
|
|
322
326
|
readonly children: readonly DisplayPrimitive[];
|
|
323
327
|
};
|
|
324
328
|
/** Uniform alpha over a subtree, for watermark washout and image opacity. */
|