@stll/folio-core 0.3.1 → 0.5.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.
Files changed (120) hide show
  1. package/dist/ai-edits/apply.d.ts +3 -9
  2. package/dist/ai-edits/apply.js +94 -14
  3. package/dist/ai-edits/blockRange.d.ts +33 -0
  4. package/dist/ai-edits/blockRange.js +58 -0
  5. package/dist/ai-edits/headless.d.ts +35 -5
  6. package/dist/ai-edits/headless.js +92 -22
  7. package/dist/ai-edits/index.d.ts +5 -3
  8. package/dist/ai-edits/index.js +3 -2
  9. package/dist/ai-edits/snapshot.d.ts +14 -2
  10. package/dist/ai-edits/snapshot.js +12 -1
  11. package/dist/ai-edits/types.d.ts +42 -3
  12. package/dist/compat/eigenpal.d.ts +3 -2
  13. package/dist/compat/eigenpal.js +6 -5
  14. package/dist/controller/layoutPipeline.d.ts +1 -0
  15. package/dist/controller/layoutPipeline.js +35 -115
  16. package/dist/controller/layoutScheduler.d.ts +2 -1
  17. package/dist/controller/layoutScheduler.js +6 -0
  18. package/dist/document-operations.d.ts +122 -0
  19. package/dist/document-operations.js +575 -0
  20. package/dist/docx/blockContentParser.js +17 -7
  21. package/dist/docx/encryption/agileDecryption.d.ts +6 -0
  22. package/dist/docx/encryption/agileDecryption.js +209 -0
  23. package/dist/docx/encryption/compoundFile.d.ts +20 -0
  24. package/dist/docx/encryption/compoundFile.js +251 -0
  25. package/dist/docx/encryption/containerFormat.d.ts +15 -0
  26. package/dist/docx/encryption/containerFormat.js +40 -0
  27. package/dist/docx/encryption/cryptoBytes.d.ts +11 -0
  28. package/dist/docx/encryption/cryptoBytes.js +55 -0
  29. package/dist/docx/encryption/encryptionInfo.d.ts +32 -0
  30. package/dist/docx/encryption/encryptionInfo.js +128 -0
  31. package/dist/docx/encryption/errors.d.ts +18 -0
  32. package/dist/docx/encryption/errors.js +13 -0
  33. package/dist/docx/encryption/index.d.ts +4 -0
  34. package/dist/docx/encryption/index.js +4 -0
  35. package/dist/docx/encryption/openEncryptedDocx.d.ts +28 -0
  36. package/dist/docx/encryption/openEncryptedDocx.js +65 -0
  37. package/dist/docx/footnoteParser.d.ts +1 -17
  38. package/dist/docx/footnoteParser.js +1 -27
  39. package/dist/docx/groupDrawingParser.d.ts +8 -0
  40. package/dist/docx/groupDrawingParser.js +131 -0
  41. package/dist/docx/index.d.ts +3 -1
  42. package/dist/docx/index.js +3 -1
  43. package/dist/docx/numberingParser.d.ts +3 -1
  44. package/dist/docx/numberingParser.js +8 -2
  45. package/dist/docx/paragraphParser.js +9 -3
  46. package/dist/docx/parser.d.ts +4 -5
  47. package/dist/docx/parser.js +8 -2
  48. package/dist/docx/runParser.js +8 -1
  49. package/dist/docx/selectiveXmlPatch.js +4 -10
  50. package/dist/docx/serializer/numberingSerializer.js +3 -1
  51. package/dist/docx/settingsParser.d.ts +6 -3
  52. package/dist/docx/settingsParser.js +2 -0
  53. package/dist/docx/unzip.d.ts +7 -5
  54. package/dist/docx/unzip.js +8 -2
  55. package/dist/docx/xmlParser.js +11 -4
  56. package/dist/index.d.ts +3 -2
  57. package/dist/index.js +6 -5
  58. package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
  59. package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
  60. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -8
  61. package/dist/layout-bridge/convert/headerFooterLayout.js +21 -13
  62. package/dist/layout-bridge/convert/toFlowBlocks.js +58 -19
  63. package/dist/layout-engine/index.d.ts +6 -5
  64. package/dist/layout-engine/index.js +116 -27
  65. package/dist/layout-engine/keep-together.d.ts +13 -8
  66. package/dist/layout-engine/keep-together.js +40 -25
  67. package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
  68. package/dist/layout-engine/layoutInstrumentation.js +10 -1
  69. package/dist/layout-engine/measure/measureBlocks.js +43 -19
  70. package/dist/layout-engine/measure/measureParagraph.js +91 -14
  71. package/dist/layout-engine/paginator.d.ts +2 -1
  72. package/dist/layout-engine/paginator.js +9 -3
  73. package/dist/layout-engine/textBoxGroup.d.ts +7 -0
  74. package/dist/layout-engine/textBoxGroup.js +8 -0
  75. package/dist/layout-engine/types.d.ts +33 -9
  76. package/dist/layout-engine/types.js +20 -2
  77. package/dist/layout-painter/renderPage.d.ts +15 -1
  78. package/dist/layout-painter/renderPage.js +29 -12
  79. package/dist/layout-painter/renderParagraph.js +44 -9
  80. package/dist/layout-painter/renderTable.js +27 -7
  81. package/dist/managers/DocumentLoaderManager.d.ts +3 -1
  82. package/dist/managers/DocumentLoaderManager.js +13 -5
  83. package/dist/managers/TableSelectionManager.js +1 -1
  84. package/dist/model.js +1 -1
  85. package/dist/paged-layout/pageText.d.ts +14 -0
  86. package/dist/paged-layout/pageText.js +21 -0
  87. package/dist/prosemirror/attrs/index.js +3 -0
  88. package/dist/prosemirror/commands/comments.js +75 -5
  89. package/dist/prosemirror/commands/hyperlink.js +4 -2
  90. package/dist/prosemirror/commands/image.js +2 -1
  91. package/dist/prosemirror/commands/index.js +1 -1
  92. package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
  93. package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
  94. package/dist/prosemirror/conversion/fromProseDoc.js +21 -5
  95. package/dist/prosemirror/conversion/toProseDoc.js +43 -15
  96. package/dist/prosemirror/extensions/core/ParagraphExtension.js +3 -0
  97. package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
  98. package/dist/prosemirror/extensions/nodes/TableExtension.js +12 -11
  99. package/dist/prosemirror/findReplaceSelection.js +2 -1
  100. package/dist/prosemirror/index.js +1 -1
  101. package/dist/prosemirror/insertOperations.js +1 -1
  102. package/dist/prosemirror/schema/nodes.d.ts +14 -7
  103. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  104. package/dist/prosemirror/utils/tabCalculator.js +4 -2
  105. package/dist/redline.d.ts +20 -0
  106. package/dist/redline.js +127 -0
  107. package/dist/server.d.ts +7 -3
  108. package/dist/server.js +6 -2
  109. package/dist/symbols.d.ts +35 -0
  110. package/dist/symbols.js +512 -0
  111. package/dist/utils/clipboard.js +3 -3
  112. package/dist/utils/fontLoader.js +3 -0
  113. package/dist/utils/fontResolver.js +65 -1
  114. package/dist/utils/zoom.d.ts +21 -0
  115. package/dist/utils/zoom.js +21 -0
  116. package/dist/version-comparison.d.ts +92 -0
  117. package/dist/version-comparison.js +436 -0
  118. package/package.json +7 -3
  119. package/dist/paged-layout/headerFooterMargins.d.ts +0 -73
  120. package/dist/paged-layout/headerFooterMargins.js +0 -120
@@ -23,13 +23,7 @@ type ApplyFolioAIEditOperationsOptions = {
23
23
  author?: string;
24
24
  createCommentId?: (text: string) => number;
25
25
  };
26
- declare const applyFolioAIEditOperations: ({
27
- view,
28
- snapshot,
29
- operations,
30
- mode,
31
- author,
32
- createCommentId
33
- }: ApplyFolioAIEditOperationsOptions) => FolioAIEditApplyResult;
26
+ declare const applyFolioAIEditOperations: (options: ApplyFolioAIEditOperationsOptions) => FolioAIEditApplyResult;
27
+ declare const previewFolioAIEditOperations: (options: ApplyFolioAIEditOperationsOptions) => FolioAIEditApplyResult;
34
28
  //#endregion
35
- export { FolioAIEditView, applyFolioAIEditOperations };
29
+ export { FolioAIEditView, applyFolioAIEditOperations, previewFolioAIEditOperations };
@@ -193,7 +193,7 @@ const buildEmphasisInlineContent = (schema, text, baseMarks) => {
193
193
  }
194
194
  return nodes.length > 0 ? nodes : [schema.text(text, [...baseMarks])];
195
195
  };
196
- const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", createCommentId }) => {
196
+ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode = "tracked-changes", author = "AI", createCommentId, revisionIdSeed }) => {
197
197
  const applied = [];
198
198
  const skipped = [];
199
199
  const resolved = [];
@@ -210,6 +210,13 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
210
210
  const liveBlocks = collectLiveBlocksByHash(view.state.doc);
211
211
  const liveBlocksByParaId = collectLiveBlocksByParaId(view.state.doc);
212
212
  for (const [index, operation] of operations.entries()) {
213
+ if (mode === "tracked-changes" && operation.type === "formatRange") {
214
+ skipped.push({
215
+ id: operation.id,
216
+ reason: "unsupportedMode"
217
+ });
218
+ continue;
219
+ }
213
220
  const commentText = getOperationCommentText(operation);
214
221
  if (commentText !== void 0 && (!commentType || createCommentId === void 0)) {
215
222
  skipped.push({
@@ -244,7 +251,7 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
244
251
  skipped
245
252
  };
246
253
  let tr = view.state.tr;
247
- let revisionSeed = nextRevisionSeed(resolved.length);
254
+ let revisionSeed = revisionIdSeed ?? nextRevisionSeed(resolved.length);
248
255
  const date = (/* @__PURE__ */ new Date()).toISOString();
249
256
  for (const item of resolved.toSorted((left, right) => {
250
257
  if (left.from !== right.from) return right.from - left.from;
@@ -254,7 +261,8 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
254
261
  const stepsBefore = tr.steps.length;
255
262
  let appliedRevisionIds;
256
263
  switch (item.operation.type) {
257
- case "replaceInBlock": {
264
+ case "replaceInBlock":
265
+ case "replaceRange": {
258
266
  const revisionIdDelete = revisionSeed++;
259
267
  const revisionIdInsert = revisionSeed++;
260
268
  tr = applyTextReplacement({
@@ -270,6 +278,17 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
270
278
  if (mode === "tracked-changes") appliedRevisionIds = [revisionIdDelete, revisionIdInsert];
271
279
  break;
272
280
  }
281
+ case "commentOnRange":
282
+ if (commentMark) tr = tr.addMark(item.from, item.to, commentMark);
283
+ break;
284
+ case "formatRange":
285
+ for (const [name, enabled] of Object.entries(item.operation.formatting)) {
286
+ const markType = view.state.schema.marks[name];
287
+ if (!markType) continue;
288
+ if (enabled) tr = tr.addMark(item.from, item.to, name === "underline" ? markType.create({ style: "single" }) : markType.create());
289
+ else tr = tr.removeMark(item.from, item.to, markType);
290
+ }
291
+ break;
273
292
  case "replaceBlock": {
274
293
  const revisionIdDelete = revisionSeed++;
275
294
  const revisionIdInsert = revisionSeed++;
@@ -314,7 +333,7 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
314
333
  if (mode === "tracked-changes" && item.operation.pageBreakBefore === true && (!item.insertText || item.insertText.length === 0)) {
315
334
  skipped.push({
316
335
  id: item.operation.id,
317
- reason: "unsupportedBlock"
336
+ reason: "unsupportedMode"
318
337
  });
319
338
  continue;
320
339
  }
@@ -338,6 +357,7 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
338
357
  attrs["listMarker"] = null;
339
358
  attrs["listMarkerHidden"] = null;
340
359
  attrs["listLevelNumFmts"] = null;
360
+ attrs["listLevelStarts"] = null;
341
361
  attrs["listAbstractNumId"] = null;
342
362
  attrs["listStartOverride"] = null;
343
363
  }
@@ -350,7 +370,7 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
350
370
  if (mode === "tracked-changes") {
351
371
  skipped.push({
352
372
  id: item.operation.id,
353
- reason: "unsupportedBlock"
373
+ reason: "unsupportedMode"
354
374
  });
355
375
  continue;
356
376
  }
@@ -411,15 +431,36 @@ const applyFolioAIEditOperations = ({ view, snapshot, operations, mode = "tracke
411
431
  skipped
412
432
  };
413
433
  };
434
+ const applyFolioAIEditOperations = (options) => applyFolioAIEditOperationsInternal(options);
435
+ const previewFolioAIEditOperations = (options) => {
436
+ const { view, createCommentId, ...applyOptions } = options;
437
+ let previewCommentId = -1;
438
+ const previewView = {
439
+ state: view.state,
440
+ dispatch: (transaction) => {
441
+ previewView.state = previewView.state.apply(transaction);
442
+ }
443
+ };
444
+ const result = applyFolioAIEditOperationsInternal({
445
+ ...applyOptions,
446
+ view: previewView,
447
+ ...createCommentId !== void 0 && { createCommentId: () => previewCommentId-- },
448
+ revisionIdSeed: -1e9
449
+ });
450
+ return {
451
+ applied: result.applied.map(({ id }) => ({ id })),
452
+ skipped: result.skipped
453
+ };
454
+ };
414
455
  const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark }) => {
415
456
  let nextTr = tr;
416
457
  const replacement = stripInlineEmphasisMarkers((() => {
417
- if (item.operation.type === "replaceInBlock") return item.operation.replace;
458
+ if (item.operation.type === "replaceInBlock" || item.operation.type === "replaceRange") return item.operation.replace;
418
459
  if (item.operation.type === "replaceBlock") return item.operation.text;
419
460
  return "";
420
461
  })());
421
462
  if (mode === "direct") {
422
- if (item.operation.type === "replaceInBlock" && hasInlineEmphasis(item.operation.replace)) {
463
+ if ((item.operation.type === "replaceInBlock" || item.operation.type === "replaceRange") && hasInlineEmphasis(item.operation.replace)) {
423
464
  const content = buildEmphasisInlineContent(nextTr.doc.type.schema, item.operation.replace, commentMark ? [commentMark] : []);
424
465
  return nextTr.replaceWith(item.from, item.to, content);
425
466
  }
@@ -447,6 +488,9 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
447
488
  sourceText = item.operation.find;
448
489
  sourceCleanStart = cleanBlock.text.indexOf(item.operation.find);
449
490
  if (sourceCleanStart === -1) sourceText = null;
491
+ } else if (item.operation.type === "replaceRange") {
492
+ sourceCleanStart = item.operation.range.startOffset;
493
+ sourceText = cleanBlock.text.slice(sourceCleanStart, item.operation.range.endOffset);
450
494
  } else if (item.operation.type === "replaceBlock") {
451
495
  sourceText = cleanBlock.text;
452
496
  sourceCleanStart = 0;
@@ -518,12 +562,13 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
518
562
  return nextTr;
519
563
  };
520
564
  const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId, doc }) => {
521
- const anchor = snapshot.anchors[operation.blockId];
565
+ const blockId = operation.type === "replaceRange" || operation.type === "commentOnRange" || operation.type === "formatRange" ? operation.range.blockId : operation.blockId;
566
+ const anchor = snapshot.anchors[blockId];
522
567
  if (!anchor) return {
523
568
  type: "skip",
524
569
  reason: "missingBlock"
525
570
  };
526
- const encodedParaId = getFolioParaIdFromBlockId(operation.blockId);
571
+ const encodedParaId = getFolioParaIdFromBlockId(blockId);
527
572
  let live;
528
573
  if (encodedParaId !== null) {
529
574
  live = liveBlocksByParaId.get(encodedParaId);
@@ -532,7 +577,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
532
577
  reason: "missingBlock"
533
578
  };
534
579
  } else {
535
- const ordinal = ordinalAmongSameHash(snapshot, operation.blockId);
580
+ const ordinal = ordinalAmongSameHash(snapshot, blockId);
536
581
  if (ordinal < 0) return {
537
582
  type: "skip",
538
583
  reason: "missingBlock"
@@ -548,10 +593,44 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
548
593
  const blockTo = live.to;
549
594
  const cleanBlock = buildCleanBlockText(blockNode, blockFrom);
550
595
  const currentText = cleanBlock.text;
551
- if (hashFolioAIBlockText(normalizeFolioAIBlockText(currentText)) !== anchor.textHash) return {
596
+ const currentTextHash = hashFolioAIBlockText(normalizeFolioAIBlockText(currentText));
597
+ if (operation.precondition !== void 0 && currentTextHash !== operation.precondition.blockTextHash) return {
598
+ type: "skip",
599
+ reason: "preconditionFailed"
600
+ };
601
+ if (currentTextHash !== anchor.textHash) return {
552
602
  type: "skip",
553
603
  reason: "changedBlock"
554
604
  };
605
+ if (operation.type === "replaceRange" || operation.type === "commentOnRange" || operation.type === "formatRange") {
606
+ const { startOffset, endOffset, selectedTextHash } = operation.range;
607
+ const from = cleanBlock.offsets[startOffset];
608
+ const to = cleanBlock.offsets[endOffset];
609
+ if (from === void 0 || to === void 0) return {
610
+ type: "skip",
611
+ reason: "staleRange"
612
+ };
613
+ const selectedText = currentText.slice(startOffset, endOffset);
614
+ if (hashFolioAIBlockText(selectedText) !== selectedTextHash) return {
615
+ type: "skip",
616
+ reason: "staleRange"
617
+ };
618
+ if (operation.type === "replaceRange" && selectedText === operation.replace) return {
619
+ type: "skip",
620
+ reason: "noopOperation"
621
+ };
622
+ return {
623
+ type: "resolved",
624
+ operation: {
625
+ operation,
626
+ from,
627
+ to,
628
+ blockFrom,
629
+ blockTo,
630
+ blockNode
631
+ }
632
+ };
633
+ }
555
634
  if (operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") {
556
635
  if (operation.text.length === 0 && operation.pageBreakBefore !== true) return {
557
636
  type: "skip",
@@ -623,7 +702,8 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
623
702
  type: "skip",
624
703
  reason: "noopOperation"
625
704
  };
626
- const range = resolveTextInCleanBlock(cleanBlock, getOperationQuote(operation) || currentText);
705
+ const quote = getOperationQuote(operation);
706
+ const range = resolveTextInCleanBlock(cleanBlock, quote || currentText);
627
707
  if (range.type !== "resolved") return range;
628
708
  return {
629
709
  type: "resolved",
@@ -674,10 +754,10 @@ const getTextRangeFromCleanBlock = (cleanBlock) => {
674
754
  to
675
755
  };
676
756
  };
677
- const getOperationCommentText = (operation) => operation.comment?.text;
757
+ const getOperationCommentText = (operation) => "comment" in operation ? operation.comment?.text : void 0;
678
758
  const getOperationQuote = (operation) => {
679
759
  if (operation.type === "replaceInBlock") return operation.find;
680
760
  if (operation.type === "commentOnBlock") return operation.quote;
681
761
  };
682
762
  //#endregion
683
- export { applyFolioAIEditOperations };
763
+ export { applyFolioAIEditOperations, previewFolioAIEditOperations };
@@ -0,0 +1,33 @@
1
+ import { FolioAIEditSnapshot } from "./types.js";
2
+ import { Node } from "prosemirror-model";
3
+
4
+ //#region src/ai-edits/blockRange.d.ts
5
+ type DocPositionRange = {
6
+ from: number;
7
+ to: number;
8
+ };
9
+ type ResolveFolioAIBlockRangeOptions = {
10
+ blockId: string;
11
+ doc: Node;
12
+ snapshot?: FolioAIEditSnapshot | null | undefined;
13
+ };
14
+ declare const resolveFolioAIBlockRange: ({
15
+ blockId,
16
+ doc,
17
+ snapshot
18
+ }: ResolveFolioAIBlockRangeOptions) => DocPositionRange | null;
19
+ /**
20
+ * Clamp a `{from, to}` pair so both endpoints fit inside a document of
21
+ * `docSize` (in PM content positions). Block-boundary snapshots and stale
22
+ * range data sometimes produce a `to` one past the last inline position;
23
+ * `view.state.doc.resolve(...)` rejects that with
24
+ * "Position … out of range", and `TextSelection.between` doesn't help — it
25
+ * needs *valid* resolved positions. Clamping before resolution is the cheap
26
+ * defensive step.
27
+ *
28
+ * Order is preserved: if both endpoints exceed `docSize`, the returned
29
+ * `from` may equal `to`, yielding a cursor selection at the doc end.
30
+ */
31
+ declare function clampRangeToDocSize(docSize: number, range: DocPositionRange): DocPositionRange;
32
+ //#endregion
33
+ export { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange };
@@ -0,0 +1,58 @@
1
+ import { getFolioParaIdFromBlockId, getSequentialFolioBlockIdIndex } from "../types/block-id.js";
2
+ import { createFolioAIEditSnapshot } from "./snapshot.js";
3
+ import { findParagraphByParaId } from "../prosemirror/utils/findParagraphByParaId.js";
4
+ //#region src/ai-edits/blockRange.ts
5
+ const resolveFolioAIBlockRange = ({ blockId, doc, snapshot }) => {
6
+ const paraId = getFolioParaIdFromBlockId(blockId);
7
+ if (paraId !== null) {
8
+ const liveRange = findParagraphByParaId(doc, paraId);
9
+ if (liveRange !== null) return {
10
+ from: liveRange.from,
11
+ to: liveRange.to
12
+ };
13
+ }
14
+ const resolvedSnapshot = snapshot ?? createFolioAIEditSnapshot(doc);
15
+ const anchor = resolvedSnapshot.anchors[blockId] ?? resolveSequentialBlockAnchor(blockId, resolvedSnapshot);
16
+ if (!anchor) return null;
17
+ return clampRangeToDocSize(doc.content.size, anchor);
18
+ };
19
+ /**
20
+ * Resolve a `seq-NNNN` fallback id by document position.
21
+ *
22
+ * A sequential id is only minted for a paragraph the source DOCX left
23
+ * without a `w14:paraId`. The live editor's `ParaIdAllocator` fills
24
+ * that gap with a fresh random hex paraId, so a snapshot of the live
25
+ * document keys the block by that hex and never reproduces the
26
+ * server's `seq-NNNN`: the direct anchor lookup misses, and a
27
+ * paraId-based live lookup can't match either (no node carries a
28
+ * `seq-` paraId). The seq number is the block's 1-based position in
29
+ * the same non-empty-block walk the server extractor and
30
+ * `createFolioAIEditSnapshot` share, so it indexes the snapshot's
31
+ * ordered `blocks` directly.
32
+ */
33
+ const resolveSequentialBlockAnchor = (blockId, snapshot) => {
34
+ const index = getSequentialFolioBlockIdIndex(blockId);
35
+ if (index === null) return;
36
+ const block = snapshot.blocks.at(index - 1);
37
+ return block ? snapshot.anchors[block.id] : void 0;
38
+ };
39
+ /**
40
+ * Clamp a `{from, to}` pair so both endpoints fit inside a document of
41
+ * `docSize` (in PM content positions). Block-boundary snapshots and stale
42
+ * range data sometimes produce a `to` one past the last inline position;
43
+ * `view.state.doc.resolve(...)` rejects that with
44
+ * "Position … out of range", and `TextSelection.between` doesn't help — it
45
+ * needs *valid* resolved positions. Clamping before resolution is the cheap
46
+ * defensive step.
47
+ *
48
+ * Order is preserved: if both endpoints exceed `docSize`, the returned
49
+ * `from` may equal `to`, yielding a cursor selection at the doc end.
50
+ */
51
+ function clampRangeToDocSize(docSize, range) {
52
+ return {
53
+ from: Math.min(Math.max(range.from, 0), docSize),
54
+ to: Math.min(Math.max(range.to, 0), docSize)
55
+ };
56
+ }
57
+ //#endregion
58
+ export { clampRangeToDocSize, resolveFolioAIBlockRange };
@@ -1,11 +1,13 @@
1
1
  import { FolioAIBlock, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditSnapshot } from "./types.js";
2
2
  import { document_d_exports } from "../types/document.js";
3
+ import { FolioDocumentOperationBatch, FolioDocumentOperationResult } from "../document-operations.js";
3
4
  import { FolioReviewChange, FolioReviewChangeKind } from "./read.js";
4
5
 
5
6
  //#region src/ai-edits/headless.d.ts
6
7
  /** Options for {@link FolioDocxReviewer.fromBuffer}. */
7
8
  type FolioDocxReviewerOptions = {
8
- /** Default author for tracked changes and comments. (default: `"AI"`) */author?: string;
9
+ /** Default author for tracked changes and comments. (default: `"AI"`) */author?: string; /** Password for Agile-encrypted .docx files (Office 2010+). */
10
+ password?: string | undefined;
9
11
  };
10
12
  /** Options for {@link FolioDocxReviewer.applyOperations}. */
11
13
  type FolioApplyOperationsOptions = {
@@ -17,6 +19,8 @@ type FolioApplyOperationsOptions = {
17
19
  */
18
20
  snapshot?: FolioAIEditSnapshot;
19
21
  };
22
+ /** Options for {@link FolioDocxReviewer.applyDocumentOperations}. */
23
+ type FolioApplyDocumentOperationsOptions = Omit<FolioApplyOperationsOptions, "mode">;
20
24
  /** Options for {@link FolioDocxReviewer.getContentAsText}. */
21
25
  type FolioGetContentAsTextOptions = {
22
26
  /**
@@ -26,6 +30,19 @@ type FolioGetContentAsTextOptions = {
26
30
  */
27
31
  annotated?: boolean;
28
32
  };
33
+ type FolioDocumentStoryHandle = {
34
+ type: "main";
35
+ } | {
36
+ type: "header" | "footer";
37
+ relationshipId: string;
38
+ } | {
39
+ type: "footnote" | "endnote";
40
+ noteId: number;
41
+ };
42
+ type FolioDocumentStory = {
43
+ handle: FolioDocumentStoryHandle;
44
+ text: string;
45
+ };
29
46
  /** Filter for {@link FolioDocxReviewer.getChanges}. */
30
47
  type FolioReviewChangeFilter = {
31
48
  author?: string;
@@ -104,6 +121,13 @@ declare class FolioDocxReviewer {
104
121
  * (including any comments) is retained for {@link toBuffer}.
105
122
  */
106
123
  applyOperations(operations: FolioAIEditOperation[], options?: FolioApplyOperationsOptions): FolioAIEditApplyResult;
124
+ /**
125
+ * Apply a versioned document-operation batch against the current state.
126
+ * This is the contract entry point for serialized callers; the legacy
127
+ * {@link applyOperations} method delegates here so both APIs keep identical
128
+ * edit semantics.
129
+ */
130
+ applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
107
131
  /**
108
132
  * The body blocks with their stable ids, in document order — the same
109
133
  * `FolioAIBlock` shape {@link snapshot} builds, reused verbatim so the ids
@@ -130,6 +154,10 @@ declare class FolioDocxReviewer {
130
154
  * Empty parts and separator notes are omitted.
131
155
  */
132
156
  getNotesAsText(): string;
157
+ /** Discover every readable document story through a typed, serializable handle. */
158
+ listStories(): FolioDocumentStory[];
159
+ /** Read one discovered story; returns null when its handle is no longer present. */
160
+ readStory(handle: FolioDocumentStoryHandle): FolioDocumentStory | null;
133
161
  /**
134
162
  * The tracked changes (insertions and deletions) present in the body, read
135
163
  * from the same `insertion` / `deletion` marks the editor renders. Each
@@ -209,9 +237,11 @@ declare class FolioDocxReviewer {
209
237
  private trySelectiveSave;
210
238
  /**
211
239
  * Count every tracked change an accept-all / reject-all sweep resolves: the
212
- * inline insertion / deletion groups {@link getChanges} enumerates, plus
213
- * paragraph-level changes (`pPrMark`, `_propertyChanges`) that live on
214
- * paragraph attrs rather than inline marks.
240
+ * inline insertion / deletion groups {@link getChanges} enumerates, plus the
241
+ * property-change records living on node attrs rather than inline marks —
242
+ * paragraph-level (`pPrMark`, `_propertyChanges`, the inline sectPr's
243
+ * `propertyChanges`) and table-level (`tblPrChange` / `trPrChange` /
244
+ * `tcPrChange`).
215
245
  */
216
246
  private countTrackedChanges;
217
247
  /** Apply any {@link resolveComment} overrides recorded for these comments. */
@@ -249,4 +279,4 @@ type ApplyFolioAIEditsToBufferResult = FolioAIEditApplyResult & {
249
279
  */
250
280
  declare const applyFolioAIEditsToBuffer: (buffer: ArrayBuffer, operations: FolioAIEditOperation[], options?: ApplyFolioAIEditsToBufferOptions) => Promise<ApplyFolioAIEditsToBufferResult>;
251
281
  //#endregion
252
- export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FolioApplyOperationsOptions, FolioDocxReviewer, FolioDocxReviewerOptions, FolioGetContentAsTextOptions, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, applyFolioAIEditsToBuffer };
282
+ export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FolioApplyDocumentOperationsOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocxReviewer, FolioDocxReviewerOptions, FolioGetContentAsTextOptions, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, applyFolioAIEditsToBuffer };
@@ -1,9 +1,9 @@
1
+ import { buildAnnotatedBlockText } from "./clean-text.js";
2
+ import { createFolioAIEditSnapshot, normalizeFolioAIBlockText } from "./snapshot.js";
3
+ import { applyFolioDocumentOperations } from "../document-operations.js";
1
4
  import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
2
5
  import { deterministicHexId } from "../utils/hexId.js";
3
6
  import { repackDocx } from "../docx/rezip.js";
4
- import { buildAnnotatedBlockText } from "./clean-text.js";
5
- import { createFolioAIEditSnapshot, normalizeFolioAIBlockText } from "./snapshot.js";
6
- import { applyFolioAIEditOperations } from "./apply.js";
7
7
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
8
8
  import { createReply } from "../docx/replyToComment.js";
9
9
  import { attemptSelectiveSave } from "../docx/selectiveSave.js";
@@ -181,17 +181,19 @@ var FolioDocxReviewer = class FolioDocxReviewer {
181
181
  static async fromBuffer(buffer, options = {}) {
182
182
  const baseDocument = await parseDocx(buffer, {
183
183
  detectVariables: false,
184
- preloadFonts: false
184
+ preloadFonts: false,
185
+ password: options.password
185
186
  });
186
187
  const plugins = singletonManager.getPlugins();
188
+ const state = ensureDeterministicParaIdsInState(EditorState.create({
189
+ schema,
190
+ doc: toProseDoc(baseDocument),
191
+ plugins
192
+ }));
187
193
  return new FolioDocxReviewer({
188
194
  baseDocument,
189
- originalBuffer: buffer,
190
- state: ensureDeterministicParaIdsInState(EditorState.create({
191
- schema,
192
- doc: toProseDoc(baseDocument),
193
- plugins
194
- })),
195
+ originalBuffer: baseDocument.originalBuffer ?? buffer,
196
+ state,
195
197
  author: options.author ?? "AI"
196
198
  });
197
199
  }
@@ -208,18 +210,33 @@ var FolioDocxReviewer = class FolioDocxReviewer {
208
210
  * (including any comments) is retained for {@link toBuffer}.
209
211
  */
210
212
  applyOperations(operations, options = {}) {
211
- const snapshot = options.snapshot ?? this.snapshot();
213
+ const { applied, skipped } = this.applyDocumentOperations({
214
+ version: 1,
215
+ operations,
216
+ mode: options.mode ?? "tracked-changes"
217
+ }, { ...options.snapshot !== void 0 && { snapshot: options.snapshot } });
218
+ return {
219
+ applied,
220
+ skipped
221
+ };
222
+ }
223
+ /**
224
+ * Apply a versioned document-operation batch against the current state.
225
+ * This is the contract entry point for serialized callers; the legacy
226
+ * {@link applyOperations} method delegates here so both APIs keep identical
227
+ * edit semantics.
228
+ */
229
+ applyDocumentOperations(batch, options = {}) {
212
230
  const view = {
213
231
  state: this.state,
214
232
  dispatch: (transaction) => {
215
233
  view.state = view.state.apply(transaction);
216
234
  }
217
235
  };
218
- const result = applyFolioAIEditOperations({
236
+ const result = applyFolioDocumentOperations({
219
237
  view,
220
- snapshot,
221
- operations,
222
- mode: options.mode ?? "tracked-changes",
238
+ snapshot: options.snapshot ?? this.snapshot(),
239
+ batch,
223
240
  author: this.author,
224
241
  createCommentId: (text) => {
225
242
  const comment = createReviewerComment(text, this.author);
@@ -257,7 +274,8 @@ var FolioDocxReviewer = class FolioDocxReviewer {
257
274
  return snapshot.blocks.map((block) => {
258
275
  const from = startById.get(block.id);
259
276
  const node = from === void 0 ? null : this.state.doc.nodeAt(from);
260
- return formatBlockLine(block, node ? buildAnnotatedBlockText(node) : block.text);
277
+ const text = node ? buildAnnotatedBlockText(node) : block.text;
278
+ return formatBlockLine(block, text);
261
279
  }).join("\n");
262
280
  }
263
281
  /**
@@ -291,6 +309,50 @@ var FolioDocxReviewer = class FolioDocxReviewer {
291
309
  }
292
310
  return lines.join("\n");
293
311
  }
312
+ /** Discover every readable document story through a typed, serializable handle. */
313
+ listStories() {
314
+ const pkg = this.baseDocument.package;
315
+ const stories = [{
316
+ handle: { type: "main" },
317
+ text: this.getContentAsText()
318
+ }];
319
+ for (const [relationshipId, header] of pkg.headers ?? []) stories.push({
320
+ handle: {
321
+ type: "header",
322
+ relationshipId
323
+ },
324
+ text: normalizeFolioAIBlockText(getHeaderFooterText(header))
325
+ });
326
+ for (const [relationshipId, footer] of pkg.footers ?? []) stories.push({
327
+ handle: {
328
+ type: "footer",
329
+ relationshipId
330
+ },
331
+ text: normalizeFolioAIBlockText(getHeaderFooterText(footer))
332
+ });
333
+ for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) stories.push({
334
+ handle: {
335
+ type: "footnote",
336
+ noteId: footnote.id
337
+ },
338
+ text: normalizeFolioAIBlockText(getFootnoteText(footnote))
339
+ });
340
+ for (const endnote of pkg.endnotes ?? []) if (!isSeparatorEndnote(endnote)) stories.push({
341
+ handle: {
342
+ type: "endnote",
343
+ noteId: endnote.id
344
+ },
345
+ text: normalizeFolioAIBlockText(getEndnoteText(endnote))
346
+ });
347
+ return stories;
348
+ }
349
+ /** Read one discovered story; returns null when its handle is no longer present. */
350
+ readStory(handle) {
351
+ const stories = this.listStories();
352
+ if (handle.type === "main") return stories.at(0) ?? null;
353
+ if (handle.type === "header" || handle.type === "footer") return stories.find(({ handle: candidate }) => candidate.type === handle.type && candidate.relationshipId === handle.relationshipId) ?? null;
354
+ return stories.find(({ handle: candidate }) => (candidate.type === "footnote" || candidate.type === "endnote") && candidate.type === handle.type && candidate.noteId === handle.noteId) ?? null;
355
+ }
294
356
  /**
295
357
  * The tracked changes (insertions and deletions) present in the body, read
296
358
  * from the same `insertion` / `deletion` marks the editor renders. Each
@@ -462,18 +524,26 @@ var FolioDocxReviewer = class FolioDocxReviewer {
462
524
  }
463
525
  /**
464
526
  * Count every tracked change an accept-all / reject-all sweep resolves: the
465
- * inline insertion / deletion groups {@link getChanges} enumerates, plus
466
- * paragraph-level changes (`pPrMark`, `_propertyChanges`) that live on
467
- * paragraph attrs rather than inline marks.
527
+ * inline insertion / deletion groups {@link getChanges} enumerates, plus the
528
+ * property-change records living on node attrs rather than inline marks —
529
+ * paragraph-level (`pPrMark`, `_propertyChanges`, the inline sectPr's
530
+ * `propertyChanges`) and table-level (`tblPrChange` / `trPrChange` /
531
+ * `tcPrChange`).
468
532
  */
469
533
  countTrackedChanges() {
534
+ const hasEntries = (value) => Array.isArray(value) && value.length > 0;
470
535
  let count = this.getChanges().length;
471
536
  this.state.doc.descendants((node) => {
472
- if (node.type.name !== "paragraph") return;
537
+ const typeName = node.type.name;
538
+ if (typeName === "table" && hasEntries(node.attrs["tblPrChange"])) count += 1;
539
+ if (typeName === "tableRow" && hasEntries(node.attrs["trPrChange"])) count += 1;
540
+ if ((typeName === "tableCell" || typeName === "tableHeader") && hasEntries(node.attrs["tcPrChange"])) count += 1;
541
+ if (typeName !== "paragraph") return;
473
542
  const pPrMark = node.attrs["pPrMark"];
474
543
  if (pPrMark !== null && pPrMark !== void 0) count += 1;
475
- const propertyChanges = node.attrs["_propertyChanges"];
476
- if (Array.isArray(propertyChanges) && propertyChanges.length > 0) count += 1;
544
+ if (hasEntries(node.attrs["_propertyChanges"])) count += 1;
545
+ const sectionProperties = node.attrs["_sectionProperties"];
546
+ if (hasEntries(sectionProperties?.propertyChanges)) count += 1;
477
547
  return false;
478
548
  });
479
549
  return count;
@@ -1,8 +1,10 @@
1
- import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAISignatureParty } from "./types.js";
1
+ import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAISignatureParty, FolioAITextRangeHandle } from "./types.js";
2
2
  import { FolioAIEditView, applyFolioAIEditOperations } from "./apply.js";
3
3
  import { buildAnnotatedBlockText } from "./clean-text.js";
4
+ import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationType, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
4
5
  import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
5
- import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
6
+ import { FolioDocumentStory, FolioDocumentStoryHandle } from "./headless.js";
7
+ import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
6
8
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
7
9
  import { WordDiffSegment, diffWordSegments } from "./word-diff.js";
8
- export { type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAISignatureParty, type FolioCommentAnchor, type FolioReviewChange, type FolioReviewChangeKind, type WordDiffSegment, applyFolioAIEditOperations, buildAnnotatedBlockText, createFolioAIEditSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, normalizeFolioAIBlockText };
10
+ export { type ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineFormatting, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioCommentAnchor, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationType, type FolioDocumentStory, type FolioDocumentStoryHandle, type FolioReviewChange, type FolioReviewChangeKind, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch };
@@ -1,7 +1,8 @@
1
1
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
2
2
  import { buildAnnotatedBlockText } from "./clean-text.js";
3
- import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
3
+ import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
4
4
  import { diffWordSegments } from "./word-diff.js";
5
5
  import { applyFolioAIEditOperations } from "./apply.js";
6
+ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
6
7
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
7
- export { applyFolioAIEditOperations, buildAnnotatedBlockText, createFolioAIEditSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, normalizeFolioAIBlockText };
8
+ export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch };
@@ -1,9 +1,21 @@
1
- import { FolioAIEditSnapshot } from "./types.js";
1
+ import { FolioAIEditSnapshot, FolioAITextRangeHandle } from "./types.js";
2
2
  import { Node } from "prosemirror-model";
3
3
 
4
4
  //#region src/ai-edits/snapshot.d.ts
5
5
  declare const normalizeFolioAIBlockText: (text: string) => string;
6
6
  declare const hashFolioAIBlockText: (text: string) => string;
7
+ type CreateFolioAITextRangeHandleOptions = {
8
+ blockId: string;
9
+ text: string;
10
+ startOffset: number;
11
+ endOffset: number;
12
+ };
13
+ declare const createFolioAITextRangeHandle: ({
14
+ blockId,
15
+ text,
16
+ startOffset,
17
+ endOffset
18
+ }: CreateFolioAITextRangeHandleOptions) => FolioAITextRangeHandle | null;
7
19
  declare const createFolioAIEditSnapshot: (doc: Node) => FolioAIEditSnapshot;
8
20
  //#endregion
9
- export { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText };
21
+ export { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText };