@stll/folio-core 0.11.0 → 0.13.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 (263) hide show
  1. package/dist/ai-edits/apply.d.ts +2 -1
  2. package/dist/ai-edits/apply.js +693 -63
  3. package/dist/ai-edits/block-identity.d.ts +4 -0
  4. package/dist/ai-edits/block-identity.js +9 -0
  5. package/dist/ai-edits/blockRange.d.ts +28 -1
  6. package/dist/ai-edits/blockRange.js +68 -2
  7. package/dist/ai-edits/clean-text.js +2 -1
  8. package/dist/ai-edits/headless.d.ts +4 -4
  9. package/dist/ai-edits/headless.js +9 -9
  10. package/dist/ai-edits/index.d.ts +2 -2
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.d.ts +7 -7
  13. package/dist/ai-edits/read.js +110 -3
  14. package/dist/ai-edits/snapshot.js +21 -3
  15. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  16. package/dist/ai-edits/table-cell-mutations.js +238 -0
  17. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  18. package/dist/ai-edits/table-mutation-plan.js +90 -0
  19. package/dist/ai-edits/table-row-column-mutations.d.ts +108 -0
  20. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  21. package/dist/ai-edits/table-targets.d.ts +32 -0
  22. package/dist/ai-edits/table-targets.js +70 -0
  23. package/dist/ai-edits/types.d.ts +62 -1
  24. package/dist/ai-edits/word-diff.js +21 -0
  25. package/dist/compat/eigenpal.d.ts +3 -3
  26. package/dist/compat/eigenpal.js +3 -3
  27. package/dist/content-controls/mutateContentControls.js +1 -1
  28. package/dist/controller/activeEditorStory.d.ts +36 -0
  29. package/dist/controller/activeEditorStory.js +30 -0
  30. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  31. package/dist/controller/contentControlWidgetController.js +141 -0
  32. package/dist/controller/fontReadiness.js +2 -0
  33. package/dist/controller/headerFooterEditorManager.js +2 -2
  34. package/dist/controller/hiddenEditorApi.js +2 -2
  35. package/dist/controller/hiddenEditorManager.d.ts +12 -1
  36. package/dist/controller/hiddenEditorManager.js +44 -12
  37. package/dist/controller/layoutPipeline.d.ts +2 -0
  38. package/dist/controller/layoutPipeline.js +17 -2
  39. package/dist/controller/noteEditorManager.d.ts +36 -0
  40. package/dist/controller/noteEditorManager.js +210 -0
  41. package/dist/document-operations.d.ts +43 -11
  42. package/dist/document-operations.js +186 -18
  43. package/dist/docx/blockContentParser.js +21 -7
  44. package/dist/docx/commentParser.js +6 -1
  45. package/dist/docx/commentRangeIntegrity.d.ts +6 -0
  46. package/dist/docx/commentRangeIntegrity.js +186 -0
  47. package/dist/docx/conformance.js +62 -1
  48. package/dist/docx/documentGrid.d.ts +8 -0
  49. package/dist/docx/documentGrid.js +8 -0
  50. package/dist/docx/drawingUtils.js +1 -1
  51. package/dist/docx/encryption/agileDecryption.js +12 -2
  52. package/dist/docx/encryption/compoundFile.js +6 -1
  53. package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
  54. package/dist/docx/encryption/encryptionInfo.js +18 -2
  55. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  56. package/dist/docx/ensureParaIds.js +8 -7
  57. package/dist/docx/fieldParser.js +1 -1
  58. package/dist/docx/groupDrawingParser.js +10 -5
  59. package/dist/docx/hyperlinkParser.js +1 -1
  60. package/dist/docx/imageParser.js +10 -6
  61. package/dist/docx/metadataPrivacy.js +17 -3
  62. package/dist/docx/notePropertiesParser.js +1 -1
  63. package/dist/docx/numberingParser.js +1 -1
  64. package/dist/docx/paragraphParser.js +70 -61
  65. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  66. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  67. package/dist/docx/paragraphTraversal.js +1 -1
  68. package/dist/docx/parser.js +3 -3
  69. package/dist/docx/rezip.js +20 -9
  70. package/dist/docx/runParser.js +17 -6
  71. package/dist/docx/sdtProperties.js +1 -1
  72. package/dist/docx/sectionParser.js +18 -5
  73. package/dist/docx/selectiveSave.js +5 -1
  74. package/dist/docx/selectiveXmlPatch.d.ts +22 -1
  75. package/dist/docx/selectiveXmlPatch.js +61 -1
  76. package/dist/docx/serializer/blockSdtSerializer.js +3 -2
  77. package/dist/docx/serializer/commentSerializer.js +5 -6
  78. package/dist/docx/serializer/documentSerializer.js +2 -2
  79. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  80. package/dist/docx/serializer/noteSerializer.js +2 -2
  81. package/dist/docx/serializer/paragraphSerializer.js +23 -15
  82. package/dist/docx/serializer/runSerializer.js +25 -21
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
  84. package/dist/docx/serializer/stylesSerializer.js +1 -1
  85. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  86. package/dist/docx/serializer/tableSerializer.js +26 -20
  87. package/dist/docx/serializer/xmlUtils.d.ts +13 -1
  88. package/dist/docx/serializer/xmlUtils.js +27 -1
  89. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  90. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  91. package/dist/docx/server/boundedArchive.d.ts +14 -3
  92. package/dist/docx/server/boundedArchive.js +56 -11
  93. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  94. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  95. package/dist/docx/server/extractDocxText.js +55 -8
  96. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  97. package/dist/docx/server/inspectDocxPackage.js +217 -0
  98. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  99. package/dist/docx/server/validateDocxConformance.js +336 -0
  100. package/dist/docx/settingsParser.js +26 -3
  101. package/dist/docx/shapeParser.js +1 -1
  102. package/dist/docx/styleParser.js +4 -2
  103. package/dist/docx/tableParser.d.ts +8 -1
  104. package/dist/docx/tableParser.js +31 -10
  105. package/dist/docx/textBoxParser.d.ts +3 -3
  106. package/dist/docx/textBoxParser.js +16 -12
  107. package/dist/docx/unzip.js +1 -0
  108. package/dist/docx/vmlImageParser.js +11 -3
  109. package/dist/docx/xmlParser.js +11 -1
  110. package/dist/docx/xmlSafety.d.ts +5 -0
  111. package/dist/docx/xmlSafety.js +9 -0
  112. package/dist/fields/resolveFieldValues.js +1 -3
  113. package/dist/fonts/embeddedFonts.d.ts +33 -5
  114. package/dist/fonts/embeddedFonts.js +37 -8
  115. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  116. package/dist/i18n/messages/catalogs.gen.js +17 -0
  117. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  118. package/dist/index.d.ts +3 -3
  119. package/dist/index.js +3 -3
  120. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  121. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  122. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  123. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  124. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  125. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  126. package/dist/layout-bridge/convert/toFlowBlocks.js +151 -20
  127. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  128. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  129. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  130. package/dist/layout-engine/index.d.ts +2 -2
  131. package/dist/layout-engine/index.js +58 -30
  132. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  133. package/dist/layout-engine/justificationCompatibility.js +8 -0
  134. package/dist/layout-engine/keep-together.js +11 -1
  135. package/dist/layout-engine/measure/cache.js +23 -5
  136. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
  137. package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
  138. package/dist/layout-engine/measure/lineBreakProvider.js +56 -37
  139. package/dist/layout-engine/measure/measureBlocks.js +15 -4
  140. package/dist/layout-engine/measure/measureContainer.js +1 -1
  141. package/dist/layout-engine/measure/measureHelpers.d.ts +11 -2
  142. package/dist/layout-engine/measure/measureHelpers.js +15 -4
  143. package/dist/layout-engine/measure/measureParagraph.js +85 -25
  144. package/dist/layout-engine/measure/measureProvider.js +4 -3
  145. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  146. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  147. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  148. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  149. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  150. package/dist/layout-engine/paginator.js +5 -1
  151. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  152. package/dist/layout-engine/paragraphSpacing.js +107 -13
  153. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  154. package/dist/layout-engine/types.d.ts +48 -15
  155. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  156. package/dist/layout-painter/renderImage.d.ts +12 -1
  157. package/dist/layout-painter/renderImage.js +21 -4
  158. package/dist/layout-painter/renderPage.d.ts +5 -2
  159. package/dist/layout-painter/renderPage.js +59 -14
  160. package/dist/layout-painter/renderParagraph.js +55 -15
  161. package/dist/layout-painter/renderTable.d.ts +5 -1
  162. package/dist/layout-painter/renderTable.js +7 -3
  163. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  164. package/dist/layout-painter/renderTextBox.js +25 -14
  165. package/dist/layout-painter/renderWatermark.d.ts +1 -1
  166. package/dist/layout-painter/renderWatermark.js +2 -1
  167. package/dist/managers/DocumentLoaderManager.js +1 -1
  168. package/dist/markdown/fromMarkdown.js +1 -1
  169. package/dist/model.js +1 -1
  170. package/dist/prosemirror/attrs/index.d.ts +4 -2
  171. package/dist/prosemirror/attrs/index.js +175 -18
  172. package/dist/prosemirror/commands/comments.d.ts +79 -4
  173. package/dist/prosemirror/commands/comments.js +622 -13
  174. package/dist/prosemirror/commands/contentControls.js +1 -1
  175. package/dist/prosemirror/commands/hyperlink.js +15 -1
  176. package/dist/prosemirror/commands/image.js +7 -1
  177. package/dist/prosemirror/commands/index.d.ts +2 -2
  178. package/dist/prosemirror/commands/index.js +2 -2
  179. package/dist/prosemirror/commands/pageBreak.js +1 -1
  180. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  181. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  182. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  183. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  184. package/dist/prosemirror/commentIdAllocator.js +13 -3
  185. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  186. package/dist/prosemirror/conversion/fromProseDoc.js +404 -62
  187. package/dist/prosemirror/conversion/index.js +1 -1
  188. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  189. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  190. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  191. package/dist/prosemirror/conversion/toProseDoc.js +364 -103
  192. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  193. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  194. package/dist/prosemirror/extensions/core/ParagraphExtension.js +6 -2
  195. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  196. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
  197. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  198. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  199. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  200. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  201. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  202. package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
  203. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  204. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
  205. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  206. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +70 -11
  207. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  208. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  209. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  210. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  211. package/dist/prosemirror/extensions/nodes/TableExtension.js +14 -6
  212. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  213. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  214. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  215. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  216. package/dist/prosemirror/index.d.ts +1 -1
  217. package/dist/prosemirror/index.js +1 -1
  218. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +36 -10
  219. package/dist/prosemirror/plugins/contentControlWidgets.js +128 -94
  220. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
  221. package/dist/prosemirror/plugins/revisionIds.js +57 -0
  222. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  223. package/dist/prosemirror/plugins/suggestionMode.js +5 -4
  224. package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
  225. package/dist/prosemirror/runFormattingMarkNames.js +40 -0
  226. package/dist/prosemirror/schema/index.d.ts +2 -2
  227. package/dist/prosemirror/schema/marks.d.ts +30 -3
  228. package/dist/prosemirror/schema/marks.js +15 -0
  229. package/dist/prosemirror/schema/nodes.d.ts +111 -6
  230. package/dist/prosemirror/selectionState.js +1 -0
  231. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  232. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  233. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  234. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  235. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  236. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  237. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  238. package/dist/prosemirror/validation.js +9 -1
  239. package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
  240. package/dist/server.d.ts +7 -3
  241. package/dist/server.js +7 -3
  242. package/dist/utils/clipboard.d.ts +10 -1
  243. package/dist/utils/clipboard.js +33 -6
  244. package/dist/utils/colorResolver.d.ts +8 -1
  245. package/dist/utils/colorResolver.js +35 -5
  246. package/dist/utils/fontFamilyMerge.js +9 -11
  247. package/dist/utils/fontLoader.js +3 -2
  248. package/dist/utils/fontResolver.d.ts +2 -1
  249. package/dist/utils/fontResolver.js +31 -7
  250. package/dist/utils/fontWeights.d.ts +1 -1
  251. package/dist/utils/fontWeights.js +1 -1
  252. package/dist/utils/hexId.d.ts +11 -1
  253. package/dist/utils/hexId.js +11 -1
  254. package/dist/utils/paragraphFormattingMerge.js +1 -0
  255. package/dist/utils/sanitizeImageSrc.d.ts +21 -0
  256. package/dist/utils/sanitizeImageSrc.js +30 -0
  257. package/dist/utils/stripXmlDeclarations.d.ts +11 -0
  258. package/dist/utils/stripXmlDeclarations.js +35 -0
  259. package/dist/version-comparison.d.ts +22 -2
  260. package/dist/version-comparison.js +41 -12
  261. package/dist/watermark/index.d.ts +11 -1
  262. package/dist/watermark/index.js +22 -1
  263. package/package.json +3 -2
@@ -1,6 +1,6 @@
1
1
  import { panic } from "better-result";
2
- import { keymap } from "prosemirror-keymap";
3
2
  import { Schema } from "prosemirror-model";
3
+ import { keymap } from "prosemirror-keymap";
4
4
  //#region src/prosemirror/extensions/ExtensionManager.ts
5
5
  /**
6
6
  * Extension Manager
@@ -41,7 +41,7 @@ import { SuperscriptExtension } from "./marks/SuperscriptExtension.js";
41
41
  import { TextColorExtension } from "./marks/TextColorExtension.js";
42
42
  import { TextEffectExtension } from "./marks/TextEffectExtension.js";
43
43
  import { EmbossExtension, EmphasisMarkExtension, ImprintExtension, TextOutlineExtension, TextShadowExtension } from "./marks/TextEffectsExtensions.js";
44
- import { DeletionExtension, InsertionExtension } from "./marks/TrackedChangeExtensions.js";
44
+ import { DeletionExtension, InsertionExtension, RunPropertyChangeExtension } from "./marks/TrackedChangeExtensions.js";
45
45
  import { UnderlineExtension } from "./marks/UnderlineExtension.js";
46
46
  import { BlockSdtExtension } from "./nodes/BlockSdtExtension.js";
47
47
  import { FieldExtension } from "./nodes/FieldExtension.js";
@@ -50,10 +50,12 @@ import { HorizontalRuleExtension } from "./nodes/HorizontalRuleExtension.js";
50
50
  import { ImageExtension } from "./nodes/ImageExtension.js";
51
51
  import { MathExtension } from "./nodes/MathExtension.js";
52
52
  import { PageBreakExtension } from "./nodes/PageBreakExtension.js";
53
+ import { RenderedPageBreakExtension } from "./nodes/RenderedPageBreakExtension.js";
53
54
  import { SdtExtension } from "./nodes/SdtExtension.js";
54
55
  import { ShapeExtension } from "./nodes/ShapeExtension.js";
55
56
  import { TabExtension } from "./nodes/TabExtension.js";
56
57
  import { TextBoxExtension } from "./nodes/TextBoxExtension.js";
58
+ import { TextBoxAnchorExtension } from "./nodes/TextBoxAnchorExtension.js";
57
59
  //#region src/prosemirror/extensions/StarterKit.ts
58
60
  /**
59
61
  * Create the full set of extensions for the DOCX editor
@@ -101,10 +103,12 @@ function createStarterKit(options = {}) {
101
103
  add("comment", CommentExtension());
102
104
  add("insertion", InsertionExtension());
103
105
  add("deletion", DeletionExtension());
106
+ add("runPropertyChange", RunPropertyChangeExtension());
104
107
  add("hardBreak", HardBreakExtension());
105
108
  add("tab", TabExtension());
106
109
  add("image", ImageExtension());
107
110
  add("textBox", TextBoxExtension());
111
+ add("textBoxAnchor", TextBoxAnchorExtension());
108
112
  add("shape", ShapeExtension());
109
113
  add("imageDrag", ImageDragExtension());
110
114
  add("imagePaste", ImagePasteExtension());
@@ -112,6 +116,7 @@ function createStarterKit(options = {}) {
112
116
  add("gapCursor", GapCursorExtension());
113
117
  add("horizontalRule", HorizontalRuleExtension());
114
118
  add("pageBreak", PageBreakExtension());
119
+ add("renderedPageBreak", RenderedPageBreakExtension());
115
120
  add("field", FieldExtension());
116
121
  add("sdt", SdtExtension());
117
122
  add("blockSdt", BlockSdtExtension());
@@ -1,6 +1,6 @@
1
- import { autospacingMatchesBase } from "../../autospacingBase.js";
2
1
  import { directionIsRtl } from "../../paragraphDirection.js";
3
2
  import { expectParagraphAttrs } from "../../attrs/index.js";
3
+ import { autospacingMatchesBase } from "../../autospacingBase.js";
4
4
  import { createNodeExtension } from "../create.js";
5
5
  import { paragraphToStyle } from "../../../utils/formatToStyle.js";
6
6
  import { collectHeadings } from "../../../utils/headingCollector.js";
@@ -187,8 +187,10 @@ const paragraphNodeSpec = {
187
187
  spaceAfter: { default: null },
188
188
  lineSpacing: { default: null },
189
189
  lineSpacingRule: { default: null },
190
+ snapToGrid: { default: null },
190
191
  spacingExplicit: { default: null },
191
192
  spacingFromDocDefaults: { default: null },
193
+ spacingFromImplicitDefaultStyle: { default: null },
192
194
  indentLeft: { default: null },
193
195
  indentRight: { default: null },
194
196
  indentFirstLine: { default: null },
@@ -219,6 +221,7 @@ const paragraphNodeSpec = {
219
221
  pageBreakBefore: { default: null },
220
222
  renderedPageBreakBefore: { default: null },
221
223
  _pageBreakCarrier: { default: null },
224
+ _trailingPageBreak: { default: null },
222
225
  keepNext: { default: null },
223
226
  keepLines: { default: null },
224
227
  widowControl: { default: null },
@@ -234,7 +237,8 @@ const paragraphNodeSpec = {
234
237
  _autospacingBase: { default: null },
235
238
  _sectionProperties: { default: null },
236
239
  _propertyChanges: { default: null },
237
- pPrMark: { default: null }
240
+ pPrMark: { default: null },
241
+ _suggestedInsert: { default: null }
238
242
  },
239
243
  parseDOM: [{
240
244
  tag: "p",
@@ -55,6 +55,7 @@ const INHERITED_PARA_ATTRS = [
55
55
  "styleId",
56
56
  "lineSpacing",
57
57
  "lineSpacingRule",
58
+ "snapToGrid",
58
59
  "spaceAfter",
59
60
  "spaceBefore",
60
61
  "contextualSpacing"
@@ -1,4 +1,5 @@
1
1
  import { createExtension } from "../create.js";
2
+ import { sanitizeImageSrc } from "../../../utils/sanitizeImageSrc.js";
2
3
  import { getClipboardImageFiles } from "../../../utils/clipboard.js";
3
4
  import { isSafeImageFile } from "../../../utils/imageValidation.js";
4
5
  import { Plugin, TextSelection } from "prosemirror-state";
@@ -20,13 +21,18 @@ async function readFileAsDataUrl(file) {
20
21
  }
21
22
  async function loadImageSize(src) {
22
23
  return await new Promise((resolve, reject) => {
24
+ const safeSrc = sanitizeImageSrc(src);
25
+ if (!safeSrc) {
26
+ reject(/* @__PURE__ */ new Error("Rejected unsafe image source"));
27
+ return;
28
+ }
23
29
  const img = new Image();
24
30
  img.addEventListener("load", () => resolve({
25
31
  width: img.naturalWidth || 1,
26
32
  height: img.naturalHeight || 1
27
33
  }));
28
34
  img.addEventListener("error", () => reject(/* @__PURE__ */ new Error("Failed to load pasted image")));
29
- img.src = src;
35
+ img.src = safeSrc;
30
36
  });
31
37
  }
32
38
  async function insertImageFiles(view, files) {
@@ -37,7 +43,9 @@ async function insertImageFiles(view, files) {
37
43
  if (!await isSafeImageFile(file)) continue;
38
44
  let dataUrl;
39
45
  try {
40
- dataUrl = await readFileAsDataUrl(file);
46
+ const safeSrc = sanitizeImageSrc(await readFileAsDataUrl(file));
47
+ if (!safeSrc) continue;
48
+ dataUrl = safeSrc;
41
49
  } catch {
42
50
  continue;
43
51
  }
@@ -1,7 +1,7 @@
1
- import { PPR_CHANGE_SCOPED_ATTR_KEYS } from "../../commands/propertyChangeScope.js";
2
1
  import { Priority } from "../types.js";
3
2
  import { createExtension } from "../create.js";
4
3
  import { SUGGESTION_META, makeRevisionInfo } from "../../plugins/suggestionMode.js";
4
+ import { PPR_CHANGE_SCOPED_ATTR_KEYS } from "../../commands/propertyChangeScope.js";
5
5
  import { goToNextCell, goToPrevCell } from "../nodes/TableExtension.js";
6
6
  //#region src/prosemirror/extensions/features/ListExtension.ts
7
7
  function chainCommands(...commands) {
@@ -1,9 +1,18 @@
1
1
  import { Extension } from "../types.js";
2
2
  import { EditorState, PluginKey } from "prosemirror-state";
3
+ import { Node } from "prosemirror-model";
3
4
 
4
5
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts
5
6
  declare const paraIdAllocatorKey: PluginKey<any>;
7
+ /**
8
+ * Allocate initial paragraph IDs before constructing editor state.
9
+ *
10
+ * Rebuilding only changed branches avoids applying one ProseMirror transaction
11
+ * step per missing paragraph. Live edits still use the transaction-based plugin
12
+ * below so history, mappings, and duplicate ownership retain their semantics.
13
+ */
14
+ declare const ensureParaIdsInDoc: (doc: Node) => Node;
6
15
  declare const ensureParaIdsInState: (state: EditorState) => EditorState;
7
16
  declare const ParaIdAllocatorExtension: (options?: Partial<{}> | undefined) => Extension;
8
17
  //#endregion
9
- export { ParaIdAllocatorExtension, ensureParaIdsInState, paraIdAllocatorKey };
18
+ export { ParaIdAllocatorExtension, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey };
@@ -1,8 +1,46 @@
1
- import { generateHexId } from "../../../utils/hexId.js";
2
1
  import { createExtension } from "../create.js";
3
2
  import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
3
+ import { generateHexId } from "../../../utils/hexId.js";
4
+ import { Fragment } from "prosemirror-model";
4
5
  import { Plugin, PluginKey } from "prosemirror-state";
5
6
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.ts
7
+ /**
8
+ * ParaIdAllocator — assigns a stable `w14:paraId` to every paragraph.
9
+ *
10
+ * Lifted from
11
+ * https://github.com/eigenpal/docx-editor/blob/main/packages/core/src/prosemirror/extensions/features/ParaIdAllocatorExtension.ts
12
+ * (Apache-2.0). Adapted to folio's `createExtension` + import style;
13
+ * keep behaviour in sync upstream. folio divergences: duplicate
14
+ * resolution maps the original paragraph's position through the
15
+ * transaction (see below), and the appended allocation transaction is
16
+ * excluded from paragraph change tracking like `ensureParaIdsInState`.
17
+ *
18
+ * Why: AI tooling, chat citation chips, and the change tracker all
19
+ * anchor on `paraId`. A paragraph with `paraId: null` is invisible
20
+ * to those surfaces; a duplicated paraId (the second half of an
21
+ * Enter-split, or content pasted from another doc) silently desyncs
22
+ * their anchors. This plugin closes both gaps by allocating fresh
23
+ * ids in an `appendTransaction` hook after every doc-changed step.
24
+ *
25
+ * Id lifecycle rules (locked by the co-located tests):
26
+ * - Missing/empty/reserved-zero id: a fresh random id is minted.
27
+ * - Split: the half holding the paragraph's original start position
28
+ * keeps the id; the other half gets a fresh one. Anchors inside the
29
+ * content stay resolvable on the half that carries them.
30
+ * - Merge/join: the surviving paragraph keeps its id; the absorbed id
31
+ * dangles by design (consumers degrade to snapshot fallback).
32
+ * - Paste/duplicate carrying an id already in the doc: the paragraph
33
+ * whose mapped pre-transaction position matches keeps it — pasting a
34
+ * copy above its source no longer steals the source's id. When no
35
+ * occurrence maps back (e.g. both are new), the first in document
36
+ * order keeps it.
37
+ * - Paste of an id unknown to this doc (cross-doc paste, cut-then-
38
+ * paste move): the id is kept, so a moved paragraph stays anchored.
39
+ * - Undo/redo: allocation applies with `addToHistory: false`, and
40
+ * ProseMirror remaps it into the originating history event. Redoing a
41
+ * split therefore restores the same allocated id instead of minting
42
+ * another one.
43
+ */
6
44
  const paraIdAllocatorKey = new PluginKey("paraIdAllocator");
7
45
  const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
8
46
  /**
@@ -76,6 +114,59 @@ const collectParaIdUpdates = (doc, keeperPositions) => {
76
114
  updates.sort((a, b) => a.pos - b.pos);
77
115
  return updates;
78
116
  };
117
+ const collectInitialParaIds = (doc) => {
118
+ let needsRewrite = false;
119
+ const taken = /* @__PURE__ */ new Set();
120
+ doc.descendants((node) => {
121
+ if (node.type.name !== "paragraph") return true;
122
+ const id = node.attrs["paraId"];
123
+ if (!isUsableParaId(id) || taken.has(id)) needsRewrite = true;
124
+ else taken.add(id);
125
+ return false;
126
+ });
127
+ return {
128
+ needsRewrite,
129
+ taken
130
+ };
131
+ };
132
+ const rewriteInitialParaIds = (parent, taken, seen) => {
133
+ let changed = false;
134
+ const children = [];
135
+ parent.forEach((child) => {
136
+ let next = child;
137
+ if (child.type.name === "paragraph") {
138
+ const id = child.attrs["paraId"];
139
+ if (!isUsableParaId(id) || seen.has(id)) {
140
+ let newId = generateHexId();
141
+ while (taken.has(newId)) newId = generateHexId();
142
+ taken.add(newId);
143
+ seen.add(newId);
144
+ next = child.type.create({
145
+ ...child.attrs,
146
+ paraId: newId
147
+ }, child.content, child.marks);
148
+ } else seen.add(id);
149
+ } else if (child.childCount > 0) {
150
+ const content = rewriteInitialParaIds(child, taken, seen);
151
+ if (content !== child.content) next = child.copy(content);
152
+ }
153
+ if (next !== child) changed = true;
154
+ children.push(next);
155
+ });
156
+ return changed ? Fragment.fromArray(children) : parent.content;
157
+ };
158
+ /**
159
+ * Allocate initial paragraph IDs before constructing editor state.
160
+ *
161
+ * Rebuilding only changed branches avoids applying one ProseMirror transaction
162
+ * step per missing paragraph. Live edits still use the transaction-based plugin
163
+ * below so history, mappings, and duplicate ownership retain their semantics.
164
+ */
165
+ const ensureParaIdsInDoc = (doc) => {
166
+ const { needsRewrite, taken } = collectInitialParaIds(doc);
167
+ if (!needsRewrite) return doc;
168
+ return doc.copy(rewriteInitialParaIds(doc, taken, /* @__PURE__ */ new Set()));
169
+ };
79
170
  const ensureParaIdsInState = (state) => {
80
171
  const updates = collectParaIdUpdates(state.doc);
81
172
  if (updates.length === 0) return state;
@@ -109,4 +200,4 @@ const ParaIdAllocatorExtension = createExtension({
109
200
  }
110
201
  });
111
202
  //#endregion
112
- export { ParaIdAllocatorExtension, ensureParaIdsInState, paraIdAllocatorKey };
203
+ export { ParaIdAllocatorExtension, ensureParaIdsInDoc, ensureParaIdsInState, paraIdAllocatorKey };
@@ -30,6 +30,7 @@ declare function hasUntrackedChanges(state: EditorState): boolean;
30
30
  */
31
31
  declare function clearTrackedChanges(state: EditorState): Transaction;
32
32
  declare function ignoreTrackedChanges(tr: Transaction): Transaction;
33
+ declare function markStructuralChange(tr: Transaction): Transaction;
33
34
  declare const ParagraphChangeTrackerExtension: (options?: Partial<{}> | undefined) => Extension;
34
35
  //#endregion
35
- export { ParagraphChangeTrackerExtension, ParagraphChangeTrackerState, clearTrackedChanges, getChangeTrackerState, getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges, paragraphChangeTrackerKey };
36
+ export { ParagraphChangeTrackerExtension, ParagraphChangeTrackerState, clearTrackedChanges, getChangeTrackerState, getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges, markStructuralChange, paragraphChangeTrackerKey };
@@ -12,6 +12,7 @@ import { AddMarkStep, AddNodeMarkStep, RemoveMarkStep, RemoveNodeMarkStep } from
12
12
  const paragraphChangeTrackerKey = new PluginKey("paragraphChangeTracker");
13
13
  const CLEAR_META = "clear";
14
14
  const IGNORE_META = "ignore";
15
+ const STRUCTURAL_META = "structural";
15
16
  /**
16
17
  * Count paragraph nodes in a ProseMirror document
17
18
  */
@@ -103,7 +104,7 @@ function createParagraphChangeTrackerPlugin() {
103
104
  const newCount = countParagraphs(tr.doc);
104
105
  const newState = {
105
106
  changedParaIds: new Set(prevState.changedParaIds),
106
- structuralChange: prevState.structuralChange,
107
+ structuralChange: prevState.structuralChange || meta === STRUCTURAL_META,
107
108
  hasUntrackedChanges: prevState.hasUntrackedChanges,
108
109
  paragraphCount: newCount
109
110
  };
@@ -177,6 +178,9 @@ function clearTrackedChanges(state) {
177
178
  function ignoreTrackedChanges(tr) {
178
179
  return tr.setMeta(paragraphChangeTrackerKey, IGNORE_META);
179
180
  }
181
+ function markStructuralChange(tr) {
182
+ return tr.setMeta(paragraphChangeTrackerKey, STRUCTURAL_META);
183
+ }
180
184
  const ParagraphChangeTrackerExtension = createExtension({
181
185
  name: "paragraphChangeTracker",
182
186
  defaultOptions: {},
@@ -185,4 +189,4 @@ const ParagraphChangeTrackerExtension = createExtension({
185
189
  }
186
190
  });
187
191
  //#endregion
188
- export { ParagraphChangeTrackerExtension, clearTrackedChanges, getChangeTrackerState, getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges, paragraphChangeTrackerKey };
192
+ export { ParagraphChangeTrackerExtension, clearTrackedChanges, getChangeTrackerState, getChangedParagraphIds, hasStructuralChanges, hasUntrackedChanges, ignoreTrackedChanges, markStructuralChange, paragraphChangeTrackerKey };
@@ -1,3 +1,4 @@
1
+ import { stripXmlDeclarations } from "../../../utils/stripXmlDeclarations.js";
1
2
  //#region src/prosemirror/extensions/features/pasteCleanup.ts
2
3
  /**
3
4
  * Office / web paste cleanup
@@ -112,11 +113,22 @@ function splitStyleDeclarations(value) {
112
113
  }
113
114
  const TAG_TAIL = `(?:"[^"]*"|'[^']*'|[^>"'])*`;
114
115
  const NAMESPACED_TAG = new RegExp(`<\\/?(?:[owvm]|st\\d+):${TAG_TAIL}>`, "gi");
115
- const XML_PROCESSING_INSTRUCTION = new RegExp(`<\\?xml${TAG_TAIL}>`, "gi");
116
116
  const STRAY_XML_TAG = new RegExp(`<\\/?xml${TAG_TAIL}>`, "gi");
117
117
  const NOISE_TAG = new RegExp(`<\\/?(?:font|meta|link)${TAG_TAIL}>`, "gi");
118
118
  const EMPTY_SPAN = new RegExp(`<span(?:\\s${TAG_TAIL})?><\\/span>`, "gi");
119
119
  const MAX_EMPTY_SPAN_PASSES = 5;
120
+ const PM_SLICE_MARKER = /\bdata-pm-slice\s*=/i;
121
+ const TEXTBOX_ANCHOR_ATTR = /\s+data-docx-textbox-anchor(?:="[^"]*"|='[^']*')?/gi;
122
+ /**
123
+ * Remove the `data-docx-textbox-anchor` marker from HTML that did not come
124
+ * from a ProseMirror clipboard slice (see {@link PM_SLICE_MARKER}). Internal
125
+ * copy/paste of a real text box carries the marker HTML unmodified so it
126
+ * keeps working; anything else has the marker stripped defensively.
127
+ */
128
+ function stripForeignTextBoxAnchors(html, originalHtml) {
129
+ if (PM_SLICE_MARKER.test(originalHtml)) return html;
130
+ return html.replace(TEXTBOX_ANCHOR_ATTR, "");
131
+ }
120
132
  /**
121
133
  * Remove empty spans left behind after stripping `mso-*` styles. Only truly
122
134
  * empty spans are removed (whitespace-only spans are kept so word-separating
@@ -144,12 +156,13 @@ function cleanPastedHtml(html) {
144
156
  if (!html) return html;
145
157
  try {
146
158
  let cleaned = stripHtmlComments(html);
147
- cleaned = cleaned.replace(XML_PROCESSING_INSTRUCTION, "");
159
+ cleaned = stripXmlDeclarations(cleaned);
148
160
  cleaned = cleaned.replace(NAMESPACED_TAG, "");
149
161
  cleaned = cleaned.replace(STRAY_XML_TAG, "");
150
162
  cleaned = cleaned.replace(NOISE_TAG, "");
151
163
  cleaned = stripMsoStyles(cleaned);
152
164
  cleaned = stripEmptySpans(cleaned);
165
+ cleaned = stripForeignTextBoxAnchors(cleaned, html);
153
166
  return cleaned.trim();
154
167
  } catch {
155
168
  return html;
@@ -1,5 +1,5 @@
1
- import { formatPx, twipsToPixels } from "../../../utils/units.js";
2
1
  import { expectCharacterSpacingMarkAttrs } from "../../attrs/index.js";
2
+ import { formatPx, twipsToPixels } from "../../../utils/units.js";
3
3
  import { createMarkExtension } from "../create.js";
4
4
  //#region src/prosemirror/extensions/marks/CharacterSpacingExtension.ts
5
5
  /**
@@ -1,11 +1,37 @@
1
1
  import { expectHyperlinkMarkAttrs } from "../../attrs/index.js";
2
2
  import { createMarkExtension } from "../create.js";
3
3
  import { isMarkActive } from "./markUtils.js";
4
+ import { normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
4
5
  import { panic } from "better-result";
5
6
  //#region src/prosemirror/extensions/marks/HyperlinkExtension.ts
6
7
  /**
7
8
  * Hyperlink Mark Extension
8
9
  */
10
+ /**
11
+ * Sanitize an href arriving from (or being emitted to) the DOM: pasted HTML,
12
+ * clipboard content, or a mark attr already stored on the document. Only
13
+ * allow-listed absolute URLs (http/https/mailto/tel) and internal bookmark
14
+ * anchors survive; everything else (javascript:, data:, file:, ...) becomes
15
+ * an empty href, matching how the codebase already signals "no link" for an
16
+ * unresolved hyperlink (see `toProseDoc.ts`'s `hyperlink.href || ""`).
17
+ */
18
+ function sanitizeStoredHref(rawHref) {
19
+ if (!rawHref) return "";
20
+ const trimmed = rawHref.trim();
21
+ if (trimmed.startsWith("#")) return trimmed;
22
+ return sanitizeExternalUrl(trimmed) ?? "";
23
+ }
24
+ /**
25
+ * Normalize/sanitize a URL typed by a user (hyperlink dialog/popup) before it
26
+ * is written into a mark. Protocol-less input (e.g. "example.com") is
27
+ * accepted and normalized to https; anything resolving to a disallowed
28
+ * scheme is dropped.
29
+ */
30
+ function normalizeHyperlinkInput(rawHref) {
31
+ const trimmed = rawHref.trim();
32
+ if (trimmed.startsWith("#")) return trimmed;
33
+ return normalizeUserUrl(trimmed);
34
+ }
9
35
  function isHyperlinkActive(state) {
10
36
  const hlType = state.schema.marks["hyperlink"];
11
37
  if (!hlType) return false;
@@ -62,14 +88,14 @@ const HyperlinkExtension = createMarkExtension({
62
88
  parseDOM: [{
63
89
  tag: "a[href]",
64
90
  getAttrs: (dom) => ({
65
- href: dom.getAttribute("href") ?? "",
91
+ href: sanitizeStoredHref(dom.getAttribute("href") ?? void 0),
66
92
  tooltip: dom.getAttribute("title") ?? void 0
67
93
  })
68
94
  }],
69
95
  toDOM(mark) {
70
96
  const { href, tooltip } = expectHyperlinkMarkAttrs(mark);
71
97
  const domAttrs = {
72
- href,
98
+ href: sanitizeStoredHref(href),
73
99
  target: "_blank",
74
100
  rel: "noopener noreferrer"
75
101
  };
@@ -89,7 +115,7 @@ const HyperlinkExtension = createMarkExtension({
89
115
  if (empty) return false;
90
116
  if (dispatch) {
91
117
  const mark = hlType.create({
92
- href,
118
+ href: normalizeHyperlinkInput(href),
93
119
  tooltip: tooltip || null
94
120
  });
95
121
  let tr = state.tr.addMark(from, to, mark);
@@ -127,7 +153,7 @@ const HyperlinkExtension = createMarkExtension({
127
153
  const insertHyperlink = (text, href, tooltip) => (state, dispatch) => {
128
154
  if (dispatch) {
129
155
  const mark = hlType.create({
130
- href,
156
+ href: normalizeHyperlinkInput(href),
131
157
  tooltip: tooltip || null
132
158
  });
133
159
  const textNode = state.schema.text(text, [mark]);
@@ -18,5 +18,13 @@ declare const InsertionExtension: (options?: Partial<Record<string, unknown>> |
18
18
  * Renders with per-author colored strikethrough.
19
19
  */
20
20
  declare const DeletionExtension: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
21
+ /**
22
+ * Run property change — formatting changed while review tracking was active.
23
+ *
24
+ * The current formatting remains represented by the normal formatting marks;
25
+ * this mark carries the previous run properties and revision metadata needed
26
+ * to serialize, list, accept, and reject the change.
27
+ */
28
+ declare const RunPropertyChangeExtension: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
21
29
  //#endregion
22
- export { DeletionExtension, InsertionExtension };
30
+ export { DeletionExtension, InsertionExtension, RunPropertyChangeExtension };
@@ -18,6 +18,20 @@ import { AUTHOR_COLORS, getAuthorColorIdx } from "../../../utils/authorColors.js
18
18
  const insertionStyle = (color) => `color: ${color}; text-decoration: underline; text-decoration-color: ${color};`;
19
19
  const deletionStyle = (color) => `color: ${color}; text-decoration: line-through; text-decoration-color: ${color};`;
20
20
  /**
21
+ * Suggested (AI-proposed) tracked changes reuse the insertion/deletion visual
22
+ * grammar but with a DOTTED stroke and a dedicated suggestion hue, kept
23
+ * distinct from the per-author redline colors. The hue and tint are driven by
24
+ * the `--suggestion-color` / `--suggestion-bg` CSS custom properties (see
25
+ * editor.css); the inline fallbacks below keep the paged-canvas projection —
26
+ * which reuses this same `toDOM` style string — legible when the stylesheet
27
+ * variables are not in scope.
28
+ */
29
+ const SUGGESTION_COLOR = "var(--suggestion-color, #6d3bd6)";
30
+ const SUGGESTION_TINT = "var(--suggestion-bg, color-mix(in oklch, #6d3bd6 12%, transparent))";
31
+ const SUGGESTION_TINT_LAYER = `linear-gradient(${SUGGESTION_TINT}, ${SUGGESTION_TINT})`;
32
+ const suggestedInsertionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: underline; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
33
+ const suggestedDeletionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: line-through; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
34
+ /**
21
35
  * Insertion mark — text added in tracked changes
22
36
  * Renders with per-author colored underline.
23
37
  */
@@ -29,7 +43,10 @@ const InsertionExtension = createMarkExtension({
29
43
  revisionId: { default: 0 },
30
44
  author: { default: "" },
31
45
  date: { default: null },
32
- moveKind: { default: null }
46
+ moveKind: { default: null },
47
+ initials: { default: null },
48
+ provenance: { default: "user" },
49
+ suggestionId: { default: null }
33
50
  },
34
51
  inclusive: false,
35
52
  parseDOM: [{
@@ -43,20 +60,23 @@ const InsertionExtension = createMarkExtension({
43
60
  }
44
61
  }],
45
62
  toDOM(mark) {
46
- const { revisionId, author, date } = expectTrackedChangeMarkAttrs(mark);
63
+ const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
47
64
  const idx = getAuthorColorIdx(author);
48
65
  const color = AUTHOR_COLORS[idx] ?? "#000000";
49
66
  const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
67
+ const suggested = provenance === "suggested";
50
68
  return [
51
69
  "span",
52
70
  {
53
- class: "docx-insertion",
71
+ class: suggested ? "docx-insertion docx-insertion--suggested" : "docx-insertion",
54
72
  "data-revision-id": String(revisionId),
55
73
  "data-author": author,
56
74
  "data-tc-author-idx": String(idx),
75
+ ...suggested ? { "data-provenance": "suggested" } : {},
76
+ ...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
57
77
  ...date ? { "data-date": date } : {},
58
- ...titleParts.length > 0 ? { title: `Inserted: ${titleParts.join(", ")}` } : {},
59
- style: insertionStyle(color)
78
+ ...titleParts.length > 0 ? { title: `${suggested ? "Suggested" : "Inserted"}: ${titleParts.join(", ")}` } : {},
79
+ style: suggested ? suggestedInsertionStyle() : insertionStyle(color)
60
80
  },
61
81
  0
62
82
  ];
@@ -75,7 +95,10 @@ const DeletionExtension = createMarkExtension({
75
95
  revisionId: { default: 0 },
76
96
  author: { default: "" },
77
97
  date: { default: null },
78
- moveKind: { default: null }
98
+ moveKind: { default: null },
99
+ initials: { default: null },
100
+ provenance: { default: "user" },
101
+ suggestionId: { default: null }
79
102
  },
80
103
  inclusive: false,
81
104
  parseDOM: [{
@@ -89,20 +112,56 @@ const DeletionExtension = createMarkExtension({
89
112
  }
90
113
  }],
91
114
  toDOM(mark) {
92
- const { revisionId, author, date } = expectTrackedChangeMarkAttrs(mark);
115
+ const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
93
116
  const idx = getAuthorColorIdx(author);
94
117
  const color = AUTHOR_COLORS[idx] ?? "#000000";
95
118
  const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
119
+ const suggested = provenance === "suggested";
96
120
  return [
97
121
  "span",
98
122
  {
99
- class: "docx-deletion",
123
+ class: suggested ? "docx-deletion docx-deletion--suggested" : "docx-deletion",
100
124
  "data-revision-id": String(revisionId),
101
125
  "data-author": author,
102
126
  "data-tc-author-idx": String(idx),
127
+ ...suggested ? { "data-provenance": "suggested" } : {},
128
+ ...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
103
129
  ...date ? { "data-date": date } : {},
104
- ...titleParts.length > 0 ? { title: `Deleted: ${titleParts.join(", ")}` } : {},
105
- style: deletionStyle(color)
130
+ ...titleParts.length > 0 ? { title: `${suggested ? "Suggested deletion" : "Deleted"}: ${titleParts.join(", ")}` } : {},
131
+ style: suggested ? suggestedDeletionStyle() : deletionStyle(color)
132
+ },
133
+ 0
134
+ ];
135
+ }
136
+ }
137
+ });
138
+ /**
139
+ * Run property change — formatting changed while review tracking was active.
140
+ *
141
+ * The current formatting remains represented by the normal formatting marks;
142
+ * this mark carries the previous run properties and revision metadata needed
143
+ * to serialize, list, accept, and reject the change.
144
+ */
145
+ const RunPropertyChangeExtension = createMarkExtension({
146
+ name: "runPropertyChange",
147
+ schemaMarkName: "runPropertyChange",
148
+ markSpec: {
149
+ attrs: {
150
+ changes: { default: [] },
151
+ provenance: { default: "user" },
152
+ suggestionId: { default: null }
153
+ },
154
+ inclusive: false,
155
+ parseDOM: [{ tag: "span.docx-run-property-change" }],
156
+ toDOM(mark) {
157
+ const suggested = mark.attrs["provenance"] === "suggested";
158
+ const suggestionId = mark.attrs["suggestionId"];
159
+ return [
160
+ "span",
161
+ {
162
+ class: suggested ? "docx-run-property-change docx-run-property-change--suggested" : "docx-run-property-change",
163
+ ...suggested ? { "data-provenance": "suggested" } : {},
164
+ ...suggested && typeof suggestionId === "string" ? { "data-suggestion-id": suggestionId } : {}
106
165
  },
107
166
  0
108
167
  ];
@@ -110,4 +169,4 @@ const DeletionExtension = createMarkExtension({
110
169
  }
111
170
  });
112
171
  //#endregion
113
- export { DeletionExtension, InsertionExtension };
172
+ export { DeletionExtension, InsertionExtension, RunPropertyChangeExtension };
@@ -1,6 +1,6 @@
1
- import { parseFieldInstruction } from "../../../docx/fieldParser.js";
2
1
  import { expectFieldAttrs } from "../../attrs/index.js";
3
2
  import { createNodeExtension } from "../create.js";
3
+ import { parseFieldInstruction } from "../../../docx/fieldParser.js";
4
4
  //#region src/prosemirror/extensions/nodes/FieldExtension.ts
5
5
  /**
6
6
  * Field Extension — inline field node (PAGE, NUMPAGES, DATE, MERGEFIELD, etc.)
@@ -0,0 +1,7 @@
1
+ import { NodeExtension } from "../types.js";
2
+
3
+ //#region src/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts
4
+ /** Zero-width cached pagination boundary preserved from DOCX. */
5
+ declare const RenderedPageBreakExtension: (options?: Partial<Record<string, unknown>> | undefined) => NodeExtension;
6
+ //#endregion
7
+ export { RenderedPageBreakExtension };
@@ -0,0 +1,22 @@
1
+ import { createNodeExtension } from "../create.js";
2
+ //#region src/prosemirror/extensions/nodes/RenderedPageBreakExtension.ts
3
+ /** Zero-width cached pagination boundary preserved from DOCX. */
4
+ const RenderedPageBreakExtension = createNodeExtension({
5
+ name: "renderedPageBreak",
6
+ schemaNodeName: "renderedPageBreak",
7
+ nodeSpec: {
8
+ inline: true,
9
+ group: "inline",
10
+ atom: true,
11
+ selectable: false,
12
+ parseDOM: [{ tag: "span[data-docx-rendered-page-break]" }],
13
+ toDOM() {
14
+ return ["span", {
15
+ "data-docx-rendered-page-break": "true",
16
+ style: "display: none;"
17
+ }];
18
+ }
19
+ }
20
+ });
21
+ //#endregion
22
+ export { RenderedPageBreakExtension };
@@ -1,5 +1,5 @@
1
- import { AnyExtension, Extension, NodeExtension } from "../types.js";
2
1
  import { ColorValue } from "../../../types/colors.js";
2
+ import { AnyExtension, Extension, NodeExtension } from "../types.js";
3
3
  import { Command, EditorState } from "prosemirror-state";
4
4
  import { Node } from "prosemirror-model";
5
5