react-native-nitro-markdown 0.5.2 → 0.5.4
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 +311 -669
- package/android/CMakeLists.txt +8 -1
- package/android/build.gradle +9 -2
- package/android/consumer-rules.pro +31 -0
- package/android/gradle.properties +2 -0
- package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +68 -22
- package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +6 -18
- package/cpp/bindings/HybridMarkdownParser.cpp +40 -12
- package/cpp/bindings/HybridMarkdownParser.hpp +4 -4
- package/cpp/bindings/HybridMarkdownSession.cpp +2 -0
- package/cpp/core/MD4CParser.cpp +147 -86
- package/cpp/core/MarkdownSessionCore.cpp +2 -0
- package/cpp/core/MarkdownTypes.hpp +1 -1
- package/ios/HybridMarkdownSession.swift +89 -46
- package/lib/commonjs/headless.js +34 -8
- package/lib/commonjs/headless.js.map +1 -1
- package/lib/commonjs/index.js +48 -38
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +1 -1
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/markdown.js +57 -16
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/blockquote.js +15 -13
- package/lib/commonjs/renderers/blockquote.js.map +1 -1
- package/lib/commonjs/renderers/code.js +58 -54
- package/lib/commonjs/renderers/code.js.map +1 -1
- package/lib/commonjs/renderers/heading.js +48 -46
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/horizontal-rule.js +10 -8
- package/lib/commonjs/renderers/horizontal-rule.js.map +1 -1
- package/lib/commonjs/renderers/image.js +18 -4
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/link.js +7 -2
- package/lib/commonjs/renderers/link.js.map +1 -1
- package/lib/commonjs/renderers/list.js +75 -68
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/math.js +8 -5
- package/lib/commonjs/renderers/math.js.map +1 -1
- package/lib/commonjs/renderers/paragraph.js +15 -13
- package/lib/commonjs/renderers/paragraph.js.map +1 -1
- package/lib/commonjs/renderers/style-cache.js +14 -0
- package/lib/commonjs/renderers/style-cache.js.map +1 -0
- package/lib/commonjs/renderers/table/cell-content.js +1 -1
- package/lib/commonjs/renderers/table/cell-content.js.map +1 -1
- package/lib/commonjs/renderers/table/index.js +17 -6
- package/lib/commonjs/renderers/table/index.js.map +1 -1
- package/lib/commonjs/theme.js +7 -7
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/commonjs/utils/code-highlight.js +24 -25
- package/lib/commonjs/utils/code-highlight.js.map +1 -1
- package/lib/module/headless.js +35 -7
- package/lib/module/headless.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +1 -1
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/markdown.js +58 -17
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/blockquote.js +15 -13
- package/lib/module/renderers/blockquote.js.map +1 -1
- package/lib/module/renderers/code.js +58 -54
- package/lib/module/renderers/code.js.map +1 -1
- package/lib/module/renderers/heading.js +48 -46
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/horizontal-rule.js +10 -8
- package/lib/module/renderers/horizontal-rule.js.map +1 -1
- package/lib/module/renderers/image.js +19 -5
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/link.js +7 -2
- package/lib/module/renderers/link.js.map +1 -1
- package/lib/module/renderers/list.js +75 -68
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/math.js +8 -5
- package/lib/module/renderers/math.js.map +1 -1
- package/lib/module/renderers/paragraph.js +15 -13
- package/lib/module/renderers/paragraph.js.map +1 -1
- package/lib/module/renderers/style-cache.js +10 -0
- package/lib/module/renderers/style-cache.js.map +1 -0
- package/lib/module/renderers/table/cell-content.js +1 -1
- package/lib/module/renderers/table/cell-content.js.map +1 -1
- package/lib/module/renderers/table/index.js +17 -6
- package/lib/module/renderers/table/index.js.map +1 -1
- package/lib/module/theme.js +7 -7
- package/lib/module/theme.js.map +1 -1
- package/lib/module/utils/code-highlight.js +24 -25
- package/lib/module/utils/code-highlight.js.map +1 -1
- package/lib/typescript/commonjs/Markdown.nitro.d.ts +1 -0
- package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/headless.d.ts +10 -2
- package/lib/typescript/commonjs/headless.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown.d.ts +8 -3
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/blockquote.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/paragraph.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/style-cache.d.ts +3 -0
- package/lib/typescript/commonjs/renderers/style-cache.d.ts.map +1 -0
- package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +4 -3
- package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/code-highlight.d.ts +1 -1
- package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -1
- package/lib/typescript/module/Markdown.nitro.d.ts +1 -0
- package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/module/headless.d.ts +10 -2
- package/lib/typescript/module/headless.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/markdown.d.ts +8 -3
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/blockquote.d.ts +1 -1
- package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/module/renderers/code.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.d.ts +1 -1
- package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/module/renderers/horizontal-rule.d.ts +1 -1
- package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/module/renderers/image.d.ts.map +1 -1
- package/lib/typescript/module/renderers/link.d.ts.map +1 -1
- package/lib/typescript/module/renderers/list.d.ts +1 -1
- package/lib/typescript/module/renderers/list.d.ts.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts +1 -1
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/renderers/paragraph.d.ts +1 -1
- package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/module/renderers/style-cache.d.ts +3 -0
- package/lib/typescript/module/renderers/style-cache.d.ts.map +1 -0
- package/lib/typescript/module/renderers/table/cell-content.d.ts +4 -3
- package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -1
- package/lib/typescript/module/renderers/table/index.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/lib/typescript/module/utils/code-highlight.d.ts +1 -1
- package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -1
- package/nitro.json +12 -3
- package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +2 -2
- package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +2 -2
- package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +2 -0
- package/nitrogen/generated/shared/c++/ParserOptions.hpp +6 -2
- package/package.json +8 -6
- package/react-native-nitro-markdown.podspec +3 -0
- package/src/Markdown.nitro.ts +1 -0
- package/src/headless.ts +58 -8
- package/src/index.ts +16 -2
- package/src/markdown-stream.tsx +1 -0
- package/src/markdown.tsx +108 -23
- package/src/renderers/blockquote.tsx +22 -17
- package/src/renderers/code.tsx +76 -57
- package/src/renderers/heading.tsx +60 -54
- package/src/renderers/horizontal-rule.tsx +17 -12
- package/src/renderers/image.tsx +24 -5
- package/src/renderers/link.tsx +8 -2
- package/src/renderers/list.tsx +93 -74
- package/src/renderers/math.tsx +14 -5
- package/src/renderers/paragraph.tsx +22 -17
- package/src/renderers/style-cache.ts +14 -0
- package/src/renderers/table/cell-content.tsx +15 -4
- package/src/renderers/table/index.tsx +30 -13
- package/src/theme.ts +34 -14
- package/src/utils/code-highlight.ts +133 -44
package/README.md
CHANGED
|
@@ -4,59 +4,57 @@
|
|
|
4
4
|
|
|
5
5
|
# react-native-nitro-markdown
|
|
6
6
|
|
|
7
|
-
Native Markdown parsing and rendering for React Native.
|
|
7
|
+
Native Markdown parsing and rendering for React Native, powered by [md4c](https://github.com/mity/md4c) (C++) through [Nitro Modules](https://github.com/mrousavy/nitro) (JSI).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Features
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
11
|
+
- **Native C++ parser** -- synchronous parsing via JSI with minimal JS thread overhead
|
|
12
|
+
- **Full rendering pipeline** -- parser + renderer + streaming session in one package
|
|
13
|
+
- **GFM support** -- tables, strikethrough, task lists, autolinks
|
|
14
|
+
- **Math rendering** -- inline and block LaTeX via `react-native-mathjax-svg` (optional)
|
|
15
|
+
- **Opt-in HTML AST nodes** -- preserve raw HTML as `html_inline` / `html_block` for custom renderers
|
|
16
|
+
- **Large document support** -- top-level block virtualization and cached renderer styles
|
|
17
|
+
- **Code highlighting** -- built-in JS/TS, Python, and Bash tokenizer with custom highlighter support
|
|
18
|
+
- **Headless API** -- parse markdown and extract text without any UI
|
|
19
|
+
- **Streaming** -- incremental rendering for chat/LLM token streams
|
|
20
|
+
- **Customizable** -- themes, per-node style overrides, custom renderers, AST transforms, plugin pipeline
|
|
18
21
|
|
|
19
22
|
## Requirements
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- `react-native-svg >=13.0.0`
|
|
24
|
+
| Dependency | Version |
|
|
25
|
+
|---|---|
|
|
26
|
+
| React Native | `>=0.75.0` |
|
|
27
|
+
| react-native-nitro-modules | `>=0.35.4` |
|
|
28
|
+
| react-native-mathjax-svg *(optional)* | `>=0.9.0` |
|
|
29
|
+
| react-native-svg *(optional, for math)* | `>=13.0.0` |
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
With npm:
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Optional math support:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
bun add react-native-mathjax-svg react-native-svg
|
|
36
|
+
npm install react-native-nitro-markdown react-native-nitro-modules
|
|
37
|
+
cd ios && pod install
|
|
41
38
|
```
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
With Bun:
|
|
44
41
|
|
|
45
42
|
```bash
|
|
43
|
+
bun add react-native-nitro-markdown react-native-nitro-modules
|
|
46
44
|
cd ios && pod install
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
For math rendering:
|
|
50
48
|
|
|
51
49
|
```bash
|
|
52
|
-
|
|
53
|
-
bunx expo prebuild
|
|
50
|
+
npm install react-native-mathjax-svg react-native-svg
|
|
54
51
|
```
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
**Expo** (development build):
|
|
57
54
|
|
|
58
55
|
```bash
|
|
59
|
-
|
|
56
|
+
npx expo install react-native-nitro-markdown react-native-nitro-modules
|
|
57
|
+
npx expo prebuild
|
|
60
58
|
```
|
|
61
59
|
|
|
62
60
|
## Quick Start
|
|
@@ -73,343 +71,248 @@ export function Example() {
|
|
|
73
71
|
}
|
|
74
72
|
```
|
|
75
73
|
|
|
76
|
-
##
|
|
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
|
-
| `onError` | Structured error reporting for parse and plugin failures | README examples |
|
|
117
|
-
| `highlightCode` / `defaultHighlighter` | Opt-in syntax highlighting for code blocks | README examples |
|
|
118
|
-
| `tableOptions` | Per-instance table column and measurement tuning | README examples |
|
|
119
|
-
| `stripSourceOffsets` | Remove source position data from parsed AST | README examples |
|
|
120
|
-
| `MarkdownSession.reset` / `MarkdownSession.replace` | Full and partial buffer mutation | README examples |
|
|
121
|
-
|
|
122
|
-
## Feature Index
|
|
123
|
-
|
|
124
|
-
Use this table as a quick map from feature -> API -> demo usage.
|
|
125
|
-
|
|
126
|
-
| Feature | API | What it does | Demo |
|
|
127
|
-
| ------------------------ | --------------------------------------------------- | --------------------------------------------------------- | ------------------------------------- |
|
|
128
|
-
| Basic markdown rendering | `Markdown` | Parse and render markdown in one component | `app/render-default.tsx` |
|
|
129
|
-
| Parser flags | `options` (`gfm`, `math`) | Enable GFM and math parsing | `app/render-default.tsx` |
|
|
130
|
-
| Plugin pipeline | `plugins` (`beforeParse`, `afterParse`) | Rewrite markdown input or AST around parse | README examples |
|
|
131
|
-
| AST transform | `astTransform` | Post-parse AST rewrite before render | `app/render-custom.tsx` |
|
|
132
|
-
| Pre-parsed AST render | `sourceAst` | Skip parsing during render and render existing AST | README examples |
|
|
133
|
-
| Parse lifecycle | `onParsingInProgress`, `onParseComplete` | Observe parse start/finish and consume normalized text | README examples |
|
|
134
|
-
| Link interception | `onLinkPress` | Override default URL open behavior | README examples |
|
|
135
|
-
| Large doc virtualization | `virtualize`, `virtualizationMinBlocks` | Virtualizes top-level blocks for very large markdown docs | README examples |
|
|
136
|
-
| Streaming markdown | `MarkdownStream` + `createMarkdownSession` | Render incrementally appended markdown content | `app/render-stream.tsx` |
|
|
137
|
-
| Timed highlight sync | `useStream(timestamps)` | Sync highlight position to playback timeline | README examples |
|
|
138
|
-
| Headless parsing | `parseMarkdown`, `parseMarkdownWithOptions` | Parse markdown without built-in UI | `app/index.tsx` + README examples |
|
|
139
|
-
| Custom node rendering | `renderers` + built-in renderer components | Replace specific node UI while preserving parser behavior | `app/render-custom.tsx` |
|
|
140
|
-
| Styling and theme | `theme`, `styles`, `stylingStrategy`, `mergeThemes` | Control visual tokens and per-node styles | `app/render-default-styles.tsx` |
|
|
141
|
-
| Syntax highlighting | `highlightCode`, `defaultHighlighter`, `codeTokenColors` | Opt-in token-colored code block rendering | README examples |
|
|
142
|
-
| Plugin priority | `MarkdownPlugin.priority` | Control plugin execution order | README examples |
|
|
143
|
-
| Error reporting | `onError` | Observe parse and plugin failures without crashing | README examples |
|
|
144
|
-
| Table tuning | `tableOptions` | Configure column width and measurement debounce | README examples |
|
|
145
|
-
| AST cleanup | `stripSourceOffsets` | Remove source positions from AST for compact storage | README examples |
|
|
146
|
-
| Session mutation | `MarkdownSession.reset`, `MarkdownSession.replace` | Replace or partially edit the session text buffer | README examples |
|
|
147
|
-
| Low-level parser access | `MarkdownParserModule` | Direct access to Nitro parser methods | README examples |
|
|
74
|
+
## API Reference
|
|
148
75
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
### Main Entry (`react-native-nitro-markdown`)
|
|
152
|
-
|
|
153
|
-
- Parser and headless helpers:
|
|
154
|
-
- `parseMarkdown`
|
|
155
|
-
- `parseMarkdownWithOptions`
|
|
156
|
-
- `getTextContent`
|
|
157
|
-
- `getFlattenedText`
|
|
158
|
-
- `MarkdownParserModule`
|
|
159
|
-
- Components:
|
|
160
|
-
- `Markdown`
|
|
161
|
-
- `MarkdownStream`
|
|
162
|
-
- Hooks and sessions:
|
|
163
|
-
- `useMarkdownSession`
|
|
164
|
-
- `useStream`
|
|
165
|
-
- `createMarkdownSession`
|
|
166
|
-
- Context:
|
|
167
|
-
- `MarkdownContext`
|
|
168
|
-
- `useMarkdownContext`
|
|
169
|
-
- Theme:
|
|
170
|
-
- `defaultMarkdownTheme`
|
|
171
|
-
- `minimalMarkdownTheme`
|
|
172
|
-
- `mergeThemes`
|
|
173
|
-
- Built-in renderers:
|
|
174
|
-
- `Heading`, `Paragraph`, `Link`, `Blockquote`, `HorizontalRule`
|
|
175
|
-
- `CodeBlock`, `InlineCode`
|
|
176
|
-
- `List`, `ListItem`, `TaskListItem`
|
|
177
|
-
- `TableRenderer`, `Image`, `MathInline`, `MathBlock`
|
|
178
|
-
- Syntax highlighting:
|
|
179
|
-
- `defaultHighlighter`
|
|
180
|
-
- `CodeHighlighter`, `HighlightedToken`, `TokenType`
|
|
181
|
-
- Types:
|
|
182
|
-
- `MarkdownNode`, `ParserOptions`, `MarkdownParser`
|
|
183
|
-
- `MarkdownProps`, `AstTransform`, `MarkdownPlugin`, `MarkdownStreamProps`, `MarkdownVirtualizationOptions`
|
|
184
|
-
- `CustomRenderers`, `CustomRenderer`, `CustomRendererProps`
|
|
185
|
-
- `NodeRendererProps`, `BaseCustomRendererProps`, `EnhancedRendererProps`
|
|
186
|
-
- `HeadingRendererProps`, `LinkRendererProps`, `ImageRendererProps`
|
|
187
|
-
- `CodeBlockRendererProps`, `InlineCodeRendererProps`
|
|
188
|
-
- `ListRendererProps`, `TaskListItemRendererProps`
|
|
189
|
-
- `LinkPressHandler`, `MarkdownContextValue`
|
|
190
|
-
- `MarkdownTheme`, `PartialMarkdownTheme`, `NodeStyleOverrides`, `StylingStrategy`
|
|
191
|
-
- `MarkdownSession`
|
|
192
|
-
|
|
193
|
-
### Headless Entry (`react-native-nitro-markdown/headless`)
|
|
194
|
-
|
|
195
|
-
Exports only parser-related API (`parseMarkdown`, `parseMarkdownWithOptions`, `extractPlainText`, `extractPlainTextWithOptions`, `getTextContent`, `getFlattenedText`, `stripSourceOffsets`, types). Use this when you do not need built-in UI rendering.
|
|
196
|
-
|
|
197
|
-
## Component API
|
|
198
|
-
|
|
199
|
-
## `Markdown`
|
|
76
|
+
### Parser Options
|
|
200
77
|
|
|
201
|
-
|
|
202
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
203
|
-
```
|
|
78
|
+
`ParserOptions` controls native parser extensions. Defaults are conservative for HTML and feature-complete for Markdown extensions.
|
|
204
79
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
| Prop | Type | Default | Description |
|
|
212
|
-
| --------------------- | ---------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
213
|
-
| `children` | `string` | required | Markdown input string. |
|
|
214
|
-
| `options` | `ParserOptions` | `undefined` | Parser flags (`gfm`, `math`). |
|
|
215
|
-
| `plugins` | `MarkdownPlugin[]` | `undefined` | Optional parser plugin hooks (`beforeParse`, `afterParse`). |
|
|
216
|
-
| `sourceAst` | `MarkdownNode` | `undefined` | Pre-parsed AST. When provided, native parse is skipped. |
|
|
217
|
-
| `astTransform` | `AstTransform` | `undefined` | Transform hook applied after plugins, before rendering and `onParseComplete`. |
|
|
218
|
-
| `renderers` | `CustomRenderers` | `{}` | Per-node custom renderers. |
|
|
219
|
-
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` or `minimalMarkdownTheme` | Theme token overrides. |
|
|
220
|
-
| `styles` | `NodeStyleOverrides` | `undefined` | Per-node style overrides. |
|
|
221
|
-
| `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Base styling preset. |
|
|
222
|
-
| `style` | `StyleProp<ViewStyle>` | `undefined` | Container style for the root `View`. |
|
|
223
|
-
| `onParsingInProgress` | `() => void` | `undefined` | Called when parse inputs change. |
|
|
224
|
-
| `onParseComplete` | `(result) => void` | `undefined` | Called with `{ raw, ast, text }` after successful parse. |
|
|
225
|
-
| `onLinkPress` | `LinkPressHandler` | `undefined` | Intercepts link press before default open behavior. Return `false` to block default open. |
|
|
226
|
-
| `onError` | `(error, phase, pluginName?) => void` | `undefined` | Called when a parse or plugin error occurs. `phase` is `'parse'`, `'before-plugin'`, or `'after-plugin'`. |
|
|
227
|
-
| `highlightCode` | `boolean \| CodeHighlighter` | `undefined` | Enables syntax highlighting for code blocks. Pass `true` for the built-in highlighter or a custom `CodeHighlighter` function. |
|
|
228
|
-
| `tableOptions` | `{ minColumnWidth?: number; measurementStabilizeMs?: number }` | `undefined` | Table layout tuning. `minColumnWidth` defaults to `60`; `measurementStabilizeMs` defaults to `140`. |
|
|
229
|
-
| `virtualize` | `boolean \| "auto"` | `false` | Enables top-level block virtualization. Use `"auto"` to activate by block threshold. |
|
|
230
|
-
| `virtualizationMinBlocks` | `number` | `40` | Minimum top-level block count before virtualization activates. |
|
|
231
|
-
| `virtualization` | `MarkdownVirtualizationOptions` | `undefined` | Optional FlatList tuning (`windowSize`, `initialNumToRender`, batching, clipping). |
|
|
232
|
-
|
|
233
|
-
Notes:
|
|
234
|
-
|
|
235
|
-
- Parse failures are caught and rendered as a fallback message (`Error parsing markdown`).
|
|
236
|
-
- `text` in `onParseComplete` is produced by `getFlattenedText(ast)`.
|
|
237
|
-
- `astTransform` should be wrapped with `useCallback` to avoid unnecessary re-parses.
|
|
238
|
-
- `astTransform` is a post-parse AST rewrite hook. It does not add parser syntax support and is not a markdown-it plugin API.
|
|
239
|
-
- Plugin pipeline order is: `beforeParse` plugins (sorted by `priority` desc) -> parse/sourceAst -> `afterParse` plugins (sorted by `priority` desc) -> `astTransform` -> render.
|
|
240
|
-
- `onError` is called per-failure; individual plugin failures do not abort the full pipeline.
|
|
241
|
-
- Tables render immediately with estimated column widths, then refine widths after layout measurement to improve reliability on slower layout cycles.
|
|
242
|
-
- For very large markdown content, enable `virtualize` to avoid mounting all top-level blocks at once.
|
|
243
|
-
- `virtualize="auto"` enables threshold-driven virtualization while keeping small markdown renders on plain `View` trees.
|
|
244
|
-
|
|
245
|
-
### Virtualization example (large docs)
|
|
80
|
+
| Option | Default | Enables | Notes |
|
|
81
|
+
|---|---:|---|---|
|
|
82
|
+
| `gfm` | `true` | Tables, strikethrough, task lists, permissive autolinks | Set `false` for stricter CommonMark-style parsing |
|
|
83
|
+
| `math` | `true` | Inline and display LaTeX spans | Rendering falls back to plain styled text unless optional math deps are installed |
|
|
84
|
+
| `html` | `false` | `html_inline` and `html_block` AST nodes | Raw HTML is not rendered by default; provide custom renderers |
|
|
246
85
|
|
|
247
86
|
```tsx
|
|
248
|
-
<Markdown
|
|
249
|
-
virtualize="auto"
|
|
250
|
-
virtualizationMinBlocks={30}
|
|
251
|
-
virtualization={{
|
|
252
|
-
initialNumToRender: 10,
|
|
253
|
-
maxToRenderPerBatch: 10,
|
|
254
|
-
windowSize: 8,
|
|
255
|
-
updateCellsBatchingPeriod: 16,
|
|
256
|
-
removeClippedSubviews: true,
|
|
257
|
-
}}
|
|
258
|
-
>
|
|
87
|
+
<Markdown options={{ gfm: true, math: true, html: false }}>
|
|
259
88
|
{content}
|
|
260
89
|
</Markdown>
|
|
261
90
|
```
|
|
262
91
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
- Keep `Markdown` as the primary vertical scroller when `virtualize` is enabled.
|
|
266
|
-
- Avoid nesting it inside another vertical `ScrollView`, or virtualization effectiveness drops.
|
|
92
|
+
### `<Markdown>`
|
|
267
93
|
|
|
268
|
-
|
|
94
|
+
The main component. Parses a markdown string and renders it.
|
|
269
95
|
|
|
270
96
|
```tsx
|
|
271
|
-
import {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const astTransform = useCallback<AstTransform>((ast) => {
|
|
275
|
-
const transformNode = (node: Parameters<AstTransform>[0]) => ({
|
|
276
|
-
...node,
|
|
277
|
-
content:
|
|
278
|
-
node.type === "text"
|
|
279
|
-
? (node.content ?? "").replace(/:wink:/g, "😉")
|
|
280
|
-
: node.content,
|
|
281
|
-
children: node.children?.map(transformNode),
|
|
282
|
-
});
|
|
97
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
98
|
+
```
|
|
283
99
|
|
|
284
|
-
|
|
285
|
-
|
|
100
|
+
| Prop | Type | Default | Description |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `children` | `string` | required | Markdown input string |
|
|
103
|
+
| `options` | `ParserOptions` | -- | Parser flags (`gfm`, `math`, `html`) |
|
|
104
|
+
| `plugins` | `MarkdownPlugin[]` | -- | Plugin hooks (`beforeParse`, `afterParse`) |
|
|
105
|
+
| `sourceAst` | `MarkdownNode` | -- | Pre-parsed AST; skips native parse when provided |
|
|
106
|
+
| `astTransform` | `AstTransform` | -- | Post-parse AST rewrite before render |
|
|
107
|
+
| `renderers` | `CustomRenderers` | `{}` | Per-node custom renderer overrides |
|
|
108
|
+
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
|
|
109
|
+
| `styles` | `NodeStyleOverrides` | -- | Per-node style overrides |
|
|
110
|
+
| `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Base styling preset |
|
|
111
|
+
| `style` | `StyleProp<ViewStyle>` | -- | Container style |
|
|
112
|
+
| `onLinkPress` | `LinkPressHandler` | -- | Intercept link presses; return `false` to block default open |
|
|
113
|
+
| `onParsingInProgress` | `() => void` | -- | Called when parse inputs change |
|
|
114
|
+
| `onParseComplete` | `(result) => void` | -- | Called with `{ raw, ast, text }` after parse |
|
|
115
|
+
| `onError` | `(error, phase, pluginName?) => void` | -- | Error handler for parse/plugin failures |
|
|
116
|
+
| `highlightCode` | `boolean \| CodeHighlighter` | -- | Enable syntax highlighting for code blocks |
|
|
117
|
+
| `tableOptions` | `{ minColumnWidth?; measurementStabilizeMs? }` | -- | Table layout tuning |
|
|
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
|
+
### `<MarkdownStream>`
|
|
125
|
+
|
|
126
|
+
Renders markdown from a streaming session. Extends `MarkdownProps` (minus `children`).
|
|
286
127
|
|
|
287
|
-
|
|
128
|
+
```tsx
|
|
129
|
+
import { MarkdownStream } from "react-native-nitro-markdown";
|
|
288
130
|
```
|
|
289
131
|
|
|
290
|
-
|
|
132
|
+
| Prop | Type | Default | Description |
|
|
133
|
+
|---|---|---|---|
|
|
134
|
+
| `session` | `MarkdownSession` | required | Session supplying streamed text |
|
|
135
|
+
| `updateIntervalMs` | `number` | `50` | Flush interval for `"interval"` strategy |
|
|
136
|
+
| `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Update cadence |
|
|
137
|
+
| `useTransitionUpdates` | `boolean` | `false` | Wrap updates in `startTransition` |
|
|
138
|
+
| `incrementalParsing` | `boolean` | `true` | Append-optimized incremental AST updates |
|
|
291
139
|
|
|
292
|
-
|
|
293
|
-
import { Markdown, type MarkdownPlugin } from "react-native-nitro-markdown";
|
|
140
|
+
### `MarkdownSession`
|
|
294
141
|
|
|
295
|
-
|
|
296
|
-
{
|
|
297
|
-
name: "rewrite-before-parse",
|
|
298
|
-
priority: 10, // runs before lower-priority plugins
|
|
299
|
-
beforeParse: (input) => input.replace(/:rocket:/g, "ROCKET_TOKEN"),
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
name: "rewrite-after-parse",
|
|
303
|
-
afterParse: (ast) => {
|
|
304
|
-
const visit = (node: typeof ast): typeof ast => ({
|
|
305
|
-
...node,
|
|
306
|
-
content:
|
|
307
|
-
node.type === "text"
|
|
308
|
-
? (node.content ?? "").replace(/ROCKET_TOKEN/g, "🚀")
|
|
309
|
-
: node.content,
|
|
310
|
-
children: node.children?.map(visit),
|
|
311
|
-
});
|
|
312
|
-
return visit(ast);
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
];
|
|
142
|
+
A native text buffer with change listeners, used for streaming.
|
|
316
143
|
|
|
317
|
-
|
|
144
|
+
```tsx
|
|
145
|
+
import { createMarkdownSession } from "react-native-nitro-markdown";
|
|
146
|
+
|
|
147
|
+
const session = createMarkdownSession();
|
|
148
|
+
session.append("# Hello\n");
|
|
149
|
+
session.append("Streaming content...");
|
|
318
150
|
```
|
|
319
151
|
|
|
320
|
-
|
|
152
|
+
| Method | Signature | Description |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `append` | `(chunk: string) => number` | Append text, returns new UTF-16 length |
|
|
155
|
+
| `clear` | `() => void` | Clear buffer, emit reset event |
|
|
156
|
+
| `reset` | `(text: string) => void` | Replace full buffer content |
|
|
157
|
+
| `replace` | `(from, to, text) => number` | Partial buffer mutation |
|
|
158
|
+
| `getAllText` | `() => string` | Get full session text |
|
|
159
|
+
| `getLength` | `() => number` | Get UTF-16 length without copy |
|
|
160
|
+
| `getTextRange` | `(from, to) => string` | Get substring range |
|
|
161
|
+
| `addListener` | `(listener) => () => void` | Subscribe to mutation events; returns unsubscribe |
|
|
162
|
+
| `highlightPosition` | `number` | Mutable cursor for stream highlight |
|
|
163
|
+
|
|
164
|
+
### `useMarkdownSession()`
|
|
165
|
+
|
|
166
|
+
Creates and owns a `MarkdownSession` for a component lifecycle.
|
|
321
167
|
|
|
322
168
|
```tsx
|
|
323
|
-
import {
|
|
169
|
+
import { useMarkdownSession } from "react-native-nitro-markdown";
|
|
324
170
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.warn(`[markdown] ${phase} error`, { error, pluginName });
|
|
328
|
-
}}
|
|
329
|
-
plugins={plugins}
|
|
330
|
-
>
|
|
331
|
-
{content}
|
|
332
|
-
</Markdown>;
|
|
171
|
+
const { getSession, isStreaming, setIsStreaming, stop, clear, setHighlight } =
|
|
172
|
+
useMarkdownSession();
|
|
333
173
|
```
|
|
334
174
|
|
|
335
|
-
###
|
|
175
|
+
### `useStream(timestamps?)`
|
|
176
|
+
|
|
177
|
+
Extends `useMarkdownSession` with timeline sync for timed playback.
|
|
336
178
|
|
|
337
179
|
```tsx
|
|
338
|
-
import {
|
|
180
|
+
import { useStream } from "react-native-nitro-markdown";
|
|
339
181
|
|
|
340
|
-
|
|
341
|
-
|
|
182
|
+
const stream = useStream({ 0: 0, 1: 500, 2: 1000 });
|
|
183
|
+
stream.sync(currentTimeMs);
|
|
342
184
|
|
|
343
|
-
|
|
344
|
-
|
|
185
|
+
<MarkdownStream session={stream.getSession()} />;
|
|
186
|
+
```
|
|
345
187
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
};
|
|
188
|
+
### Headless API
|
|
189
|
+
|
|
190
|
+
Parse markdown without any UI. Available from both entry points.
|
|
350
191
|
|
|
351
|
-
|
|
192
|
+
```tsx
|
|
193
|
+
import {
|
|
194
|
+
parseMarkdown,
|
|
195
|
+
parseMarkdownWithOptions,
|
|
196
|
+
extractPlainText,
|
|
197
|
+
getTextContent,
|
|
198
|
+
getFlattenedText,
|
|
199
|
+
stripSourceOffsets,
|
|
200
|
+
} from "react-native-nitro-markdown/headless";
|
|
352
201
|
```
|
|
353
202
|
|
|
354
|
-
|
|
203
|
+
| Function | Description |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `parseMarkdown(text, options?)` | Parse to AST (options: `{ gfm?, math?, html? }`) |
|
|
206
|
+
| `parseMarkdownWithOptions(text, options)` | Parse with explicit options |
|
|
207
|
+
| `extractPlainText(text)` | Parse and return plain text from native parser |
|
|
208
|
+
| `extractPlainTextWithOptions(text, options)` | Same with parser flags |
|
|
209
|
+
| `getTextContent(node)` | Concatenate text recursively (no normalization) |
|
|
210
|
+
| `getFlattenedText(node)` | Normalized plain text with block separators |
|
|
211
|
+
| `stripSourceOffsets(node)` | Remove `beg`/`end` fields from AST |
|
|
212
|
+
|
|
213
|
+
### Custom Renderers
|
|
214
|
+
|
|
215
|
+
Override rendering for specific node types:
|
|
355
216
|
|
|
356
217
|
```tsx
|
|
357
218
|
import {
|
|
358
219
|
Markdown,
|
|
359
|
-
|
|
220
|
+
type HeadingRendererProps,
|
|
221
|
+
type CodeBlockRendererProps,
|
|
360
222
|
} from "react-native-nitro-markdown";
|
|
361
223
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
224
|
+
<Markdown
|
|
225
|
+
renderers={{
|
|
226
|
+
heading: ({ level, children }: HeadingRendererProps) => (
|
|
227
|
+
<MyHeading level={level}>{children}</MyHeading>
|
|
228
|
+
),
|
|
229
|
+
code_block: ({ language, content }: CodeBlockRendererProps) => (
|
|
230
|
+
<MyCode language={language} content={content} />
|
|
231
|
+
),
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
{content}
|
|
366
235
|
</Markdown>;
|
|
367
236
|
```
|
|
368
237
|
|
|
369
|
-
|
|
238
|
+
Renderers receive `EnhancedRendererProps` with `node`, `children`, and `Renderer` (for recursive rendering). Node-specific props are mapped automatically:
|
|
239
|
+
|
|
240
|
+
| Node type | Extra props |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `heading` | `level` |
|
|
243
|
+
| `link` | `href`, `title` |
|
|
244
|
+
| `image` | `url`, `alt`, `title` |
|
|
245
|
+
| `code_block` | `content`, `language` |
|
|
246
|
+
| `code_inline` | `content` |
|
|
247
|
+
| `list` | `ordered`, `start` |
|
|
248
|
+
| `task_list_item` | `checked` |
|
|
249
|
+
| `html_inline`, `html_block` | Read raw HTML from `node.content` |
|
|
250
|
+
|
|
251
|
+
Return `undefined` to fall back to the built-in renderer, or `null` to render nothing.
|
|
252
|
+
|
|
253
|
+
#### Rendering HTML Nodes
|
|
254
|
+
|
|
255
|
+
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.
|
|
370
256
|
|
|
371
257
|
```tsx
|
|
258
|
+
import { Text, View } from "react-native";
|
|
372
259
|
import { Markdown } from "react-native-nitro-markdown";
|
|
373
260
|
|
|
374
261
|
<Markdown
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
262
|
+
options={{ html: true }}
|
|
263
|
+
renderers={{
|
|
264
|
+
html_inline: ({ node }) => {
|
|
265
|
+
if (node.content === "<br>") return <Text>{"\n"}</Text>;
|
|
266
|
+
return null;
|
|
267
|
+
},
|
|
268
|
+
html_block: ({ node }) => {
|
|
269
|
+
if (!node.content?.includes('data-kind="release-note"')) return null;
|
|
270
|
+
return (
|
|
271
|
+
<View>
|
|
272
|
+
<Text>Release note</Text>
|
|
273
|
+
</View>
|
|
274
|
+
);
|
|
275
|
+
},
|
|
381
276
|
}}
|
|
382
277
|
>
|
|
383
278
|
{content}
|
|
384
279
|
</Markdown>;
|
|
385
280
|
```
|
|
386
281
|
|
|
387
|
-
|
|
282
|
+
Do not pass untrusted HTML directly into a WebView from a renderer. Sanitize or map known tags/attributes to native components.
|
|
388
283
|
|
|
389
|
-
|
|
390
|
-
import { MarkdownStream } from "react-native-nitro-markdown";
|
|
391
|
-
```
|
|
284
|
+
### Theme API
|
|
392
285
|
|
|
393
|
-
|
|
286
|
+
```tsx
|
|
287
|
+
import {
|
|
288
|
+
Markdown,
|
|
289
|
+
defaultMarkdownTheme,
|
|
290
|
+
minimalMarkdownTheme,
|
|
291
|
+
mergeThemes,
|
|
292
|
+
} from "react-native-nitro-markdown";
|
|
394
293
|
|
|
395
|
-
|
|
294
|
+
const theme = mergeThemes(defaultMarkdownTheme, {
|
|
295
|
+
colors: { text: "#0f172a", link: "#1d4ed8" },
|
|
296
|
+
fontSizes: { m: 16 },
|
|
297
|
+
});
|
|
396
298
|
|
|
397
|
-
|
|
299
|
+
<Markdown theme={theme}>{content}</Markdown>;
|
|
300
|
+
```
|
|
398
301
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
302
|
+
`MarkdownTheme` includes:
|
|
303
|
+
- **colors** -- `text`, `heading`, `link`, `code`, `codeBackground`, `blockquote`, `border`, `surface`, table colors, and optional `codeTokenColors` for syntax highlighting
|
|
304
|
+
- **spacing** -- `xs`, `s`, `m`, `l`, `xl`
|
|
305
|
+
- **fontSizes** -- `xs` through `xl`, plus `h1`-`h6`
|
|
306
|
+
- **fontFamilies** -- `regular`, `heading`, `mono`
|
|
307
|
+
- **borderRadius** -- `s`, `m`, `l`
|
|
308
|
+
- **headingWeight** -- optional font weight override
|
|
309
|
+
- **showCodeLanguage** -- show/hide language label on code blocks
|
|
406
310
|
|
|
407
|
-
|
|
311
|
+
Use `stylingStrategy="minimal"` for a bare baseline, or `NodeStyleOverrides` for per-node style overrides (text nodes accept `TextStyle`, container nodes accept `ViewStyle`).
|
|
408
312
|
|
|
409
|
-
|
|
410
|
-
- `MarkdownStream` consumes native session change ranges (`from`, `to`) and uses `getTextRange()` for contiguous appends to avoid full-buffer copies during token streams.
|
|
313
|
+
## Examples
|
|
411
314
|
|
|
412
|
-
### Streaming
|
|
315
|
+
### Streaming
|
|
413
316
|
|
|
414
317
|
```tsx
|
|
415
318
|
import { useEffect } from "react";
|
|
@@ -425,7 +328,6 @@ export function StreamingExample() {
|
|
|
425
328
|
const s = session.getSession();
|
|
426
329
|
s.append("# Streaming\n");
|
|
427
330
|
s.append("This text arrives in chunks.");
|
|
428
|
-
|
|
429
331
|
return () => session.clear();
|
|
430
332
|
}, [session]);
|
|
431
333
|
|
|
@@ -434,371 +336,113 @@ export function StreamingExample() {
|
|
|
434
336
|
session={session.getSession()}
|
|
435
337
|
options={{ gfm: true }}
|
|
436
338
|
updateStrategy="raf"
|
|
437
|
-
useTransitionUpdates
|
|
438
339
|
/>
|
|
439
340
|
);
|
|
440
341
|
}
|
|
441
342
|
```
|
|
442
343
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
## `createMarkdownSession()`
|
|
446
|
-
|
|
447
|
-
Creates and returns a native `MarkdownSession` instance.
|
|
344
|
+
### AST Transform
|
|
448
345
|
|
|
449
346
|
```tsx
|
|
450
|
-
import {
|
|
451
|
-
|
|
452
|
-
const session = createMarkdownSession();
|
|
453
|
-
session.append("hello");
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
`MarkdownSession` methods:
|
|
457
|
-
|
|
458
|
-
| Method | Signature | Description |
|
|
459
|
-
| --- | --- | --- |
|
|
460
|
-
| `append` | `(chunk: string) => number` | Appends text and returns new UTF-16 length. |
|
|
461
|
-
| `clear` | `() => void` | Clears buffer and emits a reset range event (`0, 0`). |
|
|
462
|
-
| `reset` | `(text: string) => void` | Replaces full buffer content and emits a full-range change event. |
|
|
463
|
-
| `replace` | `(from: number, to: number, text: string) => number` | Partial buffer mutation; returns new total UTF-16 length. |
|
|
464
|
-
| `getAllText` | `() => string` | Returns full session text. |
|
|
465
|
-
| `getLength` | `() => number` | Returns current UTF-16 text length without materializing a copy. |
|
|
466
|
-
| `getTextRange` | `(from: number, to: number) => string` | Returns a substring range for delta-driven streaming updates. |
|
|
467
|
-
| `addListener` | `(listener: (from: number, to: number) => void) => () => void` | Subscribes to mutation range events and returns an unsubscribe function. |
|
|
468
|
-
| `highlightPosition` | `number` | Mutable cursor used by stream highlight workflows. |
|
|
469
|
-
|
|
470
|
-
Demo usage:
|
|
471
|
-
|
|
472
|
-
- Referenced in `apps/example/app/render-stream.tsx` sample markdown content and used directly in README examples.
|
|
473
|
-
|
|
474
|
-
Manual session + stream rendering:
|
|
475
|
-
|
|
476
|
-
```tsx
|
|
477
|
-
import {
|
|
478
|
-
createMarkdownSession,
|
|
479
|
-
MarkdownStream,
|
|
480
|
-
} from "react-native-nitro-markdown";
|
|
347
|
+
import { useCallback } from "react";
|
|
348
|
+
import { Markdown, type AstTransform } from "react-native-nitro-markdown";
|
|
481
349
|
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
350
|
+
const transform = useCallback<AstTransform>((ast) => {
|
|
351
|
+
const visit = (node: Parameters<AstTransform>[0]): typeof node => ({
|
|
352
|
+
...node,
|
|
353
|
+
content:
|
|
354
|
+
node.type === "text"
|
|
355
|
+
? (node.content ?? "").replace(/:wink:/g, "😉")
|
|
356
|
+
: node.content,
|
|
357
|
+
children: node.children?.map(visit),
|
|
358
|
+
});
|
|
359
|
+
return visit(ast);
|
|
360
|
+
}, []);
|
|
485
361
|
|
|
486
|
-
<
|
|
362
|
+
<Markdown astTransform={transform}>{"Hello :wink:"}</Markdown>;
|
|
487
363
|
```
|
|
488
364
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
Creates and owns one `MarkdownSession` for a component lifecycle.
|
|
492
|
-
|
|
493
|
-
Returns:
|
|
494
|
-
|
|
495
|
-
| Field | Type | Description |
|
|
496
|
-
| ---------------- | ---------------------------- | ------------------------------------------------------------- |
|
|
497
|
-
| `getSession` | `() => MarkdownSession` | Returns the stable native session instance. |
|
|
498
|
-
| `isStreaming` | `boolean` | Stateful flag for app-level streaming UI. |
|
|
499
|
-
| `setIsStreaming` | `(value: boolean) => void` | Setter for `isStreaming`. |
|
|
500
|
-
| `stop` | `() => void` | Sets `isStreaming` to `false`. |
|
|
501
|
-
| `clear` | `() => void` | Clears session content and resets `highlightPosition` to `0`. |
|
|
502
|
-
| `setHighlight` | `(position: number) => void` | Sets `session.highlightPosition`. |
|
|
503
|
-
|
|
504
|
-
Demo usage:
|
|
505
|
-
|
|
506
|
-
- `apps/example/app/render-stream.tsx`
|
|
507
|
-
|
|
508
|
-
## `useStream(timestamps?)`
|
|
509
|
-
|
|
510
|
-
Builds on `useMarkdownSession` and adds timeline sync helpers.
|
|
511
|
-
|
|
512
|
-
- `timestamps` type: `Record<number, number>` where key = word/token index, value = timestamp in ms.
|
|
513
|
-
- `sync(currentTimeMs)` computes highlight position from timestamp map.
|
|
514
|
-
- Uses optimized lookup for monotonic timelines and handles non-monotonic maps safely.
|
|
515
|
-
|
|
516
|
-
Additional returned fields:
|
|
517
|
-
|
|
518
|
-
| Field | Type | Description |
|
|
519
|
-
| -------------- | --------------------------------- | ----------------------------------------- |
|
|
520
|
-
| `isPlaying` | `boolean` | Playback state for timed streaming. |
|
|
521
|
-
| `setIsPlaying` | `(value: boolean) => void` | Setter for `isPlaying`. |
|
|
522
|
-
| `sync` | `(currentTimeMs: number) => void` | Applies timeline-based highlight updates. |
|
|
523
|
-
|
|
524
|
-
Example:
|
|
365
|
+
### Plugin Pipeline
|
|
525
366
|
|
|
526
367
|
```tsx
|
|
527
|
-
|
|
528
|
-
0: 0,
|
|
529
|
-
1: 500,
|
|
530
|
-
2: 1000,
|
|
531
|
-
});
|
|
368
|
+
import { Markdown, type MarkdownPlugin } from "react-native-nitro-markdown";
|
|
532
369
|
|
|
533
|
-
|
|
534
|
-
|
|
370
|
+
const plugins: MarkdownPlugin[] = [
|
|
371
|
+
{
|
|
372
|
+
name: "rewrite-before-parse",
|
|
373
|
+
priority: 10,
|
|
374
|
+
beforeParse: (input) => input.replace(/:rocket:/g, "ROCKET_TOKEN"),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "rewrite-after-parse",
|
|
378
|
+
afterParse: (ast) => {
|
|
379
|
+
const visit = (node: typeof ast): typeof ast => ({
|
|
380
|
+
...node,
|
|
381
|
+
content:
|
|
382
|
+
node.type === "text"
|
|
383
|
+
? (node.content ?? "").replace(/ROCKET_TOKEN/g, "🚀")
|
|
384
|
+
: node.content,
|
|
385
|
+
children: node.children?.map(visit),
|
|
386
|
+
});
|
|
387
|
+
return visit(ast);
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
];
|
|
535
391
|
|
|
536
|
-
<
|
|
392
|
+
<Markdown plugins={plugins}>{"Launch :rocket:"}</Markdown>;
|
|
537
393
|
```
|
|
538
394
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
- README examples (timed playback scenario)
|
|
542
|
-
|
|
543
|
-
## Headless API
|
|
395
|
+
### Pre-parsed AST
|
|
544
396
|
|
|
545
397
|
```tsx
|
|
546
|
-
import {
|
|
547
|
-
parseMarkdown,
|
|
548
|
-
parseMarkdownWithOptions,
|
|
549
|
-
extractPlainText,
|
|
550
|
-
extractPlainTextWithOptions,
|
|
551
|
-
getTextContent,
|
|
552
|
-
getFlattenedText,
|
|
553
|
-
stripSourceOffsets,
|
|
554
|
-
} from "react-native-nitro-markdown/headless";
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
| Function | Signature | Description |
|
|
558
|
-
| -------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
559
|
-
| `parseMarkdown` | `(text: string) => MarkdownNode` | Parses markdown using default parser settings. |
|
|
560
|
-
| `parseMarkdownWithOptions` | `(text: string, options: ParserOptions) => MarkdownNode` | Parses markdown with `gfm` and/or `math` flags. |
|
|
561
|
-
| `extractPlainText` | `(text: string) => string` | Parses and returns normalized plain text directly from native parser. |
|
|
562
|
-
| `extractPlainTextWithOptions` | `(text: string, options: ParserOptions) => string` | Same as above with parser flags. |
|
|
563
|
-
| `getTextContent` | `(node: MarkdownNode) => string` | Concatenates text recursively without layout normalization. |
|
|
564
|
-
| `getFlattenedText` | `(node: MarkdownNode) => string` | Returns normalized plain text with paragraph and block separators. |
|
|
565
|
-
| `stripSourceOffsets` | `(node: MarkdownNode) => MarkdownNode` | Recursively removes `beg`/`end` source position fields. Useful for compact serialization or snapshot testing. |
|
|
566
|
-
|
|
567
|
-
### Parser Options
|
|
568
|
-
|
|
569
|
-
```ts
|
|
570
|
-
type ParserOptions = {
|
|
571
|
-
gfm?: boolean;
|
|
572
|
-
math?: boolean;
|
|
573
|
-
};
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
Example:
|
|
398
|
+
import { Markdown, parseMarkdownWithOptions } from "react-native-nitro-markdown";
|
|
577
399
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
400
|
+
const ast = parseMarkdownWithOptions(content, {
|
|
401
|
+
gfm: true,
|
|
402
|
+
math: true,
|
|
403
|
+
html: false,
|
|
582
404
|
});
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
### `MarkdownParserModule` (low-level Nitro access)
|
|
586
|
-
|
|
587
|
-
Use this only when you want direct method access (`parse`, `parseWithOptions`, `extractPlainText`, `extractPlainTextWithOptions`).
|
|
588
|
-
|
|
589
|
-
```tsx
|
|
590
|
-
import {
|
|
591
|
-
MarkdownParserModule,
|
|
592
|
-
type ParserOptions,
|
|
593
|
-
} from "react-native-nitro-markdown/headless";
|
|
594
|
-
|
|
595
|
-
const options: ParserOptions = { gfm: true };
|
|
596
|
-
const jsonAst = JSON.parse(
|
|
597
|
-
MarkdownParserModule.parseWithOptions("# Hello", options),
|
|
598
|
-
);
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
## Custom Renderer API
|
|
602
|
-
|
|
603
|
-
## `renderers` prop contract
|
|
604
|
-
|
|
605
|
-
`CustomRenderers` is:
|
|
606
|
-
|
|
607
|
-
```ts
|
|
608
|
-
type CustomRenderers = Partial<Record<MarkdownNode["type"], CustomRenderer>>;
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
`CustomRenderer` receives `EnhancedRendererProps` and returns:
|
|
612
|
-
|
|
613
|
-
- React node to override default rendering
|
|
614
|
-
- `undefined` to fallback to built-in renderer
|
|
615
|
-
- `null` to render nothing
|
|
616
|
-
|
|
617
|
-
`EnhancedRendererProps` always includes:
|
|
618
|
-
|
|
619
|
-
- `node`: current `MarkdownNode`
|
|
620
|
-
- `children`: pre-rendered node children
|
|
621
|
-
- `Renderer`: recursive renderer component for nested custom rendering
|
|
622
|
-
|
|
623
|
-
And conditionally includes mapped fields by node type:
|
|
624
|
-
|
|
625
|
-
| Node type | Extra mapped fields |
|
|
626
|
-
| ---------------- | --------------------- |
|
|
627
|
-
| `heading` | `level` |
|
|
628
|
-
| `link` | `href`, `title` |
|
|
629
|
-
| `image` | `url`, `alt`, `title` |
|
|
630
|
-
| `code_block` | `content`, `language` |
|
|
631
|
-
| `code_inline` | `content` |
|
|
632
|
-
| `list` | `ordered`, `start` |
|
|
633
|
-
| `task_list_item` | `checked` |
|
|
634
|
-
|
|
635
|
-
### Example: Custom heading + code block
|
|
636
|
-
|
|
637
|
-
```tsx
|
|
638
|
-
import {
|
|
639
|
-
Markdown,
|
|
640
|
-
type HeadingRendererProps,
|
|
641
|
-
type CodeBlockRendererProps,
|
|
642
|
-
} from "react-native-nitro-markdown";
|
|
643
|
-
|
|
644
|
-
const renderers = {
|
|
645
|
-
heading: ({ level, children }: HeadingRendererProps) => (
|
|
646
|
-
<MyHeading level={level}>{children}</MyHeading>
|
|
647
|
-
),
|
|
648
|
-
code_block: ({ language, content }: CodeBlockRendererProps) => (
|
|
649
|
-
<MyCode language={language} content={content} />
|
|
650
|
-
),
|
|
651
|
-
};
|
|
652
405
|
|
|
653
|
-
<Markdown
|
|
406
|
+
<Markdown sourceAst={ast}>{"ignored when sourceAst is provided"}</Markdown>;
|
|
654
407
|
```
|
|
655
408
|
|
|
656
|
-
###
|
|
409
|
+
### Virtualization (large documents)
|
|
657
410
|
|
|
658
411
|
```tsx
|
|
659
|
-
import { Text } from "react-native";
|
|
660
|
-
import {
|
|
661
|
-
Markdown,
|
|
662
|
-
useMarkdownContext,
|
|
663
|
-
type CustomRendererProps,
|
|
664
|
-
} from "react-native-nitro-markdown";
|
|
665
|
-
|
|
666
|
-
function ThemedParagraph({ children }: Pick<CustomRendererProps, "children">) {
|
|
667
|
-
const { theme } = useMarkdownContext();
|
|
668
|
-
return <Text style={{ color: theme.colors.text }}>{children}</Text>;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
412
|
<Markdown
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
413
|
+
virtualize="auto"
|
|
414
|
+
virtualizationMinBlocks={30}
|
|
415
|
+
virtualization={{
|
|
416
|
+
initialNumToRender: 10,
|
|
417
|
+
maxToRenderPerBatch: 10,
|
|
418
|
+
windowSize: 8,
|
|
676
419
|
}}
|
|
677
420
|
>
|
|
678
421
|
{content}
|
|
679
|
-
</Markdown
|
|
422
|
+
</Markdown>
|
|
680
423
|
```
|
|
681
424
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
Default link renderer behavior:
|
|
685
|
-
|
|
686
|
-
1. Trims incoming href.
|
|
687
|
-
2. Calls `onLinkPress(href)` if provided.
|
|
688
|
-
3. Stops if handler returns `false`.
|
|
689
|
-
4. Allows only protocol-based links with these schemes:
|
|
690
|
-
- `http:`
|
|
691
|
-
- `https:`
|
|
692
|
-
- `mailto:`
|
|
693
|
-
- `tel:`
|
|
694
|
-
- `sms:`
|
|
695
|
-
5. Uses `Linking.canOpenURL` before `Linking.openURL`.
|
|
696
|
-
|
|
697
|
-
Relative URLs and anchors are ignored by default open behavior, but you can handle them in `onLinkPress`.
|
|
698
|
-
|
|
699
|
-
## Theme API
|
|
425
|
+
Keep `Markdown` as the primary vertical scroller when virtualization is enabled -- avoid nesting inside another `ScrollView`.
|
|
700
426
|
|
|
701
|
-
|
|
427
|
+
### Syntax Highlighting
|
|
702
428
|
|
|
703
429
|
```tsx
|
|
704
|
-
import type {
|
|
705
|
-
MarkdownTheme,
|
|
706
|
-
PartialMarkdownTheme,
|
|
707
|
-
} from "react-native-nitro-markdown";
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
`MarkdownTheme` fields:
|
|
711
|
-
|
|
712
|
-
- `colors`
|
|
713
|
-
- `text`, `textMuted`, `heading`, `link`, `code`, `codeBackground`, `codeLanguage`
|
|
714
|
-
- `blockquote`, `border`, `surface`, `surfaceLight`, `accent`
|
|
715
|
-
- `tableBorder`, `tableHeader`, `tableHeaderText`, `tableRowEven`, `tableRowOdd`
|
|
716
|
-
- `codeTokenColors?`: `{ keyword?, string?, comment?, number?, operator?, punctuation?, type?, default? }` — per-token colors used by `highlightCode`
|
|
717
|
-
- `spacing`: `xs`, `s`, `m`, `l`, `xl`
|
|
718
|
-
- `fontSizes`: `xs`, `s`, `m`, `l`, `xl`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
|
|
719
|
-
- `fontFamilies`: `regular`, `heading`, `mono`
|
|
720
|
-
- `headingWeight?`
|
|
721
|
-
- `borderRadius`: `s`, `m`, `l`
|
|
722
|
-
- `showCodeLanguage`
|
|
723
|
-
|
|
724
|
-
Helpers:
|
|
725
|
-
|
|
726
|
-
- `defaultMarkdownTheme`
|
|
727
|
-
- `minimalMarkdownTheme`
|
|
728
|
-
- `mergeThemes(base, partial)`
|
|
729
|
-
|
|
730
|
-
`NodeStyleOverrides` lets you override per-node styles with type-safe shape checking:
|
|
731
|
-
|
|
732
|
-
```ts
|
|
733
|
-
// Text-type nodes accept TextStyle; view-type nodes accept ViewStyle.
|
|
734
|
-
// TypeScript will catch mismatched style shapes at compile time.
|
|
735
|
-
type NodeStyleOverrides = Partial<
|
|
736
|
-
{
|
|
737
|
-
text: TextStyle; bold: TextStyle; italic: TextStyle; /* ... */
|
|
738
|
-
document: ViewStyle; blockquote: ViewStyle; code_block: ViewStyle; /* ... */
|
|
739
|
-
}
|
|
740
|
-
>;
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
Example with `mergeThemes`:
|
|
430
|
+
import type { CodeHighlighter } from "react-native-nitro-markdown";
|
|
744
431
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
Markdown,
|
|
748
|
-
defaultMarkdownTheme,
|
|
749
|
-
mergeThemes,
|
|
750
|
-
} from "react-native-nitro-markdown";
|
|
432
|
+
// Built-in highlighter (JS/TS, Python, Bash)
|
|
433
|
+
<Markdown highlightCode>{"```typescript\nconst x: number = 42;\n```"}</Markdown>
|
|
751
434
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
},
|
|
757
|
-
fontSizes: {
|
|
758
|
-
m: 16,
|
|
759
|
-
},
|
|
760
|
-
});
|
|
435
|
+
// Custom highlighter
|
|
436
|
+
const myHighlighter: CodeHighlighter = (language, code) => {
|
|
437
|
+
return [{ text: code, type: "default" }];
|
|
438
|
+
};
|
|
761
439
|
|
|
762
|
-
<Markdown
|
|
440
|
+
<Markdown highlightCode={myHighlighter}>{content}</Markdown>
|
|
763
441
|
```
|
|
764
442
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
Use these when composing custom renderer maps.
|
|
768
|
-
|
|
769
|
-
| Component | Key props |
|
|
770
|
-
| ---------------- | ------------------------------------------------ |
|
|
771
|
-
| `Heading` | `level`, `children`, `style` |
|
|
772
|
-
| `Paragraph` | `children`, `inListItem`, `style` |
|
|
773
|
-
| `Link` | `href`, `children`, `style` |
|
|
774
|
-
| `Blockquote` | `children`, `style` |
|
|
775
|
-
| `HorizontalRule` | `style` |
|
|
776
|
-
| `CodeBlock` | `language`, `content`, `node`, `style` |
|
|
777
|
-
| `InlineCode` | `content`, `node`, `children`, `style` |
|
|
778
|
-
| `List` | `ordered`, `start`, `depth`, `children`, `style` |
|
|
779
|
-
| `ListItem` | `children`, `index`, `ordered`, `start`, `style` |
|
|
780
|
-
| `TaskListItem` | `children`, `checked`, `style` |
|
|
781
|
-
| `TableRenderer` | `node`, `Renderer`, `style` |
|
|
782
|
-
| `Image` | `url`, `title`, `alt`, `Renderer`, `style` |
|
|
783
|
-
| `MathInline` | `content`, `style` |
|
|
784
|
-
| `MathBlock` | `content`, `style` |
|
|
785
|
-
|
|
786
|
-
## Supported AST Node Types
|
|
787
|
-
|
|
788
|
-
`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`
|
|
789
|
-
|
|
790
|
-
Notes:
|
|
791
|
-
|
|
792
|
-
- `html_inline` and `html_block` are parsed but not rendered by default.
|
|
793
|
-
- Table internals (`table_head`, `table_body`, `table_row`, `table_cell`) are renderer internals; override `table` for custom table UI.
|
|
794
|
-
|
|
795
|
-
## Recipes
|
|
796
|
-
|
|
797
|
-
### Intercept links with `onLinkPress`
|
|
443
|
+
### Link Interception
|
|
798
444
|
|
|
799
445
|
```tsx
|
|
800
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
801
|
-
|
|
802
446
|
<Markdown
|
|
803
447
|
onLinkPress={(href) => {
|
|
804
448
|
if (href.startsWith("/")) {
|
|
@@ -808,61 +452,59 @@ import { Markdown } from "react-native-nitro-markdown";
|
|
|
808
452
|
}}
|
|
809
453
|
>
|
|
810
454
|
{content}
|
|
811
|
-
</Markdown
|
|
455
|
+
</Markdown>
|
|
812
456
|
```
|
|
813
457
|
|
|
814
|
-
|
|
458
|
+
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`.
|
|
815
459
|
|
|
816
|
-
|
|
817
|
-
import {
|
|
818
|
-
parseMarkdown,
|
|
819
|
-
getFlattenedText,
|
|
820
|
-
} from "react-native-nitro-markdown/headless";
|
|
460
|
+
## Supported Node Types
|
|
821
461
|
|
|
822
|
-
|
|
823
|
-
const searchableText = getFlattenedText(ast);
|
|
824
|
-
```
|
|
462
|
+
`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`
|
|
825
463
|
|
|
826
|
-
|
|
464
|
+
`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`.
|
|
827
465
|
|
|
828
|
-
|
|
829
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
466
|
+
## Package Exports
|
|
830
467
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
colors: {
|
|
835
|
-
text: "#0f172a",
|
|
836
|
-
link: "#1d4ed8",
|
|
837
|
-
},
|
|
838
|
-
}}
|
|
839
|
-
>
|
|
840
|
-
{content}
|
|
841
|
-
</Markdown>;
|
|
842
|
-
```
|
|
468
|
+
### Main (`react-native-nitro-markdown`)
|
|
469
|
+
|
|
470
|
+
Components, hooks, sessions, themes, built-in renderers, syntax highlighting, and all headless APIs.
|
|
843
471
|
|
|
844
|
-
|
|
472
|
+
### Headless (`react-native-nitro-markdown/headless`)
|
|
845
473
|
|
|
846
|
-
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
- Use
|
|
474
|
+
Parser and text utilities only -- no React dependencies. Use this for server-side processing, search indexing, or custom renderers.
|
|
475
|
+
|
|
476
|
+
## Performance Tips
|
|
477
|
+
|
|
478
|
+
- Use `updateStrategy="raf"` for streaming UI updates.
|
|
479
|
+
- Batch `session.append()` calls at 50-100ms intervals rather than per-character.
|
|
480
|
+
- Enable `virtualize="auto"` for long documents and keep `Markdown` as the primary vertical scroller.
|
|
481
|
+
- Memoize custom `plugins`, `renderers`, `theme`, and `styles` objects when they are created inside a component.
|
|
482
|
+
- Use the headless API when you only need parsing, plain text extraction, search indexing, or a custom renderer.
|
|
851
483
|
|
|
852
484
|
## Troubleshooting
|
|
853
485
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
486
|
+
| Problem | Solution |
|
|
487
|
+
|---|---|
|
|
488
|
+
| Math renders as code-style fallback | Install `react-native-mathjax-svg` and `react-native-svg` |
|
|
489
|
+
| iOS build fails after install | Run `pod install` in your iOS directory |
|
|
490
|
+
| Expo app doesn't load native module | Use a development build (`expo prebuild` + `expo run`), not Expo Go |
|
|
491
|
+
| Android heading font weight looks wrong | Set `theme.headingWeight` explicitly |
|
|
492
|
+
|
|
493
|
+
## Example App
|
|
494
|
+
|
|
495
|
+
The `apps/example` directory contains a full demo app with these screens:
|
|
496
|
+
|
|
497
|
+
| Screen | File | Demonstrates |
|
|
498
|
+
|---|---|---|
|
|
499
|
+
| Bench | `app/index.tsx` | Smoke tests + benchmark vs JS parsers |
|
|
500
|
+
| Default | `app/render-default.tsx` | Built-in renderer defaults |
|
|
501
|
+
| Styles | `app/render-default-styles.tsx` | Theme and style overrides |
|
|
502
|
+
| Custom | `app/render-custom.tsx` | HTML AST rendering, custom renderers, AST transform |
|
|
503
|
+
| Stream | `app/render-stream.tsx` | Live streaming with token append |
|
|
862
504
|
|
|
863
505
|
## Contributing
|
|
864
506
|
|
|
865
|
-
See
|
|
507
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
866
508
|
|
|
867
509
|
## License
|
|
868
510
|
|