@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/plan.js
CHANGED
|
@@ -457,16 +457,8 @@ const isEmptyParagraphNode = (block, snapshot) => {
|
|
|
457
457
|
const anchor = snapshot.anchors[block.id] ?? panic("A comparison snapshot block has no matching anchor", { blockId: block.id });
|
|
458
458
|
return block.text === "" && block.table === void 0 && anchor.to - anchor.from === 2;
|
|
459
459
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const targetBlocks = targetSnapshot.blocks;
|
|
463
|
-
const baseLast = baseBlocks.at(-1);
|
|
464
|
-
const targetLast = targetBlocks.at(-1);
|
|
465
|
-
const terminalCarrierPair = story.type === "main" && baseLast !== void 0 && isEmptyParagraphNode(baseLast, baseSnapshot) && targetLast !== void 0 && isEmptyParagraphNode(targetLast, targetSnapshot) ? {
|
|
466
|
-
type: "pair",
|
|
467
|
-
baseBlock: baseLast,
|
|
468
|
-
targetBlock: targetLast
|
|
469
|
-
} : null;
|
|
460
|
+
/** Align both stories, optionally holding the two last paragraphs out as a pair. */
|
|
461
|
+
const alignedSteps = (baseBlocks, targetBlocks, terminalCarrierPair) => {
|
|
470
462
|
const alignedBaseBlocks = terminalCarrierPair ? baseBlocks.slice(0, -1) : baseBlocks;
|
|
471
463
|
const alignedTargetBlocks = terminalCarrierPair ? targetBlocks.slice(0, -1) : targetBlocks;
|
|
472
464
|
const steps = [];
|
|
@@ -485,6 +477,65 @@ const buildSteps = ({ story, baseSnapshot, targetSnapshot }) => {
|
|
|
485
477
|
return steps;
|
|
486
478
|
};
|
|
487
479
|
/**
|
|
480
|
+
* Whether an alignment left the story's last paragraph removed with no
|
|
481
|
+
* paragraph of its own container surviving in front of it.
|
|
482
|
+
*
|
|
483
|
+
* That paragraph's mark is the one a story cannot lose, and a deleted mark
|
|
484
|
+
* joins two paragraphs of ONE container, so with nothing of that container in
|
|
485
|
+
* front of it there is no chain a removal could run down. The paragraph then
|
|
486
|
+
* stays whatever the plan says, and the story ends on a blank line the target
|
|
487
|
+
* does not have — unless the words the target ends with are written into it,
|
|
488
|
+
* which is what reserving it for the target's last paragraph does.
|
|
489
|
+
*/
|
|
490
|
+
const terminalCarrierIsStranded = (steps, baseBlocks) => {
|
|
491
|
+
const carrier = baseBlocks.at(-1);
|
|
492
|
+
if (carrier === void 0) return false;
|
|
493
|
+
const removed = new Set(steps.flatMap((step) => step.type === "baseOnly" ? [step.block.id] : []));
|
|
494
|
+
if (!removed.has(carrier.id)) return false;
|
|
495
|
+
const container = containerKeyOf(carrier);
|
|
496
|
+
for (let index = baseBlocks.length - 2; index >= 0; index--) {
|
|
497
|
+
const block = baseBlocks[index];
|
|
498
|
+
if (block === void 0 || containerKeyOf(block) !== container) return true;
|
|
499
|
+
if (!removed.has(block.id)) return false;
|
|
500
|
+
}
|
|
501
|
+
return true;
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* Whether an alignment ADDED the story's last paragraph with something other
|
|
505
|
+
* than paragraphs between it and the base's.
|
|
506
|
+
*
|
|
507
|
+
* An inserted mark at a container's end has the same problem its deletion
|
|
508
|
+
* does: nothing follows it, so rejecting the addition cannot close the break
|
|
509
|
+
* back over the next paragraph. The applier rotates instead — the paragraph
|
|
510
|
+
* the run was appended after takes the inserted mark, and the last one takes
|
|
511
|
+
* the free mark — and that rotation reaches only across paragraphs. A table
|
|
512
|
+
* among them stops it, and the words the target ends with have to be written
|
|
513
|
+
* into the base's own last paragraph instead.
|
|
514
|
+
*/
|
|
515
|
+
const addedTerminalCarrierIsStranded = (steps, baseBlocks, targetBlocks) => {
|
|
516
|
+
const baseLast = baseBlocks.at(-1);
|
|
517
|
+
const targetLast = targetBlocks.at(-1);
|
|
518
|
+
if (baseLast === void 0 || targetLast === void 0) return false;
|
|
519
|
+
if (!steps.some((step) => step.type === "targetOnly" && step.block.id === targetLast.id)) return false;
|
|
520
|
+
const baseLastStep = steps.findIndex((step) => step.type === "pair" && step.baseBlock.id === baseLast.id || step.type === "baseOnly" && step.block.id === baseLast.id);
|
|
521
|
+
return baseLastStep === -1 || steps.slice(baseLastStep + 1).some((step) => step.type !== "targetOnly");
|
|
522
|
+
};
|
|
523
|
+
const buildSteps = ({ baseSnapshot, targetSnapshot }) => {
|
|
524
|
+
const baseBlocks = baseSnapshot.blocks;
|
|
525
|
+
const targetBlocks = targetSnapshot.blocks;
|
|
526
|
+
const baseLast = baseBlocks.at(-1);
|
|
527
|
+
const targetLast = targetBlocks.at(-1);
|
|
528
|
+
const carrierPair = baseLast !== void 0 && targetLast !== void 0 ? {
|
|
529
|
+
type: "pair",
|
|
530
|
+
baseBlock: baseLast,
|
|
531
|
+
targetBlock: targetLast
|
|
532
|
+
} : null;
|
|
533
|
+
const bothStoriesEndBlank = carrierPair !== null && isEmptyParagraphNode(carrierPair.baseBlock, baseSnapshot) && isEmptyParagraphNode(carrierPair.targetBlock, targetSnapshot);
|
|
534
|
+
const steps = alignedSteps(baseBlocks, targetBlocks, bothStoriesEndBlank ? carrierPair : null);
|
|
535
|
+
if (carrierPair === null || bothStoriesEndBlank || !shareAContainer(carrierPair.baseBlock, carrierPair.targetBlock) || !(terminalCarrierIsStranded(steps, baseBlocks) || addedTerminalCarrierIsStranded(steps, baseBlocks, targetBlocks))) return steps;
|
|
536
|
+
return alignedSteps(baseBlocks, targetBlocks, carrierPair);
|
|
537
|
+
};
|
|
538
|
+
/**
|
|
488
539
|
* Two blocks are in the same container when a paragraph mark between them
|
|
489
540
|
* exists at all: two body paragraphs, or two paragraphs of one table cell. A
|
|
490
541
|
* mark cannot span a cell boundary, so a split or a merge across one is not a
|
|
@@ -714,67 +765,178 @@ const lastBlockByContainer = (blocks) => {
|
|
|
714
765
|
return last;
|
|
715
766
|
};
|
|
716
767
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
768
|
+
* Rewrite the plan so a container's final paragraph mark survives the removal
|
|
769
|
+
* of the paragraphs that end it.
|
|
719
770
|
*
|
|
720
771
|
* A deleted paragraph mark means "merge this paragraph into the following
|
|
721
772
|
* one". A container's final paragraph has no following one, so the mark cannot
|
|
722
773
|
* say it: the applier leaves that mark alone, and the removal has to be
|
|
723
|
-
* expressed one paragraph earlier. The
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
774
|
+
* expressed one paragraph earlier. The carrier is that final paragraph, and
|
|
775
|
+
* every trailing removal resolves onto it.
|
|
776
|
+
*
|
|
777
|
+
* Nothing is inserted where the run was: the chain runs from the last
|
|
778
|
+
* SURVIVING paragraph forward. Its mark goes, each removed paragraph's mark
|
|
779
|
+
* goes with it, and the carrier loses its words and keeps its mark as the
|
|
780
|
+
* paragraph the merged text lands in. A carrier that has no words to lose is
|
|
781
|
+
* not deleted at all: its removal is entirely the marks in front of it, and an
|
|
782
|
+
* operation that would write no revision belongs nowhere in the plan.
|
|
727
783
|
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
784
|
+
* Something IS inserted there: the inserted paragraphs stand where the removed
|
|
785
|
+
* ones did, and the last of them lands INSIDE the carrier as inserted runs
|
|
786
|
+
* before its kept mark, so the carrier is the target's last paragraph rather
|
|
787
|
+
* than a blank line after it. The other inserted paragraphs carry inserted
|
|
788
|
+
* marks of their own and sit before the carrier. The mark count then works out
|
|
789
|
+
* on its own — one mark deleted for every removed paragraph but the carrier,
|
|
790
|
+
* one inserted for every added paragraph but the last — so the chain is not
|
|
791
|
+
* rotated as well.
|
|
734
792
|
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
793
|
+
* The carrier keeps its own mark either way, and a paragraph's properties live
|
|
794
|
+
* on its mark, so the merged paragraph ends up with the carrier's. Those
|
|
795
|
+
* properties therefore become the target's, written as `w:pPrChange` so
|
|
796
|
+
* rejecting restores what the carrier had. That is bookkeeping for the merge
|
|
797
|
+
* rather than an edit of its own, so it adds no entry to the change list: what
|
|
798
|
+
* the reader is told is that paragraphs were removed and paragraphs added.
|
|
738
799
|
*/
|
|
739
|
-
const
|
|
800
|
+
const withTrailingDeletionRules = ({ baseSnapshot, targetSnapshot, operations, nextOperationId }) => {
|
|
801
|
+
const plan = [...operations];
|
|
802
|
+
const deletionIndexByBlockId = /* @__PURE__ */ new Map();
|
|
803
|
+
const insertIndexesByAnchor = /* @__PURE__ */ new Map();
|
|
804
|
+
const tableAnchorIds = /* @__PURE__ */ new Set();
|
|
805
|
+
const markedBlockIds = /* @__PURE__ */ new Set();
|
|
806
|
+
for (const [index, operation] of plan.entries()) switch (operation.type) {
|
|
807
|
+
case "deleteBlock":
|
|
808
|
+
deletionIndexByBlockId.set(operation.blockId, index);
|
|
809
|
+
break;
|
|
810
|
+
case "insertBeforeBlock":
|
|
811
|
+
case "insertAfterBlock": {
|
|
812
|
+
const placed = insertIndexesByAnchor.get(operation.blockId) ?? [];
|
|
813
|
+
placed.push(index);
|
|
814
|
+
insertIndexesByAnchor.set(operation.blockId, placed);
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
case "insertTable":
|
|
818
|
+
tableAnchorIds.add(operation.blockId);
|
|
819
|
+
break;
|
|
820
|
+
case "splitBlock":
|
|
821
|
+
case "mergeBlockWithNext":
|
|
822
|
+
markedBlockIds.add(operation.blockId);
|
|
823
|
+
break;
|
|
824
|
+
default: break;
|
|
825
|
+
}
|
|
826
|
+
if (deletionIndexByBlockId.size === 0) return plan;
|
|
740
827
|
const blocks = baseSnapshot.blocks;
|
|
741
|
-
const deletedBlockIds = new Set(operations.flatMap((operation) => operation.type === "deleteBlock" ? [operation.blockId] : []));
|
|
742
|
-
if (deletedBlockIds.size === 0) return [];
|
|
743
|
-
const placedAtBlockIds = new Set(operations.flatMap((operation) => operation.type === "insertBeforeBlock" || operation.type === "insertAfterBlock" || operation.type === "insertTable" ? [operation.blockId] : []));
|
|
744
|
-
const markedBlockIds = new Set(operations.flatMap((operation) => operation.type === "splitBlock" || operation.type === "mergeBlockWithNext" ? [operation.blockId] : []));
|
|
745
|
-
const targetLastByContainer = lastBlockByContainer(targetSnapshot.blocks);
|
|
746
828
|
const indexById = new Map(blocks.map((block, index) => [block.id, index]));
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
829
|
+
/**
|
|
830
|
+
* Walk back from a container's last block over the run of deleted
|
|
831
|
+
* paragraphs it ends with. A block of another container in between — a
|
|
832
|
+
* table between two body paragraphs, a table nested in a cell — ends the
|
|
833
|
+
* walk: no mark joins across it.
|
|
834
|
+
*/
|
|
835
|
+
const trailingRunOf = (container, carrier) => {
|
|
836
|
+
const blockIds = [];
|
|
837
|
+
const insertIndexes = [];
|
|
751
838
|
let chainStart = null;
|
|
752
|
-
let
|
|
839
|
+
let holdsAnInsertedTable = false;
|
|
840
|
+
let index = indexById.get(carrier.id) ?? panic("A container's last block is not in the snapshot it came from", { blockId: carrier.id });
|
|
753
841
|
for (; index >= 0; index--) {
|
|
754
842
|
const block = blocks[index];
|
|
755
843
|
if (block === void 0 || containerKeyOf(block) !== container) break;
|
|
756
|
-
if (!
|
|
844
|
+
if (!deletionIndexByBlockId.has(block.id)) {
|
|
757
845
|
chainStart = markedBlockIds.has(block.id) ? null : block;
|
|
758
846
|
break;
|
|
759
847
|
}
|
|
760
|
-
|
|
848
|
+
blockIds.push(block.id);
|
|
849
|
+
insertIndexes.push(...insertIndexesByAnchor.get(block.id) ?? []);
|
|
850
|
+
holdsAnInsertedTable ||= tableAnchorIds.has(block.id);
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
blockIds,
|
|
854
|
+
chainStart,
|
|
855
|
+
insertIndexes: insertIndexes.toSorted((left, right) => left - right),
|
|
856
|
+
holdsAnInsertedTable
|
|
857
|
+
};
|
|
858
|
+
};
|
|
859
|
+
const targetLastByContainer = lastBlockByContainer(targetSnapshot.blocks);
|
|
860
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
861
|
+
const appended = [];
|
|
862
|
+
for (const [container, carrier] of lastBlockByContainer(blocks)) {
|
|
863
|
+
const carrierDeletionIndex = deletionIndexByBlockId.get(carrier.id);
|
|
864
|
+
if (carrierDeletionIndex === void 0) continue;
|
|
865
|
+
const run = trailingRunOf(container, carrier);
|
|
866
|
+
if (run.holdsAnInsertedTable) continue;
|
|
867
|
+
const lastInsertIndex = run.insertIndexes.at(-1);
|
|
868
|
+
if (lastInsertIndex === void 0) {
|
|
869
|
+
if (run.chainStart) appended.push({
|
|
870
|
+
id: nextOperationId(),
|
|
871
|
+
type: "mergeBlockWithNext",
|
|
872
|
+
blockId: run.chainStart.id
|
|
873
|
+
});
|
|
874
|
+
if (isEmptyParagraphNode(carrier, baseSnapshot)) dropped.add(carrierDeletionIndex);
|
|
875
|
+
} else {
|
|
876
|
+
const lastInsert = plan[lastInsertIndex];
|
|
877
|
+
if (lastInsert === void 0 || lastInsert.type !== "insertBeforeBlock" && lastInsert.type !== "insertAfterBlock" || lastInsert.moveId !== void 0) continue;
|
|
878
|
+
const carrierDeletion = plan[carrierDeletionIndex];
|
|
879
|
+
if (carrierDeletion?.type !== "deleteBlock" || carrierDeletion.moveId !== void 0) continue;
|
|
880
|
+
dropped.add(lastInsertIndex);
|
|
881
|
+
if (carrier.text === lastInsert.text) dropped.add(carrierDeletionIndex);
|
|
882
|
+
else plan[carrierDeletionIndex] = {
|
|
883
|
+
id: carrierDeletion.id,
|
|
884
|
+
type: "replaceBlock",
|
|
885
|
+
blockId: carrier.id,
|
|
886
|
+
text: lastInsert.text
|
|
887
|
+
};
|
|
888
|
+
for (const index of run.insertIndexes.slice(0, -1)) {
|
|
889
|
+
const insert = plan[index];
|
|
890
|
+
if (insert?.type !== "insertBeforeBlock" && insert?.type !== "insertAfterBlock") continue;
|
|
891
|
+
plan[index] = {
|
|
892
|
+
id: insert.id,
|
|
893
|
+
type: "insertBeforeBlock",
|
|
894
|
+
blockId: carrier.id,
|
|
895
|
+
text: insert.text,
|
|
896
|
+
...insert.moveId !== void 0 && { moveId: insert.moveId },
|
|
897
|
+
styleId: insert.styleId ?? null,
|
|
898
|
+
listLevel: insert.listLevel ?? null
|
|
899
|
+
};
|
|
900
|
+
}
|
|
761
901
|
}
|
|
762
|
-
if (placedInRun) continue;
|
|
763
|
-
if (chainStart) added.push({
|
|
764
|
-
id: nextOperationId(),
|
|
765
|
-
type: "mergeBlockWithNext",
|
|
766
|
-
blockId: chainStart.id
|
|
767
|
-
});
|
|
768
902
|
const targetCarrier = targetLastByContainer.get(container);
|
|
769
903
|
const properties = targetCarrier && changedParagraphProperties(carrier, targetCarrier);
|
|
770
|
-
if (properties)
|
|
904
|
+
if (properties) appended.push({
|
|
771
905
|
id: nextOperationId(),
|
|
772
906
|
type: "setBlockParagraphProperties",
|
|
773
907
|
blockId: carrier.id,
|
|
774
908
|
properties
|
|
775
909
|
});
|
|
776
910
|
}
|
|
777
|
-
return
|
|
911
|
+
return [...plan.filter((_, index) => !dropped.has(index)), ...appended];
|
|
912
|
+
};
|
|
913
|
+
const cellCoordinate = ({ tableIndex, rowIndex, cellIndex }) => ({
|
|
914
|
+
tableIndex,
|
|
915
|
+
rowIndex,
|
|
916
|
+
cellIndex
|
|
917
|
+
});
|
|
918
|
+
/**
|
|
919
|
+
* The cells the alignment paired, one entry each. A cell holds several
|
|
920
|
+
* paragraphs and each pairs on its own, so the first pairing of a cell is the
|
|
921
|
+
* one kept: later ones would name the same two cells.
|
|
922
|
+
*/
|
|
923
|
+
const tableGeometryPairingsOf = (steps) => {
|
|
924
|
+
const pairings = [];
|
|
925
|
+
const seen = /* @__PURE__ */ new Set();
|
|
926
|
+
for (const step of steps) {
|
|
927
|
+
if (step.type !== "pair") continue;
|
|
928
|
+
const base = step.baseBlock.table;
|
|
929
|
+
const target = step.targetBlock.table;
|
|
930
|
+
if (!base || !target) continue;
|
|
931
|
+
const key = `${String(base.tableIndex)}:${String(base.rowIndex)}:${String(base.cellIndex)}`;
|
|
932
|
+
if (seen.has(key)) continue;
|
|
933
|
+
seen.add(key);
|
|
934
|
+
pairings.push({
|
|
935
|
+
base: cellCoordinate(base),
|
|
936
|
+
target: cellCoordinate(target)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
return pairings;
|
|
778
940
|
};
|
|
779
941
|
/**
|
|
780
942
|
* Plan one story's comparison, or `null` when it needs more operations than
|
|
@@ -782,7 +944,6 @@ const trailingDeletionOperations = ({ baseSnapshot, targetSnapshot, operations,
|
|
|
782
944
|
*/
|
|
783
945
|
const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations }) => {
|
|
784
946
|
const steps = buildSteps({
|
|
785
|
-
story,
|
|
786
947
|
baseSnapshot,
|
|
787
948
|
targetSnapshot
|
|
788
949
|
});
|
|
@@ -794,6 +955,7 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations }
|
|
|
794
955
|
const anchorIds = nextBaseBlockIdByStep(steps);
|
|
795
956
|
const changes = [];
|
|
796
957
|
const operations = [];
|
|
958
|
+
const tableTemplates = [];
|
|
797
959
|
/**
|
|
798
960
|
* The anchor everything past the base document's content hangs from: its
|
|
799
961
|
* last BODY-LEVEL paragraph, which the format guarantees exists because a
|
|
@@ -1056,23 +1218,20 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations }
|
|
|
1056
1218
|
targetBlockIds: step.blocks.map(({ id }) => id)
|
|
1057
1219
|
});
|
|
1058
1220
|
const before = anchorIds[stepIndex] ?? null;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
position: "before",
|
|
1065
|
-
rows
|
|
1066
|
-
});
|
|
1067
|
-
break;
|
|
1068
|
-
}
|
|
1069
|
-
if (tailAnchorId !== null) operations.push({
|
|
1070
|
-
id: nextOperationId(),
|
|
1221
|
+
const anchorBlockId = before ?? tailAnchorId;
|
|
1222
|
+
if (anchorBlockId === null) break;
|
|
1223
|
+
const operationId = nextOperationId();
|
|
1224
|
+
operations.push({
|
|
1225
|
+
id: operationId,
|
|
1071
1226
|
type: "insertTable",
|
|
1072
|
-
blockId:
|
|
1073
|
-
position: "after",
|
|
1227
|
+
blockId: anchorBlockId,
|
|
1228
|
+
position: before === null ? "after" : "before",
|
|
1074
1229
|
rows
|
|
1075
1230
|
});
|
|
1231
|
+
tableTemplates.push({
|
|
1232
|
+
operationId,
|
|
1233
|
+
targetTableIndex: step.location.tableIndex
|
|
1234
|
+
});
|
|
1076
1235
|
break;
|
|
1077
1236
|
}
|
|
1078
1237
|
case "targetRow": {
|
|
@@ -1093,13 +1252,19 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations }
|
|
|
1093
1252
|
for (const block of step.blocks) pushInsertOperation(block, anchorIds[stepIndex] ?? null);
|
|
1094
1253
|
break;
|
|
1095
1254
|
}
|
|
1255
|
+
const rowOperationId = nextOperationId();
|
|
1096
1256
|
operations.push({
|
|
1097
|
-
id:
|
|
1257
|
+
id: rowOperationId,
|
|
1098
1258
|
type: "insertTableRow",
|
|
1099
1259
|
blockId: anchor.blockId,
|
|
1100
1260
|
position: anchor.position,
|
|
1101
1261
|
cellTexts: cells
|
|
1102
1262
|
});
|
|
1263
|
+
tableTemplates.push({
|
|
1264
|
+
operationId: rowOperationId,
|
|
1265
|
+
targetTableIndex: step.location.tableIndex,
|
|
1266
|
+
targetRowIndex: step.location.rowIndex
|
|
1267
|
+
});
|
|
1103
1268
|
break;
|
|
1104
1269
|
}
|
|
1105
1270
|
case "targetColumn":
|
|
@@ -1126,15 +1291,17 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations }
|
|
|
1126
1291
|
}
|
|
1127
1292
|
if (operations.length > maxOperations) return null;
|
|
1128
1293
|
}
|
|
1129
|
-
|
|
1294
|
+
const planned = withTrailingDeletionRules({
|
|
1130
1295
|
baseSnapshot,
|
|
1131
1296
|
targetSnapshot,
|
|
1132
1297
|
operations,
|
|
1133
1298
|
nextOperationId
|
|
1134
|
-
})
|
|
1135
|
-
return
|
|
1299
|
+
});
|
|
1300
|
+
return planned.length > maxOperations ? null : {
|
|
1136
1301
|
changes,
|
|
1137
|
-
operations
|
|
1302
|
+
operations: planned,
|
|
1303
|
+
tableTemplates,
|
|
1304
|
+
tableGeometryPairings: tableGeometryPairingsOf(steps)
|
|
1138
1305
|
};
|
|
1139
1306
|
};
|
|
1140
1307
|
//#endregion
|
package/dist/compare/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FolioAIBlockParagraphProperties, FolioAIBlockTableLocation, FolioAIEditSkippedOperation, FolioAIInlineFormatting } from "../ai-edits/types.js";
|
|
2
2
|
import { WordDiffGranularity } from "../ai-edits/word-diff.js";
|
|
3
3
|
import { FolioDocumentStoryHandle, FolioNumberingLevel } from "../ai-edits/headless.js";
|
|
4
|
-
import { CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant,
|
|
4
|
+
import { CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision } from "./verification.js";
|
|
5
5
|
//#region src/compare/types.d.ts
|
|
6
6
|
/** Everything {@link compareDocx} needs; nothing it reads from the ambient clock. */
|
|
7
7
|
type CompareDocxOptions = {
|
|
@@ -279,18 +279,20 @@ declare class CompareDocxSerializeError extends CompareDocxSerializeError_base<{
|
|
|
279
279
|
}> {}
|
|
280
280
|
declare const CompareDocxFinalParagraphMarkError_base: import("better-result").TaggedErrorClass<"CompareDocxFinalParagraphMarkError">;
|
|
281
281
|
/**
|
|
282
|
-
* A container's final paragraph mark carries a
|
|
283
|
-
* not open.
|
|
282
|
+
* A container's final paragraph mark carries a revision, so the package would
|
|
283
|
+
* not open, or would open carrying one no reader can resolve.
|
|
284
284
|
*
|
|
285
285
|
* A deleted paragraph mark means "merge this paragraph into the following
|
|
286
|
-
* one",
|
|
287
|
-
* the
|
|
288
|
-
*
|
|
286
|
+
* one", an inserted one means the break was added and rejecting it closes the
|
|
287
|
+
* paragraph back over the next one, and a container's last paragraph has no
|
|
288
|
+
* following one either way. Checked before the package is written, and fatal
|
|
289
|
+
* under either `onUnverified` setting: there is no redline to emit when a
|
|
290
|
+
* consumer refuses the file.
|
|
289
291
|
*/
|
|
290
292
|
declare class CompareDocxFinalParagraphMarkError extends CompareDocxFinalParagraphMarkError_base<{
|
|
291
293
|
message: string;
|
|
292
294
|
/** Every container that carries one, each named structurally. */
|
|
293
|
-
|
|
295
|
+
revisions: readonly FinalParagraphMarkRevision[];
|
|
294
296
|
}> {}
|
|
295
297
|
type CompareDocxError = CompareDocxApplyError | CompareDocxFinalParagraphMarkError | CompareDocxOperationLimitError | CompareDocxParseError | CompareDocxRoundTripError | CompareDocxSerializeError | InvalidCompareDocxOptionsError;
|
|
296
298
|
//#endregion
|
package/dist/compare/types.js
CHANGED
|
@@ -29,13 +29,15 @@ var CompareDocxRoundTripError = class extends TaggedError("CompareDocxRoundTripE
|
|
|
29
29
|
var CompareDocxOperationLimitError = class extends TaggedError("CompareDocxOperationLimitError") {};
|
|
30
30
|
var CompareDocxSerializeError = class extends TaggedError("CompareDocxSerializeError") {};
|
|
31
31
|
/**
|
|
32
|
-
* A container's final paragraph mark carries a
|
|
33
|
-
* not open.
|
|
32
|
+
* A container's final paragraph mark carries a revision, so the package would
|
|
33
|
+
* not open, or would open carrying one no reader can resolve.
|
|
34
34
|
*
|
|
35
35
|
* A deleted paragraph mark means "merge this paragraph into the following
|
|
36
|
-
* one",
|
|
37
|
-
* the
|
|
38
|
-
*
|
|
36
|
+
* one", an inserted one means the break was added and rejecting it closes the
|
|
37
|
+
* paragraph back over the next one, and a container's last paragraph has no
|
|
38
|
+
* following one either way. Checked before the package is written, and fatal
|
|
39
|
+
* under either `onUnverified` setting: there is no redline to emit when a
|
|
40
|
+
* consumer refuses the file.
|
|
39
41
|
*/
|
|
40
42
|
var CompareDocxFinalParagraphMarkError = class extends TaggedError("CompareDocxFinalParagraphMarkError") {};
|
|
41
43
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FolioAIBlock } from "../ai-edits/types.js";
|
|
2
2
|
import { FolioDocumentStoryHandle } from "../ai-edits/headless.js";
|
|
3
|
+
import { ParagraphMarkChangeKind } from "@stll/docx-core/model";
|
|
3
4
|
//#region src/compare/verification.d.ts
|
|
4
5
|
/** The two directions of the round trip, each an invariant of its own. */
|
|
5
6
|
declare const COMPARE_VERIFICATION_INVARIANTS: readonly ["accept-reproduces-target", "reject-reproduces-base"];
|
|
@@ -9,7 +10,7 @@ type CompareVerificationInvariant = (typeof COMPARE_VERIFICATION_INVARIANTS)[num
|
|
|
9
10
|
* that fits is the one reported, because a block in the wrong container is a
|
|
10
11
|
* different finding from a block whose words are wrong even when both are true.
|
|
11
12
|
*/
|
|
12
|
-
declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "style", "list-level", "inline-formatting", "whitespace", "text"];
|
|
13
|
+
declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "table-geometry", "style", "list-level", "inline-formatting", "whitespace", "text"];
|
|
13
14
|
type CompareVerificationCause = (typeof COMPARE_VERIFICATION_CAUSES)[number];
|
|
14
15
|
/** One invariant that did not hold, in one story. */
|
|
15
16
|
type CompareVerificationFailure = {
|
|
@@ -44,6 +45,13 @@ type ClassifyOptions = {
|
|
|
44
45
|
/** What the invariant says it should leave. */
|
|
45
46
|
expected: readonly string[];
|
|
46
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The failure two table-geometry projections describe, or `null` when they
|
|
50
|
+
* agree. One line per table, so the detail names which table diverged and
|
|
51
|
+
* whether the count itself did — never a property value, which could carry a
|
|
52
|
+
* style name either document chose.
|
|
53
|
+
*/
|
|
54
|
+
declare const classifyGeometryMismatch: ({ invariant, story, actual, expected }: ClassifyOptions) => CompareVerificationFailure | null;
|
|
47
55
|
/**
|
|
48
56
|
* The failure two projections describe, or `null` when they agree.
|
|
49
57
|
*
|
|
@@ -52,32 +60,43 @@ type ClassifyOptions = {
|
|
|
52
60
|
*/
|
|
53
61
|
declare const classifyProjectionMismatch: ({ invariant, story, actual, expected }: ClassifyOptions) => CompareVerificationFailure | null;
|
|
54
62
|
/**
|
|
55
|
-
* A container whose last paragraph carries a
|
|
63
|
+
* A container whose last paragraph carries a revision on its mark.
|
|
56
64
|
*
|
|
57
65
|
* Structural facts only — a path through the package model and an index — so
|
|
58
66
|
* the finding is safe to log, report or quote.
|
|
59
67
|
*/
|
|
60
|
-
type
|
|
68
|
+
type FinalParagraphMarkRevision = {
|
|
61
69
|
/** Where the container sits in the package model, e.g. `package.document.content`. */
|
|
62
70
|
container: string;
|
|
63
71
|
/** The paragraph's index among its container's children. */
|
|
64
72
|
paragraphIndex: number;
|
|
65
|
-
/** The mark kind found there
|
|
66
|
-
kind:
|
|
73
|
+
/** The mark kind found there, `moveFrom` / `moveTo` for a relocation's ends. */
|
|
74
|
+
kind: ParagraphMarkChangeKind;
|
|
67
75
|
};
|
|
68
76
|
/**
|
|
69
|
-
* Every container in a package whose final paragraph mark carries a
|
|
77
|
+
* Every container in a package whose final paragraph mark carries a revision.
|
|
70
78
|
*
|
|
71
|
-
* A deleted paragraph mark says "join this paragraph with the one after it"
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
79
|
+
* A deleted paragraph mark says "join this paragraph with the one after it",
|
|
80
|
+
* and an inserted one says that break was ADDED, so rejecting it closes the
|
|
81
|
+
* paragraph back over the next one. The last paragraph of a body, a table
|
|
82
|
+
* cell, a header or footer, a note or a text box has no paragraph after it, so
|
|
83
|
+
* neither direction states an edit that can be carried out: a consumer refuses
|
|
84
|
+
* the package, or opens it and leaves a revision standing that neither
|
|
85
|
+
* accepting nor rejecting everything can clear. The exception is a cell of a
|
|
86
|
+
* row the package is DELETING: there the mark leaves with its row.
|
|
75
87
|
*
|
|
76
88
|
* The walk is over the package model rather than over a list of the containers
|
|
77
89
|
* known today: a container is any sequence that ends in a paragraph, so a part
|
|
78
90
|
* the model grows later is covered the day it arrives instead of the day
|
|
79
91
|
* someone remembers this function.
|
|
92
|
+
*
|
|
93
|
+
* `since` scopes it to the revisions a comparison MINTED: a base may arrive
|
|
94
|
+
* carrying one of these on a paragraph in a part no story mounts, which folio
|
|
95
|
+
* preserves the way it preserves everything else it parses. What this proves
|
|
96
|
+
* is that the comparison writes none of its own.
|
|
80
97
|
*/
|
|
81
|
-
declare const
|
|
98
|
+
declare const revisedFinalParagraphMarks: (packageModel: unknown, { since }?: {
|
|
99
|
+
since?: number;
|
|
100
|
+
}) => FinalParagraphMarkRevision[];
|
|
82
101
|
//#endregion
|
|
83
|
-
export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant,
|
|
102
|
+
export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision, classifyGeometryMismatch, classifyProjectionMismatch, projectSupportedInlineFormatting, revisedFinalParagraphMarks, sameProjection };
|