react-native-nitro-markdown 0.4.0 → 0.4.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.
- package/README.md +325 -270
- package/lib/commonjs/renderers/heading.js +3 -1
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/table.js +8 -4
- package/lib/commonjs/renderers/table.js.map +1 -1
- package/lib/commonjs/theme.js +3 -0
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/module/renderers/heading.js +3 -1
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/table.js +8 -4
- package/lib/module/renderers/table.js.map +1 -1
- package/lib/module/theme.js +3 -0
- package/lib/module/theme.js.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts +2 -1
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts +2 -1
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/renderers/heading.tsx +18 -1
- package/src/renderers/table.tsx +5 -3
- package/src/theme.ts +8 -2
package/README.md
CHANGED
|
@@ -3,77 +3,68 @@
|
|
|
3
3
|
<img src="./readme/stream-demo.gif" alt="react-native-nitro-markdown stream demo" width="300" />
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
|
-
# react-native-nitro-markdown
|
|
6
|
+
# react-native-nitro-markdown
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[](https://www.npmjs.com/package/react-native-nitro-markdown)
|
|
11
|
-
[](https://opensource.org/licenses/MIT)
|
|
12
|
-
[](https://nitro.margelo.com)
|
|
13
|
-
|
|
14
|
-
**react-native-nitro-markdown** is a high-performance Markdown parser built on **[md4c](https://github.com/mity/md4c)** (C++) and **[Nitro Modules](https://nitro.margelo.com)**. It parses complex Markdown, GFM, and LaTeX Math into a structured AST **synchronously** via JSI, bypassing the React Native Bridge entirely.
|
|
8
|
+
Fast, native Markdown parsing and rendering for React Native. Built on md4c (C++) and Nitro Modules (JSI), it turns Markdown into a typed AST synchronously and renders it with a batteries-included React Native renderer.
|
|
15
9
|
|
|
16
10
|
---
|
|
17
11
|
|
|
18
|
-
##
|
|
12
|
+
## Why this package exists
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
JavaScript Markdown parsers do a lot of work on the JS thread and often trigger GC pauses on large documents. This package moves parsing to native C++ and uses JSI to return the AST without going through the RN bridge.
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
| :-------------------------- | :--------- | :---------------- | :-------------------- |
|
|
24
|
-
| **🚀 Nitro Markdown (C++)** | **~29 ms** | **1x (Baseline)** | **~1 frame** (Smooth) |
|
|
25
|
-
| 📋 CommonMark (JS) | ~82 ms | 2.8x slower | ~5 frames (Jank) |
|
|
26
|
-
| 🏗️ Markdown-It (JS) | ~118 ms | 4.0x slower | ~7 frames (Jank) |
|
|
27
|
-
| 💨 Marked (JS) | ~400 ms | 13.5x slower | ~24 frames (Freeze) |
|
|
16
|
+
**How it works:**
|
|
28
17
|
|
|
29
|
-
|
|
18
|
+
Markdown string -> md4c C++ parser -> JSON AST -> React Native renderers
|
|
30
19
|
|
|
31
20
|
---
|
|
32
21
|
|
|
33
|
-
##
|
|
22
|
+
## Features
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
- Native C++ parser with JSI access (fast, synchronous parsing)
|
|
25
|
+
- Full renderer included (Markdown component)
|
|
26
|
+
- Headless API for custom renderers or processing
|
|
27
|
+
- GFM support (tables, strikethrough, task lists, autolinks)
|
|
28
|
+
- LaTeX math parsing (inline and block)
|
|
29
|
+
- Streaming support for token-by-token updates
|
|
30
|
+
- Theming and per-node style overrides
|
|
31
|
+
- Built-in renderers exposed for reuse
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
---
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
## Requirements
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
37
|
+
- React Native >= 0.75
|
|
38
|
+
- react-native-nitro-modules
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
> `npm install react-native-svg react-native-mathjax-svg`
|
|
40
|
+
Optional (for math rendering):
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
- react-native-mathjax-svg
|
|
43
|
+
- react-native-svg
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
53
48
|
|
|
54
|
-
|
|
49
|
+
Install the package and Nitro Modules:
|
|
55
50
|
|
|
56
51
|
```bash
|
|
57
52
|
bun add react-native-nitro-markdown react-native-nitro-modules
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
Optional math dependencies:
|
|
61
56
|
|
|
62
57
|
```bash
|
|
63
|
-
|
|
58
|
+
bun add react-native-mathjax-svg react-native-svg
|
|
64
59
|
```
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
**Standard**
|
|
61
|
+
iOS pods:
|
|
69
62
|
|
|
70
63
|
```bash
|
|
71
64
|
cd ios && pod install
|
|
72
65
|
```
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
If you are using Expo, you must run a **Prebuild** (Development Build) because this package contains native C++ code.
|
|
67
|
+
Expo (requires a development build):
|
|
77
68
|
|
|
78
69
|
```bash
|
|
79
70
|
bunx expo install react-native-nitro-markdown react-native-nitro-modules
|
|
@@ -82,11 +73,7 @@ bunx expo prebuild
|
|
|
82
73
|
|
|
83
74
|
---
|
|
84
75
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
### Option 1: Batteries Included (Simplest)
|
|
88
|
-
|
|
89
|
-
Use the `Markdown` component with clean, neutral styling that stays out of the way:
|
|
76
|
+
## Quick Start
|
|
90
77
|
|
|
91
78
|
```tsx
|
|
92
79
|
import { Markdown } from "react-native-nitro-markdown";
|
|
@@ -100,11 +87,52 @@ export function MyComponent() {
|
|
|
100
87
|
}
|
|
101
88
|
```
|
|
102
89
|
|
|
103
|
-
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Feature Guide
|
|
93
|
+
|
|
94
|
+
Start here (pick the approach that matches your use case):
|
|
95
|
+
|
|
96
|
+
- `Markdown` for static or preloaded content
|
|
97
|
+
- `MarkdownStream` + `useMarkdownSession` for streaming tokens
|
|
98
|
+
- `headless` API for custom rendering or data processing
|
|
99
|
+
|
|
100
|
+
### 1) Parsing options (GFM and Math)
|
|
101
|
+
|
|
102
|
+
Parsing options are passed using the `options` prop.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<Markdown options={{ gfm: true, math: true }}>{content}</Markdown>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `gfm` enables GitHub Flavored Markdown features supported by md4c.
|
|
109
|
+
- `math` enables `$...$` and `$$...$$` parsing into math nodes.
|
|
104
110
|
|
|
105
|
-
###
|
|
111
|
+
### 2) Styling and themes
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
You can override tokens using the `theme` prop. Only provide the tokens you want to change.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
117
|
+
|
|
118
|
+
const theme = {
|
|
119
|
+
colors: {
|
|
120
|
+
text: "#0f172a",
|
|
121
|
+
heading: "#0f172a",
|
|
122
|
+
link: "#2563eb",
|
|
123
|
+
codeBackground: "#f1f5f9",
|
|
124
|
+
},
|
|
125
|
+
showCodeLanguage: true,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
<Markdown theme={theme}>{content}</Markdown>;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If you use a custom heading font on Android and do not load a bold variant, set `headingWeight: "normal"` to avoid font fallback.
|
|
132
|
+
|
|
133
|
+
### 3) Per-node style overrides
|
|
134
|
+
|
|
135
|
+
Override the styles for specific node types with `styles`.
|
|
108
136
|
|
|
109
137
|
```tsx
|
|
110
138
|
<Markdown
|
|
@@ -114,13 +142,13 @@ Apply quick style overrides to specific node types without writing custom render
|
|
|
114
142
|
blockquote: { borderLeftColor: "#0ea5e9" },
|
|
115
143
|
}}
|
|
116
144
|
>
|
|
117
|
-
{
|
|
145
|
+
{content}
|
|
118
146
|
</Markdown>
|
|
119
147
|
```
|
|
120
148
|
|
|
121
|
-
###
|
|
149
|
+
### 4) Custom renderers
|
|
122
150
|
|
|
123
|
-
|
|
151
|
+
Provide a custom renderer for any node type. You get pre-mapped props for common values.
|
|
124
152
|
|
|
125
153
|
```tsx
|
|
126
154
|
import {
|
|
@@ -131,136 +159,67 @@ import {
|
|
|
131
159
|
} from "react-native-nitro-markdown";
|
|
132
160
|
|
|
133
161
|
const renderers = {
|
|
134
|
-
// Pre-mapped `level` prop - no need for node.level!
|
|
135
162
|
heading: ({ level, children }: HeadingRendererProps) => (
|
|
136
163
|
<MyHeading level={level}>{children}</MyHeading>
|
|
137
164
|
),
|
|
138
|
-
|
|
139
|
-
// Pre-mapped `content` and `language` - no getTextContent() needed!
|
|
140
165
|
code_block: ({ content, language }: CodeBlockRendererProps) => (
|
|
141
|
-
<CodeBlock
|
|
142
|
-
content={content}
|
|
143
|
-
language={language}
|
|
144
|
-
style={{ borderWidth: 2 }}
|
|
145
|
-
/>
|
|
166
|
+
<CodeBlock content={content} language={language} />
|
|
146
167
|
),
|
|
147
168
|
};
|
|
148
169
|
|
|
149
|
-
<Markdown renderers={renderers}
|
|
150
|
-
{markdown}
|
|
151
|
-
</Markdown>;
|
|
170
|
+
<Markdown renderers={renderers}>{content}</Markdown>;
|
|
152
171
|
```
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
Custom renderer behavior:
|
|
155
174
|
|
|
156
|
-
- `
|
|
157
|
-
- `
|
|
158
|
-
- `
|
|
159
|
-
- `code_block` → `content`, `language`
|
|
160
|
-
- `code_inline` → `content`
|
|
161
|
-
- `list` → `ordered`, `start`
|
|
162
|
-
- `task_list_item` → `checked`
|
|
175
|
+
- Return `undefined` to fall back to the built-in renderer.
|
|
176
|
+
- Return `null` to render nothing for that node.
|
|
177
|
+
- The `Renderer` prop lets you render nested children the same way the default renderer does.
|
|
163
178
|
|
|
164
|
-
|
|
179
|
+
Pre-mapped props by node type:
|
|
165
180
|
|
|
166
|
-
|
|
181
|
+
| Node type | Extra props |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| `heading` | `level` |
|
|
184
|
+
| `link` | `href`, `title` |
|
|
185
|
+
| `image` | `url`, `alt`, `title` |
|
|
186
|
+
| `code_block` | `content`, `language` |
|
|
187
|
+
| `code_inline` | `content` |
|
|
188
|
+
| `list` | `ordered`, `start` |
|
|
189
|
+
| `task_list_item` | `checked` |
|
|
167
190
|
|
|
168
|
-
|
|
169
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
191
|
+
### 5) Built-in renderers
|
|
170
192
|
|
|
171
|
-
|
|
172
|
-
colors: {
|
|
173
|
-
text: "#0f172a",
|
|
174
|
-
heading: "#0f172a",
|
|
175
|
-
link: "#0ea5e9",
|
|
176
|
-
codeBackground: "#e2e8f0",
|
|
177
|
-
},
|
|
178
|
-
showCodeLanguage: false,
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
<Markdown theme={myTheme}>{"# Custom Themed Markdown"}</Markdown>;
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Defaults live in `defaultMarkdownTheme` and are intentionally neutral so you can layer your own palette on top.
|
|
185
|
-
|
|
186
|
-
**Theme Properties:**
|
|
187
|
-
|
|
188
|
-
- `colors` - All color tokens (text, heading, link, code, codeBackground, codeLanguage, etc.)
|
|
189
|
-
- `spacing` - Spacing tokens (xs, s, m, l, xl)
|
|
190
|
-
- `fontSizes` - Font sizes (xs, s, m, l, xl, h1-h6)
|
|
191
|
-
- `fontFamilies` - Font families for regular, heading, and mono text
|
|
192
|
-
- `borderRadius` - Border radius tokens (s, m, l)
|
|
193
|
-
- `showCodeLanguage` - Show/hide code block language labels
|
|
194
|
-
|
|
195
|
-
### Option 5: Minimal Styling Strategy
|
|
196
|
-
|
|
197
|
-
Start with a clean slate using the `stylingStrategy` prop:
|
|
198
|
-
|
|
199
|
-
```tsx
|
|
200
|
-
<Markdown stylingStrategy="minimal" theme={myLightTheme}>
|
|
201
|
-
{content}
|
|
202
|
-
</Markdown>
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
This zeros out all spacing and removes opinionated colors, letting you build up from scratch.
|
|
206
|
-
|
|
207
|
-
### Option 6: Style Props on Individual Renderers
|
|
208
|
-
|
|
209
|
-
All built-in renderers accept a `style` prop for fine-grained overrides:
|
|
193
|
+
All built-in renderers are exported so you can reuse them in custom renderers.
|
|
210
194
|
|
|
211
195
|
```tsx
|
|
212
196
|
import { Heading, CodeBlock, InlineCode } from "react-native-nitro-markdown";
|
|
213
|
-
|
|
214
|
-
// Works in custom renderers
|
|
215
|
-
<Heading level={1} style={{ color: "hotpink" }}>Title</Heading>
|
|
216
|
-
<CodeBlock content={code} style={{ borderRadius: 0 }} />
|
|
217
|
-
<InlineCode style={{ backgroundColor: "#ff0" }}>code</InlineCode>
|
|
218
197
|
```
|
|
219
198
|
|
|
220
|
-
|
|
199
|
+
Available renderers:
|
|
221
200
|
|
|
222
|
-
|
|
201
|
+
- `Heading`
|
|
202
|
+
- `Paragraph`
|
|
203
|
+
- `Link`
|
|
204
|
+
- `Blockquote`
|
|
205
|
+
- `HorizontalRule`
|
|
206
|
+
- `CodeBlock`
|
|
207
|
+
- `InlineCode`
|
|
208
|
+
- `List`
|
|
209
|
+
- `ListItem`
|
|
210
|
+
- `TaskListItem`
|
|
211
|
+
- `TableRenderer`
|
|
212
|
+
- `Image`
|
|
213
|
+
- `MathInline`
|
|
214
|
+
- `MathBlock`
|
|
223
215
|
|
|
224
|
-
|
|
225
|
-
// Before: Manual extraction required
|
|
226
|
-
code_block: ({ node }) => (
|
|
227
|
-
<CodeBlock content={getTextContent(node)} language={node.language} />
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// After: Just pass the node
|
|
231
|
-
code_block: ({ node }) => <CodeBlock node={node} />;
|
|
232
|
-
|
|
233
|
-
// Or use the pre-mapped content prop (recommended)
|
|
234
|
-
code_block: ({ content, language }) => (
|
|
235
|
-
<CodeBlock content={content} language={language} />
|
|
236
|
-
);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Option 8: Headless (Minimal Bundle)
|
|
216
|
+
### 6) Streaming (LLM tokens)
|
|
240
217
|
|
|
241
|
-
|
|
218
|
+
Use `MarkdownStream` plus `useMarkdownSession` to stream updates efficiently.
|
|
242
219
|
|
|
243
220
|
```tsx
|
|
244
|
-
import {
|
|
245
|
-
|
|
246
|
-
getTextContent,
|
|
247
|
-
getFlattenedText,
|
|
248
|
-
} from "react-native-nitro-markdown/headless";
|
|
249
|
-
|
|
250
|
-
const ast = parseMarkdown("# Hello World");
|
|
251
|
-
const text = getTextContent(ast); // "Hello World"
|
|
252
|
-
const fullText = getFlattenedText(ast); // "Hello World\n\n" (Normalized with line breaks)
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Option 9: High-Performance Streaming (LLMs)
|
|
256
|
-
|
|
257
|
-
When streaming text token-by-token (e.g., from ChatGPT or Gemini), you should batch UI updates to avoid re-rendering on every token:
|
|
258
|
-
|
|
259
|
-
```tsx
|
|
260
|
-
import {
|
|
261
|
-
MarkdownStream,
|
|
262
|
-
useMarkdownSession,
|
|
263
|
-
} from "react-native-nitro-markdown";
|
|
221
|
+
import { useEffect } from "react";
|
|
222
|
+
import { MarkdownStream, useMarkdownSession } from "react-native-nitro-markdown";
|
|
264
223
|
|
|
265
224
|
export function AIResponseStream() {
|
|
266
225
|
const session = useMarkdownSession();
|
|
@@ -274,7 +233,6 @@ export function AIResponseStream() {
|
|
|
274
233
|
<MarkdownStream
|
|
275
234
|
session={session.getSession()}
|
|
276
235
|
options={{ gfm: true }}
|
|
277
|
-
updateIntervalMs={60}
|
|
278
236
|
updateStrategy="raf"
|
|
279
237
|
useTransitionUpdates
|
|
280
238
|
/>
|
|
@@ -282,183 +240,280 @@ export function AIResponseStream() {
|
|
|
282
240
|
}
|
|
283
241
|
```
|
|
284
242
|
|
|
285
|
-
|
|
286
|
-
- `updateStrategy="raf"` for smooth, frame-aligned updates
|
|
287
|
-
- `updateIntervalMs={50–100}` when using `updateStrategy="interval"`
|
|
288
|
-
- Avoid per-token UI updates by batching token appends
|
|
243
|
+
Recommended streaming defaults:
|
|
289
244
|
|
|
290
|
-
|
|
245
|
+
- `updateStrategy="raf"` for frame-aligned updates
|
|
246
|
+
- `updateIntervalMs` between `50` and `100` when using interval strategy
|
|
247
|
+
- Avoid per-token UI updates by batching appends
|
|
291
248
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
249
|
+
### 7) Headless parsing
|
|
250
|
+
|
|
251
|
+
Use the headless entry when you only need the AST or want to build your own renderer.
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import {
|
|
255
|
+
parseMarkdown,
|
|
256
|
+
parseMarkdownWithOptions,
|
|
257
|
+
getTextContent,
|
|
258
|
+
getFlattenedText,
|
|
259
|
+
} from "react-native-nitro-markdown/headless";
|
|
297
260
|
|
|
298
|
-
|
|
261
|
+
const ast = parseMarkdown("# Hello World");
|
|
262
|
+
const text = getTextContent(ast);
|
|
263
|
+
const normalized = getFlattenedText(ast);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 8) Plain text extraction
|
|
299
267
|
|
|
300
|
-
|
|
268
|
+
If you are already using the `Markdown` component, you can get the plain text during parse.
|
|
301
269
|
|
|
302
270
|
```tsx
|
|
303
271
|
<Markdown
|
|
304
272
|
onParseComplete={(result) => {
|
|
305
|
-
console.log(result.text);
|
|
306
|
-
console.log(result.ast); // Full AST
|
|
273
|
+
console.log(result.text);
|
|
307
274
|
}}
|
|
308
275
|
>
|
|
309
|
-
{
|
|
276
|
+
{content}
|
|
310
277
|
</Markdown>
|
|
311
278
|
```
|
|
312
279
|
|
|
280
|
+
### 9) Tables, images, and math
|
|
281
|
+
|
|
282
|
+
- Tables are rendered with a horizontal scroll view and measured column widths.
|
|
283
|
+
- Images use React Native `Image` and try to preserve the real aspect ratio.
|
|
284
|
+
- Math nodes render with `react-native-mathjax-svg` if installed; otherwise they fall back to a code-style look.
|
|
285
|
+
|
|
313
286
|
---
|
|
314
287
|
|
|
315
|
-
##
|
|
288
|
+
## Common Recipes
|
|
316
289
|
|
|
317
|
-
|
|
290
|
+
### Open links with custom behavior
|
|
318
291
|
|
|
319
292
|
```tsx
|
|
320
|
-
import {
|
|
321
|
-
|
|
322
|
-
MarkdownContext,
|
|
323
|
-
} from "react-native-nitro-markdown";
|
|
324
|
-
|
|
325
|
-
const MyCustomRenderer = ({ children }) => {
|
|
326
|
-
const { theme, stylingStrategy } = useMarkdownContext();
|
|
293
|
+
import { Markdown, type LinkRendererProps } from "react-native-nitro-markdown";
|
|
294
|
+
import { Text, Linking } from "react-native";
|
|
327
295
|
|
|
328
|
-
|
|
296
|
+
const renderers = {
|
|
297
|
+
link: ({ href, children }: LinkRendererProps) => (
|
|
298
|
+
<Text
|
|
299
|
+
style={{ textDecorationLine: "underline" }}
|
|
300
|
+
onPress={async () => {
|
|
301
|
+
if (href && (await Linking.canOpenURL(href))) {
|
|
302
|
+
Linking.openURL(href);
|
|
303
|
+
}
|
|
304
|
+
}}
|
|
305
|
+
>
|
|
306
|
+
{children}
|
|
307
|
+
</Text>
|
|
308
|
+
),
|
|
329
309
|
};
|
|
330
|
-
```
|
|
331
310
|
|
|
332
|
-
|
|
311
|
+
<Markdown renderers={renderers}>{content}</Markdown>;
|
|
312
|
+
```
|
|
333
313
|
|
|
334
|
-
|
|
314
|
+
### Custom image renderer (placeholder + fixed height)
|
|
335
315
|
|
|
336
316
|
```tsx
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
parseMarkdown,
|
|
340
|
-
parseMarkdownWithOptions,
|
|
341
|
-
getTextContent,
|
|
342
|
-
getFlattenedText,
|
|
343
|
-
} from "./headless";
|
|
317
|
+
import { Markdown, type ImageRendererProps } from "react-native-nitro-markdown";
|
|
318
|
+
import { View, Image, Text } from "react-native";
|
|
344
319
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
320
|
+
const renderers = {
|
|
321
|
+
image: ({ url, title, alt }: ImageRendererProps) => (
|
|
322
|
+
<View>
|
|
323
|
+
<Image source={{ uri: url }} style={{ height: 220, borderRadius: 12 }} />
|
|
324
|
+
{(title || alt) && (
|
|
325
|
+
<Text style={{ marginTop: 6, opacity: 0.6 }}>{title || alt}</Text>
|
|
326
|
+
)}
|
|
327
|
+
</View>
|
|
328
|
+
),
|
|
350
329
|
};
|
|
330
|
+
```
|
|
351
331
|
|
|
352
|
-
|
|
353
|
-
export { useMarkdownContext, MarkdownContext };
|
|
332
|
+
### Render HTML nodes as code (opt-in)
|
|
354
333
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
HorizontalRule,
|
|
362
|
-
CodeBlock,
|
|
363
|
-
InlineCode,
|
|
364
|
-
List,
|
|
365
|
-
ListItem,
|
|
366
|
-
TaskListItem,
|
|
367
|
-
TableRenderer,
|
|
368
|
-
Image,
|
|
369
|
-
MathInline,
|
|
370
|
-
MathBlock,
|
|
334
|
+
```tsx
|
|
335
|
+
import { Markdown, CodeBlock, InlineCode } from "react-native-nitro-markdown";
|
|
336
|
+
|
|
337
|
+
const renderers = {
|
|
338
|
+
html_block: ({ node }) => <CodeBlock content={node.content ?? \"\"} />,
|
|
339
|
+
html_inline: ({ node }) => <InlineCode content={node.content ?? \"\"} />,
|
|
371
340
|
};
|
|
372
341
|
```
|
|
373
342
|
|
|
374
|
-
|
|
343
|
+
### Minimal styling + custom palette
|
|
375
344
|
|
|
376
|
-
|
|
345
|
+
```tsx
|
|
346
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
377
347
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
348
|
+
<Markdown
|
|
349
|
+
stylingStrategy="minimal"
|
|
350
|
+
theme={{ colors: { text: "#e2e8f0", link: "#38bdf8" } }}
|
|
351
|
+
>
|
|
352
|
+
{content}
|
|
353
|
+
</Markdown>;
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Build a search index (headless)
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
import { parseMarkdown, getFlattenedText } from "react-native-nitro-markdown/headless";
|
|
360
|
+
|
|
361
|
+
const ast = parseMarkdown(content);
|
|
362
|
+
const plainText = getFlattenedText(ast);
|
|
363
|
+
```
|
|
384
364
|
|
|
385
365
|
---
|
|
386
366
|
|
|
387
|
-
|
|
367
|
+
## API Reference
|
|
388
368
|
|
|
389
|
-
|
|
369
|
+
### Markdown component
|
|
390
370
|
|
|
391
|
-
```
|
|
392
|
-
import {
|
|
371
|
+
```tsx
|
|
372
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
373
|
+
```
|
|
393
374
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
375
|
+
Props:
|
|
376
|
+
|
|
377
|
+
| Prop | Type | Default | Description |
|
|
378
|
+
| --- | --- | --- | --- |
|
|
379
|
+
| `children` | `string` | required | Markdown string to parse and render |
|
|
380
|
+
| `options` | `{ gfm?: boolean; math?: boolean }` | `undefined` | Parser options |
|
|
381
|
+
| `renderers` | `CustomRenderers` | `{}` | Custom renderers by node type |
|
|
382
|
+
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
|
|
383
|
+
| `styles` | `NodeStyleOverrides` | `undefined` | Per-node style overrides |
|
|
384
|
+
| `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Styling baseline |
|
|
385
|
+
| `style` | `StyleProp<ViewStyle>` | `undefined` | Container style |
|
|
386
|
+
| `onParsingInProgress` | `() => void` | `undefined` | Called when parsing starts |
|
|
387
|
+
| `onParseComplete` | `(result) => void` | `undefined` | Called with `{ raw, ast, text }` |
|
|
388
|
+
|
|
389
|
+
### MarkdownStream
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import { MarkdownStream } from "react-native-nitro-markdown";
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Props (in addition to all `Markdown` props except `children`):
|
|
396
|
+
|
|
397
|
+
| Prop | Type | Default | Description |
|
|
398
|
+
| --- | --- | --- | --- |
|
|
399
|
+
| `session` | `MarkdownSession` | required | Active session for streaming text |
|
|
400
|
+
| `updateIntervalMs` | `number` | `50` | Throttle interval for updates |
|
|
401
|
+
| `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Update strategy |
|
|
402
|
+
| `useTransitionUpdates` | `boolean` | `false` | Use React transitions |
|
|
403
|
+
|
|
404
|
+
### Hooks and sessions
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import { useMarkdownSession, createMarkdownSession, useStream } from "react-native-nitro-markdown";
|
|
398
408
|
```
|
|
399
409
|
|
|
400
|
-
|
|
410
|
+
- `useMarkdownSession()` returns a managed session and helpers: `getSession`, `setIsStreaming`, `stop`, `clear`, `setHighlight`.
|
|
411
|
+
- `createMarkdownSession()` creates a session without React hooks.
|
|
412
|
+
- `useStream(timestamps)` adds a simple sync helper for time-based streaming.
|
|
401
413
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
414
|
+
### Headless API
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
import { parseMarkdown, parseMarkdownWithOptions, getTextContent, getFlattenedText } from "react-native-nitro-markdown/headless";
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Theme utilities
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import {
|
|
424
|
+
defaultMarkdownTheme,
|
|
425
|
+
minimalMarkdownTheme,
|
|
426
|
+
mergeThemes,
|
|
427
|
+
} from "react-native-nitro-markdown";
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Types
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
import type {
|
|
434
|
+
CustomRenderers,
|
|
435
|
+
NodeStyleOverrides,
|
|
436
|
+
MarkdownTheme,
|
|
437
|
+
PartialMarkdownTheme,
|
|
438
|
+
} from "react-native-nitro-markdown";
|
|
439
|
+
```
|
|
406
440
|
|
|
407
441
|
---
|
|
408
442
|
|
|
409
|
-
##
|
|
443
|
+
## Supported Node Types
|
|
410
444
|
|
|
411
|
-
|
|
445
|
+
You can customize any of these node types with `renderers` or `styles`.
|
|
412
446
|
|
|
413
|
-
|
|
447
|
+
`document`, `heading`, `paragraph`, `text`, `bold`, `italic`, `strikethrough`, `link`, `image`, `code_inline`, `code_block`, `blockquote`, `horizontal_rule`, `line_break`, `soft_break`, `table`, `table_head`, `table_body`, `table_row`, `table_cell`, `list`, `list_item`, `task_list_item`, `math_inline`, `math_block`, `html_block`, `html_inline`
|
|
448
|
+
|
|
449
|
+
Note: `html_inline` and `html_block` are parsed but not rendered by default.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## AST Shape
|
|
454
|
+
|
|
455
|
+
The headless API returns a typed AST. This is the core shape used by the renderer.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
414
458
|
export interface MarkdownNode {
|
|
415
|
-
type:
|
|
459
|
+
type:
|
|
460
|
+
| "document"
|
|
461
|
+
| "heading"
|
|
462
|
+
| "paragraph"
|
|
463
|
+
| "text"
|
|
464
|
+
| "bold"
|
|
465
|
+
| "italic"
|
|
466
|
+
| "strikethrough"
|
|
467
|
+
| "link"
|
|
468
|
+
| "image"
|
|
469
|
+
| "code_inline"
|
|
470
|
+
| "code_block"
|
|
471
|
+
| "blockquote"
|
|
472
|
+
| "horizontal_rule"
|
|
473
|
+
| "line_break"
|
|
474
|
+
| "soft_break"
|
|
475
|
+
| "table"
|
|
476
|
+
| "table_head"
|
|
477
|
+
| "table_body"
|
|
478
|
+
| "table_row"
|
|
479
|
+
| "table_cell"
|
|
480
|
+
| "list"
|
|
481
|
+
| "list_item"
|
|
482
|
+
| "task_list_item"
|
|
483
|
+
| "math_inline"
|
|
484
|
+
| "math_block"
|
|
485
|
+
| "html_block"
|
|
486
|
+
| "html_inline";
|
|
416
487
|
content?: string;
|
|
417
488
|
children?: MarkdownNode[];
|
|
418
489
|
level?: number;
|
|
419
490
|
href?: string;
|
|
420
|
-
|
|
491
|
+
title?: string;
|
|
492
|
+
alt?: string;
|
|
421
493
|
language?: string;
|
|
422
|
-
|
|
494
|
+
ordered?: boolean;
|
|
495
|
+
start?: number;
|
|
496
|
+
checked?: boolean;
|
|
497
|
+
align?: string;
|
|
423
498
|
isHeader?: boolean;
|
|
424
499
|
}
|
|
425
500
|
```
|
|
426
501
|
|
|
427
502
|
---
|
|
428
503
|
|
|
429
|
-
##
|
|
504
|
+
## Troubleshooting
|
|
430
505
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
case 'math_inline':
|
|
437
|
-
return <MathView math={node.content} style={styles.math} />;
|
|
438
|
-
case 'math_block':
|
|
439
|
-
return <MathView math={node.content} style={styles.mathBlock} />;
|
|
440
|
-
```
|
|
506
|
+
- Math renders as plain text: install `react-native-mathjax-svg` and `react-native-svg`.
|
|
507
|
+
- iOS build errors: run `pod install` after installing dependencies.
|
|
508
|
+
- Expo: you must use a development build (`expo prebuild` + `expo run`), not Expo Go.
|
|
509
|
+
- Android heading font looks wrong: set `headingWeight: "normal"` when your font has no bold variant.
|
|
441
510
|
|
|
442
511
|
---
|
|
443
512
|
|
|
444
|
-
##
|
|
445
|
-
|
|
446
|
-
| Metric | Size |
|
|
447
|
-
| :------------------- | :------ |
|
|
448
|
-
| **Packed (tarball)** | ~75 kB |
|
|
449
|
-
| **Unpacked** | ~325 kB |
|
|
450
|
-
| **Total files** | 55 |
|
|
513
|
+
## Contributing
|
|
451
514
|
|
|
452
|
-
|
|
515
|
+
See `CONTRIBUTING.md` for the workflow and development commands.
|
|
453
516
|
|
|
454
|
-
##
|
|
455
|
-
|
|
456
|
-
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
457
|
-
|
|
458
|
-
## 📄 License
|
|
517
|
+
## License
|
|
459
518
|
|
|
460
519
|
MIT
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
Built with ❤️ using [Nitro Modules](https://nitro.margelo.com) and [md4c](https://github.com/mity/md4c).
|