@saiansh2525/react-native-nitro-markdown 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +468 -0
  3. package/android/CMakeLists.txt +40 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +7 -0
  8. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +61 -0
  9. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +27 -0
  10. package/cpp/CMakeLists.txt +46 -0
  11. package/cpp/bindings/HybridMarkdownParser.cpp +114 -0
  12. package/cpp/bindings/HybridMarkdownParser.hpp +28 -0
  13. package/cpp/bindings/HybridMarkdownSession.cpp +0 -0
  14. package/cpp/core/MD4CParser.cpp +440 -0
  15. package/cpp/core/MD4CParser.hpp +21 -0
  16. package/cpp/core/MarkdownSessionCore.cpp +0 -0
  17. package/cpp/core/MarkdownTypes.hpp +124 -0
  18. package/cpp/md4c/md4c.c +6610 -0
  19. package/cpp/md4c/md4c.h +410 -0
  20. package/ios/HybridMarkdownSession.swift +64 -0
  21. package/lib/commonjs/Markdown.nitro.js +6 -0
  22. package/lib/commonjs/Markdown.nitro.js.map +1 -0
  23. package/lib/commonjs/MarkdownContext.js +17 -0
  24. package/lib/commonjs/MarkdownContext.js.map +1 -0
  25. package/lib/commonjs/MarkdownSession.js +11 -0
  26. package/lib/commonjs/MarkdownSession.js.map +1 -0
  27. package/lib/commonjs/default-markdown-renderer.js +217 -0
  28. package/lib/commonjs/default-markdown-renderer.js.map +1 -0
  29. package/lib/commonjs/headless.js +98 -0
  30. package/lib/commonjs/headless.js.map +1 -0
  31. package/lib/commonjs/index.js +226 -0
  32. package/lib/commonjs/index.js.map +1 -0
  33. package/lib/commonjs/markdown-stream.js +32 -0
  34. package/lib/commonjs/markdown-stream.js.map +1 -0
  35. package/lib/commonjs/markdown.js +385 -0
  36. package/lib/commonjs/markdown.js.map +1 -0
  37. package/lib/commonjs/package.json +1 -0
  38. package/lib/commonjs/renderers/blockquote.js +36 -0
  39. package/lib/commonjs/renderers/blockquote.js.map +1 -0
  40. package/lib/commonjs/renderers/code.js +99 -0
  41. package/lib/commonjs/renderers/code.js.map +1 -0
  42. package/lib/commonjs/renderers/heading.js +63 -0
  43. package/lib/commonjs/renderers/heading.js.map +1 -0
  44. package/lib/commonjs/renderers/horizontal-rule.js +29 -0
  45. package/lib/commonjs/renderers/horizontal-rule.js.map +1 -0
  46. package/lib/commonjs/renderers/image.js +184 -0
  47. package/lib/commonjs/renderers/image.js.map +1 -0
  48. package/lib/commonjs/renderers/link.js +35 -0
  49. package/lib/commonjs/renderers/link.js.map +1 -0
  50. package/lib/commonjs/renderers/list.js +114 -0
  51. package/lib/commonjs/renderers/list.js.map +1 -0
  52. package/lib/commonjs/renderers/math.js +137 -0
  53. package/lib/commonjs/renderers/math.js.map +1 -0
  54. package/lib/commonjs/renderers/paragraph.js +37 -0
  55. package/lib/commonjs/renderers/paragraph.js.map +1 -0
  56. package/lib/commonjs/renderers/table.js +290 -0
  57. package/lib/commonjs/renderers/table.js.map +1 -0
  58. package/lib/commonjs/specs/MarkdownSession.nitro.js +6 -0
  59. package/lib/commonjs/specs/MarkdownSession.nitro.js.map +1 -0
  60. package/lib/commonjs/theme.js +191 -0
  61. package/lib/commonjs/theme.js.map +1 -0
  62. package/lib/commonjs/use-markdown-stream.js +71 -0
  63. package/lib/commonjs/use-markdown-stream.js.map +1 -0
  64. package/lib/module/Markdown.nitro.js +4 -0
  65. package/lib/module/Markdown.nitro.js.map +1 -0
  66. package/lib/module/MarkdownContext.js +12 -0
  67. package/lib/module/MarkdownContext.js.map +1 -0
  68. package/lib/module/MarkdownSession.js +7 -0
  69. package/lib/module/MarkdownSession.js.map +1 -0
  70. package/lib/module/default-markdown-renderer.js +212 -0
  71. package/lib/module/default-markdown-renderer.js.map +1 -0
  72. package/lib/module/headless.js +90 -0
  73. package/lib/module/headless.js.map +1 -0
  74. package/lib/module/index.js +21 -0
  75. package/lib/module/index.js.map +1 -0
  76. package/lib/module/markdown-stream.js +27 -0
  77. package/lib/module/markdown-stream.js.map +1 -0
  78. package/lib/module/markdown.js +380 -0
  79. package/lib/module/markdown.js.map +1 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/renderers/blockquote.js +31 -0
  82. package/lib/module/renderers/blockquote.js.map +1 -0
  83. package/lib/module/renderers/code.js +93 -0
  84. package/lib/module/renderers/code.js.map +1 -0
  85. package/lib/module/renderers/heading.js +58 -0
  86. package/lib/module/renderers/heading.js.map +1 -0
  87. package/lib/module/renderers/horizontal-rule.js +24 -0
  88. package/lib/module/renderers/horizontal-rule.js.map +1 -0
  89. package/lib/module/renderers/image.js +179 -0
  90. package/lib/module/renderers/image.js.map +1 -0
  91. package/lib/module/renderers/link.js +30 -0
  92. package/lib/module/renderers/link.js.map +1 -0
  93. package/lib/module/renderers/list.js +107 -0
  94. package/lib/module/renderers/list.js.map +1 -0
  95. package/lib/module/renderers/math.js +131 -0
  96. package/lib/module/renderers/math.js.map +1 -0
  97. package/lib/module/renderers/paragraph.js +32 -0
  98. package/lib/module/renderers/paragraph.js.map +1 -0
  99. package/lib/module/renderers/table.js +285 -0
  100. package/lib/module/renderers/table.js.map +1 -0
  101. package/lib/module/specs/MarkdownSession.nitro.js +4 -0
  102. package/lib/module/specs/MarkdownSession.nitro.js.map +1 -0
  103. package/lib/module/theme.js +186 -0
  104. package/lib/module/theme.js.map +1 -0
  105. package/lib/module/use-markdown-stream.js +66 -0
  106. package/lib/module/use-markdown-stream.js.map +1 -0
  107. package/lib/typescript/commonjs/Markdown.nitro.d.ts +13 -0
  108. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/MarkdownContext.d.ts +65 -0
  110. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/MarkdownSession.d.ts +4 -0
  112. package/lib/typescript/commonjs/MarkdownSession.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/default-markdown-renderer.d.ts +10 -0
  114. package/lib/typescript/commonjs/default-markdown-renderer.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/headless.d.ts +61 -0
  116. package/lib/typescript/commonjs/headless.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/index.d.ts +22 -0
  118. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/markdown-stream.d.ts +15 -0
  120. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/markdown.d.ts +60 -0
  122. package/lib/typescript/commonjs/markdown.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/package.json +1 -0
  124. package/lib/typescript/commonjs/renderers/blockquote.d.ts +9 -0
  125. package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/renderers/code.d.ts +19 -0
  127. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/renderers/heading.d.ts +10 -0
  129. package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +8 -0
  131. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/renderers/image.d.ts +13 -0
  133. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/renderers/link.d.ts +10 -0
  135. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/renderers/list.d.ts +26 -0
  137. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -0
  138. package/lib/typescript/commonjs/renderers/math.d.ts +14 -0
  139. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -0
  140. package/lib/typescript/commonjs/renderers/paragraph.d.ts +10 -0
  141. package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/renderers/table.d.ts +12 -0
  143. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +12 -0
  145. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/theme.d.ts +65 -0
  147. package/lib/typescript/commonjs/theme.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/use-markdown-stream.d.ts +22 -0
  149. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -0
  150. package/lib/typescript/module/Markdown.nitro.d.ts +13 -0
  151. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -0
  152. package/lib/typescript/module/MarkdownContext.d.ts +65 -0
  153. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -0
  154. package/lib/typescript/module/MarkdownSession.d.ts +4 -0
  155. package/lib/typescript/module/MarkdownSession.d.ts.map +1 -0
  156. package/lib/typescript/module/default-markdown-renderer.d.ts +10 -0
  157. package/lib/typescript/module/default-markdown-renderer.d.ts.map +1 -0
  158. package/lib/typescript/module/headless.d.ts +61 -0
  159. package/lib/typescript/module/headless.d.ts.map +1 -0
  160. package/lib/typescript/module/index.d.ts +22 -0
  161. package/lib/typescript/module/index.d.ts.map +1 -0
  162. package/lib/typescript/module/markdown-stream.d.ts +15 -0
  163. package/lib/typescript/module/markdown-stream.d.ts.map +1 -0
  164. package/lib/typescript/module/markdown.d.ts +60 -0
  165. package/lib/typescript/module/markdown.d.ts.map +1 -0
  166. package/lib/typescript/module/package.json +1 -0
  167. package/lib/typescript/module/renderers/blockquote.d.ts +9 -0
  168. package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -0
  169. package/lib/typescript/module/renderers/code.d.ts +19 -0
  170. package/lib/typescript/module/renderers/code.d.ts.map +1 -0
  171. package/lib/typescript/module/renderers/heading.d.ts +10 -0
  172. package/lib/typescript/module/renderers/heading.d.ts.map +1 -0
  173. package/lib/typescript/module/renderers/horizontal-rule.d.ts +8 -0
  174. package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -0
  175. package/lib/typescript/module/renderers/image.d.ts +13 -0
  176. package/lib/typescript/module/renderers/image.d.ts.map +1 -0
  177. package/lib/typescript/module/renderers/link.d.ts +10 -0
  178. package/lib/typescript/module/renderers/link.d.ts.map +1 -0
  179. package/lib/typescript/module/renderers/list.d.ts +26 -0
  180. package/lib/typescript/module/renderers/list.d.ts.map +1 -0
  181. package/lib/typescript/module/renderers/math.d.ts +14 -0
  182. package/lib/typescript/module/renderers/math.d.ts.map +1 -0
  183. package/lib/typescript/module/renderers/paragraph.d.ts +10 -0
  184. package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -0
  185. package/lib/typescript/module/renderers/table.d.ts +12 -0
  186. package/lib/typescript/module/renderers/table.d.ts.map +1 -0
  187. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +12 -0
  188. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -0
  189. package/lib/typescript/module/theme.d.ts +65 -0
  190. package/lib/typescript/module/theme.d.ts.map +1 -0
  191. package/lib/typescript/module/use-markdown-stream.d.ts +22 -0
  192. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -0
  193. package/nitro.json +19 -0
  194. package/nitrogen/generated/.gitattributes +1 -0
  195. package/nitrogen/generated/android/NitroMarkdown+autolinking.cmake +82 -0
  196. package/nitrogen/generated/android/NitroMarkdown+autolinking.gradle +27 -0
  197. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +56 -0
  198. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +25 -0
  199. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  200. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +91 -0
  201. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +70 -0
  202. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/Func_void.kt +80 -0
  203. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +78 -0
  204. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/NitroMarkdownOnLoad.kt +35 -0
  205. package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +60 -0
  206. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +41 -0
  207. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +93 -0
  208. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Umbrella.hpp +45 -0
  209. package/nitrogen/generated/ios/NitroMarkdownAutolinking.mm +43 -0
  210. package/nitrogen/generated/ios/NitroMarkdownAutolinking.swift +26 -0
  211. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.cpp +11 -0
  212. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +108 -0
  213. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  214. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +59 -0
  215. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +190 -0
  216. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +22 -0
  217. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +65 -0
  218. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +26 -0
  219. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +67 -0
  220. package/nitrogen/generated/shared/c++/ParserOptions.hpp +87 -0
  221. package/package.json +134 -0
  222. package/react-native-nitro-markdown.podspec +42 -0
  223. package/src/Markdown.nitro.ts +12 -0
  224. package/src/MarkdownContext.ts +98 -0
  225. package/src/MarkdownSession.ts +8 -0
  226. package/src/default-markdown-renderer.tsx +261 -0
  227. package/src/headless.ts +171 -0
  228. package/src/index.ts +52 -0
  229. package/src/markdown-stream.tsx +32 -0
  230. package/src/markdown.tsx +521 -0
  231. package/src/renderers/blockquote.tsx +30 -0
  232. package/src/renderers/code.tsx +112 -0
  233. package/src/renderers/heading.tsx +66 -0
  234. package/src/renderers/horizontal-rule.tsx +23 -0
  235. package/src/renderers/image.tsx +204 -0
  236. package/src/renderers/link.tsx +33 -0
  237. package/src/renderers/list.tsx +123 -0
  238. package/src/renderers/math.tsx +147 -0
  239. package/src/renderers/paragraph.tsx +45 -0
  240. package/src/renderers/table.tsx +370 -0
  241. package/src/specs/MarkdownSession.nitro.ts +14 -0
  242. package/src/theme.ts +243 -0
  243. package/src/use-markdown-stream.ts +83 -0
@@ -0,0 +1,66 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import { Text, StyleSheet, type TextStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface HeadingProps {
6
+ level: number;
7
+ children: ReactNode;
8
+ style?: TextStyle;
9
+ }
10
+
11
+ export const Heading: FC<HeadingProps> = ({ level, children, style }) => {
12
+ const { theme } = useMarkdownContext();
13
+ const styles = useMemo(
14
+ () =>
15
+ StyleSheet.create({
16
+ heading: {
17
+ color: theme.colors.heading,
18
+ fontWeight: "700",
19
+ marginTop: theme.spacing.xl,
20
+ marginBottom: theme.spacing.m,
21
+ fontFamily: theme.fontFamilies.heading,
22
+ },
23
+ h1: {
24
+ fontSize: theme.fontSizes.h1,
25
+ lineHeight: theme.fontSizes.h1 * 1.3,
26
+ borderBottomWidth: 1,
27
+ borderBottomColor: theme.colors.border,
28
+ paddingBottom: theme.spacing.s,
29
+ },
30
+ h2: {
31
+ fontSize: theme.fontSizes.h2,
32
+ lineHeight: theme.fontSizes.h2 * 1.3,
33
+ },
34
+ h3: {
35
+ fontSize: theme.fontSizes.h3,
36
+ lineHeight: theme.fontSizes.h3 * 1.3,
37
+ },
38
+ h4: {
39
+ fontSize: theme.fontSizes.h4,
40
+ lineHeight: theme.fontSizes.h4 * 1.3,
41
+ },
42
+ h5: {
43
+ fontSize: theme.fontSizes.h5,
44
+ lineHeight: theme.fontSizes.h5 * 1.3,
45
+ },
46
+ h6: {
47
+ fontSize: theme.fontSizes.h6,
48
+ lineHeight: theme.fontSizes.h6 * 1.3,
49
+ color: theme.colors.textMuted,
50
+ },
51
+ }),
52
+ [theme]
53
+ );
54
+
55
+ const headingStyles = [
56
+ styles.heading,
57
+ level === 1 && styles.h1,
58
+ level === 2 && styles.h2,
59
+ level === 3 && styles.h3,
60
+ level === 4 && styles.h4,
61
+ level === 5 && styles.h5,
62
+ level === 6 && styles.h6,
63
+ ];
64
+
65
+ return <Text style={[...headingStyles, style]}>{children}</Text>;
66
+ };
@@ -0,0 +1,23 @@
1
+ import { useMemo, type FC } from "react";
2
+ import { View, StyleSheet, type ViewStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface HorizontalRuleProps {
6
+ style?: ViewStyle;
7
+ }
8
+
9
+ export const HorizontalRule: FC<HorizontalRuleProps> = ({ style }) => {
10
+ const { theme } = useMarkdownContext();
11
+ const styles = useMemo(
12
+ () =>
13
+ StyleSheet.create({
14
+ horizontalRule: {
15
+ height: 1,
16
+ backgroundColor: theme.colors.border,
17
+ marginVertical: theme.spacing.xl,
18
+ },
19
+ }),
20
+ [theme]
21
+ );
22
+ return <View style={[styles.horizontalRule, style]} />;
23
+ };
@@ -0,0 +1,204 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ type ReactNode,
7
+ type FC,
8
+ type ComponentType,
9
+ } from "react";
10
+ import {
11
+ View,
12
+ Text,
13
+ Image as RNImage,
14
+ StyleSheet,
15
+ type ViewStyle,
16
+ } from "react-native";
17
+
18
+ import { parseMarkdownWithOptions, type MarkdownNode } from "../headless";
19
+ import type { NodeRendererProps } from "../MarkdownContext";
20
+ import { useMarkdownContext } from "../MarkdownContext";
21
+
22
+ const renderInlineContent = (
23
+ node: MarkdownNode,
24
+ Renderer: ComponentType<NodeRendererProps>,
25
+ ): ReactNode => {
26
+ if (node.type === "paragraph" && node.children) {
27
+ return (
28
+ <>
29
+ {node.children.map((child, idx) => (
30
+ <Renderer key={idx} node={child} depth={0} inListItem={false} />
31
+ ))}
32
+ </>
33
+ );
34
+ }
35
+ return null;
36
+ };
37
+
38
+ interface ImageProps {
39
+ url: string;
40
+ title?: string;
41
+ alt?: string;
42
+ Renderer?: ComponentType<NodeRendererProps>;
43
+ style?: ViewStyle;
44
+ }
45
+
46
+ export const Image: FC<ImageProps> = ({ url, title, alt, Renderer, style }) => {
47
+ const [loading, setLoading] = useState(true);
48
+ const [error, setError] = useState(false);
49
+ const [aspectRatio, setAspectRatio] = useState<number | undefined>(undefined);
50
+ const { theme } = useMarkdownContext();
51
+
52
+ const styles = useMemo(
53
+ () =>
54
+ StyleSheet.create({
55
+ imageContainer: {
56
+ marginVertical: theme.spacing.m,
57
+ alignItems: "center",
58
+ },
59
+ image: {
60
+ width: "100%",
61
+ // If we have an aspect ratio, use it. Otherwise, use minHeight as fallback.
62
+ aspectRatio: aspectRatio,
63
+ minHeight: aspectRatio ? undefined : 200,
64
+ borderRadius: theme.borderRadius.m,
65
+ backgroundColor: theme.colors.surface,
66
+ },
67
+ imageHidden: {
68
+ opacity: 0,
69
+ position: "absolute",
70
+ },
71
+ imageLoading: {
72
+ width: "100%",
73
+ // Match the image size if possible
74
+ aspectRatio: aspectRatio,
75
+ height: aspectRatio ? undefined : 200,
76
+ borderRadius: theme.borderRadius.m,
77
+ backgroundColor: theme.colors.surface,
78
+ justifyContent: "center",
79
+ alignItems: "center",
80
+ },
81
+ imageLoadingText: {
82
+ color: theme.colors.textMuted,
83
+ fontSize: theme.fontSizes.s,
84
+ fontFamily: theme.fontFamilies.regular,
85
+ },
86
+ imageError: {
87
+ width: "100%",
88
+ padding: theme.spacing.l,
89
+ borderRadius: theme.borderRadius.m,
90
+ backgroundColor: theme.colors.surface,
91
+ alignItems: "center",
92
+ marginVertical: theme.spacing.m,
93
+ },
94
+ imageErrorText: {
95
+ color: theme.colors.textMuted,
96
+ fontSize: theme.fontSizes.s,
97
+ fontFamily: theme.fontFamilies.regular,
98
+ },
99
+ imageCaption: {
100
+ color: theme.colors.textMuted,
101
+ fontSize: theme.fontSizes.xs,
102
+ marginTop: theme.spacing.s,
103
+ fontStyle: "italic",
104
+ textAlign: "center",
105
+ fontFamily: theme.fontFamilies.regular,
106
+ },
107
+ }),
108
+ [theme, aspectRatio],
109
+ );
110
+
111
+ useLayoutEffect(() => {
112
+ // Fast path for consistent aspect ratios if checking picsum
113
+ const picsumMatch = url.match(/picsum\.photos\/.*\/(\d+)\/(\d+)/);
114
+ if (picsumMatch) {
115
+ const w = parseInt(picsumMatch[1], 10);
116
+ const h = parseInt(picsumMatch[2], 10);
117
+ if (!isNaN(w) && !isNaN(h) && h !== 0) {
118
+ setAspectRatio(w / h);
119
+ }
120
+ }
121
+
122
+ RNImage.getSize(
123
+ url,
124
+ (width, height) => {
125
+ if (width > 0 && height > 0) {
126
+ setAspectRatio(width / height);
127
+ }
128
+ },
129
+ () => {},
130
+ );
131
+ }, [url]);
132
+
133
+ const altContent = useMemo(() => {
134
+ if (!alt || !Renderer) return null;
135
+ if (
136
+ alt.includes("$") ||
137
+ alt.includes("*") ||
138
+ alt.includes("_") ||
139
+ alt.includes("`") ||
140
+ alt.includes("[")
141
+ ) {
142
+ try {
143
+ const ast = parseMarkdownWithOptions(alt, { math: true, gfm: true });
144
+ if (
145
+ ast?.type === "document" &&
146
+ ast.children?.[0]?.type === "paragraph"
147
+ ) {
148
+ const paragraph = ast.children[0];
149
+ const inlineContent = renderInlineContent(paragraph, Renderer);
150
+ if (inlineContent) {
151
+ return inlineContent;
152
+ }
153
+ }
154
+ return <Text style={styles.imageErrorText}>{alt}</Text>;
155
+ } catch {
156
+ return <Text style={styles.imageErrorText}>{alt}</Text>;
157
+ }
158
+ }
159
+ return <Text style={styles.imageErrorText}>{alt}</Text>;
160
+ }, [alt, Renderer, styles.imageErrorText]);
161
+
162
+ if (error) {
163
+ return (
164
+ <View style={[styles.imageError, style]}>
165
+ <View
166
+ style={{
167
+ flexDirection: "row",
168
+ alignItems: "baseline",
169
+ flexWrap: "wrap",
170
+ justifyContent: "center",
171
+ }}
172
+ >
173
+ <Text style={styles.imageErrorText}>🖼️ </Text>
174
+ {altContent || (
175
+ <Text style={styles.imageErrorText}>
176
+ {title || "Image failed to load"}
177
+ </Text>
178
+ )}
179
+ </View>
180
+ </View>
181
+ );
182
+ }
183
+
184
+ return (
185
+ <View style={[styles.imageContainer, style]}>
186
+ {loading && !aspectRatio && (
187
+ <View style={styles.imageLoading}>
188
+ <Text style={styles.imageLoadingText}>Loading image...</Text>
189
+ </View>
190
+ )}
191
+ <RNImage
192
+ source={{ uri: url }}
193
+ style={[styles.image, loading && !aspectRatio && styles.imageHidden]}
194
+ resizeMode="contain"
195
+ onLoad={() => setLoading(false)}
196
+ onError={() => {
197
+ setLoading(false);
198
+ setError(true);
199
+ }}
200
+ />
201
+ {title && !loading && <Text style={styles.imageCaption}>{title}</Text>}
202
+ </View>
203
+ );
204
+ };
@@ -0,0 +1,33 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import { Text, StyleSheet, Linking, type TextStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface LinkProps {
6
+ href: string;
7
+ children: ReactNode;
8
+ style?: TextStyle;
9
+ }
10
+
11
+ export const Link: FC<LinkProps> = ({ href, children, style }) => {
12
+ const { theme } = useMarkdownContext();
13
+ const styles = useMemo(
14
+ () =>
15
+ StyleSheet.create({
16
+ link: {
17
+ color: theme.colors.link,
18
+ textDecorationLine: "underline",
19
+ },
20
+ }),
21
+ [theme]
22
+ );
23
+
24
+ const handlePress = () => {
25
+ if (href) Linking.openURL(href);
26
+ };
27
+
28
+ return (
29
+ <Text style={[styles.link, style]} onPress={handlePress}>
30
+ {children}
31
+ </Text>
32
+ );
33
+ };
@@ -0,0 +1,123 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import { View, Text, StyleSheet, type ViewStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface ListProps {
6
+ ordered: boolean;
7
+ start?: number;
8
+ depth: number;
9
+ children: ReactNode;
10
+ style?: ViewStyle;
11
+ }
12
+
13
+ export const List: FC<ListProps> = ({ depth, children, style }) => {
14
+ const { theme } = useMarkdownContext();
15
+ const styles = useMemo(
16
+ () =>
17
+ StyleSheet.create({
18
+ list: {
19
+ marginBottom: theme.spacing.m,
20
+ },
21
+ listNested: {
22
+ marginLeft: theme.spacing.s,
23
+ marginBottom: 0,
24
+ },
25
+ }),
26
+ [theme],
27
+ );
28
+ return (
29
+ <View style={[styles.list, depth > 0 && styles.listNested, style]}>
30
+ {children}
31
+ </View>
32
+ );
33
+ };
34
+
35
+ interface ListItemProps {
36
+ children: ReactNode;
37
+ index: number;
38
+ ordered: boolean;
39
+ start: number;
40
+ style?: ViewStyle;
41
+ }
42
+
43
+ export const ListItem: FC<ListItemProps> = ({
44
+ children,
45
+ index,
46
+ ordered,
47
+ start,
48
+ style,
49
+ }) => {
50
+ const { theme } = useMarkdownContext();
51
+ const styles = useMemo(
52
+ () =>
53
+ StyleSheet.create({
54
+ listItem: {
55
+ flexDirection: "row",
56
+ marginBottom: theme.spacing.s,
57
+ alignItems: "flex-start",
58
+ },
59
+ listBullet: {
60
+ color: theme.colors.accent,
61
+ fontSize: theme.fontSizes.m,
62
+ lineHeight: theme.fontSizes.m * 1.6,
63
+ marginRight: theme.spacing.s,
64
+ minWidth: 20,
65
+ textAlign: "center",
66
+ fontFamily: theme.fontFamilies.regular,
67
+ },
68
+ listItemContent: {
69
+ flex: 1,
70
+ minWidth: 0,
71
+ },
72
+ }),
73
+ [theme],
74
+ );
75
+ const bullet = ordered ? `${start + index}.` : "•";
76
+ return (
77
+ <View style={[styles.listItem, style]}>
78
+ <Text style={styles.listBullet}>{bullet}</Text>
79
+ <View style={styles.listItemContent}>{children}</View>
80
+ </View>
81
+ );
82
+ };
83
+
84
+ interface TaskListItemProps {
85
+ children: ReactNode;
86
+ checked: boolean;
87
+ style?: ViewStyle;
88
+ }
89
+
90
+ export const TaskListItem: FC<TaskListItemProps> = ({
91
+ children,
92
+ checked,
93
+ style,
94
+ }) => {
95
+ const { theme } = useMarkdownContext();
96
+ const styles = useMemo(
97
+ () =>
98
+ StyleSheet.create({
99
+ taskListItem: {
100
+ flexDirection: "row",
101
+ alignItems: "flex-start",
102
+ marginBottom: theme.spacing.s,
103
+ },
104
+ taskCheckbox: {
105
+ fontSize: theme.fontSizes.l,
106
+ lineHeight: theme.fontSizes.m * 1.6,
107
+ marginRight: theme.spacing.s,
108
+ color: theme.colors.accent,
109
+ },
110
+ taskContent: {
111
+ flex: 1,
112
+ minWidth: 0,
113
+ },
114
+ }),
115
+ [theme],
116
+ );
117
+ return (
118
+ <View style={[styles.taskListItem, style]}>
119
+ <Text style={styles.taskCheckbox}>{checked ? "☑" : "☐"}</Text>
120
+ <View style={styles.taskContent}>{children}</View>
121
+ </View>
122
+ );
123
+ };
@@ -0,0 +1,147 @@
1
+ import { useMemo, type FC, type ComponentType } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Platform,
7
+ type StyleProp,
8
+ type ViewStyle,
9
+ } from "react-native";
10
+ import { useMarkdownContext } from "../MarkdownContext";
11
+ import type { MarkdownTheme } from "../theme";
12
+
13
+ let MathJaxComponent: ComponentType<{
14
+ fontSize?: number;
15
+ color?: string;
16
+ fontCache?: boolean;
17
+ style?: StyleProp<ViewStyle>;
18
+ children?: string;
19
+ }> | null = null;
20
+
21
+ try {
22
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
23
+ const mathJaxModule = require("react-native-mathjax-svg");
24
+ MathJaxComponent = mathJaxModule.default || mathJaxModule;
25
+ } catch {
26
+ // ignored
27
+ }
28
+
29
+ interface MathInlineProps {
30
+ content?: string;
31
+ style?: ViewStyle;
32
+ }
33
+
34
+ const createMathStyles = (theme: MarkdownTheme) =>
35
+ StyleSheet.create({
36
+ mathInlineContainer: {
37
+ marginHorizontal: 2,
38
+ // Ensure the inline view has layout alignment
39
+ justifyContent: "center",
40
+ },
41
+ mathInlineFallbackContainer: {
42
+ backgroundColor: theme.colors.codeBackground,
43
+ paddingHorizontal: theme.spacing.xs,
44
+ paddingVertical: 2,
45
+ borderRadius: theme.borderRadius.s,
46
+ marginHorizontal: 2,
47
+ },
48
+ mathInlineFallback: {
49
+ fontFamily:
50
+ theme.fontFamilies.mono ??
51
+ Platform.select({ ios: "Courier", android: "monospace" }),
52
+ fontSize: theme.fontSizes.s,
53
+ color: theme.colors.code,
54
+ },
55
+ mathBlockContainer: {
56
+ marginVertical: theme.spacing.m,
57
+ paddingVertical: theme.spacing.l,
58
+ paddingHorizontal: theme.spacing.l,
59
+ backgroundColor: theme.colors.surface,
60
+ borderRadius: theme.borderRadius.l,
61
+ alignItems: "center",
62
+ borderWidth: 1,
63
+ borderColor: theme.colors.border,
64
+ // Ensure we don't collapse if MathJax fails to report size immediately
65
+ minHeight: 48,
66
+ },
67
+ mathBlockFallbackContainer: {
68
+ marginVertical: theme.spacing.m,
69
+ paddingVertical: theme.spacing.m,
70
+ paddingHorizontal: theme.spacing.l,
71
+ backgroundColor: theme.colors.codeBackground,
72
+ borderRadius: theme.borderRadius.m,
73
+ alignItems: "center",
74
+ borderWidth: 1,
75
+ borderColor: theme.colors.border,
76
+ },
77
+ mathBlockFallback: {
78
+ fontFamily:
79
+ theme.fontFamilies.mono ??
80
+ Platform.select({ ios: "Courier", android: "monospace" }),
81
+ fontSize: theme.fontSizes.m,
82
+ color: theme.colors.code,
83
+ textAlign: "center",
84
+ },
85
+ });
86
+
87
+ export const MathInline: FC<MathInlineProps> = ({ content, style }) => {
88
+ const { theme } = useMarkdownContext();
89
+ const styles = useMemo(() => createMathStyles(theme), [theme]);
90
+
91
+ if (!content) return null;
92
+
93
+ if (MathJaxComponent) {
94
+ const fontSize = theme.fontSizes.s;
95
+ return (
96
+ <View style={[styles.mathInlineContainer, style]}>
97
+ <MathJaxComponent
98
+ fontSize={fontSize}
99
+ color={theme.colors.text}
100
+ fontCache={false}
101
+ style={{ backgroundColor: "transparent" }}
102
+ >
103
+ {content}
104
+ </MathJaxComponent>
105
+ </View>
106
+ );
107
+ }
108
+
109
+ return (
110
+ <View style={[styles.mathInlineFallbackContainer, style]}>
111
+ <Text style={styles.mathInlineFallback}>{content}</Text>
112
+ </View>
113
+ );
114
+ };
115
+
116
+ interface MathBlockProps {
117
+ content?: string;
118
+ style?: ViewStyle;
119
+ }
120
+
121
+ export const MathBlock: FC<MathBlockProps> = ({ content, style }) => {
122
+ const { theme } = useMarkdownContext();
123
+ const styles = useMemo(() => createMathStyles(theme), [theme]);
124
+
125
+ if (!content) return null;
126
+
127
+ if (MathJaxComponent) {
128
+ return (
129
+ <View style={[styles.mathBlockContainer, style]}>
130
+ <MathJaxComponent
131
+ fontSize={theme.fontSizes.l}
132
+ color={theme.colors.text}
133
+ fontCache={false}
134
+ style={{ backgroundColor: "transparent" }}
135
+ >
136
+ {`\\displaystyle ${content}`}
137
+ </MathJaxComponent>
138
+ </View>
139
+ );
140
+ }
141
+
142
+ return (
143
+ <View style={[styles.mathBlockFallbackContainer, style]}>
144
+ <Text style={styles.mathBlockFallback}>{content}</Text>
145
+ </View>
146
+ );
147
+ };
@@ -0,0 +1,45 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface ParagraphProps {
6
+ children: ReactNode;
7
+ inListItem?: boolean;
8
+ style?: StyleProp<ViewStyle>;
9
+ }
10
+
11
+ export const Paragraph: FC<ParagraphProps> = ({
12
+ children,
13
+ inListItem,
14
+ style,
15
+ }) => {
16
+ const { theme } = useMarkdownContext();
17
+ const styles = useMemo(
18
+ () =>
19
+ StyleSheet.create({
20
+ paragraph: {
21
+ flexDirection: "row",
22
+ flexWrap: "wrap",
23
+ marginBottom: theme.spacing.l,
24
+ gap: 0,
25
+ },
26
+ paragraphInListItem: {
27
+ marginBottom: 0,
28
+ marginTop: 0,
29
+ },
30
+ }),
31
+ [theme],
32
+ );
33
+
34
+ return (
35
+ <View
36
+ style={[
37
+ styles.paragraph,
38
+ inListItem && styles.paragraphInListItem,
39
+ style,
40
+ ]}
41
+ >
42
+ {children}
43
+ </View>
44
+ );
45
+ };