@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,521 @@
1
+ import {
2
+ defaultMarkdownTheme,
3
+ minimalMarkdownTheme,
4
+ mergeThemes,
5
+ type MarkdownTheme,
6
+ type PartialMarkdownTheme,
7
+ type NodeStyleOverrides,
8
+ type StylingStrategy,
9
+ } from "./theme";
10
+ import { useMemo, type ReactNode, type FC, Fragment } from "react";
11
+ import {
12
+ StyleSheet,
13
+ View,
14
+ Text,
15
+ type StyleProp,
16
+ type ViewStyle,
17
+ } from "react-native";
18
+ import {
19
+ parseMarkdown,
20
+ parseMarkdownWithOptions,
21
+ getFlattenedText,
22
+ getTextContent,
23
+ type MarkdownNode,
24
+ } from "./headless";
25
+ import type { ParserOptions } from "./Markdown.nitro";
26
+ import {
27
+ MarkdownContext,
28
+ useMarkdownContext,
29
+ type CustomRenderers,
30
+ type NodeRendererProps,
31
+ } from "./MarkdownContext";
32
+
33
+ import { Heading } from "./renderers/heading";
34
+ import { Paragraph } from "./renderers/paragraph";
35
+ import { Link } from "./renderers/link";
36
+ import { Blockquote } from "./renderers/blockquote";
37
+ import { HorizontalRule } from "./renderers/horizontal-rule";
38
+ import { CodeBlock, InlineCode } from "./renderers/code";
39
+ import { List, ListItem, TaskListItem } from "./renderers/list";
40
+ import { TableRenderer } from "./renderers/table";
41
+ import { Image } from "./renderers/image";
42
+ import { MathInline, MathBlock } from "./renderers/math";
43
+
44
+ export interface MarkdownProps {
45
+ /**
46
+ * The markdown string to parse and render.
47
+ */
48
+ children: string;
49
+ /**
50
+ * Parser options to enable GFM or Math support.
51
+ */
52
+ options?: ParserOptions;
53
+ /**
54
+ * Callback fired when parsing begins.
55
+ */
56
+ onParsingInProgress?: () => void;
57
+ /**
58
+ * Callback fired when parsing completes.
59
+ */
60
+ onParseComplete?: (result: {
61
+ raw: string;
62
+ ast: MarkdownNode;
63
+ text: string;
64
+ }) => void;
65
+ /**
66
+ * Custom renderers for specific markdown node types.
67
+ * Each renderer receives { node, children, Renderer } plus type-specific props.
68
+ */
69
+ renderers?: CustomRenderers;
70
+ /**
71
+ * Custom theme to override default styles.
72
+ */
73
+ theme?: PartialMarkdownTheme;
74
+ /**
75
+ * Style overrides for specific node types.
76
+ * Applied after internal styles, allowing fine-grained customization.
77
+ * @example
78
+ * ```tsx
79
+ * <Markdown styles={{ heading: { color: 'red' }, code_block: { borderRadius: 0 } }}>
80
+ * {content}
81
+ * </Markdown>
82
+ * ```
83
+ */
84
+ styles?: NodeStyleOverrides;
85
+ /**
86
+ * Styling strategy for the component.
87
+ * - "opinionated": Full styling with colors, spacing, and visual effects (default)
88
+ * - "minimal": Bare minimum styling for a clean slate
89
+ */
90
+ stylingStrategy?: StylingStrategy;
91
+ /**
92
+ * Optional style for the container view.
93
+ */
94
+ style?: StyleProp<ViewStyle>;
95
+ }
96
+
97
+ export const Markdown: FC<MarkdownProps> = ({
98
+ children,
99
+ options,
100
+ renderers = {},
101
+ theme: userTheme,
102
+ styles: nodeStyles,
103
+ stylingStrategy = "opinionated",
104
+ style,
105
+ onParsingInProgress,
106
+ onParseComplete,
107
+ }) => {
108
+ const ast = useMemo(() => {
109
+ try {
110
+ if (onParsingInProgress) {
111
+ onParsingInProgress();
112
+ }
113
+
114
+ let result: MarkdownNode;
115
+ if (options) {
116
+ result = parseMarkdownWithOptions(children, options);
117
+ } else {
118
+ result = parseMarkdown(children);
119
+ }
120
+
121
+ if (onParseComplete) {
122
+ onParseComplete({
123
+ raw: children,
124
+ ast: result,
125
+ text: getFlattenedText(result),
126
+ });
127
+ }
128
+
129
+ return result;
130
+ } catch (error) {
131
+ console.error("Failed to parse markdown:", error);
132
+ return null;
133
+ }
134
+ }, [children, options, onParsingInProgress, onParseComplete]);
135
+
136
+ const theme = useMemo(() => {
137
+ const base =
138
+ stylingStrategy === "minimal"
139
+ ? minimalMarkdownTheme
140
+ : defaultMarkdownTheme;
141
+ return mergeThemes(base, userTheme);
142
+ }, [userTheme, stylingStrategy]);
143
+
144
+ const baseStyles = useMemo(() => createBaseStyles(theme), [theme]);
145
+
146
+ if (!ast) {
147
+ return (
148
+ <View style={[baseStyles.container, style]}>
149
+ <Text style={baseStyles.errorText}>Error parsing markdown</Text>
150
+ </View>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <MarkdownContext.Provider
156
+ value={{ renderers, theme, styles: nodeStyles, stylingStrategy }}
157
+ >
158
+ <View style={[baseStyles.container, style]}>
159
+ <NodeRenderer node={ast} depth={0} inListItem={false} />
160
+ </View>
161
+ </MarkdownContext.Provider>
162
+ );
163
+ };
164
+
165
+ const isInline = (type: MarkdownNode["type"]): boolean => {
166
+ return (
167
+ type === "text" ||
168
+ type === "bold" ||
169
+ type === "italic" ||
170
+ type === "strikethrough" ||
171
+ type === "link" ||
172
+ type === "code_inline" ||
173
+ type === "soft_break" ||
174
+ type === "line_break" ||
175
+ type === "html_inline" ||
176
+ type === "math_inline"
177
+ );
178
+ };
179
+
180
+ const NodeRenderer: FC<NodeRendererProps> = ({
181
+ node,
182
+ depth,
183
+ inListItem,
184
+ parentIsText = false,
185
+ }) => {
186
+ const { renderers, theme, styles: nodeStyles } = useMarkdownContext();
187
+ const baseStyles = useMemo(() => createBaseStyles(theme), [theme]);
188
+
189
+ const renderChildren = (
190
+ children?: MarkdownNode[],
191
+ childInListItem = false,
192
+ childParentIsText = false,
193
+ ): ReactNode => {
194
+ if (!children || children.length === 0) return null;
195
+
196
+ const elements: ReactNode[] = [];
197
+ let currentInlineGroup: MarkdownNode[] = [];
198
+
199
+ const flushInlineGroup = () => {
200
+ if (currentInlineGroup.length > 0) {
201
+ const hasMath = currentInlineGroup.some(
202
+ (child) => child.type === "math_inline",
203
+ );
204
+
205
+ if (hasMath && !childParentIsText) {
206
+ elements.push(
207
+ <View
208
+ key={`inline-group-${elements.length}`}
209
+ style={{
210
+ flexDirection: "row",
211
+ alignItems: "center",
212
+ flexWrap: "wrap",
213
+ flexShrink: 1,
214
+ }}
215
+ >
216
+ {currentInlineGroup.map((n, idx) => (
217
+ <NodeRenderer
218
+ key={`${n.type}-${idx}`}
219
+ node={n}
220
+ depth={depth + 1}
221
+ inListItem={childInListItem}
222
+ parentIsText={false}
223
+ />
224
+ ))}
225
+ </View>,
226
+ );
227
+ } else {
228
+ const Wrapper = childParentIsText ? Fragment : Text;
229
+ const wrapperProps = childParentIsText
230
+ ? {}
231
+ : { style: baseStyles.text };
232
+
233
+ elements.push(
234
+ <Wrapper key={`inline-group-${elements.length}`} {...wrapperProps}>
235
+ {currentInlineGroup.map((n, idx) => (
236
+ <NodeRenderer
237
+ key={`${n.type}-${idx}`}
238
+ node={n}
239
+ depth={depth + 1}
240
+ inListItem={childInListItem}
241
+ parentIsText={true}
242
+ />
243
+ ))}
244
+ </Wrapper>,
245
+ );
246
+ }
247
+ currentInlineGroup = [];
248
+ }
249
+ };
250
+
251
+ children.forEach((child, index) => {
252
+ if (isInline(child.type)) {
253
+ currentInlineGroup.push(child);
254
+ } else {
255
+ flushInlineGroup();
256
+ elements.push(
257
+ <NodeRenderer
258
+ key={`${child.type}-${index}`}
259
+ node={child}
260
+ depth={depth + 1}
261
+ inListItem={childInListItem}
262
+ parentIsText={childParentIsText}
263
+ />,
264
+ );
265
+ }
266
+ });
267
+
268
+ flushInlineGroup();
269
+ return elements;
270
+ };
271
+
272
+ const customRenderer = renderers[node.type];
273
+ if (customRenderer) {
274
+ const childrenRendered = renderChildren(
275
+ node.children,
276
+ inListItem,
277
+ parentIsText,
278
+ );
279
+
280
+ const baseProps = {
281
+ node,
282
+ children: childrenRendered,
283
+ Renderer: NodeRenderer,
284
+ };
285
+
286
+ const enhancedProps = {
287
+ ...baseProps,
288
+ ...(node.type === "heading" && {
289
+ level: (node.level ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,
290
+ }),
291
+ ...(node.type === "link" && { href: node.href ?? "", title: node.title }),
292
+ ...(node.type === "image" && {
293
+ url: node.href ?? "",
294
+ alt: node.alt,
295
+ title: node.title,
296
+ }),
297
+ ...(node.type === "code_block" && {
298
+ content: getTextContent(node),
299
+ language: node.language,
300
+ }),
301
+ ...(node.type === "code_inline" && { content: node.content ?? "" }),
302
+ ...(node.type === "list" && {
303
+ ordered: node.ordered ?? false,
304
+ start: node.start,
305
+ }),
306
+ ...(node.type === "task_list_item" && { checked: node.checked ?? false }),
307
+ };
308
+
309
+ const result = customRenderer(enhancedProps);
310
+ if (result !== undefined) return <>{result}</>;
311
+ }
312
+
313
+ const nodeStyleOverride = nodeStyles?.[node.type];
314
+
315
+ switch (node.type) {
316
+ case "document":
317
+ return (
318
+ <View style={[baseStyles.document, nodeStyleOverride]}>
319
+ {renderChildren(node.children, false, false)}
320
+ </View>
321
+ );
322
+
323
+ case "heading":
324
+ return (
325
+ <Heading level={node.level ?? 1} style={nodeStyleOverride}>
326
+ {renderChildren(node.children, inListItem, true)}
327
+ </Heading>
328
+ );
329
+
330
+ case "paragraph":
331
+ return (
332
+ <Paragraph inListItem={inListItem} style={nodeStyleOverride}>
333
+ {renderChildren(node.children, inListItem, false)}
334
+ </Paragraph>
335
+ );
336
+
337
+ case "text":
338
+ if (parentIsText) {
339
+ return <Text>{node.content}</Text>;
340
+ }
341
+ return (
342
+ <Text style={[baseStyles.text, nodeStyleOverride]}>{node.content}</Text>
343
+ );
344
+
345
+ case "bold":
346
+ return (
347
+ <Text style={[baseStyles.bold, nodeStyleOverride]}>
348
+ {renderChildren(node.children, inListItem, true)}
349
+ </Text>
350
+ );
351
+
352
+ case "italic":
353
+ return (
354
+ <Text style={[baseStyles.italic, nodeStyleOverride]}>
355
+ {renderChildren(node.children, inListItem, true)}
356
+ </Text>
357
+ );
358
+
359
+ case "strikethrough":
360
+ return (
361
+ <Text style={[baseStyles.strikethrough, nodeStyleOverride]}>
362
+ {renderChildren(node.children, inListItem, true)}
363
+ </Text>
364
+ );
365
+
366
+ case "link":
367
+ return (
368
+ <Link href={node.href ?? ""} style={nodeStyleOverride}>
369
+ {renderChildren(node.children, inListItem, true)}
370
+ </Link>
371
+ );
372
+
373
+ case "image":
374
+ return (
375
+ <Image
376
+ url={node.href ?? ""}
377
+ title={node.title}
378
+ alt={node.alt}
379
+ Renderer={NodeRenderer}
380
+ style={nodeStyleOverride}
381
+ />
382
+ );
383
+
384
+ case "code_inline":
385
+ return <InlineCode style={nodeStyleOverride}>{node.content}</InlineCode>;
386
+
387
+ case "code_block":
388
+ return (
389
+ <CodeBlock
390
+ language={node.language}
391
+ content={getTextContent(node)}
392
+ style={nodeStyleOverride}
393
+ />
394
+ );
395
+
396
+ case "blockquote":
397
+ return (
398
+ <Blockquote style={nodeStyleOverride}>
399
+ {renderChildren(node.children, inListItem, false)}
400
+ </Blockquote>
401
+ );
402
+
403
+ case "horizontal_rule":
404
+ return <HorizontalRule style={nodeStyleOverride} />;
405
+
406
+ case "line_break":
407
+ return <Text>{"\n"}</Text>;
408
+
409
+ case "soft_break":
410
+ return <Text> </Text>;
411
+
412
+ case "math_inline": {
413
+ let mathContent = getTextContent(node);
414
+ if (!mathContent) return null;
415
+ mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
416
+ return <MathInline content={mathContent} style={nodeStyleOverride} />;
417
+ }
418
+
419
+ case "math_block":
420
+ return (
421
+ <MathBlock content={getTextContent(node)} style={nodeStyleOverride} />
422
+ );
423
+
424
+ case "list":
425
+ return (
426
+ <List
427
+ ordered={node.ordered ?? false}
428
+ start={node.start}
429
+ depth={depth}
430
+ style={nodeStyleOverride}
431
+ >
432
+ {node.children?.map((child, index) => {
433
+ if (child.type === "task_list_item") {
434
+ return (
435
+ <NodeRenderer
436
+ key={index}
437
+ node={child}
438
+ depth={depth + 1}
439
+ inListItem={true}
440
+ parentIsText={false}
441
+ />
442
+ );
443
+ }
444
+ return (
445
+ <ListItem
446
+ key={index}
447
+ index={index}
448
+ ordered={node.ordered ?? false}
449
+ start={node.start ?? 1}
450
+ >
451
+ <NodeRenderer
452
+ node={child}
453
+ depth={depth + 1}
454
+ inListItem={true}
455
+ parentIsText={false}
456
+ />
457
+ </ListItem>
458
+ );
459
+ })}
460
+ </List>
461
+ );
462
+
463
+ case "list_item":
464
+ return <>{renderChildren(node.children, true, false)}</>;
465
+
466
+ case "task_list_item":
467
+ return (
468
+ <TaskListItem checked={node.checked ?? false} style={nodeStyleOverride}>
469
+ {renderChildren(node.children, true, false)}
470
+ </TaskListItem>
471
+ );
472
+
473
+ case "table":
474
+ return (
475
+ <TableRenderer
476
+ node={node}
477
+ Renderer={NodeRenderer}
478
+ style={nodeStyleOverride}
479
+ />
480
+ );
481
+
482
+ case "table_head":
483
+ case "table_body":
484
+ case "table_row":
485
+ case "table_cell":
486
+ return null;
487
+
488
+ default:
489
+ return null;
490
+ }
491
+ };
492
+
493
+ const createBaseStyles = (theme: MarkdownTheme) =>
494
+ StyleSheet.create({
495
+ container: {
496
+ flex: 1,
497
+ },
498
+ document: {
499
+ flex: 1,
500
+ },
501
+ errorText: {
502
+ color: "#f87171",
503
+ fontSize: 14,
504
+ fontFamily: theme.fontFamilies.mono ?? "monospace",
505
+ },
506
+ text: {
507
+ color: theme.colors.text,
508
+ fontSize: theme.fontSizes.m,
509
+ lineHeight: theme.fontSizes.m * 1.6,
510
+ fontFamily: theme.fontFamilies.regular,
511
+ },
512
+ bold: {
513
+ fontWeight: "700",
514
+ },
515
+ italic: {
516
+ fontStyle: "italic",
517
+ },
518
+ strikethrough: {
519
+ textDecorationLine: "line-through",
520
+ },
521
+ });
@@ -0,0 +1,30 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import { View, StyleSheet, type ViewStyle } from "react-native";
3
+ import { useMarkdownContext } from "../MarkdownContext";
4
+
5
+ interface BlockquoteProps {
6
+ children: ReactNode;
7
+ style?: ViewStyle;
8
+ }
9
+
10
+ export const Blockquote: FC<BlockquoteProps> = ({ children, style }) => {
11
+ const { theme } = useMarkdownContext();
12
+ const styles = useMemo(
13
+ () =>
14
+ StyleSheet.create({
15
+ blockquote: {
16
+ borderLeftWidth: 4,
17
+ borderLeftColor: theme.colors.blockquote,
18
+ paddingLeft: theme.spacing.l,
19
+ marginVertical: theme.spacing.m,
20
+ backgroundColor: theme.colors.surfaceLight,
21
+ paddingVertical: theme.spacing.m,
22
+ paddingRight: theme.spacing.m,
23
+ borderRadius: theme.borderRadius.s,
24
+ },
25
+ }),
26
+ [theme]
27
+ );
28
+
29
+ return <View style={[styles.blockquote, style]}>{children}</View>;
30
+ };
@@ -0,0 +1,112 @@
1
+ import { ReactNode, useMemo, type FC } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ ScrollView,
7
+ Platform,
8
+ type ViewStyle,
9
+ type TextStyle,
10
+ } from "react-native";
11
+ import { useMarkdownContext } from "../MarkdownContext";
12
+ import type { MarkdownNode } from "../headless";
13
+ import { getTextContent } from "../headless";
14
+
15
+ interface CodeBlockProps {
16
+ language?: string;
17
+ content?: string;
18
+ node?: MarkdownNode;
19
+ style?: ViewStyle;
20
+ }
21
+
22
+ export const CodeBlock: FC<CodeBlockProps> = ({
23
+ language,
24
+ content,
25
+ node,
26
+ style,
27
+ }) => {
28
+ const { theme } = useMarkdownContext();
29
+
30
+ const displayContent = content ?? (node ? getTextContent(node) : "");
31
+
32
+ const styles = useMemo(
33
+ () =>
34
+ StyleSheet.create({
35
+ codeBlock: {
36
+ backgroundColor: theme.colors.codeBackground,
37
+ borderRadius: theme.borderRadius.m,
38
+ padding: theme.spacing.l,
39
+ marginVertical: theme.spacing.m,
40
+ borderWidth: 1,
41
+ borderColor: theme.colors.border,
42
+ },
43
+ codeLanguage: {
44
+ color: theme.colors.codeLanguage,
45
+ fontSize: theme.fontSizes.xs,
46
+ fontWeight: "600",
47
+ marginBottom: theme.spacing.s,
48
+ textTransform: "uppercase",
49
+ letterSpacing: 0.5,
50
+ },
51
+ codeBlockText: {
52
+ fontFamily:
53
+ theme.fontFamilies.mono ??
54
+ Platform.select({ ios: "Courier", android: "monospace" }),
55
+ fontSize: theme.fontSizes.s,
56
+ color: theme.colors.text,
57
+ lineHeight: theme.fontSizes.s * 1.5,
58
+ },
59
+ }),
60
+ [theme]
61
+ );
62
+
63
+ const showLanguage = theme.showCodeLanguage && language;
64
+
65
+ return (
66
+ <View style={[styles.codeBlock, style]}>
67
+ {showLanguage && <Text style={styles.codeLanguage}>{language}</Text>}
68
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
69
+ <Text style={styles.codeBlockText}>{displayContent}</Text>
70
+ </ScrollView>
71
+ </View>
72
+ );
73
+ };
74
+
75
+ interface InlineCodeProps {
76
+ content?: string;
77
+ node?: MarkdownNode;
78
+ children?: ReactNode;
79
+ style?: TextStyle;
80
+ }
81
+
82
+ export const InlineCode: FC<InlineCodeProps> = ({
83
+ content,
84
+ node,
85
+ children,
86
+ style,
87
+ }) => {
88
+ const { theme } = useMarkdownContext();
89
+
90
+ const displayContent =
91
+ content ?? children ?? (node ? getTextContent(node) : "");
92
+
93
+ const styles = useMemo(
94
+ () =>
95
+ StyleSheet.create({
96
+ codeInline: {
97
+ fontFamily:
98
+ theme.fontFamilies.mono ??
99
+ Platform.select({ ios: "Courier", android: "monospace" }),
100
+ fontSize: theme.fontSizes.s,
101
+ color: theme.colors.code,
102
+ backgroundColor: theme.colors.codeBackground,
103
+ paddingHorizontal: theme.spacing.xs,
104
+ paddingVertical: 2,
105
+ borderRadius: theme.borderRadius.s,
106
+ ...(Platform.OS === "android" && { includeFontPadding: false }),
107
+ },
108
+ }),
109
+ [theme]
110
+ );
111
+ return <Text style={[styles.codeInline, style]}>{displayContent}</Text>;
112
+ };