react-native-nitro-markdown 0.4.2 → 0.5.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.
- package/README.md +605 -318
- package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +27 -8
- package/cpp/bindings/HybridMarkdownParser.cpp +216 -66
- package/cpp/bindings/HybridMarkdownParser.hpp +2 -0
- package/ios/HybridMarkdownSession.swift +33 -7
- package/lib/commonjs/MarkdownContext.js +2 -1
- package/lib/commonjs/MarkdownContext.js.map +1 -1
- package/lib/commonjs/headless.js +41 -5
- package/lib/commonjs/headless.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +109 -13
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/markdown.js +215 -44
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/code.js +4 -3
- package/lib/commonjs/renderers/code.js.map +1 -1
- package/lib/commonjs/renderers/heading.js +1 -1
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/image.js +7 -5
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/link.js +15 -3
- package/lib/commonjs/renderers/link.js.map +1 -1
- package/lib/commonjs/renderers/list.js +2 -2
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/table.js +126 -21
- package/lib/commonjs/renderers/table.js.map +1 -1
- package/lib/commonjs/use-markdown-stream.js +16 -14
- package/lib/commonjs/use-markdown-stream.js.map +1 -1
- package/lib/commonjs/utils/incremental-ast.js +153 -0
- package/lib/commonjs/utils/incremental-ast.js.map +1 -0
- package/lib/commonjs/utils/link-security.js +21 -0
- package/lib/commonjs/utils/link-security.js.map +1 -0
- package/lib/commonjs/utils/stream-timeline.js +62 -0
- package/lib/commonjs/utils/stream-timeline.js.map +1 -0
- package/lib/module/MarkdownContext.js +2 -1
- package/lib/module/MarkdownContext.js.map +1 -1
- package/lib/module/headless.js +37 -4
- package/lib/module/headless.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +110 -14
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/markdown.js +217 -46
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/blockquote.js.map +1 -1
- package/lib/module/renderers/code.js +4 -3
- package/lib/module/renderers/code.js.map +1 -1
- package/lib/module/renderers/heading.js +1 -1
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/image.js +7 -5
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/link.js +15 -3
- package/lib/module/renderers/link.js.map +1 -1
- package/lib/module/renderers/list.js +2 -2
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/paragraph.js.map +1 -1
- package/lib/module/renderers/table.js +127 -22
- package/lib/module/renderers/table.js.map +1 -1
- package/lib/module/use-markdown-stream.js +16 -14
- package/lib/module/use-markdown-stream.js.map +1 -1
- package/lib/module/utils/incremental-ast.js +147 -0
- package/lib/module/utils/incremental-ast.js.map +1 -0
- package/lib/module/utils/link-security.js +15 -0
- package/lib/module/utils/link-security.js.map +1 -0
- package/lib/module/utils/stream-timeline.js +56 -0
- package/lib/module/utils/stream-timeline.js.map +1 -0
- package/lib/typescript/commonjs/Markdown.nitro.d.ts +5 -3
- package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/MarkdownContext.d.ts +26 -25
- package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/headless.d.ts +15 -2
- package/lib/typescript/commonjs/headless.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts +7 -2
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown.d.ts +62 -5
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/blockquote.d.ts +3 -3
- package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/code.d.ts +5 -5
- package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts +3 -3
- package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +2 -2
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/image.d.ts +2 -2
- package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/link.d.ts +3 -3
- package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts +7 -7
- package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts +4 -4
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/paragraph.d.ts +3 -3
- package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/table.d.ts +3 -3
- package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +5 -2
- package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts +2 -2
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/incremental-ast.d.ts +12 -0
- package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/link-security.d.ts +3 -0
- package/lib/typescript/commonjs/utils/link-security.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/stream-timeline.d.ts +11 -0
- package/lib/typescript/commonjs/utils/stream-timeline.d.ts.map +1 -0
- package/lib/typescript/module/Markdown.nitro.d.ts +5 -3
- package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/module/MarkdownContext.d.ts +26 -25
- package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/module/headless.d.ts +15 -2
- package/lib/typescript/module/headless.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts +7 -2
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/markdown.d.ts +62 -5
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/blockquote.d.ts +3 -3
- package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/module/renderers/code.d.ts +5 -5
- package/lib/typescript/module/renderers/code.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.d.ts +3 -3
- package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/module/renderers/horizontal-rule.d.ts +2 -2
- package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/module/renderers/image.d.ts +2 -2
- package/lib/typescript/module/renderers/image.d.ts.map +1 -1
- package/lib/typescript/module/renderers/link.d.ts +3 -3
- package/lib/typescript/module/renderers/link.d.ts.map +1 -1
- package/lib/typescript/module/renderers/list.d.ts +7 -7
- package/lib/typescript/module/renderers/list.d.ts.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts +4 -4
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/renderers/paragraph.d.ts +3 -3
- package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/module/renderers/table.d.ts +3 -3
- package/lib/typescript/module/renderers/table.d.ts.map +1 -1
- package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +5 -2
- package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts +2 -2
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/utils/incremental-ast.d.ts +12 -0
- package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -0
- package/lib/typescript/module/utils/link-security.d.ts +3 -0
- package/lib/typescript/module/utils/link-security.d.ts.map +1 -0
- package/lib/typescript/module/utils/stream-timeline.d.ts +11 -0
- package/lib/typescript/module/utils/stream-timeline.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +18 -6
- package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +4 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/Func_void_double_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +11 -3
- package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +31 -0
- package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +20 -2
- package/nitrogen/generated/ios/swift/Func_void.swift +0 -1
- package/nitrogen/generated/ios/swift/Func_void_double_double.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +4 -3
- package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +34 -10
- package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +4 -2
- package/package.json +7 -5
- package/src/Markdown.nitro.ts +7 -3
- package/src/MarkdownContext.ts +31 -25
- package/src/headless.ts +44 -6
- package/src/index.ts +8 -0
- package/src/markdown-stream.tsx +159 -15
- package/src/markdown.tsx +406 -50
- package/src/renderers/blockquote.tsx +4 -4
- package/src/renderers/code.tsx +16 -10
- package/src/renderers/heading.tsx +4 -4
- package/src/renderers/horizontal-rule.tsx +3 -3
- package/src/renderers/image.tsx +11 -9
- package/src/renderers/link.tsx +25 -7
- package/src/renderers/list.tsx +9 -12
- package/src/renderers/math.tsx +4 -4
- package/src/renderers/paragraph.tsx +3 -3
- package/src/renderers/table.tsx +270 -98
- package/src/specs/MarkdownSession.nitro.ts +6 -2
- package/src/theme.ts +3 -3
- package/src/use-markdown-stream.ts +22 -16
- package/src/utils/incremental-ast.ts +224 -0
- package/src/utils/link-security.ts +22 -0
- package/src/utils/stream-timeline.ts +72 -0
package/README.md
CHANGED
|
@@ -1,58 +1,40 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="./readme/demo.gif" alt="react-native-nitro-markdown demo" width="
|
|
3
|
-
<img src="./readme/stream-demo.gif" alt="react-native-nitro-markdown stream demo" width="300" />
|
|
2
|
+
<img src="./readme/demo.gif" alt="react-native-nitro-markdown demo" width="400" />
|
|
4
3
|
</p>
|
|
5
4
|
|
|
6
5
|
# react-native-nitro-markdown
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Native Markdown parsing and rendering for React Native.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
`react-native-nitro-markdown` uses `md4c` (C++) through Nitro Modules (JSI) to parse Markdown synchronously into a typed AST, then render it with customizable React Native components.
|
|
11
10
|
|
|
12
|
-
## Why
|
|
11
|
+
## Why use it
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Markdown string -> md4c C++ parser -> JSON AST -> React Native renderers
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Features
|
|
23
|
-
|
|
24
|
-
- Native C++ parser with JSI access (fast, synchronous parsing)
|
|
25
|
-
- Full renderer included (Markdown component)
|
|
26
|
-
- Headless API for custom renderers or processing
|
|
13
|
+
- Native parser (`md4c`) for lower JS thread overhead on large documents
|
|
14
|
+
- End-to-end solution: parser + renderer + streaming session API
|
|
15
|
+
- Headless API for custom rendering and text processing
|
|
27
16
|
- GFM support (tables, strikethrough, task lists, autolinks)
|
|
28
|
-
-
|
|
29
|
-
- Streaming support for token-by-token updates
|
|
30
|
-
- Theming and per-node style overrides
|
|
31
|
-
- Built-in renderers exposed for reuse
|
|
32
|
-
|
|
33
|
-
---
|
|
17
|
+
- Optional math rendering with `react-native-mathjax-svg`
|
|
34
18
|
|
|
35
19
|
## Requirements
|
|
36
20
|
|
|
37
|
-
- React Native
|
|
38
|
-
- react-native-nitro-modules
|
|
39
|
-
|
|
40
|
-
Optional (for math rendering):
|
|
21
|
+
- React Native `>=0.75.0`
|
|
22
|
+
- `react-native-nitro-modules`
|
|
41
23
|
|
|
42
|
-
|
|
43
|
-
- react-native-svg
|
|
24
|
+
Optional for math rendering:
|
|
44
25
|
|
|
45
|
-
|
|
26
|
+
- `react-native-mathjax-svg >=0.9.0`
|
|
27
|
+
- `react-native-svg >=13.0.0`
|
|
46
28
|
|
|
47
29
|
## Installation
|
|
48
30
|
|
|
49
|
-
|
|
31
|
+
### React Native
|
|
50
32
|
|
|
51
33
|
```bash
|
|
52
34
|
bun add react-native-nitro-markdown react-native-nitro-modules
|
|
53
35
|
```
|
|
54
36
|
|
|
55
|
-
Optional math
|
|
37
|
+
Optional math support:
|
|
56
38
|
|
|
57
39
|
```bash
|
|
58
40
|
bun add react-native-mathjax-svg react-native-svg
|
|
@@ -64,168 +46,333 @@ iOS pods:
|
|
|
64
46
|
cd ios && pod install
|
|
65
47
|
```
|
|
66
48
|
|
|
67
|
-
Expo (
|
|
49
|
+
### Expo (development build)
|
|
68
50
|
|
|
69
51
|
```bash
|
|
70
52
|
bunx expo install react-native-nitro-markdown react-native-nitro-modules
|
|
71
53
|
bunx expo prebuild
|
|
72
54
|
```
|
|
73
55
|
|
|
74
|
-
|
|
56
|
+
Optional math support:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bunx expo install react-native-mathjax-svg react-native-svg
|
|
60
|
+
```
|
|
75
61
|
|
|
76
62
|
## Quick Start
|
|
77
63
|
|
|
78
64
|
```tsx
|
|
79
65
|
import { Markdown } from "react-native-nitro-markdown";
|
|
80
66
|
|
|
81
|
-
export function
|
|
67
|
+
export function Example() {
|
|
82
68
|
return (
|
|
83
69
|
<Markdown options={{ gfm: true }}>
|
|
84
|
-
{"# Hello
|
|
70
|
+
{"# Hello\nThis is **native** markdown."}
|
|
85
71
|
</Markdown>
|
|
86
72
|
);
|
|
87
73
|
}
|
|
88
74
|
```
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
## Demo App Tour
|
|
77
|
+
|
|
78
|
+
The example app in `apps/example` now maps each major feature to a screen:
|
|
79
|
+
|
|
80
|
+
- `Bench` (`app/index.tsx`)
|
|
81
|
+
- Nitro benchmark + JS parser comparisons
|
|
82
|
+
- `Default` (`app/render-default.tsx`)
|
|
83
|
+
- Built-in renderer defaults
|
|
84
|
+
- `Styles` (`app/render-default-styles.tsx`)
|
|
85
|
+
- `styles` prop and theme token overrides
|
|
86
|
+
- `Custom` (`app/render-custom.tsx`)
|
|
87
|
+
- `renderers` overrides + `astTransform`
|
|
88
|
+
- `Stream` (`app/render-stream.tsx`)
|
|
89
|
+
- streaming UX with live token append
|
|
90
|
+
|
|
91
|
+
## Runtime API Coverage (Demo + Docs)
|
|
92
|
+
|
|
93
|
+
This table maps each runtime API to where it is demonstrated.
|
|
94
|
+
|
|
95
|
+
| API | Purpose | Demo usage |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `Markdown` | Parse + render markdown component | `apps/example/app/render-default.tsx` |
|
|
98
|
+
| `Markdown` `options` | Enable parser flags (`gfm`, `math`) | `apps/example/app/render-default.tsx` |
|
|
99
|
+
| `Markdown` `styles` | Per-node style overrides | `apps/example/app/render-default-styles.tsx` |
|
|
100
|
+
| `Markdown` `renderers` | Custom node renderer overrides | `apps/example/app/render-custom.tsx` |
|
|
101
|
+
| `Markdown` `astTransform` | Post-parse AST transform hook | `apps/example/app/render-custom.tsx` |
|
|
102
|
+
| `Markdown` `virtualize` / `virtualization*` | Large-document block virtualization | README examples |
|
|
103
|
+
| `MarkdownStream` | Stream rendering from session text | `apps/example/app/render-stream.tsx` |
|
|
104
|
+
| `useMarkdownSession` | Own and reuse a native markdown session | `apps/example/app/render-stream.tsx` |
|
|
105
|
+
| `createMarkdownSession` | Create a manual session instance | README examples |
|
|
106
|
+
| `useStream` | Timed playback sync + highlighting | README examples |
|
|
107
|
+
| `parseMarkdown` | Headless parse/benchmark pipeline | `apps/example/app/index.tsx` |
|
|
108
|
+
| `parseMarkdownWithOptions` | Headless parse with parser flags | README examples |
|
|
109
|
+
| `getTextContent` | Extract raw text from AST subtree | README examples |
|
|
110
|
+
| `getFlattenedText` | Normalize AST text for indexing/search | README examples |
|
|
111
|
+
| `MarkdownParserModule` | Low-level Nitro parser access | README examples |
|
|
112
|
+
| `mergeThemes` / `defaultMarkdownTheme` / `minimalMarkdownTheme` | Theme composition and style presets | `apps/example/app/render-default-styles.tsx` + README examples |
|
|
113
|
+
| `useMarkdownContext` / `MarkdownContext` | Access theme/renderer/link handlers inside custom trees | README examples |
|
|
114
|
+
| Built-in renderer components (`CodeBlock`, `TableRenderer`, etc.) | Compose renderer overrides with built-ins | `apps/example/app/render-custom.tsx` |
|
|
115
|
+
| `onLinkPress`, `onParsingInProgress`, `onParseComplete`, `plugins`, `sourceAst` | Advanced lifecycle/link/pipeline control | README examples |
|
|
116
|
+
|
|
117
|
+
## Feature Index
|
|
118
|
+
|
|
119
|
+
Use this table as a quick map from feature -> API -> demo usage.
|
|
120
|
+
|
|
121
|
+
| Feature | API | What it does | Demo |
|
|
122
|
+
| ------------------------ | --------------------------------------------------- | --------------------------------------------------------- | ------------------------------------- |
|
|
123
|
+
| Basic markdown rendering | `Markdown` | Parse and render markdown in one component | `app/render-default.tsx` |
|
|
124
|
+
| Parser flags | `options` (`gfm`, `math`) | Enable GFM and math parsing | `app/render-default.tsx` |
|
|
125
|
+
| Plugin pipeline | `plugins` (`beforeParse`, `afterParse`) | Rewrite markdown input or AST around parse | README examples |
|
|
126
|
+
| AST transform | `astTransform` | Post-parse AST rewrite before render | `app/render-custom.tsx` |
|
|
127
|
+
| Pre-parsed AST render | `sourceAst` | Skip parsing during render and render existing AST | README examples |
|
|
128
|
+
| Parse lifecycle | `onParsingInProgress`, `onParseComplete` | Observe parse start/finish and consume normalized text | README examples |
|
|
129
|
+
| Link interception | `onLinkPress` | Override default URL open behavior | README examples |
|
|
130
|
+
| Large doc virtualization | `virtualize`, `virtualizationMinBlocks` | Virtualizes top-level blocks for very large markdown docs | README examples |
|
|
131
|
+
| Streaming markdown | `MarkdownStream` + `createMarkdownSession` | Render incrementally appended markdown content | `app/render-stream.tsx` |
|
|
132
|
+
| Timed highlight sync | `useStream(timestamps)` | Sync highlight position to playback timeline | README examples |
|
|
133
|
+
| Headless parsing | `parseMarkdown`, `parseMarkdownWithOptions` | Parse markdown without built-in UI | `app/index.tsx` + README examples |
|
|
134
|
+
| Custom node rendering | `renderers` + built-in renderer components | Replace specific node UI while preserving parser behavior | `app/render-custom.tsx` |
|
|
135
|
+
| Styling and theme | `theme`, `styles`, `stylingStrategy`, `mergeThemes` | Control visual tokens and per-node styles | `app/render-default-styles.tsx` |
|
|
136
|
+
| Low-level parser access | `MarkdownParserModule` | Direct access to Nitro parser methods | README examples |
|
|
137
|
+
|
|
138
|
+
## Package Exports
|
|
139
|
+
|
|
140
|
+
### Main Entry (`react-native-nitro-markdown`)
|
|
141
|
+
|
|
142
|
+
- Parser and headless helpers:
|
|
143
|
+
- `parseMarkdown`
|
|
144
|
+
- `parseMarkdownWithOptions`
|
|
145
|
+
- `getTextContent`
|
|
146
|
+
- `getFlattenedText`
|
|
147
|
+
- `MarkdownParserModule`
|
|
148
|
+
- Components:
|
|
149
|
+
- `Markdown`
|
|
150
|
+
- `MarkdownStream`
|
|
151
|
+
- Hooks and sessions:
|
|
152
|
+
- `useMarkdownSession`
|
|
153
|
+
- `useStream`
|
|
154
|
+
- `createMarkdownSession`
|
|
155
|
+
- Context:
|
|
156
|
+
- `MarkdownContext`
|
|
157
|
+
- `useMarkdownContext`
|
|
158
|
+
- Theme:
|
|
159
|
+
- `defaultMarkdownTheme`
|
|
160
|
+
- `minimalMarkdownTheme`
|
|
161
|
+
- `mergeThemes`
|
|
162
|
+
- Built-in renderers:
|
|
163
|
+
- `Heading`, `Paragraph`, `Link`, `Blockquote`, `HorizontalRule`
|
|
164
|
+
- `CodeBlock`, `InlineCode`
|
|
165
|
+
- `List`, `ListItem`, `TaskListItem`
|
|
166
|
+
- `TableRenderer`, `Image`, `MathInline`, `MathBlock`
|
|
167
|
+
- Types:
|
|
168
|
+
- `MarkdownNode`, `ParserOptions`, `MarkdownParser`
|
|
169
|
+
- `MarkdownProps`, `AstTransform`, `MarkdownPlugin`, `MarkdownStreamProps`, `MarkdownVirtualizationOptions`
|
|
170
|
+
- `CustomRenderers`, `CustomRenderer`, `CustomRendererProps`
|
|
171
|
+
- `NodeRendererProps`, `BaseCustomRendererProps`, `EnhancedRendererProps`
|
|
172
|
+
- `HeadingRendererProps`, `LinkRendererProps`, `ImageRendererProps`
|
|
173
|
+
- `CodeBlockRendererProps`, `InlineCodeRendererProps`
|
|
174
|
+
- `ListRendererProps`, `TaskListItemRendererProps`
|
|
175
|
+
- `LinkPressHandler`, `MarkdownContextValue`
|
|
176
|
+
- `MarkdownTheme`, `PartialMarkdownTheme`, `NodeStyleOverrides`, `StylingStrategy`
|
|
177
|
+
- `MarkdownSession`
|
|
178
|
+
|
|
179
|
+
### Headless Entry (`react-native-nitro-markdown/headless`)
|
|
180
|
+
|
|
181
|
+
Exports only parser-related API (`parseMarkdown`, `parseMarkdownWithOptions`, `extractPlainText`, `extractPlainTextWithOptions`, `getTextContent`, `getFlattenedText`, types). Use this when you do not need built-in UI rendering.
|
|
182
|
+
|
|
183
|
+
## Component API
|
|
184
|
+
|
|
185
|
+
## `Markdown`
|
|
93
186
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
- `MarkdownStream` + `useMarkdownSession` for streaming tokens
|
|
98
|
-
- `headless` API for custom rendering or data processing
|
|
99
|
-
|
|
100
|
-
### 1) Parsing options (GFM and Math)
|
|
187
|
+
```tsx
|
|
188
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
189
|
+
```
|
|
101
190
|
|
|
102
|
-
|
|
191
|
+
Demo usage:
|
|
192
|
+
|
|
193
|
+
- `apps/example/app/render-default.tsx`
|
|
194
|
+
- `apps/example/app/render-default-styles.tsx`
|
|
195
|
+
- `apps/example/app/render-custom.tsx`
|
|
196
|
+
|
|
197
|
+
| Prop | Type | Default | Description |
|
|
198
|
+
| --------------------- | ---------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
199
|
+
| `children` | `string` | required | Markdown input string. |
|
|
200
|
+
| `options` | `ParserOptions` | `undefined` | Parser flags (`gfm`, `math`). |
|
|
201
|
+
| `plugins` | `MarkdownPlugin[]` | `undefined` | Optional parser plugin hooks (`beforeParse`, `afterParse`). |
|
|
202
|
+
| `sourceAst` | `MarkdownNode` | `undefined` | Pre-parsed AST. When provided, native parse is skipped. |
|
|
203
|
+
| `astTransform` | `AstTransform` | `undefined` | Transform hook applied after plugins, before rendering and `onParseComplete`. |
|
|
204
|
+
| `renderers` | `CustomRenderers` | `{}` | Per-node custom renderers. |
|
|
205
|
+
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` or `minimalMarkdownTheme` | Theme token overrides. |
|
|
206
|
+
| `styles` | `NodeStyleOverrides` | `undefined` | Per-node style overrides. |
|
|
207
|
+
| `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Base styling preset. |
|
|
208
|
+
| `style` | `StyleProp<ViewStyle>` | `undefined` | Container style for the root `View`. |
|
|
209
|
+
| `onParsingInProgress` | `() => void` | `undefined` | Called when parse inputs change. |
|
|
210
|
+
| `onParseComplete` | `(result) => void` | `undefined` | Called with `{ raw, ast, text }` after successful parse. |
|
|
211
|
+
| `onLinkPress` | `LinkPressHandler` | `undefined` | Intercepts link press before default open behavior. Return `false` to block default open. |
|
|
212
|
+
| `virtualize` | `boolean \| "auto"` | `false` | Enables top-level block virtualization. Use `"auto"` to activate by block threshold. |
|
|
213
|
+
| `virtualizationMinBlocks` | `number` | `40` | Minimum top-level block count before virtualization activates. |
|
|
214
|
+
| `virtualization` | `MarkdownVirtualizationOptions` | `undefined` | Optional FlatList tuning (`windowSize`, `initialNumToRender`, batching, clipping). |
|
|
215
|
+
|
|
216
|
+
Notes:
|
|
217
|
+
|
|
218
|
+
- Parse failures are caught and rendered as a fallback message (`Error parsing markdown`).
|
|
219
|
+
- `text` in `onParseComplete` is produced by `getFlattenedText(ast)`.
|
|
220
|
+
- `astTransform` should be wrapped with `useCallback` to avoid unnecessary re-parses.
|
|
221
|
+
- `astTransform` is a post-parse AST rewrite hook. It does not add parser syntax support and is not a markdown-it plugin API.
|
|
222
|
+
- Plugin pipeline order is: `beforeParse` -> parse/sourceAst -> `afterParse` -> `astTransform` -> render.
|
|
223
|
+
- Tables render immediately with estimated column widths, then refine widths after layout measurement to improve reliability on slower layout cycles.
|
|
224
|
+
- For very large markdown content, enable `virtualize` to avoid mounting all top-level blocks at once.
|
|
225
|
+
- `virtualize="auto"` enables threshold-driven virtualization while keeping small markdown renders on plain `View` trees.
|
|
226
|
+
|
|
227
|
+
### Virtualization example (large docs)
|
|
103
228
|
|
|
104
229
|
```tsx
|
|
105
|
-
<Markdown
|
|
230
|
+
<Markdown
|
|
231
|
+
virtualize="auto"
|
|
232
|
+
virtualizationMinBlocks={30}
|
|
233
|
+
virtualization={{
|
|
234
|
+
initialNumToRender: 10,
|
|
235
|
+
maxToRenderPerBatch: 10,
|
|
236
|
+
windowSize: 8,
|
|
237
|
+
updateCellsBatchingPeriod: 16,
|
|
238
|
+
removeClippedSubviews: true,
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
{content}
|
|
242
|
+
</Markdown>
|
|
106
243
|
```
|
|
107
244
|
|
|
108
|
-
|
|
109
|
-
- `math` enables `$...$` and `$$...$$` parsing into math nodes.
|
|
245
|
+
Virtualization notes:
|
|
110
246
|
|
|
111
|
-
|
|
247
|
+
- Keep `Markdown` as the primary vertical scroller when `virtualize` is enabled.
|
|
248
|
+
- Avoid nesting it inside another vertical `ScrollView`, or virtualization effectiveness drops.
|
|
112
249
|
|
|
113
|
-
|
|
250
|
+
### AST transform example
|
|
114
251
|
|
|
115
252
|
```tsx
|
|
116
|
-
import {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
253
|
+
import { useCallback } from "react";
|
|
254
|
+
import { Markdown, type AstTransform } from "react-native-nitro-markdown";
|
|
255
|
+
|
|
256
|
+
const astTransform = useCallback<AstTransform>((ast) => {
|
|
257
|
+
const transformNode = (node: Parameters<AstTransform>[0]) => ({
|
|
258
|
+
...node,
|
|
259
|
+
content:
|
|
260
|
+
node.type === "text"
|
|
261
|
+
? (node.content ?? "").replace(/:wink:/g, "😉")
|
|
262
|
+
: node.content,
|
|
263
|
+
children: node.children?.map(transformNode),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return transformNode(ast);
|
|
267
|
+
}, []);
|
|
268
|
+
|
|
269
|
+
<Markdown astTransform={astTransform}>{"Hello :wink:"}</Markdown>;
|
|
129
270
|
```
|
|
130
271
|
|
|
131
|
-
|
|
272
|
+
### Plugin pipeline example (`beforeParse` + `afterParse`)
|
|
132
273
|
|
|
133
|
-
|
|
274
|
+
```tsx
|
|
275
|
+
import { Markdown, type MarkdownPlugin } from "react-native-nitro-markdown";
|
|
134
276
|
|
|
135
|
-
|
|
277
|
+
const plugins: MarkdownPlugin[] = [
|
|
278
|
+
{
|
|
279
|
+
name: "rewrite-before-parse",
|
|
280
|
+
beforeParse: (input) => input.replace(/:rocket:/g, "ROCKET_TOKEN"),
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "rewrite-after-parse",
|
|
284
|
+
afterParse: (ast) => {
|
|
285
|
+
const visit = (node: typeof ast): typeof ast => ({
|
|
286
|
+
...node,
|
|
287
|
+
content:
|
|
288
|
+
node.type === "text"
|
|
289
|
+
? (node.content ?? "").replace(/ROCKET_TOKEN/g, "🚀")
|
|
290
|
+
: node.content,
|
|
291
|
+
children: node.children?.map(visit),
|
|
292
|
+
});
|
|
293
|
+
return visit(ast);
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
];
|
|
136
297
|
|
|
137
|
-
|
|
138
|
-
<Markdown
|
|
139
|
-
styles={{
|
|
140
|
-
heading: { color: "#0ea5e9", fontWeight: "900" },
|
|
141
|
-
code_block: { backgroundColor: "#e2e8f0", borderRadius: 16 },
|
|
142
|
-
blockquote: { borderLeftColor: "#0ea5e9" },
|
|
143
|
-
}}
|
|
144
|
-
>
|
|
145
|
-
{content}
|
|
146
|
-
</Markdown>
|
|
298
|
+
<Markdown plugins={plugins}>{"Launch :rocket:"}</Markdown>;
|
|
147
299
|
```
|
|
148
300
|
|
|
149
|
-
###
|
|
150
|
-
|
|
151
|
-
Provide a custom renderer for any node type. You get pre-mapped props for common values.
|
|
301
|
+
### `sourceAst` example (skip parsing in render)
|
|
152
302
|
|
|
153
303
|
```tsx
|
|
154
304
|
import {
|
|
155
305
|
Markdown,
|
|
156
|
-
|
|
157
|
-
type HeadingRendererProps,
|
|
158
|
-
type CodeBlockRendererProps,
|
|
306
|
+
parseMarkdownWithOptions,
|
|
159
307
|
} from "react-native-nitro-markdown";
|
|
160
308
|
|
|
161
|
-
const
|
|
162
|
-
heading: ({ level, children }: HeadingRendererProps) => (
|
|
163
|
-
<MyHeading level={level}>{children}</MyHeading>
|
|
164
|
-
),
|
|
165
|
-
code_block: ({ content, language }: CodeBlockRendererProps) => (
|
|
166
|
-
<CodeBlock content={content} language={language} />
|
|
167
|
-
),
|
|
168
|
-
};
|
|
309
|
+
const sourceAst = parseMarkdownWithOptions(content, { gfm: true, math: true });
|
|
169
310
|
|
|
170
|
-
<Markdown
|
|
311
|
+
<Markdown sourceAst={sourceAst}>
|
|
312
|
+
{"children is ignored when sourceAst is provided"}
|
|
313
|
+
</Markdown>;
|
|
171
314
|
```
|
|
172
315
|
|
|
173
|
-
|
|
174
|
-
|
|
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.
|
|
316
|
+
### Parse lifecycle callbacks example
|
|
178
317
|
|
|
179
|
-
|
|
180
|
-
|
|
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` |
|
|
318
|
+
```tsx
|
|
319
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
190
320
|
|
|
191
|
-
|
|
321
|
+
<Markdown
|
|
322
|
+
onParsingInProgress={() => setIsParsing(true)}
|
|
323
|
+
onParseComplete={({ raw, ast, text }) => {
|
|
324
|
+
setIsParsing(false);
|
|
325
|
+
setWordCount(text.trim().split(/\s+/).length);
|
|
326
|
+
setLastRaw(raw);
|
|
327
|
+
setLastAst(ast);
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
{content}
|
|
331
|
+
</Markdown>;
|
|
332
|
+
```
|
|
192
333
|
|
|
193
|
-
|
|
334
|
+
## `MarkdownStream`
|
|
194
335
|
|
|
195
336
|
```tsx
|
|
196
|
-
import {
|
|
337
|
+
import { MarkdownStream } from "react-native-nitro-markdown";
|
|
197
338
|
```
|
|
198
339
|
|
|
199
|
-
|
|
340
|
+
`MarkdownStreamProps` extends `MarkdownProps` except `children`.
|
|
200
341
|
|
|
201
|
-
|
|
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`
|
|
342
|
+
Demo usage:
|
|
215
343
|
|
|
216
|
-
|
|
344
|
+
- `apps/example/app/render-stream.tsx`
|
|
217
345
|
|
|
218
|
-
|
|
346
|
+
| Prop | Type | Default | Description |
|
|
347
|
+
| ---------------------- | --------------------- | ------------ | ---------------------------------------------------------------------------------------- |
|
|
348
|
+
| `session` | `MarkdownSession` | required | Session object that supplies streamed text chunks. |
|
|
349
|
+
| `updateIntervalMs` | `number` | `50` | Flush interval when `updateStrategy="interval"`. |
|
|
350
|
+
| `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Update cadence (`setTimeout` vs `requestAnimationFrame`). |
|
|
351
|
+
| `useTransitionUpdates` | `boolean` | `false` | Applies `startTransition` to streamed UI updates. |
|
|
352
|
+
| `incrementalParsing` | `boolean` | `true` | Enables append-optimized incremental AST updates (falls back to full parse when unsafe). |
|
|
353
|
+
|
|
354
|
+
Notes:
|
|
355
|
+
|
|
356
|
+
- If any plugin defines `beforeParse`, `MarkdownStream` disables incremental AST mode for correctness.
|
|
357
|
+
- `MarkdownStream` consumes native session change ranges (`from`, `to`) and uses `getTextRange()` for contiguous appends to avoid full-buffer copies during token streams.
|
|
358
|
+
|
|
359
|
+
### Streaming Example
|
|
219
360
|
|
|
220
361
|
```tsx
|
|
221
362
|
import { useEffect } from "react";
|
|
222
|
-
import {
|
|
363
|
+
import {
|
|
364
|
+
MarkdownStream,
|
|
365
|
+
useMarkdownSession,
|
|
366
|
+
} from "react-native-nitro-markdown";
|
|
223
367
|
|
|
224
|
-
export function
|
|
368
|
+
export function StreamingExample() {
|
|
225
369
|
const session = useMarkdownSession();
|
|
226
370
|
|
|
227
371
|
useEffect(() => {
|
|
228
|
-
session.getSession()
|
|
372
|
+
const s = session.getSession();
|
|
373
|
+
s.append("# Streaming\n");
|
|
374
|
+
s.append("This text arrives in chunks.");
|
|
375
|
+
|
|
229
376
|
return () => session.clear();
|
|
230
377
|
}, [session]);
|
|
231
378
|
|
|
@@ -240,279 +387,419 @@ export function AIResponseStream() {
|
|
|
240
387
|
}
|
|
241
388
|
```
|
|
242
389
|
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
390
|
+
## Hooks and Session API
|
|
248
391
|
|
|
249
|
-
|
|
392
|
+
## `createMarkdownSession()`
|
|
250
393
|
|
|
251
|
-
|
|
394
|
+
Creates and returns a native `MarkdownSession` instance.
|
|
252
395
|
|
|
253
396
|
```tsx
|
|
254
|
-
import {
|
|
255
|
-
parseMarkdown,
|
|
256
|
-
parseMarkdownWithOptions,
|
|
257
|
-
getTextContent,
|
|
258
|
-
getFlattenedText,
|
|
259
|
-
} from "react-native-nitro-markdown/headless";
|
|
397
|
+
import { createMarkdownSession } from "react-native-nitro-markdown";
|
|
260
398
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const normalized = getFlattenedText(ast);
|
|
399
|
+
const session = createMarkdownSession();
|
|
400
|
+
session.append("hello");
|
|
264
401
|
```
|
|
265
402
|
|
|
266
|
-
|
|
403
|
+
`MarkdownSession` methods:
|
|
404
|
+
|
|
405
|
+
| Method | Signature | Description |
|
|
406
|
+
| --- | --- | --- |
|
|
407
|
+
| `append` | `(chunk: string) => number` | Appends text and returns new UTF-16 length. |
|
|
408
|
+
| `clear` | `() => void` | Clears buffer and emits a reset range event (`0, 0`). |
|
|
409
|
+
| `getAllText` | `() => string` | Returns full session text. |
|
|
410
|
+
| `getLength` | `() => number` | Returns current UTF-16 text length without materializing a copy. |
|
|
411
|
+
| `getTextRange` | `(from: number, to: number) => string` | Returns a substring range for delta-driven streaming updates. |
|
|
412
|
+
| `addListener` | `(listener: (from: number, to: number) => void) => () => void` | Subscribes to mutation range events and returns an unsubscribe function. |
|
|
413
|
+
| `highlightPosition` | `number` | Mutable cursor used by stream highlight workflows. |
|
|
267
414
|
|
|
268
|
-
|
|
415
|
+
Demo usage:
|
|
416
|
+
|
|
417
|
+
- Referenced in `apps/example/app/render-stream.tsx` sample markdown content and used directly in README examples.
|
|
418
|
+
|
|
419
|
+
Manual session + stream rendering:
|
|
269
420
|
|
|
270
421
|
```tsx
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
422
|
+
import {
|
|
423
|
+
createMarkdownSession,
|
|
424
|
+
MarkdownStream,
|
|
425
|
+
} from "react-native-nitro-markdown";
|
|
426
|
+
|
|
427
|
+
const session = createMarkdownSession();
|
|
428
|
+
session.append("# Hello\n");
|
|
429
|
+
session.append("Streaming content...");
|
|
430
|
+
|
|
431
|
+
<MarkdownStream session={session} updateStrategy="raf" />;
|
|
278
432
|
```
|
|
279
433
|
|
|
280
|
-
|
|
434
|
+
## `useMarkdownSession()`
|
|
281
435
|
|
|
282
|
-
|
|
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.
|
|
436
|
+
Creates and owns one `MarkdownSession` for a component lifecycle.
|
|
285
437
|
|
|
286
|
-
|
|
438
|
+
Returns:
|
|
287
439
|
|
|
288
|
-
|
|
440
|
+
| Field | Type | Description |
|
|
441
|
+
| ---------------- | ---------------------------- | ------------------------------------------------------------- |
|
|
442
|
+
| `getSession` | `() => MarkdownSession` | Returns the stable native session instance. |
|
|
443
|
+
| `isStreaming` | `boolean` | Stateful flag for app-level streaming UI. |
|
|
444
|
+
| `setIsStreaming` | `(value: boolean) => void` | Setter for `isStreaming`. |
|
|
445
|
+
| `stop` | `() => void` | Sets `isStreaming` to `false`. |
|
|
446
|
+
| `clear` | `() => void` | Clears session content and resets `highlightPosition` to `0`. |
|
|
447
|
+
| `setHighlight` | `(position: number) => void` | Sets `session.highlightPosition`. |
|
|
289
448
|
|
|
290
|
-
|
|
449
|
+
Demo usage:
|
|
291
450
|
|
|
292
|
-
|
|
293
|
-
import { Markdown, type LinkRendererProps } from "react-native-nitro-markdown";
|
|
294
|
-
import { Text, Linking } from "react-native";
|
|
451
|
+
- `apps/example/app/render-stream.tsx`
|
|
295
452
|
|
|
296
|
-
|
|
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
|
-
),
|
|
309
|
-
};
|
|
453
|
+
## `useStream(timestamps?)`
|
|
310
454
|
|
|
311
|
-
|
|
312
|
-
|
|
455
|
+
Builds on `useMarkdownSession` and adds timeline sync helpers.
|
|
456
|
+
|
|
457
|
+
- `timestamps` type: `Record<number, number>` where key = word/token index, value = timestamp in ms.
|
|
458
|
+
- `sync(currentTimeMs)` computes highlight position from timestamp map.
|
|
459
|
+
- Uses optimized lookup for monotonic timelines and handles non-monotonic maps safely.
|
|
460
|
+
|
|
461
|
+
Additional returned fields:
|
|
462
|
+
|
|
463
|
+
| Field | Type | Description |
|
|
464
|
+
| -------------- | --------------------------------- | ----------------------------------------- |
|
|
465
|
+
| `isPlaying` | `boolean` | Playback state for timed streaming. |
|
|
466
|
+
| `setIsPlaying` | `(value: boolean) => void` | Setter for `isPlaying`. |
|
|
467
|
+
| `sync` | `(currentTimeMs: number) => void` | Applies timeline-based highlight updates. |
|
|
313
468
|
|
|
314
|
-
|
|
469
|
+
Example:
|
|
315
470
|
|
|
316
471
|
```tsx
|
|
317
|
-
|
|
318
|
-
|
|
472
|
+
const stream = useStream({
|
|
473
|
+
0: 0,
|
|
474
|
+
1: 500,
|
|
475
|
+
2: 1000,
|
|
476
|
+
});
|
|
319
477
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
{(title || alt) && (
|
|
325
|
-
<Text style={{ marginTop: 6, opacity: 0.6 }}>{title || alt}</Text>
|
|
326
|
-
)}
|
|
327
|
-
</View>
|
|
328
|
-
),
|
|
329
|
-
};
|
|
478
|
+
// e.g. in media time update callback:
|
|
479
|
+
stream.sync(currentTimeMs);
|
|
480
|
+
|
|
481
|
+
<MarkdownStream session={stream.getSession()} />;
|
|
330
482
|
```
|
|
331
483
|
|
|
332
|
-
|
|
484
|
+
Demo usage:
|
|
485
|
+
|
|
486
|
+
- README examples (timed playback scenario)
|
|
487
|
+
|
|
488
|
+
## Headless API
|
|
333
489
|
|
|
334
490
|
```tsx
|
|
335
|
-
import {
|
|
491
|
+
import {
|
|
492
|
+
parseMarkdown,
|
|
493
|
+
parseMarkdownWithOptions,
|
|
494
|
+
extractPlainText,
|
|
495
|
+
extractPlainTextWithOptions,
|
|
496
|
+
getTextContent,
|
|
497
|
+
getFlattenedText,
|
|
498
|
+
} from "react-native-nitro-markdown/headless";
|
|
499
|
+
```
|
|
336
500
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
501
|
+
| Function | Signature | Description |
|
|
502
|
+
| -------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
503
|
+
| `parseMarkdown` | `(text: string) => MarkdownNode` | Parses markdown using default parser settings. |
|
|
504
|
+
| `parseMarkdownWithOptions` | `(text: string, options: ParserOptions) => MarkdownNode` | Parses markdown with `gfm` and/or `math` flags. |
|
|
505
|
+
| `extractPlainText` | `(text: string) => string` | Parses and returns normalized plain text directly from native parser. |
|
|
506
|
+
| `extractPlainTextWithOptions` | `(text: string, options: ParserOptions) => string` | Same as above with parser flags. |
|
|
507
|
+
| `getTextContent` | `(node: MarkdownNode) => string` | Concatenates text recursively without layout normalization. |
|
|
508
|
+
| `getFlattenedText` | `(node: MarkdownNode) => string` | Returns normalized plain text with paragraph and block separators. |
|
|
509
|
+
|
|
510
|
+
### Parser Options
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
type ParserOptions = {
|
|
514
|
+
gfm?: boolean;
|
|
515
|
+
math?: boolean;
|
|
340
516
|
};
|
|
341
517
|
```
|
|
342
518
|
|
|
343
|
-
|
|
519
|
+
Example:
|
|
344
520
|
|
|
345
521
|
```tsx
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
theme={{ colors: { text: "#e2e8f0", link: "#38bdf8" } }}
|
|
351
|
-
>
|
|
352
|
-
{content}
|
|
353
|
-
</Markdown>;
|
|
522
|
+
const ast = parseMarkdownWithOptions(markdown, {
|
|
523
|
+
gfm: true, // tables, task lists, strikethrough
|
|
524
|
+
math: true, // inline/block LaTeX
|
|
525
|
+
});
|
|
354
526
|
```
|
|
355
527
|
|
|
356
|
-
###
|
|
528
|
+
### `MarkdownParserModule` (low-level Nitro access)
|
|
529
|
+
|
|
530
|
+
Use this only when you want direct method access (`parse`, `parseWithOptions`, `extractPlainText`, `extractPlainTextWithOptions`).
|
|
357
531
|
|
|
358
532
|
```tsx
|
|
359
|
-
import {
|
|
533
|
+
import {
|
|
534
|
+
MarkdownParserModule,
|
|
535
|
+
type ParserOptions,
|
|
536
|
+
} from "react-native-nitro-markdown/headless";
|
|
360
537
|
|
|
361
|
-
const
|
|
362
|
-
const
|
|
538
|
+
const options: ParserOptions = { gfm: true };
|
|
539
|
+
const jsonAst = JSON.parse(
|
|
540
|
+
MarkdownParserModule.parseWithOptions("# Hello", options),
|
|
541
|
+
);
|
|
363
542
|
```
|
|
364
543
|
|
|
365
|
-
|
|
544
|
+
## Custom Renderer API
|
|
366
545
|
|
|
367
|
-
##
|
|
546
|
+
## `renderers` prop contract
|
|
368
547
|
|
|
369
|
-
|
|
548
|
+
`CustomRenderers` is:
|
|
370
549
|
|
|
371
|
-
```
|
|
372
|
-
|
|
550
|
+
```ts
|
|
551
|
+
type CustomRenderers = Partial<Record<MarkdownNode["type"], CustomRenderer>>;
|
|
373
552
|
```
|
|
374
553
|
|
|
375
|
-
|
|
554
|
+
`CustomRenderer` receives `EnhancedRendererProps` and returns:
|
|
376
555
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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 }` |
|
|
556
|
+
- React node to override default rendering
|
|
557
|
+
- `undefined` to fallback to built-in renderer
|
|
558
|
+
- `null` to render nothing
|
|
388
559
|
|
|
389
|
-
|
|
560
|
+
`EnhancedRendererProps` always includes:
|
|
390
561
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
562
|
+
- `node`: current `MarkdownNode`
|
|
563
|
+
- `children`: pre-rendered node children
|
|
564
|
+
- `Renderer`: recursive renderer component for nested custom rendering
|
|
394
565
|
|
|
395
|
-
|
|
566
|
+
And conditionally includes mapped fields by node type:
|
|
396
567
|
|
|
397
|
-
|
|
|
398
|
-
|
|
|
399
|
-
| `
|
|
400
|
-
| `
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
568
|
+
| Node type | Extra mapped fields |
|
|
569
|
+
| ---------------- | --------------------- |
|
|
570
|
+
| `heading` | `level` |
|
|
571
|
+
| `link` | `href`, `title` |
|
|
572
|
+
| `image` | `url`, `alt`, `title` |
|
|
573
|
+
| `code_block` | `content`, `language` |
|
|
574
|
+
| `code_inline` | `content` |
|
|
575
|
+
| `list` | `ordered`, `start` |
|
|
576
|
+
| `task_list_item` | `checked` |
|
|
403
577
|
|
|
404
|
-
###
|
|
578
|
+
### Example: Custom heading + code block
|
|
405
579
|
|
|
406
580
|
```tsx
|
|
407
|
-
import {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
- `useStream(timestamps)` adds a simple sync helper for time-based streaming.
|
|
581
|
+
import {
|
|
582
|
+
Markdown,
|
|
583
|
+
type HeadingRendererProps,
|
|
584
|
+
type CodeBlockRendererProps,
|
|
585
|
+
} from "react-native-nitro-markdown";
|
|
413
586
|
|
|
414
|
-
|
|
587
|
+
const renderers = {
|
|
588
|
+
heading: ({ level, children }: HeadingRendererProps) => (
|
|
589
|
+
<MyHeading level={level}>{children}</MyHeading>
|
|
590
|
+
),
|
|
591
|
+
code_block: ({ language, content }: CodeBlockRendererProps) => (
|
|
592
|
+
<MyCode language={language} content={content} />
|
|
593
|
+
),
|
|
594
|
+
};
|
|
415
595
|
|
|
416
|
-
|
|
417
|
-
import { parseMarkdown, parseMarkdownWithOptions, getTextContent, getFlattenedText } from "react-native-nitro-markdown/headless";
|
|
596
|
+
<Markdown renderers={renderers}>{content}</Markdown>;
|
|
418
597
|
```
|
|
419
598
|
|
|
420
|
-
###
|
|
599
|
+
### `useMarkdownContext` example (inside custom renderer tree)
|
|
421
600
|
|
|
422
601
|
```tsx
|
|
602
|
+
import { Text } from "react-native";
|
|
423
603
|
import {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
604
|
+
Markdown,
|
|
605
|
+
useMarkdownContext,
|
|
606
|
+
type CustomRendererProps,
|
|
427
607
|
} from "react-native-nitro-markdown";
|
|
608
|
+
|
|
609
|
+
function ThemedParagraph({ children }: Pick<CustomRendererProps, "children">) {
|
|
610
|
+
const { theme } = useMarkdownContext();
|
|
611
|
+
return <Text style={{ color: theme.colors.text }}>{children}</Text>;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
<Markdown
|
|
615
|
+
renderers={{
|
|
616
|
+
paragraph: ({ children }: CustomRendererProps) => (
|
|
617
|
+
<ThemedParagraph>{children}</ThemedParagraph>
|
|
618
|
+
),
|
|
619
|
+
}}
|
|
620
|
+
>
|
|
621
|
+
{content}
|
|
622
|
+
</Markdown>;
|
|
428
623
|
```
|
|
429
624
|
|
|
430
|
-
|
|
625
|
+
## Link Handling Behavior
|
|
626
|
+
|
|
627
|
+
Default link renderer behavior:
|
|
628
|
+
|
|
629
|
+
1. Trims incoming href.
|
|
630
|
+
2. Calls `onLinkPress(href)` if provided.
|
|
631
|
+
3. Stops if handler returns `false`.
|
|
632
|
+
4. Allows only protocol-based links with these schemes:
|
|
633
|
+
- `http:`
|
|
634
|
+
- `https:`
|
|
635
|
+
- `mailto:`
|
|
636
|
+
- `tel:`
|
|
637
|
+
- `sms:`
|
|
638
|
+
5. Uses `Linking.canOpenURL` before `Linking.openURL`.
|
|
639
|
+
|
|
640
|
+
Relative URLs and anchors are ignored by default open behavior, but you can handle them in `onLinkPress`.
|
|
641
|
+
|
|
642
|
+
## Theme API
|
|
643
|
+
|
|
644
|
+
## `MarkdownTheme`
|
|
431
645
|
|
|
432
646
|
```tsx
|
|
433
647
|
import type {
|
|
434
|
-
CustomRenderers,
|
|
435
|
-
NodeStyleOverrides,
|
|
436
648
|
MarkdownTheme,
|
|
437
649
|
PartialMarkdownTheme,
|
|
438
650
|
} from "react-native-nitro-markdown";
|
|
439
651
|
```
|
|
440
652
|
|
|
441
|
-
|
|
653
|
+
`MarkdownTheme` fields:
|
|
654
|
+
|
|
655
|
+
- `colors`
|
|
656
|
+
- `text`, `textMuted`, `heading`, `link`, `code`, `codeBackground`, `codeLanguage`
|
|
657
|
+
- `blockquote`, `border`, `surface`, `surfaceLight`, `accent`
|
|
658
|
+
- `tableBorder`, `tableHeader`, `tableHeaderText`, `tableRowEven`, `tableRowOdd`
|
|
659
|
+
- `spacing`: `xs`, `s`, `m`, `l`, `xl`
|
|
660
|
+
- `fontSizes`: `xs`, `s`, `m`, `l`, `xl`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
|
|
661
|
+
- `fontFamilies`: `regular`, `heading`, `mono`
|
|
662
|
+
- `headingWeight?`
|
|
663
|
+
- `borderRadius`: `s`, `m`, `l`
|
|
664
|
+
- `showCodeLanguage`
|
|
665
|
+
|
|
666
|
+
Helpers:
|
|
667
|
+
|
|
668
|
+
- `defaultMarkdownTheme`
|
|
669
|
+
- `minimalMarkdownTheme`
|
|
670
|
+
- `mergeThemes(base, partial)`
|
|
671
|
+
|
|
672
|
+
`NodeStyleOverrides` lets you override per-node styles:
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
type NodeStyleOverrides = Partial<
|
|
676
|
+
Record<MarkdownNode["type"], ViewStyle | TextStyle>
|
|
677
|
+
>;
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Example with `mergeThemes`:
|
|
442
681
|
|
|
443
|
-
|
|
682
|
+
```tsx
|
|
683
|
+
import {
|
|
684
|
+
Markdown,
|
|
685
|
+
defaultMarkdownTheme,
|
|
686
|
+
mergeThemes,
|
|
687
|
+
} from "react-native-nitro-markdown";
|
|
444
688
|
|
|
445
|
-
|
|
689
|
+
const theme = mergeThemes(defaultMarkdownTheme, {
|
|
690
|
+
colors: {
|
|
691
|
+
text: "#0f172a",
|
|
692
|
+
link: "#1d4ed8",
|
|
693
|
+
},
|
|
694
|
+
fontSizes: {
|
|
695
|
+
m: 16,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
<Markdown theme={theme}>{content}</Markdown>;
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## Built-in Renderer Components
|
|
703
|
+
|
|
704
|
+
Use these when composing custom renderer maps.
|
|
705
|
+
|
|
706
|
+
| Component | Key props |
|
|
707
|
+
| ---------------- | ------------------------------------------------ |
|
|
708
|
+
| `Heading` | `level`, `children`, `style` |
|
|
709
|
+
| `Paragraph` | `children`, `inListItem`, `style` |
|
|
710
|
+
| `Link` | `href`, `children`, `style` |
|
|
711
|
+
| `Blockquote` | `children`, `style` |
|
|
712
|
+
| `HorizontalRule` | `style` |
|
|
713
|
+
| `CodeBlock` | `language`, `content`, `node`, `style` |
|
|
714
|
+
| `InlineCode` | `content`, `node`, `children`, `style` |
|
|
715
|
+
| `List` | `ordered`, `start`, `depth`, `children`, `style` |
|
|
716
|
+
| `ListItem` | `children`, `index`, `ordered`, `start`, `style` |
|
|
717
|
+
| `TaskListItem` | `children`, `checked`, `style` |
|
|
718
|
+
| `TableRenderer` | `node`, `Renderer`, `style` |
|
|
719
|
+
| `Image` | `url`, `title`, `alt`, `Renderer`, `style` |
|
|
720
|
+
| `MathInline` | `content`, `style` |
|
|
721
|
+
| `MathBlock` | `content`, `style` |
|
|
722
|
+
|
|
723
|
+
## Supported AST Node Types
|
|
446
724
|
|
|
447
725
|
`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
726
|
|
|
449
|
-
|
|
727
|
+
Notes:
|
|
450
728
|
|
|
451
|
-
|
|
729
|
+
- `html_inline` and `html_block` are parsed but not rendered by default.
|
|
730
|
+
- Table internals (`table_head`, `table_body`, `table_row`, `table_cell`) are renderer internals; override `table` for custom table UI.
|
|
452
731
|
|
|
453
|
-
##
|
|
732
|
+
## Recipes
|
|
454
733
|
|
|
455
|
-
|
|
734
|
+
### Intercept links with `onLinkPress`
|
|
456
735
|
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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";
|
|
487
|
-
content?: string;
|
|
488
|
-
children?: MarkdownNode[];
|
|
489
|
-
level?: number;
|
|
490
|
-
href?: string;
|
|
491
|
-
title?: string;
|
|
492
|
-
alt?: string;
|
|
493
|
-
language?: string;
|
|
494
|
-
ordered?: boolean;
|
|
495
|
-
start?: number;
|
|
496
|
-
checked?: boolean;
|
|
497
|
-
align?: string;
|
|
498
|
-
isHeader?: boolean;
|
|
499
|
-
}
|
|
736
|
+
```tsx
|
|
737
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
738
|
+
|
|
739
|
+
<Markdown
|
|
740
|
+
onLinkPress={(href) => {
|
|
741
|
+
if (href.startsWith("/")) {
|
|
742
|
+
router.push(href);
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
}}
|
|
746
|
+
>
|
|
747
|
+
{content}
|
|
748
|
+
</Markdown>;
|
|
500
749
|
```
|
|
501
750
|
|
|
502
|
-
|
|
751
|
+
### Use headless mode to build search index
|
|
503
752
|
|
|
504
|
-
|
|
753
|
+
```tsx
|
|
754
|
+
import {
|
|
755
|
+
parseMarkdown,
|
|
756
|
+
getFlattenedText,
|
|
757
|
+
} from "react-native-nitro-markdown/headless";
|
|
505
758
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
759
|
+
const ast = parseMarkdown(content);
|
|
760
|
+
const searchableText = getFlattenedText(ast);
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Minimal styling baseline
|
|
764
|
+
|
|
765
|
+
```tsx
|
|
766
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
767
|
+
|
|
768
|
+
<Markdown
|
|
769
|
+
stylingStrategy="minimal"
|
|
770
|
+
theme={{
|
|
771
|
+
colors: {
|
|
772
|
+
text: "#0f172a",
|
|
773
|
+
link: "#1d4ed8",
|
|
774
|
+
},
|
|
775
|
+
}}
|
|
776
|
+
>
|
|
777
|
+
{content}
|
|
778
|
+
</Markdown>;
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
## Performance Guidance
|
|
782
|
+
|
|
783
|
+
- For streaming text, prefer `updateStrategy="raf"`.
|
|
784
|
+
- If you use interval strategy, `updateIntervalMs` between `50` and `100` is a good baseline.
|
|
785
|
+
- Batch `session.append(...)` calls instead of appending one character at a time.
|
|
786
|
+
- For large markdown documents, enable `virtualize` and tune `virtualization.windowSize` / `maxToRenderPerBatch`.
|
|
787
|
+
- Use the headless API if you do not need built-in renderers.
|
|
788
|
+
|
|
789
|
+
## Troubleshooting
|
|
510
790
|
|
|
511
|
-
|
|
791
|
+
- Math appears as plain code-style fallback:
|
|
792
|
+
- Install `react-native-mathjax-svg` and `react-native-svg`.
|
|
793
|
+
- iOS native build issues after install:
|
|
794
|
+
- Run `pod install` in your iOS project.
|
|
795
|
+
- Expo app does not load native module:
|
|
796
|
+
- Use a development build (`expo prebuild` + `expo run`), not Expo Go.
|
|
797
|
+
- Android heading font weight looks wrong:
|
|
798
|
+
- Set `theme.headingWeight` explicitly (for custom fonts without bold variants, use `"normal"`).
|
|
512
799
|
|
|
513
800
|
## Contributing
|
|
514
801
|
|
|
515
|
-
See `CONTRIBUTING.md
|
|
802
|
+
See `CONTRIBUTING.md`.
|
|
516
803
|
|
|
517
804
|
## License
|
|
518
805
|
|