react-native-marcus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +227 -0
  3. package/RNMarcus.podspec +78 -0
  4. package/android/build.gradle +220 -0
  5. package/android/proguard-rules.pro +1 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/cpp/CMakeLists.txt +41 -0
  9. package/android/src/main/cpp/MarkdownParser.cpp +49 -0
  10. package/android/src/main/cpp/MarkdownParser.h +56 -0
  11. package/android/src/main/cpp/OnLoad.cpp +17 -0
  12. package/android/src/main/java/app/getbullet/marcus/MarcusModule.kt +28 -0
  13. package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +31 -0
  14. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +63 -0
  15. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorViewManager.kt +32 -0
  16. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorViewManagerSpec.kt +21 -0
  17. package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +333 -0
  18. package/android/src/main/java/app/getbullet/marcus/MarkdownParseResult.kt +12 -0
  19. package/android/src/main/java/app/getbullet/marcus/MarkdownParser.kt +89 -0
  20. package/android/src/main/java/app/getbullet/marcus/MarkdownRange.kt +11 -0
  21. package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +81 -0
  22. package/android/src/main/java/app/getbullet/marcus/MarkdownTextWatcher.kt +17 -0
  23. package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +84 -0
  24. package/android/src/main/java/app/getbullet/marcus/ReactLineHeight.java +29 -0
  25. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +6 -0
  26. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +105 -0
  27. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +67 -0
  28. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +6 -0
  29. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontFamilySpan.kt +28 -0
  30. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontSizeSpan.kt +7 -0
  31. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownForegroundColorSpan.kt +6 -0
  32. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +57 -0
  33. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +6 -0
  34. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLineHeightSpan.kt +21 -0
  35. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownSpan.kt +7 -0
  36. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownStrikethroughSpan.kt +5 -0
  37. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownUnderlineSpan.kt +5 -0
  38. package/android/src/main/new_arch/CMakeLists.txt +70 -0
  39. package/android/src/main/new_arch/RNMarcusSpec.cpp +43 -0
  40. package/android/src/main/new_arch/RNMarcusSpec.h +36 -0
  41. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorShadowNode.cpp +218 -0
  42. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorShadowNode.h +54 -0
  43. package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +156 -0
  44. package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +130 -0
  45. package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +205 -0
  46. package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +78 -0
  47. package/android/src/test/resources/render-model.txt +583 -0
  48. package/apple/MarcusModule.h +8 -0
  49. package/apple/MarcusModule.mm +26 -0
  50. package/apple/MarcusRange.h +15 -0
  51. package/apple/MarcusRange.mm +15 -0
  52. package/apple/MarcusTextInputDecoratorComponentView.h +12 -0
  53. package/apple/MarcusTextInputDecoratorComponentView.mm +121 -0
  54. package/apple/MarcusTextInputDecoratorShadowNode.cpp +251 -0
  55. package/apple/MarcusTextInputDecoratorShadowNode.h +65 -0
  56. package/apple/MarcusTextInputDecoratorShadowNode.mm +204 -0
  57. package/apple/MarcusTextInputDecoratorViewManager.h +8 -0
  58. package/apple/MarcusTextInputDecoratorViewManager.mm +11 -0
  59. package/apple/MarkdownAdaptiveImageGlyph.swift +44 -0
  60. package/apple/MarkdownAttributes.h +31 -0
  61. package/apple/MarkdownAttributes.m +9 -0
  62. package/apple/MarkdownBackedTextInputDelegate.swift +117 -0
  63. package/apple/MarkdownDecorator.swift +316 -0
  64. package/apple/MarkdownFonts.swift +21 -0
  65. package/apple/MarkdownFormatter+React.swift +51 -0
  66. package/apple/MarkdownFormatter.swift +509 -0
  67. package/apple/MarkdownLog.h +10 -0
  68. package/apple/MarkdownLog.mm +6 -0
  69. package/apple/MarkdownParser.swift +200 -0
  70. package/apple/MarkdownSwiftInterop.h +23 -0
  71. package/apple/MarkdownTextBackground.swift +17 -0
  72. package/apple/MarkdownTextFieldObserver.swift +90 -0
  73. package/apple/MarkdownTextLayoutFragment.swift +205 -0
  74. package/apple/MarkdownTextLayoutManagerDelegate.swift +84 -0
  75. package/apple/MarkdownTextStorageDelegate.swift +32 -0
  76. package/apple/MarkdownTextViewObserver.swift +30 -0
  77. package/apple/RCTMarcusStyle+Codegen.h +18 -0
  78. package/apple/RCTMarcusStyle+Codegen.mm +64 -0
  79. package/apple/RCTMarcusStyle.h +41 -0
  80. package/apple/RCTMarcusStyle.m +5 -0
  81. package/apple/RCTMarcusUtils.h +26 -0
  82. package/apple/RCTMarcusUtils.mm +26 -0
  83. package/apple/RCTTextInput+AdaptiveImageGlyph.h +16 -0
  84. package/apple/RCTTextInput+AdaptiveImageGlyph.mm +79 -0
  85. package/apple/RCTTextInputComponentView+Marcus.h +11 -0
  86. package/apple/RCTTextInputComponentView+Marcus.mm +79 -0
  87. package/cpp/MarkdownCxx.modulemap +8 -0
  88. package/cpp/MarkdownGlobal.cpp +48 -0
  89. package/cpp/MarkdownGlobal.h +33 -0
  90. package/cpp/MarkdownWorkletParser.cpp +93 -0
  91. package/cpp/MarkdownWorkletParser.h +45 -0
  92. package/cpp/RuntimeDecorator.cpp +39 -0
  93. package/cpp/RuntimeDecorator.h +14 -0
  94. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorState.h +33 -0
  95. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextInputDecoratorViewComponentDescriptor.h +17 -0
  96. package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  97. package/lib/commonjs/MarkdownTextInput.js +99 -0
  98. package/lib/commonjs/MarkdownTextInput.js.map +1 -0
  99. package/lib/commonjs/MarkdownTextInput.web.js +618 -0
  100. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
  101. package/lib/commonjs/NativeMarcusModule.js +9 -0
  102. package/lib/commonjs/NativeMarcusModule.js.map +1 -0
  103. package/lib/commonjs/commonTypes.js +2 -0
  104. package/lib/commonjs/commonTypes.js.map +1 -0
  105. package/lib/commonjs/global.d.js +2 -0
  106. package/lib/commonjs/global.d.js.map +1 -0
  107. package/lib/commonjs/index.js +27 -0
  108. package/lib/commonjs/index.js.map +1 -0
  109. package/lib/commonjs/package.json +1 -0
  110. package/lib/commonjs/parser/index.js +7030 -0
  111. package/lib/commonjs/parser/index.js.map +7 -0
  112. package/lib/commonjs/rangeUtils.js +71 -0
  113. package/lib/commonjs/rangeUtils.js.map +1 -0
  114. package/lib/commonjs/react-native-web.d.js +2 -0
  115. package/lib/commonjs/react-native-web.d.js.map +1 -0
  116. package/lib/commonjs/styleUtils.js +91 -0
  117. package/lib/commonjs/styleUtils.js.map +1 -0
  118. package/lib/commonjs/web/InputHistory.js +111 -0
  119. package/lib/commonjs/web/InputHistory.js.map +1 -0
  120. package/lib/commonjs/web/MarkdownTextInput.css +72 -0
  121. package/lib/commonjs/web/utils/blockLayout.js +132 -0
  122. package/lib/commonjs/web/utils/blockLayout.js.map +1 -0
  123. package/lib/commonjs/web/utils/blockUtils.js +219 -0
  124. package/lib/commonjs/web/utils/blockUtils.js.map +1 -0
  125. package/lib/commonjs/web/utils/browserUtils.js +40 -0
  126. package/lib/commonjs/web/utils/browserUtils.js.map +1 -0
  127. package/lib/commonjs/web/utils/cursorUtils.js +157 -0
  128. package/lib/commonjs/web/utils/cursorUtils.js.map +1 -0
  129. package/lib/commonjs/web/utils/inputUtils.js +152 -0
  130. package/lib/commonjs/web/utils/inputUtils.js.map +1 -0
  131. package/lib/commonjs/web/utils/measureUtils.js +0 -0
  132. package/lib/commonjs/web/utils/measureUtils.js.map +1 -0
  133. package/lib/commonjs/web/utils/parserUtils.js +552 -0
  134. package/lib/commonjs/web/utils/parserUtils.js.map +1 -0
  135. package/lib/commonjs/web/utils/treeUtils.js +109 -0
  136. package/lib/commonjs/web/utils/treeUtils.js.map +1 -0
  137. package/lib/commonjs/web/utils/webStyleUtils.js +59 -0
  138. package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -0
  139. package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  140. package/lib/module/MarkdownTextInput.js +94 -0
  141. package/lib/module/MarkdownTextInput.js.map +1 -0
  142. package/lib/module/MarkdownTextInput.web.js +612 -0
  143. package/lib/module/MarkdownTextInput.web.js.map +1 -0
  144. package/lib/module/NativeMarcusModule.js +5 -0
  145. package/lib/module/NativeMarcusModule.js.map +1 -0
  146. package/lib/module/commonTypes.js +2 -0
  147. package/lib/module/commonTypes.js.map +1 -0
  148. package/lib/module/global.d.js +2 -0
  149. package/lib/module/global.d.js.map +1 -0
  150. package/lib/module/index.js +5 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/package.json +1 -0
  153. package/lib/module/parser/index.js +7029 -0
  154. package/lib/module/parser/index.js.map +7 -0
  155. package/lib/module/rangeUtils.js +66 -0
  156. package/lib/module/rangeUtils.js.map +1 -0
  157. package/lib/module/react-native-web.d.js +2 -0
  158. package/lib/module/react-native-web.d.js.map +1 -0
  159. package/lib/module/styleUtils.js +88 -0
  160. package/lib/module/styleUtils.js.map +1 -0
  161. package/lib/module/web/InputHistory.js +106 -0
  162. package/lib/module/web/InputHistory.js.map +1 -0
  163. package/lib/module/web/MarkdownTextInput.css +72 -0
  164. package/lib/module/web/utils/blockLayout.js +128 -0
  165. package/lib/module/web/utils/blockLayout.js.map +1 -0
  166. package/lib/module/web/utils/blockUtils.js +210 -0
  167. package/lib/module/web/utils/blockUtils.js.map +1 -0
  168. package/lib/module/web/utils/browserUtils.js +36 -0
  169. package/lib/module/web/utils/browserUtils.js.map +1 -0
  170. package/lib/module/web/utils/cursorUtils.js +150 -0
  171. package/lib/module/web/utils/cursorUtils.js.map +1 -0
  172. package/lib/module/web/utils/inputUtils.js +144 -0
  173. package/lib/module/web/utils/inputUtils.js.map +1 -0
  174. package/lib/module/web/utils/measureUtils.js +0 -0
  175. package/lib/module/web/utils/measureUtils.js.map +1 -0
  176. package/lib/module/web/utils/parserUtils.js +547 -0
  177. package/lib/module/web/utils/parserUtils.js.map +1 -0
  178. package/lib/module/web/utils/treeUtils.js +103 -0
  179. package/lib/module/web/utils/treeUtils.js.map +1 -0
  180. package/lib/module/web/utils/webStyleUtils.js +54 -0
  181. package/lib/module/web/utils/webStyleUtils.js.map +1 -0
  182. package/lib/typescript/commonjs/e2e/checks.test.d.ts +2 -0
  183. package/lib/typescript/commonjs/e2e/checks.test.d.ts.map +1 -0
  184. package/lib/typescript/commonjs/e2e/editing.test.d.ts +2 -0
  185. package/lib/typescript/commonjs/e2e/editing.test.d.ts.map +1 -0
  186. package/lib/typescript/commonjs/e2e/helpers.d.ts +23 -0
  187. package/lib/typescript/commonjs/e2e/helpers.d.ts.map +1 -0
  188. package/lib/typescript/commonjs/e2e/smoke.test.d.ts +2 -0
  189. package/lib/typescript/commonjs/e2e/smoke.test.d.ts.map +1 -0
  190. package/lib/typescript/commonjs/package.json +1 -0
  191. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +63 -0
  192. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  193. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +22 -0
  194. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -0
  195. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +41 -0
  196. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/src/NativeMarcusModule.d.ts +7 -0
  198. package/lib/typescript/commonjs/src/NativeMarcusModule.d.ts.map +1 -0
  199. package/lib/typescript/commonjs/src/commonTypes.d.ts +10 -0
  200. package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -0
  201. package/lib/typescript/commonjs/src/index.d.ts +5 -0
  202. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  203. package/lib/typescript/commonjs/src/parser/index.d.ts +8 -0
  204. package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts +8 -0
  206. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -0
  207. package/lib/typescript/commonjs/src/rangeUtils.d.ts +6 -0
  208. package/lib/typescript/commonjs/src/rangeUtils.d.ts.map +1 -0
  209. package/lib/typescript/commonjs/src/styleUtils.d.ts +8 -0
  210. package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -0
  211. package/lib/typescript/commonjs/src/web/InputHistory.d.ts +24 -0
  212. package/lib/typescript/commonjs/src/web/InputHistory.d.ts.map +1 -0
  213. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +41 -0
  214. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -0
  215. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +33 -0
  216. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -0
  217. package/lib/typescript/commonjs/src/web/utils/browserUtils.d.ts +7 -0
  218. package/lib/typescript/commonjs/src/web/utils/browserUtils.d.ts.map +1 -0
  219. package/lib/typescript/commonjs/src/web/utils/cursorUtils.d.ts +12 -0
  220. package/lib/typescript/commonjs/src/web/utils/cursorUtils.d.ts.map +1 -0
  221. package/lib/typescript/commonjs/src/web/utils/inputUtils.d.ts +12 -0
  222. package/lib/typescript/commonjs/src/web/utils/inputUtils.d.ts.map +1 -0
  223. package/lib/typescript/commonjs/src/web/utils/measureUtils.d.ts +22 -0
  224. package/lib/typescript/commonjs/src/web/utils/measureUtils.d.ts.map +1 -0
  225. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +44 -0
  226. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -0
  227. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +25 -0
  228. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -0
  229. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +7 -0
  230. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -0
  231. package/lib/typescript/module/e2e/checks.test.d.ts +2 -0
  232. package/lib/typescript/module/e2e/checks.test.d.ts.map +1 -0
  233. package/lib/typescript/module/e2e/editing.test.d.ts +2 -0
  234. package/lib/typescript/module/e2e/editing.test.d.ts.map +1 -0
  235. package/lib/typescript/module/e2e/helpers.d.ts +23 -0
  236. package/lib/typescript/module/e2e/helpers.d.ts.map +1 -0
  237. package/lib/typescript/module/e2e/smoke.test.d.ts +2 -0
  238. package/lib/typescript/module/e2e/smoke.test.d.ts.map +1 -0
  239. package/lib/typescript/module/package.json +1 -0
  240. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +63 -0
  241. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  242. package/lib/typescript/module/src/MarkdownTextInput.d.ts +22 -0
  243. package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -0
  244. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +41 -0
  245. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -0
  246. package/lib/typescript/module/src/NativeMarcusModule.d.ts +7 -0
  247. package/lib/typescript/module/src/NativeMarcusModule.d.ts.map +1 -0
  248. package/lib/typescript/module/src/commonTypes.d.ts +10 -0
  249. package/lib/typescript/module/src/commonTypes.d.ts.map +1 -0
  250. package/lib/typescript/module/src/index.d.ts +5 -0
  251. package/lib/typescript/module/src/index.d.ts.map +1 -0
  252. package/lib/typescript/module/src/parser/index.d.ts +8 -0
  253. package/lib/typescript/module/src/parser/index.d.ts.map +1 -0
  254. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts +8 -0
  255. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -0
  256. package/lib/typescript/module/src/rangeUtils.d.ts +6 -0
  257. package/lib/typescript/module/src/rangeUtils.d.ts.map +1 -0
  258. package/lib/typescript/module/src/styleUtils.d.ts +8 -0
  259. package/lib/typescript/module/src/styleUtils.d.ts.map +1 -0
  260. package/lib/typescript/module/src/web/InputHistory.d.ts +24 -0
  261. package/lib/typescript/module/src/web/InputHistory.d.ts.map +1 -0
  262. package/lib/typescript/module/src/web/utils/blockLayout.d.ts +41 -0
  263. package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -0
  264. package/lib/typescript/module/src/web/utils/blockUtils.d.ts +33 -0
  265. package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -0
  266. package/lib/typescript/module/src/web/utils/browserUtils.d.ts +7 -0
  267. package/lib/typescript/module/src/web/utils/browserUtils.d.ts.map +1 -0
  268. package/lib/typescript/module/src/web/utils/cursorUtils.d.ts +12 -0
  269. package/lib/typescript/module/src/web/utils/cursorUtils.d.ts.map +1 -0
  270. package/lib/typescript/module/src/web/utils/inputUtils.d.ts +12 -0
  271. package/lib/typescript/module/src/web/utils/inputUtils.d.ts.map +1 -0
  272. package/lib/typescript/module/src/web/utils/measureUtils.d.ts +22 -0
  273. package/lib/typescript/module/src/web/utils/measureUtils.d.ts.map +1 -0
  274. package/lib/typescript/module/src/web/utils/parserUtils.d.ts +44 -0
  275. package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -0
  276. package/lib/typescript/module/src/web/utils/treeUtils.d.ts +25 -0
  277. package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -0
  278. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +7 -0
  279. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -0
  280. package/package.json +167 -0
  281. package/react-native.config.js +10 -0
  282. package/src/MarcusTextInputDecoratorViewNativeComponent.ts +77 -0
  283. package/src/MarkdownTextInput.tsx +139 -0
  284. package/src/MarkdownTextInput.web.tsx +958 -0
  285. package/src/NativeMarcusModule.ts +8 -0
  286. package/src/commonTypes.ts +30 -0
  287. package/src/global.d.ts +1 -0
  288. package/src/index.tsx +4 -0
  289. package/src/parser/index.ts +556 -0
  290. package/src/parser/micromark-extension-mentions.ts +82 -0
  291. package/src/rangeUtils.ts +68 -0
  292. package/src/react-native-web.d.ts +1 -0
  293. package/src/styleUtils.ts +100 -0
  294. package/src/web/InputHistory.ts +137 -0
  295. package/src/web/MarkdownTextInput.css +72 -0
  296. package/src/web/utils/blockLayout.ts +163 -0
  297. package/src/web/utils/blockUtils.ts +245 -0
  298. package/src/web/utils/browserUtils.ts +41 -0
  299. package/src/web/utils/cursorUtils.ts +204 -0
  300. package/src/web/utils/inputUtils.ts +206 -0
  301. package/src/web/utils/measureUtils.ts +0 -0
  302. package/src/web/utils/parserUtils.ts +813 -0
  303. package/src/web/utils/treeUtils.ts +149 -0
  304. package/src/web/utils/webStyleUtils.ts +69 -0
@@ -0,0 +1,44 @@
1
+ import UIKit
2
+
3
+ /// Genmoji handling for iOS 18+.
4
+ ///
5
+ /// An adaptive image glyph inserted directly would live in the text as an
6
+ /// `NSAdaptiveImageGlyph`, which markdown formatting has no representation for.
7
+ /// Instead it is flattened to a plain image and pasted, so it arrives as an
8
+ /// ordinary attachment.
9
+ @objc public final class MarkdownAdaptiveImageGlyph: NSObject {
10
+
11
+ @objc(handlePasteInTextInputView:glyph:)
12
+ public static func handlePaste(in textInputView: UIView, glyph: Any) {
13
+ guard let image = flattenedImage(from: glyph) else { return }
14
+
15
+ // Round-tripping through the pasteboard is what routes the image into the
16
+ // text input, so the user's own clipboard contents are saved and restored.
17
+ let pasteboard = UIPasteboard.general
18
+ let savedItems = pasteboard.items
19
+ pasteboard.image = image
20
+
21
+ let paste = #selector(UIResponderStandardEditActions.paste(_:))
22
+ if textInputView.canPerformAction(paste, withSender: nil) {
23
+ textInputView.perform(paste, with: nil)
24
+ }
25
+
26
+ pasteboard.items = savedItems
27
+ }
28
+
29
+ private static func flattenedImage(from glyph: Any) -> UIImage? {
30
+ guard #available(iOS 18.0, *),
31
+ let glyph = glyph as? NSAdaptiveImageGlyph,
32
+ let original = UIImage(data: glyph.imageContent)
33
+ else { return nil }
34
+
35
+ let targetSize = CGSize(width: 128, height: 128)
36
+ let resized = UIGraphicsImageRenderer(size: targetSize).image { _ in
37
+ original.draw(in: CGRect(origin: .zero, size: targetSize))
38
+ }
39
+
40
+ // The PNG round-trip drops the adaptive glyph's extra representations.
41
+ guard let pngData = resized.pngData() else { return nil }
42
+ return UIImage(data: pngData)
43
+ }
44
+ }
@@ -0,0 +1,31 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ // Custom attributed-string keys used across the module. They live here rather
6
+ // than alongside the formatter so that consumers which only need the key -- the
7
+ // `_textOf:equals:` swizzle, for instance -- don't pull in the formatter.
8
+
9
+ // Marks a string as markdown-formatted, which switches the swizzled
10
+ // `_textOf:equals:` to compare plain strings instead of full attributes.
11
+ FOUNDATION_EXPORT const
12
+ NSAttributedStringKey RCTMarcusTextAttributeName NS_SWIFT_NAME(marcusText);
13
+
14
+ // Carries a MarkdownTextBackground for mention pills, drawn by the layout
15
+ // fragment rather than by NSBackgroundColorAttributeName.
16
+ FOUNDATION_EXPORT const NSAttributedStringKey
17
+ RCTMarcusTextBackgroundAttributeName NS_SWIFT_NAME(marcusTextBackground);
18
+
19
+ // Blockquote nesting level, used to draw the ribbons.
20
+ FOUNDATION_EXPORT const NSAttributedStringKey
21
+ RCTMarcusBlockquoteDepthAttributeName NS_SWIFT_NAME(marcusBlockquoteDepth);
22
+
23
+ // Distance from the paragraph's left edge to the blockquote's own box: the
24
+ // indent the line already carried when the blockquote was applied, plus any
25
+ // room left for a list marker drawn in front of the ribbons. They are drawn
26
+ // from there rather than from the text origin, which also moves with containers
27
+ // nested inside the quote (a list, say).
28
+ FOUNDATION_EXPORT const NSAttributedStringKey
29
+ RCTMarcusBlockquoteIndentAttributeName NS_SWIFT_NAME(marcusBlockquoteIndent);
30
+
31
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,9 @@
1
+ #import <RNMarcus/MarkdownAttributes.h>
2
+
3
+ const NSAttributedStringKey RCTMarcusTextAttributeName = @"RCTMarcusText";
4
+ const NSAttributedStringKey RCTMarcusTextBackgroundAttributeName =
5
+ @"RCTMarcusTextBackground";
6
+ const NSAttributedStringKey RCTMarcusBlockquoteDepthAttributeName =
7
+ @"RCTMarcusBlockquoteDepth";
8
+ const NSAttributedStringKey RCTMarcusBlockquoteIndentAttributeName =
9
+ @"RCTMarcusBlockquoteIndent";
@@ -0,0 +1,117 @@
1
+ import React
2
+ import UIKit
3
+
4
+ /// Sits in front of the text view's real delegate purely to fix the caret
5
+ /// position after a blockquote; every other callback is forwarded untouched.
6
+ @objc
7
+ public final class MarkdownBackedTextInputDelegate: NSObject,
8
+ RCTBackedTextInputDelegate
9
+ {
10
+
11
+ private weak var textView: RCTUITextView?
12
+ private let originalTextInputDelegate: RCTBackedTextInputDelegate?
13
+
14
+ @objc public init(textView: RCTUITextView) {
15
+ self.textView = textView
16
+ self.originalTextInputDelegate = textView.textInputDelegate
17
+ super.init()
18
+ textView.textInputDelegate = self
19
+ }
20
+
21
+ deinit {
22
+ textView?.textInputDelegate = originalTextInputDelegate
23
+ }
24
+
25
+ public func textInputDidChangeSelection() {
26
+ originalTextInputDelegate?.textInputDidChangeSelection()
27
+
28
+ // After a newline at the end of a blockquote, the typing attributes for the
29
+ // next line still carry the blockquote's paragraph indents, which pushes the
30
+ // caret to the right instead of leaving it at the start of the line.
31
+ guard let textView,
32
+ let typingAttributes = textView.typingAttributes,
33
+ let paragraphStyle = typingAttributes[.paragraphStyle]
34
+ as? NSParagraphStyle,
35
+ let mutableParagraphStyle = paragraphStyle.mutableCopy()
36
+ as? NSMutableParagraphStyle
37
+ else { return }
38
+
39
+ mutableParagraphStyle.firstLineHeadIndent = 0
40
+ mutableParagraphStyle.headIndent = 0
41
+
42
+ var updated = typingAttributes
43
+ updated[.paragraphStyle] = mutableParagraphStyle
44
+ textView.typingAttributes = updated
45
+ }
46
+
47
+ // MARK: - Straight forwarding
48
+
49
+ public func textInputDidChange() {
50
+ originalTextInputDelegate?.textInputDidChange()
51
+ }
52
+ public func textInputDidBeginEditing() {
53
+ originalTextInputDelegate?.textInputDidBeginEditing()
54
+ }
55
+ public func textInputDidEndEditing() {
56
+ originalTextInputDelegate?.textInputDidEndEditing()
57
+ }
58
+ public func textInputDidReturn() {
59
+ originalTextInputDelegate?.textInputDidReturn()
60
+ }
61
+
62
+ public func textInputShouldBeginEditing() -> Bool {
63
+ originalTextInputDelegate?.textInputShouldBeginEditing() ?? true
64
+ }
65
+
66
+ public func textInputShouldEndEditing() -> Bool {
67
+ originalTextInputDelegate?.textInputShouldEndEditing() ?? true
68
+ }
69
+
70
+ public func textInputShouldReturn() -> Bool {
71
+ originalTextInputDelegate?.textInputShouldReturn() ?? true
72
+ }
73
+
74
+ public func textInputShouldSubmitOnReturn() -> Bool {
75
+ originalTextInputDelegate?.textInputShouldSubmitOnReturn() ?? true
76
+ }
77
+
78
+ // The protocol annotates the return as nonnull, but its own documentation says
79
+ // returning nil rejects the change. Declared as an implicitly unwrapped
80
+ // optional so a nil from the real delegate is forwarded rather than being
81
+ // silently turned into "accept".
82
+ public func textInputShouldChangeText(_ text: String, in range: NSRange)
83
+ -> String!
84
+ {
85
+ originalTextInputDelegate?.textInputShouldChangeText(text, in: range)
86
+ }
87
+
88
+ // MARK: - Paste
89
+
90
+ // These are not part of RCTBackedTextInputDelegate; they are added by a
91
+ // patch applied in the New Expensify app, so they are forwarded dynamically
92
+ // and only when the real delegate actually implements them.
93
+ // See https://github.com/Expensify/App/blob/main/patches
94
+
95
+ @objc(textInputDidPaste:withData:)
96
+ public func textInputDidPaste(_ type: String, withData data: String) {
97
+ let selector = #selector(
98
+ MarkdownBackedTextInputDelegate.textInputDidPaste(_:withData:)
99
+ )
100
+ guard let delegate = originalTextInputDelegate as AnyObject?,
101
+ delegate.responds(to: selector)
102
+ else { return }
103
+ _ = delegate.perform(selector, with: type, with: data)
104
+ }
105
+
106
+ @objc(textInputDidPaste:)
107
+ public func textInputDidPaste(_ items: [[String: String]]) {
108
+ let selector = #selector(
109
+ MarkdownBackedTextInputDelegate.textInputDidPaste(_:)
110
+ as (MarkdownBackedTextInputDelegate) -> ([[String: String]]) -> Void
111
+ )
112
+ guard let delegate = originalTextInputDelegate as AnyObject?,
113
+ delegate.responds(to: selector)
114
+ else { return }
115
+ _ = delegate.perform(selector, with: items)
116
+ }
117
+ }
@@ -0,0 +1,316 @@
1
+ import React
2
+ import UIKit
3
+
4
+ /// Owns everything the decorator does to a React Native text input: installing
5
+ /// the observers, delegates and layout-manager delegate that apply markdown
6
+ /// formatting, and tearing them down again.
7
+ ///
8
+ /// The Fabric component view is a thin Objective-C++ shell around this, because
9
+ /// `RCTComponentViewProtocol` carries C++ types in its signatures and cannot be
10
+ /// implemented in Swift.
11
+ @objc public final class MarkdownDecorator: NSObject {
12
+
13
+ private var markdownUtils = RCTMarcusUtils()
14
+
15
+ private weak var textView: RCTUITextView?
16
+ private weak var textField: RCTUITextField?
17
+
18
+ /// What `attach(to:)` last bound to, so a repeated call for an unchanged
19
+ /// backed view can be skipped.
20
+ private weak var attachedBackedTextInputView: UIView?
21
+
22
+ /// The backed view's own smart-dashes setting, restored on detach.
23
+ private var previousSmartDashesType: UITextSmartDashesType?
24
+
25
+ private var layoutManagerDelegate: MarkdownTextLayoutManagerDelegate?
26
+ private var backedTextInputDelegate: MarkdownBackedTextInputDelegate?
27
+ private var textStorageDelegate: MarkdownTextStorageDelegate?
28
+ private var textViewObserver: MarkdownTextViewObserver?
29
+ private var textFieldObserver: MarkdownTextFieldObserver?
30
+
31
+ @objc public private(set) var isAttached = false
32
+
33
+ // MARK: - Props
34
+
35
+ @objc public var markdownStyle: RCTMarcusStyle? {
36
+ get { markdownUtils.markdownStyle }
37
+ set { markdownUtils.markdownStyle = newValue }
38
+ }
39
+
40
+ @objc public var parserId: NSNumber? {
41
+ get { markdownUtils.parserId }
42
+ set { markdownUtils.parserId = newValue }
43
+ }
44
+
45
+ /// Drops all per-mount state so the view can be recycled.
46
+ @objc public func reset() {
47
+ markdownUtils = RCTMarcusUtils()
48
+ }
49
+
50
+ // MARK: - Attach / detach
51
+
52
+ /// Attaches to the backed text input inside `textInputComponentView`.
53
+ ///
54
+ /// Detaches first, so this doubles as the re-attach path used when React
55
+ /// Native swaps its backed view on a `multiline` change.
56
+ @objc public func attach(to textInputComponentView: UIView) {
57
+ guard
58
+ let backedTextInputView =
59
+ textInputComponentView.value(forKey: "_backedTextInputView") as? UIView
60
+ else {
61
+ assertionFailure(
62
+ "TextInput component view has no backed text input view."
63
+ )
64
+ return
65
+ }
66
+
67
+ // The re-attach hook fires for every subview added to the TextInput
68
+ // component view, so ignore the ones that leave the backed view unchanged.
69
+ if isAttached, backedTextInputView === attachedBackedTextInputView {
70
+ return
71
+ }
72
+
73
+ detach()
74
+
75
+ if let textField = backedTextInputView as? RCTUITextField {
76
+ attach(toTextField: textField)
77
+ } else if let textView = backedTextInputView as? RCTUITextView {
78
+ attach(toTextView: textView)
79
+ } else {
80
+ assertionFailure("Cannot enable Markdown for this type of TextInput.")
81
+ return
82
+ }
83
+
84
+ attachedBackedTextInputView = backedTextInputView
85
+ isAttached = true
86
+ }
87
+
88
+ @objc public func detach() {
89
+ guard isAttached else { return }
90
+ isAttached = false
91
+
92
+ if let textView {
93
+ textView.textLayoutManager?.delegate = nil
94
+ backedTextInputDelegate = nil
95
+ if let textViewObserver {
96
+ textView.removeObserver(
97
+ textViewObserver,
98
+ forKeyPath: "defaultTextAttributes",
99
+ context: nil
100
+ )
101
+ }
102
+ setSmartDashesEnabled(true, on: textView)
103
+ setAdaptiveImageGlyphSupport(false, on: textView)
104
+ textViewObserver = nil
105
+ textStorageDelegate = nil
106
+ textView.textStorage.delegate = nil
107
+ self.textView = nil
108
+ }
109
+
110
+ if let textField {
111
+ if let textFieldObserver {
112
+ textField.removeTarget(
113
+ textFieldObserver,
114
+ action: #selector(MarkdownTextFieldObserver.textFieldDidChange(_:)),
115
+ for: .editingChanged
116
+ )
117
+ textField.removeTarget(
118
+ textFieldObserver,
119
+ action: #selector(
120
+ MarkdownTextFieldObserver.textFieldDidEndEditing(_:)
121
+ ),
122
+ for: .editingDidEnd
123
+ )
124
+ textField.removeObserver(
125
+ textFieldObserver,
126
+ forKeyPath: "text",
127
+ context: nil
128
+ )
129
+ textField.removeObserver(
130
+ textFieldObserver,
131
+ forKeyPath: "attributedText",
132
+ context: nil
133
+ )
134
+ }
135
+ setSmartDashesEnabled(true, on: textField)
136
+ setAdaptiveImageGlyphSupport(false, on: textField)
137
+ textFieldObserver = nil
138
+ self.textField = nil
139
+ }
140
+
141
+ layoutManagerDelegate = nil
142
+ attachedBackedTextInputView = nil
143
+ }
144
+
145
+ /// Re-applies formatting after a style prop change.
146
+ @objc public func applyNewStyles() {
147
+ if let textView, let attributedText = textView.attributedText {
148
+ textView.textStorage.setAttributedString(attributedText)
149
+ }
150
+ if let textField, let textFieldObserver {
151
+ textFieldObserver.textFieldDidChange(textField)
152
+ }
153
+ }
154
+
155
+ // MARK: - Single line
156
+
157
+ private func attach(toTextField textField: RCTUITextField) {
158
+ self.textField = textField
159
+
160
+ // Formatting would be overwritten otherwise.
161
+ assert(textField.adjustsFontSizeToFitWidth == false)
162
+
163
+ setSmartDashesEnabled(false, on: textField)
164
+ setAdaptiveImageGlyphSupport(true, on: textField)
165
+
166
+ let observer = MarkdownTextFieldObserver(
167
+ textField: textField,
168
+ markdownUtils: markdownUtils
169
+ )
170
+ textFieldObserver = observer
171
+
172
+ textField.addTarget(
173
+ observer,
174
+ action: #selector(MarkdownTextFieldObserver.textFieldDidChange(_:)),
175
+ for: .editingChanged
176
+ )
177
+ textField.addTarget(
178
+ observer,
179
+ action: #selector(MarkdownTextFieldObserver.textFieldDidEndEditing(_:)),
180
+ for: .editingDidEnd
181
+ )
182
+ textField.addObserver(
183
+ observer,
184
+ forKeyPath: "text",
185
+ options: .new,
186
+ context: nil
187
+ )
188
+ textField.addObserver(
189
+ observer,
190
+ forKeyPath: "attributedText",
191
+ options: .new,
192
+ context: nil
193
+ )
194
+
195
+ // Format the initial value.
196
+ observer.textFieldDidChange(textField)
197
+
198
+ // UITextField keeps its TextKit 2 stack private. The key names are spelled
199
+ // backwards so they don't appear as literals in the binary.
200
+ guard
201
+ let textContainer = textField.value(
202
+ forKey: Self.reversed("reniatnoCtxet_")
203
+ ) as? NSTextContainer,
204
+ let textStorage = textField.value(forKey: Self.reversed("egarotStxet_"))
205
+ as? NSTextStorage
206
+ else { return }
207
+
208
+ let delegate = MarkdownTextLayoutManagerDelegate(
209
+ textStorage: textStorage,
210
+ markdownUtils: markdownUtils
211
+ )
212
+ layoutManagerDelegate = delegate
213
+ textContainer.textLayoutManager?.delegate = delegate
214
+
215
+ // TODO: register blockquotes layout manager
216
+ // https://github.com/Expensify/react-native-live-markdown/issues/87
217
+ }
218
+
219
+ // MARK: - Multiline
220
+
221
+ private func attach(toTextView textView: RCTUITextView) {
222
+ self.textView = textView
223
+
224
+ setSmartDashesEnabled(false, on: textView)
225
+ setAdaptiveImageGlyphSupport(true, on: textView)
226
+
227
+ assert(textView.textStorage.delegate == nil)
228
+ let storageDelegate = MarkdownTextStorageDelegate(
229
+ textView: textView,
230
+ markdownUtils: markdownUtils
231
+ )
232
+ textStorageDelegate = storageDelegate
233
+ textView.textStorage.delegate = storageDelegate
234
+
235
+ let observer = MarkdownTextViewObserver(
236
+ textView: textView,
237
+ markdownUtils: markdownUtils
238
+ )
239
+ textViewObserver = observer
240
+ textView.addObserver(
241
+ observer,
242
+ forKeyPath: "defaultTextAttributes",
243
+ options: .new,
244
+ context: nil
245
+ )
246
+
247
+ // Format the initial value.
248
+ if let attributedText = textView.attributedText {
249
+ textView.textStorage.setAttributedString(attributedText)
250
+ }
251
+
252
+ let delegate = MarkdownTextLayoutManagerDelegate(
253
+ textStorage: textView.textStorage,
254
+ markdownUtils: markdownUtils
255
+ )
256
+ layoutManagerDelegate = delegate
257
+ textView.textLayoutManager?.delegate = delegate
258
+
259
+ // Fixes the caret position after a blockquote.
260
+ backedTextInputDelegate = MarkdownBackedTextInputDelegate(
261
+ textView: textView
262
+ )
263
+ }
264
+
265
+ // MARK: - Helpers
266
+
267
+ private static func reversed(_ value: String) -> String {
268
+ String(value.reversed())
269
+ }
270
+
271
+ /// Turns off the keyboard's `--` to em dash substitution while the input is
272
+ /// decorated, restoring whatever it was on detach so a recycled view is not
273
+ /// left changed.
274
+ ///
275
+ /// Smart punctuation rewrites the characters as they are typed, so `--` and
276
+ /// `---` never reach the parser: a setext heading or thematic break silently
277
+ /// stays plain text, while pasting the same characters works. Markdown syntax
278
+ /// is not prose, so the substitution is wrong here even though it is right in
279
+ /// a renderer.
280
+ ///
281
+ /// React Native does not expose this trait (it has `smartInsertDelete`, which
282
+ /// is unrelated), and `autoCorrect={false}` does not cover it either -- smart
283
+ /// dashes are independent of autocorrection.
284
+ private func setSmartDashesEnabled(_ enabled: Bool, on textView: RCTUITextView) {
285
+ if enabled {
286
+ textView.smartDashesType = previousSmartDashesType ?? .default
287
+ previousSmartDashesType = nil
288
+ } else {
289
+ previousSmartDashesType = textView.smartDashesType
290
+ textView.smartDashesType = .no
291
+ }
292
+ }
293
+
294
+ private func setSmartDashesEnabled(_ enabled: Bool, on textField: RCTUITextField) {
295
+ if enabled {
296
+ textField.smartDashesType = previousSmartDashesType ?? .default
297
+ previousSmartDashesType = nil
298
+ } else {
299
+ previousSmartDashesType = textField.smartDashesType
300
+ textField.smartDashesType = .no
301
+ }
302
+ }
303
+
304
+ private func setAdaptiveImageGlyphSupport(
305
+ _ enabled: Bool,
306
+ on textInputView: UIView
307
+ ) {
308
+ guard #available(iOS 18.0, *) else { return }
309
+ guard
310
+ textInputView.responds(
311
+ to: NSSelectorFromString("setSupportsAdaptiveImageGlyph:")
312
+ )
313
+ else { return }
314
+ textInputView.setValue(enabled, forKey: "supportsAdaptiveImageGlyph")
315
+ }
316
+ }
@@ -0,0 +1,21 @@
1
+ import UIKit
2
+
3
+ /// How a run's font is derived from the one already on the text.
4
+ ///
5
+ /// The formatter needs React Native's font resolution -- it understands weight
6
+ /// and style names, family fallbacks and the text-size multiplier, and matching
7
+ /// it by hand would be a second implementation to keep in step. Nothing else in
8
+ /// the formatter needs React at all, though, so naming that one dependency is
9
+ /// what lets the file compile, and so be tested, without the pod graph.
10
+ ///
11
+ /// `MarkdownReactFonts` is the production implementation; the test target
12
+ /// substitutes one whose output it can predict.
13
+ ///
14
+ /// Not `@objc`: nothing in Objective-C names this type -- the `@objc` entry
15
+ /// point supplies the implementation itself -- and marking it so would restrict
16
+ /// conformance to classes for no reason.
17
+ public protocol MarkdownFontProviding {
18
+ func update(_ font: UIFont?, weight: String) -> UIFont?
19
+ func update(_ font: UIFont?, style: String) -> UIFont?
20
+ func update(_ font: UIFont?, family: String?, size: CGFloat, weight: String?) -> UIFont?
21
+ }
@@ -0,0 +1,51 @@
1
+ import React
2
+ import UIKit
3
+
4
+ /// React Native's font resolution, and the only part of formatting that needs
5
+ /// the React pod graph.
6
+ @objc public final class MarkdownReactFonts: NSObject, MarkdownFontProviding {
7
+ public func update(_ font: UIFont?, weight: String) -> UIFont? {
8
+ RCTFont.update(font, withWeight: weight)
9
+ }
10
+
11
+ public func update(_ font: UIFont?, style: String) -> UIFont? {
12
+ RCTFont.update(font, withStyle: style)
13
+ }
14
+
15
+ public func update(
16
+ _ font: UIFont?,
17
+ family: String?,
18
+ size: CGFloat,
19
+ weight: String?
20
+ ) -> UIFont? {
21
+ RCTFont.update(
22
+ font,
23
+ withFamily: family,
24
+ size: NSNumber(value: Float(size)),
25
+ weight: weight,
26
+ style: nil,
27
+ variant: nil,
28
+ scaleMultiplier: 0
29
+ )
30
+ }
31
+ }
32
+
33
+ extension MarkdownFormatter {
34
+ /// The entry point the Objective-C++ callers use, unchanged. It supplies the
35
+ /// React font resolution so those call sites never have to know a seam exists.
36
+ @objc(formatAttributedString:defaultTextAttributes:ranges:style:)
37
+ public static func format(
38
+ _ attributedString: NSMutableAttributedString,
39
+ defaultTextAttributes: [NSAttributedString.Key: Any],
40
+ ranges: [MarcusRange],
41
+ style: RCTMarcusStyle
42
+ ) {
43
+ format(
44
+ attributedString,
45
+ defaultTextAttributes: defaultTextAttributes,
46
+ ranges: ranges,
47
+ style: style,
48
+ fonts: MarkdownReactFonts()
49
+ )
50
+ }
51
+ }