react-native-nitro-markdown 0.1.2 → 0.3.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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -119
  3. package/android/src/main/cpp/cpp-adapter.cpp +1 -1
  4. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +61 -0
  5. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +5 -1
  6. package/cpp/bindings/HybridMarkdownParser.cpp +2 -2
  7. package/cpp/bindings/HybridMarkdownParser.hpp +2 -2
  8. package/cpp/bindings/HybridMarkdownSession.cpp +0 -0
  9. package/cpp/core/MarkdownSessionCore.cpp +0 -0
  10. package/ios/HybridMarkdownSession.swift +64 -0
  11. package/lib/commonjs/MarkdownContext.js +17 -0
  12. package/lib/commonjs/MarkdownContext.js.map +1 -0
  13. package/lib/commonjs/MarkdownSession.js +11 -0
  14. package/lib/commonjs/MarkdownSession.js.map +1 -0
  15. package/lib/commonjs/default-markdown-renderer.js +217 -0
  16. package/lib/commonjs/default-markdown-renderer.js.map +1 -0
  17. package/lib/commonjs/headless.js +61 -0
  18. package/lib/commonjs/headless.js.map +1 -0
  19. package/lib/commonjs/index.js +220 -13
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/markdown-stream.js +32 -0
  22. package/lib/commonjs/markdown-stream.js.map +1 -0
  23. package/lib/commonjs/markdown.js +377 -0
  24. package/lib/commonjs/markdown.js.map +1 -0
  25. package/lib/commonjs/package.json +1 -0
  26. package/lib/commonjs/renderers/blockquote.js +36 -0
  27. package/lib/commonjs/renderers/blockquote.js.map +1 -0
  28. package/lib/commonjs/renderers/code.js +99 -0
  29. package/lib/commonjs/renderers/code.js.map +1 -0
  30. package/lib/commonjs/renderers/heading.js +63 -0
  31. package/lib/commonjs/renderers/heading.js.map +1 -0
  32. package/lib/commonjs/renderers/horizontal-rule.js +29 -0
  33. package/lib/commonjs/renderers/horizontal-rule.js.map +1 -0
  34. package/lib/commonjs/renderers/image.js +163 -0
  35. package/lib/commonjs/renderers/image.js.map +1 -0
  36. package/lib/commonjs/renderers/link.js +35 -0
  37. package/lib/commonjs/renderers/link.js.map +1 -0
  38. package/lib/commonjs/renderers/list.js +118 -0
  39. package/lib/commonjs/renderers/list.js.map +1 -0
  40. package/lib/commonjs/renderers/math.js +127 -0
  41. package/lib/commonjs/renderers/math.js.map +1 -0
  42. package/lib/commonjs/renderers/paragraph.js +39 -0
  43. package/lib/commonjs/renderers/paragraph.js.map +1 -0
  44. package/lib/commonjs/renderers/table.js +290 -0
  45. package/lib/commonjs/renderers/table.js.map +1 -0
  46. package/lib/commonjs/specs/MarkdownSession.nitro.js +6 -0
  47. package/lib/commonjs/specs/MarkdownSession.nitro.js.map +1 -0
  48. package/lib/commonjs/theme.js +191 -0
  49. package/lib/commonjs/theme.js.map +1 -0
  50. package/lib/commonjs/use-markdown-stream.js +71 -0
  51. package/lib/commonjs/use-markdown-stream.js.map +1 -0
  52. package/lib/module/MarkdownContext.js +12 -0
  53. package/lib/module/MarkdownContext.js.map +1 -0
  54. package/lib/module/MarkdownSession.js +7 -0
  55. package/lib/module/MarkdownSession.js.map +1 -0
  56. package/lib/module/default-markdown-renderer.js +212 -0
  57. package/lib/module/default-markdown-renderer.js.map +1 -0
  58. package/lib/module/headless.js +54 -0
  59. package/lib/module/headless.js.map +1 -0
  60. package/lib/module/index.js +18 -10
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/markdown-stream.js +27 -0
  63. package/lib/module/markdown-stream.js.map +1 -0
  64. package/lib/module/markdown.js +372 -0
  65. package/lib/module/markdown.js.map +1 -0
  66. package/lib/module/package.json +1 -0
  67. package/lib/module/renderers/blockquote.js +31 -0
  68. package/lib/module/renderers/blockquote.js.map +1 -0
  69. package/lib/module/renderers/code.js +93 -0
  70. package/lib/module/renderers/code.js.map +1 -0
  71. package/lib/module/renderers/heading.js +58 -0
  72. package/lib/module/renderers/heading.js.map +1 -0
  73. package/lib/module/renderers/horizontal-rule.js +24 -0
  74. package/lib/module/renderers/horizontal-rule.js.map +1 -0
  75. package/lib/module/renderers/image.js +158 -0
  76. package/lib/module/renderers/image.js.map +1 -0
  77. package/lib/module/renderers/link.js +30 -0
  78. package/lib/module/renderers/link.js.map +1 -0
  79. package/lib/module/renderers/list.js +111 -0
  80. package/lib/module/renderers/list.js.map +1 -0
  81. package/lib/module/renderers/math.js +121 -0
  82. package/lib/module/renderers/math.js.map +1 -0
  83. package/lib/module/renderers/paragraph.js +34 -0
  84. package/lib/module/renderers/paragraph.js.map +1 -0
  85. package/lib/module/renderers/table.js +285 -0
  86. package/lib/module/renderers/table.js.map +1 -0
  87. package/lib/module/specs/MarkdownSession.nitro.js +4 -0
  88. package/lib/module/specs/MarkdownSession.nitro.js.map +1 -0
  89. package/lib/module/theme.js +186 -0
  90. package/lib/module/theme.js.map +1 -0
  91. package/lib/module/use-markdown-stream.js +66 -0
  92. package/lib/module/use-markdown-stream.js.map +1 -0
  93. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -0
  94. package/lib/typescript/commonjs/MarkdownContext.d.ts +65 -0
  95. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -0
  96. package/lib/typescript/commonjs/MarkdownSession.d.ts +4 -0
  97. package/lib/typescript/commonjs/MarkdownSession.d.ts.map +1 -0
  98. package/lib/typescript/commonjs/default-markdown-renderer.d.ts +10 -0
  99. package/lib/typescript/commonjs/default-markdown-renderer.d.ts.map +1 -0
  100. package/lib/typescript/commonjs/headless.d.ts +57 -0
  101. package/lib/typescript/commonjs/headless.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/index.d.ts +22 -0
  103. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  104. package/lib/typescript/commonjs/markdown-stream.d.ts +15 -0
  105. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -0
  106. package/lib/typescript/commonjs/markdown.d.ts +47 -0
  107. package/lib/typescript/commonjs/markdown.d.ts.map +1 -0
  108. package/lib/typescript/commonjs/package.json +1 -0
  109. package/lib/typescript/commonjs/renderers/blockquote.d.ts +9 -0
  110. package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/renderers/code.d.ts +19 -0
  112. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/renderers/heading.d.ts +10 -0
  114. package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +8 -0
  116. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/renderers/image.d.ts +13 -0
  118. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/renderers/link.d.ts +10 -0
  120. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/renderers/list.d.ts +26 -0
  122. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/renderers/math.d.ts +14 -0
  124. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/renderers/paragraph.d.ts +10 -0
  126. package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/renderers/table.d.ts +12 -0
  128. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +12 -0
  130. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/theme.d.ts +65 -0
  132. package/lib/typescript/commonjs/theme.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/use-markdown-stream.d.ts +22 -0
  134. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -0
  135. package/lib/typescript/module/Markdown.nitro.d.ts +13 -0
  136. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -0
  137. package/lib/typescript/module/MarkdownContext.d.ts +65 -0
  138. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -0
  139. package/lib/typescript/module/MarkdownSession.d.ts +4 -0
  140. package/lib/typescript/module/MarkdownSession.d.ts.map +1 -0
  141. package/lib/typescript/module/default-markdown-renderer.d.ts +10 -0
  142. package/lib/typescript/module/default-markdown-renderer.d.ts.map +1 -0
  143. package/lib/typescript/module/headless.d.ts +57 -0
  144. package/lib/typescript/module/headless.d.ts.map +1 -0
  145. package/lib/typescript/module/index.d.ts +22 -0
  146. package/lib/typescript/module/index.d.ts.map +1 -0
  147. package/lib/typescript/module/markdown-stream.d.ts +15 -0
  148. package/lib/typescript/module/markdown-stream.d.ts.map +1 -0
  149. package/lib/typescript/module/markdown.d.ts +47 -0
  150. package/lib/typescript/module/markdown.d.ts.map +1 -0
  151. package/lib/typescript/module/package.json +1 -0
  152. package/lib/typescript/module/renderers/blockquote.d.ts +9 -0
  153. package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -0
  154. package/lib/typescript/module/renderers/code.d.ts +19 -0
  155. package/lib/typescript/module/renderers/code.d.ts.map +1 -0
  156. package/lib/typescript/module/renderers/heading.d.ts +10 -0
  157. package/lib/typescript/module/renderers/heading.d.ts.map +1 -0
  158. package/lib/typescript/module/renderers/horizontal-rule.d.ts +8 -0
  159. package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -0
  160. package/lib/typescript/module/renderers/image.d.ts +13 -0
  161. package/lib/typescript/module/renderers/image.d.ts.map +1 -0
  162. package/lib/typescript/module/renderers/link.d.ts +10 -0
  163. package/lib/typescript/module/renderers/link.d.ts.map +1 -0
  164. package/lib/typescript/module/renderers/list.d.ts +26 -0
  165. package/lib/typescript/module/renderers/list.d.ts.map +1 -0
  166. package/lib/typescript/module/renderers/math.d.ts +14 -0
  167. package/lib/typescript/module/renderers/math.d.ts.map +1 -0
  168. package/lib/typescript/module/renderers/paragraph.d.ts +10 -0
  169. package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -0
  170. package/lib/typescript/module/renderers/table.d.ts +12 -0
  171. package/lib/typescript/module/renderers/table.d.ts.map +1 -0
  172. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +12 -0
  173. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -0
  174. package/lib/typescript/module/theme.d.ts +65 -0
  175. package/lib/typescript/module/theme.d.ts.map +1 -0
  176. package/lib/typescript/module/use-markdown-stream.d.ts +22 -0
  177. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -0
  178. package/nitro.json +5 -2
  179. package/nitrogen/generated/android/NitroMarkdown+autolinking.cmake +3 -2
  180. package/nitrogen/generated/android/NitroMarkdown+autolinking.gradle +1 -1
  181. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +17 -5
  182. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +4 -4
  183. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  184. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +91 -0
  185. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +70 -0
  186. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/Func_void.kt +80 -0
  187. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +78 -0
  188. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/NitroMarkdownOnLoad.kt +1 -1
  189. package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +2 -2
  190. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +28 -4
  191. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +72 -6
  192. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Umbrella.hpp +11 -4
  193. package/nitrogen/generated/ios/NitroMarkdownAutolinking.mm +11 -3
  194. package/nitrogen/generated/ios/NitroMarkdownAutolinking.swift +16 -3
  195. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.cpp +11 -0
  196. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +108 -0
  197. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  198. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +59 -0
  199. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +190 -0
  200. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +3 -3
  201. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +4 -4
  202. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +26 -0
  203. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +67 -0
  204. package/nitrogen/generated/shared/c++/ParserOptions.hpp +22 -14
  205. package/package.json +45 -7
  206. package/react-native-nitro-markdown.podspec +5 -5
  207. package/src/MarkdownContext.ts +98 -0
  208. package/src/MarkdownSession.ts +8 -0
  209. package/src/default-markdown-renderer.tsx +261 -0
  210. package/src/headless.ts +116 -0
  211. package/src/index.ts +47 -60
  212. package/src/markdown-stream.tsx +32 -0
  213. package/src/markdown.tsx +497 -0
  214. package/src/renderers/blockquote.tsx +30 -0
  215. package/src/renderers/code.tsx +112 -0
  216. package/src/renderers/heading.tsx +66 -0
  217. package/src/renderers/horizontal-rule.tsx +23 -0
  218. package/src/renderers/image.tsx +175 -0
  219. package/src/renderers/link.tsx +33 -0
  220. package/src/renderers/list.tsx +131 -0
  221. package/src/renderers/math.tsx +141 -0
  222. package/src/renderers/paragraph.tsx +47 -0
  223. package/src/renderers/table.tsx +370 -0
  224. package/src/specs/MarkdownSession.nitro.ts +16 -0
  225. package/src/theme.ts +243 -0
  226. package/src/use-markdown-stream.ts +83 -0
  227. package/ios/NitroMarkdown-Bridging-Header.h +0 -14
  228. package/lib/commonjs/MarkdownJS.reference.js +0 -114
  229. package/lib/commonjs/MarkdownJS.reference.js.map +0 -1
  230. package/lib/module/MarkdownJS.reference.js +0 -107
  231. package/lib/module/MarkdownJS.reference.js.map +0 -1
  232. package/lib/typescript/Markdown.nitro.d.ts.map +0 -1
  233. package/lib/typescript/MarkdownJS.reference.d.ts +0 -33
  234. package/lib/typescript/MarkdownJS.reference.d.ts.map +0 -1
  235. package/lib/typescript/index.d.ts +0 -22
  236. package/lib/typescript/index.d.ts.map +0 -1
  237. package/src/MarkdownJS.reference.ts +0 -122
  238. /package/lib/typescript/{Markdown.nitro.d.ts → commonjs/Markdown.nitro.d.ts} +0 -0
package/src/index.ts CHANGED
@@ -1,65 +1,52 @@
1
- import { NitroModules } from 'react-native-nitro-modules';
2
- import type { MarkdownParser, ParserOptions } from './Markdown.nitro';
1
+ export * from "./headless";
3
2
 
4
- export type { ParserOptions } from './Markdown.nitro';
3
+ export { DefaultMarkdownRenderer } from "./default-markdown-renderer";
4
+ export { Markdown } from "./markdown";
5
+ export { MarkdownStream } from "./markdown-stream";
5
6
 
6
- export interface MarkdownNode {
7
- type:
8
- | 'document'
9
- | 'heading'
10
- | 'paragraph'
11
- | 'text'
12
- | 'bold'
13
- | 'italic'
14
- | 'strikethrough'
15
- | 'link'
16
- | 'image'
17
- | 'code_inline'
18
- | 'code_block'
19
- | 'blockquote'
20
- | 'horizontal_rule'
21
- | 'line_break'
22
- | 'soft_break'
23
- | 'table'
24
- | 'table_head'
25
- | 'table_body'
26
- | 'table_row'
27
- | 'table_cell'
28
- | 'list'
29
- | 'list_item'
30
- | 'task_list_item'
31
- | 'math_inline'
32
- | 'math_block'
33
- | 'html_block'
34
- | 'html_inline';
35
- content?: string;
36
- level?: number;
37
- href?: string;
38
- title?: string;
39
- alt?: string;
40
- language?: string;
41
- ordered?: boolean;
42
- start?: number;
43
- checked?: boolean;
44
- isHeader?: boolean;
45
- align?: string;
46
- children?: MarkdownNode[];
47
- }
7
+ export { useMarkdownContext, MarkdownContext } from "./MarkdownContext";
8
+ export type {
9
+ CustomRenderer,
10
+ CustomRenderers,
11
+ CustomRendererProps,
12
+ NodeRendererProps,
13
+ BaseCustomRendererProps,
14
+ EnhancedRendererProps,
15
+ HeadingRendererProps,
16
+ LinkRendererProps,
17
+ ImageRendererProps,
18
+ CodeBlockRendererProps,
19
+ InlineCodeRendererProps,
20
+ ListRendererProps,
21
+ TaskListItemRendererProps,
22
+ MarkdownContextValue,
23
+ } from "./MarkdownContext";
48
24
 
49
- export const MarkdownParserModule =
50
- NitroModules.createHybridObject<MarkdownParser>('MarkdownParser');
25
+ export {
26
+ defaultMarkdownTheme,
27
+ lightMarkdownTheme,
28
+ darkMarkdownTheme,
29
+ minimalMarkdownTheme,
30
+ mergeThemes,
31
+ } from "./theme";
32
+ export type {
33
+ MarkdownTheme,
34
+ PartialMarkdownTheme,
35
+ NodeStyleOverrides,
36
+ StylingStrategy,
37
+ } from "./theme";
51
38
 
52
- export function parseMarkdown(text: string): MarkdownNode {
53
- const jsonStr = MarkdownParserModule.parse(text);
54
- return JSON.parse(jsonStr) as MarkdownNode;
55
- }
39
+ export { Heading } from "./renderers/heading";
40
+ export { Paragraph } from "./renderers/paragraph";
41
+ export { Link } from "./renderers/link";
42
+ export { Blockquote } from "./renderers/blockquote";
43
+ export { HorizontalRule } from "./renderers/horizontal-rule";
44
+ export { CodeBlock, InlineCode } from "./renderers/code";
45
+ export { List, ListItem, TaskListItem } from "./renderers/list";
46
+ export { TableRenderer } from "./renderers/table";
47
+ export { Image } from "./renderers/image";
48
+ export { MathInline, MathBlock } from "./renderers/math";
56
49
 
57
- export function parseMarkdownWithOptions(
58
- text: string,
59
- options: ParserOptions
60
- ): MarkdownNode {
61
- const jsonStr = MarkdownParserModule.parseWithOptions(text, options);
62
- return JSON.parse(jsonStr) as MarkdownNode;
63
- }
64
-
65
- export { MarkdownParser };
50
+ export { createMarkdownSession } from "./MarkdownSession";
51
+ export type { MarkdownSession } from "./MarkdownSession";
52
+ export { useMarkdownSession, useStream } from "./use-markdown-stream";
@@ -0,0 +1,32 @@
1
+ import { useState, useEffect, type FC } from "react";
2
+ import { Markdown, type MarkdownProps } from "./markdown";
3
+ import type { MarkdownSession } from "./specs/MarkdownSession.nitro";
4
+
5
+ export interface MarkdownStreamProps extends Omit<MarkdownProps, "children"> {
6
+ /**
7
+ * The active MarkdownSession to stream content from.
8
+ */
9
+ session: MarkdownSession;
10
+ }
11
+
12
+ /**
13
+ * A component that renders streaming Markdown from a MarkdownSession.
14
+ * It efficiently subscribes to session updates to minimize parent re-renders.
15
+ */
16
+ export const MarkdownStream: FC<MarkdownStreamProps> = ({
17
+ session,
18
+ ...props
19
+ }) => {
20
+ const [text, setText] = useState(() => session.getAllText());
21
+
22
+ useEffect(() => {
23
+ // Ensure initial state is synced
24
+ setText(session.getAllText());
25
+
26
+ return session.addListener(() => {
27
+ setText(session.getAllText());
28
+ });
29
+ }, [session]);
30
+
31
+ return <Markdown {...props}>{text}</Markdown>;
32
+ };
@@ -0,0 +1,497 @@
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
+ getTextContent,
22
+ type MarkdownNode,
23
+ } from "./headless";
24
+ import type { ParserOptions } from "./Markdown.nitro";
25
+ import {
26
+ MarkdownContext,
27
+ useMarkdownContext,
28
+ type CustomRenderers,
29
+ type NodeRendererProps,
30
+ } from "./MarkdownContext";
31
+
32
+ import { Heading } from "./renderers/heading";
33
+ import { Paragraph } from "./renderers/paragraph";
34
+ import { Link } from "./renderers/link";
35
+ import { Blockquote } from "./renderers/blockquote";
36
+ import { HorizontalRule } from "./renderers/horizontal-rule";
37
+ import { CodeBlock, InlineCode } from "./renderers/code";
38
+ import { List, ListItem, TaskListItem } from "./renderers/list";
39
+ import { TableRenderer } from "./renderers/table";
40
+ import { Image } from "./renderers/image";
41
+ import { MathInline, MathBlock } from "./renderers/math";
42
+
43
+ export interface MarkdownProps {
44
+ /**
45
+ * The markdown string to parse and render.
46
+ */
47
+ children: string;
48
+ /**
49
+ * Parser options to enable GFM or Math support.
50
+ */
51
+ options?: ParserOptions;
52
+ /**
53
+ * Custom renderers for specific markdown node types.
54
+ * Each renderer receives { node, children, Renderer } plus type-specific props.
55
+ */
56
+ renderers?: CustomRenderers;
57
+ /**
58
+ * Custom theme to override default styles.
59
+ */
60
+ theme?: PartialMarkdownTheme;
61
+ /**
62
+ * Style overrides for specific node types.
63
+ * Applied after internal styles, allowing fine-grained customization.
64
+ * @example
65
+ * ```tsx
66
+ * <Markdown styles={{ heading: { color: 'red' }, code_block: { borderRadius: 0 } }}>
67
+ * {content}
68
+ * </Markdown>
69
+ * ```
70
+ */
71
+ styles?: NodeStyleOverrides;
72
+ /**
73
+ * Styling strategy for the component.
74
+ * - "opinionated": Full styling with colors, spacing, and visual effects (default)
75
+ * - "minimal": Bare minimum styling for a clean slate
76
+ */
77
+ stylingStrategy?: StylingStrategy;
78
+ /**
79
+ * Optional style for the container view.
80
+ */
81
+ style?: StyleProp<ViewStyle>;
82
+ }
83
+
84
+ export const Markdown: FC<MarkdownProps> = ({
85
+ children,
86
+ options,
87
+ renderers = {},
88
+ theme: userTheme,
89
+ styles: nodeStyles,
90
+ stylingStrategy = "opinionated",
91
+ style,
92
+ }) => {
93
+ const ast = useMemo(() => {
94
+ try {
95
+ if (options) {
96
+ return parseMarkdownWithOptions(children, options);
97
+ }
98
+ return parseMarkdown(children);
99
+ } catch (error) {
100
+ console.error("Failed to parse markdown:", error);
101
+ return null;
102
+ }
103
+ }, [children, options]);
104
+
105
+ const theme = useMemo(() => {
106
+ const base =
107
+ stylingStrategy === "minimal"
108
+ ? minimalMarkdownTheme
109
+ : defaultMarkdownTheme;
110
+ return mergeThemes(base, userTheme);
111
+ }, [userTheme, stylingStrategy]);
112
+
113
+ const baseStyles = useMemo(() => createBaseStyles(theme), [theme]);
114
+
115
+ if (!ast) {
116
+ return (
117
+ <View style={[baseStyles.container, style]}>
118
+ <Text style={baseStyles.errorText}>Error parsing markdown</Text>
119
+ </View>
120
+ );
121
+ }
122
+
123
+ return (
124
+ <MarkdownContext.Provider
125
+ value={{ renderers, theme, styles: nodeStyles, stylingStrategy }}
126
+ >
127
+ <View style={[baseStyles.container, style]}>
128
+ <NodeRenderer node={ast} depth={0} inListItem={false} />
129
+ </View>
130
+ </MarkdownContext.Provider>
131
+ );
132
+ };
133
+
134
+ const isInline = (type: MarkdownNode["type"]): boolean => {
135
+ return (
136
+ type === "text" ||
137
+ type === "bold" ||
138
+ type === "italic" ||
139
+ type === "strikethrough" ||
140
+ type === "link" ||
141
+ type === "code_inline" ||
142
+ type === "soft_break" ||
143
+ type === "line_break" ||
144
+ type === "html_inline" ||
145
+ type === "math_inline"
146
+ );
147
+ };
148
+
149
+ const NodeRenderer: FC<NodeRendererProps> = ({
150
+ node,
151
+ depth,
152
+ inListItem,
153
+ parentIsText = false,
154
+ }) => {
155
+ const { renderers, theme, styles: nodeStyles } = useMarkdownContext();
156
+ const baseStyles = useMemo(() => createBaseStyles(theme), [theme]);
157
+
158
+ const renderChildren = (
159
+ children?: MarkdownNode[],
160
+ childInListItem = false,
161
+ childParentIsText = false
162
+ ): ReactNode => {
163
+ if (!children || children.length === 0) return null;
164
+
165
+ const elements: ReactNode[] = [];
166
+ let currentInlineGroup: MarkdownNode[] = [];
167
+
168
+ const flushInlineGroup = () => {
169
+ if (currentInlineGroup.length > 0) {
170
+ const hasMath = currentInlineGroup.some(
171
+ (child) => child.type === "math_inline"
172
+ );
173
+
174
+ if (hasMath && !childParentIsText) {
175
+ elements.push(
176
+ <View
177
+ key={`inline-group-${elements.length}`}
178
+ style={{
179
+ flexDirection: "row",
180
+ alignItems: "center",
181
+ flexWrap: "wrap",
182
+ flexShrink: 1,
183
+ }}
184
+ >
185
+ {currentInlineGroup.map((n, idx) => (
186
+ <NodeRenderer
187
+ key={`${n.type}-${idx}`}
188
+ node={n}
189
+ depth={depth + 1}
190
+ inListItem={childInListItem}
191
+ parentIsText={false}
192
+ />
193
+ ))}
194
+ </View>
195
+ );
196
+ } else {
197
+ const Wrapper = childParentIsText ? Fragment : Text;
198
+ const wrapperProps = childParentIsText
199
+ ? {}
200
+ : { style: baseStyles.text };
201
+
202
+ elements.push(
203
+ <Wrapper key={`inline-group-${elements.length}`} {...wrapperProps}>
204
+ {currentInlineGroup.map((n, idx) => (
205
+ <NodeRenderer
206
+ key={`${n.type}-${idx}`}
207
+ node={n}
208
+ depth={depth + 1}
209
+ inListItem={childInListItem}
210
+ parentIsText={true}
211
+ />
212
+ ))}
213
+ </Wrapper>
214
+ );
215
+ }
216
+ currentInlineGroup = [];
217
+ }
218
+ };
219
+
220
+ children.forEach((child, index) => {
221
+ if (isInline(child.type)) {
222
+ currentInlineGroup.push(child);
223
+ } else {
224
+ flushInlineGroup();
225
+ elements.push(
226
+ <NodeRenderer
227
+ key={`${child.type}-${index}`}
228
+ node={child}
229
+ depth={depth + 1}
230
+ inListItem={childInListItem}
231
+ parentIsText={childParentIsText}
232
+ />
233
+ );
234
+ }
235
+ });
236
+
237
+ flushInlineGroup();
238
+ return elements;
239
+ };
240
+
241
+ const customRenderer = renderers[node.type];
242
+ if (customRenderer) {
243
+ const childrenRendered = renderChildren(
244
+ node.children,
245
+ inListItem,
246
+ parentIsText
247
+ );
248
+
249
+ const baseProps = {
250
+ node,
251
+ children: childrenRendered,
252
+ Renderer: NodeRenderer,
253
+ };
254
+
255
+ const enhancedProps = {
256
+ ...baseProps,
257
+ // Heading
258
+ ...(node.type === "heading" && {
259
+ level: (node.level ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,
260
+ }),
261
+ // Link
262
+ ...(node.type === "link" && { href: node.href ?? "", title: node.title }),
263
+ // Image
264
+ ...(node.type === "image" && {
265
+ url: node.href ?? "",
266
+ alt: node.alt,
267
+ title: node.title,
268
+ }),
269
+ // Code block
270
+ ...(node.type === "code_block" && {
271
+ content: getTextContent(node),
272
+ language: node.language,
273
+ }),
274
+ // Inline code
275
+ ...(node.type === "code_inline" && { content: node.content ?? "" }),
276
+ // List
277
+ ...(node.type === "list" && {
278
+ ordered: node.ordered ?? false,
279
+ start: node.start,
280
+ }),
281
+ // Task list item
282
+ ...(node.type === "task_list_item" && { checked: node.checked ?? false }),
283
+ };
284
+
285
+ const result = customRenderer(enhancedProps);
286
+ if (result !== undefined) return <>{result}</>;
287
+ }
288
+
289
+ const nodeStyleOverride = nodeStyles?.[node.type];
290
+
291
+ switch (node.type) {
292
+ case "document":
293
+ return (
294
+ <View style={[baseStyles.document, nodeStyleOverride]}>
295
+ {renderChildren(node.children, false, false)}
296
+ </View>
297
+ );
298
+
299
+ case "heading":
300
+ return (
301
+ <Heading level={node.level ?? 1} style={nodeStyleOverride}>
302
+ {renderChildren(node.children, inListItem, true)}
303
+ </Heading>
304
+ );
305
+
306
+ case "paragraph":
307
+ return (
308
+ <Paragraph inListItem={inListItem} style={nodeStyleOverride}>
309
+ {renderChildren(node.children, inListItem, false)}
310
+ </Paragraph>
311
+ );
312
+
313
+ case "text":
314
+ if (parentIsText) {
315
+ return <Text>{node.content}</Text>;
316
+ }
317
+ return (
318
+ <Text style={[baseStyles.text, nodeStyleOverride]}>{node.content}</Text>
319
+ );
320
+
321
+ case "bold":
322
+ return (
323
+ <Text style={[baseStyles.bold, nodeStyleOverride]}>
324
+ {renderChildren(node.children, inListItem, true)}
325
+ </Text>
326
+ );
327
+
328
+ case "italic":
329
+ return (
330
+ <Text style={[baseStyles.italic, nodeStyleOverride]}>
331
+ {renderChildren(node.children, inListItem, true)}
332
+ </Text>
333
+ );
334
+
335
+ case "strikethrough":
336
+ return (
337
+ <Text style={[baseStyles.strikethrough, nodeStyleOverride]}>
338
+ {renderChildren(node.children, inListItem, true)}
339
+ </Text>
340
+ );
341
+
342
+ case "link":
343
+ return (
344
+ <Link href={node.href ?? ""} style={nodeStyleOverride}>
345
+ {renderChildren(node.children, inListItem, true)}
346
+ </Link>
347
+ );
348
+
349
+ case "image":
350
+ return (
351
+ <Image
352
+ url={node.href ?? ""}
353
+ title={node.title}
354
+ alt={node.alt}
355
+ Renderer={NodeRenderer}
356
+ style={nodeStyleOverride}
357
+ />
358
+ );
359
+
360
+ case "code_inline":
361
+ return <InlineCode style={nodeStyleOverride}>{node.content}</InlineCode>;
362
+
363
+ case "code_block":
364
+ return (
365
+ <CodeBlock
366
+ language={node.language}
367
+ content={getTextContent(node)}
368
+ style={nodeStyleOverride}
369
+ />
370
+ );
371
+
372
+ case "blockquote":
373
+ return (
374
+ <Blockquote style={nodeStyleOverride}>
375
+ {renderChildren(node.children, inListItem, false)}
376
+ </Blockquote>
377
+ );
378
+
379
+ case "horizontal_rule":
380
+ return <HorizontalRule style={nodeStyleOverride} />;
381
+
382
+ case "line_break":
383
+ return <Text>{"\n"}</Text>;
384
+
385
+ case "soft_break":
386
+ return <Text> </Text>;
387
+
388
+ case "math_inline": {
389
+ let mathContent = getTextContent(node);
390
+ if (!mathContent) return null;
391
+ mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
392
+ return <MathInline content={mathContent} style={nodeStyleOverride} />;
393
+ }
394
+
395
+ case "math_block":
396
+ return (
397
+ <MathBlock content={getTextContent(node)} style={nodeStyleOverride} />
398
+ );
399
+
400
+ case "list":
401
+ return (
402
+ <List
403
+ ordered={node.ordered ?? false}
404
+ start={node.start}
405
+ depth={depth}
406
+ style={nodeStyleOverride}
407
+ >
408
+ {node.children?.map((child, index) => {
409
+ if (child.type === "task_list_item") {
410
+ return (
411
+ <NodeRenderer
412
+ key={index}
413
+ node={child}
414
+ depth={depth + 1}
415
+ inListItem={true}
416
+ parentIsText={false}
417
+ />
418
+ );
419
+ }
420
+ return (
421
+ <ListItem
422
+ key={index}
423
+ index={index}
424
+ ordered={node.ordered ?? false}
425
+ start={node.start ?? 1}
426
+ >
427
+ <NodeRenderer
428
+ node={child}
429
+ depth={depth + 1}
430
+ inListItem={true}
431
+ parentIsText={false}
432
+ />
433
+ </ListItem>
434
+ );
435
+ })}
436
+ </List>
437
+ );
438
+
439
+ case "list_item":
440
+ return <>{renderChildren(node.children, true, false)}</>;
441
+
442
+ case "task_list_item":
443
+ return (
444
+ <TaskListItem checked={node.checked ?? false} style={nodeStyleOverride}>
445
+ {renderChildren(node.children, true, false)}
446
+ </TaskListItem>
447
+ );
448
+
449
+ case "table":
450
+ return (
451
+ <TableRenderer
452
+ node={node}
453
+ Renderer={NodeRenderer}
454
+ style={nodeStyleOverride}
455
+ />
456
+ );
457
+
458
+ case "table_head":
459
+ case "table_body":
460
+ case "table_row":
461
+ case "table_cell":
462
+ return null;
463
+
464
+ default:
465
+ return null;
466
+ }
467
+ };
468
+
469
+ const createBaseStyles = (theme: MarkdownTheme) =>
470
+ StyleSheet.create({
471
+ container: {
472
+ flex: 1,
473
+ },
474
+ document: {
475
+ flex: 1,
476
+ },
477
+ errorText: {
478
+ color: "#f87171",
479
+ fontSize: 14,
480
+ fontFamily: theme.fontFamilies.mono ?? "monospace",
481
+ },
482
+ text: {
483
+ color: theme.colors.text,
484
+ fontSize: theme.fontSizes.m,
485
+ lineHeight: theme.fontSizes.m * 1.6,
486
+ fontFamily: theme.fontFamilies.regular,
487
+ },
488
+ bold: {
489
+ fontWeight: "700",
490
+ },
491
+ italic: {
492
+ fontStyle: "italic",
493
+ },
494
+ strikethrough: {
495
+ textDecorationLine: "line-through",
496
+ },
497
+ });
@@ -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
+ };