@stll/folio-core 0.37.5 → 0.39.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 (144) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/controller/layoutPipeline.js +4 -0
  42. package/dist/display-list/build/buildContext.d.ts +3 -1
  43. package/dist/display-list/build/buildDisplayList.d.ts +4 -1
  44. package/dist/display-list/build/buildDisplayList.js +38 -4
  45. package/dist/display-list/build/commentAnnotations.d.ts +10 -0
  46. package/dist/display-list/build/commentAnnotations.js +17 -0
  47. package/dist/display-list/build/paragraphPrimitives.js +39 -1
  48. package/dist/display-list/primitives.d.ts +1 -1
  49. package/dist/display-list/types.d.ts +16 -1
  50. package/dist/document-operations.d.ts +5 -3
  51. package/dist/document-operations.js +112 -5
  52. package/dist/docx/blockContentParser.js +14 -2
  53. package/dist/docx/diagramPreview.d.ts +7 -0
  54. package/dist/docx/diagramPreview.js +191 -0
  55. package/dist/docx/drawingIdNormalization.js +43 -2
  56. package/dist/docx/drawingRelationships.d.ts +10 -0
  57. package/dist/docx/drawingRelationships.js +28 -0
  58. package/dist/docx/fieldParser.js +1 -1
  59. package/dist/docx/hyperlinkParser.js +3 -5
  60. package/dist/docx/imageRawXml.d.ts +8 -0
  61. package/dist/docx/imageRawXml.js +13 -0
  62. package/dist/docx/newImage.d.ts +2 -2
  63. package/dist/docx/newImage.js +3 -3
  64. package/dist/docx/numberingParser.js +30 -27
  65. package/dist/docx/paragraphParser.js +11 -4
  66. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  67. package/dist/docx/parser.js +13 -0
  68. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  69. package/dist/docx/removeHeaderFooterParts.js +87 -0
  70. package/dist/docx/rezip.d.ts +3 -1
  71. package/dist/docx/rezip.js +143 -41
  72. package/dist/docx/runParser.js +24 -5
  73. package/dist/docx/sectionParser.js +3 -0
  74. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  75. package/dist/docx/sectionReferenceHistory.js +59 -0
  76. package/dist/docx/selectiveSave.js +11 -31
  77. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  78. package/dist/docx/selectiveXmlPatch.js +62 -2
  79. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  80. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  81. package/dist/docx/serializer/partNamespaces.js +4 -0
  82. package/dist/docx/serializer/runSerializer.js +5 -2
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  84. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  85. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  86. package/dist/docx/tableParser.js +21 -11
  87. package/dist/docx/textBoxParser.js +25 -5
  88. package/dist/docx/xmlParser.d.ts +10 -1
  89. package/dist/docx/xmlParser.js +23 -1
  90. package/dist/export-pdf.js +2 -0
  91. package/dist/headless-layout.js +1 -0
  92. package/dist/index.d.ts +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  95. package/dist/internal/compare/inline-presentation.js +222 -0
  96. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  97. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  98. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  99. package/dist/internal/sectionReferenceResolution.js +72 -0
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  101. package/dist/layout-engine/index.js +5 -5
  102. package/dist/layout-engine/sectionVerticalAlignment.d.ts +7 -0
  103. package/dist/layout-engine/sectionVerticalAlignment.js +52 -0
  104. package/dist/layout-engine/types.d.ts +3 -1
  105. package/dist/markdown/renderRuns.js +1 -0
  106. package/dist/markdown/renderTable.js +1 -0
  107. package/dist/pdf/writePdf.js +37 -2
  108. package/dist/prosemirror/attrs/index.js +55 -1
  109. package/dist/prosemirror/commands/comments.d.ts +11 -1
  110. package/dist/prosemirror/commands/comments.js +68 -6
  111. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  112. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  113. package/dist/prosemirror/conversion/fromProseDoc.js +17 -8
  114. package/dist/prosemirror/conversion/toProseDoc.js +18 -57
  115. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  116. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  117. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  118. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +39 -1
  119. package/dist/prosemirror/imageCommit.js +7 -9
  120. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  121. package/dist/prosemirror/paragraphIndentation.js +72 -0
  122. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  123. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  124. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  125. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  126. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  127. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  128. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  129. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  130. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  131. package/dist/prosemirror/runStyleFormatting.js +76 -2
  132. package/dist/prosemirror/schema/marks.d.ts +3 -1
  133. package/dist/prosemirror/schema/marks.js +2 -0
  134. package/dist/prosemirror/schema/nodes.d.ts +5 -0
  135. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  136. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  137. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  138. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  139. package/dist/redline.js +15 -2
  140. package/dist/server.d.ts +2 -2
  141. package/dist/server.js +2 -2
  142. package/dist/version-comparison.d.ts +2 -0
  143. package/dist/version-comparison.js +7 -2
  144. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { formattingEquals } from "../docx/runConsolidator.js";
1
2
  import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
2
3
  import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
3
4
  import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
@@ -5,12 +6,13 @@ import { requestDeterministicParaIds } from "../prosemirror/extensions/features/
5
6
  import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
6
7
  import { CLEARED_LIST_RENDERING_ATTRS } from "../prosemirror/listMarker.js";
7
8
  import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
9
+ import { directParagraphIndentation, paragraphIndentationAttrPatch, paragraphIndentationEqual, paragraphIndentationFromFormatting, withDirectParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
8
10
  import { directParagraphSpacing, paragraphSpacingAttrPatch, paragraphSpacingEqual, paragraphSpacingFromFormatting, withDirectParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
9
11
  import { getDocumentNumbering } from "../prosemirror/plugins/documentNumbering.js";
10
12
  import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
11
- import { selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
13
+ import { applyMarksToRunFormattingRepresentation, runFormattingInlineControlNodeName, selectRunFormattingCarrierRepresentations } from "../prosemirror/runFormattingInlineCarriers.js";
12
14
  import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
13
- import { paragraphRunStyleContextAt } from "../prosemirror/runStyleFormatting.js";
15
+ import { paragraphRunStyleContext, paragraphRunStyleContextAt } from "../prosemirror/runStyleFormatting.js";
14
16
  import { listLevelAttrPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
15
17
  import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
16
18
  import { getFolioParaIdFromBlockId } from "../types/block-id.js";
@@ -25,7 +27,7 @@ import { findEnclosingTableCell, findEnclosingTableRow, findOutermostTableBounda
25
27
  import { tableFromTemplate } from "./table-template.js";
26
28
  import { wordDiffSessionFromOptions } from "./word-diff.js";
27
29
  import { panic } from "better-result";
28
- import { Mark } from "prosemirror-model";
30
+ import { Fragment } from "prosemirror-model";
29
31
  import { TableMap } from "prosemirror-tables";
30
32
  import { canJoin, canSplit } from "prosemirror-transform";
31
33
  //#region src/ai-edits/apply.ts
@@ -149,7 +151,9 @@ const resolveReplaceBlockImpact = ({ changesText, changesStyle }) => {
149
151
  const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyle, numbering = null }) => {
150
152
  const attrs = expectParagraphAttrs(node);
151
153
  const currentDirectAlignment = directParagraphAlignment(attrs);
154
+ const currentDirectIndentation = directParagraphIndentation(attrs);
152
155
  const currentDirectSpacing = directParagraphSpacing(attrs);
156
+ const resolvedIndentationFromStyle = paragraphIndentationFromFormatting(resolvedFormattingFromStyle);
153
157
  const resolvedSpacingFromStyle = paragraphSpacingFromFormatting(resolvedFormattingFromStyle);
154
158
  const patch = {};
155
159
  let originalFormatting = attrs._originalFormatting === void 0 || attrs._originalFormatting === null ? void 0 : { ...attrs._originalFormatting };
@@ -164,13 +168,30 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
164
168
  direct: currentDirectSpacing,
165
169
  inherited: resolvedSpacingFromStyle
166
170
  }));
171
+ if (properties.indentation === void 0) Object.assign(patch, paragraphIndentationAttrPatch({
172
+ direct: currentDirectIndentation,
173
+ inherited: resolvedIndentationFromStyle
174
+ }));
167
175
  originalFormatting ??= {};
168
176
  if (nextStyleId === null) Reflect.deleteProperty(originalFormatting, "styleId");
169
177
  else originalFormatting.styleId = nextStyleId;
170
178
  if (currentDirectAlignment !== void 0) originalFormatting.alignment = currentDirectAlignment;
171
179
  originalFormattingChanged = true;
172
180
  }
173
- if (properties.listLevel !== void 0) {
181
+ if (properties.numbering !== void 0) if (properties.numbering === null) {
182
+ patch["numPr"] = null;
183
+ Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
184
+ } else {
185
+ Object.assign(patch, listLevelAttrPatch(attrs, {
186
+ numId: properties.numbering.numId,
187
+ ilvl: properties.numbering.level
188
+ }, numbering));
189
+ if (properties.listLevel === null) {
190
+ patch["numPr"] = { numId: properties.numbering.numId };
191
+ patch["numPrFromStyle"] = null;
192
+ }
193
+ }
194
+ else if (properties.listLevel !== void 0) {
174
195
  const numPr = node.attrs["numPr"];
175
196
  if (properties.listLevel === null) {
176
197
  patch["numPr"] = null;
@@ -195,6 +216,15 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
195
216
  patch["alignment"] = properties.alignment ?? alignmentFromStyle ?? null;
196
217
  originalFormattingChanged = true;
197
218
  }
219
+ if (properties.indentation !== void 0 && (styleChanged || !paragraphIndentationEqual(currentDirectIndentation, properties.indentation))) {
220
+ const directIndentation = properties.indentation ?? void 0;
221
+ originalFormatting = withDirectParagraphIndentation(originalFormatting, directIndentation);
222
+ Object.assign(patch, paragraphIndentationAttrPatch({
223
+ direct: directIndentation,
224
+ inherited: resolvedIndentationFromStyle
225
+ }));
226
+ originalFormattingChanged = true;
227
+ }
198
228
  if (properties.spacing !== void 0 && (styleChanged || !paragraphSpacingEqual(currentDirectSpacing, properties.spacing))) {
199
229
  const directSpacing = properties.spacing ?? void 0;
200
230
  originalFormatting = withDirectParagraphSpacing(originalFormatting, directSpacing);
@@ -271,6 +301,63 @@ const resolveFormattingFromStyle = ({ attrs, styleId, styleResolver }) => {
271
301
  if (attrs.alignmentFromStyle !== void 0) fallback.alignment = attrs.alignmentFromStyle;
272
302
  return Object.keys(fallback).length > 0 ? fallback : void 0;
273
303
  };
304
+ const preserveRunFormattingAcrossTrackedStyleChange = ({ allocateRevisionInfo, nextParagraphAttrs, paragraphPosition, styleResolver, tr }) => {
305
+ const propertyChangeType = tr.doc.type.schema.marks["runPropertyChange"];
306
+ if (!propertyChangeType) return {
307
+ tr,
308
+ revisionIds: []
309
+ };
310
+ const paragraph = tr.doc.nodeAt(paragraphPosition);
311
+ if (!paragraph || paragraph.type.name !== "paragraph") return {
312
+ tr,
313
+ revisionIds: []
314
+ };
315
+ const previousContext = paragraphRunStyleContext(paragraph, styleResolver);
316
+ const nextContext = paragraphRunStyleContext(paragraph.type.create(nextParagraphAttrs, paragraph.content, paragraph.marks), styleResolver);
317
+ const representations = selectRunFormattingCarrierRepresentations({
318
+ doc: tr.doc,
319
+ from: paragraphPosition + 1,
320
+ to: paragraphPosition + paragraph.nodeSize - 1
321
+ });
322
+ const revisionIds = [];
323
+ for (const representation of representations) {
324
+ if (!representation.node.marks.some(({ type }) => type.name === "deletion")) continue;
325
+ const existingChange = representation.node.marks.find((mark) => mark.type === propertyChangeType);
326
+ if (existingChange && expectRunPropertyChangeMarkAttrs(existingChange).changes.length > 0) continue;
327
+ const previousFormatting = readAuthoredRunFormatting({
328
+ context: previousContext,
329
+ marks: representation.node.marks,
330
+ styleResolver
331
+ });
332
+ if (formattingEquals(previousFormatting, readAuthoredRunFormatting({
333
+ context: nextContext,
334
+ marks: representation.node.marks,
335
+ styleResolver
336
+ }))) continue;
337
+ const info = allocateRevisionInfo();
338
+ const suggestionAttrs = info.provenance === "suggested" && info.suggestionId !== void 0 && info.suggestionId !== null ? {
339
+ provenance: "suggested",
340
+ suggestionId: info.suggestionId
341
+ } : {};
342
+ applyMarksToRunFormattingRepresentation({
343
+ tr,
344
+ representation,
345
+ marks: propertyChangeType.create({
346
+ changes: [{
347
+ type: "runPropertyChange",
348
+ info,
349
+ ...Object.keys(previousFormatting).length > 0 && { previousFormatting }
350
+ }],
351
+ ...suggestionAttrs
352
+ }).addToSet(representation.node.marks)
353
+ });
354
+ revisionIds.push(info.id);
355
+ }
356
+ return {
357
+ tr,
358
+ revisionIds
359
+ };
360
+ };
274
361
  /**
275
362
  * Apply one paragraph-property patch to a live node. A tracked patch stores
276
363
  * the complete previous pPr: a partial snapshot cannot distinguish a property
@@ -296,7 +383,8 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
296
383
  if (patch === null) return {
297
384
  tr,
298
385
  changed: false,
299
- revisionId: null
386
+ revisionId: null,
387
+ revisionIds: []
300
388
  };
301
389
  const changeInfo = revisionInfo?.();
302
390
  const change = changeInfo ? {
@@ -304,27 +392,42 @@ const applyBlockParagraphProperties = ({ tr, position, node, properties, styleRe
304
392
  info: changeInfo,
305
393
  previousFormatting: paragraphPropertiesSnapshot(node)
306
394
  } : null;
395
+ const preserveRunFormatting = change !== null && properties.styleId !== void 0 && (attrs.styleId ?? null) !== properties.styleId;
307
396
  const existing = attrs._propertyChanges;
397
+ const nextAttrs = {
398
+ ...node.attrs,
399
+ ...patch,
400
+ ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
401
+ };
402
+ const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
403
+ allocateRevisionInfo: revisionInfo,
404
+ nextParagraphAttrs: nextAttrs,
405
+ paragraphPosition: position,
406
+ styleResolver,
407
+ tr
408
+ }) : {
409
+ tr,
410
+ revisionIds: []
411
+ };
308
412
  return {
309
- tr: tr.setNodeMarkup(position, void 0, {
310
- ...node.attrs,
311
- ...patch,
312
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
313
- }),
413
+ tr: bridgeResult.tr.setNodeMarkup(position, void 0, nextAttrs),
314
414
  changed: true,
315
- revisionId: change?.info.id ?? null
415
+ revisionId: change?.info.id ?? null,
416
+ revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
316
417
  };
317
418
  };
318
419
  const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) => {
319
420
  if (item.operation.type !== "replaceBlock" || item.operation.styleId === void 0) return {
320
421
  tr,
321
- revisionId: null
422
+ revisionId: null,
423
+ revisionIds: []
322
424
  };
323
425
  const blockPosition = tr.mapping.map(item.blockFrom, -1);
324
426
  const block = tr.doc.nodeAt(blockPosition);
325
427
  if (!block) return {
326
428
  tr,
327
- revisionId: null
429
+ revisionId: null,
430
+ revisionIds: []
328
431
  };
329
432
  const attrs = expectParagraphAttrs(block);
330
433
  const resolvedFormattingFromStyle = resolveFormattingFromStyle({
@@ -339,21 +442,36 @@ const applyReplaceBlockStyleId = ({ item, tr, styleResolver, revisionInfo }) =>
339
442
  });
340
443
  if (patch === null) return {
341
444
  tr,
342
- revisionId: null
445
+ revisionId: null,
446
+ revisionIds: []
343
447
  };
344
448
  const existing = attrs._propertyChanges;
345
- const change = revisionInfo ? {
449
+ const changeInfo = revisionInfo?.();
450
+ const change = changeInfo ? {
346
451
  type: "paragraphPropertyChange",
347
- info: revisionInfo,
452
+ info: changeInfo,
348
453
  previousFormatting: paragraphPropertiesSnapshot(block)
349
454
  } : null;
455
+ const preserveRunFormatting = change !== null && (attrs.styleId ?? null) !== item.operation.styleId;
456
+ const nextAttrs = {
457
+ ...block.attrs,
458
+ ...patch,
459
+ ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
460
+ };
461
+ const bridgeResult = preserveRunFormatting && revisionInfo ? preserveRunFormattingAcrossTrackedStyleChange({
462
+ allocateRevisionInfo: revisionInfo,
463
+ nextParagraphAttrs: nextAttrs,
464
+ paragraphPosition: blockPosition,
465
+ styleResolver,
466
+ tr
467
+ }) : {
468
+ tr,
469
+ revisionIds: []
470
+ };
350
471
  return {
351
- tr: tr.setNodeMarkup(blockPosition, void 0, {
352
- ...block.attrs,
353
- ...patch,
354
- ...change ? { _propertyChanges: [...Array.isArray(existing) ? existing : [], change] } : {}
355
- }),
356
- revisionId: change?.info.id ?? null
472
+ tr: bridgeResult.tr.setNodeMarkup(blockPosition, void 0, nextAttrs),
473
+ revisionId: change?.info.id ?? null,
474
+ revisionIds: change ? [change.info.id, ...bridgeResult.revisionIds] : []
357
475
  };
358
476
  };
359
477
  const REPLACEMENT_BACKGROUND_CLEAR_FORMATTING = {
@@ -406,34 +524,23 @@ const applyInlineFormatting = ({ tr, from, to, formatting, includedProperties, s
406
524
  marks: representation.node.marks,
407
525
  ...styleResolver !== void 0 ? { styleResolver } : {}
408
526
  });
409
- const marks = reconcileRunFormattingMarks({
410
- authoredFormatting: patchAuthoredRunFormatting({
411
- authoredFormatting,
412
- formatting,
413
- ...includedProperties !== void 0 ? { includedProperties } : {}
414
- }),
415
- context: styleContext,
416
- node: representation.node,
417
- ...styleResolver !== void 0 ? { styleResolver } : {}
418
- });
419
527
  applyMarksToRunFormattingRepresentation({
420
528
  tr,
421
529
  representation,
422
- marks
530
+ marks: reconcileRunFormattingMarks({
531
+ authoredFormatting: patchAuthoredRunFormatting({
532
+ authoredFormatting,
533
+ formatting,
534
+ ...includedProperties !== void 0 ? { includedProperties } : {}
535
+ }),
536
+ context: styleContext,
537
+ node: representation.node,
538
+ ...styleResolver !== void 0 ? { styleResolver } : {}
539
+ })
423
540
  });
424
541
  }
425
542
  return tr;
426
543
  };
427
- const applyMarksToRunFormattingRepresentation = ({ tr, representation, marks }) => {
428
- const { node, position, from, to } = representation;
429
- if (Mark.sameSet(node.marks, marks)) return;
430
- if (!node.isText) {
431
- tr.setNodeMarkup(position, void 0, node.attrs, marks);
432
- return;
433
- }
434
- for (const current of node.marks) if (!marks.some((candidate) => candidate.eq(current))) tr.removeMark(from, to, current.type);
435
- for (const next of marks) if (!node.marks.some((candidate) => candidate.eq(next))) tr.addMark(from, to, next);
436
- };
437
544
  const sameDefinedFormattingValue = (left, right) => {
438
545
  if (left === right) return true;
439
546
  if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
@@ -750,7 +857,11 @@ const buildTableNode = ({ schema, rows, revision }) => {
750
857
  ...revision.provenance != null && { provenance: revision.provenance },
751
858
  ...revision.suggestionId != null && { suggestionId: revision.suggestionId }
752
859
  })] : void 0;
753
- const rowNodes = rows.map((cells) => rowType.create(revision ? { trIns: revision } : null, cells.map((text) => cellType.create(null, splitCellParagraphTexts(text).map((line) => paragraphType.create(null, line.length > 0 ? schema.text(line, marks) : null))))));
860
+ const rowNodes = rows.map((cells) => rowType.create(revision ? { trIns: revision } : null, cells.map((text) => cellType.create(null, splitCellParagraphTexts(text).map((line) => paragraphType.create(null, line.length > 0 ? cleanTextInlineNodes({
861
+ schema,
862
+ text: line,
863
+ marks
864
+ }) : null))))));
754
865
  return tableType.create(null, rowNodes);
755
866
  };
756
867
  /**
@@ -1005,34 +1116,61 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
1005
1116
  revisionIds.push(paragraphRevisionId);
1006
1117
  }
1007
1118
  if (commentMark) marks.push(commentMark);
1008
- const content = text.length > 0 ? buildEmphasisInlineContent(schema, text, marks) : null;
1119
+ const hardPageBreak = isFirstParagraph ? operation.hardPageBreak : void 0;
1120
+ let content = null;
1121
+ if (hardPageBreak !== void 0) {
1122
+ const pageBreakRun = schema.nodes["pageBreakRun"];
1123
+ if (pageBreakRun === void 0) panic("The schema has no page-break run node", { operationId: operation.id });
1124
+ content = [pageBreakRun.create(hardPageBreak, null, marks)];
1125
+ } else if (text.length > 0) content = hasCleanTextControls(text) ? cleanTextInlineNodes({
1126
+ schema,
1127
+ text,
1128
+ marks
1129
+ }) : buildEmphasisInlineContent(schema, text, marks);
1009
1130
  const attrs = isFirstParagraph ? { ...baseAttrs } : {};
1010
1131
  if (isFirstParagraph && operation.pageBreakBefore === true) attrs["pageBreakBefore"] = true;
1132
+ const explicitNumbering = operation.numbering;
1011
1133
  const listLevel = operation.listLevel;
1012
- if (isFirstParagraph && listLevel === null) {
1013
- attrs["numPr"] = null;
1014
- Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
1015
- } else if (isFirstParagraph && typeof listLevel === "number") {
1016
- const anchorNumPr = Reflect.get(baseAttrs, "numPr");
1017
- const numId = typeof anchorNumPr === "object" && anchorNumPr !== null && "numId" in anchorNumPr ? anchorNumPr.numId : void 0;
1018
- if (typeof numId === "number") Object.assign(attrs, listLevelAttrPatch(operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode), {
1019
- numId,
1020
- ilvl: listLevel
1021
- }, numbering));
1022
- else {
1023
- attrs["numPr"] = { ilvl: listLevel };
1134
+ if (isFirstParagraph) {
1135
+ if (explicitNumbering === null) {
1136
+ attrs["numPr"] = null;
1024
1137
  Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
1138
+ } else if (explicitNumbering !== void 0) {
1139
+ Object.assign(attrs, listLevelAttrPatch(operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode), {
1140
+ numId: explicitNumbering.numId,
1141
+ ilvl: explicitNumbering.level
1142
+ }, numbering));
1143
+ if (listLevel === null) {
1144
+ attrs["numPr"] = { numId: explicitNumbering.numId };
1145
+ attrs["numPrFromStyle"] = null;
1146
+ }
1147
+ } else if (listLevel === null) {
1148
+ attrs["numPr"] = null;
1149
+ Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
1150
+ } else if (typeof listLevel === "number") {
1151
+ const anchorNumPr = Reflect.get(baseAttrs, "numPr");
1152
+ const numId = typeof anchorNumPr === "object" && anchorNumPr !== null && "numId" in anchorNumPr ? anchorNumPr.numId : void 0;
1153
+ if (typeof numId === "number") Object.assign(attrs, listLevelAttrPatch(operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode), {
1154
+ numId,
1155
+ ilvl: listLevel
1156
+ }, numbering));
1157
+ else {
1158
+ attrs["numPr"] = { ilvl: listLevel };
1159
+ Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
1160
+ }
1025
1161
  }
1026
1162
  }
1027
1163
  if (isFirstParagraph && operation.styleId !== void 0) {
1028
1164
  attrs["styleId"] = operation.styleId;
1029
1165
  if (operation.inheritFormatting !== false && operation.styleId !== null) Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
1030
1166
  }
1031
- if (isFirstParagraph && (operation.styleId !== void 0 || operation.alignment !== void 0 || operation.spacing !== void 0)) {
1167
+ if (isFirstParagraph && (operation.styleId !== void 0 || operation.alignment !== void 0 || operation.spacing !== void 0 || operation.indentation !== void 0)) {
1032
1168
  const inheritedDirectAlignment = operation.inheritFormatting === false ? void 0 : directParagraphAlignment(expectParagraphAttrs(item.blockNode));
1033
1169
  const inheritedDirectSpacing = operation.inheritFormatting === false ? void 0 : directParagraphSpacing(expectParagraphAttrs(item.blockNode));
1170
+ const inheritedDirectIndentation = operation.inheritFormatting === false ? void 0 : directParagraphIndentation(expectParagraphAttrs(item.blockNode));
1034
1171
  const directAlignment = operation.alignment === void 0 ? inheritedDirectAlignment : operation.alignment ?? void 0;
1035
1172
  const directSpacing = operation.spacing === void 0 ? inheritedDirectSpacing : operation.spacing ?? void 0;
1173
+ const directIndentation = operation.indentation === void 0 ? inheritedDirectIndentation : operation.indentation ?? void 0;
1036
1174
  attrs["alignmentFromStyle"] = formattingFromStyle?.alignment;
1037
1175
  attrs["alignment"] = directAlignment ?? formattingFromStyle?.alignment ?? null;
1038
1176
  const sourceFormatting = attrs["_originalFormatting"];
@@ -1048,10 +1186,15 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
1048
1186
  else originalFormatting.alignment = directAlignment;
1049
1187
  }
1050
1188
  if (operation.spacing !== void 0) originalFormatting = withDirectParagraphSpacing(originalFormatting, directSpacing);
1189
+ if (operation.indentation !== void 0) originalFormatting = withDirectParagraphIndentation(originalFormatting, directIndentation);
1051
1190
  if (operation.styleId !== void 0 || operation.spacing !== void 0) Object.assign(attrs, paragraphSpacingAttrPatch({
1052
1191
  direct: directSpacing,
1053
1192
  inherited: paragraphSpacingFromFormatting(formattingFromStyle)
1054
1193
  }));
1194
+ if (operation.styleId !== void 0 || operation.indentation !== void 0) Object.assign(attrs, paragraphIndentationAttrPatch({
1195
+ direct: directIndentation,
1196
+ inherited: paragraphIndentationFromFormatting(formattingFromStyle)
1197
+ }));
1055
1198
  attrs["_originalFormatting"] = originalFormatting && Object.keys(originalFormatting).length > 0 ? originalFormatting : null;
1056
1199
  }
1057
1200
  if (isSuggested && suggestionId !== null && paragraphRevisionId !== null) attrs["_suggestedInsert"] = {
@@ -1198,7 +1341,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1198
1341
  }
1199
1342
  claimedTableColumns.add(columnKey);
1200
1343
  }
1201
- if ((operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") && LINE_BREAK_PATTERN.test(operation.text)) normalizations.push({
1344
+ if ((operation.type === "insertAfterBlock" || operation.type === "insertBeforeBlock") && operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text)) normalizations.push({
1202
1345
  id: operation.id,
1203
1346
  code: "splitMultilineText",
1204
1347
  paragraphCount: resolution.operation.insertTexts?.length ?? 1
@@ -1525,38 +1668,29 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1525
1668
  diffText
1526
1669
  });
1527
1670
  }
1528
- const revisionIdParagraph = producesTrackedChanges && changesStyle ? operationRevisionSeed++ : null;
1671
+ const allocateStyleRevisionInfo = () => ({
1672
+ id: operationRevisionSeed++,
1673
+ author,
1674
+ date,
1675
+ ...trackedRevisionExtras
1676
+ });
1529
1677
  const styleResult = applyReplaceBlockStyleId({
1530
1678
  item,
1531
1679
  tr,
1532
1680
  styleResolver,
1533
- ...revisionIdParagraph !== null ? { revisionInfo: {
1534
- id: revisionIdParagraph,
1535
- author,
1536
- date,
1537
- ...trackedRevisionExtras
1538
- } } : {}
1681
+ ...producesTrackedChanges && changesStyle ? { revisionInfo: allocateStyleRevisionInfo } : {}
1539
1682
  });
1540
1683
  tr = styleResult.tr;
1541
1684
  if (!changesText && commentMark) tr = tr.addMark(item.from, item.to, commentMark);
1542
1685
  if (producesTrackedChanges) appliedRevisionIds = [
1543
1686
  ...changesText ? [revisionIdDelete, revisionIdInsert] : [],
1544
1687
  ...clearedBackground ? backgroundRevisionIds : [],
1545
- ...styleResult.revisionId === null ? [] : [styleResult.revisionId]
1688
+ ...styleResult.revisionIds
1546
1689
  ];
1547
1690
  break;
1548
1691
  }
1549
1692
  case "insertAfterBlock":
1550
1693
  case "insertBeforeBlock": {
1551
- const insertTexts = item.insertTexts ?? [""];
1552
- const isEmptyInsert = insertTexts.length === 1 && insertTexts[0]?.length === 0;
1553
- if (mode === "tracked-changes" && item.operation.pageBreakBefore === true && isEmptyInsert) {
1554
- skipped.push({
1555
- id: item.operation.id,
1556
- reason: "unsupportedMode"
1557
- });
1558
- continue;
1559
- }
1560
1694
  const built = buildInsertedParagraphs({
1561
1695
  item,
1562
1696
  schema: view.state.schema,
@@ -1971,7 +2105,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
1971
2105
  continue;
1972
2106
  }
1973
2107
  tr = appliedProperties.tr;
1974
- if (appliedProperties.revisionId !== null) appliedRevisionIds = [appliedProperties.revisionId];
2108
+ if (appliedProperties.revisionIds.length > 0) appliedRevisionIds = [...appliedProperties.revisionIds];
1975
2109
  break;
1976
2110
  }
1977
2111
  case "splitBlock": {
@@ -2030,7 +2164,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
2030
2164
  ...mode === "direct" ? {} : { revisionInfo: allocateSplitParagraphPropertyRevisionInfo }
2031
2165
  });
2032
2166
  tr = appliedProperties.tr;
2033
- if (appliedProperties.revisionId !== null) appliedRevisionIds.push(appliedProperties.revisionId);
2167
+ if (appliedProperties.revisionIds.length > 0) appliedRevisionIds.push(...appliedProperties.revisionIds);
2034
2168
  }
2035
2169
  break;
2036
2170
  }
@@ -2082,7 +2216,7 @@ const applyFolioAIEditOperationsInternal = ({ view, snapshot, operations, mode =
2082
2216
  }) }
2083
2217
  });
2084
2218
  tr = appliedProperties.tr;
2085
- if (appliedProperties.revisionId !== null) appliedRevisionIds.push(appliedProperties.revisionId);
2219
+ if (appliedProperties.revisionIds.length > 0) appliedRevisionIds.push(...appliedProperties.revisionIds);
2086
2220
  }
2087
2221
  break;
2088
2222
  }
@@ -2185,6 +2319,41 @@ const previewFolioAIEditOperations = (options) => {
2185
2319
  nextRevisionId: options.revisionStamp?.idSeed ?? revisionIdCursor
2186
2320
  };
2187
2321
  };
2322
+ const CLEAN_TEXT_CONTROL_PATTERN = /[\t\n]/gu;
2323
+ const hasCleanTextControls = (text) => text.includes(" ") || text.includes("\n");
2324
+ const cleanTextInlineNodes = ({ schema, text, marks = [] }) => {
2325
+ const nodes = [];
2326
+ let offset = 0;
2327
+ for (const match of text.matchAll(CLEAN_TEXT_CONTROL_PATTERN)) {
2328
+ const index = match.index;
2329
+ const preceding = text.slice(offset, index);
2330
+ if (preceding.length > 0) nodes.push(schema.text(preceding, marks));
2331
+ const control = match[0];
2332
+ const nodeName = runFormattingInlineControlNodeName(control);
2333
+ if (nodeName === null) panic("A clean-text control pattern matched an unrepresentable character", { control });
2334
+ const type = schema.nodes[nodeName];
2335
+ if (!type) panic("The editor schema cannot represent a clean-text control character", { control });
2336
+ nodes.push(type.create(null, null, marks));
2337
+ offset = index + control.length;
2338
+ }
2339
+ const trailing = text.slice(offset);
2340
+ if (trailing.length > 0) nodes.push(schema.text(trailing, marks));
2341
+ return nodes;
2342
+ };
2343
+ const insertCleanText = ({ tr, from, to, text }) => {
2344
+ if (!hasCleanTextControls(text)) return {
2345
+ transaction: tr.insertText(text, from, to),
2346
+ end: from + text.length
2347
+ };
2348
+ const content = Fragment.fromArray(cleanTextInlineNodes({
2349
+ schema: tr.doc.type.schema,
2350
+ text
2351
+ }));
2352
+ return {
2353
+ transaction: tr.replaceWith(from, to, content),
2354
+ end: from + content.size
2355
+ };
2356
+ };
2188
2357
  const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete, revisionIdInsert, commentMark, suggestionId = null, initials, diffText }) => {
2189
2358
  let nextTr = tr;
2190
2359
  const replacement = stripInlineEmphasisMarkers((() => {
@@ -2197,8 +2366,14 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
2197
2366
  const content = buildEmphasisInlineContent(nextTr.doc.type.schema, item.operation.replace, commentMark ? [commentMark] : []);
2198
2367
  return nextTr.replaceWith(item.from, item.to, content);
2199
2368
  }
2200
- nextTr = nextTr.insertText(replacement, item.from, item.to);
2201
- if (commentMark && replacement.length > 0) nextTr = nextTr.addMark(item.from, item.from + replacement.length, commentMark);
2369
+ const inserted = insertCleanText({
2370
+ tr: nextTr,
2371
+ from: item.from,
2372
+ to: item.to,
2373
+ text: replacement
2374
+ });
2375
+ nextTr = inserted.transaction;
2376
+ if (commentMark && replacement.length > 0) nextTr = nextTr.addMark(item.from, inserted.end, commentMark);
2202
2377
  return nextTr;
2203
2378
  }
2204
2379
  const insertionType = nextTr.doc.type.schema.marks["insertion"];
@@ -2284,18 +2459,30 @@ const applyTextReplacement = ({ tr, item, mode, author, date, revisionIdDelete,
2284
2459
  continue;
2285
2460
  }
2286
2461
  if (step.kind === "ins" && insertionType) {
2287
- nextTr = nextTr.insertText(step.text, step.at, step.at);
2288
- nextTr = nextTr.addMark(step.at, step.at + step.text.length, insertionType.create(insAttrs));
2289
- if (commentMark) nextTr = nextTr.addMark(step.at, step.at + step.text.length, commentMark);
2462
+ const inserted = insertCleanText({
2463
+ tr: nextTr,
2464
+ from: step.at,
2465
+ to: step.at,
2466
+ text: step.text
2467
+ });
2468
+ nextTr = inserted.transaction;
2469
+ nextTr = nextTr.addMark(step.at, inserted.end, insertionType.create(insAttrs));
2470
+ if (commentMark) nextTr = nextTr.addMark(step.at, inserted.end, commentMark);
2290
2471
  }
2291
2472
  }
2292
2473
  return nextTr;
2293
2474
  }
2294
2475
  }
2295
2476
  if (replacement.length > 0 && insertionType) {
2296
- nextTr = nextTr.insertText(replacement, item.to, item.to);
2297
- nextTr = nextTr.addMark(item.to, item.to + replacement.length, insertionType.create(insAttrs));
2298
- if (commentMark) nextTr = nextTr.addMark(item.to, item.to + replacement.length, commentMark);
2477
+ const inserted = insertCleanText({
2478
+ tr: nextTr,
2479
+ from: item.to,
2480
+ to: item.to,
2481
+ text: replacement
2482
+ });
2483
+ nextTr = inserted.transaction;
2484
+ nextTr = nextTr.addMark(item.to, inserted.end, insertionType.create(insAttrs));
2485
+ if (commentMark) nextTr = nextTr.addMark(item.to, inserted.end, commentMark);
2299
2486
  }
2300
2487
  if (item.to > item.from && deletionType) {
2301
2488
  nextTr = nextTr.addMark(item.from, item.to, deletionType.create(delAttrs));
@@ -2403,7 +2590,7 @@ const resolveOperation = ({ snapshot, operation, liveBlocks, liveBlocksByParaId,
2403
2590
  let insertFrom;
2404
2591
  if (tableBoundary) insertFrom = isInsertAfter ? tableBoundary.after : tableBoundary.before;
2405
2592
  else insertFrom = isInsertAfter ? blockTo : blockFrom;
2406
- const insertTexts = LINE_BREAK_PATTERN.test(operation.text) ? splitInsertParagraphTexts(operation.text) : [operation.text];
2593
+ const insertTexts = operation.lineBreakMode !== "inline" && LINE_BREAK_PATTERN.test(operation.text) ? splitInsertParagraphTexts(operation.text) : [operation.text];
2407
2594
  return {
2408
2595
  type: "resolved",
2409
2596
  operation: {
@@ -1,4 +1,5 @@
1
1
  import { expectPageBreakRunAttrs } from "../prosemirror/attrs/index.js";
2
+ import { runFormattingInlineControlCharacter } from "../prosemirror/runFormattingInlineCarriers.js";
2
3
  //#region src/ai-edits/clean-text.ts
3
4
  const EMPTY_CLEAN_TEXT_STRUCTURAL_BOUNDARIES = Object.freeze([]);
4
5
  /**
@@ -38,6 +39,7 @@ const DELETION_MARK = "deletion";
38
39
  const INSERTION_MARK = "insertion";
39
40
  const COMMENT_MARK = "comment";
40
41
  const HIDDEN_MARK = "hidden";
42
+ const isOmittedFromCleanView = (node) => node.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK);
41
43
  const buildCleanBlockText = (blockNode, blockFrom) => {
42
44
  let text = "";
43
45
  const offsets = [];
@@ -57,8 +59,17 @@ const buildCleanBlockText = (blockNode, blockFrom) => {
57
59
  });
58
60
  return false;
59
61
  }
62
+ const controlCharacter = runFormattingInlineControlCharacter(node);
63
+ if (controlCharacter !== null) {
64
+ if (isOmittedFromCleanView(node)) return false;
65
+ const startPos = blockFrom + 1 + pos;
66
+ offsets.push(startPos);
67
+ text += controlCharacter;
68
+ lastEnd = startPos + node.nodeSize;
69
+ return false;
70
+ }
60
71
  if (!node.isText || node.text === void 0) return true;
61
- if (node.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK)) return false;
72
+ if (isOmittedFromCleanView(node)) return false;
62
73
  const startPos = blockFrom + 1 + pos;
63
74
  for (let i = 0; i < node.text.length; i++) offsets.push(startPos + i);
64
75
  text += node.text;