@stll/folio-core 0.32.2 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/compare/reproducible-package.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The last two clocks in the compare path are outside the document body.
|
|
4
|
+
*
|
|
5
|
+
* Every part the serializer rewrites is stored with JSZip's default entry
|
|
6
|
+
* date, which is `new Date()`. The XML is identical between two runs, but the
|
|
7
|
+
* DOS timestamp fields in the local headers are not, and they only agree when
|
|
8
|
+
* both runs land in the same two-second bucket — so the packages match most of
|
|
9
|
+
* the time and differ occasionally, which is worse than differing always.
|
|
10
|
+
*
|
|
11
|
+
* The save also stamps `dcterms:modified` in `docProps/core.xml` from the wall
|
|
12
|
+
* clock, which is the same failure one part deeper: two runs over identical
|
|
13
|
+
* inputs differ in that part alone.
|
|
14
|
+
*
|
|
15
|
+
* Restamping both from the comparison's own timestamp removes them. It also
|
|
16
|
+
* states the truth about the package: a generated redline is dated by the
|
|
17
|
+
* comparison that produced it, not by the second it happened to be written.
|
|
18
|
+
*/
|
|
19
|
+
declare const withFixedPackageDates: (buffer: ArrayBuffer, date: Date) => Promise<ArrayBuffer>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { withFixedPackageDates };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import JSZip from "jszip";
|
|
2
|
+
//#region src/compare/reproducible-package.ts
|
|
3
|
+
/**
|
|
4
|
+
* The last two clocks in the compare path are outside the document body.
|
|
5
|
+
*
|
|
6
|
+
* Every part the serializer rewrites is stored with JSZip's default entry
|
|
7
|
+
* date, which is `new Date()`. The XML is identical between two runs, but the
|
|
8
|
+
* DOS timestamp fields in the local headers are not, and they only agree when
|
|
9
|
+
* both runs land in the same two-second bucket — so the packages match most of
|
|
10
|
+
* the time and differ occasionally, which is worse than differing always.
|
|
11
|
+
*
|
|
12
|
+
* The save also stamps `dcterms:modified` in `docProps/core.xml` from the wall
|
|
13
|
+
* clock, which is the same failure one part deeper: two runs over identical
|
|
14
|
+
* inputs differ in that part alone.
|
|
15
|
+
*
|
|
16
|
+
* Restamping both from the comparison's own timestamp removes them. It also
|
|
17
|
+
* states the truth about the package: a generated redline is dated by the
|
|
18
|
+
* comparison that produced it, not by the second it happened to be written.
|
|
19
|
+
*/
|
|
20
|
+
/** JSZip deflate level `repackDocx` writes DOCX parts at. */
|
|
21
|
+
const DOCX_COMPRESSION_LEVEL = 6;
|
|
22
|
+
const CORE_PROPERTIES_PATH = "docProps/core.xml";
|
|
23
|
+
const MODIFIED_ELEMENT = /<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u;
|
|
24
|
+
/**
|
|
25
|
+
* Rewrite `dcterms:modified` where the save already wrote one. An absent
|
|
26
|
+
* element stays absent: the comparison edits the document it was handed, and
|
|
27
|
+
* synthesizing metadata the input never carried is a different decision.
|
|
28
|
+
*/
|
|
29
|
+
const withFixedModifiedDate = (corePropsXml, date) => corePropsXml.replace(MODIFIED_ELEMENT, `<dcterms:modified xsi:type="dcterms:W3CDTF">${date.toISOString()}</dcterms:modified>`);
|
|
30
|
+
const withFixedPackageDates = async (buffer, date) => {
|
|
31
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
32
|
+
const coreProps = zip.file(CORE_PROPERTIES_PATH);
|
|
33
|
+
if (coreProps) zip.file(CORE_PROPERTIES_PATH, withFixedModifiedDate(await coreProps.async("text"), date), {
|
|
34
|
+
compression: "DEFLATE",
|
|
35
|
+
compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
|
|
36
|
+
});
|
|
37
|
+
zip.forEach((_path, file) => {
|
|
38
|
+
file.date = date;
|
|
39
|
+
});
|
|
40
|
+
return await zip.generateAsync({
|
|
41
|
+
type: "arraybuffer",
|
|
42
|
+
compression: "DEFLATE",
|
|
43
|
+
compressionOptions: { level: DOCX_COMPRESSION_LEVEL }
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { withFixedPackageDates };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { FolioAIInlineFormatting } from "../ai-edits/types.js";
|
|
2
|
+
import { CompareDocxParseError, CompareDocxSerializeError } from "./types.js";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
//#region src/compare/scenario.d.ts
|
|
5
|
+
type EditScriptStep = {
|
|
6
|
+
type: "insertParagraphAfter";
|
|
7
|
+
blockIndex: number;
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: "deleteParagraph";
|
|
11
|
+
blockIndex: number;
|
|
12
|
+
} | {
|
|
13
|
+
type: "replaceWords";
|
|
14
|
+
blockIndex: number;
|
|
15
|
+
find: string;
|
|
16
|
+
replace: string;
|
|
17
|
+
} |
|
|
18
|
+
/** Delete the block at `blockIndex` and reinsert its text before `beforeBlockIndex`. */
|
|
19
|
+
{
|
|
20
|
+
type: "moveParagraph";
|
|
21
|
+
blockIndex: number;
|
|
22
|
+
beforeBlockIndex: number;
|
|
23
|
+
} |
|
|
24
|
+
/**
|
|
25
|
+
* Break one paragraph into two at `wordIndex`, which is an inserted
|
|
26
|
+
* paragraph mark and nothing else: both halves keep their words.
|
|
27
|
+
*/
|
|
28
|
+
{
|
|
29
|
+
type: "splitParagraph";
|
|
30
|
+
blockIndex: number;
|
|
31
|
+
wordIndex: number;
|
|
32
|
+
} |
|
|
33
|
+
/**
|
|
34
|
+
* Join the block at `blockIndex` with the one after it, which is a deleted
|
|
35
|
+
* paragraph mark and nothing else.
|
|
36
|
+
*/
|
|
37
|
+
{
|
|
38
|
+
type: "mergeParagraphs";
|
|
39
|
+
blockIndex: number;
|
|
40
|
+
} | {
|
|
41
|
+
type: "formatRange";
|
|
42
|
+
blockIndex: number;
|
|
43
|
+
startOffset: number;
|
|
44
|
+
endOffset: number;
|
|
45
|
+
formatting: FolioAIInlineFormatting;
|
|
46
|
+
} | {
|
|
47
|
+
type: "insertTableRow";
|
|
48
|
+
blockIndex: number;
|
|
49
|
+
cellTexts: readonly string[];
|
|
50
|
+
} | {
|
|
51
|
+
type: "deleteTableRow";
|
|
52
|
+
blockIndex: number;
|
|
53
|
+
} | {
|
|
54
|
+
type: "editTableCell";
|
|
55
|
+
blockIndex: number;
|
|
56
|
+
text: string;
|
|
57
|
+
};
|
|
58
|
+
type EditScript = readonly EditScriptStep[];
|
|
59
|
+
declare const EDIT_SCRIPT_UNRESOLVED_REASONS: readonly ["block-out-of-range", "block-not-in-table", "range-out-of-bounds", "text-not-found", "no-paragraph-boundary", "refused"];
|
|
60
|
+
type EditScriptUnresolvedReason = (typeof EDIT_SCRIPT_UNRESOLVED_REASONS)[number];
|
|
61
|
+
type EditScriptUnresolvedStep = {
|
|
62
|
+
step: EditScriptStep;
|
|
63
|
+
reason: EditScriptUnresolvedReason;
|
|
64
|
+
};
|
|
65
|
+
type EditScriptResult = {
|
|
66
|
+
/** The base document with every applied step written in directly. */
|
|
67
|
+
buffer: ArrayBuffer;
|
|
68
|
+
/** The steps that reached the document, in script order. */
|
|
69
|
+
applied: readonly EditScriptStep[];
|
|
70
|
+
unresolved: readonly EditScriptUnresolvedStep[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Apply `script` to `base` directly and return the resulting target document.
|
|
74
|
+
*/
|
|
75
|
+
declare const applyEditScript: (base: ArrayBuffer, script: EditScript) => Promise<Result<EditScriptResult, CompareDocxParseError | CompareDocxSerializeError>>;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { EDIT_SCRIPT_UNRESOLVED_REASONS, EditScript, EditScriptResult, EditScriptStep, EditScriptUnresolvedReason, EditScriptUnresolvedStep, applyEditScript };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
2
|
+
import { createFolioAITextRangeHandle } from "../ai-edits/snapshot.js";
|
|
3
|
+
import { CompareDocxParseError, CompareDocxSerializeError } from "./types.js";
|
|
4
|
+
import { Result, panic } from "better-result";
|
|
5
|
+
//#region src/compare/scenario.ts
|
|
6
|
+
/**
|
|
7
|
+
* Edit scripts: ground truth for the compare engine, by construction.
|
|
8
|
+
*
|
|
9
|
+
* A property test cannot assert "this redline is right" against a document it
|
|
10
|
+
* only guessed at. It can assert it against a document it BUILT: take a real
|
|
11
|
+
* base, apply a scripted edit directly (no tracked changes), and the result is
|
|
12
|
+
* a target whose difference from the base is known exactly. `compareDocx` then
|
|
13
|
+
* has to rediscover that difference, and the script says what it should have
|
|
14
|
+
* found.
|
|
15
|
+
*
|
|
16
|
+
* Every step is applied through the same headless applier `compareDocx` writes
|
|
17
|
+
* its own operations through, in `"direct"` mode — so the target is an ordinary
|
|
18
|
+
* document, not one carrying revisions the comparison would have to resolve
|
|
19
|
+
* first.
|
|
20
|
+
*
|
|
21
|
+
* Steps address blocks by their index in the base story's block list rather
|
|
22
|
+
* than by id, so a generator can pick targets without first parsing the
|
|
23
|
+
* document. A step that does not resolve is reported in `unresolved`, never
|
|
24
|
+
* dropped: a script silently doing less than it says would weaken every
|
|
25
|
+
* property built on it.
|
|
26
|
+
*/
|
|
27
|
+
const EDIT_SCRIPT_UNRESOLVED_REASONS = Object.freeze([
|
|
28
|
+
"block-out-of-range",
|
|
29
|
+
"block-not-in-table",
|
|
30
|
+
"range-out-of-bounds",
|
|
31
|
+
"text-not-found",
|
|
32
|
+
"no-paragraph-boundary",
|
|
33
|
+
"refused"
|
|
34
|
+
]);
|
|
35
|
+
const blockAt = (blocks, index) => Number.isInteger(index) && index >= 0 ? blocks.at(index) : void 0;
|
|
36
|
+
const planStep = ({ step, blocks, nextOperationId }) => {
|
|
37
|
+
const block = blockAt(blocks, step.blockIndex);
|
|
38
|
+
if (!block) return {
|
|
39
|
+
status: "unresolved",
|
|
40
|
+
reason: "block-out-of-range"
|
|
41
|
+
};
|
|
42
|
+
switch (step.type) {
|
|
43
|
+
case "insertParagraphAfter": return {
|
|
44
|
+
status: "planned",
|
|
45
|
+
operations: [{
|
|
46
|
+
id: nextOperationId(),
|
|
47
|
+
type: "insertAfterBlock",
|
|
48
|
+
blockId: block.id,
|
|
49
|
+
text: step.text
|
|
50
|
+
}]
|
|
51
|
+
};
|
|
52
|
+
case "deleteParagraph": return {
|
|
53
|
+
status: "planned",
|
|
54
|
+
operations: [{
|
|
55
|
+
id: nextOperationId(),
|
|
56
|
+
type: "deleteBlock",
|
|
57
|
+
blockId: block.id
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
case "replaceWords": return block.text.includes(step.find) ? {
|
|
61
|
+
status: "planned",
|
|
62
|
+
operations: [{
|
|
63
|
+
id: nextOperationId(),
|
|
64
|
+
type: "replaceInBlock",
|
|
65
|
+
blockId: block.id,
|
|
66
|
+
find: step.find,
|
|
67
|
+
replace: step.replace
|
|
68
|
+
}]
|
|
69
|
+
} : {
|
|
70
|
+
status: "unresolved",
|
|
71
|
+
reason: "text-not-found"
|
|
72
|
+
};
|
|
73
|
+
case "moveParagraph": {
|
|
74
|
+
const destination = blockAt(blocks, step.beforeBlockIndex);
|
|
75
|
+
if (!destination || destination.id === block.id) return {
|
|
76
|
+
status: "unresolved",
|
|
77
|
+
reason: "block-out-of-range"
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
status: "planned",
|
|
81
|
+
operations: [{
|
|
82
|
+
id: nextOperationId(),
|
|
83
|
+
type: "deleteBlock",
|
|
84
|
+
blockId: block.id
|
|
85
|
+
}, {
|
|
86
|
+
id: nextOperationId(),
|
|
87
|
+
type: "insertBeforeBlock",
|
|
88
|
+
blockId: destination.id,
|
|
89
|
+
text: block.text,
|
|
90
|
+
styleId: block.styleId ?? null,
|
|
91
|
+
listLevel: block.listLevel ?? null
|
|
92
|
+
}]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
case "splitParagraph": {
|
|
96
|
+
const words = block.text.split(" ");
|
|
97
|
+
if (step.wordIndex <= 0 || step.wordIndex >= words.length) return {
|
|
98
|
+
status: "unresolved",
|
|
99
|
+
reason: "no-paragraph-boundary"
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
status: "planned",
|
|
103
|
+
operations: [{
|
|
104
|
+
id: nextOperationId(),
|
|
105
|
+
type: "replaceBlock",
|
|
106
|
+
blockId: block.id,
|
|
107
|
+
text: words.slice(0, step.wordIndex).join(" ")
|
|
108
|
+
}, {
|
|
109
|
+
id: nextOperationId(),
|
|
110
|
+
type: "insertAfterBlock",
|
|
111
|
+
blockId: block.id,
|
|
112
|
+
text: words.slice(step.wordIndex).join(" "),
|
|
113
|
+
...block.styleId !== void 0 && { styleId: block.styleId }
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
case "mergeParagraphs": {
|
|
118
|
+
const next = blockAt(blocks, step.blockIndex + 1);
|
|
119
|
+
if (!next || next.table?.cellIndex !== block.table?.cellIndex) return {
|
|
120
|
+
status: "unresolved",
|
|
121
|
+
reason: "no-paragraph-boundary"
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
status: "planned",
|
|
125
|
+
operations: [{
|
|
126
|
+
id: nextOperationId(),
|
|
127
|
+
type: "replaceBlock",
|
|
128
|
+
blockId: block.id,
|
|
129
|
+
text: `${block.text} ${next.text}`
|
|
130
|
+
}, {
|
|
131
|
+
id: nextOperationId(),
|
|
132
|
+
type: "deleteBlock",
|
|
133
|
+
blockId: next.id
|
|
134
|
+
}]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
case "formatRange": {
|
|
138
|
+
const range = createFolioAITextRangeHandle({
|
|
139
|
+
blockId: block.id,
|
|
140
|
+
text: block.text,
|
|
141
|
+
startOffset: step.startOffset,
|
|
142
|
+
endOffset: step.endOffset
|
|
143
|
+
});
|
|
144
|
+
return range ? {
|
|
145
|
+
status: "planned",
|
|
146
|
+
operations: [{
|
|
147
|
+
id: nextOperationId(),
|
|
148
|
+
type: "formatRange",
|
|
149
|
+
range,
|
|
150
|
+
formatting: step.formatting
|
|
151
|
+
}]
|
|
152
|
+
} : {
|
|
153
|
+
status: "unresolved",
|
|
154
|
+
reason: "range-out-of-bounds"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
case "insertTableRow": return block.table ? {
|
|
158
|
+
status: "planned",
|
|
159
|
+
operations: [{
|
|
160
|
+
id: nextOperationId(),
|
|
161
|
+
type: "insertTableRow",
|
|
162
|
+
blockId: block.id,
|
|
163
|
+
position: "after",
|
|
164
|
+
cellTexts: [...step.cellTexts]
|
|
165
|
+
}]
|
|
166
|
+
} : {
|
|
167
|
+
status: "unresolved",
|
|
168
|
+
reason: "block-not-in-table"
|
|
169
|
+
};
|
|
170
|
+
case "deleteTableRow": return block.table ? {
|
|
171
|
+
status: "planned",
|
|
172
|
+
operations: [{
|
|
173
|
+
id: nextOperationId(),
|
|
174
|
+
type: "deleteTableRow",
|
|
175
|
+
blockId: block.id
|
|
176
|
+
}]
|
|
177
|
+
} : {
|
|
178
|
+
status: "unresolved",
|
|
179
|
+
reason: "block-not-in-table"
|
|
180
|
+
};
|
|
181
|
+
case "editTableCell": return block.table ? {
|
|
182
|
+
status: "planned",
|
|
183
|
+
operations: [{
|
|
184
|
+
id: nextOperationId(),
|
|
185
|
+
type: "replaceBlock",
|
|
186
|
+
blockId: block.id,
|
|
187
|
+
text: step.text
|
|
188
|
+
}]
|
|
189
|
+
} : {
|
|
190
|
+
status: "unresolved",
|
|
191
|
+
reason: "block-not-in-table"
|
|
192
|
+
};
|
|
193
|
+
default: return panic("Unhandled edit script step", { step });
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Apply `script` to `base` directly and return the resulting target document.
|
|
198
|
+
*/
|
|
199
|
+
const applyEditScript = async (base, script) => {
|
|
200
|
+
const parsed = await Result.tryPromise({
|
|
201
|
+
try: async () => await FolioDocxReviewer.fromBuffer(base),
|
|
202
|
+
catch: (cause) => new CompareDocxParseError({
|
|
203
|
+
message: "The edit script's base document could not be parsed.",
|
|
204
|
+
side: "base",
|
|
205
|
+
cause
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
if (parsed.isErr()) return Result.err(parsed.error);
|
|
209
|
+
const reviewer = parsed.value;
|
|
210
|
+
const snapshot = reviewer.snapshot();
|
|
211
|
+
const unresolved = [];
|
|
212
|
+
const planned = [];
|
|
213
|
+
let operationSequence = 0;
|
|
214
|
+
const nextOperationId = () => `scenario-${++operationSequence}`;
|
|
215
|
+
for (const step of script) {
|
|
216
|
+
const plan = planStep({
|
|
217
|
+
step,
|
|
218
|
+
blocks: snapshot.blocks,
|
|
219
|
+
nextOperationId
|
|
220
|
+
});
|
|
221
|
+
if (plan.status === "unresolved") {
|
|
222
|
+
unresolved.push({
|
|
223
|
+
step,
|
|
224
|
+
reason: plan.reason
|
|
225
|
+
});
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
planned.push({
|
|
229
|
+
step,
|
|
230
|
+
operations: plan.operations
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const { skipped } = reviewer.applyOperations(planned.flatMap(({ operations }) => [...operations]), {
|
|
234
|
+
mode: "direct",
|
|
235
|
+
snapshot
|
|
236
|
+
});
|
|
237
|
+
const skippedIds = new Set(skipped.map(({ id }) => id));
|
|
238
|
+
const applied = [];
|
|
239
|
+
for (const { step, operations } of planned) {
|
|
240
|
+
if (operations.some(({ id }) => skippedIds.has(id))) {
|
|
241
|
+
unresolved.push({
|
|
242
|
+
step,
|
|
243
|
+
reason: "refused"
|
|
244
|
+
});
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
applied.push(step);
|
|
248
|
+
}
|
|
249
|
+
const serialized = await Result.tryPromise({
|
|
250
|
+
try: async () => await reviewer.toBuffer(),
|
|
251
|
+
catch: (cause) => new CompareDocxSerializeError({
|
|
252
|
+
message: "The edit script's target document could not be serialized.",
|
|
253
|
+
cause
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
return serialized.isErr() ? Result.err(serialized.error) : Result.ok({
|
|
257
|
+
buffer: serialized.value,
|
|
258
|
+
applied,
|
|
259
|
+
unresolved
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
export { EDIT_SCRIPT_UNRESOLVED_REASONS, applyEditScript };
|