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,245 @@
1
+ import type { PartialMarkdownStyle } from "../../styleUtils"
2
+ import { toNumber } from "./blockLayout"
3
+ import type { LineLayout } from "./blockLayout"
4
+ import type { NodeType } from "./treeUtils"
5
+
6
+ /**
7
+ * Assigns CSS properties, skipping the ones the style does not define.
8
+ *
9
+ * Lengths arrive here already carrying their unit -- `processMarkdownStyle` runs the style through
10
+ * react-native-web's compiler before it reaches the builder -- but `parseRangesToHTMLNodes` can be
11
+ * called with a plain object that has not, and `CSSStyleDeclaration` silently drops a bare number.
12
+ * Adding the unit here means both spellings render the same.
13
+ */
14
+ function applyStyle(node: HTMLElement, styles: Record<string, string | number | undefined>) {
15
+ const target = node
16
+ Object.entries(styles).forEach(([property, value]) => {
17
+ if (value === undefined || value === null || value === "") {
18
+ return
19
+ }
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ ;(target.style as any)[property] = typeof value === "number" ? `${value}px` : value
22
+ })
23
+ }
24
+
25
+ /**
26
+ * Applies the layout the block walk computed for one line.
27
+ *
28
+ * Everything a container contributes lands here rather than on the container's own span: its gutter
29
+ * is part of the paragraph's indent, and its ribbon is painted behind the whole paragraph. That is
30
+ * how the native formatters do it too, and on the web it is the only placement that works -- a
31
+ * container's span begins before the markers of the containers outside it, so a border drawn on it
32
+ * would sit in front of a bullet that is meant to precede it, and a gutter set on it would open
33
+ * before that bullet rather than after.
34
+ *
35
+ * `padding-left` holds every line of the paragraph at the indent, and the negative `text-indent`
36
+ * pulls the first one back to where its own marker starts -- `firstLineHeadIndent` and `headIndent`,
37
+ * spelled in CSS. A line that wraps, or a line continuing the block with no marker of its own, then
38
+ * lines up with the text above it instead of with the marker.
39
+ */
40
+ function addLineLayout(
41
+ node: HTMLElement,
42
+ layout: LineLayout | undefined,
43
+ markdownStyle: PartialMarkdownStyle,
44
+ ) {
45
+ applyStyle(node, {
46
+ margin: "0",
47
+ padding: "0",
48
+ })
49
+
50
+ if (!layout) {
51
+ return
52
+ }
53
+
54
+ if (layout.indent > 0) {
55
+ applyStyle(node, {
56
+ paddingLeft: `${layout.indent}px`,
57
+ textIndent: `${layout.firstLineIndent - layout.indent}px`,
58
+ })
59
+ }
60
+
61
+ const width = toNumber(markdownStyle.blockquote?.borderWidth)
62
+ if (layout.ribbons.length === 0 || width <= 0) {
63
+ return
64
+ }
65
+
66
+ // One background layer per ribbon, each a solid bar of its own. They are painted on the paragraph
67
+ // rather than on any span so that they run its full height -- a quote that wraps gets one
68
+ // unbroken bar, exactly as `MarkdownTextLayoutFragment` draws it over the whole fragment.
69
+ const color = (markdownStyle.blockquote?.borderColor as string) || "transparent"
70
+ applyStyle(node, {
71
+ backgroundImage: layout.ribbons.map(() => `linear-gradient(${color}, ${color})`).join(", "),
72
+ backgroundPosition: layout.ribbons.map((x) => `${x}px 0`).join(", "),
73
+ backgroundSize: layout.ribbons.map(() => `${width}px 100%`).join(", "),
74
+ backgroundRepeat: "no-repeat",
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Opens the space a marker holds for the container that follows it.
80
+ *
81
+ * `MarkdownFormatter` kerns the marker's last character; the effect is the same, and it is what
82
+ * puts a nested list's indent between the quote's `>` and the bullet rather than in front of both.
83
+ */
84
+ function addBlockPrefixGap(node: HTMLElement, gap: number | undefined) {
85
+ if (gap) {
86
+ node.style.marginRight = `${gap}px`
87
+ }
88
+ }
89
+
90
+ function addStyleToBlock(
91
+ targetElement: HTMLElement,
92
+ type: NodeType,
93
+ markdownStyle: PartialMarkdownStyle,
94
+ isMultiline = true,
95
+ ) {
96
+ const node = targetElement
97
+
98
+ switch (type) {
99
+ case "syntax":
100
+ applyStyle(node, { color: markdownStyle.syntax?.color as string })
101
+ break
102
+ case "bold":
103
+ node.style.fontWeight = "bold"
104
+ break
105
+ case "italic":
106
+ node.style.fontStyle = "italic"
107
+ break
108
+ case "strikethrough":
109
+ node.style.textDecoration = "line-through"
110
+ break
111
+ case "emoji":
112
+ applyStyle(node, {
113
+ fontFamily: markdownStyle.emoji?.fontFamily,
114
+ fontSize: markdownStyle.emoji?.fontSize,
115
+ // Native re-centres a run whose font changed against the line height; the browser has no
116
+ // equivalent, and an emoji font left on the alphabetic baseline sits low.
117
+ verticalAlign: "middle",
118
+ })
119
+ break
120
+ case "mention-here":
121
+ addMentionStyle(node, markdownStyle.mentionHere)
122
+ break
123
+ case "mention-user":
124
+ addMentionStyle(node, markdownStyle.mentionUser)
125
+ break
126
+ case "mention-report":
127
+ addMentionStyle(node, markdownStyle.mentionReport)
128
+ break
129
+ case "link":
130
+ applyStyle(node, {
131
+ color: markdownStyle.link?.color as string,
132
+ textDecoration: "underline",
133
+ })
134
+ break
135
+ // Both carry a font, a colour and a background, and nothing else: native draws no border, no
136
+ // corner radius and no padding around either, so neither does this.
137
+ case "code":
138
+ applyStyle(node, {
139
+ fontFamily: markdownStyle.code?.fontFamily,
140
+ fontSize: markdownStyle.code?.fontSize,
141
+ color: markdownStyle.code?.color as string,
142
+ backgroundColor: markdownStyle.code?.backgroundColor as string,
143
+ })
144
+ break
145
+ case "pre":
146
+ applyStyle(node, {
147
+ fontFamily: markdownStyle.pre?.fontFamily,
148
+ fontSize: markdownStyle.pre?.fontSize,
149
+ color: markdownStyle.pre?.color as string,
150
+ backgroundColor: markdownStyle.pre?.backgroundColor as string,
151
+ })
152
+ break
153
+ // Block containers hold no box of their own: their gutters and ribbons belong to the
154
+ // paragraph, in `addLineLayout`. All that is left is letting a long unbroken word inside one
155
+ // break rather than push the line past the input's edge.
156
+ case "blockquote":
157
+ case "list-ordered":
158
+ case "list-unordered":
159
+ applyStyle(node, { overflowWrap: "anywhere" })
160
+ break
161
+ case "heading":
162
+ addHeadingStyle(node, markdownStyle)
163
+ break
164
+ case "text":
165
+ if (!isMultiline && targetElement.parentElement?.style) {
166
+ // Move text background styles from parent to the text node
167
+ const parentElement = targetElement.parentElement
168
+ node.style.cssText = parentElement.style.cssText
169
+ parentElement.style.cssText = ""
170
+ }
171
+ break
172
+ default:
173
+ break
174
+ }
175
+ }
176
+
177
+ function addMentionStyle(
178
+ node: HTMLElement,
179
+ style:
180
+ | { color?: unknown; backgroundColor?: unknown; borderRadius?: string | number }
181
+ | undefined,
182
+ ) {
183
+ applyStyle(node, {
184
+ color: style?.color as string,
185
+ backgroundColor: style?.backgroundColor as string,
186
+ borderRadius: style?.borderRadius,
187
+ })
188
+ }
189
+
190
+ /**
191
+ * Sizes a heading the way native does: level N is the base size scaled N-1 times.
192
+ *
193
+ * The level does not reach here -- `ungroupRanges` turns it into one nested span per level -- so
194
+ * the scaling is expressed in `em` and compounds down the nesting to the same number. This runs
195
+ * once the span is in the tree, which is what lets it tell an inner level from the outermost one.
196
+ */
197
+ function addHeadingStyle(node: HTMLElement, markdownStyle: PartialMarkdownStyle) {
198
+ const nested = isChildOfMarkdownElement(node, "heading")
199
+ const scale = markdownStyle.heading?.scale
200
+
201
+ applyStyle(node, {
202
+ fontWeight: "bold",
203
+ fontSize: nested ? `${scale === undefined ? 1 : scale}em` : markdownStyle.heading?.fontSize,
204
+ })
205
+ }
206
+
207
+ const MULTILINE_MARKDOWN_TYPES = ["codeblock"]
208
+
209
+ function isMultilineMarkdownType(type: NodeType) {
210
+ return MULTILINE_MARKDOWN_TYPES.includes(type)
211
+ }
212
+
213
+ function isDescendantOfMarkdownElement(
214
+ node: HTMLElement,
215
+ predicate: (type: string | null) => boolean,
216
+ ): boolean {
217
+ let currentNode = node.parentNode
218
+ while (currentNode && (currentNode as HTMLElement)?.contentEditable !== "true") {
219
+ const elementType = (currentNode as HTMLElement).getAttribute?.("data-type")
220
+ if (predicate(elementType)) {
221
+ return true
222
+ }
223
+ currentNode = currentNode.parentNode
224
+ }
225
+ return false
226
+ }
227
+
228
+ function isChildOfMarkdownElement(node: HTMLElement, elementType: NodeType): boolean {
229
+ return isDescendantOfMarkdownElement(node, (type) => type === elementType)
230
+ }
231
+ function isChildOfMultilineMarkdownElement(node: HTMLElement): boolean {
232
+ return isDescendantOfMarkdownElement(node, (type) =>
233
+ MULTILINE_MARKDOWN_TYPES.includes(type as NodeType),
234
+ )
235
+ }
236
+
237
+ export {
238
+ addStyleToBlock,
239
+ addLineLayout,
240
+ addBlockPrefixGap,
241
+ isMultilineMarkdownType,
242
+ isChildOfMarkdownElement,
243
+ isChildOfMultilineMarkdownElement,
244
+ MULTILINE_MARKDOWN_TYPES,
245
+ }
@@ -0,0 +1,41 @@
1
+ // This module is imported during server-side rendering (e.g. Expo Router's static
2
+ // web output), where `window`/`navigator` do not exist. All checks are therefore
3
+ // evaluated lazily on first access instead of at module scope.
4
+ const canUseDOM = typeof window !== "undefined" && typeof navigator !== "undefined"
5
+
6
+ function memoize(compute: () => boolean): () => boolean {
7
+ let value: boolean | undefined
8
+ return () => {
9
+ if (value === undefined) {
10
+ value = canUseDOM && compute()
11
+ }
12
+ return value
13
+ }
14
+ }
15
+
16
+ const isFirefox = memoize(() => navigator.userAgent.toLowerCase().includes("firefox"))
17
+ const isChromium = memoize(() => "chrome" in window)
18
+
19
+ /**
20
+ * Whether the platform is a mobile browser.
21
+ * Copied from Expensify App https://github.com/Expensify/App/blob/90dee7accae79c49debf30354c160cab6c52c423/src/libs/Browser/index.website.ts#L41
22
+ */
23
+ const isMobile = memoize(() =>
24
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Silk|Opera Mini/i.test(
25
+ navigator.userAgent,
26
+ ),
27
+ )
28
+
29
+ const BrowserUtils = {
30
+ get isFirefox() {
31
+ return isFirefox()
32
+ },
33
+ get isChromium() {
34
+ return isChromium()
35
+ },
36
+ get isMobile() {
37
+ return isMobile()
38
+ },
39
+ }
40
+
41
+ export default BrowserUtils
@@ -0,0 +1,204 @@
1
+ import type { MarkdownTextInputElement } from "../../MarkdownTextInput.web"
2
+ import { isChildOfMultilineMarkdownElement } from "./blockUtils"
3
+ import { findHTMLElementInTree, getTreeNodeByIndex } from "./treeUtils"
4
+ import type { TreeNode } from "./treeUtils"
5
+
6
+ function getMaxRangeOffset(node: ChildNode): number {
7
+ return node.nodeType === Node.TEXT_NODE
8
+ ? (node.textContent ?? "").length
9
+ : node.childNodes.length
10
+ }
11
+
12
+ /**
13
+ * Range offsets are applied to the real DOM node, not the parsed markdown tree.
14
+ * When the tree is temporarily stale, clamp to the DOM node's bounds so cursor
15
+ * restoration lands at the nearest valid position instead of throwing.
16
+ */
17
+ function getClampedRangeOffset(node: ChildNode, offset: number): number {
18
+ return Math.max(0, Math.min(offset, getMaxRangeOffset(node)))
19
+ }
20
+
21
+ function setCursorPosition(
22
+ target: MarkdownTextInputElement,
23
+ startIndex: number,
24
+ endIndex: number | null = null,
25
+ shouldScrollIntoView = false,
26
+ ) {
27
+ // We don't want to move the cursor if the target is not focused
28
+ if (!target.tree || target !== document.activeElement) {
29
+ return
30
+ }
31
+
32
+ const start = Math.max(0, Math.min(startIndex, target.tree.length))
33
+ const end = endIndex ? Math.max(0, Math.min(endIndex, target.tree.length)) : null
34
+ if (end && end < start) {
35
+ return
36
+ }
37
+
38
+ const range = document.createRange()
39
+ range.selectNodeContents(target)
40
+
41
+ const startTreeNode = getTreeNodeByIndex(target.tree, start)
42
+ const endTreeNode =
43
+ end &&
44
+ startTreeNode &&
45
+ (end < startTreeNode.start || end >= startTreeNode.start + startTreeNode.length)
46
+ ? getTreeNodeByIndex(target.tree, end)
47
+ : startTreeNode
48
+ if (!startTreeNode || !endTreeNode) {
49
+ console.error("Invalid start or end tree node")
50
+ return
51
+ }
52
+
53
+ if (startTreeNode.type === "br") {
54
+ range.setStartBefore(startTreeNode.element)
55
+ } else {
56
+ const startElement = startTreeNode.element
57
+ const startNode = (startElement.childNodes[0] || startElement) as ChildNode
58
+ range.setStart(startNode, getClampedRangeOffset(startNode, start - startTreeNode.start))
59
+ }
60
+
61
+ if (endTreeNode.type === "br") {
62
+ range.setEndBefore(endTreeNode.element)
63
+ } else {
64
+ const endElement = endTreeNode.element
65
+ const endNode = (endElement.childNodes[0] || endElement) as ChildNode
66
+ range.setEnd(endNode, getClampedRangeOffset(endNode, (end || start) - endTreeNode.start))
67
+ }
68
+
69
+ if (!end) {
70
+ range.collapse(true)
71
+ }
72
+
73
+ const selection = window.getSelection()
74
+ if (selection) {
75
+ selection.setBaseAndExtent(
76
+ range.startContainer,
77
+ range.startOffset,
78
+ range.endContainer,
79
+ range.endOffset,
80
+ )
81
+ }
82
+
83
+ if (shouldScrollIntoView) {
84
+ scrollIntoView(target, endTreeNode)
85
+ }
86
+ }
87
+
88
+ function scrollIntoView(target: MarkdownTextInputElement, node: TreeNode) {
89
+ let scrollTargetElement = node.element
90
+ const targetElement = target
91
+
92
+ if (!isChildOfMultilineMarkdownElement(node.element)) {
93
+ const orderIndex = Number(node.orderIndex.split(",")[0])
94
+ const currentLine = target.tree.childNodes[orderIndex]?.element
95
+ if (currentLine) {
96
+ scrollTargetElement = currentLine
97
+ }
98
+ } else if (node.element.nodeName === "BR") {
99
+ // Force scrolling BR into view
100
+ const selection = window.getSelection()
101
+ if (selection) {
102
+ const range = document.createRange()
103
+
104
+ range.setStartBefore(node.element)
105
+ range.collapse(true)
106
+
107
+ selection.addRange(range)
108
+
109
+ // Scroll to caret
110
+ const span = document.createElement("span")
111
+ span.textContent = "\u200B" // zero-width space
112
+ range.insertNode(span)
113
+ span.scrollIntoView({ block: "center" })
114
+ span.remove() // cleanup
115
+ return
116
+ }
117
+ }
118
+
119
+ const caretRect = scrollTargetElement.getBoundingClientRect()
120
+ const targetRect = target.getBoundingClientRect()
121
+ // In case the caret is below the visible input area, scroll to the end of the node
122
+ if (caretRect.top + caretRect.height > targetRect.top + targetRect.height) {
123
+ targetElement.scrollTop =
124
+ caretRect.top -
125
+ targetRect.top +
126
+ target.scrollTop -
127
+ targetRect.height +
128
+ caretRect.height +
129
+ 4
130
+ return
131
+ }
132
+
133
+ scrollTargetElement.scrollIntoView({
134
+ block: "nearest",
135
+ })
136
+ }
137
+
138
+ function moveCursorToEnd(target: HTMLElement) {
139
+ const range = document.createRange()
140
+ const selection = window.getSelection()
141
+ if (selection) {
142
+ range.setStart(target, target.childNodes.length)
143
+ range.collapse(true)
144
+ selection.setBaseAndExtent(
145
+ range.startContainer,
146
+ range.startOffset,
147
+ range.endContainer,
148
+ range.endOffset,
149
+ )
150
+ }
151
+ }
152
+
153
+ function getCurrentCursorPosition(target: MarkdownTextInputElement) {
154
+ function getHTMLElement(node: Node) {
155
+ let element = node as HTMLElement | Text
156
+ if (element instanceof Text) {
157
+ element = node.parentElement as HTMLElement
158
+ }
159
+ return element
160
+ }
161
+
162
+ const selection = window.getSelection()
163
+ if (!selection || (selection && selection.rangeCount === 0)) {
164
+ return null
165
+ }
166
+ const range = selection.getRangeAt(0)
167
+ const startElement = getHTMLElement(range.startContainer)
168
+ const endElement =
169
+ range.startContainer === range.endContainer
170
+ ? startElement
171
+ : getHTMLElement(range.endContainer)
172
+
173
+ const startTreeNode = findHTMLElementInTree(target.tree, startElement)
174
+ const endTreeNode = findHTMLElementInTree(target.tree, endElement)
175
+
176
+ let start = -1
177
+ let end = -1
178
+ if (startTreeNode && endTreeNode) {
179
+ start = startTreeNode.start + range.startOffset
180
+
181
+ // If the end node is a root node, we need to set the end to the end of the text (FireFox fix)
182
+ if (endTreeNode?.parentNode === null) {
183
+ end = target.value.length
184
+ } else {
185
+ end = endTreeNode.start + range.endOffset
186
+ }
187
+ }
188
+ return { start, end }
189
+ }
190
+
191
+ function removeSelection() {
192
+ const selection = window.getSelection()
193
+ if (selection) {
194
+ selection.removeAllRanges()
195
+ }
196
+ }
197
+
198
+ export {
199
+ getCurrentCursorPosition,
200
+ moveCursorToEnd,
201
+ setCursorPosition,
202
+ removeSelection,
203
+ scrollIntoView,
204
+ }
@@ -0,0 +1,206 @@
1
+ import type { CSSProperties } from "react"
2
+ import type { MarkdownNativeEvent, MarkdownTextInputElement } from "../../MarkdownTextInput.web"
3
+ import { isChildOfMarkdownElement, isChildOfMultilineMarkdownElement } from "./blockUtils"
4
+ import BrowserUtils from "./browserUtils"
5
+
6
+ const ZERO_WIDTH_SPACE = "\u200B"
7
+
8
+ // If an Input Method Editor is processing key input, the 'keyCode' is 229.
9
+ // https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
10
+ function isEventComposing(nativeEvent: globalThis.KeyboardEvent | MarkdownNativeEvent) {
11
+ return nativeEvent.isComposing || nativeEvent.keyCode === 229
12
+ }
13
+
14
+ function getPlaceholderValue(placeholder: string | undefined) {
15
+ if (!placeholder) {
16
+ return ZERO_WIDTH_SPACE
17
+ }
18
+ return placeholder.length ? placeholder : ZERO_WIDTH_SPACE
19
+ }
20
+
21
+ function getElementHeight(
22
+ node: HTMLDivElement,
23
+ styles: CSSProperties,
24
+ numberOfLines: number | undefined,
25
+ ) {
26
+ if (numberOfLines) {
27
+ const tempElement = document.createElement("div")
28
+ tempElement.setAttribute("contenteditable", "true")
29
+ Object.assign(tempElement.style, styles)
30
+ tempElement.textContent = Array(numberOfLines).fill("A").join("\n")
31
+ if (node.parentElement) {
32
+ node.parentElement.appendChild(tempElement)
33
+ const height = tempElement.clientHeight
34
+ node.parentElement.removeChild(tempElement)
35
+ return height ? `${height}px` : "auto"
36
+ }
37
+ }
38
+ return styles.height ? `${styles.height}px` : "auto"
39
+ }
40
+
41
+ function normalizeValue(value: string) {
42
+ return value.replaceAll("\r\n", "\n")
43
+ }
44
+
45
+ /**
46
+ * Returns the parent of a given node that is higher in the hierarchy and is of a different type than 'text', 'br' or 'line'
47
+ */
48
+ function getTopParentNode(node: ChildNode) {
49
+ let currentParentNode = node.parentNode
50
+ while (
51
+ currentParentNode &&
52
+ ["text", "br", "line"].includes(
53
+ currentParentNode.parentElement?.getAttribute("data-type") || "",
54
+ )
55
+ ) {
56
+ currentParentNode = currentParentNode?.parentNode || null
57
+ }
58
+ return currentParentNode
59
+ }
60
+
61
+ /**
62
+ * On Firefox, when breaking one codeblock, its syntax and the <br> after it can be merged into the closing syntax of the previous codeblock.
63
+ */
64
+ function didTwoCodeblocksMerge(node: ChildNode | null) {
65
+ if (!node || !BrowserUtils.isFirefox) {
66
+ return
67
+ }
68
+ // To identify that two codeblock has merged, we check if current line ends with <br> tag, that previously was second codeblock's opening syntax line break
69
+ const hasPartOfBrokenCodeblock =
70
+ node.lastChild?.lastChild?.lastChild?.lastChild?.nodeName === "BR"
71
+ return (
72
+ BrowserUtils.isFirefox &&
73
+ (node.lastChild as HTMLElement)?.getAttribute("data-type") === "codeblock" &&
74
+ hasPartOfBrokenCodeblock
75
+ )
76
+ }
77
+
78
+ /**
79
+ * Parses the HTML structure of a MarkdownTextInputElement to a plain text string. Used for getting the correct value of the input element.
80
+ */
81
+ function parseInnerHTMLToText(
82
+ target: MarkdownTextInputElement,
83
+ cursorPosition: number,
84
+ inputType?: string,
85
+ isMultiline = true,
86
+ ): string {
87
+ const stack: ChildNode[] = [target]
88
+ let text = ""
89
+ let shouldAddNewline = false
90
+ const lastNode = target.childNodes[target.childNodes.length - 1]
91
+ // Remove the last <br> element if it's the last child of the target element. Fixes the issue with adding extra newline when pasting into the empty input.
92
+ if (lastNode?.nodeName === "DIV" && (lastNode as HTMLElement)?.innerHTML === "<br>") {
93
+ target.removeChild(lastNode)
94
+ }
95
+
96
+ while (stack.length > 0) {
97
+ const node = stack.pop() as HTMLElement
98
+ if (!node) {
99
+ break
100
+ }
101
+
102
+ // If we are operating on the nodes that are children of the MarkdownTextInputElement, we need to add a newline after each
103
+ const isTopComponent = node.parentElement?.contentEditable === "true"
104
+ if (isTopComponent) {
105
+ // When inputType is undefined, the first part of the replaced text is added as a text node.
106
+ // Because of it, we need to prevent adding new lines in this case
107
+ if (!isMultiline || (!inputType && node.nodeType === Node.TEXT_NODE)) {
108
+ shouldAddNewline = false
109
+ } else {
110
+ const firstChild = node.firstChild as HTMLElement
111
+ const containsEmptyBlockElement =
112
+ firstChild?.getAttribute?.("data-type") === "block" && firstChild.textContent === ""
113
+ if (
114
+ firstChild &&
115
+ shouldAddNewline &&
116
+ !containsEmptyBlockElement &&
117
+ !didTwoCodeblocksMerge(node.previousSibling)
118
+ ) {
119
+ text += "\n"
120
+ shouldAddNewline = false
121
+ }
122
+ shouldAddNewline = true
123
+ }
124
+ }
125
+
126
+ if (node.nodeType === Node.TEXT_NODE) {
127
+ let hasAddedNewline = false
128
+ // Fix for codeblocks: Removing last codeblock newline, moves codeblock syntax too far into the codeblock content
129
+ // skipping one <br> after the codeblock syntax. We need to force parsing it before the text node is added.
130
+ if (
131
+ node.parentElement &&
132
+ !node.parentElement.getAttribute?.("data-type") &&
133
+ isChildOfMarkdownElement(node, "pre")
134
+ ) {
135
+ text += "\n"
136
+ const nextBR =
137
+ node.parentElement?.nextElementSibling?.firstElementChild ??
138
+ node.parentElement?.nextElementSibling
139
+ if (nextBR && nextBR.tagName === "BR") {
140
+ nextBR.remove()
141
+ }
142
+ hasAddedNewline = true
143
+ }
144
+
145
+ // Parse text nodes into text
146
+ text += node.textContent
147
+
148
+ // Fix for codeblocks: If we are adding text at the end of a multiline markdown type element, we need to add a newline
149
+ // because the new text can replace the last <br> element and it will not be added to the text.
150
+ if (
151
+ node.parentElement &&
152
+ node.parentNode?.parentElement?.nextSibling &&
153
+ !node.parentNode?.nextSibling &&
154
+ isChildOfMultilineMarkdownElement(node) &&
155
+ ((!hasAddedNewline && isChildOfMarkdownElement(node, "br")) ||
156
+ (!node.parentElement.getAttribute?.("data-type") &&
157
+ isChildOfMarkdownElement(node, "syntax")))
158
+ ) {
159
+ text += "\n"
160
+ }
161
+ } else if (node.nodeName === "BR") {
162
+ const parentNode = getTopParentNode(node)
163
+
164
+ if (
165
+ (isMultiline &&
166
+ parentNode &&
167
+ parentNode.parentElement?.contentEditable !== "true" &&
168
+ !!(
169
+ (node as HTMLElement).getAttribute("data-id") ||
170
+ (node.parentElement as HTMLElement).getAttribute("data-type") === "br"
171
+ )) ||
172
+ (node.parentElement?.getAttribute("data-type") === "text" &&
173
+ isChildOfMultilineMarkdownElement(node))
174
+ ) {
175
+ // Parse br elements into newlines only if their parent is not a child of the MarkdownTextInputElement (a paragraph when writing or a div when pasting).
176
+ // It prevents adding extra newlines when entering text - and now only for multiline inputs
177
+ text += "\n"
178
+ }
179
+ } else {
180
+ let i = node.childNodes.length - 1
181
+ while (i > -1) {
182
+ const child = node.childNodes[i]
183
+ if (!child) {
184
+ break
185
+ }
186
+ stack.push(child)
187
+ i--
188
+ }
189
+ }
190
+ }
191
+ text = text.replaceAll("\r\n", "\n")
192
+
193
+ // Force letter removal if the input value haven't changed but input type is 'delete'
194
+ if (text === target.value && inputType?.includes("delete")) {
195
+ text = text.slice(0, cursorPosition - 1) + text.slice(cursorPosition)
196
+ }
197
+ return text
198
+ }
199
+
200
+ export {
201
+ isEventComposing,
202
+ getPlaceholderValue,
203
+ getElementHeight,
204
+ parseInnerHTMLToText,
205
+ normalizeValue,
206
+ }
Binary file