@stll/folio-core 0.60.0 → 0.62.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 (173) hide show
  1. package/README.md +13 -0
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +70 -75
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +3 -1
  13. package/dist/controller/canonicalClipboard.js +2 -1
  14. package/dist/controller/canonicalListRendering.d.ts +4 -6
  15. package/dist/controller/canonicalListRendering.js +3 -157
  16. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  17. package/dist/controller/headerFooterEditorManager.js +11 -0
  18. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  19. package/dist/controller/hiddenEditorManager.js +3 -1
  20. package/dist/controller/layoutPipeline.d.ts +3 -1
  21. package/dist/controller/layoutPipeline.js +13 -4
  22. package/dist/controller/layoutSession.d.ts +2 -1
  23. package/dist/controller/noteEditorManager.d.ts +1 -1
  24. package/dist/display-list/build/imagePrimitives.js +28 -12
  25. package/dist/display-list/build/paragraphPrimitives.js +15 -2
  26. package/dist/display-list/dom/renderDisplayListToDom.js +8 -5
  27. package/dist/display-list/types.d.ts +7 -3
  28. package/dist/docx/canonicalStoryRepack.js +3 -2
  29. package/dist/docx/commentTextParagraph.d.ts +6 -0
  30. package/dist/docx/commentTextParagraph.js +15 -0
  31. package/dist/docx/conformance.js +6 -1
  32. package/dist/docx/diagramPreview.js +2 -1
  33. package/dist/docx/documentParser.d.ts +13 -1
  34. package/dist/docx/documentParser.js +1 -1
  35. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  36. package/dist/docx/encryption/compoundFile.js +2 -1
  37. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  38. package/dist/docx/encryption/cryptoBytes.js +3 -18
  39. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  40. package/dist/docx/encryption/encryptionInfo.js +2 -1
  41. package/dist/docx/ensureParaIds.js +2 -1
  42. package/dist/docx/groupDrawingParser.js +28 -13
  43. package/dist/docx/listModelNormalization.d.ts +10 -0
  44. package/dist/docx/listModelNormalization.js +166 -0
  45. package/dist/docx/metadataPrivacy.js +3 -2
  46. package/dist/docx/metafileRaster.js +2 -1
  47. package/dist/docx/packageParts.js +6 -5
  48. package/dist/docx/paragraphParser.js +6 -4
  49. package/dist/docx/parser.js +5 -4
  50. package/dist/docx/preservedRunContent.js +11 -2
  51. package/dist/docx/removeHeaderFooterParts.js +8 -7
  52. package/dist/docx/replyToComment.js +3 -13
  53. package/dist/docx/rezip.js +88 -89
  54. package/dist/docx/runParser.js +12 -6
  55. package/dist/docx/runPropertyPresence.d.ts +11 -0
  56. package/dist/docx/runPropertyPresence.js +14 -0
  57. package/dist/docx/selectiveSave.js +15 -14
  58. package/dist/docx/serializer/runSerializer.js +3 -2
  59. package/dist/docx/serializer/tableSerializer.js +2 -1
  60. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  61. package/dist/docx/server/boundedArchive.js +2 -1
  62. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  63. package/dist/docx/server/inspectDocxPackage.js +5 -1
  64. package/dist/docx/server/validateDocxConformance.js +2 -1
  65. package/dist/docx/tableParser.js +7 -4
  66. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  67. package/dist/docx/tableRowPropertyPresence.js +15 -0
  68. package/dist/docx/unzip.js +2 -1
  69. package/dist/docx/xmlEncoding.js +6 -5
  70. package/dist/fonts/fontsourceFaces.js +2 -1
  71. package/dist/fonts/sfnt/parse.js +6 -9
  72. package/dist/fonts/sfnt/subset.js +2 -1
  73. package/dist/fonts/sfnt/tables.js +2 -3
  74. package/dist/fonts/sfnt/woff.js +2 -1
  75. package/dist/i18n/messages/ar.gen.js +1 -0
  76. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  77. package/dist/i18n/messages/catalogs.gen.js +17 -0
  78. package/dist/i18n/messages/cs.gen.js +1 -0
  79. package/dist/i18n/messages/de.gen.js +1 -0
  80. package/dist/i18n/messages/en.gen.js +1 -0
  81. package/dist/i18n/messages/es.gen.js +1 -0
  82. package/dist/i18n/messages/et.gen.js +1 -0
  83. package/dist/i18n/messages/fr.gen.js +1 -0
  84. package/dist/i18n/messages/he.gen.js +1 -0
  85. package/dist/i18n/messages/hi.gen.js +1 -0
  86. package/dist/i18n/messages/hu.gen.js +1 -0
  87. package/dist/i18n/messages/lt.gen.js +1 -0
  88. package/dist/i18n/messages/lv.gen.js +1 -0
  89. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  90. package/dist/i18n/messages/pl.gen.js +1 -0
  91. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  92. package/dist/i18n/messages/sk.gen.js +1 -0
  93. package/dist/i18n/messages/tr.gen.js +1 -0
  94. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  95. package/dist/index.d.ts +3 -1
  96. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  97. package/dist/internal/revisionResolutionInline.js +22 -1
  98. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  99. package/dist/layout-bridge/convert/endnoteLayout.d.ts +1 -1
  100. package/dist/layout-bridge/convert/endnoteLayout.js +1 -3
  101. package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
  102. package/dist/layout-bridge/convert/footnoteLayout.d.ts +4 -0
  103. package/dist/layout-bridge/convert/footnoteLayout.js +13 -3
  104. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -0
  105. package/dist/layout-bridge/convert/headerFooterLayout.js +12 -15
  106. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  107. package/dist/layout-engine/index.js +2 -2
  108. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  109. package/dist/layout-engine/noteAreaFlow.d.ts +4 -4
  110. package/dist/layout-engine/noteAreaFlow.js +12 -6
  111. package/dist/layout-engine/types.d.ts +5 -2
  112. package/dist/layout-painter/index.d.ts +1 -1
  113. package/dist/managers/AutoSaveManager.js +2 -1
  114. package/dist/managers/EditorModeManager.d.ts +10 -1
  115. package/dist/managers/EditorModeManager.js +15 -1
  116. package/dist/managers/FindReplaceManager.d.ts +13 -6
  117. package/dist/managers/FindReplaceManager.js +15 -9
  118. package/dist/managers/TableSelectionManager.d.ts +1 -1
  119. package/dist/managers/autoSaveCodec.js +2 -6
  120. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  121. package/dist/managers/reviewDisplayMode.js +15 -0
  122. package/dist/paged-layout/editorScrollRoot.d.ts +3 -1
  123. package/dist/paged-layout/editorScrollRoot.js +2 -2
  124. package/dist/paged-layout/pageText.d.ts +11 -2
  125. package/dist/paged-layout/pageText.js +8 -3
  126. package/dist/pdf/fonts.js +5 -14
  127. package/dist/pdf/images.js +4 -2
  128. package/dist/pdf/objects.d.ts +0 -11
  129. package/dist/pdf/objects.js +7 -9
  130. package/dist/pdf/writePdf.js +2 -1
  131. package/dist/prosemirror/commands/comments.js +51 -13
  132. package/dist/prosemirror/commentRangeInlineAtoms.d.ts +40 -0
  133. package/dist/prosemirror/commentRangeInlineAtoms.js +33 -0
  134. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  135. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  136. package/dist/prosemirror/conversion/fromProseDoc.js +14 -8
  137. package/dist/prosemirror/conversion/toProseDoc.js +16 -23
  138. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  139. package/dist/prosemirror/fieldRepresentation.js +4 -2
  140. package/dist/prosemirror/findReplaceSelection.d.ts +48 -2
  141. package/dist/prosemirror/findReplaceSelection.js +153 -38
  142. package/dist/prosemirror/inlineDisplayText.d.ts +10 -0
  143. package/dist/prosemirror/inlineDisplayText.js +29 -0
  144. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  145. package/dist/prosemirror/markupViewProjection.js +21 -3
  146. package/dist/prosemirror/plugins/transactionInvariants.js +6 -1
  147. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  148. package/dist/prosemirror/replacedAnnotations.js +71 -12
  149. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  150. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  151. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  152. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  153. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  154. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  155. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  156. package/dist/utils/base64.d.ts +1 -31
  157. package/dist/utils/base64.js +19 -33
  158. package/dist/utils/clipboard.js +5 -4
  159. package/dist/utils/colorResolver.js +11 -2
  160. package/dist/utils/findMatchView.d.ts +32 -0
  161. package/dist/utils/findMatchView.js +406 -0
  162. package/dist/utils/findReplace.d.ts +67 -4
  163. package/dist/utils/findReplace.js +359 -37
  164. package/dist/utils/findSearchKey.d.ts +29 -0
  165. package/dist/utils/findSearchKey.js +34 -0
  166. package/dist/utils/replaceText.js +138 -94
  167. package/dist/utils/sliceText.d.ts +2 -0
  168. package/dist/utils/sliceText.js +2 -0
  169. package/dist/utils/textEncoding.d.ts +2 -0
  170. package/dist/utils/textEncoding.js +2 -0
  171. package/dist/utils/tiffConverter.d.ts +0 -9
  172. package/dist/utils/tiffConverter.js +2 -4
  173. package/package.json +51 -25
@@ -2,18 +2,23 @@
2
2
  /**
3
3
  * The plain text of a single rendered page (1-based), read by joining each of
4
4
  * the page's layout fragments' `[pmStart, pmEnd)` document text with a
5
- * newline. Fragments with no `pmStart`/`pmEnd` (e.g. a page-shell placeholder)
5
+ * newline. A review projection maps those authored ranges to the displayed
6
+ * document, excluding text the view hides. Fragments with no `pmStart`/`pmEnd`
7
+ * (e.g. a page-shell placeholder)
6
8
  * are skipped. Returns `null` when the layout hasn't been computed yet or the
7
9
  * page number is out of range.
8
10
  */
9
- const getPageTextFromLayout = (layout, doc, page) => {
11
+ const getPageTextFromLayout = ({ layout, doc, page, projection }) => {
10
12
  if (!layout || !Number.isInteger(page) || page < 1) return null;
11
13
  const layoutPage = layout.pages[page - 1];
12
14
  if (!layoutPage) return null;
15
+ const viewDoc = projection?.type === "resolved" ? projection.doc : doc;
13
16
  const fragmentTexts = [];
14
17
  for (const fragment of layoutPage.fragments) {
15
18
  if (typeof fragment.pmStart !== "number" || typeof fragment.pmEnd !== "number") continue;
16
- fragmentTexts.push(doc.textBetween(fragment.pmStart, fragment.pmEnd, "\n"));
19
+ const from = projection?.type === "resolved" ? projection.toView(fragment.pmStart, 1) : fragment.pmStart;
20
+ const to = projection?.type === "resolved" ? projection.toView(fragment.pmEnd, -1) : fragment.pmEnd;
21
+ fragmentTexts.push(viewDoc.textBetween(from, to, "\n"));
17
22
  }
18
23
  return fragmentTexts.join("\n");
19
24
  };
package/dist/pdf/fonts.js CHANGED
@@ -2,6 +2,7 @@ import { parseSfnt } from "../fonts/sfnt/parse.js";
2
2
  import { subsetTrueType } from "../fonts/sfnt/subset.js";
3
3
  import { toSfntBytes } from "../fonts/sfnt/woff.js";
4
4
  import { needsShaping, placeRun } from "../shaping/placeRun.js";
5
+ import { bytesToHex, decodeText, encodeUtf16BE, encodeUtf8 } from "../utils/textEncoding.js";
5
6
  import { pdfArray, pdfAsciiString, pdfDict, pdfFlateStream, pdfName, pdfNumber, pdfNumberArray } from "./objects.js";
6
7
  import { Result, TaggedError, panic } from "better-result";
7
8
  //#region src/pdf/fonts.ts
@@ -46,7 +47,7 @@ const SUBSET_TAG_LENGTH = 6;
46
47
  const ALPHABET_LENGTH = 26;
47
48
  const UPPERCASE_A = 65;
48
49
  const WOFF_SIGNATURES = ["wOFF", "wOF2"];
49
- const readTag = (bytes) => String.fromCharCode(bytes[0] ?? 0, bytes[1] ?? 0, bytes[2] ?? 0, bytes[3] ?? 0);
50
+ const readTag = (bytes) => decodeText(Uint8Array.of(bytes[0] ?? 0, bytes[1] ?? 0, bytes[2] ?? 0, bytes[3] ?? 0), { encoding: "binary" });
50
51
  /**
51
52
  * Six uppercase letters derived from the face and the exact glyph set, so the
52
53
  * tag is stable across runs and two different subsets of one family do not
@@ -148,18 +149,8 @@ const buildWinAnsiTable = () => {
148
149
  return table;
149
150
  };
150
151
  const WIN_ANSI_BY_CODE_POINT = buildWinAnsiTable();
151
- const utf16BeHexOf = (text) => [...text].map((character) => utf16BeHex(character.codePointAt(0) ?? 0)).join("");
152
- const utf16BeHex = (codePoint) => {
153
- const SUPPLEMENTARY_START = 65536;
154
- const SURROGATE_HALF_BITS = 10;
155
- const LOW_SURROGATE_MASK = 1023;
156
- const HIGH_SURROGATE_BASE = 55296;
157
- const LOW_SURROGATE_BASE = 56320;
158
- const hex = (unit) => unit.toString(16).padStart(4, "0").toUpperCase();
159
- if (codePoint < SUPPLEMENTARY_START) return hex(codePoint);
160
- const offset = codePoint - SUPPLEMENTARY_START;
161
- return hex(HIGH_SURROGATE_BASE + (offset >> SURROGATE_HALF_BITS)) + hex(LOW_SURROGATE_BASE + (offset & LOW_SURROGATE_MASK));
162
- };
152
+ const utf16BeHexOf = (text) => bytesToHex(encodeUtf16BE(text)).toUpperCase();
153
+ const utf16BeHex = (codePoint) => utf16BeHexOf(String.fromCodePoint(codePoint));
163
154
  /** Entries per `beginbfchar` block; the CMap syntax caps this at 100. */
164
155
  const BFCHAR_BLOCK_SIZE = 100;
165
156
  const toUnicodeCMap = (glyphToText) => {
@@ -290,7 +281,7 @@ const emitFont = (document, plan) => {
290
281
  fontFileKey: font.isCff ? "FontFile3" : "FontFile2",
291
282
  fontFileRef
292
283
  }));
293
- const toUnicodeRef = document.add(pdfFlateStream([], new TextEncoder().encode(toUnicodeCMap(plan.glyphToText))));
284
+ const toUnicodeRef = document.add(pdfFlateStream([], encodeUtf8(toUnicodeCMap(plan.glyphToText))));
294
285
  const descendantRef = document.add(pdfDict([
295
286
  ["Type", pdfName("Font")],
296
287
  ["Subtype", pdfName(font.isCff ? "CIDFontType0" : "CIDFontType2")],
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../utils/textEncoding.js";
1
2
  import { pdfArray, pdfHexString, pdfName, pdfNumber } from "./objects.js";
2
3
  import { Result, TaggedError } from "better-result";
3
4
  import { inflateSync } from "node:zlib";
@@ -63,7 +64,7 @@ const readPngChunks = (bytes) => {
63
64
  const CHUNK_CRC_BYTES = 4;
64
65
  while (cursor + CHUNK_HEADER_BYTES <= bytes.length) {
65
66
  const length = view.getUint32(cursor);
66
- const type = String.fromCharCode(bytes[cursor + 4] ?? 0, bytes[cursor + 5] ?? 0, bytes[cursor + 6] ?? 0, bytes[cursor + 7] ?? 0);
67
+ const type = decodeText(bytes.subarray(cursor + 4, cursor + 8), { encoding: "binary" });
67
68
  const dataStart = cursor + CHUNK_HEADER_BYTES;
68
69
  const dataEnd = dataStart + length;
69
70
  if (dataEnd > bytes.length) return Result.err(new PdfImageError({ message: `PNG chunk ${type} runs past the end` }));
@@ -538,7 +539,7 @@ const decodeJpeg = (bytes) => {
538
539
  const length = view.getUint16(cursor + 2);
539
540
  const segment = cursor + 4;
540
541
  const frame = JPEG_FRAME_MODES[marker];
541
- if (marker === APP14 && length >= 12) adobe = String.fromCharCode(...bytes.subarray(segment, segment + 5)) === "Adobe";
542
+ if (marker === APP14 && length >= 12) adobe = decodeText(bytes.subarray(segment, segment + 5), { encoding: "binary" }) === "Adobe";
542
543
  if (frame !== void 0) {
543
544
  if (!frame.dctDecodable) return Result.err(new PdfImageError({ message: `JPEG ${frame.name} frame (marker 0xff${marker.toString(16)}) has no /DCTDecode equivalent` }));
544
545
  const height = view.getUint16(segment + 1);
@@ -565,6 +566,7 @@ const decodeImage = (source) => {
565
566
  switch (source.format) {
566
567
  case "png": return decodePng(source.bytes);
567
568
  case "jpeg": return decodeJpeg(source.bytes);
569
+ case "svg": return Result.err(new PdfImageError({ message: "SVG images are not supported in PDF" }));
568
570
  default: {
569
571
  const unreachable = source.format;
570
572
  return Result.err(new PdfImageError({ message: `unsupported image format ${String(unreachable)}` }));
@@ -1,15 +1,4 @@
1
1
  //#region src/pdf/objects.d.ts
2
- /**
3
- * The PDF object model: values, serialization, the cross-reference table and
4
- * the trailer.
5
- *
6
- * Everything a backend writes into a PDF file passes through here, which is
7
- * what makes byte-for-byte determinism enforceable rather than aspirational:
8
- * numbers go through one formatter, dictionaries carry their entries as an
9
- * ordered list (never an object or a `Map` whose iteration order depends on
10
- * how it was built), and object numbers come from one allocator in call
11
- * order.
12
- */
13
2
  /** The one number formatter. Every number in the file is emitted through it. */
14
3
  declare const formatNumber: (value: number) => string;
15
4
  type PdfRef = {
@@ -1,3 +1,4 @@
1
+ import { bytesToHex, encodeBinaryString, encodeUtf16BE, encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { panic } from "better-result";
2
3
  import { deflateSync } from "node:zlib";
3
4
  //#region src/pdf/objects.ts
@@ -94,7 +95,10 @@ const pdfFlateStream = (entries, data) => pdfStream(pdfDict([...entries, ["Filte
94
95
  const NAME_REGULAR = /^[A-Za-z0-9._\-+]$/u;
95
96
  const encodeName = (value) => {
96
97
  let out = "/";
97
- for (const char of value) out += NAME_REGULAR.test(char) ? char : `#${char.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()}`;
98
+ for (const byte of encodeUtf8(value)) {
99
+ const char = String.fromCharCode(byte);
100
+ out += NAME_REGULAR.test(char) ? char : `#${byte.toString(16).padStart(2, "0").toUpperCase()}`;
101
+ }
98
102
  return out;
99
103
  };
100
104
  const encodeAsciiString = (value) => {
@@ -108,11 +112,7 @@ const encodeAsciiString = (value) => {
108
112
  }
109
113
  return `${out})`;
110
114
  };
111
- const encodeTextString = (value) => {
112
- let hex = "FEFF";
113
- for (let index = 0; index < value.length; index += 1) hex += value.charCodeAt(index).toString(16).padStart(4, "0").toUpperCase();
114
- return `<${hex}>`;
115
- };
115
+ const encodeTextString = (value) => `<FEFF${bytesToHex(encodeUtf16BE(value)).toUpperCase()}>`;
116
116
  const createByteSink = () => {
117
117
  const chunks = [];
118
118
  let length = 0;
@@ -122,9 +122,7 @@ const createByteSink = () => {
122
122
  };
123
123
  return {
124
124
  writeAscii: (text) => {
125
- const bytes = new Uint8Array(text.length);
126
- for (let index = 0; index < text.length; index += 1) bytes[index] = text.charCodeAt(index) & 255;
127
- writeBytes(bytes);
125
+ writeBytes(encodeBinaryString(text));
128
126
  },
129
127
  writeBytes,
130
128
  offset: () => length,
@@ -1,4 +1,5 @@
1
1
  import { getShaper } from "../shaping/shaper.js";
2
+ import { encodeUtf8 } from "../utils/textEncoding.js";
2
3
  import { collectResources, renderContentStream } from "./contentStream.js";
3
4
  import { prepareFonts } from "./fonts.js";
4
5
  import { decodeImage } from "./images.js";
@@ -288,7 +289,7 @@ const writePdf = async (list, options) => {
288
289
  fonts: byFontIndex
289
290
  });
290
291
  const resources = collectResources(parts);
291
- const contentRef = document.add(pdfFlateStream([], new TextEncoder().encode(renderContentStream(parts, resources))));
292
+ const contentRef = document.add(pdfFlateStream([], encodeUtf8(renderContentStream(parts, resources))));
292
293
  const annotationRefs = [];
293
294
  for (const link of page.links) {
294
295
  const annotation = buildLinkAnnotation({
@@ -13,11 +13,13 @@ import "../extensions/nodes/SdtExtension.js";
13
13
  import { canonicalFieldNode } from "../fieldRepresentation.js";
14
14
  import { resolveStateStory } from "../markupViewProjection.js";
15
15
  import { joinAtParagraphMark } from "../paragraphMarkJoin.js";
16
+ import { carryParagraphProperties, paragraphLeftAfter } from "../paragraphPropertyCarry.js";
16
17
  import { getDocumentNumbering } from "../plugins/documentNumbering.js";
17
18
  import { getDocumentStyleResolver } from "../plugins/documentStyleState.js";
18
19
  import { resolvedPreservedXmlAttrs } from "../preservedXmlReview.js";
19
20
  import { setParagraphAttrsWithRebasedRunFormatting } from "../rebaseParagraphRunFormatting.js";
20
21
  import { rejoinRunsAt } from "../rejoinRunCarriers.js";
22
+ import { commentPoint, resolvedCommentPoints } from "../replacedAnnotations.js";
21
23
  import { getFolioNodeRevisionCarriers, nodePropertyRevisionSites } from "../revisionCarriers.js";
22
24
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
23
25
  import { reconstructRejectedRunFormattingMarks, restoreHistoricalRunFormatting } from "../runPropertyChangeResolution.js";
@@ -100,6 +102,16 @@ function resolveChange(from, to, mode, revisionIds) {
100
102
  const removeKeptMarksInBulk = revisionSet === null && keepType !== void 0;
101
103
  const matchesRevision = (mark) => revisionSet === null || typeof mark.attrs["revisionId"] === "number" && revisionSet.has(mark.attrs["revisionId"]);
102
104
  if (dispatch) {
105
+ const points = mode === "accept" ? resolvedCommentPoints({
106
+ doc: state.doc,
107
+ retiresStructure: (node, position) => {
108
+ if (node.type.name === "paragraph") return collectPPrMarkOp(node, position, from, to, mode, revisionSet)?.action === "join";
109
+ if (!rangeCoversNode(from, to, position, node)) return false;
110
+ if (node.type.name === "tableRow") return collectTableRowStructuralOp(node, position, mode, revisionSet)?.action === "remove";
111
+ return collectTableCellStructuralOps(node, position, mode, revisionSet).some((op) => op.type === "membership" && op.action === "remove");
112
+ },
113
+ removes: (node, position) => position >= from && position + node.nodeSize <= to && node.marks.some((mark) => mark.type === removeType && matchesRevision(mark))
114
+ }) : /* @__PURE__ */ new Map();
103
115
  const tr = state.tr;
104
116
  tr.setMeta(JOINED_RUNS_RESTYLED_META, true);
105
117
  const deleteRanges = [];
@@ -110,6 +122,25 @@ function resolveChange(from, to, mode, revisionIds) {
110
122
  const tableCellStructuralOps = [];
111
123
  let removedSectionEndpointCount = 0;
112
124
  const removedSectionReferences = [];
125
+ const carryRestoredProperties = (position) => {
126
+ if (mode !== "reject" || revisionSet === null) return;
127
+ const source = tr.doc.nodeAt(position);
128
+ const mark = source ? expectParagraphAttrs(source).pPrMark : null;
129
+ if (!source || mark?.kind !== "del" && mark?.kind !== "moveFrom" || revisionSet.has(mark.info.id)) return;
130
+ const survivor = paragraphLeftAfter({
131
+ doc: tr.doc,
132
+ paragraphPos: position
133
+ });
134
+ if (survivor === null) return;
135
+ carryParagraphProperties({
136
+ tr,
137
+ position: survivor,
138
+ source,
139
+ styleResolver,
140
+ numbering,
141
+ revision: mark.info
142
+ });
143
+ };
113
144
  state.doc.nodesBetween(from, to, (node, pos) => {
114
145
  if (node.type.name === "paragraph") {
115
146
  const op = collectPPrMarkOp(node, pos, from, to, mode, revisionSet);
@@ -122,18 +153,21 @@ function resolveChange(from, to, mode, revisionIds) {
122
153
  styleResolver,
123
154
  numbering
124
155
  });
125
- if (nextAttrs) if (nextAttrs["styleId"] !== node.attrs["styleId"] && styleResolver) setParagraphAttrsWithRebasedRunFormatting({
126
- nextAttrs,
127
- paragraphPosition: pos,
128
- styleResolver,
129
- tr,
130
- ...mode === "reject" && { shouldRebase: (inline) => {
131
- const deleted = inline.marks.some(({ type }) => type.name === "deletion");
132
- const direct = inline.marks.some(({ type }) => type.name === "runFormattingOverride");
133
- return !deleted || !direct;
134
- } }
135
- });
136
- else tr.setNodeMarkup(pos, void 0, nextAttrs);
156
+ if (nextAttrs) {
157
+ if (nextAttrs["styleId"] !== node.attrs["styleId"] && styleResolver) setParagraphAttrsWithRebasedRunFormatting({
158
+ nextAttrs,
159
+ paragraphPosition: pos,
160
+ styleResolver,
161
+ tr,
162
+ ...mode === "reject" && { shouldRebase: (inline) => {
163
+ const deleted = inline.marks.some(({ type }) => type.name === "deletion");
164
+ const direct = inline.marks.some(({ type }) => type.name === "runFormattingOverride");
165
+ return !deleted || !direct;
166
+ } }
167
+ });
168
+ else tr.setNodeMarkup(pos, void 0, nextAttrs);
169
+ carryRestoredProperties(pos);
170
+ }
137
171
  return true;
138
172
  }
139
173
  if (node.type.name === "tableRow" && rangeCoversNode(from, to, pos, node)) {
@@ -388,7 +422,10 @@ function resolveChange(from, to, mode, revisionIds) {
388
422
  removedSectionEndpointCount++;
389
423
  removedSectionReferences.push(...sectionReferencesOf(paragraph));
390
424
  }
391
- } catch {}
425
+ } catch {
426
+ continue;
427
+ }
428
+ if (op.markWasAdded) carryRestoredProperties(mappedPos);
392
429
  }
393
430
  if (removedSectionEndpointCount > 0) markTrackedSectionEndpointRemoval(tr, {
394
431
  sourceDoc: state.doc,
@@ -416,6 +453,7 @@ function resolveChange(from, to, mode, revisionIds) {
416
453
  if (field.type === node.type) tr.setNodeMarkup(position, void 0, node.attrs, node.marks);
417
454
  else tr.replaceWith(position, position + field.nodeSize, node);
418
455
  }
456
+ for (const [id, position] of [...points].toReversed()) tr.insert(tr.mapping.map(position, -1), commentPoint(tr.doc.type.schema, id));
419
457
  if (tr.steps.length > 0) dispatch(tr);
420
458
  }
421
459
  return true;
@@ -0,0 +1,40 @@
1
+ import { Node } from "prosemirror-model";
2
+ //#region src/prosemirror/commentRangeInlineAtoms.d.ts
3
+ /**
4
+ * Whether an inline atom extends a comment range it sits in.
5
+ *
6
+ * - `extends`: a comment mark on the atom keeps the comment's range alive.
7
+ * - `inert`: the atom is invisible to the reader, so a comment mark on it would
8
+ * be a ghost range nobody can see or select. Comment integrity strips the mark
9
+ * and never counts the atom as range content.
10
+ *
11
+ * This is separate from revision ownership: a cached rendered page break must
12
+ * stay inside its tracked-change wrapper, but it can never hold a comment on
13
+ * its own.
14
+ */
15
+ type CommentRangeInlineAtomParticipation = "extends" | "inert";
16
+ /** Keyed by the tracked-run atom set, which the fixed-point test binds to the schema. */
17
+ declare const COMMENT_RANGE_INLINE_ATOM_PARTICIPATION: Readonly<{
18
+ readonly bookmarkBoundary: "extends";
19
+ readonly commentReference: "inert";
20
+ readonly field: "extends";
21
+ readonly hardBreak: "extends";
22
+ readonly image: "extends";
23
+ readonly math: "extends";
24
+ readonly moveRangeBoundary: "extends";
25
+ readonly noteMarker: "extends";
26
+ readonly pageBreakRun: "extends";
27
+ readonly preservedXml: "extends";
28
+ readonly rangeAnchor: "inert";
29
+ readonly renderedPageBreak: "inert";
30
+ readonly shape: "extends";
31
+ readonly structuredField: "extends";
32
+ readonly symbol: "extends";
33
+ readonly tab: "extends";
34
+ readonly text: "extends";
35
+ readonly textBoxAnchor: "extends";
36
+ }>;
37
+ /** Whether a node is an inline atom that must never carry or extend a comment range. */
38
+ declare const isCommentRangeInertAtom: (node: Node) => boolean;
39
+ //#endregion
40
+ export { COMMENT_RANGE_INLINE_ATOM_PARTICIPATION, CommentRangeInlineAtomParticipation, isCommentRangeInertAtom };
@@ -0,0 +1,33 @@
1
+ import { panic } from "better-result";
2
+ //#region src/prosemirror/commentRangeInlineAtoms.ts
3
+ /** Keyed by the tracked-run atom set, which the fixed-point test binds to the schema. */
4
+ const COMMENT_RANGE_INLINE_ATOM_PARTICIPATION = Object.freeze({
5
+ bookmarkBoundary: "extends",
6
+ commentReference: "inert",
7
+ field: "extends",
8
+ hardBreak: "extends",
9
+ image: "extends",
10
+ math: "extends",
11
+ moveRangeBoundary: "extends",
12
+ noteMarker: "extends",
13
+ pageBreakRun: "extends",
14
+ preservedXml: "extends",
15
+ rangeAnchor: "inert",
16
+ renderedPageBreak: "inert",
17
+ shape: "extends",
18
+ structuredField: "extends",
19
+ symbol: "extends",
20
+ tab: "extends",
21
+ text: "extends",
22
+ textBoxAnchor: "extends"
23
+ });
24
+ const participationByName = new Map(Object.entries(COMMENT_RANGE_INLINE_ATOM_PARTICIPATION));
25
+ /** Whether a node is an inline atom that must never carry or extend a comment range. */
26
+ const isCommentRangeInertAtom = (node) => {
27
+ if (!node.isInline || !node.isAtom) return false;
28
+ const participation = participationByName.get(node.type.name);
29
+ if (!participation) panic(`Inline atom ${JSON.stringify(node.type.name)} has no comment-range participation`);
30
+ return participation === "inert";
31
+ };
32
+ //#endregion
33
+ export { COMMENT_RANGE_INLINE_ATOM_PARTICIPATION, isCommentRangeInertAtom };
@@ -2,15 +2,15 @@ import { Node, ResolvedPos } from "prosemirror-model";
2
2
  //#region src/prosemirror/containerFinalParagraph.d.ts
3
3
  /**
4
4
  * Whether the paragraph starting at `at` is the one its container ENDS with,
5
- * which is the one place a paragraph mark cannot carry a revision.
5
+ * where a paragraph break cannot join into a following paragraph.
6
6
  *
7
- * A deleted mark asks a reader to join the paragraph it ends with the one
8
- * after it, and an inserted mark says that break was added, so rejecting it
9
- * closes the paragraph back over the next one. A body, a table cell, a header
10
- * or footer, a note and a text box each end with a paragraph that nothing
11
- * follows, so neither direction can be resolved there: a consumer is left with
12
- * a revision it can only ignore, and the redline no longer resolves to the two
13
- * documents it was written from.
7
+ * Added boundaries normally rotate onto a preceding paragraph, so rejecting
8
+ * them joins back into this one. If an occupied boundary or a table prevents
9
+ * rotation, the final inserted paragraph must retain its own revision: the
10
+ * resolver removes the whole paragraph after rejecting its inserted content.
11
+ * A terminal deletion, by contrast, clears without removing the container's
12
+ * required ending. Bodies, table cells, headers, footers, notes and text boxes
13
+ * share these rules.
14
14
  *
15
15
  * A paragraph before a TABLE is not one of those: the table is a following
16
16
  * sibling, so the paragraph does not end anything and carries its own mark
@@ -14,15 +14,15 @@ const nothingFollowsIn = (parent, fromIndex, paragraphTypeName) => {
14
14
  };
15
15
  /**
16
16
  * Whether the paragraph starting at `at` is the one its container ENDS with,
17
- * which is the one place a paragraph mark cannot carry a revision.
17
+ * where a paragraph break cannot join into a following paragraph.
18
18
  *
19
- * A deleted mark asks a reader to join the paragraph it ends with the one
20
- * after it, and an inserted mark says that break was added, so rejecting it
21
- * closes the paragraph back over the next one. A body, a table cell, a header
22
- * or footer, a note and a text box each end with a paragraph that nothing
23
- * follows, so neither direction can be resolved there: a consumer is left with
24
- * a revision it can only ignore, and the redline no longer resolves to the two
25
- * documents it was written from.
19
+ * Added boundaries normally rotate onto a preceding paragraph, so rejecting
20
+ * them joins back into this one. If an occupied boundary or a table prevents
21
+ * rotation, the final inserted paragraph must retain its own revision: the
22
+ * resolver removes the whole paragraph after rejecting its inserted content.
23
+ * A terminal deletion, by contrast, clears without removing the container's
24
+ * required ending. Bodies, table cells, headers, footers, notes and text boxes
25
+ * share these rules.
26
26
  *
27
27
  * A paragraph before a TABLE is not one of those: the table is a following
28
28
  * sibling, so the paragraph does not end anything and carries its own mark
@@ -1,5 +1,6 @@
1
1
  import { joinCommentRangesAcrossParagraphs } from "../../docx/commentRangeJoin.js";
2
2
  import { completeCommentReferences } from "../../docx/commentReferenceCompletion.js";
3
+ import { buildSections } from "../../docx/documentParser.js";
3
4
  import { normalizeFoldedListNumbers } from "../../docx/foldedListNumberFields.js";
4
5
  import { getDocumentSourceStyles } from "../../docx/headerFooterVerbatim.js";
5
6
  import { EDITED_PREVIEW_FINGERPRINT, imageRawXmlFingerprint } from "../../docx/imageRawXml.js";
@@ -16,6 +17,7 @@ import { createStyleEngine } from "../../style-engine/styleEngine.js";
16
17
  import { normalizeShapeTextAnchor, presetDashForOutlineAttr } from "../../types/documentEnumValues.js";
17
18
  import { presetDashForCssBorderStyle } from "../../utils/borderCss.js";
18
19
  import { canonicalJson } from "../../utils/canonicalJson.js";
20
+ import { getFindParagraphText } from "../../utils/findReplace.js";
19
21
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
20
22
  import { emuToPixels, emuToStrokePixels, pixelsToEmu } from "../../utils/units.js";
21
23
  import { unchangedAlternateContentXml } from "../alternateContentAttrs.js";
@@ -397,7 +399,7 @@ function fromProseDoc(pmDoc, baseDocument, { stylesheetSource, reuse = "none" })
397
399
  const documentBody = { content: blocks };
398
400
  if (baseDocument?.package.document.background) documentBody.background = baseDocument.package.document.background;
399
401
  if (baseDocument?.package.document.finalSectionProperties) documentBody.finalSectionProperties = baseDocument.package.document.finalSectionProperties;
400
- if (baseDocument?.package.document.sections) documentBody.sections = baseDocument.package.document.sections;
402
+ if (baseDocument?.package.document.sections) documentBody.sections = buildSections(blocks, documentBody.finalSectionProperties);
401
403
  if (baseDocument?.package.document.comments) documentBody.comments = baseDocument.package.document.comments;
402
404
  const numbering = completeNumberingForDoc(baseDocument?.package.numbering, pmDoc);
403
405
  if (baseDocument) {
@@ -1582,12 +1584,16 @@ function extractParagraphContent(originalParagraph, _documentCounts, emptyHyperl
1582
1584
  };
1583
1585
  const appendDirectRun = (node, run, coalescePlainText = false) => {
1584
1586
  if (currentHyperlink) flushCurrentInline();
1585
- const marksKey = getMarksKey(node.marks);
1587
+ const marksKey = getMarksKey(node.marks.filter((mark) => {
1588
+ if (mark.type.name !== "runIdentity") return true;
1589
+ const { emptyFormatting, preservedAttributes, preserved } = expectRunIdentityMarkAttrs(mark);
1590
+ return emptyFormatting !== true || (preservedAttributes?.length ?? 0) > 0 || (preserved?.children?.length ?? 0) > 0;
1591
+ }));
1586
1592
  const ownerId = runIdentityId(node);
1587
1593
  const currentOwnerId = currentRun ? sourceRunOwners.get(currentRun) : void 0;
1588
1594
  const currentRunIsPlainText = currentRun?.content.every((runContent) => runContent.type === "text");
1589
1595
  const joinsOwnedSourceRun = ownerId !== void 0 && ownerId === currentOwnerId && currentRun !== null && runsShareProperties(currentRun, run);
1590
- const joinsOrdinaryText = coalescePlainText && ownerId === void 0 && currentOwnerId === void 0 && currentRunIsPlainText === true && currentMarksKey === marksKey;
1596
+ const joinsOrdinaryText = coalescePlainText && currentRun !== null && runsShareProperties(currentRun, run) && currentRunIsPlainText === true && currentMarksKey === marksKey;
1591
1597
  if (currentRun && (joinsOwnedSourceRun || joinsOrdinaryText)) {
1592
1598
  if (joinsOrdinaryText) {
1593
1599
  for (const runContent of run.content) if (runContent.type === "text") appendTextToRun(currentRun, runContent.text);
@@ -2610,7 +2616,10 @@ const reconcileEmptyFieldResultRuns = (runs, displayRun) => {
2610
2616
  return reconciled;
2611
2617
  };
2612
2618
  const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
2613
- let currentText = "";
2619
+ const currentText = getFindParagraphText({
2620
+ type: "paragraph",
2621
+ content
2622
+ });
2614
2623
  const visitRunsIn = (items, visit) => {
2615
2624
  for (const child of items) {
2616
2625
  if (child.type === "run") {
@@ -2623,9 +2632,6 @@ const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
2623
2632
  const visitRuns = (visit) => {
2624
2633
  visitRunsIn(content, visit);
2625
2634
  };
2626
- visitRuns((run) => {
2627
- for (const runContent of run.content) if (runContent.type === "text") currentText += runContent.text;
2628
- });
2629
2635
  if (currentText === displayText) return content;
2630
2636
  let replacedText = false;
2631
2637
  visitRuns((run) => {
@@ -3319,7 +3325,7 @@ function tableCellAttrsToFormatting(attrs) {
3319
3325
  if (attrs.margins && !sameResolvedValue(attrs.margins, attrs._resolvedMargins)) result.margins = buildCellMarginsFromAttrs(attrs.margins);
3320
3326
  if (attrs.textDirection !== (orig.textDirection ?? void 0)) if (attrs.textDirection) result.textDirection = attrs.textDirection;
3321
3327
  else delete result.textDirection;
3322
- return result;
3328
+ return Object.keys(result).length > 0 ? result : void 0;
3323
3329
  }
3324
3330
  const authoredWidth = attrs._authoredWidth;
3325
3331
  if (!(attrs.colspan > 1 || attrs.rowspan > 1 || authoredWidth !== void 0 || attrs.verticalAlign || backgroundChanged || attrs.borders || attrs.margins || attrs.textDirection)) return;
@@ -31,6 +31,7 @@ import { MOVE_RANGE_BOUNDARY_NODE_NAME } from "../extensions/nodes/MoveRangeBoun
31
31
  import { RANGE_ANCHOR_NODE_NAME } from "../extensions/nodes/RangeAnchorExtension.js";
32
32
  import "../extensions/nodes/SdtExtension.js";
33
33
  import { fieldRequiresStructuredContent } from "../fieldRepresentation.js";
34
+ import { getInlineDisplayText } from "../inlineDisplayText.js";
34
35
  import { inlineWrapperLayer } from "../inlineWrapperStack.js";
35
36
  import { CLEARED_LIST_RENDERING_ATTRS } from "../listMarker.js";
36
37
  import { listRenderingFor } from "../listRendering.js";
@@ -42,7 +43,7 @@ import { directionFromBidi } from "../paragraphDirection.js";
42
43
  import { paragraphIndentationFromFormatting } from "../paragraphIndentation.js";
43
44
  import { lineSpacingProvenanceFromSpacing } from "../paragraphSpacing.js";
44
45
  import { RUN_IDENTITY_MARK_NAME, hasRunIdentityPayload, runIdentityAttrs } from "../runIdentity.js";
45
- import { continueRunIdentitiesAcrossRevisions } from "../runIdentityAcrossRevisions.js";
46
+ import { continueSourceCarrierIdentitiesAcrossRevisions } from "../runIdentityAcrossRevisions.js";
46
47
  import { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, isParagraphMarkSuppressionEligible, resolveParagraphInheritedRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting } from "../runStyleFormatting.js";
47
48
  import { schema } from "../schema/index.js";
48
49
  import { PRESERVED_XML_LEVELS } from "../schema/nodes.js";
@@ -112,7 +113,7 @@ const runIdentityMark = (run, nextRunIdentityId) => {
112
113
  const payload = {
113
114
  preservedAttributes: run.preservedAttributes,
114
115
  preserved: run.formatting?.preserved,
115
- emptyFormatting: run.formatting !== void 0 && Object.keys(run.formatting).length === 0 && (run.propertyChanges?.length ?? 0) === 0 ? true : void 0
116
+ emptyFormatting: run.formatting !== void 0 && Object.keys(run.formatting).length === 0 ? true : void 0
116
117
  };
117
118
  if (!runHasPageBreakContent(run) && !runHasMixedContent(run) && !hasRunIdentityPayload(payload)) return null;
118
119
  return schema.mark(RUN_IDENTITY_MARK_NAME, runIdentityAttrs(nextRunIdentityId(), payload));
@@ -676,7 +677,7 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
676
677
  if (emptyHyperlinks) attrs._emptyHyperlinks = emptyHyperlinks;
677
678
  return createProseParagraphWithPropertySource(schema.nodes["paragraph"], paragraph, {
678
679
  attrs,
679
- content: continueRunIdentitiesAcrossRevisions(inlineNodes)
680
+ content: continueSourceCarrierIdentitiesAcrossRevisions(inlineNodes)
680
681
  });
681
682
  }
682
683
  /**
@@ -1256,7 +1257,7 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
1256
1257
  attrs.margins = effectiveFormatting.margins;
1257
1258
  attrs._resolvedMargins = effectiveFormatting.margins;
1258
1259
  }
1259
- if (formatting) attrs._originalFormatting = formatting;
1260
+ attrs._originalFormatting = formatting ?? {};
1260
1261
  if (cell.propertyChanges && cell.propertyChanges.length > 0) attrs.tcPrChange = [...cell.propertyChanges];
1261
1262
  if (cell.contentControls && cell.contentControls.length > 0) attrs.contentControls = cell.contentControls;
1262
1263
  if (cell.carrierStack?.length) attrs.carrierStack = cell.carrierStack;
@@ -1368,27 +1369,24 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
1368
1369
  });
1369
1370
  }
1370
1371
  function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyperlinkInstanceIndex, runScope, textBoxAnchors }) {
1371
- let displayText = "";
1372
1372
  let fieldFormatting;
1373
1373
  let fieldPropertyChanges;
1374
1374
  const inlineNodes = [];
1375
- const hasStructuredSourceContent = fieldResultHasPageBreakContent(field) || field.type === "simpleField" && field.content.some((content) => content.type === "hyperlink" || content.type === "preservedInline" || content.type === "inlineWrapper" || content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo");
1375
+ const hasPageBreakContent = fieldResultHasPageBreakContent(field);
1376
+ const resultRuns = field.type === "complexField" ? field.fieldResult : field.content.flatMap((content) => content.type === "run" ? [content] : []);
1376
1377
  const appendRun = (run, into) => {
1377
- for (const content of run.content) if (content.type === "text") displayText += content.text;
1378
1378
  fieldFormatting ??= run.formatting;
1379
1379
  fieldPropertyChanges ??= run.propertyChanges;
1380
- if (field.type !== "simpleField" && !hasStructuredSourceContent) return;
1381
1380
  into.push(...convertRun(run, getInheritedRunFormatting(run.formatting, field.fieldType), runScope, styleResolver, textBoxAnchors));
1382
1381
  };
1383
- const collectDisplay = (items) => {
1382
+ const collectFormatting = (items) => {
1384
1383
  for (const item of items) switch (item.type) {
1385
1384
  case "run":
1386
- for (const child of item.content) if (child.type === "text") displayText += child.text;
1387
1385
  fieldFormatting ??= item.formatting;
1388
1386
  fieldPropertyChanges ??= item.propertyChanges;
1389
1387
  break;
1390
1388
  case "hyperlink":
1391
- collectDisplay(item.children);
1389
+ collectFormatting(item.children);
1392
1390
  break;
1393
1391
  case "inlineWrapper":
1394
1392
  case "inlineSdt":
@@ -1396,20 +1394,16 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1396
1394
  case "deletion":
1397
1395
  case "moveFrom":
1398
1396
  case "moveTo":
1399
- collectDisplay(item.content);
1397
+ collectFormatting(item.content);
1400
1398
  break;
1401
1399
  case "simpleField":
1402
- collectDisplay(item.content);
1400
+ collectFormatting(item.content);
1403
1401
  break;
1404
1402
  case "complexField":
1405
- collectDisplay(item.fieldResult);
1403
+ collectFormatting(item.fieldResult);
1406
1404
  break;
1407
1405
  case "preservedInline":
1408
- displayText += item.text;
1409
- break;
1410
- case "mathEquation":
1411
- displayText += item.plainText ?? "";
1412
- break;
1406
+ case "mathEquation": break;
1413
1407
  case "bookmarkStart":
1414
1408
  case "bookmarkEnd":
1415
1409
  case "commentRangeStart":
@@ -1433,7 +1427,6 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1433
1427
  break;
1434
1428
  case "hyperlink":
1435
1429
  for (const { content: child } of withInlineWrapperStacks(content.children)) if (child.type === "run") {
1436
- for (const runContent of child.content) if (runContent.type === "text") displayText += runContent.text;
1437
1430
  fieldFormatting ??= child.formatting;
1438
1431
  fieldPropertyChanges ??= child.propertyChanges;
1439
1432
  }
@@ -1449,7 +1442,7 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1449
1442
  case "deletion":
1450
1443
  case "moveFrom":
1451
1444
  case "moveTo":
1452
- collectDisplay(content.content);
1445
+ collectFormatting(content.content);
1453
1446
  itemNodes.push(...convertTrackedChange(content, content.type === "insertion" || content.type === "moveTo" ? "insertion" : "deletion", nextHyperlinkInstanceIndex, runScope, {
1454
1447
  current: getInheritedRunFormatting,
1455
1448
  historical: getInheritedRunFormatting
@@ -1468,8 +1461,8 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
1468
1461
  styleResolver,
1469
1462
  createMark: runScope.createMark
1470
1463
  });
1471
- const createStructuredField = fieldRequiresStructuredContent(inlineNodes);
1472
- const resultRuns = field.type === "simpleField" ? field.content.flatMap((content) => content.type === "run" ? [content] : []) : field.fieldResult;
1464
+ const createStructuredField = (field.type === "simpleField" || hasPageBreakContent) && fieldRequiresStructuredContent(inlineNodes);
1465
+ const displayText = getInlineDisplayText(schema.node("paragraph", null, inlineNodes));
1473
1466
  const emptyResultRuns = displayText === "" && resultRuns.length > 0 && (field.type === "complexField" || field.content.length === resultRuns.length) && resultRuns.every((run) => run.content.every((item) => item.type === "text" && item.text === "")) ? resultRuns : void 0;
1474
1467
  const resultPropertyChanges = emptyResultRuns ? emptyFieldRunPropertyChanges(emptyResultRuns) : fieldPropertyChanges;
1475
1468
  if (!createStructuredField && resultPropertyChanges && resultPropertyChanges.length > 0) marks.push(schema.mark("runPropertyChange", { changes: [...resultPropertyChanges] }));