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,42 @@
1
+ package com.swmansion.enriched.markdown.input
2
+
3
+ import android.content.ClipData
4
+ import android.content.ClipDescription
5
+ import android.content.ClipboardManager
6
+ import android.content.Context
7
+ import android.os.PersistableBundle
8
+
9
+ /**
10
+ * Clipboard round-trip for markdown content, mirroring iOS's dual pasteboard
11
+ * representation: the clip's item text stays clean plain text for external
12
+ * paste targets, while the markdown source travels in the description extras
13
+ * under a vendor MIME type so our paste can restore formatting and block
14
+ * ranges. External clips (no vendor MIME) keep pasting as plain text.
15
+ */
16
+ object MarkdownClipboard {
17
+ const val MIME_TYPE = "text/vnd.com.swmansion.enriched-markdown"
18
+ private const val EXTRA_MARKDOWN = "com.swmansion.enriched-markdown.markdown"
19
+
20
+ fun newMarkdownClip(
21
+ markdown: String,
22
+ plainText: String,
23
+ ): ClipData {
24
+ val clip =
25
+ ClipData(
26
+ ClipDescription("Markdown", arrayOf(MIME_TYPE, ClipDescription.MIMETYPE_TEXT_PLAIN)),
27
+ ClipData.Item(plainText),
28
+ )
29
+ clip.description.extras = PersistableBundle().apply { putString(EXTRA_MARKDOWN, markdown) }
30
+ return clip
31
+ }
32
+
33
+ /** Returns the clipboard's markdown, or null if the clip wasn't created by [newMarkdownClip]. */
34
+ fun markdownFromClipboard(context: Context): String? {
35
+ val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return null
36
+ val clip = clipboard.primaryClip ?: return null
37
+ if (!clip.description.hasMimeType(MIME_TYPE)) return null
38
+ return clip.description.extras
39
+ ?.getString(EXTRA_MARKDOWN)
40
+ ?.takeIf { it.isNotEmpty() }
41
+ }
42
+ }
@@ -0,0 +1,196 @@
1
+ package com.swmansion.enriched.markdown.input.editing
2
+
3
+ import android.text.Editable
4
+ import com.swmansion.enriched.markdown.input.formatting.BlockStore
5
+ import com.swmansion.enriched.markdown.input.model.BlockRange
6
+ import com.swmansion.enriched.markdown.input.model.BlockType
7
+ import com.swmansion.enriched.markdown.input.model.MAX_LIST_DEPTH
8
+
9
+ /**
10
+ * Encapsulates block-editing operations: toggling block types, adjusting
11
+ * list depth, and querying the block at a given position. The view delegates
12
+ * here and applies UI-level consequences (formatting, anchor sync, cursor
13
+ * size, typing attributes) based on the returned result.
14
+ *
15
+ * All mutating methods normalize stores to line bounds before returning, so
16
+ * the caller can re-stamp block spans immediately.
17
+ */
18
+ class BlockEditCoordinator(
19
+ private val blockStore: BlockStore,
20
+ ) {
21
+ // ── Queries ──────────────────────────────────────────────────────────
22
+
23
+ fun blockAtPosition(
24
+ text: CharSequence,
25
+ pos: Int,
26
+ ): BlockRange? {
27
+ val lineStart = lineStartOf(text, pos)
28
+ return blockStore.blockStartingAt(lineStart)
29
+ }
30
+
31
+ fun listBlockAtPosition(
32
+ text: CharSequence,
33
+ pos: Int,
34
+ ): BlockRange? = blockAtPosition(text, pos)?.takeIf { it.type in BlockType.LIST_ITEMS }
35
+
36
+ fun listBlockAtLineStart(lineStart: Int): BlockRange? = blockStore.blockStartingAt(lineStart)?.takeIf { it.type in BlockType.LIST_ITEMS }
37
+
38
+ fun headingLevelAtPosition(
39
+ text: CharSequence,
40
+ pos: Int,
41
+ ): Int {
42
+ val block = blockAtPosition(text, pos) ?: return 0
43
+ return if (block.type in BlockType.HEADINGS) block.level else 0
44
+ }
45
+
46
+ fun listStateAtPosition(
47
+ text: CharSequence,
48
+ pos: Int,
49
+ type: BlockType,
50
+ ): Pair<Boolean, Int> {
51
+ val block = listBlockAtPosition(text, pos) ?: return false to 0
52
+ return if (block.type == type) true to block.level else false to 0
53
+ }
54
+
55
+ // ── Mutations ────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Toggles [type] at [level] on the paragraphs the selection touches.
59
+ * Returns true when the block was already active (i.e. it was turned off).
60
+ */
61
+ fun toggleBlock(
62
+ text: Editable,
63
+ type: BlockType,
64
+ level: Int,
65
+ selStart: Int,
66
+ selEnd: Int,
67
+ ): Boolean {
68
+ val existing = blockAtPosition(text, selStart)
69
+ val wasActive = existing != null && existing.type == type && existing.level == level
70
+
71
+ if (wasActive) {
72
+ blockStore.removeBlock(selStart, selEnd, text)
73
+ } else {
74
+ forEachLine(text, selStart, selEnd) { ls, le ->
75
+ blockStore.setBlock(type, level, ls, le, text)
76
+ }
77
+ }
78
+ blockStore.normalizeToLineBounds(text)
79
+ return wasActive
80
+ }
81
+
82
+ /**
83
+ * Toggles a list of [type] on the selection: turns lines into depth-0
84
+ * items (switching list type keeps each line's depth) or clears them back
85
+ * to plain paragraphs when the cursor line already carries [type].
86
+ * Returns true when the list was turned off.
87
+ */
88
+ fun toggleList(
89
+ text: Editable,
90
+ type: BlockType,
91
+ cursorPos: Int,
92
+ selStart: Int,
93
+ selEnd: Int,
94
+ ): Boolean {
95
+ val turningOff = listBlockAtPosition(text, cursorPos)?.type == type
96
+ if (turningOff) {
97
+ forEachLine(text, selStart, selEnd) { ls, le ->
98
+ blockStore.removeBlock(ls, le, text)
99
+ }
100
+ } else {
101
+ forEachLine(text, selStart, selEnd) { ls, le ->
102
+ val existing = listBlockAtLineStart(ls)
103
+ blockStore.setBlock(type, existing?.level ?: 0, ls, le, text)
104
+ }
105
+ }
106
+ blockStore.normalizeToLineBounds(text)
107
+ return turningOff
108
+ }
109
+
110
+ enum class DepthChangeResult {
111
+ /** Depth was adjusted on the existing list item(s). */
112
+ CHANGED,
113
+
114
+ /** No list existed; a new unordered list was started (delta > 0 only). */
115
+ STARTED_LIST,
116
+
117
+ /** Outdented at depth 0 — the list marker was removed. */
118
+ EXITED_LIST,
119
+
120
+ /** Nothing happened (e.g. outdent on a heading or plain paragraph). */
121
+ NO_OP,
122
+ }
123
+
124
+ /**
125
+ * Adjusts list depth by [delta] on the selected lines. When the cursor
126
+ * is on a plain paragraph and delta > 0, starts a new unordered list.
127
+ * When outdenting at depth 0, removes the list marker.
128
+ *
129
+ * Returns the action taken so the view can apply the appropriate UI effects.
130
+ */
131
+ fun changeDepth(
132
+ text: Editable,
133
+ cursorPos: Int,
134
+ selStart: Int,
135
+ selEnd: Int,
136
+ delta: Int,
137
+ ): DepthChangeResult {
138
+ val cursorBlock = listBlockAtPosition(text, cursorPos)
139
+ if (cursorBlock == null) {
140
+ if (delta > 0 && blockAtPosition(text, cursorPos) == null) {
141
+ toggleList(text, BlockType.UNORDERED_LIST_ITEM, cursorPos, selStart, selEnd)
142
+ return DepthChangeResult.STARTED_LIST
143
+ }
144
+ return DepthChangeResult.NO_OP
145
+ }
146
+ if (delta < 0 && cursorBlock.level == 0) {
147
+ toggleList(text, cursorBlock.type, cursorPos, selStart, selEnd)
148
+ return DepthChangeResult.EXITED_LIST
149
+ }
150
+
151
+ forEachLine(text, selStart, selEnd) { ls, _ ->
152
+ val block = listBlockAtLineStart(ls)
153
+ if (block != null) {
154
+ val newDepth = (block.level + delta).coerceIn(0, MAX_LIST_DEPTH)
155
+ blockStore.setBlock(block.type, newDepth, ls, ls, text)
156
+ }
157
+ }
158
+ blockStore.normalizeToLineBounds(text)
159
+ return DepthChangeResult.CHANGED
160
+ }
161
+
162
+ // ── Line utilities ───────────────────────────────────────────────────
163
+
164
+ fun lineStartOf(
165
+ text: CharSequence,
166
+ pos: Int,
167
+ ): Int {
168
+ var s = pos.coerceIn(0, text.length)
169
+ while (s > 0 && !text[s - 1].isLineBreak()) s--
170
+ return s
171
+ }
172
+
173
+ fun lineEndOf(
174
+ text: CharSequence,
175
+ pos: Int,
176
+ ): Int {
177
+ var e = pos.coerceIn(0, text.length)
178
+ while (e < text.length && !text[e].isLineBreak()) e++
179
+ return e
180
+ }
181
+
182
+ private inline fun forEachLine(
183
+ text: CharSequence,
184
+ selStart: Int,
185
+ selEnd: Int,
186
+ action: (lineStart: Int, lineEnd: Int) -> Unit,
187
+ ) {
188
+ var cursor = lineStartOf(text, selStart)
189
+ while (cursor <= text.length) {
190
+ val le = lineEndOf(text, cursor)
191
+ action(cursor, le)
192
+ if (le >= selEnd) break
193
+ cursor = le + 1
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,73 @@
1
+ package com.swmansion.enriched.markdown.input.editing
2
+
3
+ import android.text.Editable
4
+ import com.swmansion.enriched.markdown.input.detection.DetectorPipeline
5
+ import com.swmansion.enriched.markdown.input.formatting.BlockStore
6
+ import com.swmansion.enriched.markdown.input.formatting.FormattingStore
7
+ import com.swmansion.enriched.markdown.input.formatting.InputFormatter
8
+ import com.swmansion.enriched.markdown.input.formatting.MarkdownSerializer
9
+ import com.swmansion.enriched.markdown.input.math.MathChipController
10
+ import com.swmansion.enriched.markdown.input.model.BlockRange
11
+ import com.swmansion.enriched.markdown.input.model.FormattingRange
12
+
13
+ class ClipboardCoordinator(
14
+ private val formattingStore: FormattingStore,
15
+ private val blockStore: BlockStore,
16
+ private val detectorPipeline: DetectorPipeline,
17
+ private val formatter: InputFormatter,
18
+ ) {
19
+ fun allRangesForSerialization(editable: Editable?): List<FormattingRange> {
20
+ if (editable == null) return formattingStore.allRanges
21
+ val transient = detectorPipeline.allTransientFormattingRanges(editable)
22
+ if (transient.isEmpty()) return formattingStore.allRanges
23
+ return formattingStore.allRanges + transient
24
+ }
25
+
26
+ fun serializeFullDocument(
27
+ text: String,
28
+ editable: Editable?,
29
+ chips: List<MathChipController.ChipInfo> = emptyList(),
30
+ ): String =
31
+ MarkdownSerializer.serialize(text, chips, allRangesForSerialization(editable), blockStore.allRanges) { block ->
32
+ formatter.handlerForBlock(block.type)?.markdownLinePrefix(block) ?: ""
33
+ }
34
+
35
+ fun serializeSelectedRange(
36
+ fullText: String,
37
+ selStart: Int,
38
+ selEnd: Int,
39
+ editable: Editable?,
40
+ chips: List<MathChipController.ChipInfo> = emptyList(),
41
+ ): String? {
42
+ if (selStart >= selEnd) return null
43
+ val selectedText = fullText.substring(selStart, selEnd)
44
+
45
+ val clippedRanges = mutableListOf<FormattingRange>()
46
+ for (range in allRangesForSerialization(editable)) {
47
+ if (range.end <= selStart || range.start >= selEnd) continue
48
+ val clippedStart = maxOf(range.start, selStart)
49
+ val clippedEnd = minOf(range.end, selEnd)
50
+ clippedRanges.add(FormattingRange(range.type, clippedStart - selStart, clippedEnd - selStart, range.url))
51
+ }
52
+
53
+ val clippedBlockRanges = mutableListOf<BlockRange>()
54
+ for (block in blockStore.allRanges) {
55
+ if (block.end <= selStart || block.start >= selEnd) continue
56
+ val clippedStart = maxOf(block.start, selStart)
57
+ val clippedEnd = minOf(block.end, selEnd)
58
+ clippedBlockRanges.add(BlockRange(block.type, clippedStart - selStart, clippedEnd - selStart, block.level))
59
+ }
60
+
61
+ // A chip is a single character, so it is cleanly inside or outside the selection;
62
+ // rebase the survivors onto the substring like the ranges above. Copying part of
63
+ // a document must serialize its formulas, not the bare placeholder char.
64
+ val clippedChips =
65
+ chips
66
+ .filter { it.position in selStart until selEnd }
67
+ .map { it.copy(position = it.position - selStart) }
68
+
69
+ return MarkdownSerializer.serialize(selectedText, clippedChips, clippedRanges, clippedBlockRanges) { block ->
70
+ formatter.handlerForBlock(block.type)?.markdownLinePrefix(block) ?: ""
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,19 @@
1
+ package com.swmansion.enriched.markdown.input.editing
2
+
3
+ import com.swmansion.enriched.markdown.input.model.StyleType
4
+
5
+ /**
6
+ * Immutable snapshot of everything the [EditPipeline] needs to process a single
7
+ * text change. Built by the view from its pre-/post-edit state, then handed to
8
+ * [EditPipeline.processTextChange].
9
+ */
10
+ data class EditContext(
11
+ val editStart: Int,
12
+ val deletedLength: Int,
13
+ val insertedLength: Int,
14
+ val preEditText: String,
15
+ val preEditSelectionStart: Int,
16
+ val preEditSelectionEnd: Int,
17
+ val pendingStyles: Set<StyleType>,
18
+ val pendingStyleRemovals: Set<StyleType>,
19
+ )
@@ -0,0 +1,248 @@
1
+ package com.swmansion.enriched.markdown.input.editing
2
+
3
+ import android.text.Editable
4
+ import com.swmansion.enriched.markdown.input.detection.DetectorPipeline
5
+ import com.swmansion.enriched.markdown.input.formatting.BlockStore
6
+ import com.swmansion.enriched.markdown.input.formatting.FormattingStore
7
+ import com.swmansion.enriched.markdown.input.formatting.InputFormatter
8
+ import com.swmansion.enriched.markdown.input.layout.InputEventEmitter
9
+ import com.swmansion.enriched.markdown.input.model.BlockType
10
+ import com.swmansion.enriched.markdown.input.model.FormattingRange
11
+
12
+ /**
13
+ * Zero-width space: anchors an empty bullet line so the marker draws and the
14
+ * caret indents (Android won't apply a LeadingMarginSpan's indent to an empty
15
+ * paragraph). Stripped during serialization, so it never reaches the Markdown output.
16
+ */
17
+ internal const val ZWSP = '\u200B'
18
+
19
+ internal fun Char.isLineBreak(): Boolean = this == '\n' || this == '\r' || this == '\u0085' || this == '\u2028' || this == '\u2029'
20
+
21
+ /**
22
+ * View-specific operations the [EditPipeline] delegates back to its host.
23
+ * Implemented by [EnrichedMarkdownTextInputView][com.swmansion.enriched.markdown.input.EnrichedMarkdownTextInputView].
24
+ */
25
+ interface EditPipelineHost {
26
+ val editable: Editable?
27
+ val emitMarkdown: Boolean
28
+
29
+ fun syncEmptyListAnchor(restamp: Boolean): Boolean
30
+
31
+ fun forceScrollToSelection()
32
+
33
+ fun syncCursorSizeWithBlock()
34
+
35
+ fun updateActiveMention()
36
+
37
+ fun runAsATransaction(block: () -> Unit)
38
+
39
+ fun setViewSelection(position: Int)
40
+ }
41
+
42
+ /**
43
+ * Orchestrates the per-keystroke edit pipeline: store adjustment, block
44
+ * continuation, formatting, detection, and event emission.
45
+ *
46
+ * Phases run in a fixed order — each phase's postcondition is the next phase's
47
+ * precondition. New block types add behavior to [continueBlocks] (via their
48
+ * handler's [continuesOnNewline][com.swmansion.enriched.markdown.input.styles.BlockHandler.continuesOnNewline])
49
+ * without touching this pipeline.
50
+ */
51
+ class EditPipeline(
52
+ private val formattingStore: FormattingStore,
53
+ private val blockStore: BlockStore,
54
+ private val formatter: InputFormatter,
55
+ private val detectorPipeline: DetectorPipeline,
56
+ private val eventEmitter: InputEventEmitter,
57
+ private val host: EditPipelineHost,
58
+ ) {
59
+ /**
60
+ * Main entry point — called once per user-driven text change. The [context]
61
+ * captures the edit parameters and pre-edit state as an immutable snapshot.
62
+ */
63
+ fun processTextChange(context: EditContext) {
64
+ adjustStores(context)
65
+ pruneOrphanedAnchors()
66
+ continueBlocks(context)
67
+ host.editable?.let { blockStore.normalizeToLineBounds(it) }
68
+ applyPendingStyles(context)
69
+
70
+ // Settle the ZWSP anchor BEFORE stamping spans, so block formatting runs
71
+ // exactly once over the final text/ranges — otherwise a pre-ZWSP and
72
+ // post-ZWSP span would both land on the empty line ("double bullet" bug).
73
+ val anchorChanged = host.syncEmptyListAnchor(restamp = false)
74
+ val touchedNewline = anchorChanged || editTouchedNewline(context)
75
+
76
+ applyInlineFormatting()
77
+ applyBlockFormatting(touchedNewline, context.editStart, context.insertedLength)
78
+ detectLinks(context)
79
+
80
+ host.forceScrollToSelection()
81
+ host.syncCursorSizeWithBlock()
82
+
83
+ eventEmitter.emitChangeText()
84
+ if (host.emitMarkdown) eventEmitter.emitChangeMarkdown()
85
+ host.updateActiveMention()
86
+ eventEmitter.emitCaretRectChangeIfNeeded()
87
+ }
88
+
89
+ private fun adjustStores(context: EditContext) {
90
+ formattingStore.adjustForEdit(context.editStart, context.deletedLength, context.insertedLength)
91
+ blockStore.adjustForEdit(context.editStart, context.deletedLength, context.insertedLength)
92
+ }
93
+
94
+ /**
95
+ * Drops anchored blocks (headings, list items) no longer anchored at a line
96
+ * start (e.g. Backspace merged their line into the previous one). Must run
97
+ * BEFORE [BlockStore.normalizeToLineBounds] so a merged range is judged on
98
+ * its unsnapped anchor.
99
+ *
100
+ * Internal so the view's [adjustStoresForEdit][com.swmansion.enriched.markdown.input.EnrichedMarkdownTextInputView]
101
+ * helper (used by programmatic text-mutation paths) can call it too.
102
+ */
103
+ internal fun pruneOrphanedAnchors() {
104
+ val editable = host.editable ?: return
105
+ val orphans =
106
+ blockStore.allRanges.filter { range ->
107
+ range.type in BlockType.ANCHORED && !isAtLineStart(editable, range.start)
108
+ }
109
+ for (orphan in orphans) {
110
+ blockStore.removeBlock(orphan.start, orphan.start, editable)
111
+ }
112
+ }
113
+
114
+ /**
115
+ * After a newline insertion, continues a block whose handler reports
116
+ * [continuesOnNewline][com.swmansion.enriched.markdown.input.styles.BlockHandler.continuesOnNewline]
117
+ * onto the new line as a sibling at the same depth, or exits the block when
118
+ * the emptied item gets a second Enter.
119
+ */
120
+ private fun continueBlocks(context: EditContext) {
121
+ val editable = host.editable ?: return
122
+ if (context.deletedLength != 0 || context.insertedLength <= 0) return
123
+ val insertedEnd = (context.editStart + context.insertedLength).coerceAtMost(editable.length)
124
+ val insertedNewline = (context.editStart until insertedEnd).any { editable[it] == '\n' }
125
+ if (!insertedNewline) return
126
+
127
+ val prevLineEnd = context.editStart
128
+ var prevLineStart = prevLineEnd
129
+ while (prevLineStart > 0 && editable[prevLineStart - 1] != '\n') prevLineStart--
130
+
131
+ val prevBlock = blockStore.blockStartingAt(prevLineStart) ?: return
132
+ val handler = formatter.handlerForBlock(prevBlock.type) ?: return
133
+ if (!handler.continuesOnNewline) return
134
+
135
+ val prevContentLength = (prevLineStart until prevLineEnd).count { editable[it] != ZWSP }
136
+ if (prevContentLength == 0) {
137
+ // Exit: clear the block AND delete the just-inserted newline so the empty
138
+ // item collapses in place instead of leaving an extra indented blank line.
139
+ blockStore.removeBlock(prevLineStart, prevLineEnd, editable)
140
+ val newlineEnd = (context.editStart + context.insertedLength).coerceAtMost(editable.length)
141
+ host.runAsATransaction { editable.delete(context.editStart, newlineEnd) }
142
+ blockStore.adjustForEdit(context.editStart, context.insertedLength, 0)
143
+ host.setViewSelection(prevLineStart.coerceAtMost(editable.length))
144
+ return
145
+ }
146
+
147
+ val newLineStart = (context.editStart + context.insertedLength).coerceAtMost(editable.length)
148
+ blockStore.setBlock(prevBlock.type, prevBlock.level, newLineStart, newLineStart, editable)
149
+ }
150
+
151
+ private fun applyPendingStyles(context: EditContext) {
152
+ if (context.insertedLength == 0) return
153
+ if (context.pendingStyles.isEmpty() && context.pendingStyleRemovals.isEmpty()) return
154
+
155
+ val rangeStart =
156
+ if (context.preEditSelectionStart != context.preEditSelectionEnd) {
157
+ context.preEditSelectionStart
158
+ } else {
159
+ context.editStart
160
+ }
161
+ val rangeEnd = rangeStart + context.insertedLength
162
+
163
+ // Skip applying pending styles when the insertion is only line breaks —
164
+ // a phantom range over a bare newline corrupts isStyleActive() at the boundary.
165
+ val currentText = host.editable
166
+ val insertedHasGlyphContent =
167
+ currentText != null &&
168
+ rangeEnd <= currentText.length &&
169
+ (rangeStart until rangeEnd).any { !currentText[it].isLineBreak() }
170
+
171
+ if (insertedHasGlyphContent) {
172
+ for (style in context.pendingStyles) {
173
+ formattingStore.addRange(FormattingRange(style, rangeStart, rangeEnd))
174
+ }
175
+ }
176
+
177
+ for (style in context.pendingStyleRemovals) {
178
+ formattingStore.removeType(style, rangeStart, rangeEnd)
179
+ }
180
+ }
181
+
182
+ private fun applyInlineFormatting() {
183
+ val editable = host.editable ?: return
184
+ formatter.applyFormatting(editable, formattingStore.allRanges)
185
+ }
186
+
187
+ private fun applyBlockFormatting(
188
+ touchedNewline: Boolean,
189
+ editStart: Int,
190
+ insertedLength: Int,
191
+ ) {
192
+ val editable = host.editable ?: return
193
+ if (touchedNewline) {
194
+ formatter.applyBlockFormatting(editable, blockStore.allRanges)
195
+ } else {
196
+ applyBlockFormattingScopedToEdit(editable, editStart, insertedLength)
197
+ }
198
+ }
199
+
200
+ private fun applyBlockFormattingScopedToEdit(
201
+ editable: Editable,
202
+ editStart: Int,
203
+ insertedLength: Int,
204
+ ) {
205
+ val length = editable.length
206
+ val rawStart = editStart.coerceIn(0, length)
207
+ val rawEnd = (editStart + insertedLength).coerceIn(rawStart, length)
208
+
209
+ var lineStart = rawStart
210
+ while (lineStart > 0 && editable[lineStart - 1] != '\n') lineStart--
211
+ var lineEnd = rawEnd
212
+ while (lineEnd < length && editable[lineEnd] != '\n') lineEnd++
213
+
214
+ formatter.applyBlockFormatting(editable, blockStore.allRanges, lineStart, lineEnd)
215
+ }
216
+
217
+ private fun detectLinks(context: EditContext) {
218
+ val editable = host.editable ?: return
219
+ val currentText = editable.toString()
220
+ detectorPipeline.processTextChange(editable, currentText, context.editStart, context.insertedLength)
221
+ }
222
+
223
+ /**
224
+ * True when the edit inserted or deleted a line break (so block spans may
225
+ * need to move across lines). Checks the inserted run in the current text
226
+ * and the deleted run in the pre-edit text.
227
+ */
228
+ private fun editTouchedNewline(context: EditContext): Boolean {
229
+ val editable = host.editable
230
+ if (editable != null && context.insertedLength > 0) {
231
+ val end = (context.editStart + context.insertedLength).coerceAtMost(editable.length)
232
+ if ((context.editStart until end).any { editable[it].isLineBreak() }) return true
233
+ }
234
+ if (context.deletedLength > 0) {
235
+ val end = (context.editStart + context.deletedLength).coerceAtMost(context.preEditText.length)
236
+ if ((context.editStart until end).any { context.preEditText[it].isLineBreak() }) return true
237
+ }
238
+ return false
239
+ }
240
+
241
+ private fun isAtLineStart(
242
+ editable: CharSequence,
243
+ pos: Int,
244
+ ): Boolean {
245
+ if (pos < 0 || pos > editable.length) return false
246
+ return pos == 0 || editable[pos - 1].isLineBreak()
247
+ }
248
+ }
@@ -0,0 +1,52 @@
1
+ package com.swmansion.enriched.markdown.input.editing
2
+
3
+ enum class EditPhase {
4
+ Idle,
5
+ Processing,
6
+ Importing,
7
+ ManagingAnchors,
8
+ }
9
+
10
+ /**
11
+ * Tracks the current editing phase and exposes computed suppression queries.
12
+ * Replaces the scattered boolean flags (`isProcessingTextChange`,
13
+ * `isDuringTransaction`, `blockEmitting`, `isManagingAnchor`) that previously
14
+ * guarded re-entrant code paths in the view.
15
+ */
16
+ class EditSession {
17
+ var phase: EditPhase = EditPhase.Idle
18
+ private set
19
+
20
+ var isTextChanging: Boolean = false
21
+ var didTextChangeRecently: Boolean = false
22
+
23
+ val shouldSuppressTextWatcher: Boolean
24
+ get() = phase != EditPhase.Idle
25
+
26
+ val shouldSuppressEvents: Boolean
27
+ get() = phase == EditPhase.Importing
28
+
29
+ val shouldSuppressAnchorSync: Boolean
30
+ get() = phase == EditPhase.ManagingAnchors
31
+
32
+ fun enter(newPhase: EditPhase) {
33
+ phase = newPhase
34
+ }
35
+
36
+ fun exit() {
37
+ phase = EditPhase.Idle
38
+ }
39
+
40
+ fun <T> scoped(
41
+ newPhase: EditPhase,
42
+ block: () -> T,
43
+ ): T {
44
+ val previous = phase
45
+ phase = newPhase
46
+ try {
47
+ return block()
48
+ } finally {
49
+ phase = previous
50
+ }
51
+ }
52
+ }