@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.
Files changed (227) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
  2. package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
  3. package/dist/ai-edits/apply.d.ts +49 -3
  4. package/dist/ai-edits/apply.js +652 -86
  5. package/dist/ai-edits/blockRange.d.ts +7 -3
  6. package/dist/ai-edits/blockRange.js +14 -6
  7. package/dist/ai-edits/headless.d.ts +39 -5
  8. package/dist/ai-edits/headless.js +76 -17
  9. package/dist/ai-edits/index.d.ts +6 -6
  10. package/dist/ai-edits/index.js +3 -3
  11. package/dist/ai-edits/read.d.ts +4 -0
  12. package/dist/ai-edits/read.js +30 -6
  13. package/dist/ai-edits/scoped-reading.js +4 -3
  14. package/dist/ai-edits/snapshot.d.ts +42 -2
  15. package/dist/ai-edits/snapshot.js +127 -62
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
  17. package/dist/ai-edits/table-row-column-mutations.js +98 -6
  18. package/dist/ai-edits/table-targets.d.ts +12 -3
  19. package/dist/ai-edits/table-targets.js +15 -5
  20. package/dist/ai-edits/types.d.ts +219 -15
  21. package/dist/ai-edits/word-diff.d.ts +54 -11
  22. package/dist/ai-edits/word-diff.js +221 -61
  23. package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +136 -0
  25. package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
  26. package/dist/compare/__fixtures__/nested-table.js +50 -0
  27. package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
  28. package/dist/compare/__fixtures__/numbered-list.js +85 -0
  29. package/dist/compare/column-alignment.d.ts +27 -0
  30. package/dist/compare/column-alignment.js +162 -0
  31. package/dist/compare/compare.d.ts +107 -0
  32. package/dist/compare/compare.js +412 -0
  33. package/dist/compare/formatting.d.ts +29 -0
  34. package/dist/compare/formatting.js +84 -0
  35. package/dist/compare/plan.d.ts +22 -0
  36. package/dist/compare/plan.js +1141 -0
  37. package/dist/compare/reproducible-package.d.ts +21 -0
  38. package/dist/compare/reproducible-package.js +47 -0
  39. package/dist/compare/scenario.d.ts +77 -0
  40. package/dist/compare/scenario.js +263 -0
  41. package/dist/compare/types.d.ts +297 -0
  42. package/dist/compare/types.js +42 -0
  43. package/dist/compare/verification.d.ts +83 -0
  44. package/dist/compare/verification.js +189 -0
  45. package/dist/compat/eigenpal.d.ts +9 -6
  46. package/dist/compat/eigenpal.js +6 -3
  47. package/dist/controller/layoutPipeline.d.ts +13 -0
  48. package/dist/controller/layoutPipeline.js +15 -1
  49. package/dist/display-list/build/buildContext.d.ts +39 -0
  50. package/dist/display-list/build/buildContext.js +31 -0
  51. package/dist/display-list/build/buildDisplayList.d.ts +79 -0
  52. package/dist/display-list/build/buildDisplayList.js +283 -0
  53. package/dist/display-list/build/colors.d.ts +20 -0
  54. package/dist/display-list/build/colors.js +136 -0
  55. package/dist/display-list/build/floatingImages.d.ts +24 -0
  56. package/dist/display-list/build/floatingImages.js +41 -0
  57. package/dist/display-list/build/fontTable.d.ts +31 -0
  58. package/dist/display-list/build/fontTable.js +97 -0
  59. package/dist/display-list/build/furniture.d.ts +86 -0
  60. package/dist/display-list/build/furniture.js +98 -0
  61. package/dist/display-list/build/glyphs.d.ts +55 -0
  62. package/dist/display-list/build/glyphs.js +103 -0
  63. package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
  64. package/dist/display-list/build/headerFooterPrimitives.js +40 -0
  65. package/dist/display-list/build/imagePrimitives.d.ts +35 -0
  66. package/dist/display-list/build/imagePrimitives.js +264 -0
  67. package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
  68. package/dist/display-list/build/pageBorderPrimitives.js +111 -0
  69. package/dist/display-list/build/pageFurniture.d.ts +35 -0
  70. package/dist/display-list/build/pageFurniture.js +141 -0
  71. package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
  72. package/dist/display-list/build/paragraphPrimitives.js +819 -0
  73. package/dist/display-list/build/regions.d.ts +46 -0
  74. package/dist/display-list/build/regions.js +56 -0
  75. package/dist/display-list/build/storyPrimitives.d.ts +29 -0
  76. package/dist/display-list/build/storyPrimitives.js +279 -0
  77. package/dist/display-list/build/strokes.d.ts +19 -0
  78. package/dist/display-list/build/strokes.js +78 -0
  79. package/dist/display-list/build/tablePrimitives.d.ts +38 -0
  80. package/dist/display-list/build/tablePrimitives.js +409 -0
  81. package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
  82. package/dist/display-list/build/textBoxPrimitives.js +129 -0
  83. package/dist/display-list/build/textDecorations.d.ts +29 -0
  84. package/dist/display-list/build/textDecorations.js +29 -0
  85. package/dist/display-list/build/unsupported.d.ts +57 -0
  86. package/dist/display-list/build/unsupported.js +0 -0
  87. package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
  88. package/dist/display-list/build/watermarkPrimitives.js +128 -0
  89. package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
  90. package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
  91. package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
  92. package/dist/display-list/editor/displayListPagePainter.js +63 -0
  93. package/dist/display-list/editor/pageRenderer.d.ts +24 -0
  94. package/dist/display-list/editor/pageRenderer.js +36 -0
  95. package/dist/display-list/primitives.d.ts +95 -0
  96. package/dist/display-list/primitives.js +127 -0
  97. package/dist/display-list/types.d.ts +472 -0
  98. package/dist/display-list/types.js +0 -0
  99. package/dist/document-operations.d.ts +39 -8
  100. package/dist/document-operations.js +169 -16
  101. package/dist/document-stories.d.ts +16 -0
  102. package/dist/document-stories.js +48 -4
  103. package/dist/docx/hyperlinkParser.d.ts +9 -1
  104. package/dist/docx/hyperlinkParser.js +19 -13
  105. package/dist/docx/packageParts.d.ts +35 -0
  106. package/dist/docx/packageParts.js +120 -0
  107. package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
  108. package/dist/docx/paraIdRangeNormalization.js +64 -0
  109. package/dist/docx/paragraphParser.js +101 -26
  110. package/dist/docx/paragraphTraversal.js +1 -1
  111. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  112. package/dist/docx/revisionIdNormalization.d.ts +27 -0
  113. package/dist/docx/revisionIdNormalization.js +121 -0
  114. package/dist/docx/rezip.d.ts +6 -0
  115. package/dist/docx/rezip.js +51 -19
  116. package/dist/docx/selectiveSave.js +2 -2
  117. package/dist/docx/serializer/commentSerializer.js +6 -1
  118. package/dist/docx/serializer/paragraphSerializer.js +73 -20
  119. package/dist/docx/serializer/runSerializer.js +11 -10
  120. package/dist/docx/serializer/tableSerializer.js +26 -10
  121. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  122. package/dist/docx/server/createBilingualDocx.js +3 -2
  123. package/dist/docx/streamingXmlParser.d.ts +13 -1
  124. package/dist/docx/streamingXmlParser.js +49 -11
  125. package/dist/docx/unzip.d.ts +1 -2
  126. package/dist/docx/unzip.js +25 -6
  127. package/dist/docx/xmlParser.d.ts +3 -1
  128. package/dist/docx/xmlParser.js +33 -27
  129. package/dist/export-pdf.d.ts +43 -0
  130. package/dist/export-pdf.js +98 -0
  131. package/dist/fonts/headlessMeasure.d.ts +76 -0
  132. package/dist/fonts/headlessMeasure.js +0 -0
  133. package/dist/fonts/sfnt/parse.d.ts +51 -0
  134. package/dist/fonts/sfnt/parse.js +525 -0
  135. package/dist/fonts/sfnt/subset.d.ts +20 -0
  136. package/dist/fonts/sfnt/subset.js +350 -0
  137. package/dist/fonts/sfnt/tables.d.ts +88 -0
  138. package/dist/fonts/sfnt/tables.js +131 -0
  139. package/dist/fonts/sfnt/woff.d.ts +14 -0
  140. package/dist/fonts/sfnt/woff.js +163 -0
  141. package/dist/generated/text_shaper.js +324 -0
  142. package/dist/generated/text_shaper_bg.wasm +0 -0
  143. package/dist/headless-layout.d.ts +75 -0
  144. package/dist/headless-layout.js +350 -0
  145. package/dist/index.d.ts +9 -6
  146. package/dist/index.js +6 -3
  147. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  148. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  149. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  150. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  151. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  152. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  153. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  154. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  155. package/dist/layout-engine/measure/cache.d.ts +2 -0
  156. package/dist/layout-engine/measure/measureContainer.js +13 -0
  157. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  158. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  159. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  160. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  161. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  162. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  163. package/dist/layout-engine/types.d.ts +8 -0
  164. package/dist/layout-painter/renderPage.d.ts +29 -1
  165. package/dist/layout-painter/renderPage.js +47 -19
  166. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  167. package/dist/layout-painter/renderParagraph.js +1 -1
  168. package/dist/layout-painter/renderTable.js +37 -4
  169. package/dist/managers/editorShortcuts.d.ts +33 -1
  170. package/dist/managers/editorShortcuts.js +20 -1
  171. package/dist/markdown/renderRuns.js +7 -2
  172. package/dist/markdown/renderTable.js +5 -1
  173. package/dist/model.d.ts +3 -3
  174. package/dist/model.js +2 -2
  175. package/dist/pdf/contentStream.d.ts +73 -0
  176. package/dist/pdf/contentStream.js +143 -0
  177. package/dist/pdf/fonts.d.ts +96 -0
  178. package/dist/pdf/fonts.js +593 -0
  179. package/dist/pdf/images.d.ts +28 -0
  180. package/dist/pdf/images.js +439 -0
  181. package/dist/pdf/objects.d.ts +93 -0
  182. package/dist/pdf/objects.js +237 -0
  183. package/dist/pdf/pageSpace.d.ts +43 -0
  184. package/dist/pdf/pageSpace.js +57 -0
  185. package/dist/pdf/paint.d.ts +32 -0
  186. package/dist/pdf/paint.js +442 -0
  187. package/dist/pdf/writePdf.d.ts +56 -0
  188. package/dist/pdf/writePdf.js +345 -0
  189. package/dist/prosemirror/commands/comments.js +96 -6
  190. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  191. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  192. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  193. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  194. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  195. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  196. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  198. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  199. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  200. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  201. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  202. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  203. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  204. package/dist/prosemirror/revisionCarriers.js +19 -2
  205. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  206. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  207. package/dist/prosemirror/validation.js +55 -27
  208. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  209. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  210. package/dist/redline.js +12 -79
  211. package/dist/server.d.ts +3 -3
  212. package/dist/server.js +2 -2
  213. package/dist/shaping/placeRun.d.ts +51 -0
  214. package/dist/shaping/placeRun.js +84 -0
  215. package/dist/shaping/shaper.d.ts +92 -0
  216. package/dist/shaping/shaper.js +102 -0
  217. package/dist/types/block-id.d.ts +18 -2
  218. package/dist/types/block-id.js +49 -5
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/clipboard.js +4 -3
  221. package/dist/utils/fontResolver.js +183 -0
  222. package/dist/utils/htmlComments.d.ts +17 -0
  223. package/dist/utils/htmlComments.js +22 -0
  224. package/dist/utils/scriptSegments.d.ts +12 -1
  225. package/dist/utils/scriptSegments.js +17 -1
  226. package/dist/version-comparison.d.ts +1 -1
  227. 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 };