@stll/folio-core 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.d.ts +15 -0
- package/dist/ai-edits/apply.js +271 -48
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +49 -5
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +13 -6
- package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
- package/dist/compare/__fixtures__/body-sequence.js +94 -6
- package/dist/compare/compare.d.ts +14 -7
- package/dist/compare/compare.js +136 -38
- package/dist/compare/formatting.d.ts +1 -1
- package/dist/compare/formatting.js +38 -9
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +233 -66
- package/dist/compare/types.d.ts +9 -7
- package/dist/compare/types.js +7 -5
- package/dist/compare/verification.d.ts +31 -12
- package/dist/compare/verification.js +106 -18
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +11 -9
- package/dist/controller/layoutPipeline.js +24 -3
- package/dist/display-list/build/watermarkPrimitives.js +15 -3
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +33 -5
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/headerFooterParser.js +8 -14
- package/dist/docx/paragraphParser.js +48 -2
- package/dist/docx/rezip.d.ts +10 -4
- package/dist/docx/rezip.js +54 -18
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +55 -31
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +22 -12
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +83 -18
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/settingsParser.js +3 -0
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.d.ts +2 -4
- package/dist/docx/watermarkParser.js +8 -9
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/headless-layout.js +14 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
- package/dist/layout-engine/index.js +11 -4
- package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
- package/dist/layout-engine/justifiedLineFit.js +4 -4
- package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
- package/dist/layout-engine/measure/measureBlocks.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +29 -29
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderWatermark.js +11 -4
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/commands/comments.js +24 -3
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +47 -9
- package/dist/prosemirror/conversion/toProseDoc.js +61 -14
- package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +3 -9
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/fontResolver.js +51 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/dist/utils/formatToStyle.js +41 -1
- package/dist/watermark/index.js +7 -0
- package/package.json +2 -2
package/dist/compare/compare.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
2
|
+
import { projectTableGeometry } from "../ai-edits/table-geometry.js";
|
|
2
3
|
import "../document-operations.js";
|
|
3
4
|
import { pairFolioDocumentStories } from "../document-stories.js";
|
|
4
5
|
import { planStoryCompare } from "./plan.js";
|
|
5
6
|
import { withFixedPackageDates } from "./reproducible-package.js";
|
|
6
7
|
import { CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "./types.js";
|
|
7
|
-
import {
|
|
8
|
+
import { classifyGeometryMismatch, classifyProjectionMismatch, projectSupportedInlineFormatting, revisedFinalParagraphMarks } from "./verification.js";
|
|
8
9
|
import { Result, panic } from "better-result";
|
|
9
10
|
//#region src/compare/compare.ts
|
|
10
11
|
/**
|
|
@@ -151,6 +152,14 @@ const parseComparison = async (base, target, options) => {
|
|
|
151
152
|
const reviewer = baseParse.value;
|
|
152
153
|
const targetReviewer = targetParse.value;
|
|
153
154
|
const existing = existingRevisionsOf(reviewer);
|
|
155
|
+
for (const { handle } of reviewer.listStories()) reviewer.resolveReviewedStory({
|
|
156
|
+
story: handle,
|
|
157
|
+
view: "final"
|
|
158
|
+
});
|
|
159
|
+
for (const { handle } of targetReviewer.listStories()) targetReviewer.resolveReviewedStory({
|
|
160
|
+
story: handle,
|
|
161
|
+
view: "final"
|
|
162
|
+
});
|
|
154
163
|
const pairs = [];
|
|
155
164
|
const unsupported = [];
|
|
156
165
|
for (const { baseStory, revisedStory: targetStory } of pairFolioDocumentStories(reviewer.listStories().map(({ handle }) => handle), targetReviewer.listStories().map(({ handle }) => handle))) {
|
|
@@ -170,14 +179,6 @@ const parseComparison = async (base, target, options) => {
|
|
|
170
179
|
});
|
|
171
180
|
continue;
|
|
172
181
|
}
|
|
173
|
-
reviewer.resolveReviewedStory({
|
|
174
|
-
story: baseStory,
|
|
175
|
-
view: "final"
|
|
176
|
-
});
|
|
177
|
-
targetReviewer.resolveReviewedStory({
|
|
178
|
-
story: targetStory,
|
|
179
|
-
view: "final"
|
|
180
|
-
});
|
|
181
182
|
const baseSnapshot = reviewer.snapshotStory(baseStory);
|
|
182
183
|
const targetSnapshot = targetReviewer.snapshotStory(targetStory);
|
|
183
184
|
if (!baseSnapshot || !targetSnapshot) {
|
|
@@ -236,50 +237,126 @@ const planComparison = ({ pairs }) => {
|
|
|
236
237
|
return Result.ok(planned);
|
|
237
238
|
};
|
|
238
239
|
/**
|
|
240
|
+
* What a skipped operation says about the plan that derived it.
|
|
241
|
+
*
|
|
242
|
+
* Two different things go wrong at apply time, and only one of them leaves the
|
|
243
|
+
* result unusable. A reason that says the plan did not match the document it
|
|
244
|
+
* was planned against is an engine defect — the operations came from this very
|
|
245
|
+
* snapshot moments earlier, so nothing should have moved under them, and a
|
|
246
|
+
* redline built on the rest is built on a document the plan no longer
|
|
247
|
+
* describes. A reason that says the applier had nothing to write, or could not
|
|
248
|
+
* write that shape THERE, leaves the redline standing and turns the question
|
|
249
|
+
* into "was anything lost", which is what the round-trip check answers a few
|
|
250
|
+
* lines below. Refusing on those instead trades a partial answer for none, and
|
|
251
|
+
* refuses a whole document because one paragraph sits inside a structure the
|
|
252
|
+
* block snapshot does not model — a text box, a content control — where a
|
|
253
|
+
* paragraph mark has nowhere to go.
|
|
254
|
+
*/
|
|
255
|
+
const COMPARE_SKIP_DISPOSITION = {
|
|
256
|
+
missingBlock: "fatal",
|
|
257
|
+
changedBlock: "fatal",
|
|
258
|
+
ambiguousFind: "fatal",
|
|
259
|
+
missingFind: "fatal",
|
|
260
|
+
unsupportedBlock: "unwritable",
|
|
261
|
+
unsupportedMode: "fatal",
|
|
262
|
+
atomicBatchRejected: "fatal",
|
|
263
|
+
preconditionFailed: "fatal",
|
|
264
|
+
staleRange: "fatal",
|
|
265
|
+
emptyOperation: "unwritable",
|
|
266
|
+
noopOperation: "unwritable",
|
|
267
|
+
documentVersionMismatch: "fatal",
|
|
268
|
+
documentNotEditable: "fatal"
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* The table each `insertTable` / `insertTableRow` in the plan should place,
|
|
272
|
+
* resolved against the target document the plan named it in.
|
|
273
|
+
*
|
|
274
|
+
* The plan is pure and names a table by index; the node lives in the other
|
|
275
|
+
* package, which only this stage holds. A request naming a table the target
|
|
276
|
+
* does not have resolves to nothing and the operation falls back to its cell
|
|
277
|
+
* texts, which is the same redline the comparison produced before.
|
|
278
|
+
*/
|
|
279
|
+
const resolveTableTemplates = (targetTables, requests) => {
|
|
280
|
+
const templates = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const { operationId, targetTableIndex, targetRowIndex } of requests) {
|
|
282
|
+
const table = targetTables.get(targetTableIndex);
|
|
283
|
+
if (!table) continue;
|
|
284
|
+
if (targetRowIndex === void 0) {
|
|
285
|
+
templates.set(operationId, table);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const row = table.maybeChild(targetRowIndex);
|
|
289
|
+
if (row) templates.set(operationId, row);
|
|
290
|
+
}
|
|
291
|
+
return templates;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
239
294
|
* Stage 3: write the planned operations into the base document as tracked
|
|
240
295
|
* changes, then check the work rather than trust it. Both directions of the
|
|
241
|
-
* round trip are checked, structure included: accepting the
|
|
242
|
-
* revisions must reproduce the target, and rejecting them
|
|
243
|
-
* base it was compared from. A difference the operation
|
|
244
|
-
* express would otherwise leave a redline that reads
|
|
296
|
+
* round trip are checked, structure and table geometry included: accepting the
|
|
297
|
+
* story's generated revisions must reproduce the target, and rejecting them
|
|
298
|
+
* must reproduce the base it was compared from. A difference the operation
|
|
299
|
+
* vocabulary cannot express would otherwise leave a redline that reads
|
|
300
|
+
* plausibly and is wrong.
|
|
245
301
|
*
|
|
246
302
|
* The check reports rather than throws. {@link compareDocx} decides what to do
|
|
247
303
|
* with an unverified result, because "give me your best attempt and tell me
|
|
248
304
|
* what you could not represent" and "give me nothing unless you can prove it"
|
|
249
305
|
* are both legitimate asks and only the caller knows which one it is making.
|
|
250
306
|
*/
|
|
251
|
-
const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChanges }, planned) => {
|
|
307
|
+
const applyComparison = ({ reviewer, targetReviewer, revisionStamp, granularity, numberingChanges }, planned) => {
|
|
252
308
|
const changes = [...numberingChanges];
|
|
253
309
|
const failures = [];
|
|
254
310
|
let idSeed = revisionStamp.idSeed;
|
|
311
|
+
let documentChanged = false;
|
|
255
312
|
for (const { pair, plan } of planned) {
|
|
256
313
|
changes.push(...plan.changes);
|
|
257
|
-
if (plan.operations.length === 0) continue;
|
|
314
|
+
if (plan.operations.length === 0 && plan.tableGeometryPairings.length === 0) continue;
|
|
258
315
|
const baseBeforeBlocks = reviewer.readReviewedStory({
|
|
259
316
|
story: pair.baseStory,
|
|
260
317
|
view: "final"
|
|
261
318
|
})?.snapshot.blocks ?? [];
|
|
262
319
|
const baseBefore = projectBlocks(baseBeforeBlocks);
|
|
263
|
-
const
|
|
320
|
+
const baseBeforeGeometry = projectTableGeometry(reviewer.storyTables({ story: pair.baseStory }));
|
|
321
|
+
const targetTables = new Map(targetReviewer.storyTables({ story: pair.targetStory }).map(({ index, node }) => [index, node]));
|
|
322
|
+
const afterGeometry = reviewer.matchStoryTableGeometry({
|
|
264
323
|
story: pair.baseStory,
|
|
265
|
-
|
|
324
|
+
targetTables,
|
|
325
|
+
pairings: plan.tableGeometryPairings,
|
|
266
326
|
revisionStamp: {
|
|
267
327
|
date: revisionStamp.date,
|
|
268
328
|
idSeed
|
|
269
|
-
},
|
|
270
|
-
wordDiff: { granularity },
|
|
271
|
-
batch: {
|
|
272
|
-
version: 1,
|
|
273
|
-
mode: "tracked-changes",
|
|
274
|
-
operations: plan.operations
|
|
275
329
|
}
|
|
276
330
|
});
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
331
|
+
const geometryChanged = afterGeometry > idSeed;
|
|
332
|
+
documentChanged ||= geometryChanged;
|
|
333
|
+
idSeed = afterGeometry;
|
|
334
|
+
if (plan.operations.length === 0 && !geometryChanged) continue;
|
|
335
|
+
if (plan.operations.length > 0) {
|
|
336
|
+
const { skipped, nextRevisionId } = reviewer.applyDocumentOperationsToStory({
|
|
337
|
+
story: pair.baseStory,
|
|
338
|
+
snapshot: pair.baseSnapshot,
|
|
339
|
+
revisionStamp: {
|
|
340
|
+
date: revisionStamp.date,
|
|
341
|
+
idSeed
|
|
342
|
+
},
|
|
343
|
+
wordDiff: { granularity },
|
|
344
|
+
batch: {
|
|
345
|
+
version: 1,
|
|
346
|
+
mode: "tracked-changes",
|
|
347
|
+
operations: plan.operations
|
|
348
|
+
},
|
|
349
|
+
tableTemplates: resolveTableTemplates(targetTables, plan.tableTemplates)
|
|
350
|
+
});
|
|
351
|
+
if (nextRevisionId === void 0) panic("The applier did not report where it left the revision-id counter", { story: pair.baseStory });
|
|
352
|
+
idSeed = nextRevisionId;
|
|
353
|
+
documentChanged = true;
|
|
354
|
+
const refused = skipped.filter(({ reason }) => COMPARE_SKIP_DISPOSITION[reason] === "fatal");
|
|
355
|
+
if (refused.length > 0) return Result.err(new CompareDocxApplyError({
|
|
356
|
+
message: "Some derived operations were refused, so the result would not match the target.",
|
|
357
|
+
skipped: refused
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
283
360
|
const acceptedStory = reviewer.readReviewedStory({
|
|
284
361
|
story: pair.baseStory,
|
|
285
362
|
view: "final"
|
|
@@ -324,13 +401,34 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
324
401
|
});
|
|
325
402
|
if (formattingFailure) failures.push(formattingFailure);
|
|
326
403
|
}
|
|
404
|
+
const geometryAcceptFailure = classifyGeometryMismatch({
|
|
405
|
+
invariant: "accept-reproduces-target",
|
|
406
|
+
story: pair.baseStory,
|
|
407
|
+
actual: projectTableGeometry(reviewer.storyTables({
|
|
408
|
+
story: pair.baseStory,
|
|
409
|
+
view: "final"
|
|
410
|
+
})),
|
|
411
|
+
expected: projectTableGeometry(targetReviewer.storyTables({ story: pair.targetStory }))
|
|
412
|
+
});
|
|
413
|
+
if (geometryAcceptFailure) failures.push(geometryAcceptFailure);
|
|
414
|
+
const geometryRejectFailure = classifyGeometryMismatch({
|
|
415
|
+
invariant: "reject-reproduces-base",
|
|
416
|
+
story: pair.baseStory,
|
|
417
|
+
actual: projectTableGeometry(reviewer.storyTables({
|
|
418
|
+
story: pair.baseStory,
|
|
419
|
+
view: "original"
|
|
420
|
+
})),
|
|
421
|
+
expected: baseBeforeGeometry
|
|
422
|
+
});
|
|
423
|
+
if (geometryRejectFailure) failures.push(geometryRejectFailure);
|
|
327
424
|
}
|
|
328
425
|
return Result.ok({
|
|
329
426
|
changes,
|
|
330
427
|
verification: failures.length === 0 ? { status: "verified" } : {
|
|
331
428
|
status: "unverified",
|
|
332
429
|
failures
|
|
333
|
-
}
|
|
430
|
+
},
|
|
431
|
+
documentChanged
|
|
334
432
|
});
|
|
335
433
|
};
|
|
336
434
|
/**
|
|
@@ -352,13 +450,13 @@ const applyComparison = ({ reviewer, revisionStamp, granularity, numberingChange
|
|
|
352
450
|
* composition let the benchmark's own composition disagree with the shipped
|
|
353
451
|
* one within a single run.
|
|
354
452
|
*/
|
|
355
|
-
const serializeComparison = async ({ baseBuffer, baseCarriedRevisions, reviewer, packageDate },
|
|
356
|
-
if (!baseCarriedRevisions &&
|
|
357
|
-
const
|
|
358
|
-
const [
|
|
359
|
-
if (
|
|
360
|
-
message: `A container's final paragraph mark carries a ${
|
|
361
|
-
|
|
453
|
+
const serializeComparison = async ({ baseBuffer, baseCarriedRevisions, reviewer, packageDate, revisionStamp }, { documentChanged }) => {
|
|
454
|
+
if (!baseCarriedRevisions && !documentChanged) return Result.ok(baseBuffer);
|
|
455
|
+
const revisions = revisedFinalParagraphMarks(reviewer.toDocument(), { since: revisionStamp.idSeed });
|
|
456
|
+
const [first] = revisions;
|
|
457
|
+
if (first !== void 0) return Result.err(new CompareDocxFinalParagraphMarkError({
|
|
458
|
+
message: `A container's final paragraph mark carries a ${first.kind}, which no consumer can resolve: ${first.container} paragraph ${String(first.paragraphIndex)}.`,
|
|
459
|
+
revisions
|
|
362
460
|
}));
|
|
363
461
|
return await Result.tryPromise({
|
|
364
462
|
try: async () => await withFixedPackageDates(await reviewer.toBuffer(), packageDate),
|
|
@@ -399,7 +497,7 @@ const compareDocx = async (base, target, options) => {
|
|
|
399
497
|
failures: verification.failures
|
|
400
498
|
}));
|
|
401
499
|
}
|
|
402
|
-
const serialized = await serializeComparison(parsed.value,
|
|
500
|
+
const serialized = await serializeComparison(parsed.value, applied.value);
|
|
403
501
|
if (serialized.isErr()) return Result.err(serialized.error);
|
|
404
502
|
return Result.ok({
|
|
405
503
|
buffer: serialized.value,
|
|
@@ -15,7 +15,7 @@ type InlineFormattingSegmentsOptions = {
|
|
|
15
15
|
maxSegments: number;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
|
-
* Segments where `targetBlock`'s
|
|
18
|
+
* Segments where `targetBlock`'s supported run formatting differs from
|
|
19
19
|
* `baseBlock`'s, for two blocks that carry the same text. Returns `null` only
|
|
20
20
|
* when the diff would exceed `maxSegments`.
|
|
21
21
|
*
|
|
@@ -1,12 +1,41 @@
|
|
|
1
|
+
import { resolveColorToHex } from "../utils/colorResolver.js";
|
|
1
2
|
//#region src/compare/formatting.ts
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const normalizeInlineFormattingColor = (color) => resolveColorToHex(color === void 0 ? void 0 : { rgb: color }, null);
|
|
4
|
+
const changedStringValue = (baseEffective, targetEffective, baseAuthored, targetAuthored) => {
|
|
5
|
+
if (baseAuthored !== targetAuthored) return targetAuthored ?? null;
|
|
6
|
+
return baseEffective === targetEffective ? void 0 : targetEffective ?? null;
|
|
7
|
+
};
|
|
8
|
+
const changedNumberValue = (baseEffective, targetEffective, baseAuthored, targetAuthored) => {
|
|
9
|
+
if (baseAuthored !== targetAuthored) return targetAuthored ?? null;
|
|
10
|
+
return baseEffective === targetEffective ? void 0 : targetEffective ?? null;
|
|
11
|
+
};
|
|
12
|
+
const changedSupportedFormatting = (base, target) => {
|
|
13
|
+
const baseDirect = base.directFormatting ?? {};
|
|
14
|
+
const targetDirect = target.directFormatting ?? {};
|
|
15
|
+
const formatting = {};
|
|
16
|
+
const changedBoolean = (property) => {
|
|
17
|
+
if (baseDirect[property] !== targetDirect[property]) return targetDirect[property] === true;
|
|
18
|
+
return Boolean(base[property]) === Boolean(target[property]) ? void 0 : Boolean(target[property]);
|
|
19
|
+
};
|
|
20
|
+
for (const property of [
|
|
21
|
+
"bold",
|
|
22
|
+
"italic",
|
|
23
|
+
"underline",
|
|
24
|
+
"strike"
|
|
25
|
+
]) {
|
|
26
|
+
const changed = changedBoolean(property);
|
|
27
|
+
if (changed !== void 0) formatting[property] = changed;
|
|
28
|
+
}
|
|
29
|
+
const fontFamily = changedStringValue(base.fontFamily, target.fontFamily, baseDirect.fontFamily, targetDirect.fontFamily);
|
|
30
|
+
if (fontFamily !== void 0) formatting.fontFamily = fontFamily;
|
|
31
|
+
const fontSizePt = changedNumberValue(base.fontSizePt, target.fontSizePt, baseDirect.fontSizePt, targetDirect.fontSizePt);
|
|
32
|
+
if (fontSizePt !== void 0) formatting.fontSizePt = fontSizePt;
|
|
33
|
+
const color = changedStringValue(normalizeInlineFormattingColor(base.color), normalizeInlineFormattingColor(target.color), normalizeInlineFormattingColor(baseDirect.color ?? void 0), normalizeInlineFormattingColor(targetDirect.color ?? void 0));
|
|
34
|
+
if (color !== void 0) formatting.color = color;
|
|
35
|
+
return formatting;
|
|
36
|
+
};
|
|
37
|
+
const sameInlineFormatting = (left, right) => left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strike === right.strike && left.fontFamily === right.fontFamily && left.fontSizePt === right.fontSizePt && left.color === right.color;
|
|
38
|
+
const hasInlineFormatting = (formatting) => formatting.bold !== void 0 || formatting.italic !== void 0 || formatting.underline !== void 0 || formatting.strike !== void 0 || formatting.fontFamily !== void 0 || formatting.fontSizePt !== void 0 || formatting.color !== void 0;
|
|
10
39
|
/**
|
|
11
40
|
* A block's runs, or `null` when they cannot describe the block's text.
|
|
12
41
|
* Non-text inline content (a field, an image) leaves the concatenated run text
|
|
@@ -18,7 +47,7 @@ const previewRunsForBlock = (block) => {
|
|
|
18
47
|
return runs.map(({ text }) => text).join("") === block.text ? runs : null;
|
|
19
48
|
};
|
|
20
49
|
/**
|
|
21
|
-
* Segments where `targetBlock`'s
|
|
50
|
+
* Segments where `targetBlock`'s supported run formatting differs from
|
|
22
51
|
* `baseBlock`'s, for two blocks that carry the same text. Returns `null` only
|
|
23
52
|
* when the diff would exceed `maxSegments`.
|
|
24
53
|
*
|
package/dist/compare/plan.d.ts
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
import { FolioAIEditOperation, FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
|
+
import { TableGeometryPairing } from "../ai-edits/table-geometry.js";
|
|
2
3
|
import { FolioDocumentStoryHandle } from "../ai-edits/headless.js";
|
|
3
4
|
import { CompareChange } from "./types.js";
|
|
4
5
|
//#region src/compare/plan.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A table the target document already holds, which the operation naming it
|
|
8
|
+
* should place verbatim instead of rebuilding from its cell texts.
|
|
9
|
+
*
|
|
10
|
+
* The plan is pure and sees only block snapshots, so it names the table by the
|
|
11
|
+
* index the snapshot numbers tables with; the caller, which has both
|
|
12
|
+
* documents, resolves the index to the node.
|
|
13
|
+
*/
|
|
14
|
+
type CompareTableTemplateRequest = {
|
|
15
|
+
/** The `insertTable` or `insertTableRow` operation this table belongs to. */
|
|
16
|
+
operationId: string;
|
|
17
|
+
/** Index of the table in the TARGET story. */
|
|
18
|
+
targetTableIndex: number;
|
|
19
|
+
/** Set for a row insertion: which of that table's rows to place. */
|
|
20
|
+
targetRowIndex?: number;
|
|
21
|
+
};
|
|
5
22
|
type CompareStoryPlan = {
|
|
6
23
|
changes: CompareChange[];
|
|
7
24
|
operations: FolioAIEditOperation[];
|
|
25
|
+
/**
|
|
26
|
+
* Where an operation's table comes from. Kept beside the operations rather
|
|
27
|
+
* than inside them because a table node is not JSON, and the serialized
|
|
28
|
+
* operation contract describes a table by its cell texts.
|
|
29
|
+
*/
|
|
30
|
+
tableTemplates: CompareTableTemplateRequest[];
|
|
31
|
+
/**
|
|
32
|
+
* Base cells the alignment put opposite a target cell. Their tables, rows
|
|
33
|
+
* and cells are the ones whose `w:tblPr` / `w:trPr` / `w:tcPr` the caller
|
|
34
|
+
* matches: a table that stayed in place while its widths, shading or header
|
|
35
|
+
* row changed moves no block, so no operation carries the difference.
|
|
36
|
+
*/
|
|
37
|
+
tableGeometryPairings: TableGeometryPairing[];
|
|
8
38
|
};
|
|
9
39
|
type PlanStoryCompareOptions = {
|
|
10
40
|
story: FolioDocumentStoryHandle;
|
|
@@ -19,4 +49,4 @@ type PlanStoryCompareOptions = {
|
|
|
19
49
|
*/
|
|
20
50
|
declare const planStoryCompare: ({ story, baseSnapshot, targetSnapshot, maxOperations }: PlanStoryCompareOptions) => CompareStoryPlan | null;
|
|
21
51
|
//#endregion
|
|
22
|
-
export { CompareStoryPlan, PlanStoryCompareOptions, planStoryCompare };
|
|
52
|
+
export { CompareStoryPlan, CompareTableTemplateRequest, PlanStoryCompareOptions, planStoryCompare };
|