react-native-enriched-markdown-scaffold 0.7.4-scaffold.9 → 1.0.2-scaffold.2

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 (469) hide show
  1. package/README.md +54 -8
  2. package/ReactNativeEnrichedMarkdown.podspec +82 -73
  3. package/android/build.gradle +186 -1
  4. package/android/consumer-rules.pro +1 -1
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +9 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
  7. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +24 -0
  8. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +8 -0
  9. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +9 -0
  10. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +35 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +886 -23
  15. package/android/src/main/baseline-prof.txt +1 -1
  16. package/android/src/main/cpp/jni-adapter.cpp +74 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +189 -13
  18. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +17 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +50 -0
  20. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +48 -1
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +10 -1
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +42 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +82 -4
  24. package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyPressEvent.kt +24 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +54 -4
  26. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +901 -462
  27. package/android/src/main/java/com/swmansion/enriched/markdown/input/MarkdownClipboard.kt +42 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/BlockEditCoordinator.kt +196 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/ClipboardCoordinator.kt +73 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditContext.kt +19 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditPipeline.kt +248 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditSession.kt +52 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +101 -7
  34. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/LinkCoordinator.kt +69 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MarkdownTextWatcher.kt +3 -3
  36. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MentionCoordinator.kt +118 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnChangeStateEvent.kt +26 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnContextMenuItemPressEvent.kt +26 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnInputKeyPressEvent.kt +24 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnPasteEvent.kt +24 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockStore.kt +254 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +80 -87
  43. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputFormatter.kt +109 -1
  44. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +116 -22
  45. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/MarkdownSerializer.kt +167 -38
  46. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/RangeEditAdjustment.kt +149 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt +66 -5
  48. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +4 -2
  49. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockRange.kt +27 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockType.kt +48 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/FormattingRange.kt +9 -4
  52. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/InputFormatterStyle.kt +39 -2
  53. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathAtom.kt +20 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathChipStyle.kt +1 -1
  55. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MutableRangeBounds.kt +11 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputBulletSpan.kt +82 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputHeadingSpan.kt +58 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputListItemSpacingSpan.kt +37 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputOrderedListMarkerSpan.kt +63 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/BlockHandler.kt +54 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/HeadingBlockHandler.kt +27 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/OrderedListBlockHandler.kt +38 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/UnorderedListBlockHandler.kt +37 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +8 -26
  65. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +1 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +1 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +9 -2
  68. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +100 -12
  69. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +16 -1
  70. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +4 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +2 -1
  72. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +74 -18
  73. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +43 -4
  74. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +9 -1
  75. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SoftBreakRenderer.kt +16 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +30 -27
  77. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +24 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +174 -24
  79. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +27 -3
  80. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +13 -16
  81. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +1 -1
  82. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +1 -1
  83. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +182 -46
  84. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListItemSpacingSpan.kt +33 -0
  85. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListMarkerAnchorSpan.kt +38 -0
  86. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +2 -0
  87. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +1 -1
  88. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt +3 -2
  89. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +3 -2
  90. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +6 -0
  91. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +26 -0
  92. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +7 -1
  93. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +4 -1
  94. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +13 -4
  95. package/android/src/main/java/com/swmansion/enriched/markdown/styles/TableStyle.kt +6 -0
  96. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockHighlighter.kt +90 -0
  97. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockNode.kt +57 -0
  98. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/FeatureFlags.kt +1 -0
  99. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownSegment.kt +9 -0
  100. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +37 -7
  101. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/ReadableMapExtensions.kt +3 -0
  102. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/RenderedSegment.kt +10 -0
  103. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/SegmentSignature.kt +1 -0
  104. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/StreamingMarkdownFilter.kt +83 -5
  105. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/serialization/MarkdownASTSerializer.kt +4 -0
  106. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/MarkdownStyleParser.kt +74 -44
  107. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +20 -0
  108. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageCache.kt +26 -3
  109. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageDownloader.kt +22 -9
  110. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/LocalImageLoader.kt +161 -0
  111. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/extensions/TextPaintExtensions.kt +23 -0
  112. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/CheckboxTouchHelper.kt +7 -0
  113. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/TaskListTapUtils.kt +41 -21
  114. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/BlockSpacing.kt +15 -8
  115. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +58 -27
  116. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +49 -15
  117. package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +45 -0
  118. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +396 -0
  119. package/android/src/main/java/com/swmansion/enriched/markdown/views/ContextMenuPopup.kt +9 -6
  120. package/android/src/main/java/com/swmansion/enriched/markdown/views/CopyGlyph.kt +101 -0
  121. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +36 -9
  122. package/android/src/main/jni/CMakeLists.txt +53 -0
  123. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.cpp +10 -0
  124. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.h +11 -2
  125. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +10 -0
  126. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +12 -2
  127. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  128. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +24 -0
  129. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +16 -0
  130. package/android/src/math/java/com/swmansion/enriched/markdown/input/math/MathChipControllerImpl.kt +21 -4
  131. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +7 -2
  132. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathChipSpan.kt +1 -1
  133. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +6 -4
  134. package/cpp/highlight/CodeBlockHighlighter.cpp +284 -0
  135. package/cpp/highlight/CodeBlockHighlighter.hpp +66 -0
  136. package/cpp/highlight/CodeBlockLanguages.cpp +138 -0
  137. package/cpp/highlight/CodeBlockLanguages.hpp +25 -0
  138. package/cpp/highlight/HighlightGrammars.hpp +28 -0
  139. package/cpp/highlight/code_highlight_podspec.rb +211 -0
  140. package/cpp/highlight/gen-registry.mjs +155 -0
  141. package/cpp/highlight/grammar-versions.json +159 -0
  142. package/cpp/highlight/vendor/generated/generated_queries.h +1665 -0
  143. package/cpp/highlight/vendor/generated/generated_registry.cpp +59 -0
  144. package/cpp/parser/MD4CParser.cpp +173 -61
  145. package/cpp/parser/MD4CParser.hpp +1 -0
  146. package/cpp/parser/MarkdownASTNode.hpp +2 -1
  147. package/cpp/wasm/ASTSerializer.cpp +2 -0
  148. package/cpp/wasm/build.sh +1 -0
  149. package/cpp/wasm/md4c_wasm.cpp +3 -1
  150. package/docs/ACCESSIBILITY.md +14 -2
  151. package/docs/API_REFERENCE.md +234 -12
  152. package/docs/BREAKING_CHANGES.md +129 -0
  153. package/docs/CODE_HIGHLIGHT.md +133 -0
  154. package/docs/COPY_OPTIONS.md +47 -3
  155. package/docs/ELEMENTS_STRUCTURE.md +52 -4
  156. package/docs/IMAGE_CACHING.md +30 -0
  157. package/docs/INPUT.md +108 -11
  158. package/docs/LATEX_MATH.md +87 -45
  159. package/docs/MACOS.md +1 -1
  160. package/docs/MARKDOWN_STREAMING.md +27 -1
  161. package/docs/NATIVE_ASSETS.md +127 -0
  162. package/docs/RTL.md +1 -1
  163. package/docs/STYLES.md +46 -6
  164. package/docs/TESTING.md +112 -0
  165. package/docs/TEXT.md +16 -6
  166. package/docs/WEB.md +3 -1
  167. package/ios/EnrichedMarkdown.h +7 -0
  168. package/ios/EnrichedMarkdown.mm +284 -75
  169. package/ios/EnrichedMarkdownText.h +7 -0
  170. package/ios/EnrichedMarkdownText.mm +101 -73
  171. package/ios/attachments/ENRMImageAttachment.h +9 -0
  172. package/ios/attachments/ENRMImageAttachment.m +143 -40
  173. package/ios/attachments/ENRMMathInlineAttachment.h +2 -0
  174. package/ios/attachments/ENRMMathInlineAttachment.m +48 -81
  175. package/ios/attachments/ENRMMathInlineAttachmentShared.h +2 -0
  176. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
  177. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
  178. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +35 -0
  179. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +886 -23
  180. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +131 -0
  181. package/ios/input/ENRMAutoLinkDetector.h +2 -1
  182. package/ios/input/ENRMAutoLinkDetector.mm +79 -41
  183. package/ios/input/ENRMBlockEditCoordinator.h +71 -0
  184. package/ios/input/ENRMBlockEditCoordinator.mm +195 -0
  185. package/ios/input/ENRMBlockRange.h +28 -0
  186. package/ios/input/ENRMBlockRange.mm +30 -0
  187. package/ios/input/ENRMBlockStore.h +56 -0
  188. package/ios/input/ENRMBlockStore.mm +275 -0
  189. package/ios/input/ENRMClipboardCoordinator.h +45 -0
  190. package/ios/input/ENRMClipboardCoordinator.mm +120 -0
  191. package/ios/input/ENRMDetectorPipeline.h +2 -1
  192. package/ios/input/ENRMEditPipeline.h +76 -0
  193. package/ios/input/ENRMEditPipeline.mm +228 -0
  194. package/ios/input/ENRMEditSession.h +36 -0
  195. package/ios/input/ENRMEditSession.mm +63 -0
  196. package/ios/input/ENRMFormattingStore.h +17 -0
  197. package/ios/input/ENRMFormattingStore.mm +95 -105
  198. package/ios/input/ENRMInputBlockType.h +91 -0
  199. package/ios/input/ENRMInputBlockType.mm +5 -0
  200. package/ios/input/ENRMInputDecorationDrawer.h +18 -0
  201. package/ios/input/ENRMInputEventEmitter.h +56 -0
  202. package/ios/input/ENRMInputEventEmitter.mm +308 -0
  203. package/ios/input/ENRMInputFormatter.h +29 -69
  204. package/ios/input/ENRMInputFormatter.mm +216 -124
  205. package/ios/input/ENRMInputFormatterStyle.h +99 -0
  206. package/ios/input/ENRMInputFormatterStyle.mm +188 -0
  207. package/ios/input/ENRMInputLayoutManager.h +9 -0
  208. package/ios/input/ENRMInputLayoutManager.mm +30 -0
  209. package/ios/input/ENRMInputListMarkerDrawer.h +32 -0
  210. package/ios/input/ENRMInputListMarkerDrawer.mm +426 -0
  211. package/ios/input/ENRMInputParser.h +5 -6
  212. package/ios/input/ENRMInputParser.mm +461 -178
  213. package/ios/input/ENRMInputRemend.mm +2 -2
  214. package/ios/input/ENRMInputStyleStateBuilder.h +40 -0
  215. package/ios/input/ENRMInputStyleStateBuilder.mm +60 -0
  216. package/ios/input/ENRMInputTextView.mm +103 -8
  217. package/ios/input/ENRMInputTypingAttributesController.h +42 -0
  218. package/ios/input/ENRMInputTypingAttributesController.mm +203 -0
  219. package/ios/input/ENRMLinkCoordinator.h +41 -0
  220. package/ios/input/ENRMLinkCoordinator.mm +89 -0
  221. package/ios/input/ENRMMarkdownSerializer.h +29 -5
  222. package/ios/input/ENRMMarkdownSerializer.mm +170 -48
  223. package/ios/input/ENRMMathAtom.h +26 -0
  224. package/ios/input/ENRMMathAtom.mm +12 -0
  225. package/ios/input/ENRMMentionCoordinator.h +45 -0
  226. package/ios/input/ENRMMentionCoordinator.mm +167 -0
  227. package/ios/input/ENRMRangeEditAdjustment.h +38 -0
  228. package/ios/input/ENRMRangeEditAdjustment.mm +91 -0
  229. package/ios/input/ENRMRangeStoreUtils.h +39 -0
  230. package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +6 -6
  231. package/ios/input/EnrichedMarkdownTextInput+Internal.h +11 -3
  232. package/ios/input/EnrichedMarkdownTextInput.h +7 -1
  233. package/ios/input/EnrichedMarkdownTextInput.mm +1089 -1023
  234. package/ios/input/InputStylePropsUtils.h +109 -68
  235. package/ios/input/internals/EnrichedMarkdownTextInputShadowNode.mm +2 -1
  236. package/ios/input/styles/ENRMBlockHandler.h +46 -0
  237. package/ios/input/styles/ENRMHeadingBlockHandler.h +15 -0
  238. package/ios/input/styles/ENRMHeadingBlockHandler.mm +38 -0
  239. package/ios/input/styles/ENRMOrderedListBlockHandler.h +14 -0
  240. package/ios/input/styles/ENRMOrderedListBlockHandler.mm +48 -0
  241. package/ios/input/styles/ENRMUnorderedListBlockHandler.h +15 -0
  242. package/ios/input/styles/ENRMUnorderedListBlockHandler.mm +51 -0
  243. package/ios/internals/ENRMViewFreeMeasurement.h +331 -0
  244. package/ios/internals/EnrichedMarkdownShadowNode.h +1 -3
  245. package/ios/internals/EnrichedMarkdownShadowNode.mm +16 -20
  246. package/ios/internals/EnrichedMarkdownTextShadowNode.h +1 -3
  247. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +15 -20
  248. package/ios/internals/MeasurementCache.h +33 -7
  249. package/ios/internals/ShadowMeasurementUtils.h +63 -58
  250. package/ios/math/ENRMMathChipController.h +74 -0
  251. package/ios/math/ENRMMathChipController.mm +476 -0
  252. package/ios/math/ENRMRaTeXBridge.swift +4 -1
  253. package/ios/parser/ENRMMarkdownParser.h +1 -8
  254. package/ios/parser/ENRMMarkdownParser.mm +2 -11
  255. package/ios/parser/MarkdownASTNode.h +2 -1
  256. package/ios/parser/MarkdownParserBridge.mm +4 -0
  257. package/ios/renderer/AttributedRenderer.m +20 -2
  258. package/ios/renderer/BaseRenderer.h +17 -2
  259. package/ios/renderer/BaseRenderer.m +30 -0
  260. package/ios/renderer/BlockquoteRenderer.h +1 -1
  261. package/ios/renderer/BlockquoteRenderer.m +144 -35
  262. package/ios/renderer/CodeBlockRenderer.h +1 -1
  263. package/ios/renderer/CodeBlockRenderer.m +27 -23
  264. package/ios/renderer/CodeRenderer.h +1 -1
  265. package/ios/renderer/CodeRenderer.m +3 -1
  266. package/ios/renderer/ENRMImageRenderer.h +1 -1
  267. package/ios/renderer/ENRMImageRenderer.m +5 -1
  268. package/ios/renderer/ENRMMathInlineRenderer.h +1 -1
  269. package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
  270. package/ios/renderer/ENRMSpoilerRenderer.h +1 -1
  271. package/ios/renderer/ENRMSpoilerRenderer.m +3 -1
  272. package/ios/renderer/EmphasisRenderer.h +1 -1
  273. package/ios/renderer/EmphasisRenderer.m +3 -1
  274. package/ios/renderer/HeadingRenderer.h +1 -1
  275. package/ios/renderer/HeadingRenderer.m +13 -6
  276. package/ios/renderer/HighlightRenderer.h +1 -1
  277. package/ios/renderer/HighlightRenderer.m +3 -1
  278. package/ios/renderer/LinkRenderer.h +1 -1
  279. package/ios/renderer/LinkRenderer.m +3 -1
  280. package/ios/renderer/ListItemRenderer.h +13 -1
  281. package/ios/renderer/ListItemRenderer.m +102 -44
  282. package/ios/renderer/ListRenderer.h +2 -2
  283. package/ios/renderer/ListRenderer.m +63 -25
  284. package/ios/renderer/ParagraphRenderer.h +1 -1
  285. package/ios/renderer/ParagraphRenderer.m +16 -8
  286. package/ios/renderer/RenderContext.h +10 -0
  287. package/ios/renderer/RenderContext.m +44 -0
  288. package/ios/renderer/RendererFactory.m +14 -0
  289. package/ios/renderer/StrikethroughRenderer.h +1 -1
  290. package/ios/renderer/StrikethroughRenderer.m +3 -1
  291. package/ios/renderer/StrongRenderer.h +1 -1
  292. package/ios/renderer/StrongRenderer.m +3 -1
  293. package/ios/renderer/SubscriptRenderer.h +1 -1
  294. package/ios/renderer/SubscriptRenderer.m +3 -1
  295. package/ios/renderer/SuperscriptRenderer.h +1 -1
  296. package/ios/renderer/SuperscriptRenderer.m +3 -1
  297. package/ios/renderer/TextRenderer.h +2 -2
  298. package/ios/renderer/TextRenderer.m +3 -1
  299. package/ios/renderer/ThematicBreakRenderer.h +1 -1
  300. package/ios/renderer/ThematicBreakRenderer.m +3 -1
  301. package/ios/renderer/UnderlineRenderer.h +1 -1
  302. package/ios/renderer/UnderlineRenderer.m +3 -1
  303. package/ios/styles/StyleConfig.h +22 -0
  304. package/ios/styles/StyleConfig.mm +132 -0
  305. package/ios/utils/BlockquoteBorder.h +6 -0
  306. package/ios/utils/BlockquoteBorder.m +249 -52
  307. package/ios/utils/CodeBlockBackground.m +16 -2
  308. package/ios/utils/ENRMAtomicSize.h +65 -0
  309. package/ios/utils/ENRMBlockMenu.h +57 -0
  310. package/ios/utils/ENRMCodeBlockContent.h +37 -0
  311. package/ios/utils/ENRMCodeBlockContent.mm +79 -0
  312. package/ios/utils/ENRMCodeBlockHighlighter.h +37 -0
  313. package/ios/utils/ENRMCodeBlockHighlighter.mm +51 -0
  314. package/ios/utils/ENRMFeatureFlags.h +7 -0
  315. package/ios/utils/ENRMImageDownloader.h +11 -1
  316. package/ios/utils/ENRMImageDownloader.m +60 -16
  317. package/ios/utils/ENRMLocalImageLoader.h +31 -0
  318. package/ios/utils/ENRMLocalImageLoader.m +68 -0
  319. package/ios/utils/ENRMMathFallback.h +26 -0
  320. package/ios/utils/ENRMMathFallback.m +18 -0
  321. package/ios/utils/ENRMTextViewSetup.h +35 -17
  322. package/ios/utils/EditMenuUtils.h +22 -5
  323. package/ios/utils/EditMenuUtils.m +53 -9
  324. package/ios/utils/ImageRequestHeaderUtils.h +35 -0
  325. package/ios/utils/LastElementUtils.h +1 -0
  326. package/ios/utils/ListMarkerDrawer.m +29 -42
  327. package/ios/utils/MarkdownASTSerializer.m +4 -0
  328. package/ios/utils/ParagraphStyleUtils.h +5 -0
  329. package/ios/utils/ParagraphStyleUtils.m +77 -17
  330. package/ios/utils/RenderedMarkdownSegment.h +13 -1
  331. package/ios/utils/RenderedMarkdownSegment.m +20 -0
  332. package/ios/utils/SegmentRenderer.m +17 -4
  333. package/ios/utils/StreamingMarkdownFilter.h +11 -1
  334. package/ios/utils/StreamingMarkdownFilter.m +98 -2
  335. package/ios/utils/StylePropsUtils.h +63 -0
  336. package/ios/utils/TaskListTapUtils.m +24 -0
  337. package/ios/views/ENRMCodeBlockContainerView.h +53 -0
  338. package/ios/views/ENRMCodeBlockContainerView.m +559 -0
  339. package/ios/views/ENRMMathContainerView.h +16 -0
  340. package/ios/views/ENRMMathContainerView.m +74 -19
  341. package/ios/views/TableContainerView.h +22 -0
  342. package/ios/views/TableContainerView.m +261 -175
  343. package/jest.d.ts +1 -0
  344. package/jest.js +5 -0
  345. package/lib/module/EnrichedMarkdownNativeComponent.ts +73 -0
  346. package/lib/module/EnrichedMarkdownTextInput.js +84 -5
  347. package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
  348. package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +78 -5
  349. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +74 -0
  350. package/lib/module/headingDefaults.js +34 -0
  351. package/lib/module/headingDefaults.js.map +1 -0
  352. package/lib/module/index.js.map +1 -1
  353. package/lib/module/jest/index.js +191 -0
  354. package/lib/module/jest/index.js.map +1 -0
  355. package/lib/module/native/EnrichedMarkdownText.js +37 -5
  356. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  357. package/lib/module/normalizeMarkdownStyle.js +81 -19
  358. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  359. package/lib/module/normalizeMarkdownStyle.web.js +56 -18
  360. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  361. package/lib/module/normalizeMarkdownTextInputStyle.js +62 -15
  362. package/lib/module/normalizeMarkdownTextInputStyle.js.map +1 -1
  363. package/lib/module/normalizeMenuItem.js +1 -1
  364. package/lib/module/utils/textInputState.js +26 -0
  365. package/lib/module/utils/textInputState.js.map +1 -0
  366. package/lib/module/web/EnrichedMarkdownText.js +9 -5
  367. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  368. package/lib/module/web/parseMarkdown.js +4 -3
  369. package/lib/module/web/parseMarkdown.js.map +1 -1
  370. package/lib/module/web/renderers/InlineRenderers.js +6 -0
  371. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  372. package/lib/module/web/renderers/ListRenderers.js +7 -2
  373. package/lib/module/web/renderers/ListRenderers.js.map +1 -1
  374. package/lib/module/web/renderers/index.js +7 -4
  375. package/lib/module/web/renderers/index.js.map +1 -1
  376. package/lib/module/web/styles.js +83 -6
  377. package/lib/module/web/styles.js.map +1 -1
  378. package/lib/module/web/utils.js +1 -0
  379. package/lib/module/web/utils.js.map +1 -1
  380. package/lib/module/web/wasm/md4c.js +0 -0
  381. package/lib/module/web/wasm/md4c.js.map +1 -1
  382. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +66 -0
  383. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  384. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +94 -9
  385. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
  386. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +69 -5
  387. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
  388. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +66 -0
  389. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  390. package/lib/typescript/src/headingDefaults.d.ts +9 -0
  391. package/lib/typescript/src/headingDefaults.d.ts.map +1 -0
  392. package/lib/typescript/src/index.d.ts +2 -2
  393. package/lib/typescript/src/index.d.ts.map +1 -1
  394. package/lib/typescript/src/index.web.d.ts +1 -1
  395. package/lib/typescript/src/index.web.d.ts.map +1 -1
  396. package/lib/typescript/src/jest/index.d.ts +6 -0
  397. package/lib/typescript/src/jest/index.d.ts.map +1 -0
  398. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  399. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  400. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  401. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  402. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts +19 -5
  403. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts.map +1 -1
  404. package/lib/typescript/src/types/MarkdownStyle.d.ts +72 -0
  405. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  406. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +27 -0
  407. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  408. package/lib/typescript/src/types/MarkdownTextProps.d.ts +104 -9
  409. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  410. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +19 -0
  411. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  412. package/lib/typescript/src/types/events.d.ts +19 -0
  413. package/lib/typescript/src/types/events.d.ts.map +1 -1
  414. package/lib/typescript/src/utils/textInputState.d.ts +24 -0
  415. package/lib/typescript/src/utils/textInputState.d.ts.map +1 -0
  416. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  417. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  418. package/lib/typescript/src/web/parseMarkdown.d.ts +1 -1
  419. package/lib/typescript/src/web/parseMarkdown.d.ts.map +1 -1
  420. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  421. package/lib/typescript/src/web/renderers/ListRenderers.d.ts.map +1 -1
  422. package/lib/typescript/src/web/renderers/index.d.ts +2 -1
  423. package/lib/typescript/src/web/renderers/index.d.ts.map +1 -1
  424. package/lib/typescript/src/web/styles.d.ts +2 -1
  425. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  426. package/lib/typescript/src/web/types.d.ts +6 -2
  427. package/lib/typescript/src/web/types.d.ts.map +1 -1
  428. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  429. package/package.json +36 -9
  430. package/postinstall.mjs +131 -0
  431. package/ratex-version.json +21 -0
  432. package/src/EnrichedMarkdownNativeComponent.ts +73 -0
  433. package/src/EnrichedMarkdownTextInput.tsx +159 -8
  434. package/src/EnrichedMarkdownTextInputNativeComponent.ts +78 -5
  435. package/src/EnrichedMarkdownTextNativeComponent.ts +74 -0
  436. package/src/headingDefaults.ts +21 -0
  437. package/src/index.tsx +9 -0
  438. package/src/index.web.tsx +1 -0
  439. package/src/jest/index.tsx +215 -0
  440. package/src/native/EnrichedMarkdownText.tsx +53 -2
  441. package/src/normalizeMarkdownStyle.ts +102 -16
  442. package/src/normalizeMarkdownStyle.web.ts +63 -15
  443. package/src/normalizeMarkdownTextInputStyle.ts +110 -35
  444. package/src/normalizeMenuItem.ts +1 -1
  445. package/src/types/MarkdownStyle.ts +74 -0
  446. package/src/types/MarkdownStyleInternal.ts +39 -0
  447. package/src/types/MarkdownTextProps.ts +101 -9
  448. package/src/types/MarkdownTextProps.web.ts +19 -0
  449. package/src/types/events.ts +21 -0
  450. package/src/utils/textInputState.ts +35 -0
  451. package/src/web/EnrichedMarkdownText.tsx +16 -2
  452. package/src/web/parseMarkdown.ts +6 -2
  453. package/src/web/renderers/InlineRenderers.tsx +5 -0
  454. package/src/web/renderers/ListRenderers.tsx +14 -2
  455. package/src/web/renderers/index.tsx +6 -2
  456. package/src/web/styles.ts +89 -5
  457. package/src/web/types.ts +6 -1
  458. package/src/web/utils.ts +1 -0
  459. package/src/web/wasm/md4c.js +0 -0
  460. package/vendor-grammars.mjs +511 -0
  461. package/vendor-ratex.mjs +173 -0
  462. package/app.plugin.js +0 -2
  463. package/plugin/build/withAndroidMath.js +0 -19
  464. package/plugin/build/withIosMath.js +0 -28
  465. package/plugin/build/withReactNativeEnrichedMarkdown.js +0 -11
  466. package/plugin/src/withAndroidMath.ts +0 -24
  467. package/plugin/src/withIosMath.ts +0 -35
  468. package/plugin/src/withReactNativeEnrichedMarkdown.ts +0 -17
  469. package/plugin/tsconfig.build.json +0 -16
@@ -1,33 +1,47 @@
1
1
  package com.swmansion.enriched.markdown.input.formatting
2
2
 
3
+ import com.swmansion.enriched.markdown.input.model.BlockRange
4
+ import com.swmansion.enriched.markdown.input.model.BlockType
3
5
  import com.swmansion.enriched.markdown.input.model.FormattingRange
6
+ import com.swmansion.enriched.markdown.input.model.MAX_LIST_DEPTH
4
7
  import com.swmansion.enriched.markdown.input.model.MathAtom
8
+ import com.swmansion.enriched.markdown.input.model.OBJECT_REPLACEMENT
5
9
  import com.swmansion.enriched.markdown.input.model.StyleType
6
10
  import com.swmansion.enriched.markdown.parser.MarkdownASTNode
7
11
  import com.swmansion.enriched.markdown.parser.MarkdownASTNode.NodeType
8
12
  import com.swmansion.enriched.markdown.parser.Md4cFlags
9
13
  import com.swmansion.enriched.markdown.parser.Parser
10
14
  import com.swmansion.enriched.markdown.parser.isTopLevelBlock
11
- import com.swmansion.enriched.markdown.utils.common.FeatureFlags
12
15
 
13
16
  data class ParseResult(
14
17
  val plainText: String,
15
18
  val formattingRanges: List<FormattingRange>,
19
+ val blockRanges: List<BlockRange> = emptyList(),
20
+ /**
21
+ * Math nodes, each already represented in [plainText] by a single
22
+ * [OBJECT_REPLACEMENT] character at [MathAtom.position]. The caller turns them
23
+ * into chips via [com.swmansion.enriched.markdown.input.math.MathChipController.applyAtoms];
24
+ * with math disabled md4c never emits the nodes and this stays empty.
25
+ */
16
26
  val mathAtoms: List<MathAtom> = emptyList(),
17
27
  )
18
28
 
19
29
  object InputParser {
20
- private const val ATTACHMENT_CHAR = ''
21
-
22
30
  // A source paragraph break: a newline followed by one or more blank lines.
23
31
  private val PARAGRAPH_BREAK_RE = Regex("\\n[ \\t]*(?:\\n[ \\t]*)+")
24
32
 
25
33
  fun parseToPlainTextAndRanges(
26
- markdown: String,
34
+ rawMarkdown: String,
27
35
  blockSyntax: Boolean = true,
28
36
  ): ParseResult {
37
+ // U+200B is the editor's reserved empty-list anchor and is stripped from every
38
+ // output path; strip it from incoming markdown too (web paste, JS-supplied
39
+ // values) so a foreign copy can't seed a stray anchor into the buffer. Done
40
+ // before parsing so plain-text offsets and ranges stay consistent — and before
41
+ // the blockSyntax branch, so both paths see the same scrubbed source.
42
+ val markdown = MarkdownSerializer.stripZwsp(rawMarkdown)
29
43
  if (markdown.isEmpty()) {
30
- return ParseResult("", emptyList())
44
+ return ParseResult("", emptyList(), emptyList())
31
45
  }
32
46
 
33
47
  if (!blockSyntax) {
@@ -74,7 +88,12 @@ object InputParser {
74
88
  if (position < 0 || position >= decoded.length) null else atom.copy(position = position)
75
89
  }
76
90
 
77
- return ParseResult(decoded, ranges, atoms)
91
+ // Block ranges are dropped, not shifted: with block syntax off there are no blocks.
92
+ // Under the sentinel the AST holds only Paragraphs and top-level display math, and
93
+ // neither yields a range, so this is a guard rather than a fix — a range that did leak
94
+ // would reach the BlockStore and the serializer would prefix it, breaking the
95
+ // byte-for-byte round trip.
96
+ return ParseResult(decoded, ranges, emptyList(), atoms)
78
97
  }
79
98
 
80
99
  private fun parseCompleted(
@@ -82,16 +101,12 @@ object InputParser {
82
101
  fallback: String,
83
102
  blockSeparators: Boolean,
84
103
  ): ParseResult {
85
- val flags =
86
- Md4cFlags(
87
- underline = true,
88
- latexMath = FeatureFlags.IS_MATH_ENABLED,
89
- permissiveAutolinks = false,
90
- )
91
- val ast = Parser.shared.parseMarkdown(completed, flags) ?: return ParseResult(fallback, emptyList())
104
+ val flags = Md4cFlags(underline = true, permissiveAutolinks = false)
105
+ val ast = Parser.shared.parseMarkdown(completed, flags) ?: return ParseResult(fallback, emptyList(), emptyList())
92
106
 
93
107
  val plainText = StringBuilder()
94
108
  val ranges = mutableListOf<FormattingRange>()
109
+ val blockRanges = mutableListOf<BlockRange>()
95
110
  val mathAtoms = mutableListOf<MathAtom>()
96
111
 
97
112
  // md4c collapses any blank-line run into a single break, unlike iOS which keeps them.
@@ -108,28 +123,34 @@ object InputParser {
108
123
  ArrayDeque()
109
124
  }
110
125
 
111
- walkNode(ast, plainText, ranges, mathAtoms, ArrayDeque(), blankRuns, blockSeparators)
126
+ walkNode(ast, plainText, ranges, blockRanges, mathAtoms, ArrayDeque(), blankRuns, 0, false, blockSeparators)
112
127
 
113
- return ParseResult(plainText.toString(), ranges, mathAtoms)
128
+ return ParseResult(plainText.toString(), ranges, blockRanges, mathAtoms)
114
129
  }
115
130
 
116
131
  private fun walkNode(
117
132
  node: MarkdownASTNode,
118
133
  plainText: StringBuilder,
119
134
  ranges: MutableList<FormattingRange>,
135
+ blockRanges: MutableList<BlockRange>,
120
136
  mathAtoms: MutableList<MathAtom>,
121
137
  activeStyles: ArrayDeque<ActiveStyle>,
122
138
  blankRuns: ArrayDeque<Int>,
139
+ listDepth: Int,
140
+ orderedContainer: Boolean,
123
141
  blockSeparators: Boolean,
124
142
  ) {
143
+ // Math is atomic in the editor: the whole formula occupies one character, so it
144
+ // takes no style/block bookkeeping and its children are never walked (walking
145
+ // them would append the raw LaTeX as text). Returning here is what makes the
146
+ // chip's position a single offset the controller can hang a span on.
125
147
  if (node.type == NodeType.LatexMathInline || node.type == NodeType.LatexMathDisplay) {
126
- val latex = extractLatex(node)
127
148
  val position = plainText.length
128
- plainText.append(ATTACHMENT_CHAR)
149
+ plainText.append(OBJECT_REPLACEMENT)
129
150
  mathAtoms.add(
130
151
  MathAtom(
131
152
  position = position,
132
- latex = latex,
153
+ latex = extractLatex(node),
133
154
  displayMode = node.type == NodeType.LatexMathDisplay,
134
155
  ),
135
156
  )
@@ -143,18 +164,55 @@ object InputParser {
143
164
  activeStyles.addLast(ActiveStyle(styleType, plainText.length, url))
144
165
  }
145
166
 
167
+ // A list node increments the nesting depth of its items, so a list item's depth
168
+ // is (enclosing list nodes − 1) — matching the iOS depth derivation. Depth comes
169
+ // from this AST nesting, never from counting leading spaces.
170
+ val isListContainer = node.type == NodeType.UnorderedList || node.type == NodeType.OrderedList
171
+ val childListDepth = if (isListContainer) listDepth + 1 else listDepth
172
+ val childOrdered = if (isListContainer) node.type == NodeType.OrderedList else orderedContainer
173
+
174
+ // Each list item starts on its own line; md4c emits no separator between sibling
175
+ // items or before a nested sublist, so insert the line break ourselves.
176
+ if (node.type == NodeType.ListItem && plainText.isNotEmpty() && !plainText.endsWith("\n")) {
177
+ plainText.append("\n")
178
+ }
179
+ val itemStart = if (node.type == NodeType.ListItem) plainText.length else -1
180
+
181
+ // Block-level node: record where its text content begins so we can build a
182
+ // BlockRange on the way back out. PARAGRAPH is the implicit default and is
183
+ // dropped below, so it produces no stored block range.
184
+ val blockLevel = node.getAttribute("level")?.toIntOrNull() ?: 0
185
+ val blockType = nodeTypeToBlockType(node.type, blockLevel)
186
+ val blockStartPosition = plainText.length
187
+
146
188
  if (node.type == NodeType.Text) {
147
189
  plainText.append(node.content)
148
- } else if (node.type == NodeType.LineBreak) {
190
+ } else if (node.type == NodeType.LineBreak || node.type == NodeType.SoftBreak) {
149
191
  plainText.append("\n")
150
192
  }
151
193
 
152
194
  for ((index, child) in node.children.withIndex()) {
153
- // Keep the source's blank lines between top-level blocks (md4c drops them, iOS keeps them).
154
- if (blockSeparators && index > 0 && child.type.isTopLevelBlock() && plainText.isNotEmpty()) {
195
+ // Keep the source's blank lines between genuinely top-level blocks (md4c drops
196
+ // them, iOS keeps them). Inside a list, items are separated by a single newline
197
+ // (inserted above), not blank lines.
198
+ if (blockSeparators && index > 0 && listDepth == 0 && child.type.isTopLevelBlock() && plainText.isNotEmpty()) {
155
199
  plainText.append("\n".repeat(blankRuns.removeFirstOrNull() ?: 2))
156
200
  }
157
- walkNode(child, plainText, ranges, mathAtoms, activeStyles, blankRuns, blockSeparators)
201
+ walkNode(child, plainText, ranges, blockRanges, mathAtoms, activeStyles, blankRuns, childListDepth, childOrdered, blockSeparators)
202
+ }
203
+
204
+ // A list item owns its own first line; a nested sublist lives on later lines, so
205
+ // the item's range ends at the first newline after its start.
206
+ if (itemStart >= 0) {
207
+ var lineEnd = plainText.indexOf('\n', itemStart)
208
+ if (lineEnd < 0) lineEnd = plainText.length
209
+ if (lineEnd > itemStart) {
210
+ // The item sees listDepth already incremented by its enclosing list node, so
211
+ // its 0-based depth is listDepth − 1 (enclosing list nodes − 1, per iOS).
212
+ val depth = (listDepth - 1).coerceIn(0, MAX_LIST_DEPTH)
213
+ val itemType = if (orderedContainer) BlockType.ORDERED_LIST_ITEM else BlockType.UNORDERED_LIST_ITEM
214
+ blockRanges.add(BlockRange(itemType, itemStart, lineEnd, depth))
215
+ }
158
216
  }
159
217
 
160
218
  if (styleType != null) {
@@ -164,8 +222,22 @@ object InputParser {
164
222
  ranges.add(FormattingRange(styleType, activeStyle.startPosition, end, activeStyle.url))
165
223
  }
166
224
  }
225
+
226
+ // Emit the block range for handler-claimed block types only; PARAGRAPH (the
227
+ // implicit default) yields nothing. List items are emitted above with their own
228
+ // line bounds, so they are excluded here.
229
+ if (blockType != null && blockType != BlockType.PARAGRAPH && blockType !in BlockType.LIST_ITEMS) {
230
+ val end = plainText.length
231
+ if (end > blockStartPosition) {
232
+ blockRanges.add(BlockRange(blockType, blockStartPosition, end, blockLevel))
233
+ }
234
+ }
167
235
  }
168
236
 
237
+ /**
238
+ * LaTeX source of a math node. md4c may deliver it either as the node's own
239
+ * content or split across text children, so both are handled.
240
+ */
169
241
  private fun extractLatex(node: MarkdownASTNode): String {
170
242
  if (node.content.isNotEmpty()) return node.content
171
243
  if (node.children.isEmpty()) return ""
@@ -187,6 +259,28 @@ object InputParser {
187
259
  else -> null
188
260
  }
189
261
 
262
+ /**
263
+ * Maps a parser block node to a [BlockType], mirroring [nodeTypeToStyleType]
264
+ * for inline spans. A heading maps to its per-level `HEADING_n` (from the node's
265
+ * `level` attribute); an out-of-range level falls back to PARAGRAPH so a
266
+ * malformed parse degrades gracefully rather than dropping the line.
267
+ */
268
+ private fun nodeTypeToBlockType(
269
+ nodeType: NodeType,
270
+ level: Int,
271
+ ): BlockType? =
272
+ when (nodeType) {
273
+ NodeType.Paragraph -> BlockType.PARAGRAPH
274
+
275
+ NodeType.Heading -> BlockType.forHeadingLevel(level) ?: BlockType.PARAGRAPH
276
+
277
+ // List items are recognized here but emitted with their own per-line bounds and
278
+ // AST-derived nesting depth in the walk above, not via the generic emission.
279
+ NodeType.ListItem -> BlockType.UNORDERED_LIST_ITEM
280
+
281
+ else -> null
282
+ }
283
+
190
284
  private data class ActiveStyle(
191
285
  val type: StyleType,
192
286
  val startPosition: Int,
@@ -1,68 +1,197 @@
1
1
  package com.swmansion.enriched.markdown.input.formatting
2
2
 
3
- import android.text.Spanned
3
+ import android.util.Log
4
+ import com.swmansion.enriched.markdown.input.editing.isLineBreak
4
5
  import com.swmansion.enriched.markdown.input.math.MathChipController
6
+ import com.swmansion.enriched.markdown.input.model.BlockRange
7
+ import com.swmansion.enriched.markdown.input.model.BlockType
5
8
  import com.swmansion.enriched.markdown.input.model.FormattingRange
9
+ import com.swmansion.enriched.markdown.input.model.OBJECT_REPLACEMENT
6
10
  import com.swmansion.enriched.markdown.input.model.StyleType
7
11
 
8
12
  object MarkdownSerializer {
9
- private const val ATTACHMENT_CHAR = ''
13
+ private const val TAG = "MarkdownSerializer"
14
+
15
+ // Zero-width space used by the editor to anchor an empty bullet line; it is an
16
+ // internal editing aid and must never appear in serialized markdown.
17
+ private const val ZWSP = "\u200B"
10
18
 
11
19
  /**
12
- * Serialize a [Spanned] view of the storage back to markdown. Math chips
13
- * (the `` placeholder + a controller-tracked chip) become `$latex$` or
14
- * `$$latex$$` depending on the chip's display mode. Inline formatting
15
- * ranges are still applied to character offsets in the underlying text.
20
+ * Single choke point for scrubbing the empty-line ZWSP anchor out of anything
21
+ * bound for JS (serialized markdown here, plain text in `InputEventEmitter`).
22
+ * Route new output paths through this rather than scattering `replace(ZWSP, "")`
23
+ * calls, so the anchor can never leak because one path forgot to strip.
24
+ */
25
+ fun stripZwsp(text: String): String = text.replace(ZWSP, "")
26
+
27
+ /**
28
+ * Math-aware serialization — the single output path for a buffer that may hold
29
+ * math chips. Each chip's [OBJECT_REPLACEMENT] placeholder expands back to
30
+ * `$latex$` / `$$latex$$`, and **both** the inline ranges and the block ranges
31
+ * are translated through the same position map before the block-aware overload
32
+ * runs. Translating only one of the two is the silent failure this exists to
33
+ * prevent: one placeholder char becomes n characters, so every block boundary
34
+ * after a chip would land a line off and `# `/`- `/`1. ` would prefix the wrong
35
+ * line. [stripZwsp] still happens exactly once, inside the overload below.
36
+ *
37
+ * Chips are passed as plain data (not a `Spanned` + controller) so this stays a
38
+ * pure function over `String` — which is what makes the round-trip testable
39
+ * without a live editor. Positions must be offsets into [text].
16
40
  */
17
41
  fun serialize(
18
- text: Spanned,
42
+ text: String,
43
+ chips: List<MathChipController.ChipInfo>,
19
44
  ranges: List<FormattingRange>,
20
- mathChipController: MathChipController?,
45
+ blockRanges: List<BlockRange>,
46
+ blockPrefixProvider: (BlockRange) -> String,
21
47
  ): String {
22
- if (mathChipController == null || text.length == 0) {
23
- return serialize(text.toString(), ranges)
24
- }
25
- val chips = mathChipController.allChips(text)
26
- if (chips.isEmpty()) {
27
- return serialize(text.toString(), ranges)
28
- }
48
+ if (chips.isEmpty()) return serialize(text, ranges, blockRanges, blockPrefixProvider)
29
49
 
30
50
  val expanded = StringBuilder(text.length)
51
+ // positionMap[i] = offset in `expanded` of the character that was at `text[i]`.
52
+ // Length + 1 so an exclusive end offset at text.length maps too.
31
53
  val positionMap = IntArray(text.length + 1)
32
- val chipByPosition = chips.associateBy { it.position }
33
- var expandedPos = 0
34
- var i = 0
35
- while (i < text.length) {
36
- positionMap[i] = expandedPos
37
- val c = text[i]
38
- val chip = chipByPosition[i]
39
- if (c == ATTACHMENT_CHAR && chip != null) {
40
- val delim = if (chip.displayMode) "$$" else "$"
41
- expanded.append(delim)
42
- expanded.append(chip.latex)
43
- expanded.append(delim)
44
- expandedPos += delim.length * 2 + chip.latex.length
54
+ val latexByPosition = HashMap<Int, String>(chips.size)
55
+ for (chip in chips) {
56
+ if (chip.position < 0 || chip.position >= text.length) continue
57
+ if (text[chip.position] != OBJECT_REPLACEMENT) continue
58
+ latexByPosition[chip.position] = mathDelimiters(chip)
59
+ }
60
+
61
+ for (i in text.indices) {
62
+ positionMap[i] = expanded.length
63
+ val replacement = latexByPosition[i]
64
+ if (replacement != null) {
65
+ expanded.append(replacement)
45
66
  } else {
46
- expanded.append(c)
47
- expandedPos += 1
67
+ expanded.append(text[i])
48
68
  }
49
- i++
50
69
  }
51
- positionMap[text.length] = expandedPos
70
+ positionMap[text.length] = expanded.length
52
71
 
53
- val shifted =
72
+ val shiftedRanges =
54
73
  ranges.map { range ->
55
- val rangeStart = range.start.coerceIn(0, text.length)
56
- val rangeEnd = range.end.coerceIn(0, text.length)
57
74
  FormattingRange(
58
75
  range.type,
59
- positionMap[rangeStart],
60
- positionMap[rangeEnd],
76
+ positionMap[range.start.coerceIn(0, text.length)],
77
+ positionMap[range.end.coerceIn(0, text.length)],
61
78
  range.url,
62
79
  )
63
80
  }
64
81
 
65
- return serialize(expanded.toString(), shifted)
82
+ val shiftedBlockRanges =
83
+ blockRanges.map { block ->
84
+ // `ordinal` is not a constructor default — it carries an ordered item's
85
+ // number, so rebuilding without it renumbers every "1." in the document.
86
+ BlockRange(
87
+ block.type,
88
+ positionMap[block.start.coerceIn(0, text.length)],
89
+ positionMap[block.end.coerceIn(0, text.length)],
90
+ block.level,
91
+ block.ordinal,
92
+ )
93
+ }
94
+
95
+ return serialize(expanded.toString(), shiftedRanges, shiftedBlockRanges, blockPrefixProvider)
96
+ }
97
+
98
+ /**
99
+ * The chip's markdown form, delimiters included. Line breaks inside the LaTeX are
100
+ * collapsed to spaces: a chip is one line box in the editor, and emitting a real
101
+ * newline would change the serialized line count, which trips the line-count
102
+ * invariant in [serializeWithAnchors] and silently drops every block prefix in the
103
+ * document. LaTeX is whitespace-insensitive here, so the formula is unaffected.
104
+ * A blank formula expands to nothing rather than to a bare `$$` that would
105
+ * re-parse as literal text.
106
+ */
107
+ private fun mathDelimiters(chip: MathChipController.ChipInfo): String {
108
+ val latex =
109
+ buildString(chip.latex.length) {
110
+ var previousWasBreak = false
111
+ for (char in chip.latex) {
112
+ if (char.isLineBreak()) {
113
+ if (!previousWasBreak) append(' ')
114
+ previousWasBreak = true
115
+ } else {
116
+ append(char)
117
+ previousWasBreak = false
118
+ }
119
+ }
120
+ }.trim()
121
+ if (latex.isEmpty()) return ""
122
+ val delimiter = if (chip.displayMode) "$$" else "$"
123
+ return delimiter + latex + delimiter
124
+ }
125
+
126
+ /**
127
+ * Block-aware serialization: serializes inline styles exactly as the inline-only
128
+ * overload, then prepends each line's block prefix. [blockPrefixProvider] is
129
+ * asked, per block range, for the markdown line marker (e.g. `"# "`, `"- "`);
130
+ * returning `""` leaves the line unprefixed. With empty [blockRanges] the output
131
+ * is identical to the inline-only overload. Any ZWSP empty-line anchor is stripped
132
+ * so an empty bullet still serializes to a bare `"- "` rather than `"- ​"`.
133
+ */
134
+ fun serialize(
135
+ text: String,
136
+ ranges: List<FormattingRange>,
137
+ blockRanges: List<BlockRange>,
138
+ blockPrefixProvider: (BlockRange) -> String,
139
+ ): String = stripZwsp(serializeWithAnchors(text, ranges, blockRanges, blockPrefixProvider))
140
+
141
+ private fun serializeWithAnchors(
142
+ text: String,
143
+ ranges: List<FormattingRange>,
144
+ blockRanges: List<BlockRange>,
145
+ blockPrefixProvider: (BlockRange) -> String,
146
+ ): String {
147
+ val inlineMarkdown = serialize(text, ranges)
148
+ if (blockRanges.isEmpty()) return inlineMarkdown
149
+
150
+ // Block prefixes attach per line. Inline serialization only inserts inline
151
+ // delimiters (never newlines), so the serialized output has the same line
152
+ // count as the plain text — we map a block's plain-text range to line indices
153
+ // and prefix the corresponding serialized lines.
154
+ val plainLines = text.split("\n")
155
+ val markdownLines = inlineMarkdown.split("\n").toMutableList()
156
+
157
+ // Inline delimiters never cross a newline, so the line partition is preserved.
158
+ // If this ever breaks, prefixes would land on the wrong lines. Contract on
159
+ // both platforms: log and fall back to inline-only output — a library must
160
+ // not crash the host app over lost block prefixes.
161
+ if (plainLines.size != markdownLines.size) {
162
+ Log.e(TAG, "Block serialization line-count invariant violated: plain=${plainLines.size} markdown=${markdownLines.size}")
163
+ return inlineMarkdown
164
+ }
165
+
166
+ // Plain-text character offset at the start of each line.
167
+ val lineStartOffsets = IntArray(plainLines.size)
168
+ var runningOffset = 0
169
+ for (i in plainLines.indices) {
170
+ lineStartOffsets[i] = runningOffset
171
+ runningOffset += plainLines[i].length + 1 // +1 for the '\n' separator
172
+ }
173
+
174
+ for (blockRange in blockRanges) {
175
+ val prefix = blockPrefixProvider(blockRange)
176
+ if (prefix.isEmpty()) continue
177
+
178
+ val isZeroLength = blockRange.length == 0
179
+ val isListItem = blockRange.type in BlockType.LIST_ITEMS
180
+ for (lineIndex in plainLines.indices) {
181
+ val lineStart = lineStartOffsets[lineIndex]
182
+ val lineEnd = lineStart + plainLines[lineIndex].length
183
+ val overlaps = if (isZeroLength) lineStart == blockRange.start else lineEnd >= blockRange.start && lineStart < blockRange.end
184
+ if (overlaps) {
185
+ // A marker-only list line ("- " with no content) re-parses as a setext
186
+ // underline for the previous line; emit an empty list line bare. An
187
+ // empty "# " heading is valid ATX and keeps its prefix.
188
+ if (isListItem && stripZwsp(plainLines[lineIndex]).isEmpty()) continue
189
+ markdownLines[lineIndex] = prefix + markdownLines[lineIndex]
190
+ }
191
+ }
192
+ }
193
+
194
+ return markdownLines.joinToString("\n")
66
195
  }
67
196
 
68
197
  fun serialize(
@@ -0,0 +1,149 @@
1
+ package com.swmansion.enriched.markdown.input.formatting
2
+
3
+ import com.swmansion.enriched.markdown.input.model.MutableRangeBounds
4
+
5
+ internal fun <T : MutableRangeBounds> sortedInsertionIndex(
6
+ ranges: List<T>,
7
+ location: Int,
8
+ ): Int {
9
+ var index = 0
10
+ for (existing in ranges) {
11
+ if (existing.start > location) break
12
+ index++
13
+ }
14
+ return index
15
+ }
16
+
17
+ /**
18
+ * Shared shift/clip logic applied to stored ranges after a text edit. Both
19
+ * [FormattingStore] and [BlockStore] delegate here so the overlap
20
+ * classification lives in exactly one place.
21
+ */
22
+ internal object RangeEditAdjustment {
23
+ /**
24
+ * Mutates [ranges] in place to follow an edit that replaced [deletedLength]
25
+ * characters at [editLocation] with [insertedLength] characters. Ranges
26
+ * deleted outright or clipped to zero length are removed.
27
+ *
28
+ * Insert-only edits at exactly `range.end` do NOT grow the range — the typed
29
+ * characters stay outside it. An insert at exactly `range.start` grows the
30
+ * range only when [growsAtStartOnInsert] returns true; otherwise the range
31
+ * shifts and the typed characters stay outside it. Whether boundary text
32
+ * otherwise joins the range is decided elsewhere: pending styles for inline
33
+ * ranges, line re-normalization for block ranges.
34
+ *
35
+ * [inheritsReplacementAtStart]: when it returns true for a range whose start
36
+ * is the edit location, replacement text joins the range (UIKit attribute
37
+ * inheritance); when false, the old clip/remove behavior applies.
38
+ *
39
+ * [growsAtStartOnInsert]: when it returns true, a pure insert at `range.start`
40
+ * grows the range to cover the inserted text instead of shifting the range
41
+ * past it. Block ranges own their whole line, so a character typed at the line
42
+ * start must keep the line's block; inline styles must NOT set this (a
43
+ * character typed before a bold run must not become bold).
44
+ */
45
+ fun <T : MutableRangeBounds> adjustForEdit(
46
+ ranges: MutableList<T>,
47
+ editLocation: Int,
48
+ deletedLength: Int,
49
+ insertedLength: Int,
50
+ inheritsReplacementAtStart: (T) -> Boolean = { false },
51
+ growsAtStartOnInsert: (T) -> Boolean = { false },
52
+ ) {
53
+ if (deletedLength == 0 && insertedLength == 0) return
54
+
55
+ val deleteEnd = editLocation + deletedLength
56
+ val indexesToRemove = mutableListOf<Int>()
57
+
58
+ for ((idx, range) in ranges.withIndex()) {
59
+ if (deletedLength > 0) {
60
+ val inheritsReplacement =
61
+ insertedLength > 0 && range.start == editLocation && inheritsReplacementAtStart(range)
62
+ when (classifyOverlap(range.start, range.end, editLocation, deleteEnd)) {
63
+ EditOverlap.BEFORE_EDIT -> { /* no change */ }
64
+
65
+ EditOverlap.AFTER_EDIT -> {
66
+ range.start = range.start - deletedLength + insertedLength
67
+ range.end = range.end - deletedLength + insertedLength
68
+ }
69
+
70
+ EditOverlap.FULLY_DELETED -> {
71
+ if (inheritsReplacement) {
72
+ range.start = editLocation
73
+ range.end = editLocation + insertedLength
74
+ } else {
75
+ indexesToRemove.add(idx)
76
+ }
77
+ }
78
+
79
+ EditOverlap.DELETED_INSIDE -> {
80
+ range.end = range.end - deletedLength + insertedLength
81
+ }
82
+
83
+ EditOverlap.CLIPPED_END -> {
84
+ val newEnd = editLocation + insertedLength
85
+ val newLength = if (newEnd > range.start) newEnd - range.start else 0
86
+ range.end = range.start + newLength
87
+ if (newLength == 0) indexesToRemove.add(idx)
88
+ }
89
+
90
+ EditOverlap.CLIPPED_START -> {
91
+ val charsClipped = deleteEnd - range.start
92
+ val survivingLength = range.end - range.start - charsClipped
93
+ if (inheritsReplacement) {
94
+ range.start = editLocation
95
+ range.end = editLocation + insertedLength + survivingLength
96
+ } else {
97
+ range.start = editLocation + insertedLength
98
+ range.end = range.start + survivingLength
99
+ if (survivingLength == 0) indexesToRemove.add(idx)
100
+ }
101
+ }
102
+ }
103
+ } else {
104
+ when {
105
+ range.start == editLocation && growsAtStartOnInsert(range) -> {
106
+ range.end += insertedLength
107
+ }
108
+
109
+ range.start >= editLocation -> {
110
+ range.start += insertedLength
111
+ range.end += insertedLength
112
+ }
113
+
114
+ editLocation > range.start && editLocation < range.end -> {
115
+ range.end += insertedLength
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ for (idx in indexesToRemove.reversed()) {
122
+ ranges.removeAt(idx)
123
+ }
124
+
125
+ ranges.removeAll { it.end - it.start == 0 }
126
+ }
127
+
128
+ private enum class EditOverlap {
129
+ BEFORE_EDIT,
130
+ AFTER_EDIT,
131
+ FULLY_DELETED,
132
+ DELETED_INSIDE,
133
+ CLIPPED_END,
134
+ CLIPPED_START,
135
+ }
136
+
137
+ private fun classifyOverlap(
138
+ rangeStart: Int,
139
+ rangeEnd: Int,
140
+ editLocation: Int,
141
+ deleteEnd: Int,
142
+ ): EditOverlap {
143
+ if (rangeEnd <= editLocation) return EditOverlap.BEFORE_EDIT
144
+ if (rangeStart >= deleteEnd) return EditOverlap.AFTER_EDIT
145
+ if (rangeStart >= editLocation && rangeEnd <= deleteEnd) return EditOverlap.FULLY_DELETED
146
+ if (rangeStart < editLocation && rangeEnd > deleteEnd) return EditOverlap.DELETED_INSIDE
147
+ return if (rangeStart < editLocation) EditOverlap.CLIPPED_END else EditOverlap.CLIPPED_START
148
+ }
149
+ }