react-native-nitro-markdown 0.7.0 → 0.7.1
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 +128 -518
- package/lib/commonjs/MarkdownSession.js +6 -2
- package/lib/commonjs/MarkdownSession.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +8 -6
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/use-markdown-stream.js +23 -4
- package/lib/commonjs/use-markdown-stream.js.map +1 -1
- package/lib/module/MarkdownSession.js +6 -2
- package/lib/module/MarkdownSession.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +8 -6
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/use-markdown-stream.js +22 -5
- package/lib/module/use-markdown-stream.js.map +1 -1
- package/lib/typescript/commonjs/MarkdownSession.d.ts +1 -1
- package/lib/typescript/commonjs/MarkdownSession.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts +2 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-markdown-stream.d.ts +4 -1
- package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/MarkdownSession.d.ts +1 -1
- package/lib/typescript/module/MarkdownSession.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts +2 -1
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/use-markdown-stream.d.ts +4 -1
- package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/MarkdownSession.ts +9 -2
- package/src/index.ts +1 -0
- package/src/markdown-stream.tsx +12 -7
- package/src/use-markdown-stream.ts +53 -13
package/README.md
CHANGED
|
@@ -1,66 +1,57 @@
|
|
|
1
1
|
# react-native-nitro-markdown
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/react-native-nitro-markdown)
|
|
4
|
+
[](https://github.com/JoaoPauloCMarra/react-native-nitro-markdown/blob/main/LICENSE)
|
|
5
|
+
[](https://reactnative.dev/)
|
|
6
|
+
[](https://expo.dev/)
|
|
7
|
+
[](https://nitro.margelo.com/)
|
|
8
|
+
|
|
9
|
+
Markdown parsing, rendering, streaming, and headless AST access for React
|
|
10
|
+
Native, powered by md4c and Nitro Modules.
|
|
11
|
+
|
|
12
|
+
Use it when you need GitHub-flavored Markdown, custom renderers, streaming
|
|
13
|
+
chat/LLM output, syntax highlighting, math rendering, or native headless parsing
|
|
14
|
+
without building your own renderer pipeline.
|
|
7
15
|
|
|
8
16
|
<p align="center">
|
|
9
17
|
<img src="https://raw.githubusercontent.com/JoaoPauloCMarra/react-native-nitro-markdown/main/readme/demo.gif" alt="react-native-nitro-markdown demo" width="400" />
|
|
10
18
|
</p>
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## Features
|
|
20
|
+
## Install
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- **Math rendering** -- inline and block LaTeX via native RaTeX
|
|
20
|
-
- **Opt-in HTML AST nodes** -- preserve raw HTML as `html_inline` / `html_block` for custom renderers
|
|
21
|
-
- **Large document support** -- top-level block virtualization and cached renderer styles
|
|
22
|
-
- **Code highlighting** -- built-in JS/TS, Python, and Bash tokenizer with custom highlighter support
|
|
23
|
-
- **Headless API** -- parse markdown and extract text without any UI
|
|
24
|
-
- **Streaming** -- incremental rendering for chat/LLM token streams
|
|
25
|
-
- **Customizable** -- themes, per-node style overrides, custom renderers, AST transforms, plugin pipeline
|
|
26
|
-
|
|
27
|
-
## Requirements
|
|
22
|
+
```sh
|
|
23
|
+
bun add react-native-nitro-markdown react-native-nitro-modules ratex-react-native
|
|
24
|
+
```
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|---|---|
|
|
31
|
-
| React Native | `>=0.75.0` |
|
|
32
|
-
| react-native-nitro-modules | `>=0.35.7` |
|
|
33
|
-
| ratex-react-native | `>=0.1.4` |
|
|
26
|
+
For Expo development builds:
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
```sh
|
|
29
|
+
bunx expo install react-native-nitro-markdown react-native-nitro-modules ratex-react-native
|
|
30
|
+
bunx expo prebuild
|
|
31
|
+
```
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
For bare React Native apps:
|
|
38
34
|
|
|
39
|
-
```
|
|
40
|
-
npm install react-native-nitro-markdown react-native-nitro-modules ratex-react-native
|
|
35
|
+
```sh
|
|
41
36
|
cd ios && pod install
|
|
42
37
|
```
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
Expo Go cannot load Nitro native modules. Use an Expo development build or a
|
|
40
|
+
bare app.
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
bun add react-native-nitro-markdown react-native-nitro-modules ratex-react-native
|
|
48
|
-
cd ios && pod install
|
|
49
|
-
```
|
|
42
|
+
## Expo Config
|
|
50
43
|
|
|
51
|
-
|
|
44
|
+
No package config plugin is required for `react-native-nitro-markdown`.
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
bunx expo
|
|
55
|
-
bunx expo prebuild
|
|
56
|
-
```
|
|
46
|
+
Use your normal Expo app config, install the native dependencies, then run
|
|
47
|
+
`bunx expo prebuild` after adding or upgrading the package.
|
|
57
48
|
|
|
58
49
|
## Quick Start
|
|
59
50
|
|
|
60
51
|
```tsx
|
|
61
52
|
import { Markdown } from "react-native-nitro-markdown";
|
|
62
53
|
|
|
63
|
-
export function
|
|
54
|
+
export function Article() {
|
|
64
55
|
return (
|
|
65
56
|
<Markdown options={{ gfm: true }}>
|
|
66
57
|
{"# Hello\nThis is **native** markdown."}
|
|
@@ -69,534 +60,153 @@ export function Example() {
|
|
|
69
60
|
}
|
|
70
61
|
```
|
|
71
62
|
|
|
72
|
-
##
|
|
73
|
-
|
|
74
|
-
### Parser Options
|
|
75
|
-
|
|
76
|
-
`ParserOptions` controls native parser extensions. Defaults are conservative for HTML and feature-complete for Markdown extensions.
|
|
77
|
-
|
|
78
|
-
| Option | Default | Enables | Notes |
|
|
79
|
-
|---|---:|---|---|
|
|
80
|
-
| `gfm` | `true` | Tables, strikethrough, task lists, permissive autolinks | Set `false` for stricter CommonMark-style parsing |
|
|
81
|
-
| `math` | `true` | Inline and display LaTeX spans | Rendered with RaTeX; falls back to styled plain text if the peer dependency is unavailable at runtime |
|
|
82
|
-
| `html` | `false` | `html_inline` and `html_block` AST nodes | Raw HTML is not rendered by default; provide custom renderers |
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
<Markdown options={{ gfm: true, math: true, html: false }}>
|
|
86
|
-
{content}
|
|
87
|
-
</Markdown>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### `<Markdown>`
|
|
91
|
-
|
|
92
|
-
The main component. Parses a markdown string and renders it.
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
| Prop | Type | Default | Description |
|
|
99
|
-
|---|---|---|---|
|
|
100
|
-
| `children` | `string` | required | Markdown input string |
|
|
101
|
-
| `options` | `ParserOptions` | -- | Parser flags (`gfm`, `math`, `html`) |
|
|
102
|
-
| `plugins` | `MarkdownPlugin[]` | -- | Plugin hooks (`beforeParse`, `afterParse`) |
|
|
103
|
-
| `sourceAst` | `MarkdownNode` | -- | Pre-parsed AST; skips native parse when provided |
|
|
104
|
-
| `parseCache` | `boolean` | `true` | Enable internal parse result caching for repeated inputs |
|
|
105
|
-
| `astTransform` | `AstTransform` | -- | Post-parse AST rewrite before render |
|
|
106
|
-
| `renderers` | `CustomRenderers` | `{}` | Per-node custom renderer overrides |
|
|
107
|
-
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
|
|
108
|
-
| `styles` | `NodeStyleOverrides` | -- | Per-node style overrides |
|
|
109
|
-
| `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Base styling preset |
|
|
110
|
-
| `style` | `StyleProp<ViewStyle>` | -- | Container style |
|
|
111
|
-
| `onLinkPress` | `LinkPressHandler` | -- | Intercept link presses; return `false` to block default open |
|
|
112
|
-
| `onParsingInProgress` | `() => void` | -- | Called when parse inputs change |
|
|
113
|
-
| `onParseComplete` | `(result) => void` | -- | Called with `{ raw, ast, text }` after parse |
|
|
114
|
-
| `onError` | `(error, phase, pluginName?) => void` | -- | Error handler for parse/plugin failures |
|
|
115
|
-
| `highlightCode` | `boolean \| CodeHighlighter` | -- | Enable syntax highlighting for code blocks |
|
|
116
|
-
| `tableOptions` | `{ minColumnWidth?; measurementStabilizeMs? }` | -- | Table layout tuning |
|
|
117
|
-
| `imageOptions` | `UrlSafetyOptions` | `{ allowedProtocols: ["http:", "https:"] }` | Built-in image URL allowlist |
|
|
118
|
-
| `virtualize` | `boolean \| "auto"` | `false` | Top-level block virtualization |
|
|
119
|
-
| `virtualizationMinBlocks` | `number` | `40` | Block threshold for `"auto"` virtualization |
|
|
120
|
-
| `virtualization` | `MarkdownVirtualizationOptions` | -- | FlatList tuning (windowSize, batching, etc.) |
|
|
121
|
-
|
|
122
|
-
**Pipeline order:** `beforeParse` plugins (by priority desc) -> parse/sourceAst -> `afterParse` plugins (by priority desc) -> `astTransform` -> render.
|
|
123
|
-
|
|
124
|
-
When `sourceAst` is provided, `beforeParse` plugins are skipped because no source string is parsed. `afterParse` plugins still run on the provided AST.
|
|
125
|
-
|
|
126
|
-
`parseCache` defaults to `true` and caches parse results for repeated markdown inputs. Set `parseCache={false}` to force a fresh native parse on each input change. This flag has no effect when `sourceAst` is provided.
|
|
127
|
-
|
|
128
|
-
### `<MarkdownStream>`
|
|
129
|
-
|
|
130
|
-
Renders markdown from a streaming session. Extends `MarkdownProps` (minus `children`).
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
import { MarkdownStream } from "react-native-nitro-markdown";
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
| Prop | Type | Default | Description |
|
|
137
|
-
|---|---|---|---|
|
|
138
|
-
| `session` | `MarkdownSession` | required | Session supplying streamed text |
|
|
139
|
-
| `updateIntervalMs` | `number` | `50` | Flush interval for `"interval"` strategy; ignored by `"raf"` |
|
|
140
|
-
| `updateStrategy` | `"interval" \| "raf"` | `"interval"` | `"interval"` uses `updateIntervalMs`; `"raf"` schedules at most once per animation frame |
|
|
141
|
-
| `useTransitionUpdates` | `boolean` | `false` | Wrap updates in `startTransition` |
|
|
142
|
-
| `incrementalParsing` | `boolean` | `true` | Append-optimized AST updates; disabled automatically when a `beforeParse` plugin is present |
|
|
143
|
-
|
|
144
|
-
### `MarkdownSession`
|
|
145
|
-
|
|
146
|
-
A native text buffer with change listeners, used for streaming.
|
|
147
|
-
|
|
148
|
-
```tsx
|
|
149
|
-
import { createMarkdownSession } from "react-native-nitro-markdown";
|
|
150
|
-
|
|
151
|
-
const session = createMarkdownSession();
|
|
152
|
-
session.append("# Hello\n");
|
|
153
|
-
session.append("Streaming content...");
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
| Method | Signature | Description |
|
|
157
|
-
|---|---|---|
|
|
158
|
-
| `append` | `(chunk: string) => number` | Append text, returns new UTF-16 length |
|
|
159
|
-
| `clear` | `() => void` | Clear buffer, emit reset event |
|
|
160
|
-
| `reset` | `(text: string) => void` | Replace full buffer content |
|
|
161
|
-
| `replace` | `(from, to, text) => number` | Partial buffer mutation; out-of-bounds ranges are clamped and invalid ranges throw |
|
|
162
|
-
| `getAllText` | `() => string` | Get full session text |
|
|
163
|
-
| `getLength` | `() => number` | Get UTF-16 length without copy |
|
|
164
|
-
| `getTextRange` | `(from, to) => string` | Get substring range |
|
|
165
|
-
| `addListener` | `(listener) => () => void` | Subscribe to mutation events; returns unsubscribe |
|
|
166
|
-
| `highlightPosition` | `number` | Mutable cursor for stream highlight |
|
|
167
|
-
| `dispose` | `() => void` | Release native listener and buffer storage; called automatically by `useMarkdownSession` on unmount |
|
|
168
|
-
|
|
169
|
-
### `useMarkdownSession()`
|
|
170
|
-
|
|
171
|
-
Creates and owns a `MarkdownSession` for a component lifecycle.
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
import { useMarkdownSession } from "react-native-nitro-markdown";
|
|
175
|
-
|
|
176
|
-
const { getSession, isStreaming, setIsStreaming, stop, clear, setHighlight } =
|
|
177
|
-
useMarkdownSession();
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### `useStream(timestamps?)`
|
|
181
|
-
|
|
182
|
-
Extends `useMarkdownSession` with timeline sync for timed playback.
|
|
63
|
+
## Streaming
|
|
183
64
|
|
|
184
65
|
```tsx
|
|
185
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
MarkdownStream,
|
|
68
|
+
useMarkdownSession,
|
|
69
|
+
} from "react-native-nitro-markdown";
|
|
186
70
|
|
|
187
|
-
|
|
188
|
-
|
|
71
|
+
export function ChatMessage({ text }: { text: string }) {
|
|
72
|
+
const session = useMarkdownSession(text);
|
|
189
73
|
|
|
190
|
-
|
|
74
|
+
return (
|
|
75
|
+
<MarkdownStream session={session} updateStrategy="raf" incrementalParsing />
|
|
76
|
+
);
|
|
77
|
+
}
|
|
191
78
|
```
|
|
192
79
|
|
|
193
|
-
|
|
80
|
+
`MarkdownStream` batches updates for append-only text. If any plugin uses
|
|
81
|
+
`beforeParse`, incremental AST optimization is disabled so the full pipeline can
|
|
82
|
+
run correctly. `MarkdownStream` accepts the controller returned by
|
|
83
|
+
`useMarkdownSession()`. Pass `session.getSession()` only when another API needs
|
|
84
|
+
direct access to the native session object.
|
|
194
85
|
|
|
195
|
-
|
|
86
|
+
## Headless Parsing
|
|
196
87
|
|
|
197
|
-
```
|
|
88
|
+
```ts
|
|
198
89
|
import {
|
|
90
|
+
extractPlainText,
|
|
199
91
|
parseMarkdown,
|
|
200
92
|
parseMarkdownWithOptions,
|
|
201
|
-
extractPlainText,
|
|
202
|
-
getTextContent,
|
|
203
|
-
getFlattenedText,
|
|
204
|
-
stripSourceOffsets,
|
|
205
93
|
} from "react-native-nitro-markdown/headless";
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
| Function | Description |
|
|
209
|
-
|---|---|
|
|
210
|
-
| `parseMarkdown(text, options?)` | Parse to AST (options: `{ gfm?, math?, html? }`) |
|
|
211
|
-
| `parseMarkdownWithOptions(text, options)` | Parse with explicit options |
|
|
212
|
-
| `extractPlainText(text)` | Parse and return plain text from native parser |
|
|
213
|
-
| `extractPlainTextWithOptions(text, options)` | Same with parser flags |
|
|
214
|
-
| `getTextContent(node)` | Concatenate text recursively (no normalization) |
|
|
215
|
-
| `getFlattenedText(node)` | Normalized plain text with block separators |
|
|
216
|
-
| `stripSourceOffsets(node)` | Remove `beg`/`end` fields from AST |
|
|
217
|
-
|
|
218
|
-
### Custom Renderers
|
|
219
|
-
|
|
220
|
-
Override rendering for specific node types:
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
import {
|
|
224
|
-
Markdown,
|
|
225
|
-
type HeadingRendererProps,
|
|
226
|
-
type CodeBlockRendererProps,
|
|
227
|
-
} from "react-native-nitro-markdown";
|
|
228
|
-
|
|
229
|
-
<Markdown
|
|
230
|
-
renderers={{
|
|
231
|
-
heading: ({ level, children }: HeadingRendererProps) => (
|
|
232
|
-
<MyHeading level={level}>{children}</MyHeading>
|
|
233
|
-
),
|
|
234
|
-
code_block: ({ language, content }: CodeBlockRendererProps) => (
|
|
235
|
-
<MyCode language={language} content={content} />
|
|
236
|
-
),
|
|
237
|
-
}}
|
|
238
|
-
>
|
|
239
|
-
{content}
|
|
240
|
-
</Markdown>;
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Renderers receive `EnhancedRendererProps` with `node`, `children`, and `Renderer` (for recursive rendering). Node-specific props are mapped automatically:
|
|
244
|
-
|
|
245
|
-
| Node type | Extra props |
|
|
246
|
-
|---|---|
|
|
247
|
-
| `heading` | `level` |
|
|
248
|
-
| `link` | `href`, `title` |
|
|
249
|
-
| `image` | `url`, `alt`, `title` |
|
|
250
|
-
| `code_block` | `content`, `language` |
|
|
251
|
-
| `code_inline` | `content` |
|
|
252
|
-
| `list` | `ordered`, `start` |
|
|
253
|
-
| `task_list_item` | `checked` |
|
|
254
|
-
| `html_inline`, `html_block` | Read raw HTML from `node.content` |
|
|
255
|
-
|
|
256
|
-
Return `undefined` to fall back to the built-in renderer, or `null` to render nothing.
|
|
257
|
-
|
|
258
|
-
TypeScript users can import specific renderer props for stronger IDE feedback:
|
|
259
|
-
|
|
260
|
-
```tsx
|
|
261
|
-
import type {
|
|
262
|
-
CustomRenderers,
|
|
263
|
-
ImageRendererProps,
|
|
264
|
-
LinkRendererProps,
|
|
265
|
-
MathRendererProps,
|
|
266
|
-
} from "react-native-nitro-markdown";
|
|
267
94
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
),
|
|
272
|
-
image: ({ url, alt }: ImageRendererProps) => (
|
|
273
|
-
<MyImage source={{ uri: url }} accessibilityLabel={alt} />
|
|
274
|
-
),
|
|
275
|
-
math_block: ({ content }: MathRendererProps) => (
|
|
276
|
-
<MyMathBlock latex={content} />
|
|
277
|
-
),
|
|
278
|
-
};
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Use `satisfies CustomRenderers` when you want excess-property checking while preserving the exact function types for each renderer:
|
|
282
|
-
|
|
283
|
-
```tsx
|
|
284
|
-
import type { CustomRenderers } from "react-native-nitro-markdown";
|
|
285
|
-
|
|
286
|
-
const renderers = {
|
|
287
|
-
heading: ({ level, children }) => (
|
|
288
|
-
<MyHeading variant={`h${level}`}>{children}</MyHeading>
|
|
289
|
-
),
|
|
290
|
-
code_block: ({ language, content }) => (
|
|
291
|
-
<MyCodeBlock language={language ?? "text"} code={content} />
|
|
292
|
-
),
|
|
293
|
-
} satisfies CustomRenderers;
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
#### Rendering HTML Nodes
|
|
297
|
-
|
|
298
|
-
HTML parsing is opt-in and produces raw AST nodes. The built-in renderer intentionally renders nothing for `html_inline` and `html_block`; applications decide what is safe to display.
|
|
299
|
-
|
|
300
|
-
```tsx
|
|
301
|
-
import { Text, View } from "react-native";
|
|
302
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
303
|
-
|
|
304
|
-
<Markdown
|
|
305
|
-
options={{ html: true }}
|
|
306
|
-
renderers={{
|
|
307
|
-
html_inline: ({ node }) => {
|
|
308
|
-
if (node.content === "<br>") return <Text>{"\n"}</Text>;
|
|
309
|
-
return null;
|
|
310
|
-
},
|
|
311
|
-
html_block: ({ node }) => {
|
|
312
|
-
if (!node.content?.includes('data-kind="release-note"')) return null;
|
|
313
|
-
return (
|
|
314
|
-
<View>
|
|
315
|
-
<Text>Release note</Text>
|
|
316
|
-
</View>
|
|
317
|
-
);
|
|
318
|
-
},
|
|
319
|
-
}}
|
|
320
|
-
>
|
|
321
|
-
{content}
|
|
322
|
-
</Markdown>;
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
Do not pass untrusted HTML directly into a WebView from a renderer. Sanitize or map known tags/attributes to native components.
|
|
326
|
-
|
|
327
|
-
### Math Rendering
|
|
328
|
-
|
|
329
|
-
Math parsing is enabled by default. Rendering LaTeX uses the native RaTeX renderer:
|
|
330
|
-
|
|
331
|
-
```bash
|
|
332
|
-
bun add ratex-react-native
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
If `ratex-react-native` is unavailable at runtime, `math_inline` and `math_block` render as styled plain text fallback.
|
|
336
|
-
|
|
337
|
-
Inline math (`$...$`) stays inside text flow. Use block math (`$$...$$`) for large formulas; the default `math_block` renderer is horizontally scrollable when the equation is wider than the screen. Custom `math_block` renderers should preserve the same behavior for long display equations.
|
|
338
|
-
|
|
339
|
-
### Theme API
|
|
340
|
-
|
|
341
|
-
```tsx
|
|
342
|
-
import {
|
|
343
|
-
Markdown,
|
|
344
|
-
defaultMarkdownTheme,
|
|
345
|
-
minimalMarkdownTheme,
|
|
346
|
-
mergeThemes,
|
|
347
|
-
} from "react-native-nitro-markdown";
|
|
348
|
-
|
|
349
|
-
const theme = mergeThemes(defaultMarkdownTheme, {
|
|
350
|
-
colors: { text: "#0f172a", link: "#1d4ed8" },
|
|
351
|
-
fontSizes: { m: 16 },
|
|
95
|
+
const ast = parseMarkdown("# Title");
|
|
96
|
+
const astWithOffsets = parseMarkdownWithOptions("# Title", {
|
|
97
|
+
sourceAst: true,
|
|
352
98
|
});
|
|
353
|
-
|
|
354
|
-
<Markdown theme={theme}>{content}</Markdown>;
|
|
99
|
+
const text = extractPlainText("Hello **world**");
|
|
355
100
|
```
|
|
356
101
|
|
|
357
|
-
`
|
|
358
|
-
|
|
359
|
-
- **spacing** -- `xs`, `s`, `m`, `l`, `xl`
|
|
360
|
-
- **fontSizes** -- `xs` through `xl`, plus `h1`-`h6`
|
|
361
|
-
- **fontFamilies** -- `regular`, `heading`, `mono`
|
|
362
|
-
- **borderRadius** -- `s`, `m`, `l`
|
|
363
|
-
- **headingWeight** -- optional font weight override
|
|
364
|
-
- **showCodeLanguage** -- show/hide language label on code blocks
|
|
102
|
+
Use the `react-native-nitro-markdown/headless` export when you need AST data,
|
|
103
|
+
plain text extraction, indexing, validation, or tests without rendering UI.
|
|
365
104
|
|
|
366
|
-
|
|
105
|
+
## Common Options
|
|
367
106
|
|
|
368
|
-
|
|
107
|
+
| Option | Default | What it does |
|
|
108
|
+
| --------------- | ----------------- | --------------------------------------------------------- |
|
|
109
|
+
| `gfm` | `true` | Enables tables, strikethrough, task lists, and autolinks. |
|
|
110
|
+
| `parseCache` | `true` | Reuses parsed ASTs for repeated content. |
|
|
111
|
+
| `sourceAst` | `false` | Includes source offsets for tooling and editor features. |
|
|
112
|
+
| `allowHtml` | `false` | Preserves raw HTML nodes for custom renderers. |
|
|
113
|
+
| `highlightCode` | `false` | Enables built-in syntax highlighting. |
|
|
114
|
+
| `tableOptions` | Built-in defaults | Controls table measurement and minimum widths. |
|
|
369
115
|
|
|
370
|
-
|
|
116
|
+
## Custom Rendering
|
|
371
117
|
|
|
372
118
|
```tsx
|
|
373
|
-
import {
|
|
374
|
-
import {
|
|
375
|
-
MarkdownStream,
|
|
376
|
-
useMarkdownSession,
|
|
377
|
-
} from "react-native-nitro-markdown";
|
|
378
|
-
|
|
379
|
-
export function StreamingExample() {
|
|
380
|
-
const session = useMarkdownSession();
|
|
119
|
+
import type { MarkdownRenderers } from "react-native-nitro-markdown";
|
|
120
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
381
121
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}, [session]);
|
|
122
|
+
const renderers: MarkdownRenderers = {
|
|
123
|
+
paragraph({ children }) {
|
|
124
|
+
return <Text style={{ lineHeight: 22 }}>{children}</Text>;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
388
127
|
|
|
389
|
-
|
|
390
|
-
<MarkdownStream
|
|
391
|
-
session={session.getSession()}
|
|
392
|
-
options={{ gfm: true }}
|
|
393
|
-
updateStrategy="raf"
|
|
394
|
-
/>
|
|
395
|
-
);
|
|
396
|
-
}
|
|
128
|
+
<Markdown renderers={renderers}>{"Custom paragraph renderer"}</Markdown>;
|
|
397
129
|
```
|
|
398
130
|
|
|
399
|
-
|
|
131
|
+
Custom renderers receive parsed nodes and pre-mapped props for common node
|
|
132
|
+
types. For `html_inline` and `html_block`, read `node.content` directly.
|
|
400
133
|
|
|
401
|
-
|
|
402
|
-
import { useCallback } from "react";
|
|
403
|
-
import { Markdown, type AstTransform } from "react-native-nitro-markdown";
|
|
404
|
-
|
|
405
|
-
const transform = useCallback<AstTransform>((ast) => {
|
|
406
|
-
const visit = (node: Parameters<AstTransform>[0]): typeof node => ({
|
|
407
|
-
...node,
|
|
408
|
-
content:
|
|
409
|
-
node.type === "text"
|
|
410
|
-
? (node.content ?? "").replace(/:wink:/g, "😉")
|
|
411
|
-
: node.content,
|
|
412
|
-
children: node.children?.map(visit),
|
|
413
|
-
});
|
|
414
|
-
return visit(ast);
|
|
415
|
-
}, []);
|
|
416
|
-
|
|
417
|
-
<Markdown astTransform={transform}>{"Hello :wink:"}</Markdown>;
|
|
418
|
-
```
|
|
134
|
+
## Plugin Pipeline
|
|
419
135
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
```tsx
|
|
423
|
-
import { Markdown, type MarkdownPlugin } from "react-native-nitro-markdown";
|
|
136
|
+
```ts
|
|
137
|
+
import type { MarkdownPlugin } from "react-native-nitro-markdown";
|
|
424
138
|
|
|
425
139
|
const plugins: MarkdownPlugin[] = [
|
|
426
140
|
{
|
|
427
|
-
name: "
|
|
141
|
+
name: "mentions",
|
|
428
142
|
priority: 10,
|
|
429
|
-
beforeParse
|
|
430
|
-
|
|
431
|
-
{
|
|
432
|
-
name: "rewrite-after-parse",
|
|
433
|
-
afterParse: (ast) => {
|
|
434
|
-
const visit = (node: typeof ast): typeof ast => ({
|
|
435
|
-
...node,
|
|
436
|
-
content:
|
|
437
|
-
node.type === "text"
|
|
438
|
-
? (node.content ?? "").replace(/ROCKET_TOKEN/g, "🚀")
|
|
439
|
-
: node.content,
|
|
440
|
-
children: node.children?.map(visit),
|
|
441
|
-
});
|
|
442
|
-
return visit(ast);
|
|
143
|
+
beforeParse(source) {
|
|
144
|
+
return source.replaceAll("@team", "**@team**");
|
|
443
145
|
},
|
|
444
146
|
},
|
|
445
147
|
];
|
|
446
|
-
|
|
447
|
-
<Markdown plugins={plugins}>{"Launch :rocket:"}</Markdown>;
|
|
448
148
|
```
|
|
449
149
|
|
|
450
|
-
|
|
150
|
+
Pipeline order: `beforeParse` plugins, parse or `sourceAst`, `afterParse`
|
|
151
|
+
plugins, `astTransform`, then render. When `sourceAst` is provided, `beforeParse` plugins are skipped
|
|
152
|
+
because parsing already happened. Higher `priority` values run first, and
|
|
153
|
+
sorting is stable.
|
|
451
154
|
|
|
452
|
-
|
|
453
|
-
import { Markdown, parseMarkdownWithOptions } from "react-native-nitro-markdown";
|
|
155
|
+
## API
|
|
454
156
|
|
|
455
|
-
|
|
456
|
-
gfm: true,
|
|
457
|
-
math: true,
|
|
458
|
-
html: false,
|
|
459
|
-
});
|
|
157
|
+
Main export:
|
|
460
158
|
|
|
461
|
-
|
|
462
|
-
|
|
159
|
+
- `Markdown` for rendering complete markdown strings.
|
|
160
|
+
- `MarkdownStream` for incremental rendering.
|
|
161
|
+
- `MarkdownSession` and `useMarkdownSession()` for append/replace/reset flows.
|
|
162
|
+
- `useStream()` for timestamped stream state.
|
|
163
|
+
- `defaultMarkdownTheme` and theme types.
|
|
164
|
+
- Renderer components such as `Paragraph`, `Heading`, `Link`, `CodeBlock`,
|
|
165
|
+
`List`, `Table`, and `Image`.
|
|
166
|
+
- Types including `MarkdownNode`, `MarkdownPlugin`, `MarkdownRenderers`,
|
|
167
|
+
`ParserOptions`, `MarkdownTheme`, `MarkdownSessionController`, and
|
|
168
|
+
`MarkdownStreamProps`.
|
|
463
169
|
|
|
464
|
-
|
|
170
|
+
Headless export:
|
|
465
171
|
|
|
466
|
-
|
|
172
|
+
- `parseMarkdown`.
|
|
173
|
+
- `parseMarkdownWithOptions`.
|
|
174
|
+
- `extractPlainText`.
|
|
175
|
+
- `extractPlainTextWithOptions`.
|
|
176
|
+
- AST helpers such as `getTextContent`, `getFlattenedText`, and
|
|
177
|
+
`stripSourceOffsets`.
|
|
467
178
|
|
|
468
|
-
|
|
469
|
-
<Markdown
|
|
470
|
-
virtualize="auto"
|
|
471
|
-
virtualizationMinBlocks={30}
|
|
472
|
-
virtualization={{
|
|
473
|
-
initialNumToRender: 10,
|
|
474
|
-
maxToRenderPerBatch: 10,
|
|
475
|
-
windowSize: 8,
|
|
476
|
-
}}
|
|
477
|
-
>
|
|
478
|
-
{content}
|
|
479
|
-
</Markdown>
|
|
480
|
-
```
|
|
179
|
+
## Platform Support
|
|
481
180
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
// Built-in highlighter (JS/TS, Python, Bash)
|
|
490
|
-
<Markdown highlightCode>{"```typescript\nconst x: number = 42;\n```"}</Markdown>
|
|
491
|
-
|
|
492
|
-
// Custom highlighter
|
|
493
|
-
const myHighlighter: CodeHighlighter = (language, code) => {
|
|
494
|
-
return [{ text: code, type: "default" }];
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
<Markdown highlightCode={myHighlighter}>{content}</Markdown>
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### Link Interception
|
|
501
|
-
|
|
502
|
-
```tsx
|
|
503
|
-
<Markdown
|
|
504
|
-
onLinkPress={(href) => {
|
|
505
|
-
if (href.startsWith("/")) {
|
|
506
|
-
router.push(href);
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
}}
|
|
510
|
-
>
|
|
511
|
-
{content}
|
|
512
|
-
</Markdown>
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
Default link behavior: trims href, calls `onLinkPress`, validates scheme (`http:`, `https:`, `mailto:`, `tel:`, `sms:`), then opens via `Linking.openURL`. Relative URLs and anchors are ignored unless handled in `onLinkPress`.
|
|
516
|
-
|
|
517
|
-
### Image URL Safety
|
|
518
|
-
|
|
519
|
-
Built-in image rendering only loads `http:` and `https:` URLs by default. Use `imageOptions` to narrow the allowed hosts or, when your app owns the source content, add another protocol explicitly.
|
|
520
|
-
|
|
521
|
-
```tsx
|
|
522
|
-
import { Markdown, type UrlSafetyOptions } from "react-native-nitro-markdown";
|
|
523
|
-
|
|
524
|
-
const imageOptions: UrlSafetyOptions = {
|
|
525
|
-
allowedProtocols: ["https:"],
|
|
526
|
-
allowedHosts: ["cdn.example.com", "images.example.com"],
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
<Markdown imageOptions={imageOptions}>{content}</Markdown>;
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
Relative image URLs are not loaded by the default renderer. Map them in a custom `image` renderer when your app has a trusted asset resolver.
|
|
533
|
-
|
|
534
|
-
### TypeScript Surface
|
|
535
|
-
|
|
536
|
-
The package exports public types for every commonly customized surface:
|
|
537
|
-
|
|
538
|
-
- `MarkdownProps`, `MarkdownStreamProps`, `MarkdownParseCompleteResult`, `MarkdownErrorPhase`
|
|
539
|
-
- `MarkdownNode`, `MarkdownNodeType`, `HeadingLevel`, `TableCellAlign`, `ParserOptions`, `AstTransform`, `MarkdownPlugin`
|
|
540
|
-
- `CustomRenderers`, `CustomRendererPropsByNode`, and node-specific renderer props
|
|
541
|
-
- `MarkdownTheme`, `PartialMarkdownTheme`, `NodeStyleOverrides`, `TableOptions`, `UrlSafetyOptions`
|
|
542
|
-
- `MarkdownSession`, `CodeHighlighter`, `HighlightedToken`, `TokenType`
|
|
543
|
-
|
|
544
|
-
Prefer typing shared `renderers`, `plugins`, `theme`, `tableOptions`, and `imageOptions` constants instead of relying on inference from inline JSX props. That keeps mistakes visible in IDEs and gives AI-assisted edits a stricter contract to follow.
|
|
545
|
-
|
|
546
|
-
## Supported Node Types
|
|
547
|
-
|
|
548
|
-
`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`
|
|
549
|
-
|
|
550
|
-
`html_inline` and `html_block` are parsed only when `options.html` is `true`; they are not rendered by default. Use a custom renderer to handle them, and read raw HTML from `node.content`.
|
|
551
|
-
|
|
552
|
-
## Package Exports
|
|
553
|
-
|
|
554
|
-
### Main (`react-native-nitro-markdown`)
|
|
555
|
-
|
|
556
|
-
Components, hooks, sessions, themes, built-in renderers, syntax highlighting, and all headless APIs.
|
|
557
|
-
|
|
558
|
-
### Headless (`react-native-nitro-markdown/headless`)
|
|
559
|
-
|
|
560
|
-
Parser and text utilities only -- no React dependencies. Use this for server-side processing, search indexing, or custom renderers.
|
|
561
|
-
|
|
562
|
-
## Performance Tips
|
|
563
|
-
|
|
564
|
-
- Use `updateStrategy="raf"` for frame-aligned streaming UI updates; `updateIntervalMs` is ignored by `"raf"`.
|
|
565
|
-
- Batch `session.append()` calls at 50-100ms intervals rather than per-character.
|
|
566
|
-
- Enable `virtualize="auto"` for long documents and keep `Markdown` as the primary vertical scroller.
|
|
567
|
-
- Memoize custom `plugins`, `renderers`, `theme`, and `styles` objects when they are created inside a component.
|
|
568
|
-
- Use the headless API when you only need parsing, plain text extraction, search indexing, or a custom renderer.
|
|
181
|
+
| Platform | Status |
|
|
182
|
+
| -------- | ------------------------------------------ |
|
|
183
|
+
| iOS | Native parser through Nitro and md4c. |
|
|
184
|
+
| Android | Native parser through Nitro and md4c. |
|
|
185
|
+
| Expo | Development builds. |
|
|
186
|
+
| Web | Not the primary target for native parsing. |
|
|
569
187
|
|
|
570
188
|
## Troubleshooting
|
|
571
189
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
190
|
+
- **Expo Go error:** build a dev client; Expo Go cannot load Nitro modules.
|
|
191
|
+
- **Streaming updates too often:** use `updateStrategy="raf"` or an interval
|
|
192
|
+
around 50-100ms.
|
|
193
|
+
- **Plugin changes do not appear incremental:** `beforeParse` plugins force a
|
|
194
|
+
full parse by design.
|
|
195
|
+
- **Math does not render:** ensure `ratex-react-native` is installed and native
|
|
196
|
+
code has been rebuilt.
|
|
579
197
|
|
|
580
|
-
##
|
|
198
|
+
## Development
|
|
581
199
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
| Custom | `app/render-custom.tsx` | HTML AST rendering, custom renderers, AST transform |
|
|
590
|
-
| Stream | `app/render-stream.tsx` | Live streaming with token append |
|
|
591
|
-
|
|
592
|
-
## Release Checks
|
|
593
|
-
|
|
594
|
-
- `bun run harness` runs lint, typecheck, JS coverage, benchmark checks, and C++ coverage.
|
|
595
|
-
- `bun run publish-package -- --dry-run --yes` validates release docs, runs publish verification, builds the package, and performs an npm dry run.
|
|
596
|
-
|
|
597
|
-
## Contributing
|
|
200
|
+
```sh
|
|
201
|
+
bun install
|
|
202
|
+
bun run check
|
|
203
|
+
bun run release:preflight
|
|
204
|
+
bun run example:android
|
|
205
|
+
bun run example:ios
|
|
206
|
+
```
|
|
598
207
|
|
|
599
|
-
|
|
208
|
+
Run native example builds before release when changing native, Nitro, rendering,
|
|
209
|
+
or packaging files.
|
|
600
210
|
|
|
601
211
|
## License
|
|
602
212
|
|