react-native-nitro-markdown 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -82
- package/cpp/core/MD4CParser.cpp +60 -25
- package/cpp/core/MD4CParser.hpp +13 -1
- package/cpp/md4c/md4c.c +1 -1
- package/lib/commonjs/index.js +0 -22
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +53 -4
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/markdown.js +20 -5
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/code.js +9 -2
- package/lib/commonjs/renderers/code.js.map +1 -1
- package/lib/commonjs/renderers/heading.js +14 -5
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/image.js +12 -3
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/link.js +6 -1
- package/lib/commonjs/renderers/link.js.map +1 -1
- package/lib/commonjs/renderers/list.js +32 -8
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/math.js +8 -2
- package/lib/commonjs/renderers/math.js.map +1 -1
- package/lib/commonjs/renderers/table.js +8 -2
- package/lib/commonjs/renderers/table.js.map +1 -1
- package/lib/commonjs/theme.js +47 -84
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/module/index.js +1 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +54 -5
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/markdown.js +21 -6
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/code.js +9 -2
- package/lib/module/renderers/code.js.map +1 -1
- package/lib/module/renderers/heading.js +15 -6
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/image.js +13 -4
- 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 +33 -9
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/math.js +8 -2
- package/lib/module/renderers/math.js.map +1 -1
- package/lib/module/renderers/table.js +9 -3
- package/lib/module/renderers/table.js.map +1 -1
- package/lib/module/theme.js +46 -83
- package/lib/module/theme.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts +16 -0
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown.d.ts +2 -2
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.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.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts +3 -4
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts +16 -0
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/markdown.d.ts +2 -2
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/code.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.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.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/renderers/table.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts +3 -4
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -3
- package/src/markdown-stream.tsx +82 -5
- package/src/markdown.tsx +8 -2
- package/src/renderers/code.tsx +3 -0
- package/src/renderers/heading.tsx +24 -2
- package/src/renderers/image.tsx +4 -0
- package/src/renderers/link.tsx +10 -1
- package/src/renderers/list.tsx +30 -6
- package/src/renderers/math.tsx +2 -0
- package/src/renderers/table.tsx +3 -0
- package/src/theme.ts +51 -88
- package/lib/commonjs/default-markdown-renderer.js +0 -217
- package/lib/commonjs/default-markdown-renderer.js.map +0 -1
- package/lib/module/default-markdown-renderer.js +0 -212
- package/lib/module/default-markdown-renderer.js.map +0 -1
- package/lib/typescript/commonjs/default-markdown-renderer.d.ts +0 -10
- package/lib/typescript/commonjs/default-markdown-renderer.d.ts.map +0 -1
- package/lib/typescript/module/default-markdown-renderer.d.ts +0 -10
- package/lib/typescript/module/default-markdown-renderer.d.ts.map +0 -1
- package/src/default-markdown-renderer.tsx +0 -261
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ bunx expo prebuild
|
|
|
86
86
|
|
|
87
87
|
### Option 1: Batteries Included (Simplest)
|
|
88
88
|
|
|
89
|
-
Use the `Markdown` component with
|
|
89
|
+
Use the `Markdown` component with clean, neutral styling that stays out of the way:
|
|
90
90
|
|
|
91
91
|
```tsx
|
|
92
92
|
import { Markdown } from "react-native-nitro-markdown";
|
|
@@ -100,91 +100,27 @@ export function MyComponent() {
|
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
If you're rendering on a dark surface, override `theme.colors.text` (or use the `styles` prop) to match your app's palette.
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```tsx
|
|
108
|
-
import { Markdown, lightMarkdownTheme } from "react-native-nitro-markdown";
|
|
109
|
-
|
|
110
|
-
<Markdown theme={lightMarkdownTheme}>{"# Light Mode Markdown"}</Markdown>;
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Available presets:
|
|
114
|
-
|
|
115
|
-
- `defaultMarkdownTheme` / `darkMarkdownTheme` - Modern dark theme
|
|
116
|
-
- `lightMarkdownTheme` - Clean light theme
|
|
117
|
-
- `minimalMarkdownTheme` - Bare minimum styling for a clean slate
|
|
118
|
-
|
|
119
|
-
### Option 3: Custom Theming
|
|
120
|
-
|
|
121
|
-
Customize the look and feel by passing a partial `theme` object:
|
|
122
|
-
|
|
123
|
-
```tsx
|
|
124
|
-
import { Markdown } from "react-native-nitro-markdown";
|
|
125
|
-
|
|
126
|
-
const myTheme = {
|
|
127
|
-
colors: {
|
|
128
|
-
text: "#2D3748",
|
|
129
|
-
heading: "#1A202C",
|
|
130
|
-
link: "#3182CE",
|
|
131
|
-
},
|
|
132
|
-
fontFamilies: {
|
|
133
|
-
regular: "Inter",
|
|
134
|
-
heading: "Inter-Bold",
|
|
135
|
-
mono: "JetBrainsMono",
|
|
136
|
-
},
|
|
137
|
-
borderRadius: {
|
|
138
|
-
s: 4,
|
|
139
|
-
m: 8,
|
|
140
|
-
l: 16,
|
|
141
|
-
},
|
|
142
|
-
showCodeLanguage: true, // Toggle code language labels
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
<Markdown theme={myTheme}>{"# Custom Themed Markdown"}</Markdown>;
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**Theme Properties:**
|
|
149
|
-
|
|
150
|
-
- `colors` - All color tokens (text, heading, link, code, codeBackground, codeLanguage, etc.)
|
|
151
|
-
- `spacing` - Spacing tokens (xs, s, m, l, xl)
|
|
152
|
-
- `fontSizes` - Font sizes (xs, s, m, l, xl, h1-h6)
|
|
153
|
-
- `fontFamilies` - Font families for regular, heading, and mono text
|
|
154
|
-
- `borderRadius` - Border radius tokens (s, m, l)
|
|
155
|
-
- `showCodeLanguage` - Show/hide code block language labels
|
|
156
|
-
|
|
157
|
-
### Option 4: Style Overrides per Node Type
|
|
105
|
+
### Option 2: Style Overrides per Node Type
|
|
158
106
|
|
|
159
107
|
Apply quick style overrides to specific node types without writing custom renderers:
|
|
160
108
|
|
|
161
109
|
```tsx
|
|
162
110
|
<Markdown
|
|
163
111
|
styles={{
|
|
164
|
-
heading: { color: "
|
|
165
|
-
code_block: { backgroundColor: "#
|
|
166
|
-
blockquote: { borderLeftColor: "#
|
|
112
|
+
heading: { color: "#0ea5e9", fontWeight: "900" },
|
|
113
|
+
code_block: { backgroundColor: "#e2e8f0", borderRadius: 16 },
|
|
114
|
+
blockquote: { borderLeftColor: "#0ea5e9" },
|
|
167
115
|
}}
|
|
168
116
|
>
|
|
169
117
|
{markdown}
|
|
170
118
|
</Markdown>
|
|
171
119
|
```
|
|
172
120
|
|
|
173
|
-
### Option
|
|
121
|
+
### Option 3: Custom Renderers
|
|
174
122
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```tsx
|
|
178
|
-
<Markdown stylingStrategy="minimal" theme={myLightTheme}>
|
|
179
|
-
{content}
|
|
180
|
-
</Markdown>
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
This zeros out all spacing and removes opinionated colors, letting you build up from scratch.
|
|
184
|
-
|
|
185
|
-
### Option 6: Custom Renderers
|
|
186
|
-
|
|
187
|
-
Override specific node types with full control. Custom renderers now receive **pre-mapped props** for common values:
|
|
123
|
+
Override specific node types with full control. Custom renderers receive **pre-mapped props** for common values:
|
|
188
124
|
|
|
189
125
|
```tsx
|
|
190
126
|
import {
|
|
@@ -225,7 +161,56 @@ const renderers = {
|
|
|
225
161
|
- `list` → `ordered`, `start`
|
|
226
162
|
- `task_list_item` → `checked`
|
|
227
163
|
|
|
228
|
-
### Option
|
|
164
|
+
### Option 4: Token Overrides (Theme)
|
|
165
|
+
|
|
166
|
+
Customize the look and feel by passing a partial `theme` object:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
170
|
+
|
|
171
|
+
const myTheme = {
|
|
172
|
+
colors: {
|
|
173
|
+
text: "#0f172a",
|
|
174
|
+
heading: "#0f172a",
|
|
175
|
+
link: "#0ea5e9",
|
|
176
|
+
codeBackground: "#e2e8f0",
|
|
177
|
+
},
|
|
178
|
+
showCodeLanguage: false,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
<Markdown theme={myTheme}>{"# Custom Themed Markdown"}</Markdown>;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Defaults live in `defaultMarkdownTheme` and are intentionally neutral so you can layer your own palette on top.
|
|
185
|
+
|
|
186
|
+
**Theme Properties:**
|
|
187
|
+
|
|
188
|
+
- `colors` - All color tokens (text, heading, link, code, codeBackground, codeLanguage, etc.)
|
|
189
|
+
- `spacing` - Spacing tokens (xs, s, m, l, xl)
|
|
190
|
+
- `fontSizes` - Font sizes (xs, s, m, l, xl, h1-h6)
|
|
191
|
+
- `fontFamilies` - Font families for regular, heading, and mono text
|
|
192
|
+
- `headingWeight` - Optional weight for headings (useful for Android custom fonts)
|
|
193
|
+
- `borderRadius` - Border radius tokens (s, m, l)
|
|
194
|
+
- `showCodeLanguage` - Show/hide code block language labels
|
|
195
|
+
|
|
196
|
+
**Android custom fonts note:**
|
|
197
|
+
If you use a custom heading font on Android and don’t load a bold variant, set
|
|
198
|
+
`headingWeight: "normal"` (or use the `styles` prop) to avoid fallback to a
|
|
199
|
+
system serif font.
|
|
200
|
+
|
|
201
|
+
### Option 5: Minimal Styling Strategy
|
|
202
|
+
|
|
203
|
+
Start with a clean slate using the `stylingStrategy` prop:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
<Markdown stylingStrategy="minimal" theme={myLightTheme}>
|
|
207
|
+
{content}
|
|
208
|
+
</Markdown>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This zeros out all spacing and removes opinionated colors, letting you build up from scratch.
|
|
212
|
+
|
|
213
|
+
### Option 6: Style Props on Individual Renderers
|
|
229
214
|
|
|
230
215
|
All built-in renderers accept a `style` prop for fine-grained overrides:
|
|
231
216
|
|
|
@@ -238,7 +223,7 @@ import { Heading, CodeBlock, InlineCode } from "react-native-nitro-markdown";
|
|
|
238
223
|
<InlineCode style={{ backgroundColor: "#ff0" }}>code</InlineCode>
|
|
239
224
|
```
|
|
240
225
|
|
|
241
|
-
### Option
|
|
226
|
+
### Option 7: Auto Content Extraction for Code
|
|
242
227
|
|
|
243
228
|
The `CodeBlock` and `InlineCode` components now accept a `node` prop for automatic content extraction:
|
|
244
229
|
|
|
@@ -257,7 +242,7 @@ code_block: ({ content, language }) => (
|
|
|
257
242
|
);
|
|
258
243
|
```
|
|
259
244
|
|
|
260
|
-
### Option
|
|
245
|
+
### Option 8: Headless (Minimal Bundle)
|
|
261
246
|
|
|
262
247
|
For maximum control, data processing, or minimal JS overhead:
|
|
263
248
|
|
|
@@ -273,9 +258,9 @@ const text = getTextContent(ast); // "Hello World"
|
|
|
273
258
|
const fullText = getFlattenedText(ast); // "Hello World\n\n" (Normalized with line breaks)
|
|
274
259
|
```
|
|
275
260
|
|
|
276
|
-
### Option
|
|
261
|
+
### Option 9: High-Performance Streaming (LLMs)
|
|
277
262
|
|
|
278
|
-
When streaming text token-by-token (e.g., from ChatGPT or Gemini):
|
|
263
|
+
When streaming text token-by-token (e.g., from ChatGPT or Gemini), you should batch UI updates to avoid re-rendering on every token:
|
|
279
264
|
|
|
280
265
|
```tsx
|
|
281
266
|
import {
|
|
@@ -292,12 +277,31 @@ export function AIResponseStream() {
|
|
|
292
277
|
}, [session]);
|
|
293
278
|
|
|
294
279
|
return (
|
|
295
|
-
<MarkdownStream
|
|
280
|
+
<MarkdownStream
|
|
281
|
+
session={session.getSession()}
|
|
282
|
+
options={{ gfm: true }}
|
|
283
|
+
updateIntervalMs={60}
|
|
284
|
+
updateStrategy="raf"
|
|
285
|
+
useTransitionUpdates
|
|
286
|
+
/>
|
|
296
287
|
);
|
|
297
288
|
}
|
|
298
289
|
```
|
|
299
290
|
|
|
300
|
-
|
|
291
|
+
**Recommended defaults for token streaming:**
|
|
292
|
+
- `updateStrategy="raf"` for smooth, frame-aligned updates
|
|
293
|
+
- `updateIntervalMs={50–100}` when using `updateStrategy="interval"`
|
|
294
|
+
- Avoid per-token UI updates by batching token appends
|
|
295
|
+
|
|
296
|
+
**Streaming props:**
|
|
297
|
+
|
|
298
|
+
| Prop | Type | Default | Description |
|
|
299
|
+
| :-- | :-- | :-- | :-- |
|
|
300
|
+
| `updateIntervalMs` | `number` | `50` | Throttle UI updates when using interval strategy |
|
|
301
|
+
| `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Interval batching or frame-aligned updates |
|
|
302
|
+
| `useTransitionUpdates` | `boolean` | `false` | Use React transitions to keep input responsive |
|
|
303
|
+
|
|
304
|
+
### Option 10: Extracting Plain Text
|
|
301
305
|
|
|
302
306
|
You can extract the plain text representation (with proper line breaks) using the `onParseComplete` callback. This is useful for "Copy All" buttons or TTS.
|
|
303
307
|
|
|
@@ -344,11 +348,9 @@ export {
|
|
|
344
348
|
getFlattenedText,
|
|
345
349
|
} from "./headless";
|
|
346
350
|
|
|
347
|
-
// Theme
|
|
351
|
+
// Theme tokens
|
|
348
352
|
export {
|
|
349
353
|
defaultMarkdownTheme,
|
|
350
|
-
lightMarkdownTheme,
|
|
351
|
-
darkMarkdownTheme,
|
|
352
354
|
minimalMarkdownTheme,
|
|
353
355
|
mergeThemes,
|
|
354
356
|
};
|
package/cpp/core/MD4CParser.cpp
CHANGED
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
#include <stack>
|
|
5
5
|
#include <cstring>
|
|
6
|
+
#include <limits>
|
|
6
7
|
|
|
7
8
|
namespace NitroMarkdown {
|
|
8
9
|
|
|
10
|
+
namespace {
|
|
11
|
+
size_t clampInputSize(size_t inputSize) {
|
|
12
|
+
size_t maxSize = static_cast<size_t>(std::numeric_limits<MD_SIZE>::max());
|
|
13
|
+
if (inputSize > maxSize) {
|
|
14
|
+
return maxSize;
|
|
15
|
+
}
|
|
16
|
+
return inputSize;
|
|
17
|
+
}
|
|
18
|
+
} // namespace
|
|
19
|
+
|
|
9
20
|
class MD4CParser::Impl {
|
|
10
21
|
public:
|
|
11
22
|
std::shared_ptr<MarkdownNode> root;
|
|
@@ -55,25 +66,42 @@ public:
|
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
std::string getAttributeText(const MD_ATTRIBUTE* attr) {
|
|
58
|
-
if (!attr || attr->size == 0) return "";
|
|
69
|
+
if (!attr || attr->size == 0 || !attr->text) return "";
|
|
70
|
+
if (!attr->substr_types || !attr->substr_offsets) {
|
|
71
|
+
return std::string(attr->text, attr->size);
|
|
72
|
+
}
|
|
59
73
|
|
|
60
74
|
std::string result;
|
|
61
75
|
result.reserve(attr->size);
|
|
62
|
-
|
|
63
|
-
for (unsigned i = 0;
|
|
76
|
+
|
|
77
|
+
for (unsigned i = 0; ; i++) {
|
|
78
|
+
size_t start = static_cast<size_t>(attr->substr_offsets[i]);
|
|
79
|
+
size_t end = static_cast<size_t>(attr->substr_offsets[i + 1]);
|
|
80
|
+
|
|
81
|
+
if (end > attr->size) {
|
|
82
|
+
end = static_cast<size_t>(attr->size);
|
|
83
|
+
}
|
|
84
|
+
if (start > end) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
if (attr->substr_types[i] == MD_TEXT_NORMAL ||
|
|
65
89
|
attr->substr_types[i] == MD_TEXT_ENTITY ||
|
|
66
90
|
attr->substr_types[i] == MD_TEXT_NULLCHAR) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
if (end > start) {
|
|
92
|
+
result.append(attr->text + start, end - start);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (end >= attr->size) {
|
|
97
|
+
break;
|
|
70
98
|
}
|
|
71
99
|
}
|
|
72
|
-
|
|
73
|
-
if (result.empty() && attr->
|
|
100
|
+
|
|
101
|
+
if (result.empty() && attr->size > 0) {
|
|
74
102
|
result.assign(attr->text, attr->size);
|
|
75
103
|
}
|
|
76
|
-
|
|
104
|
+
|
|
77
105
|
return result;
|
|
78
106
|
}
|
|
79
107
|
|
|
@@ -133,8 +161,8 @@ public:
|
|
|
133
161
|
case MD_BLOCK_CODE: {
|
|
134
162
|
auto* d = static_cast<MD_BLOCK_CODE_DETAIL*>(detail);
|
|
135
163
|
auto node = std::make_shared<MarkdownNode>(NodeType::CodeBlock);
|
|
136
|
-
if (d->lang.
|
|
137
|
-
node->language =
|
|
164
|
+
if (d->lang.size > 0) {
|
|
165
|
+
node->language = impl->getAttributeText(&d->lang);
|
|
138
166
|
}
|
|
139
167
|
impl->pushNode(node, off);
|
|
140
168
|
break;
|
|
@@ -243,11 +271,11 @@ public:
|
|
|
243
271
|
case MD_SPAN_A: {
|
|
244
272
|
auto* d = static_cast<MD_SPAN_A_DETAIL*>(detail);
|
|
245
273
|
auto node = std::make_shared<MarkdownNode>(NodeType::Link);
|
|
246
|
-
if (d->href.
|
|
247
|
-
node->href =
|
|
274
|
+
if (d->href.size > 0) {
|
|
275
|
+
node->href = impl->getAttributeText(&d->href);
|
|
248
276
|
}
|
|
249
|
-
if (d->title.
|
|
250
|
-
node->title =
|
|
277
|
+
if (d->title.size > 0) {
|
|
278
|
+
node->title = impl->getAttributeText(&d->title);
|
|
251
279
|
}
|
|
252
280
|
impl->pushNode(node, off);
|
|
253
281
|
break;
|
|
@@ -256,11 +284,11 @@ public:
|
|
|
256
284
|
case MD_SPAN_IMG: {
|
|
257
285
|
auto* d = static_cast<MD_SPAN_IMG_DETAIL*>(detail);
|
|
258
286
|
auto node = std::make_shared<MarkdownNode>(NodeType::Image);
|
|
259
|
-
if (d->src.
|
|
260
|
-
node->href =
|
|
287
|
+
if (d->src.size > 0) {
|
|
288
|
+
node->href = impl->getAttributeText(&d->src);
|
|
261
289
|
}
|
|
262
|
-
if (d->title.
|
|
263
|
-
node->title =
|
|
290
|
+
if (d->title.size > 0) {
|
|
291
|
+
node->title = impl->getAttributeText(&d->title);
|
|
264
292
|
}
|
|
265
293
|
impl->pushNode(node, off);
|
|
266
294
|
break;
|
|
@@ -329,10 +357,17 @@ public:
|
|
|
329
357
|
if (!text || size == 0) return 0;
|
|
330
358
|
|
|
331
359
|
switch (type) {
|
|
332
|
-
case MD_TEXT_NULLCHAR:
|
|
333
|
-
|
|
360
|
+
case MD_TEXT_NULLCHAR: {
|
|
361
|
+
MD_OFFSET off = impl->lastTextEnd;
|
|
362
|
+
ptrdiff_t diff = text - impl->inputText;
|
|
363
|
+
if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
|
|
364
|
+
off = static_cast<MD_OFFSET>(diff);
|
|
365
|
+
}
|
|
366
|
+
if (impl->currentText.empty()) impl->currentTextBeg = off;
|
|
334
367
|
impl->currentText += '\0';
|
|
368
|
+
impl->lastTextEnd = off + 1;
|
|
335
369
|
break;
|
|
370
|
+
}
|
|
336
371
|
|
|
337
372
|
case MD_TEXT_BR:
|
|
338
373
|
impl->flushText();
|
|
@@ -400,7 +435,8 @@ MD4CParser::~MD4CParser() = default;
|
|
|
400
435
|
std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, const ParserOptions& options) {
|
|
401
436
|
impl_->reset();
|
|
402
437
|
impl_->inputText = markdown.c_str();
|
|
403
|
-
|
|
438
|
+
size_t inputSize = clampInputSize(markdown.size());
|
|
439
|
+
impl_->inputTextSize = inputSize;
|
|
404
440
|
|
|
405
441
|
unsigned int flags = MD_FLAG_NOHTML;
|
|
406
442
|
|
|
@@ -427,8 +463,8 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
|
|
|
427
463
|
nullptr
|
|
428
464
|
};
|
|
429
465
|
|
|
430
|
-
md_parse(markdown.c_str(),
|
|
431
|
-
static_cast<MD_SIZE>(
|
|
466
|
+
md_parse(markdown.c_str(),
|
|
467
|
+
static_cast<MD_SIZE>(inputSize),
|
|
432
468
|
&parser,
|
|
433
469
|
impl_.get());
|
|
434
470
|
|
|
@@ -437,4 +473,3 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
|
|
|
437
473
|
}
|
|
438
474
|
|
|
439
475
|
} // namespace NitroMarkdown
|
|
440
|
-
|
package/cpp/core/MD4CParser.hpp
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
#include "MarkdownTypes.hpp"
|
|
4
4
|
#include <string>
|
|
5
5
|
#include <memory>
|
|
6
|
+
#include <cstddef>
|
|
7
|
+
|
|
8
|
+
#ifdef NITRO_MARKDOWN_TESTING
|
|
9
|
+
#include "../md4c/md4c.h"
|
|
10
|
+
#include <limits>
|
|
11
|
+
#endif
|
|
6
12
|
|
|
7
13
|
namespace NitroMarkdown {
|
|
8
14
|
|
|
@@ -11,6 +17,13 @@ public:
|
|
|
11
17
|
MD4CParser();
|
|
12
18
|
~MD4CParser();
|
|
13
19
|
std::shared_ptr<MarkdownNode> parse(const std::string& markdown, const ParserOptions& options);
|
|
20
|
+
|
|
21
|
+
#ifdef NITRO_MARKDOWN_TESTING
|
|
22
|
+
static size_t clampInputSizeForTest(size_t inputSize) {
|
|
23
|
+
size_t maxSize = static_cast<size_t>(std::numeric_limits<MD_SIZE>::max());
|
|
24
|
+
return inputSize > maxSize ? maxSize : inputSize;
|
|
25
|
+
}
|
|
26
|
+
#endif
|
|
14
27
|
|
|
15
28
|
private:
|
|
16
29
|
class Impl;
|
|
@@ -18,4 +31,3 @@ private:
|
|
|
18
31
|
};
|
|
19
32
|
|
|
20
33
|
} // namespace NitroMarkdown
|
|
21
|
-
|
package/cpp/md4c/md4c.c
CHANGED
|
@@ -6489,7 +6489,7 @@ md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userd
|
|
|
6489
6489
|
ctx.code_indent_offset = (ctx.parser.flags & MD_FLAG_NOINDENTEDCODEBLOCKS) ? (OFF)(-1) : 4;
|
|
6490
6490
|
md_build_mark_char_map(&ctx);
|
|
6491
6491
|
ctx.doc_ends_with_newline = (size > 0 && ISNEWLINE_(text[size-1]));
|
|
6492
|
-
ctx.max_ref_def_output = MIN(MIN(16 * (uint64_t)size, (uint64_t)(1024 * 1024)), (uint64_t)SZ_MAX);
|
|
6492
|
+
ctx.max_ref_def_output = (SZ) MIN(MIN(16 * (uint64_t)size, (uint64_t)(1024 * 1024)), (uint64_t)SZ_MAX);
|
|
6493
6493
|
|
|
6494
6494
|
/* Reset all mark stacks and lists. */
|
|
6495
6495
|
for(i = 0; i < (int) SIZEOF_ARRAY(ctx.opener_stacks); i++)
|
package/lib/commonjs/index.js
CHANGED
|
@@ -4,14 +4,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
var _exportNames = {
|
|
7
|
-
DefaultMarkdownRenderer: true,
|
|
8
7
|
Markdown: true,
|
|
9
8
|
MarkdownStream: true,
|
|
10
9
|
useMarkdownContext: true,
|
|
11
10
|
MarkdownContext: true,
|
|
12
11
|
defaultMarkdownTheme: true,
|
|
13
|
-
lightMarkdownTheme: true,
|
|
14
|
-
darkMarkdownTheme: true,
|
|
15
12
|
minimalMarkdownTheme: true,
|
|
16
13
|
mergeThemes: true,
|
|
17
14
|
Heading: true,
|
|
@@ -44,12 +41,6 @@ Object.defineProperty(exports, "CodeBlock", {
|
|
|
44
41
|
return _code.CodeBlock;
|
|
45
42
|
}
|
|
46
43
|
});
|
|
47
|
-
Object.defineProperty(exports, "DefaultMarkdownRenderer", {
|
|
48
|
-
enumerable: true,
|
|
49
|
-
get: function () {
|
|
50
|
-
return _defaultMarkdownRenderer.DefaultMarkdownRenderer;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
44
|
Object.defineProperty(exports, "Heading", {
|
|
54
45
|
enumerable: true,
|
|
55
46
|
get: function () {
|
|
@@ -146,24 +137,12 @@ Object.defineProperty(exports, "createMarkdownSession", {
|
|
|
146
137
|
return _MarkdownSession.createMarkdownSession;
|
|
147
138
|
}
|
|
148
139
|
});
|
|
149
|
-
Object.defineProperty(exports, "darkMarkdownTheme", {
|
|
150
|
-
enumerable: true,
|
|
151
|
-
get: function () {
|
|
152
|
-
return _theme.darkMarkdownTheme;
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
140
|
Object.defineProperty(exports, "defaultMarkdownTheme", {
|
|
156
141
|
enumerable: true,
|
|
157
142
|
get: function () {
|
|
158
143
|
return _theme.defaultMarkdownTheme;
|
|
159
144
|
}
|
|
160
145
|
});
|
|
161
|
-
Object.defineProperty(exports, "lightMarkdownTheme", {
|
|
162
|
-
enumerable: true,
|
|
163
|
-
get: function () {
|
|
164
|
-
return _theme.lightMarkdownTheme;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
146
|
Object.defineProperty(exports, "mergeThemes", {
|
|
168
147
|
enumerable: true,
|
|
169
148
|
get: function () {
|
|
@@ -206,7 +185,6 @@ Object.keys(_headless).forEach(function (key) {
|
|
|
206
185
|
}
|
|
207
186
|
});
|
|
208
187
|
});
|
|
209
|
-
var _defaultMarkdownRenderer = require("./default-markdown-renderer.js");
|
|
210
188
|
var _markdown = require("./markdown.js");
|
|
211
189
|
var _markdownStream = require("./markdown-stream.js");
|
|
212
190
|
var _MarkdownContext = require("./MarkdownContext.js");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_headless","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","
|
|
1
|
+
{"version":3,"names":["_headless","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_markdown","_markdownStream","_MarkdownContext","_theme","_heading","_paragraph","_link","_blockquote","_horizontalRule","_code","_list","_table","_image","_math","_MarkdownSession","_useMarkdownStream"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAb,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AAEA,IAAAS,SAAA,GAAAb,OAAA;AACA,IAAAc,eAAA,GAAAd,OAAA;AAEA,IAAAe,gBAAA,GAAAf,OAAA;AAkBA,IAAAgB,MAAA,GAAAhB,OAAA;AAYA,IAAAiB,QAAA,GAAAjB,OAAA;AACA,IAAAkB,UAAA,GAAAlB,OAAA;AACA,IAAAmB,KAAA,GAAAnB,OAAA;AACA,IAAAoB,WAAA,GAAApB,OAAA;AACA,IAAAqB,eAAA,GAAArB,OAAA;AACA,IAAAsB,KAAA,GAAAtB,OAAA;AACA,IAAAuB,KAAA,GAAAvB,OAAA;AACA,IAAAwB,MAAA,GAAAxB,OAAA;AACA,IAAAyB,MAAA,GAAAzB,OAAA;AACA,IAAA0B,KAAA,GAAA1B,OAAA;AAEA,IAAA2B,gBAAA,GAAA3B,OAAA;AAEA,IAAA4B,kBAAA,GAAA5B,OAAA","ignoreList":[]}
|
|
@@ -13,16 +13,65 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
13
13
|
*/
|
|
14
14
|
const MarkdownStream = ({
|
|
15
15
|
session,
|
|
16
|
+
updateIntervalMs = 50,
|
|
17
|
+
updateStrategy = "interval",
|
|
18
|
+
useTransitionUpdates = false,
|
|
16
19
|
...props
|
|
17
20
|
}) => {
|
|
18
21
|
const [text, setText] = (0, _react.useState)(() => session.getAllText());
|
|
22
|
+
const pendingUpdateRef = (0, _react.useRef)(false);
|
|
23
|
+
const updateTimerRef = (0, _react.useRef)(null);
|
|
24
|
+
const rafRef = (0, _react.useRef)(null);
|
|
25
|
+
const lastEmittedRef = (0, _react.useRef)(text);
|
|
19
26
|
(0, _react.useEffect)(() => {
|
|
20
27
|
// Ensure initial state is synced
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
const initialText = session.getAllText();
|
|
29
|
+
setText(initialText);
|
|
30
|
+
lastEmittedRef.current = initialText;
|
|
31
|
+
const flushUpdate = () => {
|
|
32
|
+
updateTimerRef.current = null;
|
|
33
|
+
if (rafRef.current !== null) {
|
|
34
|
+
cancelAnimationFrame(rafRef.current);
|
|
35
|
+
rafRef.current = null;
|
|
36
|
+
}
|
|
37
|
+
if (!pendingUpdateRef.current) return;
|
|
38
|
+
pendingUpdateRef.current = false;
|
|
39
|
+
const latest = session.getAllText();
|
|
40
|
+
if (latest === lastEmittedRef.current) return;
|
|
41
|
+
lastEmittedRef.current = latest;
|
|
42
|
+
if (useTransitionUpdates) {
|
|
43
|
+
(0, _react.startTransition)(() => setText(latest));
|
|
44
|
+
} else {
|
|
45
|
+
setText(latest);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const scheduleFlush = () => {
|
|
49
|
+
if (updateStrategy === "raf") {
|
|
50
|
+
if (rafRef.current === null) {
|
|
51
|
+
rafRef.current = requestAnimationFrame(flushUpdate);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!updateTimerRef.current) {
|
|
56
|
+
updateTimerRef.current = setTimeout(flushUpdate, updateIntervalMs);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const unsubscribe = session.addListener(() => {
|
|
60
|
+
pendingUpdateRef.current = true;
|
|
61
|
+
scheduleFlush();
|
|
24
62
|
});
|
|
25
|
-
|
|
63
|
+
return () => {
|
|
64
|
+
unsubscribe();
|
|
65
|
+
if (updateTimerRef.current) {
|
|
66
|
+
clearTimeout(updateTimerRef.current);
|
|
67
|
+
updateTimerRef.current = null;
|
|
68
|
+
}
|
|
69
|
+
if (rafRef.current !== null) {
|
|
70
|
+
cancelAnimationFrame(rafRef.current);
|
|
71
|
+
rafRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}, [session, updateIntervalMs, updateStrategy, useTransitionUpdates]);
|
|
26
75
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_markdown.Markdown, {
|
|
27
76
|
...props,
|
|
28
77
|
children: text
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_markdown","_jsxRuntime","MarkdownStream","session","props","text","setText","useState","getAllText","useEffect","addListener","jsx","Markdown","children","exports"],"sourceRoot":"../../src","sources":["markdown-stream.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;
|
|
1
|
+
{"version":3,"names":["_react","require","_markdown","_jsxRuntime","MarkdownStream","session","updateIntervalMs","updateStrategy","useTransitionUpdates","props","text","setText","useState","getAllText","pendingUpdateRef","useRef","updateTimerRef","rafRef","lastEmittedRef","useEffect","initialText","current","flushUpdate","cancelAnimationFrame","latest","startTransition","scheduleFlush","requestAnimationFrame","setTimeout","unsubscribe","addListener","clearTimeout","jsx","Markdown","children","exports"],"sourceRoot":"../../src","sources":["markdown-stream.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/commonjs/markdown.js
CHANGED
|
@@ -364,22 +364,37 @@ const createBaseStyles = theme => _reactNative.StyleSheet.create({
|
|
|
364
364
|
errorText: {
|
|
365
365
|
color: "#f87171",
|
|
366
366
|
fontSize: 14,
|
|
367
|
-
fontFamily: theme.fontFamilies.mono ?? "monospace"
|
|
367
|
+
fontFamily: theme.fontFamilies.mono ?? "monospace",
|
|
368
|
+
...(_reactNative.Platform.OS === "android" && {
|
|
369
|
+
includeFontPadding: false
|
|
370
|
+
})
|
|
368
371
|
},
|
|
369
372
|
text: {
|
|
370
373
|
color: theme.colors.text,
|
|
371
374
|
fontSize: theme.fontSizes.m,
|
|
372
375
|
lineHeight: theme.fontSizes.m * 1.6,
|
|
373
|
-
fontFamily: theme.fontFamilies.regular
|
|
376
|
+
fontFamily: theme.fontFamilies.regular,
|
|
377
|
+
...(_reactNative.Platform.OS === "android" && {
|
|
378
|
+
includeFontPadding: false
|
|
379
|
+
})
|
|
374
380
|
},
|
|
375
381
|
bold: {
|
|
376
|
-
fontWeight: "700"
|
|
382
|
+
fontWeight: "700",
|
|
383
|
+
...(_reactNative.Platform.OS === "android" && {
|
|
384
|
+
includeFontPadding: false
|
|
385
|
+
})
|
|
377
386
|
},
|
|
378
387
|
italic: {
|
|
379
|
-
fontStyle: "italic"
|
|
388
|
+
fontStyle: "italic",
|
|
389
|
+
...(_reactNative.Platform.OS === "android" && {
|
|
390
|
+
includeFontPadding: false
|
|
391
|
+
})
|
|
380
392
|
},
|
|
381
393
|
strikethrough: {
|
|
382
|
-
textDecorationLine: "line-through"
|
|
394
|
+
textDecorationLine: "line-through",
|
|
395
|
+
...(_reactNative.Platform.OS === "android" && {
|
|
396
|
+
includeFontPadding: false
|
|
397
|
+
})
|
|
383
398
|
}
|
|
384
399
|
});
|
|
385
400
|
//# sourceMappingURL=markdown.js.map
|