@stll/folio-core 0.43.0 → 0.45.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 (237) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +7 -5
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/ai-edits/snapshot.js +13 -9
  6. package/dist/compare/content-alignment.js +94 -54
  7. package/dist/compare/inline-atoms.js +34 -20
  8. package/dist/compare/style-resources.js +6 -0
  9. package/dist/content-controls/mutateContentControls.js +4 -2
  10. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  11. package/dist/document-operations.js +14 -3
  12. package/dist/docx/appVersionNormalization.d.ts +0 -18
  13. package/dist/docx/blockContentParser.js +8 -0
  14. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  15. package/dist/docx/blockRangeMarkers.js +59 -0
  16. package/dist/docx/bookmarkParser.d.ts +2 -20
  17. package/dist/docx/bookmarkParser.js +6 -30
  18. package/dist/docx/borderParser.d.ts +13 -0
  19. package/dist/docx/borderParser.js +71 -0
  20. package/dist/docx/builtInStyles.d.ts +165 -0
  21. package/dist/docx/builtInStyles.js +239 -0
  22. package/dist/docx/commentIdNormalization.d.ts +3 -1
  23. package/dist/docx/commentIdNormalization.js +18 -1
  24. package/dist/docx/commentParser.d.ts +2 -1
  25. package/dist/docx/commentParser.js +80 -42
  26. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  27. package/dist/docx/commentReferenceNormalization.js +23 -14
  28. package/dist/docx/commentThreadKey.d.ts +18 -0
  29. package/dist/docx/commentThreadKey.js +22 -0
  30. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  31. package/dist/docx/danglingRelationshipReferences.js +30 -0
  32. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  33. package/dist/docx/defaultParagraphStyle.js +23 -1
  34. package/dist/docx/diagramPreview.js +87 -27
  35. package/dist/docx/documentParser.d.ts +2 -1
  36. package/dist/docx/documentParser.js +2 -2
  37. package/dist/docx/drawingUtils.d.ts +8 -1
  38. package/dist/docx/drawingUtils.js +12 -3
  39. package/dist/docx/fieldParser.js +3 -5
  40. package/dist/docx/footnoteParser.d.ts +3 -2
  41. package/dist/docx/footnoteParser.js +19 -4
  42. package/dist/docx/groupDrawingParser.js +4 -4
  43. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  44. package/dist/docx/headerFooterRefParser.js +42 -12
  45. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  46. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  47. package/dist/docx/hyperlinkParser.js +13 -17
  48. package/dist/docx/imageParser.d.ts +10 -2
  49. package/dist/docx/imageParser.js +80 -30
  50. package/dist/docx/imageRawXml.d.ts +14 -1
  51. package/dist/docx/imageRawXml.js +35 -11
  52. package/dist/docx/markupRangeMarker.d.ts +15 -0
  53. package/dist/docx/markupRangeMarker.js +44 -0
  54. package/dist/docx/mathToMathml.js +12 -14
  55. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  56. package/dist/docx/nonVisualDrawingProps.js +46 -0
  57. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  58. package/dist/docx/noteReferenceStyles.js +70 -0
  59. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  60. package/dist/docx/numberingReferenceNormalization.js +20 -1
  61. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  62. package/dist/docx/paragraphParser.js +66 -99
  63. package/dist/docx/paragraphPropertySource.js +1 -0
  64. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  65. package/dist/docx/paragraphTraversal.d.ts +37 -1
  66. package/dist/docx/paragraphTraversal.js +84 -1
  67. package/dist/docx/parseContext.d.ts +37 -0
  68. package/dist/docx/parseContext.js +67 -0
  69. package/dist/docx/parseWarningMessage.d.ts +6 -0
  70. package/dist/docx/parseWarningMessage.js +44 -0
  71. package/dist/docx/parser.js +83 -29
  72. package/dist/docx/previewBudget.d.ts +64 -0
  73. package/dist/docx/previewBudget.js +88 -0
  74. package/dist/docx/relsParser.d.ts +28 -11
  75. package/dist/docx/relsParser.js +26 -13
  76. package/dist/docx/revisionIdNormalization.js +96 -10
  77. package/dist/docx/rezip.js +80 -40
  78. package/dist/docx/runConsolidator.js +1 -2
  79. package/dist/docx/runParser.d.ts +8 -1
  80. package/dist/docx/runParser.js +30 -48
  81. package/dist/docx/sdtPropertiesPatch.js +24 -18
  82. package/dist/docx/sectionParser.d.ts +2 -1
  83. package/dist/docx/sectionParser.js +21 -65
  84. package/dist/docx/sectionReferenceHistory.js +2 -2
  85. package/dist/docx/selectiveSave.js +6 -6
  86. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  87. package/dist/docx/serializer/borderSerializer.d.ts +2 -3
  88. package/dist/docx/serializer/borderSerializer.js +13 -12
  89. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  90. package/dist/docx/serializer/commentSerializer.js +82 -72
  91. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  92. package/dist/docx/serializer/documentSerializer.js +6 -16
  93. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  94. package/dist/docx/serializer/headerFooterSerializer.js +10 -5
  95. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  96. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  97. package/dist/docx/serializer/noteSerializer.js +5 -0
  98. package/dist/docx/serializer/numberingSerializer.js +7 -6
  99. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  100. package/dist/docx/serializer/paragraphSerializer.js +47 -52
  101. package/dist/docx/serializer/partNamespaces.js +2 -2
  102. package/dist/docx/serializer/runSerializer.js +57 -31
  103. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  104. package/dist/docx/serializer/settingsSerializer.js +4 -3
  105. package/dist/docx/serializer/stylesSerializer.js +6 -6
  106. package/dist/docx/serializer/tableSerializer.js +37 -21
  107. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  108. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  109. package/dist/docx/serializer/themeSerializer.js +6 -6
  110. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  111. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  112. package/dist/docx/serializer/xmlUtils.js +1 -13
  113. package/dist/docx/server/boundedArchive.d.ts +12 -0
  114. package/dist/docx/server/boundedArchive.js +20 -1
  115. package/dist/docx/server/build.js +8 -1
  116. package/dist/docx/server/createBilingualDocument.js +10 -18
  117. package/dist/docx/server/extractDocxText.js +3 -4
  118. package/dist/docx/server/validateDocxConformance.js +22 -1
  119. package/dist/docx/shadingParser.d.ts +6 -0
  120. package/dist/docx/shadingParser.js +32 -0
  121. package/dist/docx/shapeParser.js +10 -8
  122. package/dist/docx/styleParser.js +13 -87
  123. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  124. package/dist/docx/styleReferenceResolution.js +51 -0
  125. package/dist/docx/tableLook.d.ts +57 -0
  126. package/dist/docx/tableLook.js +63 -0
  127. package/dist/docx/tableParser.d.ts +7 -9
  128. package/dist/docx/tableParser.js +64 -110
  129. package/dist/docx/textBoxParser.js +11 -6
  130. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  131. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  132. package/dist/docx/transitionalSpelling.d.ts +13 -2
  133. package/dist/docx/transitionalSpelling.js +23 -1
  134. package/dist/docx/unzip.d.ts +23 -0
  135. package/dist/docx/unzip.js +32 -22
  136. package/dist/docx/verbatimCapture.js +5 -12
  137. package/dist/docx/vmlImageParser.js +5 -4
  138. package/dist/docx/vmlPreview.d.ts +1 -3
  139. package/dist/docx/vmlPreview.js +2 -30
  140. package/dist/docx/watermarkParser.js +2 -2
  141. package/dist/docx/xmlParser.d.ts +38 -33
  142. package/dist/docx/xmlParser.js +92 -47
  143. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  144. package/dist/docx/xmlResourceLimits.js +105 -24
  145. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  146. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  147. package/dist/internal/paragraphFormattingSerialization.js +29 -8
  148. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  149. package/dist/layout-engine/index.d.ts +2 -2
  150. package/dist/layout-engine/index.js +2 -2
  151. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  152. package/dist/layout-engine/types.d.ts +8 -2
  153. package/dist/layout-engine/types.js +35 -2
  154. package/dist/layout-painter/renderImage.js +4 -3
  155. package/dist/layout-painter/renderParagraph.js +4 -3
  156. package/dist/managers/autoSaveCodec.js +2 -8
  157. package/dist/markdown/images.js +1 -4
  158. package/dist/markdown/index.js +1 -1
  159. package/dist/markdown/internals.d.ts +6 -1
  160. package/dist/markdown/internals.js +14 -1
  161. package/dist/markdown/renderBlock.js +35 -21
  162. package/dist/markdown/renderParagraph.js +14 -5
  163. package/dist/markdown/renderRuns.js +4 -3
  164. package/dist/markdown/renderTable.js +4 -3
  165. package/dist/markdown/trailers.js +41 -7
  166. package/dist/markdown/types.d.ts +3 -7
  167. package/dist/prosemirror/attrs/index.js +71 -5
  168. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  169. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  170. package/dist/prosemirror/commands/image.js +1 -0
  171. package/dist/prosemirror/commands/index.d.ts +3 -3
  172. package/dist/prosemirror/commands/index.js +2 -2
  173. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  174. package/dist/prosemirror/commands/paragraph.js +2 -2
  175. package/dist/prosemirror/commentIdAllocator.js +2 -7
  176. package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
  177. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  178. package/dist/prosemirror/conversion/toProseDoc.js +458 -335
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  180. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  181. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  182. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  183. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  184. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  185. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  186. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  187. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  188. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  189. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  190. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
  191. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
  192. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  193. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  194. package/dist/prosemirror/extensions/types.d.ts +2 -2
  195. package/dist/prosemirror/index.d.ts +3 -3
  196. package/dist/prosemirror/index.js +3 -3
  197. package/dist/prosemirror/insertOperations.d.ts +9 -2
  198. package/dist/prosemirror/insertOperations.js +9 -4
  199. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
  200. package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
  201. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  202. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  203. package/dist/prosemirror/plugins/index.d.ts +2 -2
  204. package/dist/prosemirror/plugins/index.js +2 -2
  205. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  206. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  207. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  208. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  209. package/dist/prosemirror/schema/nodes.d.ts +81 -1
  210. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  211. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  212. package/dist/prosemirror/styles/styleResolver.js +12 -0
  213. package/dist/style-engine/styleEngine.d.ts +3 -0
  214. package/dist/style-engine/styleEngine.js +3 -0
  215. package/dist/style-sets/extract.js +1 -23
  216. package/dist/style-sets/stellaStyle.js +46 -39
  217. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  218. package/dist/style-sets/styleSetNormalization.js +99 -0
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/base64.d.ts +36 -0
  221. package/dist/utils/base64.js +40 -0
  222. package/dist/utils/clipboard.js +2 -1
  223. package/dist/utils/createDocument.js +145 -20
  224. package/dist/utils/headingCollector.d.ts +8 -5
  225. package/dist/utils/headingCollector.js +23 -25
  226. package/dist/utils/tableOfContentsStyle.js +9 -2
  227. package/dist/utils/units.d.ts +10 -1
  228. package/dist/utils/units.js +12 -1
  229. package/dist/utils/urlSecurity.d.ts +8 -2
  230. package/dist/utils/urlSecurity.js +21 -3
  231. package/package.json +2 -2
  232. package/dist/docx/textWhitespace.d.ts +0 -4
  233. package/dist/docx/textWhitespace.js +0 -4
  234. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  235. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  236. package/dist/markdown/headings.d.ts +0 -13
  237. package/dist/markdown/headings.js +0 -20
@@ -1,3 +1,4 @@
1
+ import { escapeXmlText } from "@stll/docx-core";
1
2
  import JSZip from "jszip";
2
3
  //#region src/ai-edits/__fixtures__/paragraphs.ts
3
4
  /**
@@ -13,8 +14,7 @@ const ZIP_ENTRY_OPTIONS = {
13
14
  date: new Date(Date.UTC(2e3, 0, 1)),
14
15
  createFolders: false
15
16
  };
16
- const escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
17
- const paragraphXml = (text) => `<w:p><w:r><w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r></w:p>`;
17
+ const paragraphXml = (text) => `<w:p><w:r><w:t xml:space="preserve">${escapeXmlText(text)}</w:t></w:r></w:p>`;
18
18
  const buildParagraphsDocx = async (paragraphs, { inTableCell } = {}) => {
19
19
  const paragraphsXml = paragraphs.map(paragraphXml).join("");
20
20
  const body = inTableCell ? `<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr><w:tblGrid><w:gridCol w:w="9360"/></w:tblGrid><w:tr><w:tc><w:tcPr><w:tcW w:w="9360" w:type="dxa"/></w:tcPr>${paragraphsXml}</w:tc></w:tr></w:tbl>` + paragraphXml("After the table.") : paragraphsXml;
@@ -212,6 +212,7 @@ const sameReferencedNumberingLevels = ({ current, target, numId, levelsByNumId }
212
212
  for (const level of levelsByNumId.get(numId) ?? []) {
213
213
  const currentLevel = currentNumbering.getLevel(numId, level);
214
214
  const targetLevel = targetNumbering.getLevel(numId, level);
215
+ if (currentLevel === null && targetLevel === null) continue;
215
216
  if (!currentLevel || !targetLevel || canonicalJson(currentLevel) !== canonicalJson(targetLevel)) return false;
216
217
  }
217
218
  return true;
@@ -264,11 +265,12 @@ const formatBlockLine = (block, text) => {
264
265
  return `${label} ${text}`;
265
266
  };
266
267
  const formatBlockForLLM = (block) => formatBlockLine(block, block.text);
267
- const headingLevel = (block) => {
268
- const digits = /(\d+)/u.exec(block.styleId ?? block.displayLabel ?? "")?.[1];
269
- const level = digits ? Number.parseInt(digits, 10) : 1;
270
- return level >= 1 && level <= 9 ? level : 1;
271
- };
268
+ /**
269
+ * The level the snapshot already classified. Scraping a digit out of the style
270
+ * id instead read `ClauseParagraph2` as an h2 and every localized heading id
271
+ * without a digit as an h1.
272
+ */
273
+ const headingLevel = (block) => block.headingLevel ?? 1;
272
274
  const revisionIdOf = (target) => typeof target === "number" ? target : target.id;
273
275
  const commentPlainText = (comment) => (comment.content ?? []).map(paragraphPlainText).join("\n");
274
276
  const paragraphPlainText = (paragraph) => {
@@ -6,8 +6,8 @@ import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolv
6
6
  import { buildAnnotatedBlockText } from "./clean-text.js";
7
7
  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, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
8
8
  import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
9
- import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
9
+ import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
10
10
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
11
11
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
12
12
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
13
- 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_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, 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, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
13
+ 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_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, 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, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
@@ -6,6 +6,6 @@ import { buildAnnotatedBlockText } from "./clean-text.js";
6
6
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
7
7
  import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
8
8
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
9
- import { createFolioAIEditSnapshot, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
9
+ import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
10
10
  import { WORD_DIFF_GRANULARITIES, diffWordSegments } from "./word-diff.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_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
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_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
@@ -1,3 +1,4 @@
1
+ import { EMPTY_BUILT_IN_STYLE_INDEX, resolveHeadingLevel } from "../docx/builtInStyles.js";
1
2
  import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
2
3
  import { expectCharacterStyleMarkAttrs, expectHyperlinkMarkAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
3
4
  import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
@@ -335,6 +336,7 @@ const getTableLocation = ({ path, blockIndex, tableIndexByStart }) => {
335
336
  }
336
337
  };
337
338
  const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
339
+ const builtInStyles = styleResolver?.builtInStyles ?? EMPTY_BUILT_IN_STYLE_INDEX;
338
340
  const draftBlocks = [];
339
341
  const hashCounts = /* @__PURE__ */ new Map();
340
342
  const usedBlockIds = /* @__PURE__ */ new Set();
@@ -394,9 +396,9 @@ const createFolioAIEditSnapshotInternal = (doc, styleResolver) => {
394
396
  });
395
397
  }
396
398
  usedBlockIds.add(id);
397
- const headingLevel = getHeadingLevel(node);
399
+ const headingLevel = getHeadingLevel(node, builtInStyles);
398
400
  const kind = getBlockKind(node, headingLevel);
399
- const displayLabel = getDisplayLabel(node);
401
+ const displayLabel = getDisplayLabel(node, kind === "heading");
400
402
  const styleId = getStyleId(node);
401
403
  const listLevel = getListLevel(node);
402
404
  const listReference = getListReference(node);
@@ -477,19 +479,21 @@ const getBlockKind = (node, headingLevel) => {
477
479
  if (headingLevel !== void 0) return "heading";
478
480
  return "paragraph";
479
481
  };
480
- const getHeadingLevel = (node) => {
482
+ /** The block's 1-based heading level, as {@link resolveHeadingLevel} classifies it. */
483
+ const getHeadingLevel = (node, styles) => {
481
484
  const outlineLevel = node.attrs["outlineLevel"];
482
- if (typeof outlineLevel === "number" && Number.isInteger(outlineLevel) && outlineLevel >= 0 && outlineLevel <= 8) return outlineLevel + 1;
483
485
  const styleId = node.attrs["styleId"];
484
- if (typeof styleId !== "string") return;
485
- const level = /^heading(?<level>[1-9])$/iu.exec(styleId)?.groups?.["level"];
486
- return level === void 0 ? void 0 : Number.parseInt(level, 10);
486
+ const level = resolveHeadingLevel({
487
+ outlineLevel: typeof outlineLevel === "number" ? outlineLevel : null,
488
+ styleId: typeof styleId === "string" ? styleId : null
489
+ }, styles);
490
+ return level === void 0 ? void 0 : level + 1;
487
491
  };
488
- const getDisplayLabel = (node) => {
492
+ const getDisplayLabel = (node, isHeading) => {
489
493
  const listMarker = node.attrs["listMarker"];
490
494
  if (typeof listMarker === "string" && listMarker.trim().length > 0) return listMarker.trim();
491
495
  const styleId = node.attrs["styleId"];
492
- if (typeof styleId === "string" && /^heading/iu.test(styleId)) return styleId;
496
+ if (isHeading && typeof styleId === "string") return styleId;
493
497
  };
494
498
  const getListLevel = (node) => {
495
499
  const numPr = node.attrs["numPr"];
@@ -455,6 +455,27 @@ const MAX_CONTENT_STRUCTURE_PROFILE_TEXT_CODE_UNITS = 16384;
455
455
  const MAX_CONTENT_STRUCTURE_PROFILE_TOKENS = 64;
456
456
  const MAX_CONTENT_STRUCTURE_PROFILE_KIND_CODE_UNITS = 256;
457
457
  const CONTENT_STRUCTURE_ANCHOR_MINIMUM_WORD_COUNT = 3;
458
+ /** Four independent 32-bit lanes, so a digest collision needs four at once. */
459
+ const CONTENT_DIGEST_SEEDS = [
460
+ 2166136261,
461
+ 2654435769,
462
+ 2246822507,
463
+ 3266489909
464
+ ];
465
+ const CONTENT_DIGEST_PRIMES = [
466
+ 16777619,
467
+ 1540483477,
468
+ 668265263,
469
+ 374761393
470
+ ];
471
+ const foldContentDigest = (lanes, text) => {
472
+ for (let index = 0; index < text.length; index++) {
473
+ const unit = text.charCodeAt(index);
474
+ for (let lane = 0; lane < lanes.length; lane++) lanes[lane] = Math.imul((lanes[lane] ?? 0) ^ unit, CONTENT_DIGEST_PRIMES[lane] ?? 1);
475
+ }
476
+ for (let lane = 0; lane < lanes.length; lane++) lanes[lane] = Math.imul((lanes[lane] ?? 0) ^ text.length, CONTENT_DIGEST_PRIMES[lane] ?? 1);
477
+ };
478
+ const renderContentDigest = (lanes, blockCount) => `${String(blockCount)}:${[...lanes].map((lane) => (lane >>> 0).toString(36)).join(":")}`;
458
479
  const createContentStructureProfile = ({ blocks, blockStructure, idStability, physicalCellCount, retainContainerIdentity = false }) => {
459
480
  let containerIdentity = null;
460
481
  if (retainContainerIdentity) if (blocks.length > MAX_CONTENT_STRUCTURE_PROFILE_BLOCKS) containerIdentity = { status: "unavailable" };
@@ -481,39 +502,34 @@ const createContentStructureProfile = ({ blocks, blockStructure, idStability, ph
481
502
  if (idStability(block) === "stable") stableIds.push(block.id);
482
503
  }
483
504
  }
484
- if (blocks.length > MAX_CONTENT_STRUCTURE_PROFILE_BLOCKS) return {
485
- anchorTexts: [],
505
+ const lanes = Int32Array.from(CONTENT_DIGEST_SEEDS);
506
+ for (const block of blocks) foldContentDigest(lanes, JSON.stringify([
507
+ ...blockStructure(block),
508
+ block.kind,
509
+ block.text
510
+ ]));
511
+ const identity = {
486
512
  blockIds,
487
513
  containerIdentity,
488
- exactSignature: null,
514
+ contentDigest: renderContentDigest(lanes, blocks.length),
489
515
  physicalCellCount: physicalCellCount ?? null,
490
- stableIds,
491
- tokenCounts: null,
492
- tokenCount: 0
516
+ stableIds
493
517
  };
518
+ const overCap = {
519
+ ...identity,
520
+ anchors: { status: "skipped-over-cap" },
521
+ tokens: { status: "skipped-over-cap" }
522
+ };
523
+ if (blocks.length > MAX_CONTENT_STRUCTURE_PROFILE_BLOCKS) return overCap;
494
524
  let textCodeUnits = 0;
495
525
  for (const block of blocks) {
496
- if (block.kind.length > MAX_CONTENT_STRUCTURE_PROFILE_KIND_CODE_UNITS || block.text.length > MAX_CONTENT_STRUCTURE_PROFILE_TEXT_CODE_UNITS - textCodeUnits) return {
497
- anchorTexts: [],
498
- blockIds,
499
- containerIdentity,
500
- exactSignature: null,
501
- physicalCellCount: physicalCellCount ?? null,
502
- stableIds,
503
- tokenCounts: null,
504
- tokenCount: 0
505
- };
526
+ if (block.kind.length > MAX_CONTENT_STRUCTURE_PROFILE_KIND_CODE_UNITS || block.text.length > MAX_CONTENT_STRUCTURE_PROFILE_TEXT_CODE_UNITS - textCodeUnits) return overCap;
506
527
  textCodeUnits += block.text.length;
507
528
  }
508
529
  const tokenCounts = /* @__PURE__ */ new Map();
509
530
  const anchorTexts = /* @__PURE__ */ new Set();
510
531
  let tokenCount = 0;
511
532
  let tokensComplete = true;
512
- const signature = blocks.map((block) => [
513
- ...blockStructure(block),
514
- block.kind,
515
- block.text
516
- ]);
517
533
  for (const block of blocks) {
518
534
  let blockWordCount = 0;
519
535
  for (const match of block.text.matchAll(/\S+/gu)) {
@@ -530,24 +546,31 @@ const createContentStructureProfile = ({ blocks, blockStructure, idStability, ph
530
546
  if (!tokensComplete) break;
531
547
  }
532
548
  return {
533
- anchorTexts: [...anchorTexts],
534
- blockIds,
535
- containerIdentity,
536
- exactSignature: JSON.stringify(signature),
537
- physicalCellCount: physicalCellCount ?? null,
538
- stableIds,
539
- tokenCounts: tokensComplete ? tokenCounts : null,
540
- tokenCount: tokensComplete ? tokenCount : 0
549
+ ...identity,
550
+ anchors: {
551
+ status: "computed",
552
+ texts: [...anchorTexts]
553
+ },
554
+ tokens: tokensComplete ? {
555
+ status: "computed",
556
+ counts: tokenCounts,
557
+ total: tokenCount
558
+ } : { status: "skipped-over-cap" }
541
559
  };
542
560
  };
561
+ const UNKNOWN_CONTENT_STRUCTURE_SIMILARITY = { status: "unknown" };
543
562
  const contentStructureProfileSimilarity = (base, revised, workSession) => {
544
- if (!base.tokenCounts || !revised.tokenCounts || base.tokenCount === 0 || revised.tokenCount === 0) return 0;
545
- const [tokens, counterparts] = base.tokenCounts.size <= revised.tokenCounts.size ? [base.tokenCounts, revised.tokenCounts] : [revised.tokenCounts, base.tokenCounts];
546
- if (tokens.size > workSession.remainingStructuralTokenLookups) return 0;
563
+ if (base.tokens.status === "skipped-over-cap" || revised.tokens.status === "skipped-over-cap") return UNKNOWN_CONTENT_STRUCTURE_SIMILARITY;
564
+ if (base.tokens.total === 0 || revised.tokens.total === 0) return UNKNOWN_CONTENT_STRUCTURE_SIMILARITY;
565
+ const [tokens, counterparts] = base.tokens.counts.size <= revised.tokens.counts.size ? [base.tokens.counts, revised.tokens.counts] : [revised.tokens.counts, base.tokens.counts];
566
+ if (tokens.size > workSession.remainingStructuralTokenLookups) return UNKNOWN_CONTENT_STRUCTURE_SIMILARITY;
547
567
  workSession.remainingStructuralTokenLookups -= tokens.size;
548
568
  let shared = 0;
549
569
  for (const [token, count] of tokens) shared += Math.min(count, counterparts.get(token) ?? 0);
550
- return 2 * shared / (base.tokenCount + revised.tokenCount);
570
+ return {
571
+ status: "measured",
572
+ value: 2 * shared / (base.tokens.total + revised.tokens.total)
573
+ };
551
574
  };
552
575
  const tableStructureProfile = (blocks, idStability) => {
553
576
  const tableOrdinalByIndex = /* @__PURE__ */ new Map();
@@ -634,7 +657,6 @@ const uniqueExactContentSequencePairs = ({ baseKeys, revisedKeys }) => {
634
657
  const uniqueIndexes = (keys) => {
635
658
  const indexes = /* @__PURE__ */ new Map();
636
659
  keys.forEach((key, index) => {
637
- if (key === -1) return;
638
660
  indexes.set(key, indexes.has(key) ? null : index);
639
661
  });
640
662
  return indexes;
@@ -731,16 +753,15 @@ const alignProfiledContentSequence = ({ base, revised, workSession, canPair = ()
731
753
  }
732
754
  const exactSignatureKeys = /* @__PURE__ */ new Map();
733
755
  let nextExactSignatureKey = 0;
734
- const internExactSignature = (signature) => {
735
- if (signature === null) return -1;
736
- const existing = exactSignatureKeys.get(signature);
756
+ const internContentDigest = (digest) => {
757
+ const existing = exactSignatureKeys.get(digest);
737
758
  if (existing !== void 0) return existing;
738
759
  const key = nextExactSignatureKey++;
739
- exactSignatureKeys.set(signature, key);
760
+ exactSignatureKeys.set(digest, key);
740
761
  return key;
741
762
  };
742
- const baseExactSignatureKeys = base.map(({ profile }) => internExactSignature(profile.exactSignature));
743
- const revisedExactSignatureKeys = revised.map(({ profile }) => internExactSignature(profile.exactSignature));
763
+ const baseExactSignatureKeys = base.map(({ profile }) => internContentDigest(profile.contentDigest));
764
+ const revisedExactSignatureKeys = revised.map(({ profile }) => internContentDigest(profile.contentDigest));
744
765
  let persistedPairs = /* @__PURE__ */ new Set();
745
766
  if (base.some(({ profile }) => profile.containerIdentity !== null) || revised.some(({ profile }) => profile.containerIdentity !== null)) {
746
767
  const exactPairs = uniqueExactContentSequencePairs({
@@ -781,16 +802,20 @@ const alignProfiledContentSequence = ({ base, revised, workSession, canPair = ()
781
802
  if (!revisedItem) continue;
782
803
  if (!canPair(baseItem, revisedItem)) continue;
783
804
  const pairIndex = baseIndex * revised.length + revisedIndex;
784
- const exact = baseExactSignatureKeys[baseIndex] !== -1 && baseExactSignatureKeys[baseIndex] === revisedExactSignatureKeys[revisedIndex];
805
+ const exact = baseExactSignatureKeys[baseIndex] === revisedExactSignatureKeys[revisedIndex];
785
806
  const stable = stablePairs.has(pairIndex);
786
- const profileSimilarity = exact ? 1 : contentStructureProfileSimilarity(baseItem.profile, revisedItem.profile, workSession);
787
- const similar = Math.max(0, Math.min(1, profileSimilarity * similarityFactor(baseItem, revisedItem)));
807
+ const profileSimilarity = exact ? {
808
+ status: "measured",
809
+ value: 1
810
+ } : contentStructureProfileSimilarity(baseItem.profile, revisedItem.profile, workSession);
811
+ const similar = profileSimilarity.status === "measured" ? Math.max(0, Math.min(1, profileSimilarity.value * similarityFactor(baseItem, revisedItem))) : 0;
812
+ const similarEnough = profileSimilarity.status === "measured" && similar >= CONTENT_STRUCTURE_PAIR_SIMILARITY;
788
813
  const stableAtSamePosition = stable && baseIndex === revisedIndex;
789
814
  const persistedAtSamePosition = baseIndex === revisedIndex && provenanceTransitionAtSamePosition[baseIndex] === 1;
790
815
  const shiftedPersisted = baseIndex !== revisedIndex && persistedPairs.has(pairIndex);
791
816
  const persisted = persistedAtSamePosition || shiftedPersisted;
792
817
  const soleStructuralSlot = pairSoleStructuralSlot && base.length === 1 && revised.length === 1;
793
- if (!exact && !stableAtSamePosition && !persisted && !soleStructuralSlot && similar < CONTENT_STRUCTURE_PAIR_SIMILARITY) continue;
818
+ if (!exact && !stableAtSamePosition && !persisted && !soleStructuralSlot && !similarEnough) continue;
794
819
  pairScores[pairIndex] = (stable ? stableWeight : 0) + (exact ? exactWeight : 0) + (shiftedPersisted && !stable && !exact ? secondaryWeight : 0) + (persistedAtSamePosition ? continuityBonus : 0) + Math.round(similar * CONTENT_STRUCTURE_SIMILARITY_SCALE) + (soleStructuralSlot ? 1 : 0);
795
820
  }
796
821
  }
@@ -1124,11 +1149,10 @@ const contentStructureEvidenceIndexes = (base, revised) => {
1124
1149
  let nextExactKey = 0;
1125
1150
  const internExactSignatures = (items) => {
1126
1151
  for (const { profile } of items) {
1127
- if (profile.exactSignature === null) continue;
1128
- let key = exactKeys.get(profile.exactSignature);
1152
+ let key = exactKeys.get(profile.contentDigest);
1129
1153
  if (key === void 0) {
1130
1154
  key = nextExactKey++;
1131
- exactKeys.set(profile.exactSignature, key);
1155
+ exactKeys.set(profile.contentDigest, key);
1132
1156
  }
1133
1157
  exactKeyByProfile.set(profile, key);
1134
1158
  }
@@ -1152,7 +1176,7 @@ const contentStructureEvidenceIndexes = (base, revised) => {
1152
1176
  if (indexes.at(-1) !== itemIndex) indexes.push(itemIndex);
1153
1177
  } else stableIndexes.set(id, [itemIndex]);
1154
1178
  }
1155
- for (const text of profile.anchorTexts) {
1179
+ for (const text of profile.anchors.status === "computed" ? profile.anchors.texts : []) {
1156
1180
  const indexes = anchorTextIndexes.get(text);
1157
1181
  if (indexes) {
1158
1182
  if (indexes.at(-1) !== itemIndex) indexes.push(itemIndex);
@@ -1171,16 +1195,32 @@ const contentStructureEvidenceIndexes = (base, revised) => {
1171
1195
  revised: index(revised)
1172
1196
  };
1173
1197
  };
1174
- const sortedIndexesLeaveRange = (indexes, start, end) => indexes !== void 0 && ((indexes.at(0) ?? start) < start || (indexes.at(-1) ?? end - 1) >= end);
1198
+ /**
1199
+ * Whether the evidence lies outside the range, and only outside it.
1200
+ *
1201
+ * Evidence inside the range is at least as good a counterpart as evidence
1202
+ * beyond it, so a match that is also present locally does not make the local
1203
+ * pair suspect. Without the second test, two structurally identical tables in
1204
+ * one document cancel each other: each shares every exact signature and anchor
1205
+ * text with the other by construction, so each looks like it belongs
1206
+ * elsewhere, and comparing the document with itself reports both deleted and
1207
+ * both re-inserted.
1208
+ */
1209
+ const sortedIndexesLeaveRange = (indexes, start, end) => {
1210
+ if (indexes === void 0) return false;
1211
+ if ((indexes.at(0) ?? start) >= start && (indexes.at(-1) ?? end - 1) < end) return false;
1212
+ return !indexes.some((index) => index >= start && index < end);
1213
+ };
1175
1214
  const profileHasEvidenceOutsideRange = (profile, opposite, start, end) => {
1176
1215
  const exactKey = opposite.exactKeyByProfile.get(profile);
1177
1216
  if (exactKey !== void 0 && sortedIndexesLeaveRange(opposite.exactIndexes.get(exactKey), start, end)) return true;
1178
- if (profile.anchorTexts.some((text) => sortedIndexesLeaveRange(opposite.anchorTextIndexes.get(text), start, end))) return true;
1217
+ if (profile.anchors.status === "computed" && profile.anchors.texts.some((text) => sortedIndexesLeaveRange(opposite.anchorTextIndexes.get(text), start, end))) return true;
1179
1218
  return profile.stableIds.some((id) => sortedIndexesLeaveRange(opposite.stableIndexes.get(id), start, end));
1180
1219
  };
1181
1220
  const profilesShareContentAnchor = (base, revised) => {
1182
- const baseTexts = new Set(base.anchorTexts);
1183
- return revised.anchorTexts.some((text) => baseTexts.has(text));
1221
+ if (base.anchors.status === "skipped-over-cap" || revised.anchors.status === "skipped-over-cap") return "unknown";
1222
+ const baseTexts = new Set(base.anchors.texts);
1223
+ return revised.anchors.texts.some((text) => baseTexts.has(text)) ? "shares" : "disjoint";
1184
1224
  };
1185
1225
  const isBodyDocumentSegment = (segment) => segment.kind === "body";
1186
1226
  const profileBodySegments = (segments, idStability) => segments.flatMap((segment) => isBodyDocumentSegment(segment) ? [{
@@ -1273,13 +1313,13 @@ const pairTableSegmentsInGaps = ({ base, revised, baseGaps, revisedGaps, trusted
1273
1313
  const revisedEnd = revisedStart + revisedItems.length;
1274
1314
  const baseHasExternalEvidence = new Set(baseItems.filter(({ profile }) => profileHasEvidenceOutsideRange(profile, evidence.revised, revisedStart, revisedEnd)).map(({ item }) => item));
1275
1315
  const revisedHasExternalEvidence = new Set(revisedItems.filter(({ profile }) => profileHasEvidenceOutsideRange(profile, evidence.base, baseStart, baseEnd)).map(({ item }) => item));
1276
- const solePairHasContentAnchor = baseItems.length === 1 && revisedItems.length === 1 && profilesShareContentAnchor(baseItems.at(0)?.profile ?? panic("A sole base table has no profile"), revisedItems.at(0)?.profile ?? panic("A sole revised table has no profile"));
1316
+ const solePairAnchorOverlap = baseItems.length === 1 && revisedItems.length === 1 ? profilesShareContentAnchor(baseItems.at(0)?.profile ?? panic("A sole base table has no profile"), revisedItems.at(0)?.profile ?? panic("A sole revised table has no profile")) : "disjoint";
1277
1317
  const alignments = alignProfiledContentSequence({
1278
1318
  base: baseItems,
1279
1319
  revised: revisedItems,
1280
1320
  workSession,
1281
1321
  canPair: (baseItem, revisedItem) => !baseHasExternalEvidence.has(baseItem.item) && !revisedHasExternalEvidence.has(revisedItem.item),
1282
- pairSoleStructuralSlot: trustedBodyPairCount > 0 || solePairHasContentAnchor
1322
+ pairSoleStructuralSlot: trustedBodyPairCount > 0 || solePairAnchorOverlap === "shares"
1283
1323
  });
1284
1324
  for (const alignment of alignments) {
1285
1325
  if (alignment.type !== "pair") continue;
@@ -29,6 +29,15 @@ const textBlocksOf = (doc) => {
29
29
  });
30
30
  return blocks;
31
31
  };
32
+ /**
33
+ * Clean-text offset nearest a document position.
34
+ *
35
+ * `buildCleanBlockText` returns one offset per character plus one for
36
+ * end-of-block, so there is always an offset to land on. The result was typed
37
+ * `number | null` and never was: the caller's refusal branch could not run,
38
+ * while an empty `offsets` would have produced `-1` rather than the `null` the
39
+ * type promised.
40
+ */
32
41
  const offsetAt = ({ offsets, position }) => {
33
42
  let low = 0;
34
43
  let high = offsets.length;
@@ -40,16 +49,33 @@ const offsetAt = ({ offsets, position }) => {
40
49
  }
41
50
  return Math.min(low, offsets.length - 1);
42
51
  };
52
+ /**
53
+ * Attributes a conversion mints for itself rather than reads from the package.
54
+ *
55
+ * `toProseDoc` salts text-box anchor ids with a per-load nonce so a pasted span
56
+ * cannot hijack a real anchor. A comparison reads two independent conversions,
57
+ * so a minted handle differs on every run, and a key holding one makes a
58
+ * document differ from itself. Only document facts belong in a key.
59
+ *
60
+ * The guard against a node type gaining a minted attribute without being added
61
+ * here is not this list but `compareIdentity.property.test.ts`, which compares
62
+ * generated packages carrying each atom with themselves.
63
+ */
64
+ const CONVERSION_LOCAL_ATTRS = { textBoxAnchor: ["anchorId"] };
65
+ const documentFactAttrs = (node) => {
66
+ const local = CONVERSION_LOCAL_ATTRS[node.type.name];
67
+ if (!local) return node.attrs;
68
+ return Object.fromEntries(Object.entries(node.attrs).filter(([name]) => !local.includes(name)));
69
+ };
43
70
  const atomKey = (node) => canonicalJson({
44
71
  type: node.type.name,
45
- attrs: node.attrs,
72
+ attrs: documentFactAttrs(node),
46
73
  content: node.content.toJSON()
47
74
  });
48
75
  const atomBlockOf = ({ node, from }) => {
49
76
  const clean = buildCleanBlockText(node, from, { fieldResults: "omitted" });
50
77
  const supported = [];
51
78
  const unsupportedTopology = [];
52
- let unalignable = false;
53
79
  node.descendants((child, relativePosition) => {
54
80
  if (child.isText) return false;
55
81
  if (!child.isInline || !child.isAtom) return true;
@@ -58,18 +84,14 @@ const atomBlockOf = ({ node, from }) => {
58
84
  offsets: clean.offsets,
59
85
  position
60
86
  });
61
- if (offset === null) {
62
- unalignable = true;
63
- return false;
64
- }
65
87
  if (!isSupportedAtom(child)) {
66
88
  if (child.type.name === "renderedPageBreak") return false;
67
- unsupportedTopology.push(`${offset}:${child.type.name}:${canonicalJson(child.attrs)}`);
89
+ unsupportedTopology.push(`${offset}:${child.type.name}:${canonicalJson(documentFactAttrs(child))}`);
68
90
  return false;
69
91
  }
70
92
  const prepared = prepareTargetInlineAtom(child);
71
93
  if (!prepared) {
72
- unalignable = true;
94
+ unsupportedTopology.push(`${offset}:${child.type.name}:${canonicalJson(documentFactAttrs(child))}`);
73
95
  return false;
74
96
  }
75
97
  supported.push({
@@ -80,7 +102,7 @@ const atomBlockOf = ({ node, from }) => {
80
102
  });
81
103
  return false;
82
104
  });
83
- return unalignable ? null : {
105
+ return {
84
106
  node,
85
107
  from,
86
108
  cleanText: clean.text,
@@ -108,15 +130,7 @@ const targetBlockIdLookup = (snapshot) => {
108
130
  return owner?.id;
109
131
  };
110
132
  };
111
- const atomBlocksOf = (doc) => {
112
- const blocks = [];
113
- for (const block of textBlocksOf(doc)) {
114
- const atoms = atomBlockOf(block);
115
- if (!atoms) return null;
116
- blocks.push(atoms);
117
- }
118
- return blocks;
119
- };
133
+ const atomBlocksOf = (doc) => textBlocksOf(doc).map(atomBlockOf);
120
134
  const sameBlockTopology = (left, right) => left.node.type === right.node.type && left.cleanText === right.cleanText && canonicalJson(left.unsupportedTopology) === canonicalJson(right.unsupportedTopology);
121
135
  const matchingAtomGroup = ({ live, target, liveStart, targetStart }) => {
122
136
  if (live.length === target.length && live.every((atom, index) => atom.key === target[index]?.key)) return live.map((_, index) => [liveStart + index, targetStart + index]);
@@ -260,7 +274,7 @@ const matchInlineAtoms = ({ state, targetSnapshot, revisionStamp, originalRevisi
260
274
  let sourceBlocks;
261
275
  const liveBlocks = atomBlocksOf(reviewed.state.doc);
262
276
  const targetBlocks = atomBlocksOf(targetDocument);
263
- if (!liveBlocks || !targetBlocks || liveBlocks.length !== targetBlocks.length) return { status: "unalignable" };
277
+ if (liveBlocks.length !== targetBlocks.length) return { status: "unalignable" };
264
278
  const actions = [];
265
279
  for (const [index, live] of liveBlocks.entries()) {
266
280
  const target = targetBlocks[index];
@@ -366,7 +380,7 @@ const sameInlineAtoms = (leftDocument, rightDocument) => {
366
380
  if (!hasSupportedAtom(leftDocument) && !hasSupportedAtom(rightDocument)) return true;
367
381
  const leftBlocks = atomBlocksOf(leftDocument);
368
382
  const rightBlocks = atomBlocksOf(rightDocument);
369
- if (!leftBlocks || !rightBlocks || leftBlocks.length !== rightBlocks.length) return false;
383
+ if (leftBlocks.length !== rightBlocks.length) return false;
370
384
  return leftBlocks.every((left, index) => {
371
385
  const right = rightBlocks[index];
372
386
  if (left.supported.length === 0 && right?.supported.length === 0) return true;
@@ -213,6 +213,12 @@ const importReferencedStyleDefinitions = ({ sourceStyles, destinationStyles, sou
213
213
  styleIdMap: /* @__PURE__ */ new Map(),
214
214
  importedStyleIds: []
215
215
  };
216
+ if (requested.length === 0 && !sourceStyles && !destinationStyles) return {
217
+ status: "unchanged",
218
+ styles: destinationStyles,
219
+ styleIdMap: /* @__PURE__ */ new Map(),
220
+ importedStyleIds: []
221
+ };
216
222
  if (!sourceStyles) return {
217
223
  status: "unalignable",
218
224
  detail: "the target has no style definitions"
@@ -1,5 +1,6 @@
1
1
  import { formatDate } from "../docx/fieldParser.js";
2
2
  import { ContentControlBoundError, ContentControlLockedError, ContentControlTypeError } from "./errors.js";
3
+ import { sanitizeXmlCharacters } from "@stll/docx-core";
3
4
  //#region src/content-controls/mutateContentControls.ts
4
5
  /**
5
6
  * Pure (immutable) write helpers for block-level content controls.
@@ -220,7 +221,8 @@ function controlMatchesFilter(control, filter) {
220
221
  if (filter.sdtType !== void 0 && control.properties.sdtType !== filter.sdtType) return false;
221
222
  return true;
222
223
  }
223
- function makeParagraphFromText(text) {
224
+ function makeParagraphFromText(rawText) {
225
+ const text = sanitizeXmlCharacters(rawText);
224
226
  return {
225
227
  type: "paragraph",
226
228
  content: text.length === 0 ? [] : [{
@@ -300,7 +302,7 @@ function setContentControlValue(doc, filter, input, options = {}) {
300
302
  ...control,
301
303
  properties: {
302
304
  ...control.properties,
303
- dropdownLastValue: input.value,
305
+ dropdownLastValue: sanitizeXmlCharacters(input.value),
304
306
  showingPlaceholder: false,
305
307
  ...strippedRawPropertiesXml !== void 0 ? { rawPropertiesXml: strippedRawPropertiesXml } : {}
306
308
  },
@@ -1,3 +1,5 @@
1
+ import { bytesToDataUrl } from "../../utils/base64.js";
2
+ import { anchorTargetAttrs } from "../../utils/urlSecurity.js";
1
3
  import { STROKE_DASH_FACTORS, walkRegions } from "../primitives.js";
2
4
  import { panic } from "better-result";
3
5
  //#region src/display-list/dom/renderDisplayListToDom.ts
@@ -42,7 +44,6 @@ const PAGE_CLASS_NAME = "layout-page";
42
44
  */
43
45
  const PAGE_ELEMENT_ID_PREFIX = "page-";
44
46
  const DEGREES_PER_RADIAN = 180 / Math.PI;
45
- const BASE64_CHUNK_SIZE = 32768;
46
47
  const IMAGE_MIME_TYPES = {
47
48
  png: "image/png",
48
49
  jpeg: "image/jpeg"
@@ -98,17 +99,11 @@ const resolveImage = (ref, images) => {
98
99
  if (source === void 0) panic(`renderDisplayListToDom: image ref ${ref} is out of range (${images.length} images)`);
99
100
  return source;
100
101
  };
101
- const toBase64 = (bytes) => {
102
- let binary = "";
103
- for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_SIZE) binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_SIZE));
104
- return btoa(binary);
105
- };
106
102
  /** A `Blob` part cannot be backed by shared memory, so prove it is not. */
107
103
  const isBlobPart = (bytes) => bytes.buffer instanceof ArrayBuffer;
108
104
  const binarySrc = (bytes, mimeType) => {
109
105
  if (isBlobPart(bytes) && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function") return URL.createObjectURL(new Blob([bytes], { type: mimeType }));
110
- if (typeof btoa === "function") return `data:${mimeType};base64,${toBase64(bytes)}`;
111
- panic("renderDisplayListToDom: no Blob or btoa to build a binary source from");
106
+ return bytesToDataUrl(bytes, mimeType);
112
107
  };
113
108
  const fontFaceRule = (face, embedded, src) => `@font-face { font-family: ${quoteFontFamily(embedded.id)}; src: url("${src}"); font-weight: ${face.weight}; font-style: ${face.italic ? "italic" : "normal"}; }`;
114
109
  /**
@@ -489,6 +484,11 @@ const paintLink = ({ rect, target, tooltip }, context) => {
489
484
  element.style.width = px(rect.widthPx);
490
485
  element.style.height = px(rect.heightPx);
491
486
  element.href = linkHref(target);
487
+ if (target.kind !== "page") {
488
+ const anchor = anchorTargetAttrs(void 0);
489
+ element.target = anchor.target;
490
+ element.rel = anchor.rel;
491
+ }
492
492
  if (tooltip !== void 0) element.title = tooltip;
493
493
  context.parent.append(element);
494
494
  };