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
@@ -0,0 +1,254 @@
1
+ package com.swmansion.enriched.markdown.input.formatting
2
+
3
+ import com.swmansion.enriched.markdown.input.model.BlockRange
4
+ import com.swmansion.enriched.markdown.input.model.BlockType
5
+ import com.swmansion.enriched.markdown.input.model.MAX_LIST_DEPTH
6
+ import java.util.Collections
7
+
8
+ /**
9
+ * Stores the block-level (paragraph-scoped) ranges for the editor, mirroring
10
+ * [FormattingStore]. Unlike inline ranges, block ranges never overlap: at most
11
+ * one block covers any given paragraph, and ranges are kept normalized to
12
+ * whole-line boundaries.
13
+ */
14
+ class BlockStore {
15
+ private val ranges = mutableListOf<BlockRange>()
16
+
17
+ val allRanges: List<BlockRange> get() = Collections.unmodifiableList(ranges)
18
+
19
+ /**
20
+ * Returns the block whose paragraph starts exactly at [lineStart], or null.
21
+ * [ranges] is kept sorted by `start`, so this is an O(log n) binary search —
22
+ * the single lookup the view funnels through instead of the linear
23
+ * `allRanges.firstOrNull { it.start == lineStart }` scans it used to repeat.
24
+ * Relies on the one-block-per-paragraph invariant (starts are unique).
25
+ */
26
+ fun blockStartingAt(lineStart: Int): BlockRange? {
27
+ var lo = 0
28
+ var hi = ranges.size - 1
29
+ while (lo <= hi) {
30
+ val mid = (lo + hi) ushr 1
31
+ val start = ranges[mid].start
32
+ when {
33
+ start < lineStart -> lo = mid + 1
34
+ start > lineStart -> hi = mid - 1
35
+ else -> return ranges[mid]
36
+ }
37
+ }
38
+ return null
39
+ }
40
+
41
+ /**
42
+ * Incoming ranges are trusted to be non-overlapping and line-scoped — the
43
+ * parser owns that invariant (md4c block structure never overlaps at the same
44
+ * nesting level, and nested containers are not yet mapped). Revisit
45
+ * enforcement here if a container block type (list, blockquote) is added.
46
+ */
47
+ fun setRanges(newRanges: List<BlockRange>) {
48
+ ranges.clear()
49
+ ranges.addAll(newRanges.sortedBy { it.start })
50
+ recomputeListMetadata()
51
+ }
52
+
53
+ fun clearAll() {
54
+ ranges.clear()
55
+ }
56
+
57
+ /**
58
+ * Sets/replaces the block on every paragraph the given range touches, expanding
59
+ * to whole-line boundaries within [text]. Removes any block previously covering
60
+ * those paragraphs.
61
+ */
62
+ fun setBlock(
63
+ type: BlockType,
64
+ level: Int,
65
+ paragraphStart: Int,
66
+ paragraphEnd: Int,
67
+ text: CharSequence,
68
+ ) {
69
+ val (start, end) = paragraphBounds(paragraphStart, paragraphEnd, text)
70
+ removeBlocksOverlapping(start, end)
71
+ // An anchored block (heading, list item) on an empty line is kept as a zero-length
72
+ // anchor (see adjustForEdit); other blocks need real content.
73
+ if (end < start || (end == start && type !in BlockType.ANCHORED)) return
74
+
75
+ val block = BlockRange(type, start, end, level)
76
+ ranges.add(sortedInsertionIndex(ranges, start), block)
77
+ }
78
+
79
+ /**
80
+ * Clears any block on the paragraphs the given range touches (reverting them to
81
+ * the implicit paragraph default).
82
+ */
83
+ fun removeBlock(
84
+ paragraphStart: Int,
85
+ paragraphEnd: Int,
86
+ text: CharSequence,
87
+ ) {
88
+ val (start, end) = paragraphBounds(paragraphStart, paragraphEnd, text)
89
+ removeBlocksOverlapping(start, end)
90
+ }
91
+
92
+ /**
93
+ * Shifts/clips block ranges to follow a text edit (see [RangeEditAdjustment]),
94
+ * with anchored-block (heading / list item) persistence layered on top: a block
95
+ * deleted exactly to its end collapses to a zero-length anchor at the edit
96
+ * location (its line survives), and existing anchors shift/keep/drop with their
97
+ * line. The view's prune/normalize pass reconciles anchors against the final text.
98
+ */
99
+ fun adjustForEdit(
100
+ editLocation: Int,
101
+ deletedLength: Int,
102
+ insertedLength: Int,
103
+ ) {
104
+ if (deletedLength == 0 && insertedLength == 0) return
105
+
106
+ val deleteEnd = editLocation + deletedLength
107
+ val delta = insertedLength - deletedLength
108
+
109
+ val anchors = ranges.filter { it.length == 0 && it.type in BlockType.ANCHORED }
110
+ ranges.removeAll { it.length == 0 }
111
+
112
+ // At most one range can end exactly at deleteEnd, so this restores at most
113
+ // one collapsed block.
114
+ val collapsed =
115
+ ranges.firstOrNull {
116
+ it.type in BlockType.ANCHORED && it.start >= editLocation && it.end == deleteEnd
117
+ }
118
+
119
+ RangeEditAdjustment.adjustForEdit(
120
+ ranges,
121
+ editLocation,
122
+ deletedLength,
123
+ insertedLength,
124
+ inheritsReplacementAtStart = { true },
125
+ growsAtStartOnInsert = { true },
126
+ )
127
+
128
+ for (anchor in anchors) {
129
+ when {
130
+ anchor.start <= editLocation -> { /* keeps its position */ }
131
+
132
+ anchor.start >= deleteEnd -> {
133
+ anchor.start += delta
134
+ anchor.end = anchor.start
135
+ }
136
+
137
+ else -> {
138
+ continue // the anchor's line was deleted
139
+ }
140
+ }
141
+ ranges.add(sortedInsertionIndex(ranges, anchor.start), anchor)
142
+ }
143
+
144
+ if (collapsed != null && collapsed !in ranges) {
145
+ ranges.add(
146
+ sortedInsertionIndex(ranges, editLocation),
147
+ BlockRange(collapsed.type, editLocation, editLocation, collapsed.level),
148
+ )
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Snaps every stored range to the line bounds of its start position.
154
+ * Absorbs edge-typed chars, clips split ranges to first line, drops
155
+ * duplicates. On an empty line an anchored block (heading, list item)
156
+ * persists as a zero-length anchor; any other collapsed range is dropped.
157
+ * List depths are clamped so an item nests at most one level under the
158
+ * previous adjacent item (CommonMark cannot represent orphan nesting).
159
+ * Call after [adjustForEdit] once [text] is final. Idempotent.
160
+ */
161
+ fun normalizeToLineBounds(text: CharSequence) {
162
+ if (ranges.isEmpty()) return
163
+
164
+ var previousEnd = -1
165
+ val iterator = ranges.listIterator()
166
+ while (iterator.hasNext()) {
167
+ val range = iterator.next()
168
+ val (lineStart, lineEnd) = paragraphBounds(range.start, range.start, text)
169
+ val isEmptyLine = lineEnd == lineStart
170
+ if ((isEmptyLine && range.type !in BlockType.ANCHORED) || lineStart <= previousEnd) {
171
+ // Dedup: drop a range that resolves to an already-claimed paragraph. This is
172
+ // a legitimate self-heal path (a heading anchor and the block it merges into
173
+ // can briefly share a start after a line-join), so it is deliberately silent.
174
+ iterator.remove()
175
+ continue
176
+ }
177
+ range.start = lineStart
178
+ range.end = lineEnd
179
+ previousEnd = lineEnd
180
+ }
181
+
182
+ recomputeListMetadata()
183
+ }
184
+
185
+ /**
186
+ * Clamps list depths to valid ancestry (an item nests at most one level under
187
+ * the previous adjacent list item — CommonMark cannot represent orphan nesting)
188
+ * and renumbers ordered items among their adjacent same-depth, same-type run.
189
+ */
190
+ private fun recomputeListMetadata() {
191
+ var prevEnd = -2
192
+ var prevDepth = -1
193
+ val counters = IntArray(MAX_LIST_DEPTH + 2)
194
+ val counterTypes = arrayOfNulls<BlockType>(MAX_LIST_DEPTH + 2)
195
+ for (range in ranges) {
196
+ if (range.type !in BlockType.LIST_ITEMS) {
197
+ prevDepth = -1
198
+ continue
199
+ }
200
+ val adjacent = prevDepth >= 0 && range.start == prevEnd + 1
201
+ if (!adjacent) {
202
+ counters.fill(0)
203
+ counterTypes.fill(null)
204
+ }
205
+ val maxDepth = if (adjacent) prevDepth + 1 else 0
206
+ if (range.level > maxDepth) range.level = maxDepth
207
+ val depth = range.level
208
+ for (i in depth + 1..MAX_LIST_DEPTH + 1) {
209
+ counters[i] = 0
210
+ counterTypes[i] = null
211
+ }
212
+ if (counterTypes[depth] != range.type) {
213
+ counters[depth] = 0
214
+ counterTypes[depth] = range.type
215
+ }
216
+ counters[depth]++
217
+ range.ordinal = counters[depth]
218
+ prevEnd = range.end
219
+ prevDepth = range.level
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Drops any stored block overlapping `[start, end)`. Blocks never partially
225
+ * overlap, so a touched block is removed wholesale; a zero-length anchor is
226
+ * dropped when it sits within the bounds (so toggle-off clears an empty line).
227
+ */
228
+ private fun removeBlocksOverlapping(
229
+ start: Int,
230
+ end: Int,
231
+ ) {
232
+ ranges.removeAll { (it.end > start && it.start < end) || (it.length == 0 && it.start in start..end) }
233
+ }
234
+
235
+ /** Expands a selection to cover whole lines (line-scoped block boundaries). */
236
+ private fun paragraphBounds(
237
+ rangeStart: Int,
238
+ rangeEnd: Int,
239
+ text: CharSequence,
240
+ ): Pair<Int, Int> {
241
+ if (text.isEmpty()) return 0 to 0
242
+
243
+ val clampedStart = rangeStart.coerceIn(0, text.length)
244
+ val clampedEnd = rangeEnd.coerceIn(clampedStart, text.length)
245
+
246
+ var lineStart = clampedStart
247
+ while (lineStart > 0 && text[lineStart - 1] != '\n') lineStart--
248
+
249
+ var lineEnd = clampedEnd
250
+ while (lineEnd < text.length && text[lineEnd] != '\n') lineEnd++
251
+
252
+ return lineStart to lineEnd
253
+ }
254
+ }
@@ -53,6 +53,55 @@ class FormattingStore {
53
53
  return if (link != null && start > link.start && start < link.end) Pair(link.end, link.end) else null
54
54
  }
55
55
 
56
+ enum class ToggleResult {
57
+ /** The style was active and has been removed (or is active at the caret). */
58
+ WAS_ACTIVE,
59
+
60
+ /** The style was inactive and has been added (or is inactive at the caret). */
61
+ WAS_INACTIVE,
62
+ }
63
+
64
+ /**
65
+ * Toggles [type] on the range `[start, end)`. For a selection (`start < end`),
66
+ * removes the style when active at `start`, otherwise strips [conflictingStyles]
67
+ * and adds it. For a caret (`start == end`), reports the current state without
68
+ * mutating — the caller manages pending-style bookkeeping.
69
+ */
70
+ fun toggleStyle(
71
+ type: StyleType,
72
+ start: Int,
73
+ end: Int,
74
+ conflictingStyles: Set<StyleType>,
75
+ ): ToggleResult {
76
+ val isActive = isStyleActive(type, start)
77
+ if (start < end) {
78
+ if (isActive) {
79
+ removeType(type, start, end)
80
+ } else {
81
+ for (conflict in conflictingStyles) {
82
+ removeType(conflict, start, end)
83
+ }
84
+ addRange(FormattingRange(type, start, end))
85
+ }
86
+ }
87
+ return if (isActive) ToggleResult.WAS_ACTIVE else ToggleResult.WAS_INACTIVE
88
+ }
89
+
90
+ /**
91
+ * Returns true when toggling [type] ON at [position] should be refused
92
+ * because one of the [blockingStyles] is currently active there.
93
+ * Toggling OFF (the style is already active) is never blocked.
94
+ */
95
+ fun isToggleBlocked(
96
+ type: StyleType,
97
+ position: Int,
98
+ blockingStyles: Set<StyleType>,
99
+ ): Boolean {
100
+ if (blockingStyles.isEmpty()) return false
101
+ if (isStyleActive(type, position)) return false
102
+ return blockingStyles.any { isStyleActive(it, position) }
103
+ }
104
+
56
105
  fun addRange(newRange: FormattingRange) {
57
106
  var mergedStart = newRange.start
58
107
  var mergedEnd = newRange.end
@@ -72,7 +121,7 @@ class FormattingStore {
72
121
  }
73
122
 
74
123
  val merged = FormattingRange(newRange.type, mergedStart, mergedEnd, newRange.url)
75
- val insertAt = sortedInsertionIndex(mergedStart)
124
+ val insertAt = sortedInsertionIndex(ranges, mergedStart)
76
125
  ranges.add(insertAt, merged)
77
126
  }
78
127
 
@@ -104,7 +153,7 @@ class FormattingStore {
104
153
 
105
154
  // Remainders are fragments of a just-removed range and cannot overlap others.
106
155
  for (remainder in remainders) {
107
- val insertAt = sortedInsertionIndex(remainder.start)
156
+ val insertAt = sortedInsertionIndex(ranges, remainder.start)
108
157
  ranges.add(insertAt, remainder)
109
158
  }
110
159
  }
@@ -113,99 +162,43 @@ class FormattingStore {
113
162
  ranges.remove(range)
114
163
  }
115
164
 
165
+ /** Shifts/clips formatting ranges to follow a text edit. See [RangeEditAdjustment]. */
116
166
  fun adjustForEdit(
117
167
  editLocation: Int,
118
168
  deletedLength: Int,
119
169
  insertedLength: Int,
120
170
  ) {
121
- if (deletedLength == 0 && insertedLength == 0) return
122
-
123
- val deleteEnd = editLocation + deletedLength
124
- val indexesToRemove = mutableListOf<Int>()
171
+ RangeEditAdjustment.adjustForEdit(
172
+ ranges,
173
+ editLocation,
174
+ deletedLength,
175
+ insertedLength,
176
+ inheritsReplacementAtStart = { it.type != StyleType.LINK },
177
+ )
178
+ coalesceAdjacentSameTypeRanges()
179
+ }
125
180
 
126
- for ((idx, range) in ranges.withIndex()) {
127
- if (deletedLength > 0) {
128
- when (classifyOverlap(range.start, range.end, editLocation, deleteEnd)) {
129
- EditOverlap.BEFORE_EDIT -> { /* no change */ }
130
-
131
- EditOverlap.AFTER_EDIT -> {
132
- range.start = range.start - deletedLength + insertedLength
133
- range.end = range.end - deletedLength + insertedLength
134
- }
135
-
136
- EditOverlap.FULLY_DELETED -> {
137
- indexesToRemove.add(idx)
138
- }
139
-
140
- EditOverlap.DELETED_INSIDE -> {
141
- range.end = range.end - deletedLength + insertedLength
142
- }
143
-
144
- EditOverlap.CLIPPED_END -> {
145
- val newEnd = editLocation + insertedLength
146
- val newLength = if (newEnd > range.start) newEnd - range.start else 0
147
- range.end = range.start + newLength
148
- if (newLength == 0) indexesToRemove.add(idx)
149
- }
150
-
151
- EditOverlap.CLIPPED_START -> {
152
- val charsClipped = deleteEnd - range.start
153
- val newStart = editLocation + insertedLength
154
- val oldLength = range.length
155
- range.start = newStart
156
- range.end = newStart + oldLength - charsClipped
157
- if (range.length == 0) indexesToRemove.add(idx)
158
- }
159
- }
160
- } else {
161
- when {
162
- range.start >= editLocation -> {
163
- range.start += insertedLength
164
- range.end += insertedLength
165
- }
166
-
167
- editLocation > range.start && editLocation < range.end -> {
168
- range.end += insertedLength
169
- }
181
+ /**
182
+ * Merge same-type (and same-url) ranges left adjacent or overlapping by an
183
+ * edit — e.g. deleting the space in "**foo** **bar**" leaves two touching
184
+ * bold ranges that would serialize as "**foo****bar**". [addRange] keeps
185
+ * this invariant on insert; the edit path must too.
186
+ */
187
+ private fun coalesceAdjacentSameTypeRanges() {
188
+ var idx = 0
189
+ while (idx < ranges.size) {
190
+ val current = ranges[idx]
191
+ var next = idx + 1
192
+ while (next < ranges.size && ranges[next].start <= current.end) {
193
+ val candidate = ranges[next]
194
+ if (candidate.type == current.type && candidate.url == current.url) {
195
+ current.end = maxOf(current.end, candidate.end)
196
+ ranges.removeAt(next)
197
+ } else {
198
+ next++
170
199
  }
171
200
  }
201
+ idx++
172
202
  }
173
-
174
- for (idx in indexesToRemove.reversed()) {
175
- ranges.removeAt(idx)
176
- }
177
-
178
- ranges.removeAll { it.length == 0 }
179
- }
180
-
181
- private fun sortedInsertionIndex(location: Int): Int {
182
- var index = 0
183
- for (existing in ranges) {
184
- if (existing.start > location) break
185
- index++
186
- }
187
- return index
188
- }
189
-
190
- private enum class EditOverlap {
191
- BEFORE_EDIT,
192
- AFTER_EDIT,
193
- FULLY_DELETED,
194
- DELETED_INSIDE,
195
- CLIPPED_END,
196
- CLIPPED_START,
197
- }
198
-
199
- private fun classifyOverlap(
200
- rangeStart: Int,
201
- rangeEnd: Int,
202
- editLocation: Int,
203
- deleteEnd: Int,
204
- ): EditOverlap {
205
- if (rangeEnd <= editLocation) return EditOverlap.BEFORE_EDIT
206
- if (rangeStart >= deleteEnd) return EditOverlap.AFTER_EDIT
207
- if (rangeStart >= editLocation && rangeEnd <= deleteEnd) return EditOverlap.FULLY_DELETED
208
- if (rangeStart < editLocation && rangeEnd > deleteEnd) return EditOverlap.DELETED_INSIDE
209
- return if (rangeStart < editLocation) EditOverlap.CLIPPED_END else EditOverlap.CLIPPED_START
210
203
  }
211
204
  }
@@ -2,16 +2,23 @@ package com.swmansion.enriched.markdown.input.formatting
2
2
 
3
3
  import android.text.Spannable
4
4
  import android.text.style.CharacterStyle
5
+ import com.swmansion.enriched.markdown.input.model.BlockRange
6
+ import com.swmansion.enriched.markdown.input.model.BlockType
5
7
  import com.swmansion.enriched.markdown.input.model.FormattingRange
6
8
  import com.swmansion.enriched.markdown.input.model.InputFormatterStyle
7
9
  import com.swmansion.enriched.markdown.input.model.StyleType
10
+ import com.swmansion.enriched.markdown.input.spans.InputListItemSpacingSpan
11
+ import com.swmansion.enriched.markdown.input.styles.BlockHandler
8
12
  import com.swmansion.enriched.markdown.input.styles.BoldStyleHandler
13
+ import com.swmansion.enriched.markdown.input.styles.HeadingBlockHandler
9
14
  import com.swmansion.enriched.markdown.input.styles.ItalicStyleHandler
10
15
  import com.swmansion.enriched.markdown.input.styles.LinkStyleHandler
16
+ import com.swmansion.enriched.markdown.input.styles.OrderedListBlockHandler
11
17
  import com.swmansion.enriched.markdown.input.styles.SpoilerStyleHandler
12
18
  import com.swmansion.enriched.markdown.input.styles.StrikethroughStyleHandler
13
19
  import com.swmansion.enriched.markdown.input.styles.StyleHandler
14
20
  import com.swmansion.enriched.markdown.input.styles.UnderlineStyleHandler
21
+ import com.swmansion.enriched.markdown.input.styles.UnorderedListBlockHandler
15
22
 
16
23
  /**
17
24
  * Marker interface so we only remove spans we created, leaving
@@ -19,7 +26,15 @@ import com.swmansion.enriched.markdown.input.styles.UnderlineStyleHandler
19
26
  */
20
27
  interface MarkdownSpan
21
28
 
22
- class InputFormatter {
29
+ /**
30
+ * @property displayDensity px-per-dp of the host display, handed to the list block
31
+ * handlers so they can build density-correct spans without a Context. This is a
32
+ * platform constant, not style data, which is why it lives here rather than in
33
+ * [InputFormatterStyle].
34
+ */
35
+ class InputFormatter(
36
+ private val displayDensity: Float = 1f,
37
+ ) {
23
38
  val handlers: Map<StyleType, StyleHandler> =
24
39
  mapOf(
25
40
  StyleType.BOLD to BoldStyleHandler(),
@@ -30,6 +45,22 @@ class InputFormatter {
30
45
  StyleType.SPOILER to SpoilerStyleHandler(),
31
46
  )
32
47
 
48
+ /**
49
+ * Block handlers, keyed by block type. A single [HeadingBlockHandler] serves all
50
+ * six heading levels — it reads the level from the [BlockRange] — so it is mapped
51
+ * under every `HEADING_n` key. One [UnorderedListBlockHandler] serves every list
52
+ * depth (depth lives on the range), so it is mapped under the single list key.
53
+ */
54
+ val blockHandlers: Map<BlockType, BlockHandler> =
55
+ buildMap {
56
+ val heading = HeadingBlockHandler()
57
+ for (type in BlockType.HEADINGS) put(type, heading)
58
+ put(BlockType.UNORDERED_LIST_ITEM, UnorderedListBlockHandler(displayDensity))
59
+ put(BlockType.ORDERED_LIST_ITEM, OrderedListBlockHandler(displayDensity))
60
+ }
61
+
62
+ fun handlerForBlock(type: BlockType): BlockHandler? = blockHandlers[type]
63
+
33
64
  private var style: InputFormatterStyle? = null
34
65
 
35
66
  fun updateStyle(newStyle: InputFormatterStyle): Boolean {
@@ -38,16 +69,20 @@ class InputFormatter {
38
69
  return true
39
70
  }
40
71
 
72
+ fun resolveHeadingFontSizePx(level: Int): Float? = style?.headingStyle(level)?.fontSizePx
73
+
41
74
  fun applyFormatting(
42
75
  spannable: Spannable,
43
76
  ranges: List<FormattingRange>,
44
77
  ) {
45
78
  val currentStyle = style ?: return
46
79
 
80
+ val blockSpanClasses = blockHandlers.values.flatMap { it.spanClasses() }.toSet()
47
81
  val existingSpans =
48
82
  spannable
49
83
  .getSpans(0, spannable.length, CharacterStyle::class.java)
50
84
  .filterIsInstance<MarkdownSpan>()
85
+ .filter { span -> blockSpanClasses.none { it.isInstance(span) } }
51
86
  val linkSpanClasses = handlers[StyleType.LINK]?.spanClasses().orEmpty().toSet()
52
87
  val existingLinkSpans = existingSpans.filter { span -> linkSpanClasses.any { it.isInstance(span) } }
53
88
  for (span in existingLinkSpans) {
@@ -108,6 +143,79 @@ class InputFormatter {
108
143
  }
109
144
  }
110
145
 
146
+ /**
147
+ * Applies each block range's paragraph-scoped spans via its handler, after the
148
+ * inline pass. Mirrors [applyFormatting]: removes the block spans we previously
149
+ * set (identified by the registered handlers' [BlockHandler.spanClasses]) and
150
+ * re-applies from the current ranges. With no handlers registered (PR1) both
151
+ * the cleanup and the apply loops are no-ops, so block formatting is inert.
152
+ */
153
+ fun applyBlockFormatting(
154
+ spannable: Spannable,
155
+ blockRanges: List<BlockRange>,
156
+ ) = applyBlockFormatting(spannable, blockRanges, 0, spannable.length)
157
+
158
+ /**
159
+ * Re-stamps block spans, scoped to `[scopeStart, scopeEnd)`. Only the block spans
160
+ * we created that intersect the scope are removed, and only the block ranges
161
+ * intersecting the scope are re-applied — so an edit re-normalizes just the
162
+ * affected line(s) instead of the whole document on every keystroke. Pass the
163
+ * full document range for a wholesale re-apply (import / style change).
164
+ */
165
+ fun applyBlockFormatting(
166
+ spannable: Spannable,
167
+ blockRanges: List<BlockRange>,
168
+ scopeStart: Int,
169
+ scopeEnd: Int,
170
+ ) {
171
+ val currentStyle = style ?: return
172
+ if (blockHandlers.isEmpty()) return
173
+
174
+ val start = scopeStart.coerceIn(0, spannable.length)
175
+ val end = scopeEnd.coerceIn(start, spannable.length)
176
+
177
+ val blockSpanClasses = blockHandlers.values.flatMap { it.spanClasses() }.toSet()
178
+
179
+ val existingBlockSpans =
180
+ spannable
181
+ .getSpans(start, end, Any::class.java)
182
+ .filter { span -> span is MarkdownSpan && blockSpanClasses.any { it.isInstance(span) } }
183
+ for (span in existingBlockSpans) {
184
+ spannable.removeSpan(span)
185
+ }
186
+
187
+ for (range in blockRanges) {
188
+ val isAnchor = range.length == 0 && range.type in BlockType.ANCHORED
189
+ if (!isAnchor && (range.start >= range.end || range.start < 0 || range.end > spannable.length)) continue
190
+ if (isAnchor && (range.start < 0 || range.start > spannable.length)) continue
191
+ if (isAnchor) {
192
+ if (range.start < start || range.start > end) continue
193
+ } else if (range.end <= start || range.start >= end) {
194
+ continue
195
+ }
196
+ val handler = blockHandlers[range.type] ?: continue
197
+
198
+ // Extend a zero-length anchor to cover the next character (usually '\n') so
199
+ // the Layout gives the empty line the block's metrics. At the very end of
200
+ // text the span stays zero-length; the view-level paint override handles
201
+ // cursor height for that edge case.
202
+ val anchorEnd = if (isAnchor && range.start < spannable.length) range.start + 1 else range.end
203
+ val flags = if (isAnchor) Spannable.SPAN_INCLUSIVE_INCLUSIVE else Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
204
+ for (span in handler.createSpans(range, currentStyle)) {
205
+ // A LineHeightSpan (list-item spacing) must cover only the item's first
206
+ // character so it spaces just the first visual line, not wrapped lines.
207
+ val spanEnd =
208
+ if (span is InputListItemSpacingSpan) {
209
+ (range.start + 1).coerceAtMost(range.end).coerceAtMost(spannable.length)
210
+ } else {
211
+ anchorEnd
212
+ }
213
+ if (span is InputListItemSpacingSpan && spanEnd <= range.start) continue
214
+ spannable.setSpan(span, range.start, spanEnd, flags)
215
+ }
216
+ }
217
+ }
218
+
111
219
  private data class SpanDescriptor(
112
220
  val start: Int,
113
221
  val end: Int,