react-native-marcus 0.1.0 → 0.2.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 (294) hide show
  1. package/README.md +220 -63
  2. package/RNMarcus.podspec +6 -0
  3. package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +2 -1
  4. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorView.kt +168 -0
  5. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManager.kt +38 -0
  6. package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManagerSpec.kt +19 -0
  7. package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +23 -1
  8. package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPainter.kt +147 -0
  9. package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPass.kt +19 -0
  10. package/android/src/main/java/app/getbullet/marcus/MarkdownCopy.kt +87 -0
  11. package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +652 -113
  12. package/android/src/main/java/app/getbullet/marcus/MarkdownRanges.kt +47 -0
  13. package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +140 -61
  14. package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +37 -7
  15. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownAbsoluteLineHeightSpan.kt +48 -0
  16. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +102 -48
  17. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +22 -4
  18. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBulletSpan.kt +78 -0
  19. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpan.kt +177 -0
  20. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontStyleSpan.kt +32 -0
  21. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontWeightSpan.kt +34 -0
  22. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +24 -11
  23. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLetterSpacingSpan.kt +32 -0
  24. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownTextShadowSpan.kt +32 -0
  25. package/android/src/main/new_arch/CMakeLists.txt +17 -0
  26. package/android/src/main/new_arch/RNMarcusSpec.h +1 -0
  27. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.cpp +235 -0
  28. package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.h +62 -0
  29. package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
  30. package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
  31. package/android/src/test/java/app/getbullet/marcus/MarkdownCopyTest.kt +69 -0
  32. package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +170 -25
  33. package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +22 -5
  34. package/android/src/test/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpanTest.kt +91 -0
  35. package/android/src/test/resources/render-model.txt +221 -16
  36. package/apple/MarcusSwizzle.h +25 -0
  37. package/apple/MarcusTextDecoratorComponentView.h +22 -0
  38. package/apple/MarcusTextDecoratorComponentView.mm +72 -0
  39. package/apple/MarcusTextDecoratorShadowNode.h +62 -0
  40. package/apple/MarcusTextDecoratorShadowNode.mm +192 -0
  41. package/apple/MarcusTextDecoratorViewManager.h +4 -0
  42. package/apple/MarcusTextDecoratorViewManager.mm +11 -0
  43. package/apple/MarcusTextInputDecoratorComponentView.mm +1 -1
  44. package/apple/MarcusTextInputDecoratorShadowNode.mm +1 -1
  45. package/apple/MarcusTextLayoutManager.h +40 -0
  46. package/apple/MarcusTextLayoutManager.mm +173 -0
  47. package/apple/MarkdownAttributes.h +23 -2
  48. package/apple/MarkdownAttributes.m +6 -0
  49. package/apple/MarkdownCopy.swift +63 -0
  50. package/apple/MarkdownDecorator.swift +2 -2
  51. package/apple/MarkdownFormatter+React.swift +23 -0
  52. package/apple/MarkdownFormatter.swift +869 -148
  53. package/apple/MarkdownParagraphLayoutManager.swift +455 -0
  54. package/apple/MarkdownSwiftInterop.h +0 -1
  55. package/apple/MarkdownTextBackground.swift +59 -1
  56. package/apple/MarkdownTextFieldObserver.swift +2 -2
  57. package/apple/MarkdownTextLayoutFragment.swift +159 -33
  58. package/apple/MarkdownTextLayoutManagerDelegate.swift +67 -3
  59. package/apple/MarkdownTextStorageDelegate.swift +2 -2
  60. package/apple/MarkdownTextViewObserver.swift +2 -2
  61. package/apple/MarkdownUtils.swift +38 -0
  62. package/apple/MarkdownWritingDirection.swift +132 -0
  63. package/apple/RCTMarcusStyle+Codegen.h +115 -9
  64. package/apple/RCTMarcusStyle.h +128 -31
  65. package/apple/RCTMarcusStyle.m +48 -0
  66. package/apple/RCTParagraphComponentView+Marcus.h +17 -0
  67. package/apple/RCTParagraphComponentView+Marcus.mm +38 -0
  68. package/apple/RCTTextInputComponentView+Marcus.mm +1 -25
  69. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorState.h +37 -0
  70. package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorViewComponentDescriptor.h +17 -0
  71. package/lib/commonjs/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  72. package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  73. package/lib/commonjs/MarkdownText.js +330 -0
  74. package/lib/commonjs/MarkdownText.js.map +1 -0
  75. package/lib/commonjs/MarkdownText.web.js +529 -0
  76. package/lib/commonjs/MarkdownText.web.js.map +1 -0
  77. package/lib/commonjs/MarkdownTextInput.js +22 -27
  78. package/lib/commonjs/MarkdownTextInput.js.map +1 -1
  79. package/lib/commonjs/MarkdownTextInput.web.js +12 -7
  80. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
  81. package/lib/commonjs/atomUtils.js +112 -0
  82. package/lib/commonjs/atomUtils.js.map +1 -0
  83. package/lib/commonjs/childrenUtils.js +36 -0
  84. package/lib/commonjs/childrenUtils.js.map +1 -0
  85. package/lib/commonjs/index.js +8 -7
  86. package/lib/commonjs/index.js.map +1 -1
  87. package/lib/commonjs/math/environment.js +69 -0
  88. package/lib/commonjs/math/environment.js.map +1 -0
  89. package/lib/commonjs/math/index.js +61 -0
  90. package/lib/commonjs/math/index.js.map +1 -0
  91. package/lib/commonjs/math/mathjax.js +548 -0
  92. package/lib/commonjs/math/mathjax.js.map +1 -0
  93. package/lib/commonjs/metro.js +172 -0
  94. package/lib/commonjs/metro.js.map +1 -0
  95. package/lib/commonjs/parser/index.d.ts +3 -0
  96. package/lib/commonjs/parser/index.js +450 -42
  97. package/lib/commonjs/parser/index.js.map +4 -4
  98. package/lib/commonjs/stripUtils.js +743 -0
  99. package/lib/commonjs/stripUtils.js.map +1 -0
  100. package/lib/commonjs/styleUtils.js +194 -23
  101. package/lib/commonjs/styleUtils.js.map +1 -1
  102. package/lib/commonjs/web/MarkdownElements.css +67 -0
  103. package/lib/commonjs/web/MarkdownTextInput.css +9 -7
  104. package/lib/commonjs/web/utils/blockLayout.js +95 -7
  105. package/lib/commonjs/web/utils/blockLayout.js.map +1 -1
  106. package/lib/commonjs/web/utils/blockUtils.js +327 -54
  107. package/lib/commonjs/web/utils/blockUtils.js.map +1 -1
  108. package/lib/commonjs/web/utils/elementUtils.js +80 -0
  109. package/lib/commonjs/web/utils/elementUtils.js.map +1 -0
  110. package/lib/commonjs/web/utils/parserUtils.js +84 -28
  111. package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
  112. package/lib/commonjs/web/utils/treeUtils.js +6 -0
  113. package/lib/commonjs/web/utils/treeUtils.js.map +1 -1
  114. package/lib/commonjs/web/utils/webStyleUtils.js +25 -5
  115. package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -1
  116. package/lib/module/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  117. package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  118. package/lib/module/MarkdownText.js +325 -0
  119. package/lib/module/MarkdownText.js.map +1 -0
  120. package/lib/module/MarkdownText.web.js +524 -0
  121. package/lib/module/MarkdownText.web.js.map +1 -0
  122. package/lib/module/MarkdownTextInput.js +24 -29
  123. package/lib/module/MarkdownTextInput.js.map +1 -1
  124. package/lib/module/MarkdownTextInput.web.js +12 -7
  125. package/lib/module/MarkdownTextInput.web.js.map +1 -1
  126. package/lib/module/atomUtils.js +106 -0
  127. package/lib/module/atomUtils.js.map +1 -0
  128. package/lib/module/childrenUtils.js +33 -0
  129. package/lib/module/childrenUtils.js.map +1 -0
  130. package/lib/module/index.js +1 -1
  131. package/lib/module/index.js.map +1 -1
  132. package/lib/module/math/environment.js +67 -0
  133. package/lib/module/math/environment.js.map +1 -0
  134. package/lib/module/math/index.js +56 -0
  135. package/lib/module/math/index.js.map +1 -0
  136. package/lib/module/math/mathjax.js +541 -0
  137. package/lib/module/math/mathjax.js.map +1 -0
  138. package/lib/module/metro.js +169 -0
  139. package/lib/module/metro.js.map +1 -0
  140. package/lib/module/parser/index.d.ts +3 -0
  141. package/lib/module/parser/index.js +450 -42
  142. package/lib/module/parser/index.js.map +4 -4
  143. package/lib/module/stripUtils.js +739 -0
  144. package/lib/module/stripUtils.js.map +1 -0
  145. package/lib/module/styleUtils.js +196 -25
  146. package/lib/module/styleUtils.js.map +1 -1
  147. package/lib/module/web/MarkdownElements.css +67 -0
  148. package/lib/module/web/MarkdownTextInput.css +9 -7
  149. package/lib/module/web/utils/blockLayout.js +94 -8
  150. package/lib/module/web/utils/blockLayout.js.map +1 -1
  151. package/lib/module/web/utils/blockUtils.js +328 -56
  152. package/lib/module/web/utils/blockUtils.js.map +1 -1
  153. package/lib/module/web/utils/elementUtils.js +75 -0
  154. package/lib/module/web/utils/elementUtils.js.map +1 -0
  155. package/lib/module/web/utils/parserUtils.js +86 -30
  156. package/lib/module/web/utils/parserUtils.js.map +1 -1
  157. package/lib/module/web/utils/treeUtils.js +6 -0
  158. package/lib/module/web/utils/treeUtils.js.map +1 -1
  159. package/lib/module/web/utils/webStyleUtils.js +25 -5
  160. package/lib/module/web/utils/webStyleUtils.js.map +1 -1
  161. package/lib/typescript/commonjs/e2e/links.test.d.ts +2 -0
  162. package/lib/typescript/commonjs/e2e/links.test.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
  164. package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
  166. package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/src/MarkdownText.d.ts +127 -0
  168. package/lib/typescript/commonjs/src/MarkdownText.d.ts.map +1 -0
  169. package/lib/typescript/commonjs/src/MarkdownText.web.d.ts +126 -0
  170. package/lib/typescript/commonjs/src/MarkdownText.web.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +23 -1
  172. package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +26 -4
  174. package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/src/atomUtils.d.ts +58 -0
  176. package/lib/typescript/commonjs/src/atomUtils.d.ts.map +1 -0
  177. package/lib/typescript/commonjs/src/childrenUtils.d.ts +12 -0
  178. package/lib/typescript/commonjs/src/childrenUtils.d.ts.map +1 -0
  179. package/lib/typescript/commonjs/src/commonTypes.d.ts +39 -3
  180. package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/src/index.d.ts +2 -1
  182. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/src/math/environment.d.ts +7 -0
  184. package/lib/typescript/commonjs/src/math/environment.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/src/math/index.d.ts +64 -0
  186. package/lib/typescript/commonjs/src/math/index.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/src/math/mathjax.d.ts +63 -0
  188. package/lib/typescript/commonjs/src/math/mathjax.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/src/metro.d.ts +59 -0
  190. package/lib/typescript/commonjs/src/metro.d.ts.map +1 -0
  191. package/lib/typescript/commonjs/src/parser/index.d.ts +3 -3
  192. package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -1
  193. package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/src/rangeUtils.d.ts +1 -1
  195. package/lib/typescript/commonjs/src/stripUtils.d.ts +108 -0
  196. package/lib/typescript/commonjs/src/stripUtils.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/src/styleUtils.d.ts +62 -4
  198. package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -1
  199. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +32 -4
  200. package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +20 -8
  202. package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts +37 -0
  204. package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +5 -5
  206. package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +6 -1
  208. package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +2 -2
  210. package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -1
  211. package/lib/typescript/module/e2e/links.test.d.ts +2 -0
  212. package/lib/typescript/module/e2e/links.test.d.ts.map +1 -0
  213. package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
  214. package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
  215. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
  216. package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  217. package/lib/typescript/module/src/MarkdownText.d.ts +127 -0
  218. package/lib/typescript/module/src/MarkdownText.d.ts.map +1 -0
  219. package/lib/typescript/module/src/MarkdownText.web.d.ts +126 -0
  220. package/lib/typescript/module/src/MarkdownText.web.d.ts.map +1 -0
  221. package/lib/typescript/module/src/MarkdownTextInput.d.ts +23 -1
  222. package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -1
  223. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +26 -4
  224. package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -1
  225. package/lib/typescript/module/src/atomUtils.d.ts +58 -0
  226. package/lib/typescript/module/src/atomUtils.d.ts.map +1 -0
  227. package/lib/typescript/module/src/childrenUtils.d.ts +12 -0
  228. package/lib/typescript/module/src/childrenUtils.d.ts.map +1 -0
  229. package/lib/typescript/module/src/commonTypes.d.ts +39 -3
  230. package/lib/typescript/module/src/commonTypes.d.ts.map +1 -1
  231. package/lib/typescript/module/src/index.d.ts +2 -1
  232. package/lib/typescript/module/src/index.d.ts.map +1 -1
  233. package/lib/typescript/module/src/math/environment.d.ts +7 -0
  234. package/lib/typescript/module/src/math/environment.d.ts.map +1 -0
  235. package/lib/typescript/module/src/math/index.d.ts +64 -0
  236. package/lib/typescript/module/src/math/index.d.ts.map +1 -0
  237. package/lib/typescript/module/src/math/mathjax.d.ts +63 -0
  238. package/lib/typescript/module/src/math/mathjax.d.ts.map +1 -0
  239. package/lib/typescript/module/src/metro.d.ts +59 -0
  240. package/lib/typescript/module/src/metro.d.ts.map +1 -0
  241. package/lib/typescript/module/src/parser/index.d.ts +3 -3
  242. package/lib/typescript/module/src/parser/index.d.ts.map +1 -1
  243. package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -1
  244. package/lib/typescript/module/src/rangeUtils.d.ts +1 -1
  245. package/lib/typescript/module/src/stripUtils.d.ts +108 -0
  246. package/lib/typescript/module/src/stripUtils.d.ts.map +1 -0
  247. package/lib/typescript/module/src/styleUtils.d.ts +62 -4
  248. package/lib/typescript/module/src/styleUtils.d.ts.map +1 -1
  249. package/lib/typescript/module/src/web/utils/blockLayout.d.ts +32 -4
  250. package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -1
  251. package/lib/typescript/module/src/web/utils/blockUtils.d.ts +20 -8
  252. package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -1
  253. package/lib/typescript/module/src/web/utils/elementUtils.d.ts +37 -0
  254. package/lib/typescript/module/src/web/utils/elementUtils.d.ts.map +1 -0
  255. package/lib/typescript/module/src/web/utils/parserUtils.d.ts +5 -5
  256. package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -1
  257. package/lib/typescript/module/src/web/utils/treeUtils.d.ts +6 -1
  258. package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -1
  259. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +2 -2
  260. package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -1
  261. package/package.json +29 -2
  262. package/react-native.config.js +8 -1
  263. package/src/MarcusTextDecoratorViewNativeComponent.ts +199 -0
  264. package/src/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
  265. package/src/MarkdownText.tsx +472 -0
  266. package/src/MarkdownText.web.tsx +668 -0
  267. package/src/MarkdownTextInput.tsx +42 -38
  268. package/src/MarkdownTextInput.web.tsx +36 -13
  269. package/src/atomUtils.ts +112 -0
  270. package/src/childrenUtils.ts +39 -0
  271. package/src/commonTypes.ts +69 -9
  272. package/src/index.tsx +2 -1
  273. package/src/math/environment.ts +68 -0
  274. package/src/math/index.ts +78 -0
  275. package/src/math/mathjax.ts +604 -0
  276. package/src/metro.ts +238 -0
  277. package/src/parser/index.ts +175 -10
  278. package/src/parser/micromark-extension-mentions.ts +104 -11
  279. package/src/stripUtils.ts +919 -0
  280. package/src/styleUtils.ts +290 -26
  281. package/src/web/MarkdownElements.css +67 -0
  282. package/src/web/MarkdownTextInput.css +9 -7
  283. package/src/web/utils/blockLayout.ts +124 -14
  284. package/src/web/utils/blockUtils.ts +365 -59
  285. package/src/web/utils/elementUtils.ts +87 -0
  286. package/src/web/utils/parserUtils.ts +119 -31
  287. package/src/web/utils/treeUtils.ts +6 -1
  288. package/src/web/utils/webStyleUtils.ts +32 -8
  289. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +0 -6
  290. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +0 -6
  291. package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +0 -6
  292. package/apple/RCTMarcusStyle+Codegen.mm +0 -64
  293. package/apple/RCTMarcusUtils.h +0 -26
  294. package/apple/RCTMarcusUtils.mm +0 -26
@@ -1,6 +1,10 @@
1
- import type { PartialMarkdownStyle } from "../../styleUtils"
2
- import { toNumber } from "./blockLayout"
3
- import type { LineLayout } from "./blockLayout"
1
+ import type {
2
+ MarkdownBoxStyle,
3
+ MarkdownTextStyle,
4
+ } from "../../MarcusTextInputDecoratorViewNativeComponent"
5
+ import type { ResolvedMarkdownStyle } from "../../styleUtils"
6
+ import { SHOWN_MARKERS, toNumber } from "./blockLayout"
7
+ import type { LineLayout, MarkerRendering } from "./blockLayout"
4
8
  import type { NodeType } from "./treeUtils"
5
9
 
6
10
  /**
@@ -22,17 +26,109 @@ function applyStyle(node: HTMLElement, styles: Record<string, string | number |
22
26
  })
23
27
  }
24
28
 
29
+ /**
30
+ * Everything a `MarkdownTextStyle` says, in CSS.
31
+ *
32
+ * One place rather than a line per entry in the switch below, because every styled type now carries
33
+ * the same set of text attributes: the difference between a mention and a heading is the box around
34
+ * it and the size it is given, not which properties it understands. `MarkdownFormatter` reads the
35
+ * same struct into attributed-string attributes on iOS and into spans on Android.
36
+ *
37
+ * Undefined properties are dropped by `applyStyle`, so a run inherits whatever the prose around it
38
+ * is drawn in -- which is what an unset text attribute means on all three platforms.
39
+ */
40
+ function textStyle(style: Partial<MarkdownTextStyle> | undefined) {
41
+ if (!style) {
42
+ return {}
43
+ }
44
+
45
+ return {
46
+ color: style.color as string | undefined,
47
+ fontFamily: style.fontFamily,
48
+ fontSize: style.fontSize,
49
+ fontStyle: style.fontStyle,
50
+ fontWeight: style.fontWeight,
51
+ letterSpacing: style.letterSpacing,
52
+ lineHeight: style.lineHeight,
53
+ textDecorationLine: style.textDecorationLine,
54
+ textDecorationColor: style.textDecorationColor as string | undefined,
55
+ textDecorationStyle: style.textDecorationStyle,
56
+ textShadow: textShadow(style),
57
+ }
58
+ }
59
+
60
+ /**
61
+ * React Native's three shadow properties as the one CSS declaration they add up to.
62
+ *
63
+ * The offset and the radius stay raw numbers through `processMarkdownStyle` -- an offset is an
64
+ * object rather than a length, and half a shadow carrying units would not compose -- so the units
65
+ * are put on here.
66
+ */
67
+ function textShadow(style: Partial<MarkdownTextStyle>): string | undefined {
68
+ const color = style.textShadowColor as string | undefined
69
+ const radius = toNumber(style.textShadowRadius)
70
+ const x = toNumber(style.textShadowOffset?.width)
71
+ const y = toNumber(style.textShadowOffset?.height)
72
+
73
+ if (!color && radius <= 0 && x === 0 && y === 0) {
74
+ return undefined
75
+ }
76
+
77
+ return `${x}px ${y}px ${radius}px ${color || "currentColor"}`
78
+ }
79
+
80
+ /**
81
+ * The room a boxed run holds open on one axis: the axis value where it is set, and the value for
82
+ * both axes where it is not.
83
+ *
84
+ * A fenced block usually wants more above and below than beside it, and the single `padding` a
85
+ * style used to carry could not say that. The native formatters resolve the pair the same way.
86
+ */
87
+ function axis(both: unknown, axisValue: unknown): number {
88
+ return axisValue === undefined || axisValue === null || axisValue === ""
89
+ ? toNumber(both as string | number | undefined)
90
+ : toNumber(axisValue as string | number | undefined)
91
+ }
92
+
93
+ /**
94
+ * The box a run of code or a mention sits in: its fill, its border, and the room inside and around
95
+ * it, written as the two-value shorthands so the horizontal halves stay direction-agnostic.
96
+ */
97
+ function boxStyle(style: Partial<MarkdownBoxStyle> | undefined) {
98
+ if (!style) {
99
+ return {}
100
+ }
101
+
102
+ const borderWidth = toNumber(style.borderWidth)
103
+
104
+ return {
105
+ backgroundColor: style.backgroundColor as string | undefined,
106
+ borderRadius: style.borderRadius,
107
+ borderWidth: borderWidth > 0 ? `${borderWidth}px` : undefined,
108
+ borderStyle: borderWidth > 0 ? "solid" : undefined,
109
+ borderColor: borderWidth > 0 ? (style.borderColor as string) || "currentColor" : undefined,
110
+ padding: `${axis(style.padding, style.paddingVertical)}px ${axis(
111
+ style.padding,
112
+ style.paddingHorizontal,
113
+ )}px`,
114
+ margin: `${axis(style.margin, style.marginVertical)}px ${axis(
115
+ style.margin,
116
+ style.marginHorizontal,
117
+ )}px`,
118
+ }
119
+ }
120
+
25
121
  /**
26
122
  * Applies the layout the block walk computed for one line.
27
123
  *
28
- * Everything a container contributes lands here rather than on the container's own span: its gutter
124
+ * Everything a container contributes lands here rather than on the container's own element: its gutter
29
125
  * is part of the paragraph's indent, and its ribbon is painted behind the whole paragraph. That is
30
126
  * 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
127
+ * container's element begins before the markers of the containers outside it, so a border drawn on it
32
128
  * would sit in front of a bullet that is meant to precede it, and a gutter set on it would open
33
129
  * before that bullet rather than after.
34
130
  *
35
- * `padding-left` holds every line of the paragraph at the indent, and the negative `text-indent`
131
+ * `padding-inline-start` holds every line of the paragraph at the indent, and the negative `text-indent`
36
132
  * pulls the first one back to where its own marker starts -- `firstLineHeadIndent` and `headIndent`,
37
133
  * spelled in CSS. A line that wraps, or a line continuing the block with no marker of its own, then
38
134
  * lines up with the text above it instead of with the marker.
@@ -40,7 +136,7 @@ function applyStyle(node: HTMLElement, styles: Record<string, string | number |
40
136
  function addLineLayout(
41
137
  node: HTMLElement,
42
138
  layout: LineLayout | undefined,
43
- markdownStyle: PartialMarkdownStyle,
139
+ markdownStyle: ResolvedMarkdownStyle,
44
140
  ) {
45
141
  applyStyle(node, {
46
142
  margin: "0",
@@ -53,7 +149,10 @@ function addLineLayout(
53
149
 
54
150
  if (layout.indent > 0) {
55
151
  applyStyle(node, {
56
- paddingLeft: `${layout.indent}px`,
152
+ // Logical rather than `padding-left`, because a container's gutter opens on the line's
153
+ // leading edge -- which is what `marginStart` and `paddingStart` name, and what both native
154
+ // platforms already indent from.
155
+ paddingInlineStart: `${layout.indent}px`,
57
156
  textIndent: `${layout.firstLineIndent - layout.indent}px`,
58
157
  })
59
158
  }
@@ -64,17 +163,87 @@ function addLineLayout(
64
163
  }
65
164
 
66
165
  // 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.
166
+ // rather than on anything inside it so that they run its full height -- a quote that wraps gets
167
+ // one unbroken bar, exactly as `MarkdownTextLayoutFragment` draws it over the whole fragment.
168
+ //
169
+ // The offsets are distances from the line's leading edge -- the same numbers `getLeadingMargin`
170
+ // is handed on Android -- so the edge they are measured from has to move with the line's own
171
+ // direction. `RIBBON_EDGE` leaves that edge to be named later, by `setRibbonEdge`.
69
172
  const color = (markdownStyle.blockquote?.borderColor as string) || "transparent"
173
+ const radius = toNumber(markdownStyle.blockquote?.borderRadius)
174
+
70
175
  applyStyle(node, {
71
- backgroundImage: layout.ribbons.map(() => `linear-gradient(${color}, ${color})`).join(", "),
72
- backgroundPosition: layout.ribbons.map((x) => `${x}px 0`).join(", "),
176
+ backgroundImage: layout.ribbons.map(() => bar(color, radius)).join(", "),
177
+ backgroundPositionX: layout.ribbons.map((x) => `${RIBBON_EDGE} ${x}px`).join(", "),
178
+ // One value for every layer: a shorter list is repeated to cover them all.
179
+ backgroundPositionY: "0",
73
180
  backgroundSize: layout.ribbons.map(() => `${width}px 100%`).join(", "),
74
181
  backgroundRepeat: "no-repeat",
75
182
  })
76
183
  }
77
184
 
185
+ /**
186
+ * The custom property naming the edge a ribbon's offset is measured from, and the reference to it
187
+ * the offsets are written against.
188
+ *
189
+ * `background-position` is the one part of a line still spelled physically: it has no logical form.
190
+ * So the edge is named indirectly and `setRibbonEdge` fills it in once the line has its text --
191
+ * which is also why it is a custom property rather than a value written straight into the offsets:
192
+ * the offsets are known while building the line, the direction only after.
193
+ *
194
+ * The fallback in the reference is what a line gets before then, and what one gets in a browser
195
+ * that cannot answer the question at all.
196
+ */
197
+ const RIBBON_EDGE_PROPERTY = "--marcus-ribbon-edge"
198
+ const RIBBON_EDGE = `var(${RIBBON_EDGE_PROPERTY}, left)`
199
+
200
+ /**
201
+ * Turns a line's ribbons round when the line reads right to left.
202
+ *
203
+ * Asked of the browser rather than worked out from the text, because the browser is what lays the
204
+ * line out: whatever it resolved `dir="auto"` to is where it put the gutter, and the bars belong on
205
+ * that side whether or not a bidi rule written here would have agreed. `:dir()` is unsupported in
206
+ * browsers older than Safari 16.4, where `matches` throws and the fallback edge stands.
207
+ *
208
+ * Called once the line holds its text, since that is what a line's own direction is read from, and
209
+ * only for a line that has ribbons: nothing else on a line is measured from an edge.
210
+ */
211
+ function setRibbonEdge(node: HTMLElement, layout: LineLayout | undefined) {
212
+ if (!layout || layout.ribbons.length === 0) {
213
+ return
214
+ }
215
+
216
+ try {
217
+ if (node.matches(":dir(rtl)")) {
218
+ node.style.setProperty(RIBBON_EDGE_PROPERTY, "right")
219
+ }
220
+ } catch {
221
+ // A browser without `:dir()`. Left is the fallback the offsets already carry.
222
+ }
223
+ }
224
+
225
+ /**
226
+ * One quote bar, as a background layer.
227
+ *
228
+ * A gradient has no corners, so a bar that is asked to round its ends is drawn as an SVG rectangle
229
+ * instead. The SVG carries no `viewBox`, which means its user units are the pixels the layer is
230
+ * sized to and `rx` is the radius that was asked for rather than a radius stretched by the height
231
+ * of the line. `MarkdownTextLayoutFragment` and `MarkdownBlockIndentSpan` round the same bar with a
232
+ * rounded-rect path.
233
+ */
234
+ function bar(color: string, radius: number): string {
235
+ if (radius <= 0) {
236
+ return `linear-gradient(${color}, ${color})`
237
+ }
238
+
239
+ const svg =
240
+ `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">` +
241
+ `<rect width="100%" height="100%" rx="${radius}" ry="${radius}" fill="${color}"/>` +
242
+ `</svg>`
243
+
244
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`
245
+ }
246
+
78
247
  /**
79
248
  * Opens the space a marker holds for the container that follows it.
80
249
  *
@@ -83,30 +252,49 @@ function addLineLayout(
83
252
  */
84
253
  function addBlockPrefixGap(node: HTMLElement, gap: number | undefined) {
85
254
  if (gap) {
86
- node.style.marginRight = `${gap}px`
255
+ // Logical: the room belongs after the marker in reading order, which is its left in a
256
+ // right-to-left line. `MarkdownFormatter` kerns the marker's last character, and kerning
257
+ // follows the run's own direction for the same reason.
258
+ node.style.marginInlineEnd = `${gap}px`
87
259
  }
88
260
  }
89
261
 
90
262
  function addStyleToBlock(
91
263
  targetElement: HTMLElement,
92
264
  type: NodeType,
93
- markdownStyle: PartialMarkdownStyle,
265
+ markdownStyle: ResolvedMarkdownStyle,
94
266
  isMultiline = true,
267
+ rendering: MarkerRendering = SHOWN_MARKERS,
95
268
  ) {
96
269
  const node = targetElement
97
270
 
98
271
  switch (type) {
272
+ // A fenced block's language is its own type so that something can read it,
273
+ // but in an input it is part of the fence you are typing and reads as
274
+ // markup, so it is coloured like the rest of it.
99
275
  case "syntax":
100
- applyStyle(node, { color: markdownStyle.syntax?.color as string })
276
+ case "language": {
277
+ // A list's marker is the one syntax a display keeps, and it is drawn there
278
+ // rather than shown -- everything else the colour is for has been stripped
279
+ // out by the time a display is built, so this is the only case where the
280
+ // two components part company.
281
+ const list = rendering.display && type === "syntax" ? markerList(node) : null
282
+
283
+ if (list) {
284
+ addMarkerStyle(node, list, markdownStyle, rendering)
285
+ } else {
286
+ applyStyle(node, textStyle(markdownStyle.syntax))
287
+ }
101
288
  break
102
- case "bold":
103
- node.style.fontWeight = "bold"
289
+ }
290
+ case "strong":
291
+ applyStyle(node, textStyle(markdownStyle.strong))
104
292
  break
105
- case "italic":
106
- node.style.fontStyle = "italic"
293
+ case "emphasis":
294
+ applyStyle(node, textStyle(markdownStyle.emphasis))
107
295
  break
108
296
  case "strikethrough":
109
- node.style.textDecoration = "line-through"
297
+ applyStyle(node, textStyle(markdownStyle.strikethrough))
110
298
  break
111
299
  case "emoji":
112
300
  applyStyle(node, {
@@ -117,43 +305,42 @@ function addStyleToBlock(
117
305
  verticalAlign: "middle",
118
306
  })
119
307
  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)
308
+ // A pill: the same inline box an inline run of code sits in, and drawn the same way. The
309
+ // padding and margin push the words either side of it apart, the padding grows into the
310
+ // line's own spacing above and below rather than pushing the lines apart, and a mention that
311
+ // wraps is rounded only where it begins and ends.
312
+ case "mention":
313
+ applyStyle(node, textStyle(markdownStyle.mention))
314
+ applyStyle(node, boxStyle(markdownStyle.mention))
128
315
  break
129
316
  case "link":
130
- applyStyle(node, {
131
- color: markdownStyle.link?.color as string,
132
- textDecoration: "underline",
133
- })
317
+ applyStyle(node, textStyle(markdownStyle.link))
134
318
  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.
319
+ // An inline box, which is what the platforms draw by hand: the padding and margin push the
320
+ // words either side of it apart, the padding grows into the line's own spacing above and
321
+ // below rather than pushing the lines apart, and a run that wraps is rounded only where it
322
+ // begins and ends. All three are what CSS does with an inline element by default.
137
323
  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
- })
324
+ applyStyle(node, textStyle(markdownStyle.code))
325
+ applyStyle(node, boxStyle(markdownStyle.code))
144
326
  break
327
+ // Only the text attributes of a block; the box it sits in is `codeblock`, which is the range
328
+ // that covers the whole thing rather than only the code inside it.
145
329
  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
- })
330
+ applyStyle(node, textStyle(markdownStyle.pre))
152
331
  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
332
+ case "codeblock":
333
+ addCodeBlockStyle(node, markdownStyle, isMultiline)
334
+ break
335
+ // A quoted block holds no box of its own: its gutter and its ribbons belong to the paragraph,
336
+ // in `addLineLayout`. What is left is the text inside it, and letting a long unbroken word
155
337
  // break rather than push the line past the input's edge.
156
338
  case "blockquote":
339
+ applyStyle(node, textStyle(markdownStyle.blockquote))
340
+ applyStyle(node, { overflowWrap: "anywhere" })
341
+ break
342
+ // Neither do the list containers, and their markers are styled from the `syntax` range inside
343
+ // their prefix rather than from here.
157
344
  case "list-ordered":
158
345
  case "list-unordered":
159
346
  applyStyle(node, { overflowWrap: "anywhere" })
@@ -174,32 +361,150 @@ function addStyleToBlock(
174
361
  }
175
362
  }
176
363
 
177
- function addMentionStyle(
364
+ /**
365
+ * The box behind a fenced or indented block: one fill for the whole thing, from the first line to
366
+ * the last, which is what the native formatters draw.
367
+ *
368
+ * `codeblock` covers the block including its fences, and the builder merges all of its lines into
369
+ * one element, so here the box is a real box rather than the several line rects each platform has
370
+ * to fill by hand. What that costs is the one thing an element cannot do: a block opened after a
371
+ * bullet or a quote's `>` shares that line in the buffer but not on screen, because a block-level
372
+ * box drops below the marker in front of it.
373
+ *
374
+ * A singleline input has no room for a block at all -- every line of it is drawn on one -- so there
375
+ * the background stays an inline one, painted behind the text.
376
+ */
377
+ function addCodeBlockStyle(
178
378
  node: HTMLElement,
179
- style:
180
- | { color?: unknown; backgroundColor?: unknown; borderRadius?: string | number }
181
- | undefined,
379
+ markdownStyle: ResolvedMarkdownStyle,
380
+ isMultiline: boolean,
182
381
  ) {
382
+ const style = markdownStyle.pre
383
+ const box = boxStyle(style)
384
+
385
+ applyStyle(node, {
386
+ backgroundColor: box.backgroundColor,
387
+ borderRadius: box.borderRadius,
388
+ borderWidth: box.borderWidth,
389
+ borderStyle: box.borderStyle,
390
+ borderColor: box.borderColor,
391
+ })
392
+
393
+ if (!isMultiline) {
394
+ return
395
+ }
396
+
183
397
  applyStyle(node, {
184
- color: style?.color as string,
185
- backgroundColor: style?.backgroundColor as string,
186
- borderRadius: style?.borderRadius,
398
+ display: "block",
399
+ // The padding is inside the box, so the box still spans the line rather than overflowing it.
400
+ boxSizing: "border-box",
401
+ padding: box.padding,
402
+ margin: box.margin,
403
+ })
404
+ }
405
+
406
+ /**
407
+ * The list whose marker `node` is, or null if this syntax is something else.
408
+ *
409
+ * A marker is a `syntax` inside the `block-prefix` of a list container, which is a shape only a
410
+ * list has: a quote's `>` is removed with the rest of the markup before a display is built.
411
+ */
412
+ function markerList(node: HTMLElement): "list-ordered" | "list-unordered" | null {
413
+ let current = node.parentNode as HTMLElement | null
414
+ let inPrefix = false
415
+
416
+ while (current && current.contentEditable !== "true") {
417
+ const type = current.getAttribute?.("data-type")
418
+
419
+ if (type === "block-prefix") {
420
+ inPrefix = true
421
+ } else if (type === "list-ordered" || type === "list-unordered") {
422
+ return inPrefix ? type : null
423
+ }
424
+
425
+ current = current.parentNode as HTMLElement | null
426
+ }
427
+
428
+ return null
429
+ }
430
+
431
+ /**
432
+ * Draws a list's marker: a bullet for an unordered item, the number at its own scale for an
433
+ * ordered one.
434
+ *
435
+ * Both are sized from the base font rather than from a length of their own, so a list marks itself
436
+ * in proportion to the prose it marks, and both are drawn from the list's own `marker` style over
437
+ * `syntax` -- which, in a display where every other piece of syntax has been removed, is where a
438
+ * marker's colour came from before lists could name one. `MarkdownFormatter` draws the same two
439
+ * shapes from the same numbers on iOS and Android.
440
+ *
441
+ * The bullet keeps the marker character inside it, boxed and invisible: it is what a reader copies
442
+ * and what a screen reader announces, and the box is exactly the width `layoutBlocks` indented the
443
+ * text past.
444
+ */
445
+ function addMarkerStyle(
446
+ node: HTMLElement,
447
+ type: "list-ordered" | "list-unordered",
448
+ markdownStyle: ResolvedMarkdownStyle,
449
+ rendering: MarkerRendering,
450
+ ) {
451
+ const list = type === "list-ordered" ? markdownStyle.orderedList : markdownStyle.unorderedList
452
+ // What the list says about its marker, over what `syntax` says about markup in general. Before
453
+ // lists had a `marker` entry this was `syntax.color` and nothing else, which is what an unset
454
+ // `marker` still resolves to.
455
+ const style = { ...markdownStyle.syntax, ...list?.marker }
456
+ const color = (style.color as string) || ""
457
+ const { fontSize } = rendering
458
+ const scale = toNumber(list?.markerScale)
459
+
460
+ if (scale <= 0 || fontSize <= 0) {
461
+ applyStyle(node, textStyle(style))
462
+ return
463
+ }
464
+
465
+ if (type === "list-ordered") {
466
+ applyStyle(node, textStyle(style))
467
+ applyStyle(node, { fontSize: fontSize * scale })
468
+ return
469
+ }
470
+
471
+ const diameter = fontSize * scale
472
+
473
+ // A box one line high, aligned to the top of the line, with the circle painted in the middle of
474
+ // it: that is what centres the bullet on the line rather than on the text, which is where both
475
+ // native platforms draw it. The height is the marker character's own line box -- an inline block
476
+ // holding one line of text is exactly one line high -- so it follows the line height without
477
+ // having to be told it.
478
+ applyStyle(node, textStyle(style))
479
+ applyStyle(node, {
480
+ display: "inline-block",
481
+ width: diameter,
482
+ verticalAlign: "top",
483
+ backgroundImage: `radial-gradient(circle closest-side, ${
484
+ color || "currentColor"
485
+ } 100%, transparent 100%)`,
486
+ backgroundSize: `${diameter}px ${diameter}px`,
487
+ backgroundPosition: "center",
488
+ backgroundRepeat: "no-repeat",
489
+ // The character is still in the box, and stays out of sight of everything but a selection.
490
+ color: "transparent",
491
+ overflow: "hidden",
187
492
  })
188
493
  }
189
494
 
190
495
  /**
191
496
  * Sizes a heading the way native does: level N is the base size scaled N-1 times.
192
497
  *
193
- * The level does not reach here -- `ungroupRanges` turns it into one nested span per level -- so
498
+ * The level does not reach here -- `ungroupRanges` turns it into one nested element per level -- so
194
499
  * 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.
500
+ * once the element is in the tree, which is what lets it tell an inner level from the outermost one.
196
501
  */
197
- function addHeadingStyle(node: HTMLElement, markdownStyle: PartialMarkdownStyle) {
502
+ function addHeadingStyle(node: HTMLElement, markdownStyle: ResolvedMarkdownStyle) {
198
503
  const nested = isChildOfMarkdownElement(node, "heading")
199
504
  const scale = markdownStyle.heading?.scale
200
505
 
506
+ applyStyle(node, textStyle(markdownStyle.heading))
201
507
  applyStyle(node, {
202
- fontWeight: "bold",
203
508
  fontSize: nested ? `${scale === undefined ? 1 : scale}em` : markdownStyle.heading?.fontSize,
204
509
  })
205
510
  }
@@ -237,6 +542,7 @@ function isChildOfMultilineMarkdownElement(node: HTMLElement): boolean {
237
542
  export {
238
543
  addStyleToBlock,
239
544
  addLineLayout,
545
+ setRibbonEdge,
240
546
  addBlockPrefixGap,
241
547
  isMultilineMarkdownType,
242
548
  isChildOfMarkdownElement,
@@ -0,0 +1,87 @@
1
+ import type { HTMLMarkdownElement } from "../../MarkdownTextInput.web"
2
+ import type { NodeType } from "./treeUtils"
3
+
4
+ /**
5
+ * The tag each markdown type is built as.
6
+ *
7
+ * A range says what a run of text *is*; the element it becomes should say the same thing to
8
+ * everything that reads the document rather than looking at it. A screen reader announces a
9
+ * `<strong>` and a `<blockquote>` and says nothing about a span; text copied out of the input
10
+ * arrives in another editor still emphasised; and the page `expo export` prerenders carries the
11
+ * structure rather than a wall of undifferentiated spans.
12
+ *
13
+ * `data-type` stays on every one of them regardless -- it is what the styling, the CSS and the
14
+ * render-model baselines are keyed on, and it distinguishes the several types that share a tag.
15
+ *
16
+ * Two pairings are worth spelling out. A fenced or indented block is `<pre>` from `codeblock`, the
17
+ * range covering the whole thing including its fences, with `<code>` inside it from `pre`, the
18
+ * range covering only the code -- which is the nesting HTML asks for, in the opposite naming to
19
+ * ours. And `link` is an anchor even in the input, where it has no `href` yet: an anchor without
20
+ * one is exactly what HTML has for a link whose destination is not resolved.
21
+ *
22
+ * Everything absent from here is a span, because HTML has nothing that means it: markdown's own
23
+ * punctuation, the parts of a link's markup, a mention, an emoji run.
24
+ */
25
+ /** Marks an element as one of ours, for the rules that undo the tag's browser defaults. */
26
+ const ELEMENT_CLASS = "react-native-marcus"
27
+
28
+ const TAGS: Partial<Record<NodeType, string>> = {
29
+ "strong": "strong",
30
+ "emphasis": "em",
31
+ // GFM renders `~~x~~` as `<del>`, and so does this. `<s>` is for text that is no longer accurate
32
+ // rather than text that was taken out.
33
+ "strikethrough": "del",
34
+ "code": "code",
35
+ "pre": "code",
36
+ "codeblock": "pre",
37
+ "blockquote": "blockquote",
38
+ "list-ordered": "ol",
39
+ "list-unordered": "ul",
40
+ "list-item": "li",
41
+ "link": "a",
42
+ }
43
+
44
+ /**
45
+ * The types that become a block-level element, and so cannot sit in a `<p>`.
46
+ *
47
+ * `pre` is not among them: it is built as `<code>`, which is phrasing content whatever it is
48
+ * displayed as. Its enclosing `codeblock` is the `<pre>`, and that is.
49
+ */
50
+ const BLOCK_LEVEL_TYPES = new Set<NodeType>([
51
+ "blockquote",
52
+ "codeblock",
53
+ "heading",
54
+ "list-ordered",
55
+ "list-unordered",
56
+ ])
57
+
58
+ function isBlockLevelType(type: NodeType) {
59
+ return BLOCK_LEVEL_TYPES.has(type)
60
+ }
61
+
62
+ /**
63
+ * Builds the element for a range, tagged and typed.
64
+ *
65
+ * `level` applies to headings only, and is the ATX level: `1` through `6` produce that heading,
66
+ * and `0` produces a span. A level arrives as one nested range per level -- `ungroupRanges` is what
67
+ * turns `depth` into the nesting the size scaling compounds down -- so only the outermost of them
68
+ * can be the heading itself, and the copies inside it are spans. `<h2><h2>` is not a thing.
69
+ */
70
+ function createMarkdownElement(type: NodeType, level = 0): HTMLMarkdownElement {
71
+ const tag =
72
+ type === "heading" ? (level > 0 ? `h${Math.min(level, 6)}` : "span") : TAGS[type] ?? "span"
73
+ const element = document.createElement(tag) as HTMLMarkdownElement
74
+
75
+ element.setAttribute("data-type", type)
76
+
77
+ // Every tag but the span brings the browser's own idea of how it looks, and `MarkdownElements.css`
78
+ // hands that back. The class is what scopes those rules to elements this library built, rather
79
+ // than to every `<blockquote data-type>` on the page.
80
+ if (tag !== "span") {
81
+ element.className = ELEMENT_CLASS
82
+ }
83
+
84
+ return element
85
+ }
86
+
87
+ export { createMarkdownElement, isBlockLevelType, ELEMENT_CLASS }