@stll/folio-core 0.9.0 → 0.10.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 (44) hide show
  1. package/dist/ai-edits/headless.d.ts +52 -12
  2. package/dist/ai-edits/headless.js +181 -55
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/controller/layoutPipeline.js +2 -1
  6. package/dist/document-operations.d.ts +11 -2
  7. package/dist/document-operations.js +7 -1
  8. package/dist/document-stories.d.ts +10 -0
  9. package/dist/document-stories.js +27 -0
  10. package/dist/docx/corePropertiesParser.d.ts +8 -0
  11. package/dist/docx/corePropertiesParser.js +53 -0
  12. package/dist/docx/index.d.ts +2 -1
  13. package/dist/docx/index.js +2 -1
  14. package/dist/docx/metadataPrivacy.d.ts +40 -0
  15. package/dist/docx/metadataPrivacy.js +131 -0
  16. package/dist/docx/parser.js +4 -1
  17. package/dist/docx/rezip.d.ts +5 -4
  18. package/dist/docx/rezip.js +6 -8
  19. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  20. package/dist/layout-engine/measure/cache.js +2 -7
  21. package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
  22. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
  23. package/dist/layout-engine/measure/measureParagraph.js +32 -36
  24. package/dist/layout-engine/measure/tableCellFloating.js +39 -33
  25. package/dist/layout-engine/types.d.ts +5 -3
  26. package/dist/prosemirror/attrs/index.js +1 -0
  27. package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
  28. package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
  29. package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
  30. package/dist/prosemirror/conversion/toProseDoc.js +40 -68
  31. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
  32. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  33. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  34. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  35. package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
  36. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
  37. package/dist/prosemirror/schema/nodes.d.ts +2 -1
  38. package/dist/redline.d.ts +26 -11
  39. package/dist/redline.js +95 -62
  40. package/dist/server.d.ts +5 -4
  41. package/dist/server.js +5 -4
  42. package/dist/version-comparison.d.ts +65 -11
  43. package/dist/version-comparison.js +187 -29
  44. package/package.json +1 -1
@@ -30,22 +30,50 @@ type FolioGetContentAsTextOptions = {
30
30
  */
31
31
  annotated?: boolean;
32
32
  };
33
+ declare const FOLIO_REVIEWED_VIEWS: readonly ["original", "current-markup", "final"];
34
+ type FolioReviewedView = (typeof FOLIO_REVIEWED_VIEWS)[number];
35
+ declare const FOLIO_RESOLVED_REVIEWED_VIEWS: readonly ["original", "final"];
36
+ type FolioResolvedReviewedView = (typeof FOLIO_RESOLVED_REVIEWED_VIEWS)[number];
33
37
  type FolioDocumentStoryHandle = {
34
38
  type: "main";
35
39
  } | {
36
- type: "header" | "footer";
40
+ type: "header";
37
41
  relationshipId: string;
38
42
  } | {
39
- type: "footnote" | "endnote";
43
+ type: "footer";
44
+ relationshipId: string;
45
+ } | {
46
+ type: "footnote";
47
+ noteId: number;
48
+ } | {
49
+ type: "endnote";
40
50
  noteId: number;
41
51
  };
42
- type FolioEditableDocumentStoryHandle = Exclude<FolioDocumentStoryHandle, {
43
- type: "footnote" | "endnote";
44
- }>;
52
+ type FolioEditableDocumentStoryHandle = FolioDocumentStoryHandle;
45
53
  type FolioDocumentStory = {
46
54
  handle: FolioDocumentStoryHandle;
47
55
  text: string;
48
56
  };
57
+ type FolioReadReviewedStoryOptions = {
58
+ story?: FolioEditableDocumentStoryHandle;
59
+ view?: FolioReviewedView;
60
+ };
61
+ type FolioResolveReviewedStoryOptions = {
62
+ story?: FolioEditableDocumentStoryHandle;
63
+ view: FolioResolvedReviewedView;
64
+ };
65
+ type FolioReviewedStory = {
66
+ story: FolioEditableDocumentStoryHandle;
67
+ view: FolioReviewedView;
68
+ snapshot: FolioAIEditSnapshot;
69
+ text: string;
70
+ changes: FolioReviewChange[];
71
+ };
72
+ declare const UnsupportedFolioReviewedViewError_base: import("better-result").TaggedErrorClass<"UnsupportedFolioReviewedViewError", {
73
+ message: string;
74
+ receivedView: unknown;
75
+ }>;
76
+ declare class UnsupportedFolioReviewedViewError extends UnsupportedFolioReviewedViewError_base {}
49
77
  declare const FolioDocumentStoryNotFoundError_base: import("better-result").TaggedErrorClass<"FolioDocumentStoryNotFoundError", {
50
78
  message: string;
51
79
  story: FolioEditableDocumentStoryHandle;
@@ -55,6 +83,8 @@ type FolioApplyDocumentOperationsToStoryOptions = FolioApplyDocumentOperationsOp
55
83
  story: FolioEditableDocumentStoryHandle;
56
84
  batch: FolioDocumentOperationBatch;
57
85
  };
86
+ declare const isFolioReviewedView: (value: unknown) => value is FolioReviewedView;
87
+ declare const isFolioResolvedReviewedView: (value: unknown) => value is FolioResolvedReviewedView;
58
88
  /** Filter for {@link FolioDocxReviewer.getChanges}. */
59
89
  type FolioReviewChangeFilter = {
60
90
  author?: string;
@@ -109,7 +139,7 @@ declare class FolioDocxReviewer {
109
139
  private readonly baseDocument;
110
140
  private readonly originalBuffer;
111
141
  private state;
112
- private readonly headerFooterStoryStates;
142
+ private readonly secondaryStoryStates;
113
143
  private readonly createdComments;
114
144
  private readonly documentOperationUndoEntries;
115
145
  /**
@@ -129,8 +159,17 @@ declare class FolioDocxReviewer {
129
159
  * the anchor map the apply layer resolves operations against.
130
160
  */
131
161
  snapshot(): FolioAIEditSnapshot;
162
+ /** Return parsed package metadata without exposing the mutable document model. */
163
+ getDocumentProperties(): Readonly<NonNullable<document_d_exports.Document["package"]["properties"]>> | null;
132
164
  /** Snapshot one editable story into stable, operation-ready blocks. */
133
165
  snapshotStory(story: FolioEditableDocumentStoryHandle): FolioAIEditSnapshot | null;
166
+ /** Read one story through an immutable reviewed-view projection. */
167
+ readReviewedStory(options?: FolioReadReviewedStoryOptions): FolioReviewedStory | null;
168
+ /** Resolve one editable story to its original or final state. */
169
+ resolveReviewedStory({
170
+ story,
171
+ view
172
+ }: FolioResolveReviewedStoryOptions): boolean;
134
173
  /**
135
174
  * Apply operations against the current state. Reuses the live-editor applier
136
175
  * verbatim via a headless `{ state, dispatch }` seam; the resulting state
@@ -144,7 +183,7 @@ declare class FolioDocxReviewer {
144
183
  * edit semantics.
145
184
  */
146
185
  applyDocumentOperations(batch: FolioDocumentOperationBatch, options?: FolioApplyDocumentOperationsOptions): FolioDocumentOperationResult;
147
- /** Apply a versioned operation batch to the main story, a header, or a footer. */
186
+ /** Apply a versioned operation batch to one editable document story. */
148
187
  applyDocumentOperationsToStory({
149
188
  story,
150
189
  batch,
@@ -174,9 +213,8 @@ declare class FolioDocxReviewer {
174
213
  /**
175
214
  * Header / footer and footnote / endnote text as labeled, LLM-ready lines,
176
215
  * one per non-empty part: `[header default] …`, `[footer default] …`,
177
- * `[footnote #N] …`, `[endnote #N] …`. Header and footer lines reflect
178
- * in-memory edits; note bodies reflect the parsed source. Empty parts and
179
- * separator notes are omitted.
216
+ * `[footnote #N] …`, `[endnote #N] …`. Lines reflect in-memory edits. Empty
217
+ * parts and separator notes are omitted.
180
218
  */
181
219
  getNotesAsText(): string;
182
220
  /** Discover every readable document story through a typed, serializable handle. */
@@ -264,8 +302,10 @@ declare class FolioDocxReviewer {
264
302
  private requireEditableStoryState;
265
303
  private setEditableStoryState;
266
304
  private getHeaderFooterStory;
305
+ private getNoteStory;
267
306
  private getHeaderFooterStoryText;
268
- private mergeEditedHeaderFooterStories;
307
+ private getNoteStoryText;
308
+ private mergeEditedSecondaryStories;
269
309
  /**
270
310
  * Count every tracked change an accept-all / reject-all sweep resolves: the
271
311
  * inline insertion / deletion groups {@link getChanges} enumerates, plus the
@@ -310,4 +350,4 @@ type ApplyFolioAIEditsToBufferResult = FolioAIEditApplyResult & {
310
350
  */
311
351
  declare const applyFolioAIEditsToBuffer: (buffer: ArrayBuffer, operations: FolioAIEditOperation[], options?: ApplyFolioAIEditsToBufferOptions) => Promise<ApplyFolioAIEditsToBufferResult>;
312
352
  //#endregion
313
- export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, applyFolioAIEditsToBuffer };
353
+ export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView };
@@ -13,7 +13,7 @@ import { acceptAIEditRevision, acceptAllChanges, rejectAIEditRevision, rejectAll
13
13
  import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
14
14
  import { getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
15
15
  import { schema, singletonManager } from "../prosemirror/schema/index.js";
16
- import { headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
16
+ import { footnoteToProseDoc, headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
17
17
  import { TaggedError } from "better-result";
18
18
  import { EditorState } from "prosemirror-state";
19
19
  //#region src/ai-edits/headless.ts
@@ -34,8 +34,7 @@ import { EditorState } from "prosemirror-state";
34
34
  * paragraphs in `document.xml` (leaving every untouched part byte-exact), with
35
35
  * a full repack as the fallback for structural edits.
36
36
  *
37
- * Scope: main, header, and footer blocks. Footnote and endnote bodies build on
38
- * the note serialization path separately.
37
+ * Scope: main, header, footer, footnote, and endnote blocks.
39
38
  */
40
39
  /**
41
40
  * Standalone comment-id allocator for the reviewer. The React editor uses
@@ -115,9 +114,43 @@ const ensureDeterministicParaIdsInState = (state) => {
115
114
  tr.setMeta("addToHistory", false);
116
115
  return state.apply(tr);
117
116
  };
117
+ const FOLIO_REVIEWED_VIEWS = Object.freeze([
118
+ "original",
119
+ "current-markup",
120
+ "final"
121
+ ]);
122
+ const FOLIO_RESOLVED_REVIEWED_VIEWS = Object.freeze(["original", "final"]);
123
+ var UnsupportedFolioReviewedViewError = class extends TaggedError("UnsupportedFolioReviewedViewError")() {};
118
124
  var FolioDocumentStoryNotFoundError = class extends TaggedError("FolioDocumentStoryNotFoundError")() {};
119
125
  const MAIN_STORY = Object.freeze({ type: "main" });
120
126
  const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
127
+ const noteStoryKey = ({ type, noteId }) => `${type}:${noteId}`;
128
+ const secondaryStoryKey = (story) => story.type === "header" || story.type === "footer" ? headerFooterStoryKey(story) : noteStoryKey(story);
129
+ const isFolioReviewedView = (value) => FOLIO_REVIEWED_VIEWS.some((view) => view === value);
130
+ const isFolioResolvedReviewedView = (value) => FOLIO_RESOLVED_REVIEWED_VIEWS.some((view) => view === value);
131
+ const applyCommandToState = (state, command) => {
132
+ let nextState = state;
133
+ command(state, (transaction) => {
134
+ nextState = nextState.apply(transaction);
135
+ });
136
+ return nextState;
137
+ };
138
+ const resolveReviewedState = (state, view) => {
139
+ if (view === "current-markup") return state;
140
+ return applyCommandToState(state, view === "original" ? rejectAllChanges() : acceptAllChanges());
141
+ };
142
+ const formatStoryStateForLLM = (state, annotated) => {
143
+ const snapshot = createFolioAIEditSnapshot(state.doc);
144
+ if (!annotated) return snapshot.blocks.map(formatBlockForLLM).join("\n");
145
+ const startById = /* @__PURE__ */ new Map();
146
+ for (const anchor of Object.values(snapshot.anchors)) startById.set(anchor.id, anchor.from);
147
+ return snapshot.blocks.map((block) => {
148
+ const from = startById.get(block.id);
149
+ const node = from === void 0 ? null : state.doc.nodeAt(from);
150
+ const text = node ? buildAnnotatedBlockText(node) : block.text;
151
+ return formatBlockLine(block, text);
152
+ }).join("\n");
153
+ };
121
154
  /**
122
155
  * One LLM-ready line for a block: `[<blockId>] text`, with an `(h<level>)` tag
123
156
  * for headings and the list marker for list items, so a model can copy the
@@ -166,7 +199,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
166
199
  baseDocument;
167
200
  originalBuffer;
168
201
  state;
169
- headerFooterStoryStates = /* @__PURE__ */ new Map();
202
+ secondaryStoryStates = /* @__PURE__ */ new Map();
170
203
  createdComments = [];
171
204
  documentOperationUndoEntries = [];
172
205
  /**
@@ -211,11 +244,51 @@ var FolioDocxReviewer = class FolioDocxReviewer {
211
244
  snapshot() {
212
245
  return createFolioAIEditSnapshot(this.state.doc);
213
246
  }
247
+ /** Return parsed package metadata without exposing the mutable document model. */
248
+ getDocumentProperties() {
249
+ const properties = this.baseDocument.package.properties;
250
+ if (!properties) return null;
251
+ return Object.freeze({
252
+ ...properties,
253
+ ...properties.created !== void 0 ? { created: new Date(properties.created.getTime()) } : {},
254
+ ...properties.modified !== void 0 ? { modified: new Date(properties.modified.getTime()) } : {}
255
+ });
256
+ }
214
257
  /** Snapshot one editable story into stable, operation-ready blocks. */
215
258
  snapshotStory(story) {
216
259
  const state = this.getEditableStoryState(story);
217
260
  return state ? createFolioAIEditSnapshot(state.doc) : null;
218
261
  }
262
+ /** Read one story through an immutable reviewed-view projection. */
263
+ readReviewedStory(options = {}) {
264
+ const story = options.story ?? MAIN_STORY;
265
+ const view = options.view ?? "final";
266
+ if (!isFolioReviewedView(view)) throw new UnsupportedFolioReviewedViewError({
267
+ message: "Unsupported reviewed document view.",
268
+ receivedView: view
269
+ });
270
+ const sourceState = this.getEditableStoryState(story);
271
+ if (!sourceState) return null;
272
+ const state = resolveReviewedState(sourceState, view);
273
+ return {
274
+ story,
275
+ view,
276
+ snapshot: createFolioAIEditSnapshot(state.doc),
277
+ text: formatStoryStateForLLM(state, view === "current-markup"),
278
+ changes: getTrackedChangesFromDoc(state.doc)
279
+ };
280
+ }
281
+ /** Resolve one editable story to its original or final state. */
282
+ resolveReviewedStory({ story = MAIN_STORY, view }) {
283
+ if (!isFolioResolvedReviewedView(view)) throw new UnsupportedFolioReviewedViewError({
284
+ message: "Only original and final views can replace editable story state.",
285
+ receivedView: view
286
+ });
287
+ const sourceState = this.getEditableStoryState(story);
288
+ if (!sourceState) return false;
289
+ this.setEditableStoryState(story, resolveReviewedState(sourceState, view));
290
+ return true;
291
+ }
219
292
  /**
220
293
  * Apply operations against the current state. Reuses the live-editor applier
221
294
  * verbatim via a headless `{ state, dispatch }` seam; the resulting state
@@ -251,7 +324,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
251
324
  createUndoEntry: true
252
325
  });
253
326
  }
254
- /** Apply a versioned operation batch to the main story, a header, or a footer. */
327
+ /** Apply a versioned operation batch to one editable document story. */
255
328
  applyDocumentOperationsToStory({ story, batch, snapshot }) {
256
329
  return this.applyDocumentOperationsInternal({
257
330
  story,
@@ -348,23 +421,13 @@ var FolioDocxReviewer = class FolioDocxReviewer {
348
421
  * (clean) output flattens tracked changes and is unchanged.
349
422
  */
350
423
  getContentAsText(options = {}) {
351
- if (!options.annotated) return this.getContent().map(formatBlockForLLM).join("\n");
352
- const snapshot = this.snapshot();
353
- const startById = /* @__PURE__ */ new Map();
354
- for (const anchor of Object.values(snapshot.anchors)) startById.set(anchor.id, anchor.from);
355
- return snapshot.blocks.map((block) => {
356
- const from = startById.get(block.id);
357
- const node = from === void 0 ? null : this.state.doc.nodeAt(from);
358
- const text = node ? buildAnnotatedBlockText(node) : block.text;
359
- return formatBlockLine(block, text);
360
- }).join("\n");
424
+ return formatStoryStateForLLM(this.state, options.annotated === true);
361
425
  }
362
426
  /**
363
427
  * Header / footer and footnote / endnote text as labeled, LLM-ready lines,
364
428
  * one per non-empty part: `[header default] …`, `[footer default] …`,
365
- * `[footnote #N] …`, `[endnote #N] …`. Header and footer lines reflect
366
- * in-memory edits; note bodies reflect the parsed source. Empty parts and
367
- * separator notes are omitted.
429
+ * `[footnote #N] …`, `[endnote #N] …`. Lines reflect in-memory edits. Empty
430
+ * parts and separator notes are omitted.
368
431
  */
369
432
  getNotesAsText() {
370
433
  const pkg = this.baseDocument.package;
@@ -384,12 +447,20 @@ var FolioDocxReviewer = class FolioDocxReviewer {
384
447
  pushHeaderFooter(pkg.footers, "footer");
385
448
  for (const footnote of pkg.footnotes ?? []) {
386
449
  if (isSeparatorFootnote(footnote)) continue;
387
- const text = normalizeFolioAIBlockText(getFootnoteText(footnote));
450
+ const handle = {
451
+ type: "footnote",
452
+ noteId: footnote.id
453
+ };
454
+ const text = this.getNoteStoryText(handle, footnote);
388
455
  if (text.length > 0) lines.push(`[footnote #${footnote.id}] ${text}`);
389
456
  }
390
457
  for (const endnote of pkg.endnotes ?? []) {
391
458
  if (isSeparatorEndnote(endnote)) continue;
392
- const text = normalizeFolioAIBlockText(getEndnoteText(endnote));
459
+ const handle = {
460
+ type: "endnote",
461
+ noteId: endnote.id
462
+ };
463
+ const text = this.getNoteStoryText(handle, endnote);
393
464
  if (text.length > 0) lines.push(`[endnote #${endnote.id}] ${text}`);
394
465
  }
395
466
  return lines.join("\n");
@@ -421,20 +492,26 @@ var FolioDocxReviewer = class FolioDocxReviewer {
421
492
  text: this.getHeaderFooterStoryText(handle, footer)
422
493
  });
423
494
  }
424
- for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) stories.push({
425
- handle: {
495
+ for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) {
496
+ const handle = {
426
497
  type: "footnote",
427
498
  noteId: footnote.id
428
- },
429
- text: normalizeFolioAIBlockText(getFootnoteText(footnote))
430
- });
431
- for (const endnote of pkg.endnotes ?? []) if (!isSeparatorEndnote(endnote)) stories.push({
432
- handle: {
499
+ };
500
+ stories.push({
501
+ handle,
502
+ text: this.getNoteStoryText(handle, footnote)
503
+ });
504
+ }
505
+ for (const endnote of pkg.endnotes ?? []) if (!isSeparatorEndnote(endnote)) {
506
+ const handle = {
433
507
  type: "endnote",
434
508
  noteId: endnote.id
435
- },
436
- text: normalizeFolioAIBlockText(getEndnoteText(endnote))
437
- });
509
+ };
510
+ stories.push({
511
+ handle,
512
+ text: this.getNoteStoryText(handle, endnote)
513
+ });
514
+ }
438
515
  return stories;
439
516
  }
440
517
  /** Read one discovered story; returns null when its handle is no longer present. */
@@ -578,7 +655,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
578
655
  /** The current document model with edits merged back in. */
579
656
  toDocument() {
580
657
  const document = updateDocumentContent(this.baseDocument, this.state.doc);
581
- this.mergeEditedHeaderFooterStories(document);
658
+ this.mergeEditedSecondaryStories(document);
582
659
  if (this.createdComments.length > 0 || this.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides([...document.package.document.comments ?? [], ...this.createdComments]);
583
660
  return document;
584
661
  }
@@ -604,11 +681,20 @@ var FolioDocxReviewer = class FolioDocxReviewer {
604
681
  * is the graceful handling — no separate error surface is needed here.
605
682
  */
606
683
  async trySelectiveSave(document) {
684
+ const changedParaIds = new Set(getChangedParagraphIds(this.state));
685
+ let structuralChange = hasStructuralChanges(this.state);
686
+ let untrackedChanges = hasUntrackedChanges(this.state);
687
+ for (const entry of this.secondaryStoryStates.values()) {
688
+ if (entry.handle.type !== "footnote" && entry.handle.type !== "endnote") continue;
689
+ for (const paraId of getChangedParagraphIds(entry.state)) changedParaIds.add(paraId);
690
+ structuralChange ||= hasStructuralChanges(entry.state);
691
+ untrackedChanges ||= hasUntrackedChanges(entry.state);
692
+ }
607
693
  try {
608
694
  return await attemptSelectiveSave(document, this.originalBuffer, {
609
- changedParaIds: getChangedParagraphIds(this.state),
610
- structuralChange: hasStructuralChanges(this.state),
611
- hasUntrackedChanges: hasUntrackedChanges(this.state)
695
+ changedParaIds,
696
+ structuralChange,
697
+ hasUntrackedChanges: untrackedChanges
612
698
  });
613
699
  } catch {
614
700
  return null;
@@ -616,20 +702,21 @@ var FolioDocxReviewer = class FolioDocxReviewer {
616
702
  }
617
703
  getEditableStoryState(story) {
618
704
  if (story.type === "main") return this.state;
619
- const key = headerFooterStoryKey(story);
620
- const existing = this.headerFooterStoryStates.get(key);
705
+ const key = secondaryStoryKey(story);
706
+ const existing = this.secondaryStoryStates.get(key);
621
707
  if (existing) return existing.state;
622
- const source = this.getHeaderFooterStory(story);
708
+ const source = story.type === "header" || story.type === "footer" ? this.getHeaderFooterStory(story) : this.getNoteStory(story);
623
709
  if (!source) return null;
710
+ const conversionOptions = {
711
+ ...this.baseDocument.package.styles !== void 0 && { styles: this.baseDocument.package.styles },
712
+ ...this.baseDocument.package.theme !== void 0 && { theme: this.baseDocument.package.theme }
713
+ };
624
714
  const state = ensureDeterministicParaIdsInState(EditorState.create({
625
715
  schema,
626
- doc: headerFooterToProseDoc(source.content, {
627
- ...this.baseDocument.package.styles !== void 0 && { styles: this.baseDocument.package.styles },
628
- ...this.baseDocument.package.theme !== void 0 && { theme: this.baseDocument.package.theme }
629
- }),
716
+ doc: story.type === "header" || story.type === "footer" ? headerFooterToProseDoc(source.content, conversionOptions) : footnoteToProseDoc(source.content, conversionOptions),
630
717
  plugins: singletonManager.getPlugins()
631
718
  }));
632
- this.headerFooterStoryStates.set(key, {
719
+ this.secondaryStoryStates.set(key, {
633
720
  handle: story,
634
721
  initialState: state,
635
722
  state
@@ -649,7 +736,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
649
736
  this.state = state;
650
737
  return;
651
738
  }
652
- const entry = this.headerFooterStoryStates.get(headerFooterStoryKey(story));
739
+ const entry = this.secondaryStoryStates.get(secondaryStoryKey(story));
653
740
  if (!entry) throw new FolioDocumentStoryNotFoundError({
654
741
  message: `Document story ${JSON.stringify(story)} was not found.`,
655
742
  story
@@ -659,31 +746,70 @@ var FolioDocxReviewer = class FolioDocxReviewer {
659
746
  getHeaderFooterStory(story) {
660
747
  return (story.type === "header" ? this.baseDocument.package.headers : this.baseDocument.package.footers)?.get(story.relationshipId);
661
748
  }
749
+ getNoteStory(story) {
750
+ if (story.type === "footnote") return this.baseDocument.package.footnotes?.find((note) => note.id === story.noteId && !isSeparatorFootnote(note));
751
+ return this.baseDocument.package.endnotes?.find((note) => note.id === story.noteId && !isSeparatorEndnote(note));
752
+ }
662
753
  getHeaderFooterStoryText(story, source) {
663
- const state = this.headerFooterStoryStates.get(headerFooterStoryKey(story))?.state;
754
+ const state = this.secondaryStoryStates.get(headerFooterStoryKey(story))?.state;
664
755
  return normalizeFolioAIBlockText(state?.doc.textContent ?? getHeaderFooterText(source));
665
756
  }
666
- mergeEditedHeaderFooterStories(document) {
757
+ getNoteStoryText(story, source) {
758
+ const state = this.secondaryStoryStates.get(noteStoryKey(story))?.state;
759
+ const sourceText = source.type === "footnote" ? getFootnoteText(source) : getEndnoteText(source);
760
+ return normalizeFolioAIBlockText(state?.doc.textContent ?? sourceText);
761
+ }
762
+ mergeEditedSecondaryStories(document) {
667
763
  let headers;
668
764
  let footers;
669
- for (const entry of this.headerFooterStoryStates.values()) {
765
+ let footnotes;
766
+ let endnotes;
767
+ for (const entry of this.secondaryStoryStates.values()) {
670
768
  if (entry.state === entry.initialState) continue;
671
- const source = this.getHeaderFooterStory(entry.handle);
769
+ if (entry.handle.type === "header" || entry.handle.type === "footer") {
770
+ const source = this.getHeaderFooterStory(entry.handle);
771
+ if (!source) continue;
772
+ const edited = {
773
+ ...source,
774
+ content: proseDocToBlocks(entry.state.doc)
775
+ };
776
+ if (entry.handle.type === "header") {
777
+ headers ??= new Map(document.package.headers);
778
+ headers.set(entry.handle.relationshipId, edited);
779
+ continue;
780
+ }
781
+ footers ??= new Map(document.package.footers);
782
+ footers.set(entry.handle.relationshipId, edited);
783
+ continue;
784
+ }
785
+ if (entry.handle.type === "footnote") {
786
+ const { noteId } = entry.handle;
787
+ const source = this.baseDocument.package.footnotes?.find((note) => note.id === noteId && !isSeparatorFootnote(note));
788
+ if (!source) continue;
789
+ const edited = {
790
+ ...source,
791
+ content: proseDocToBlocks(entry.state.doc)
792
+ };
793
+ footnotes ??= [...document.package.footnotes ?? []];
794
+ const index = footnotes.findIndex((note) => note.id === noteId);
795
+ if (index !== -1) footnotes[index] = edited;
796
+ continue;
797
+ }
798
+ const { noteId } = entry.handle;
799
+ const source = this.baseDocument.package.endnotes?.find((note) => note.id === noteId && !isSeparatorEndnote(note));
672
800
  if (!source) continue;
673
801
  const edited = {
674
802
  ...source,
675
803
  content: proseDocToBlocks(entry.state.doc)
676
804
  };
677
- if (entry.handle.type === "header") {
678
- headers ??= new Map(document.package.headers);
679
- headers.set(entry.handle.relationshipId, edited);
680
- continue;
681
- }
682
- footers ??= new Map(document.package.footers);
683
- footers.set(entry.handle.relationshipId, edited);
805
+ endnotes ??= [...document.package.endnotes ?? []];
806
+ const index = endnotes.findIndex((note) => note.id === noteId);
807
+ if (index !== -1) endnotes[index] = edited;
684
808
  }
685
809
  if (headers) document.package.headers = headers;
686
810
  if (footers) document.package.footers = footers;
811
+ if (footnotes) document.package.footnotes = footnotes;
812
+ if (endnotes) document.package.endnotes = endnotes;
687
813
  }
688
814
  /**
689
815
  * Count every tracked change an accept-all / reject-all sweep resolves: the
@@ -767,4 +893,4 @@ const applyFolioAIEditsToBuffer = async (buffer, operations, options = {}) => {
767
893
  };
768
894
  };
769
895
  //#endregion
770
- export { FolioDocumentStoryNotFoundError, FolioDocxReviewer, applyFolioAIEditsToBuffer };
896
+ export { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioDocxReviewer, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView };
@@ -4,9 +4,9 @@ import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolv
4
4
  import { buildAnnotatedBlockText } from "./clean-text.js";
5
5
  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, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
6
6
  import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
7
- import { FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle } from "./headless.js";
7
+ import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
8
8
  import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./snapshot.js";
9
9
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
10
10
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
11
11
  import { WordDiffSegment, diffWordSegments } from "./word-diff.js";
12
- export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, 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 FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReviewChange, type FolioReviewChangeKind, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
12
+ export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, 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, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, 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 FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
@@ -7,5 +7,5 @@ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT
7
7
  import { clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange } from "./blockRange.js";
8
8
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
9
9
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
10
- import { FolioDocumentStoryNotFoundError } from "./headless.js";
11
- 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, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
10
+ import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
11
+ 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, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange };
@@ -78,7 +78,8 @@ function runLayoutPipeline(deps, state, options = {}) {
78
78
  if (documentSettings?.autoHyphenation === true) flowOpts.automaticHyphenation = {
79
79
  enabled: true,
80
80
  ...documentSettings.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: documentSettings.doNotHyphenateCaps } : {},
81
- ...documentSettings.consecutiveHyphenLimit !== void 0 ? { consecutiveLineLimit: documentSettings.consecutiveHyphenLimit } : {}
81
+ ...documentSettings.consecutiveHyphenLimit !== void 0 ? { consecutiveLineLimit: documentSettings.consecutiveHyphenLimit } : {},
82
+ ...documentSettings.hyphenationZoneTwips !== void 0 ? { hyphenationZoneTwips: documentSettings.hyphenationZoneTwips } : {}
82
83
  };
83
84
  let newBlocks = toFlowBlocks(state.doc, flowOpts);
84
85
  const previewState = templatePreviewValuesKey.getState(state);
@@ -5,7 +5,7 @@ import { FolioAIEditView } from "./ai-edits/apply.js";
5
5
  declare const FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION: 1;
6
6
  declare const FOLIO_DOCUMENT_OPERATION_TYPES: readonly ["replaceInBlock", "replaceRange", "commentOnRange", "formatRange", "insertAfterBlock", "insertBeforeBlock", "replaceBlock", "deleteBlock", "commentOnBlock", "insertSignatureTable"];
7
7
  declare const FOLIO_DOCUMENT_OPERATION_MODES: readonly ["direct", "tracked-changes"];
8
- declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main"];
8
+ declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main", "header", "footer", "footnote", "endnote"];
9
9
  declare const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS: readonly ["blockTextHash"];
10
10
  declare const FOLIO_DOCUMENT_OPERATION_BATCH_MODES: readonly ["best-effort", "atomic"];
11
11
  type FolioDocumentOperation = FolioAIEditOperation;
@@ -68,8 +68,17 @@ type FolioDocumentOperationIssue = {
68
68
  recovery: FolioDocumentOperationRecovery;
69
69
  };
70
70
  type FolioDocumentOperationStory = "main" | {
71
- type: "header" | "footer";
71
+ type: "header";
72
72
  relationshipId: string;
73
+ } | {
74
+ type: "footer";
75
+ relationshipId: string;
76
+ } | {
77
+ type: "footnote";
78
+ noteId: number;
79
+ } | {
80
+ type: "endnote";
81
+ noteId: number;
73
82
  };
74
83
  /** One typed target affected by a successfully applied document operation. */
75
84
  type FolioDocumentOperationAffectedTarget = {
@@ -15,7 +15,13 @@ const FOLIO_DOCUMENT_OPERATION_TYPES = Object.freeze([
15
15
  "insertSignatureTable"
16
16
  ]);
17
17
  const FOLIO_DOCUMENT_OPERATION_MODES = Object.freeze(["direct", "tracked-changes"]);
18
- const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze(["main"]);
18
+ const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
19
+ "main",
20
+ "header",
21
+ "footer",
22
+ "footnote",
23
+ "endnote"
24
+ ]);
19
25
  const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
20
26
  const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
21
27
  const DIRECT_AND_TRACKED_MODES = FOLIO_DOCUMENT_OPERATION_MODES;
@@ -0,0 +1,10 @@
1
+ import { FolioDocumentStoryHandle } from "./ai-edits/headless.js";
2
+
3
+ //#region src/document-stories.d.ts
4
+ type FolioDocumentStoryPair = {
5
+ baseStory: FolioDocumentStoryHandle | null;
6
+ revisedStory: FolioDocumentStoryHandle | null;
7
+ };
8
+ declare const pairFolioDocumentStories: (baseStories: readonly FolioDocumentStoryHandle[], revisedStories: readonly FolioDocumentStoryHandle[]) => FolioDocumentStoryPair[];
9
+ //#endregion
10
+ export { FolioDocumentStoryPair, pairFolioDocumentStories };
@@ -0,0 +1,27 @@
1
+ //#region src/document-stories.ts
2
+ const documentStoryKey = (story) => {
3
+ if (story.type === "main") return story.type;
4
+ if (story.type === "header" || story.type === "footer") return `${story.type}:${story.relationshipId}`;
5
+ return `${story.type}:${String(story.noteId)}`;
6
+ };
7
+ const pairFolioDocumentStories = (baseStories, revisedStories) => {
8
+ const revisedByKey = new Map(revisedStories.map((story) => [documentStoryKey(story), story]));
9
+ const pairedKeys = /* @__PURE__ */ new Set();
10
+ const pairs = [];
11
+ for (const baseStory of baseStories) {
12
+ const key = documentStoryKey(baseStory);
13
+ const revisedStory = revisedByKey.get(key) ?? null;
14
+ pairs.push({
15
+ baseStory,
16
+ revisedStory
17
+ });
18
+ if (revisedStory) pairedKeys.add(key);
19
+ }
20
+ for (const revisedStory of revisedStories) if (!pairedKeys.has(documentStoryKey(revisedStory))) pairs.push({
21
+ baseStory: null,
22
+ revisedStory
23
+ });
24
+ return pairs;
25
+ };
26
+ //#endregion
27
+ export { pairFolioDocumentStories };
@@ -0,0 +1,8 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+
3
+ //#region src/docx/corePropertiesParser.d.ts
4
+ type DocumentProperties = NonNullable<document_d_exports.DocxPackage["properties"]>;
5
+ /** Parse recognized package metadata from `docProps/core.xml`. */
6
+ declare const parseCoreProperties: (xml: string | null) => DocumentProperties | undefined;
7
+ //#endregion
8
+ export { parseCoreProperties };