@stll/folio-core 0.37.5 → 0.38.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 (121) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/document-operations.d.ts +5 -3
  42. package/dist/document-operations.js +112 -5
  43. package/dist/docx/blockContentParser.js +14 -2
  44. package/dist/docx/drawingIdNormalization.js +43 -2
  45. package/dist/docx/drawingRelationships.d.ts +10 -0
  46. package/dist/docx/drawingRelationships.js +28 -0
  47. package/dist/docx/fieldParser.js +1 -1
  48. package/dist/docx/hyperlinkParser.js +3 -5
  49. package/dist/docx/imageRawXml.d.ts +8 -0
  50. package/dist/docx/imageRawXml.js +13 -0
  51. package/dist/docx/newImage.d.ts +2 -2
  52. package/dist/docx/newImage.js +3 -3
  53. package/dist/docx/numberingParser.js +30 -27
  54. package/dist/docx/paragraphParser.js +11 -4
  55. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  56. package/dist/docx/removeHeaderFooterParts.js +87 -0
  57. package/dist/docx/rezip.d.ts +3 -1
  58. package/dist/docx/rezip.js +143 -41
  59. package/dist/docx/runParser.js +16 -5
  60. package/dist/docx/sectionParser.js +3 -0
  61. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  62. package/dist/docx/sectionReferenceHistory.js +59 -0
  63. package/dist/docx/selectiveSave.js +11 -31
  64. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  65. package/dist/docx/selectiveXmlPatch.js +62 -2
  66. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  67. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  68. package/dist/docx/serializer/partNamespaces.js +4 -0
  69. package/dist/docx/serializer/runSerializer.js +2 -1
  70. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  71. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  72. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  73. package/dist/docx/tableParser.js +21 -11
  74. package/dist/docx/xmlParser.d.ts +10 -1
  75. package/dist/docx/xmlParser.js +23 -1
  76. package/dist/index.d.ts +4 -4
  77. package/dist/index.js +3 -3
  78. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  79. package/dist/internal/compare/inline-presentation.js +222 -0
  80. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  81. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  82. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  83. package/dist/internal/sectionReferenceResolution.js +72 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  85. package/dist/markdown/renderRuns.js +1 -0
  86. package/dist/markdown/renderTable.js +1 -0
  87. package/dist/prosemirror/attrs/index.js +20 -1
  88. package/dist/prosemirror/commands/comments.d.ts +11 -1
  89. package/dist/prosemirror/commands/comments.js +68 -6
  90. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  91. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  92. package/dist/prosemirror/conversion/fromProseDoc.js +16 -8
  93. package/dist/prosemirror/conversion/toProseDoc.js +16 -57
  94. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  95. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  96. package/dist/prosemirror/imageCommit.js +7 -9
  97. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  98. package/dist/prosemirror/paragraphIndentation.js +72 -0
  99. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  100. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  101. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  102. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  103. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  104. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  105. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  106. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  107. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  108. package/dist/prosemirror/runStyleFormatting.js +76 -2
  109. package/dist/prosemirror/schema/marks.d.ts +3 -1
  110. package/dist/prosemirror/schema/marks.js +2 -0
  111. package/dist/prosemirror/schema/nodes.d.ts +3 -0
  112. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  113. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  114. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  115. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  116. package/dist/redline.js +15 -2
  117. package/dist/server.d.ts +2 -2
  118. package/dist/server.js +2 -2
  119. package/dist/version-comparison.d.ts +2 -0
  120. package/dist/version-comparison.js +7 -2
  121. package/package.json +2 -2
@@ -1,29 +1,44 @@
1
+ import { matchInlineAtoms } from "../compare/inline-atoms.js";
2
+ import { matchInlineProvenance } from "../compare/inline-provenance.js";
3
+ import { stageSectionBoundaryProperties } from "../compare/section-boundary-properties.js";
4
+ import { importReferencedStyleDefinitions } from "../compare/style-resources.js";
1
5
  import { applyFolioDocumentOperations } from "../document-operations.js";
6
+ import { rebindDrawingImageRelationship } from "../docx/drawingRelationships.js";
2
7
  import { getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote } from "../docx/footnoteParser.js";
3
8
  import { getHeaderFooterText } from "../docx/headerFooterParser.js";
9
+ import { getCachedNumberingMap } from "../docx/numberingParser.js";
4
10
  import { recreateProseNodeWithParagraphPropertySource, transferProseParagraphPropertySource } from "../docx/paragraphPropertySource.js";
5
11
  import { parseDocx } from "../docx/parser.js";
6
12
  import { createReply } from "../docx/replyToComment.js";
7
13
  import { repackDocx } from "../docx/rezip.js";
14
+ import { sectionReferenceHistory } from "../docx/sectionReferenceHistory.js";
8
15
  import { attemptSelectiveSave } from "../docx/selectiveSave.js";
9
16
  import { pluginsForHeadlessRevisionResolution } from "../internal/headlessRevisionResolutionGuard.js";
10
17
  import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
18
+ import { captureSectionReferenceInventory, resolvedSectionReferenceLosses, withSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
19
+ import { expectCharacterStyleMarkAttrs } from "../prosemirror/attrs/index.js";
11
20
  import { acceptAIEditRevision, rejectAIEditRevision, resolveAllChangesInHeadlessState } from "../prosemirror/commands/comments.js";
21
+ import { sectionRejectProperties } from "../prosemirror/commands/propertyChangeScope.js";
12
22
  import { proseDocToBlocks, updateDocumentContent } from "../prosemirror/conversion/fromProseDoc.js";
13
23
  import { footnoteToProseDoc, headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
14
24
  import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/AutoBidiDetectionExtension.js";
15
25
  import { getChangedParagraphIds, getTrackedSectionEndpointRemoval, hasStructuralChanges, hasUntrackedChanges } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
16
- import { createDocumentNumberingPlugin } from "../prosemirror/plugins/documentNumbering.js";
17
- import { createDocumentStylesPlugin, getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
26
+ import "../prosemirror/listMarker.js";
27
+ import { createDocumentNumberingPlugin, withDocumentNumbering } from "../prosemirror/plugins/documentNumbering.js";
28
+ import { createDocumentStylesPlugin, getDocumentStyleResolver, withDocumentStyles } from "../prosemirror/plugins/documentStyles.js";
18
29
  import { schema, singletonManager } from "../prosemirror/schema/index.js";
30
+ import { canonicalJson } from "../utils/canonicalJson.js";
19
31
  import { deterministicHexId } from "../utils/hexId.js";
20
32
  import { buildAnnotatedBlockText } from "./clean-text.js";
21
33
  import { getCommentAnchorsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot } from "./read.js";
22
- import { createFolioAIEditSnapshotWithStyleResolver, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText, sourceDocumentOf } from "./snapshot.js";
34
+ import { createFolioAIEditSnapshotWithStyleResolver, detachFolioAIEditSnapshotExternalHyperlinks, folioStoryTables, isFolioAIContentBlock, normalizeFolioAIBlockText, sourceDocumentOf } from "./snapshot.js";
23
35
  import { matchTableGeometry } from "./table-geometry.js";
36
+ import { tableTemplateCanCrossPackageLosslessly } from "./table-template.js";
24
37
  import { TaggedError, panic } from "better-result";
25
38
  import { Fragment } from "prosemirror-model";
26
39
  import { EditorState } from "prosemirror-state";
40
+ import { REVIEW_CARRIERS } from "@stll/docx-core/model";
41
+ import JSZip from "jszip";
27
42
  //#region src/ai-edits/headless.ts
28
43
  /**
29
44
  * Headless `.docx` review path: buffer -> apply AI edits -> buffer, with
@@ -160,6 +175,47 @@ const resolvedStoryBlockProjection = (block) => {
160
175
  delete persisted.idStability;
161
176
  return persisted;
162
177
  };
178
+ const numberingLevelsOf = (numbering) => {
179
+ if (!numbering) return [];
180
+ const levelsByAbstractId = new Map(numbering.abstractNums.map((abstractNum) => [abstractNum.abstractNumId, abstractNum.levels]));
181
+ const levels = [];
182
+ for (const instance of numbering.nums) {
183
+ const overrides = new Map((instance.levelOverrides ?? []).map((override) => [override.ilvl, override]));
184
+ for (const level of levelsByAbstractId.get(instance.abstractNumId) ?? []) {
185
+ const override = overrides.get(level.ilvl);
186
+ const resolved = override?.lvl ?? level;
187
+ levels.push({
188
+ numId: instance.numId,
189
+ level: resolved.ilvl,
190
+ format: resolved.numFmt,
191
+ levelText: resolved.lvlText,
192
+ ...(override?.startOverride ?? resolved.start) !== void 0 ? { start: override?.startOverride ?? resolved.start } : {}
193
+ });
194
+ }
195
+ }
196
+ return levels;
197
+ };
198
+ const referencedNumberingLevelsByNumId = ({ references }) => {
199
+ const levelsByNumId = /* @__PURE__ */ new Map();
200
+ for (const { numId, level } of references) {
201
+ const levels = levelsByNumId.get(numId) ?? /* @__PURE__ */ new Set();
202
+ levelsByNumId.set(numId, levels);
203
+ levels.add(level);
204
+ for (let ancestor = 0; ancestor <= Math.min(level, 8); ancestor += 1) levels.add(ancestor);
205
+ }
206
+ return new Map([...levelsByNumId].map(([numId, levels]) => [numId, [...levels].toSorted((left, right) => left - right)]));
207
+ };
208
+ const sameReferencedNumberingLevels = ({ current, target, numId, levelsByNumId }) => {
209
+ if (!current) return false;
210
+ const currentNumbering = getCachedNumberingMap(current);
211
+ const targetNumbering = getCachedNumberingMap(target);
212
+ for (const level of levelsByNumId.get(numId) ?? []) {
213
+ const currentLevel = currentNumbering.getLevel(numId, level);
214
+ const targetLevel = targetNumbering.getLevel(numId, level);
215
+ if (!currentLevel || !targetLevel || canonicalJson(currentLevel) !== canonicalJson(targetLevel)) return false;
216
+ }
217
+ return true;
218
+ };
163
219
  const comparisonAccessByReviewer = /* @__PURE__ */ new WeakMap();
164
220
  const MAIN_STORY = Object.freeze({ type: "main" });
165
221
  const headerFooterStoryKey = ({ type, relationshipId }) => `${type}:${relationshipId}`;
@@ -242,9 +298,16 @@ var FolioDocxReviewer = class FolioDocxReviewer {
242
298
  /** Default author for tracked changes and comments. */
243
299
  author;
244
300
  baseDocument;
301
+ finalSectionPropertiesOverride;
245
302
  originalBuffer;
246
303
  state;
247
304
  secondaryStoryStates = /* @__PURE__ */ new Map();
305
+ removedHeaderFooterStories = /* @__PURE__ */ new Map();
306
+ sectionReferenceRemovals = [];
307
+ importedStyles;
308
+ importedMedia = /* @__PURE__ */ new Map();
309
+ importedHeaders = /* @__PURE__ */ new Map();
310
+ importedFooters = /* @__PURE__ */ new Map();
248
311
  resolvedStoryExpectations = /* @__PURE__ */ new Map();
249
312
  createdComments = [];
250
313
  usedCommentIds;
@@ -265,10 +328,231 @@ var FolioDocxReviewer = class FolioDocxReviewer {
265
328
  this.author = args.author;
266
329
  this.usedCommentIds = new Set((args.baseDocument.package.document.comments ?? []).map(({ id }) => id));
267
330
  comparisonAccessByReviewer.set(this, Object.freeze({
331
+ stageTerminalTableReviewCarrier: (target) => this.stageTerminalTableReviewCarrier(target),
332
+ stageTargetStyles: (source, snapshots, importedHeaderFooterSnapshots) => this.stageTargetStyles(source, snapshots, importedHeaderFooterSnapshots),
333
+ createComparisonHeaderFooter: (source, story) => this.createComparisonHeaderFooter(source, story),
334
+ finalSectionProperties: () => this.currentFinalSectionProperties(),
335
+ stageFinalSectionProperties: ({ target, previous, revision }) => this.stageFinalSectionProperties({
336
+ target,
337
+ previous,
338
+ revision
339
+ }),
340
+ stageMappedSectionBoundaries: (options) => this.stageMappedSectionBoundaries(options),
341
+ matchInlineAtoms: ({ story, ...options }) => {
342
+ const state = this.getEditableStoryState(story);
343
+ if (!state) return panic("A compared story lost its editable state", { story });
344
+ const result = matchInlineAtoms({
345
+ ...options,
346
+ state,
347
+ author: this.author
348
+ });
349
+ switch (result.status) {
350
+ case "matched":
351
+ if (result.transaction.docChanged) this.setEditableStoryState(story, state.apply(result.transaction));
352
+ return {
353
+ status: "matched",
354
+ nextRevisionId: result.nextRevisionId,
355
+ changedTargetBlockIds: result.changedTargetBlockIds,
356
+ rangeCount: result.rangeCount,
357
+ documentChanged: result.transaction.docChanged
358
+ };
359
+ case "unalignable":
360
+ case "budget-exceeded": return result;
361
+ default: return panic("Unhandled inline atom result", { result });
362
+ }
363
+ },
364
+ matchInlineProvenance: ({ story, ...options }) => {
365
+ const state = this.getEditableStoryState(story);
366
+ if (!state) return panic("A compared story lost its editable state", { story });
367
+ const result = matchInlineProvenance({
368
+ ...options,
369
+ state,
370
+ author: this.author
371
+ });
372
+ switch (result.status) {
373
+ case "matched":
374
+ if (result.transaction.docChanged) this.setEditableStoryState(story, state.apply(result.transaction));
375
+ return {
376
+ status: "matched",
377
+ nextRevisionId: result.nextRevisionId,
378
+ changedTargetBlockIds: result.changedTargetBlockIds,
379
+ rangeCount: result.rangeCount,
380
+ documentChanged: result.transaction.docChanged
381
+ };
382
+ case "unalignable":
383
+ case "budget-exceeded": return result;
384
+ default: return panic("Unhandled inline provenance result", { result });
385
+ }
386
+ },
268
387
  projectStories: (mode) => this.projectComparisonStoriesInternal(mode),
269
- snapshotReviewedStory: (options) => this.snapshotReviewedStoryInternal(options)
388
+ snapshotReviewedStory: (options) => this.snapshotReviewedStoryInternal(options),
389
+ numberingDefinitions: () => this.baseDocument.package.numbering,
390
+ planTargetNumberingReferences: (target, references) => this.planTargetNumberingReferences(target, references),
391
+ stageTargetNumbering: (target, references, remappedNumIds) => this.stageTargetNumbering(target, references, remappedNumIds)
270
392
  }));
271
393
  }
394
+ stageTargetStyles(source, snapshots, importedHeaderFooterSnapshots) {
395
+ const destination = this.baseDocument.package;
396
+ const sourcePackage = source.baseDocument.package;
397
+ const destinationStyles = this.importedStyles ?? destination.styles;
398
+ const existing = new Set(destinationStyles?.styles.map(({ styleId }) => styleId));
399
+ const sourceStyleIds = new Set(sourcePackage.styles?.styles.map(({ styleId }) => styleId));
400
+ const collect = (document) => {
401
+ const references = /* @__PURE__ */ new Set();
402
+ document.descendants((node) => {
403
+ const styleId = node.attrs["styleId"];
404
+ if (typeof styleId === "string" && styleId.length > 0) references.add(styleId);
405
+ for (const mark of node.marks) if (mark.type.name === "characterStyle") references.add(expectCharacterStyleMarkAttrs(mark).styleId);
406
+ });
407
+ return references;
408
+ };
409
+ const hasUnstyledParagraph = (document) => {
410
+ let found = false;
411
+ document.descendants((node) => {
412
+ if (node.isTextblock && typeof node.attrs["styleId"] !== "string") {
413
+ found = true;
414
+ return false;
415
+ }
416
+ return true;
417
+ });
418
+ return found;
419
+ };
420
+ const resourceSnapshots = canonicalJson(sourcePackage.styles?.docDefaults) === canonicalJson(destinationStyles?.docDefaults) && canonicalJson(sourcePackage.theme) === canonicalJson(destination.theme) ? snapshots : importedHeaderFooterSnapshots;
421
+ const referencedStyleIds = /* @__PURE__ */ new Set();
422
+ let materializeDefaultParagraphStyle = false;
423
+ for (const snapshot of resourceSnapshots) {
424
+ if (hasUnstyledParagraph(sourceDocumentOf(snapshot))) materializeDefaultParagraphStyle = true;
425
+ for (const styleId of collect(sourceDocumentOf(snapshot))) if (sourceStyleIds.has(styleId) && !existing.has(styleId)) referencedStyleIds.add(styleId);
426
+ }
427
+ const reservedStyleIds = /* @__PURE__ */ new Set();
428
+ if (referencedStyleIds.size > 0) for (const handle of this.listStoryHandlesInternal()) {
429
+ const state = this.getEditableStoryState(handle);
430
+ if (!state) continue;
431
+ for (const styleId of collect(state.doc)) if (referencedStyleIds.has(styleId)) reservedStyleIds.add(styleId);
432
+ }
433
+ const result = importReferencedStyleDefinitions({
434
+ sourceStyles: sourcePackage.styles,
435
+ destinationStyles,
436
+ sourceTheme: sourcePackage.theme,
437
+ destinationTheme: destination.theme,
438
+ referencedStyleIds: [...referencedStyleIds],
439
+ reservedStyleIds: [...reservedStyleIds],
440
+ materializeDefaultParagraphStyle
441
+ });
442
+ if (result.status === "imported") {
443
+ this.importedStyles = result.styles;
444
+ this.state = withDocumentStyles(this.state, result.styles);
445
+ for (const entry of this.secondaryStoryStates.values()) {
446
+ const wasUntouched = entry.state === entry.initialState;
447
+ const refreshed = withDocumentStyles(entry.state, result.styles);
448
+ entry.state = refreshed;
449
+ if (wasUntouched) entry.initialState = refreshed;
450
+ }
451
+ }
452
+ return result;
453
+ }
454
+ /**
455
+ * A raw final table has no paragraph mark after it for Word to merge into
456
+ * when the target ends in a paragraph. Folio-exact comparison adds this
457
+ * untracked receiver before planning; its private marker makes resolving in
458
+ * Folio restore the raw source view.
459
+ */
460
+ stageTerminalTableReviewCarrier(target) {
461
+ if (this.state.doc.lastChild?.type.name !== "table" || target.lastChild?.type.name !== "paragraph") return false;
462
+ const paragraphType = this.state.schema.nodes["paragraph"];
463
+ if (!paragraphType) return panic("The schema has no paragraph node for a terminal-table review carrier");
464
+ const carrier = paragraphType.create({ reviewCarrier: REVIEW_CARRIERS.TERMINAL_TABLE });
465
+ this.state = this.state.apply(this.state.tr.insert(this.state.doc.content.size, carrier));
466
+ return true;
467
+ }
468
+ stageTargetNumbering(target, references, remappedNumIds) {
469
+ if (references.length === 0) return "unchanged";
470
+ if (!target) return "conflict";
471
+ const current = this.baseDocument.package.numbering ?? {
472
+ abstractNums: [],
473
+ nums: []
474
+ };
475
+ const nums = new Map(current.nums.map((entry) => [entry.numId, entry]));
476
+ const abstracts = new Map(current.abstractNums.map((entry) => [entry.abstractNumId, entry]));
477
+ const targetNums = new Map(target.nums.map((entry) => [entry.numId, entry]));
478
+ const targetAbstracts = new Map(target.abstractNums.map((entry) => [entry.abstractNumId, entry]));
479
+ const levelsByNumId = referencedNumberingLevelsByNumId({ references });
480
+ const importedAbstractIds = /* @__PURE__ */ new Map();
481
+ let nextAbstractId = 0;
482
+ for (const id of abstracts.keys()) nextAbstractId = Math.max(nextAbstractId, id + 1);
483
+ for (const numId of levelsByNumId.keys()) {
484
+ const targetNum = targetNums.get(numId);
485
+ if (!targetNum) return "conflict";
486
+ const targetAbstract = targetAbstracts.get(targetNum.abstractNumId);
487
+ if (!targetAbstract) return "conflict";
488
+ const stagedNumId = remappedNumIds.get(numId) ?? numId;
489
+ if (nums.get(stagedNumId)) {
490
+ if (stagedNumId === numId && sameReferencedNumberingLevels({
491
+ current,
492
+ target,
493
+ numId,
494
+ levelsByNumId
495
+ })) continue;
496
+ return "conflict";
497
+ }
498
+ let abstractNumId = importedAbstractIds.get(targetAbstract.abstractNumId);
499
+ if (abstractNumId === void 0) {
500
+ abstractNumId = targetAbstract.abstractNumId;
501
+ const collision = abstracts.get(abstractNumId);
502
+ if (collision && canonicalJson(collision) !== canonicalJson(targetAbstract)) abstractNumId = nextAbstractId++;
503
+ nextAbstractId = Math.max(nextAbstractId, abstractNumId + 1);
504
+ importedAbstractIds.set(targetAbstract.abstractNumId, abstractNumId);
505
+ abstracts.set(abstractNumId, {
506
+ ...targetAbstract,
507
+ abstractNumId
508
+ });
509
+ }
510
+ nums.set(stagedNumId, {
511
+ ...targetNum,
512
+ numId: stagedNumId,
513
+ abstractNumId
514
+ });
515
+ }
516
+ if (nums.size === current.nums.length) return "unchanged";
517
+ const numbering = {
518
+ ...current,
519
+ abstractNums: [...abstracts.values()],
520
+ nums: [...nums.values()]
521
+ };
522
+ this.baseDocument.package.numbering = numbering;
523
+ this.state = withDocumentNumbering(this.state, numbering);
524
+ for (const entry of this.secondaryStoryStates.values()) entry.state = withDocumentNumbering(entry.state, numbering);
525
+ return "staged";
526
+ }
527
+ planTargetNumberingReferences(target, references) {
528
+ if (references.length === 0) return /* @__PURE__ */ new Map();
529
+ if (!target) return null;
530
+ const current = this.baseDocument.package.numbering ?? {
531
+ abstractNums: [],
532
+ nums: []
533
+ };
534
+ const nums = new Map(current.nums.map((entry) => [entry.numId, entry]));
535
+ const targetNums = new Map(target.nums.map((entry) => [entry.numId, entry]));
536
+ const targetAbstracts = new Map(target.abstractNums.map((entry) => [entry.abstractNumId, entry]));
537
+ const levelsByNumId = referencedNumberingLevelsByNumId({ references });
538
+ let nextNumId = 0;
539
+ for (const id of nums.keys()) nextNumId = Math.max(nextNumId, id + 1);
540
+ for (const id of targetNums.keys()) nextNumId = Math.max(nextNumId, id + 1);
541
+ const remapped = /* @__PURE__ */ new Map();
542
+ for (const numId of levelsByNumId.keys()) {
543
+ const targetNum = targetNums.get(numId);
544
+ const targetAbstract = targetNum ? targetAbstracts.get(targetNum.abstractNumId) : void 0;
545
+ if (!targetNum || !targetAbstract) return null;
546
+ if (!nums.get(numId)) continue;
547
+ if (!sameReferencedNumberingLevels({
548
+ current,
549
+ target,
550
+ numId,
551
+ levelsByNumId
552
+ })) remapped.set(numId, nextNumId++);
553
+ }
554
+ return remapped;
555
+ }
272
556
  /** Parse a `.docx` buffer into a reviewer. */
273
557
  static async fromBuffer(buffer, options = {}) {
274
558
  const baseDocument = await parseDocx(buffer, {
@@ -308,25 +592,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
308
592
  * packages can be compared entry by entry.
309
593
  */
310
594
  readNumberingDefinitions() {
311
- const numbering = this.baseDocument.package.numbering;
312
- if (!numbering) return [];
313
- const levelsByAbstractId = new Map(numbering.abstractNums.map((abstractNum) => [abstractNum.abstractNumId, abstractNum.levels]));
314
- const levels = [];
315
- for (const instance of numbering.nums) {
316
- const overrides = new Map((instance.levelOverrides ?? []).map((override) => [override.ilvl, override]));
317
- for (const level of levelsByAbstractId.get(instance.abstractNumId) ?? []) {
318
- const override = overrides.get(level.ilvl);
319
- const resolved = override?.lvl ?? level;
320
- levels.push({
321
- numId: instance.numId,
322
- level: resolved.ilvl,
323
- format: resolved.numFmt,
324
- levelText: resolved.lvlText,
325
- ...(override?.startOverride ?? resolved.start) !== void 0 ? { start: override?.startOverride ?? resolved.start } : {}
326
- });
327
- }
328
- }
329
- return levels.toSorted((left, right) => left.numId - right.numId || left.level - right.level);
595
+ return numberingLevelsOf(this.baseDocument.package.numbering).toSorted((left, right) => left.numId - right.numId || left.level - right.level);
330
596
  }
331
597
  /** Return parsed package metadata without exposing the mutable document model. */
332
598
  getDocumentProperties() {
@@ -357,12 +623,18 @@ var FolioDocxReviewer = class FolioDocxReviewer {
357
623
  const handles = this.listStoryHandlesInternal();
358
624
  let highestId = 0;
359
625
  let present = false;
360
- if (mode === "with-revision-census") for (const handle of handles) {
361
- const state = this.getEditableStoryState(handle);
362
- if (!state) continue;
363
- const stats = getTrackedChangeStatsFromDoc(state.doc);
364
- highestId = Math.max(highestId, stats.highestId);
365
- present ||= stats.present;
626
+ if (mode === "with-revision-census") {
627
+ for (const change of this.currentFinalSectionProperties()?.propertyChanges ?? []) {
628
+ highestId = Math.max(highestId, change.info.id);
629
+ present = true;
630
+ }
631
+ for (const handle of handles) {
632
+ const state = this.getEditableStoryState(handle);
633
+ if (!state) continue;
634
+ const stats = getTrackedChangeStatsFromDoc(state.doc);
635
+ highestId = Math.max(highestId, stats.highestId);
636
+ present ||= stats.present;
637
+ }
366
638
  }
367
639
  const stories = [];
368
640
  for (const handle of handles) stories.push({
@@ -661,10 +933,18 @@ var FolioDocxReviewer = class FolioDocxReviewer {
661
933
  type: "header",
662
934
  relationshipId
663
935
  });
936
+ for (const relationshipId of this.importedHeaders.keys()) handles.push({
937
+ type: "header",
938
+ relationshipId
939
+ });
664
940
  for (const relationshipId of pkg.footers?.keys() ?? []) handles.push({
665
941
  type: "footer",
666
942
  relationshipId
667
943
  });
944
+ for (const relationshipId of this.importedFooters.keys()) handles.push({
945
+ type: "footer",
946
+ relationshipId
947
+ });
668
948
  for (const footnote of pkg.footnotes ?? []) if (!isSeparatorFootnote(footnote)) handles.push({
669
949
  type: "footnote",
670
950
  noteId: footnote.id
@@ -673,7 +953,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
673
953
  type: "endnote",
674
954
  noteId: endnote.id
675
955
  });
676
- return handles;
956
+ return handles.filter((handle) => handle.type !== "header" && handle.type !== "footer" || !this.removedHeaderFooterStories.has(headerFooterStoryKey(handle)));
677
957
  }
678
958
  /** Discover every readable document story through a typed, serializable handle. */
679
959
  listStories() {
@@ -696,7 +976,14 @@ var FolioDocxReviewer = class FolioDocxReviewer {
696
976
  * entry where the document model identifies them as one authored change.
697
977
  */
698
978
  getChanges(filter) {
699
- const changes = getTrackedChangesFromDoc(this.state.doc);
979
+ const changes = [...getTrackedChangesFromDoc(this.state.doc), ...(this.currentFinalSectionProperties()?.propertyChanges ?? []).map(({ info }) => ({
980
+ id: info.id,
981
+ type: "sectionPropertiesChanged",
982
+ author: info.author,
983
+ date: info.date ?? null,
984
+ text: "",
985
+ blockId: null
986
+ }))];
700
987
  if (!filter) return changes;
701
988
  return changes.filter((change) => (filter.author === void 0 || change.author === filter.author) && (filter.type === void 0 || change.type === filter.type));
702
989
  }
@@ -786,6 +1073,86 @@ var FolioDocxReviewer = class FolioDocxReviewer {
786
1073
  this.resolvedOverrides.set(target.id, resolved);
787
1074
  return true;
788
1075
  }
1076
+ currentFinalSectionProperties() {
1077
+ return this.finalSectionPropertiesOverride ?? this.baseDocument.package.document.finalSectionProperties;
1078
+ }
1079
+ stageFinalSectionProperties({ target, previous, revision }) {
1080
+ if (canonicalJson(target) === canonicalJson(previous)) return false;
1081
+ const previousReferences = sectionReferenceHistory({
1082
+ previous,
1083
+ target
1084
+ });
1085
+ this.finalSectionPropertiesOverride = {
1086
+ ...target,
1087
+ propertyChanges: [{
1088
+ type: "sectionPropertyChange",
1089
+ info: {
1090
+ id: revision.idSeed,
1091
+ author: this.author,
1092
+ date: revision.date
1093
+ },
1094
+ previousProperties: previous,
1095
+ ...previousReferences !== void 0 && { previousReferences },
1096
+ currentProperties: target
1097
+ }]
1098
+ };
1099
+ return true;
1100
+ }
1101
+ stageMappedSectionBoundaries({ target, originalRevisionIdSeed, maxRanges, revision, mapTargetProperties }) {
1102
+ const result = stageSectionBoundaryProperties({
1103
+ state: this.state,
1104
+ target,
1105
+ originalRevisionIdSeed,
1106
+ revisionStamp: revision,
1107
+ author: this.author,
1108
+ maxRanges,
1109
+ mapTargetProperties
1110
+ });
1111
+ if (result.status !== "matched") return result;
1112
+ const documentChanged = result.transaction.docChanged;
1113
+ if (documentChanged) this.state = this.state.apply(result.transaction);
1114
+ return {
1115
+ status: "matched",
1116
+ rangeCount: result.rangeCount,
1117
+ nextRevisionId: result.nextRevisionId,
1118
+ documentChanged
1119
+ };
1120
+ }
1121
+ resolveFinalSectionProperties(mode, id) {
1122
+ const current = this.currentFinalSectionProperties();
1123
+ const changes = current?.propertyChanges;
1124
+ const targetIndex = changes?.findIndex(({ info }) => info.id === id);
1125
+ if (!current || !changes || changes.length === 0 || targetIndex === void 0 || targetIndex < 0) return 0;
1126
+ const { propertyChanges: _propertyChanges, ...liveProperties } = current;
1127
+ const previousStates = changes.map((change) => sectionRejectProperties({
1128
+ live: current,
1129
+ previousProperties: change.previousProperties,
1130
+ previousReferences: change.previousReferences
1131
+ }));
1132
+ let resolvedProperties = previousStates[0] ?? liveProperties;
1133
+ const remaining = [];
1134
+ for (const [index, change] of changes.entries()) {
1135
+ const nextProperties = previousStates[index + 1] ?? liveProperties;
1136
+ if (index === targetIndex) {
1137
+ if (mode === "accept") resolvedProperties = nextProperties;
1138
+ continue;
1139
+ }
1140
+ remaining.push({
1141
+ ...change,
1142
+ previousProperties: resolvedProperties,
1143
+ ...change.previousReferences !== void 0 && { previousReferences: {
1144
+ ...resolvedProperties.headerReferences !== void 0 && { headerReferences: resolvedProperties.headerReferences },
1145
+ ...resolvedProperties.footerReferences !== void 0 && { footerReferences: resolvedProperties.footerReferences }
1146
+ } }
1147
+ });
1148
+ resolvedProperties = nextProperties;
1149
+ }
1150
+ this.finalSectionPropertiesOverride = {
1151
+ ...resolvedProperties,
1152
+ ...remaining.length > 0 && { propertyChanges: remaining }
1153
+ };
1154
+ return 1;
1155
+ }
789
1156
  /**
790
1157
  * Accept an existing tracked change, keeping its text and dropping the
791
1158
  * redline. Pass a {@link FolioReviewChange} from {@link getChanges} or its
@@ -794,14 +1161,26 @@ var FolioDocxReviewer = class FolioDocxReviewer {
794
1161
  * revision is no longer present (already resolved, or never existed).
795
1162
  */
796
1163
  acceptChange(target) {
797
- return this.runCommand(acceptAIEditRevision(revisionIdOf(target)));
1164
+ return this.resolveWithSectionReferenceHistory(() => this.acceptChangeInternal(target));
1165
+ }
1166
+ acceptChangeInternal(target) {
1167
+ const id = revisionIdOf(target);
1168
+ const bodyChanged = this.runCommand(acceptAIEditRevision(id));
1169
+ const sectionChanged = this.resolveFinalSectionProperties("accept", id) > 0;
1170
+ return bodyChanged || sectionChanged;
798
1171
  }
799
1172
  /**
800
1173
  * Reject an existing tracked change: an insertion's text is removed, a
801
1174
  * deletion's text is restored. See {@link acceptChange} for targeting.
802
1175
  */
803
1176
  rejectChange(target) {
804
- return this.runCommand(rejectAIEditRevision(revisionIdOf(target)));
1177
+ return this.resolveWithSectionReferenceHistory(() => this.rejectChangeInternal(target));
1178
+ }
1179
+ rejectChangeInternal(target) {
1180
+ const id = revisionIdOf(target);
1181
+ const bodyChanged = this.runCommand(rejectAIEditRevision(id));
1182
+ const sectionChanged = this.resolveFinalSectionProperties("reject", id) > 0;
1183
+ return bodyChanged || sectionChanged;
805
1184
  }
806
1185
  /**
807
1186
  * Accept every tracked change in the package. Returns the number of changes
@@ -812,11 +1191,45 @@ var FolioDocxReviewer = class FolioDocxReviewer {
812
1191
  * document still carries a redline nobody can see from the body.
813
1192
  */
814
1193
  acceptAll() {
815
- return this.resolveEveryStory("accept");
1194
+ return this.resolveWithSectionReferenceHistory(() => this.resolveEveryStory("accept") + this.resolveEveryFinalSectionChange("accept"));
816
1195
  }
817
1196
  /** Reject every tracked change in the package. See {@link acceptAll}. */
818
1197
  rejectAll() {
819
- return this.resolveEveryStory("reject");
1198
+ return this.resolveWithSectionReferenceHistory(() => this.resolveEveryStory("reject") + this.resolveEveryFinalSectionChange("reject"));
1199
+ }
1200
+ resolveWithSectionReferenceHistory(resolve) {
1201
+ const before = captureSectionReferenceInventory(this.state.doc, this.currentFinalSectionProperties());
1202
+ const result = resolve();
1203
+ const removedEndpointReferences = getTrackedSectionEndpointRemoval(this.state)?.removedReferences ?? [];
1204
+ if (before.revisionRelationships.size > 0 || removedEndpointReferences.length > 0) {
1205
+ const after = captureSectionReferenceInventory(this.state.doc, this.currentFinalSectionProperties());
1206
+ const removed = resolvedSectionReferenceLosses({
1207
+ before,
1208
+ after
1209
+ });
1210
+ this.sectionReferenceRemovals.push(...removed);
1211
+ for (const reference of [...removed, ...removedEndpointReferences]) {
1212
+ if (after.references.some(({ part, relationshipId }) => part === reference.part && relationshipId === reference.relationshipId)) continue;
1213
+ const handle = {
1214
+ type: reference.part,
1215
+ relationshipId: reference.relationshipId
1216
+ };
1217
+ this.removedHeaderFooterStories.set(headerFooterStoryKey(handle), handle);
1218
+ }
1219
+ }
1220
+ return result;
1221
+ }
1222
+ resolveEveryFinalSectionChange(mode) {
1223
+ const current = this.currentFinalSectionProperties();
1224
+ const firstChange = current?.propertyChanges?.at(0);
1225
+ if (!current || !firstChange) return 0;
1226
+ const { propertyChanges, ...liveProperties } = current;
1227
+ this.finalSectionPropertiesOverride = mode === "accept" ? liveProperties : sectionRejectProperties({
1228
+ live: current,
1229
+ previousProperties: firstChange.previousProperties,
1230
+ previousReferences: firstChange.previousReferences
1231
+ });
1232
+ return propertyChanges?.length ?? 0;
820
1233
  }
821
1234
  resolveEveryStory(mode) {
822
1235
  let count = 0;
@@ -841,14 +1254,39 @@ var FolioDocxReviewer = class FolioDocxReviewer {
841
1254
  });
842
1255
  return {
843
1256
  mainState: this.state,
1257
+ finalSectionPropertiesOverride: this.finalSectionPropertiesOverride,
844
1258
  secondaryStoryStates,
1259
+ sectionReferenceRemovals: [...this.sectionReferenceRemovals],
1260
+ removedHeaderFooterStories: [...this.removedHeaderFooterStories.values()],
1261
+ importedStyles: this.importedStyles,
1262
+ importedMedia: new Map(this.importedMedia),
1263
+ importedHeaders: new Map(this.importedHeaders),
1264
+ importedFooters: new Map(this.importedFooters),
845
1265
  createdComments: [...this.createdComments],
846
1266
  resolvedOverrides: new Map(this.resolvedOverrides),
847
1267
  resolvedStoryExpectations: [...this.resolvedStoryExpectations.values()]
848
1268
  };
849
1269
  }
850
1270
  documentFromStateSnapshot(snapshot) {
851
- const document = updateDocumentContent(this.baseDocument, snapshot.mainState.doc);
1271
+ const document = updateDocumentContent(snapshot.importedStyles === void 0 ? this.baseDocument : {
1272
+ ...this.baseDocument,
1273
+ package: {
1274
+ ...this.baseDocument.package,
1275
+ styles: snapshot.importedStyles
1276
+ }
1277
+ }, snapshot.mainState.doc);
1278
+ if (snapshot.finalSectionPropertiesOverride !== void 0) document.package.document.finalSectionProperties = snapshot.finalSectionPropertiesOverride;
1279
+ if (snapshot.importedStyles !== void 0) document.package.styles = snapshot.importedStyles;
1280
+ if (snapshot.importedMedia.size > 0) document.package.media = new Map([...document.package.media ?? [], ...snapshot.importedMedia]);
1281
+ if (snapshot.importedHeaders.size > 0) document.package.headers = new Map([...document.package.headers ?? [], ...snapshot.importedHeaders]);
1282
+ if (snapshot.importedFooters.size > 0) document.package.footers = new Map([...document.package.footers ?? [], ...snapshot.importedFooters]);
1283
+ for (const handle of snapshot.removedHeaderFooterStories) if (handle.type === "header") {
1284
+ document.package.headers = new Map(document.package.headers);
1285
+ document.package.headers.delete(handle.relationshipId);
1286
+ } else {
1287
+ document.package.footers = new Map(document.package.footers);
1288
+ document.package.footers.delete(handle.relationshipId);
1289
+ }
852
1290
  this.mergeEditedSecondaryStories(document, snapshot.secondaryStoryStates);
853
1291
  if (snapshot.createdComments.length > 0 || snapshot.resolvedOverrides.size > 0) document.package.document.comments = this.withResolvedOverrides([...document.package.document.comments ?? [], ...snapshot.createdComments], snapshot.resolvedOverrides);
854
1292
  return document;
@@ -856,7 +1294,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
856
1294
  captureSaveSnapshot() {
857
1295
  const snapshot = this.captureReviewerState();
858
1296
  const changedParaIds = new Set(getChangedParagraphIds(snapshot.mainState));
859
- let structuralChange = hasStructuralChanges(snapshot.mainState);
1297
+ let structuralChange = hasStructuralChanges(snapshot.mainState) || snapshot.finalSectionPropertiesOverride !== void 0 || snapshot.importedStyles !== void 0 || snapshot.importedHeaders.size > 0 || snapshot.importedFooters.size > 0;
860
1298
  let untrackedChanges = hasUntrackedChanges(snapshot.mainState);
861
1299
  const changedNoteParaIds = /* @__PURE__ */ new Set();
862
1300
  for (const entry of snapshot.secondaryStoryStates) {
@@ -875,6 +1313,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
875
1313
  changedParaIds
876
1314
  },
877
1315
  changedNoteParaIds,
1316
+ sectionReferenceRemovals: snapshot.sectionReferenceRemovals,
878
1317
  sectionEndpointRemoval: getTrackedSectionEndpointRemoval(snapshot.mainState),
879
1318
  resolvedStoryExpectations: snapshot.resolvedStoryExpectations
880
1319
  };
@@ -896,11 +1335,17 @@ var FolioDocxReviewer = class FolioDocxReviewer {
896
1335
  ...save.document,
897
1336
  originalBuffer: this.originalBuffer
898
1337
  };
1338
+ const repack = () => repackDocx(repackDocument, { changedNoteParaIds: save.changedNoteParaIds });
1339
+ const repackReferences = () => save.sectionReferenceRemovals.length > 0 ? withSectionReferenceResolution({
1340
+ document: repackDocument,
1341
+ removedReferences: save.sectionReferenceRemovals,
1342
+ repack
1343
+ }) : repack();
899
1344
  const buffer = save.sectionEndpointRemoval ? await withTrackedSectionEndpointRemoval({
900
1345
  document: repackDocument,
901
1346
  resolution: save.sectionEndpointRemoval,
902
- repack: () => repackDocx(repackDocument, { changedNoteParaIds: save.changedNoteParaIds })
903
- }) : await repackDocx(repackDocument, { changedNoteParaIds: save.changedNoteParaIds });
1347
+ repack: repackReferences
1348
+ }) : await repackReferences();
904
1349
  await this.assertResolvedStoriesSerialized(buffer, save.resolvedStoryExpectations);
905
1350
  return buffer;
906
1351
  }
@@ -995,8 +1440,92 @@ var FolioDocxReviewer = class FolioDocxReviewer {
995
1440
  entry.state = state;
996
1441
  }
997
1442
  getHeaderFooterStory(story) {
1443
+ if (this.removedHeaderFooterStories.has(headerFooterStoryKey(story))) return void 0;
1444
+ const importedStory = (story.type === "header" ? this.importedHeaders : this.importedFooters).get(story.relationshipId);
1445
+ if (importedStory) return importedStory;
998
1446
  return (story.type === "header" ? this.baseDocument.package.headers : this.baseDocument.package.footers)?.get(story.relationshipId);
999
1447
  }
1448
+ async createComparisonHeaderFooter(source, story) {
1449
+ const target = source.getHeaderFooterStory(story);
1450
+ if (!target || !this.canImportHeaderFooterContent(source, story)) return null;
1451
+ let importedWatermark = {};
1452
+ let importedMedia;
1453
+ if (target.watermark !== void 0 || target.rawWatermarkXml !== void 0) {
1454
+ const watermark = target.watermark;
1455
+ if (story.type !== "header" || watermark?.kind !== "picture" || watermark.imageTargetExternal || !watermark.imageTarget || !target.rawWatermarkXml) return null;
1456
+ const media = source.baseDocument.package.media?.get(watermark.imageTarget);
1457
+ if (!media || !/^image\/(?:png|jpeg|gif|tiff|bmp)$/u.test(media.mimeType)) return null;
1458
+ const xml = rebindDrawingImageRelationship({
1459
+ xml: target.rawWatermarkXml,
1460
+ previousId: watermark.imageRId,
1461
+ nextId: watermark.imageRId
1462
+ });
1463
+ if (xml === null) return null;
1464
+ const extension = media.path.split(".").at(-1)?.toLowerCase();
1465
+ if (!extension || !/^[a-z0-9]+$/u.test(extension)) return null;
1466
+ const originalZip = await JSZip.loadAsync(this.originalBuffer);
1467
+ const existingPaths = new Set(Object.keys(originalZip.files).map((path) => path.toLowerCase()));
1468
+ let suffix = 1;
1469
+ let path = `word/media/folio-import-${suffix}.${extension}`;
1470
+ while (existingPaths.has(path.toLowerCase()) || this.baseDocument.package.media?.has(path) || this.importedMedia.has(path)) path = `word/media/folio-import-${++suffix}.${extension}`;
1471
+ importedMedia = {
1472
+ ...media,
1473
+ path,
1474
+ data: media.data.slice(0)
1475
+ };
1476
+ importedWatermark = {
1477
+ watermark: {
1478
+ ...watermark,
1479
+ imageTarget: path
1480
+ },
1481
+ rawWatermarkXml: xml,
1482
+ ...target.watermarkBlockIndex !== void 0 && { watermarkBlockIndex: target.watermarkBlockIndex }
1483
+ };
1484
+ }
1485
+ const taken = /* @__PURE__ */ new Set([
1486
+ ...this.baseDocument.package.relationships?.keys() ?? [],
1487
+ ...this.baseDocument.package.headers?.keys() ?? [],
1488
+ ...this.baseDocument.package.footers?.keys() ?? [],
1489
+ ...this.importedHeaders.keys(),
1490
+ ...this.importedFooters.keys()
1491
+ ]);
1492
+ let suffix = 1;
1493
+ let relationshipId = `rId${suffix}`;
1494
+ while (taken.has(relationshipId)) relationshipId = `rId${++suffix}`;
1495
+ const handle = {
1496
+ type: story.type,
1497
+ relationshipId
1498
+ };
1499
+ const empty = {
1500
+ type: story.type,
1501
+ hdrFtrType: target.hdrFtrType,
1502
+ ...importedWatermark,
1503
+ content: [{
1504
+ type: "paragraph",
1505
+ content: []
1506
+ }]
1507
+ };
1508
+ if (importedMedia) this.importedMedia.set(importedMedia.path, importedMedia);
1509
+ (story.type === "header" ? this.importedHeaders : this.importedFooters).set(relationshipId, empty);
1510
+ const state = ensureBaseDirectionInState(EditorState.create({
1511
+ schema,
1512
+ doc: ensureDeterministicParaIdsInDoc(headerFooterToProseDoc(empty.content)),
1513
+ plugins: createHeadlessPlugins(this.baseDocument.package.styles, this.baseDocument.package.numbering)
1514
+ }));
1515
+ this.secondaryStoryStates.set(headerFooterStoryKey(handle), {
1516
+ handle,
1517
+ initialState: state,
1518
+ state
1519
+ });
1520
+ return handle;
1521
+ }
1522
+ canImportHeaderFooterContent(source, story) {
1523
+ const state = source.getEditableStoryState(story);
1524
+ if (!state) return false;
1525
+ if (!tableTemplateCanCrossPackageLosslessly(state.doc, "rebind-external")) return false;
1526
+ const snapshot = source.snapshotStory(story);
1527
+ return snapshot !== null && detachFolioAIEditSnapshotExternalHyperlinks(snapshot) !== null;
1528
+ }
1000
1529
  getNoteStory(story) {
1001
1530
  if (story.type === "footnote") return this.baseDocument.package.footnotes?.find((note) => note.id === story.noteId && !isSeparatorFootnote(note));
1002
1531
  return this.baseDocument.package.endnotes?.find((note) => note.id === story.noteId && !isSeparatorEndnote(note));
@@ -1029,7 +1558,7 @@ var FolioDocxReviewer = class FolioDocxReviewer {
1029
1558
  for (const entry of secondaryStoryStates) {
1030
1559
  if (entry.state === entry.initialState) continue;
1031
1560
  if (entry.handle.type === "header" || entry.handle.type === "footer") {
1032
- const source = this.getHeaderFooterStory(entry.handle);
1561
+ const source = entry.handle.type === "header" ? document.package.headers?.get(entry.handle.relationshipId) : document.package.footers?.get(entry.handle.relationshipId);
1033
1562
  if (!source) continue;
1034
1563
  const edited = {
1035
1564
  ...source,