react-native-nitro-markdown 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +20 -2
  2. package/cpp/bindings/HybridMarkdownParser.cpp +2 -0
  3. package/cpp/core/MD4CParser.cpp +73 -39
  4. package/cpp/core/MarkdownTypes.hpp +6 -1
  5. package/cpp/md4c/md4c.c +79 -56
  6. package/cpp/md4c/md4c.h +7 -4
  7. package/lib/commonjs/headless.js +38 -1
  8. package/lib/commonjs/headless.js.map +1 -1
  9. package/lib/commonjs/markdown.js +19 -11
  10. package/lib/commonjs/markdown.js.map +1 -1
  11. package/lib/commonjs/renderers/image.js +26 -5
  12. package/lib/commonjs/renderers/image.js.map +1 -1
  13. package/lib/commonjs/renderers/list.js +1 -5
  14. package/lib/commonjs/renderers/list.js.map +1 -1
  15. package/lib/commonjs/renderers/math.js +14 -4
  16. package/lib/commonjs/renderers/math.js.map +1 -1
  17. package/lib/commonjs/renderers/paragraph.js +1 -3
  18. package/lib/commonjs/renderers/paragraph.js.map +1 -1
  19. package/lib/module/headless.js +36 -0
  20. package/lib/module/headless.js.map +1 -1
  21. package/lib/module/markdown.js +20 -12
  22. package/lib/module/markdown.js.map +1 -1
  23. package/lib/module/renderers/image.js +27 -6
  24. package/lib/module/renderers/image.js.map +1 -1
  25. package/lib/module/renderers/list.js +1 -5
  26. package/lib/module/renderers/list.js.map +1 -1
  27. package/lib/module/renderers/math.js +14 -4
  28. package/lib/module/renderers/math.js.map +1 -1
  29. package/lib/module/renderers/paragraph.js +1 -3
  30. package/lib/module/renderers/paragraph.js.map +1 -1
  31. package/lib/typescript/commonjs/headless.d.ts +4 -0
  32. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/markdown.d.ts +13 -0
  34. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -1
  40. package/lib/typescript/module/headless.d.ts +4 -0
  41. package/lib/typescript/module/headless.d.ts.map +1 -1
  42. package/lib/typescript/module/markdown.d.ts +13 -0
  43. package/lib/typescript/module/markdown.d.ts.map +1 -1
  44. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  45. package/lib/typescript/module/renderers/list.d.ts.map +1 -1
  46. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  47. package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
  48. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -1
  49. package/nitrogen/generated/ios/NitroMarkdownAutolinking.swift +8 -7
  50. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +2 -2
  51. package/package.json +1 -1
  52. package/src/headless.ts +56 -1
  53. package/src/markdown.tsx +40 -16
  54. package/src/renderers/image.tsx +35 -6
  55. package/src/renderers/list.tsx +5 -13
  56. package/src/renderers/math.tsx +8 -2
  57. package/src/renderers/paragraph.tsx +1 -3
  58. package/src/specs/MarkdownSession.nitro.ts +4 -6
package/README.md CHANGED
@@ -76,8 +76,8 @@ cd ios && pod install
76
76
  If you are using Expo, you must run a **Prebuild** (Development Build) because this package contains native C++ code.
77
77
 
78
78
  ```bash
79
- npx expo install react-native-nitro-markdown react-native-nitro-modules
80
- npx expo prebuild
79
+ bunx expo install react-native-nitro-markdown react-native-nitro-modules
80
+ bunx expo prebuild
81
81
  ```
82
82
 
83
83
  ---
@@ -265,10 +265,12 @@ For maximum control, data processing, or minimal JS overhead:
265
265
  import {
266
266
  parseMarkdown,
267
267
  getTextContent,
268
+ getFlattenedText,
268
269
  } from "react-native-nitro-markdown/headless";
269
270
 
270
271
  const ast = parseMarkdown("# Hello World");
271
272
  const text = getTextContent(ast); // "Hello World"
273
+ const fullText = getFlattenedText(ast); // "Hello World\n\n" (Normalized with line breaks)
272
274
  ```
273
275
 
274
276
  ### Option 10: High-Performance Streaming (LLMs)
@@ -295,6 +297,21 @@ export function AIResponseStream() {
295
297
  }
296
298
  ```
297
299
 
300
+ ### Option 11: Extracting Plain Text
301
+
302
+ 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
+
304
+ ```tsx
305
+ <Markdown
306
+ onParseComplete={(result) => {
307
+ console.log(result.text); // "Hello World\n\nThis is bold text."
308
+ console.log(result.ast); // Full AST
309
+ }}
310
+ >
311
+ {markdown}
312
+ </Markdown>
313
+ ```
314
+
298
315
  ---
299
316
 
300
317
  ## 🎨 Using Context in Custom Renderers
@@ -324,6 +341,7 @@ export {
324
341
  parseMarkdown,
325
342
  parseMarkdownWithOptions,
326
343
  getTextContent,
344
+ getFlattenedText,
327
345
  } from "./headless";
328
346
 
329
347
  // Theme presets
@@ -48,6 +48,8 @@ std::string HybridMarkdownParser::nodeToJson(const std::shared_ptr<InternalMarkd
48
48
  std::ostringstream json;
49
49
  json << "{";
50
50
  json << "\"type\":\"" << ::NitroMarkdown::nodeTypeToString(node->type) << "\"";
51
+ json << ",\"beg\":" << node->beg;
52
+ json << ",\"end\":" << node->end;
51
53
 
52
54
  if (node->content.has_value()) {
53
55
  json << ",\"content\":\"" << escapeJson(node->content.value()) << "\"";
@@ -12,6 +12,9 @@ public:
12
12
  std::stack<std::shared_ptr<MarkdownNode>> nodeStack;
13
13
  std::string currentText;
14
14
  const char* inputText = nullptr;
15
+ size_t inputTextSize = 0;
16
+ OFF currentTextBeg = 0;
17
+ OFF lastTextEnd = 0;
15
18
 
16
19
  void reset() {
17
20
  root = std::make_shared<MarkdownNode>(NodeType::Document);
@@ -19,28 +22,34 @@ public:
19
22
  nodeStack.push(root);
20
23
  currentText.clear();
21
24
  currentText.reserve(256);
25
+ currentTextBeg = 0;
26
+ lastTextEnd = 0;
22
27
  }
23
28
 
24
29
  void flushText() {
25
30
  if (!currentText.empty() && !nodeStack.empty()) {
26
31
  auto textNode = std::make_shared<MarkdownNode>(NodeType::Text);
27
32
  textNode->content = std::move(currentText);
33
+ textNode->beg = currentTextBeg;
34
+ textNode->end = lastTextEnd;
28
35
  nodeStack.top()->addChild(std::move(textNode));
29
36
  currentText.clear();
30
37
  }
31
38
  }
32
39
 
33
- void pushNode(std::shared_ptr<MarkdownNode> node) {
40
+ void pushNode(std::shared_ptr<MarkdownNode> node, OFF beg = 0) {
34
41
  flushText();
35
42
  if (node && !nodeStack.empty()) {
43
+ node->beg = beg;
36
44
  nodeStack.top()->addChild(node);
37
45
  nodeStack.push(std::move(node));
38
46
  }
39
47
  }
40
48
 
41
- void popNode() {
49
+ void popNode(OFF end = 0) {
42
50
  flushText();
43
51
  if (nodeStack.size() > 1) {
52
+ nodeStack.top()->end = end;
44
53
  nodeStack.pop();
45
54
  }
46
55
  }
@@ -68,7 +77,7 @@ public:
68
77
  return result;
69
78
  }
70
79
 
71
- static int enterBlock(MD_BLOCKTYPE type, void* detail, void* userdata) {
80
+ static int enterBlock(MD_BLOCKTYPE type, void* detail, MD_OFFSET off, void* userdata) {
72
81
  auto* impl = static_cast<Impl*>(userdata);
73
82
 
74
83
  switch (type) {
@@ -76,14 +85,14 @@ public:
76
85
  break;
77
86
 
78
87
  case MD_BLOCK_QUOTE: {
79
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Blockquote));
88
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Blockquote), off);
80
89
  break;
81
90
  }
82
91
 
83
92
  case MD_BLOCK_UL: {
84
93
  auto node = std::make_shared<MarkdownNode>(NodeType::List);
85
94
  node->ordered = false;
86
- impl->pushNode(node);
95
+ impl->pushNode(node, off);
87
96
  break;
88
97
  }
89
98
 
@@ -92,7 +101,7 @@ public:
92
101
  auto node = std::make_shared<MarkdownNode>(NodeType::List);
93
102
  node->ordered = true;
94
103
  node->start = d->start;
95
- impl->pushNode(node);
104
+ impl->pushNode(node, off);
96
105
  break;
97
106
  }
98
107
 
@@ -101,17 +110,15 @@ public:
101
110
  if (d->is_task) {
102
111
  auto node = std::make_shared<MarkdownNode>(NodeType::TaskListItem);
103
112
  node->checked = (d->task_mark == 'x' || d->task_mark == 'X');
104
- impl->pushNode(node);
113
+ impl->pushNode(node, off);
105
114
  } else {
106
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::ListItem));
115
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::ListItem), off);
107
116
  }
108
117
  break;
109
118
  }
110
119
 
111
120
  case MD_BLOCK_HR: {
112
- impl->flushText();
113
- impl->nodeStack.top()->addChild(
114
- std::make_shared<MarkdownNode>(NodeType::HorizontalRule));
121
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::HorizontalRule), off);
115
122
  break;
116
123
  }
117
124
 
@@ -119,7 +126,7 @@ public:
119
126
  auto* d = static_cast<MD_BLOCK_H_DETAIL*>(detail);
120
127
  auto node = std::make_shared<MarkdownNode>(NodeType::Heading);
121
128
  node->level = d->level;
122
- impl->pushNode(node);
129
+ impl->pushNode(node, off);
123
130
  break;
124
131
  }
125
132
 
@@ -129,37 +136,37 @@ public:
129
136
  if (d->lang.text && d->lang.size > 0) {
130
137
  node->language = std::string(d->lang.text, d->lang.size);
131
138
  }
132
- impl->pushNode(node);
139
+ impl->pushNode(node, off);
133
140
  break;
134
141
  }
135
142
 
136
143
  case MD_BLOCK_HTML: {
137
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::HtmlBlock));
144
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::HtmlBlock), off);
138
145
  break;
139
146
  }
140
147
 
141
148
  case MD_BLOCK_P: {
142
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Paragraph));
149
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Paragraph), off);
143
150
  break;
144
151
  }
145
152
 
146
153
  case MD_BLOCK_TABLE: {
147
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Table));
154
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Table), off);
148
155
  break;
149
156
  }
150
157
 
151
158
  case MD_BLOCK_THEAD: {
152
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableHead));
159
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableHead), off);
153
160
  break;
154
161
  }
155
162
 
156
163
  case MD_BLOCK_TBODY: {
157
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableBody));
164
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableBody), off);
158
165
  break;
159
166
  }
160
167
 
161
168
  case MD_BLOCK_TR: {
162
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableRow));
169
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::TableRow), off);
163
170
  break;
164
171
  }
165
172
 
@@ -173,7 +180,7 @@ public:
173
180
  case MD_ALIGN_RIGHT: node->align = TextAlign::Right; break;
174
181
  default: node->align = TextAlign::Default; break;
175
182
  }
176
- impl->pushNode(node);
183
+ impl->pushNode(node, off);
177
184
  break;
178
185
  }
179
186
 
@@ -187,7 +194,7 @@ public:
187
194
  case MD_ALIGN_RIGHT: node->align = TextAlign::Right; break;
188
195
  default: node->align = TextAlign::Default; break;
189
196
  }
190
- impl->pushNode(node);
197
+ impl->pushNode(node, off);
191
198
  break;
192
199
  }
193
200
  }
@@ -195,38 +202,41 @@ public:
195
202
  return 0;
196
203
  }
197
204
 
198
- static int leaveBlock(MD_BLOCKTYPE type, void* detail, void* userdata) {
205
+ static int leaveBlock(MD_BLOCKTYPE type, void* detail, MD_OFFSET off, void* userdata) {
199
206
  (void)detail;
200
207
  auto* impl = static_cast<Impl*>(userdata);
201
208
 
202
209
  switch (type) {
203
210
  case MD_BLOCK_DOC:
211
+ impl->root->end = off;
212
+ break;
204
213
  case MD_BLOCK_HR:
214
+ impl->popNode(off);
205
215
  break;
206
216
  default:
207
- impl->popNode();
217
+ impl->popNode(off);
208
218
  break;
209
219
  }
210
220
 
211
221
  return 0;
212
222
  }
213
223
 
214
- static int enterSpan(MD_SPANTYPE type, void* detail, void* userdata) {
224
+ static int enterSpan(MD_SPANTYPE type, void* detail, MD_OFFSET off, void* userdata) {
215
225
  auto* impl = static_cast<Impl*>(userdata);
216
226
 
217
227
  switch (type) {
218
228
  case MD_SPAN_EM: {
219
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic));
229
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic), off);
220
230
  break;
221
231
  }
222
232
 
223
233
  case MD_SPAN_STRONG: {
224
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Bold));
234
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Bold), off);
225
235
  break;
226
236
  }
227
237
 
228
238
  case MD_SPAN_DEL: {
229
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Strikethrough));
239
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Strikethrough), off);
230
240
  break;
231
241
  }
232
242
 
@@ -239,7 +249,7 @@ public:
239
249
  if (d->title.text && d->title.size > 0) {
240
250
  node->title = std::string(d->title.text, d->title.size);
241
251
  }
242
- impl->pushNode(node);
252
+ impl->pushNode(node, off);
243
253
  break;
244
254
  }
245
255
 
@@ -252,33 +262,33 @@ public:
252
262
  if (d->title.text && d->title.size > 0) {
253
263
  node->title = std::string(d->title.text, d->title.size);
254
264
  }
255
- impl->pushNode(node);
265
+ impl->pushNode(node, off);
256
266
  break;
257
267
  }
258
268
 
259
269
  case MD_SPAN_CODE: {
260
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::CodeInline));
270
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::CodeInline), off);
261
271
  break;
262
272
  }
263
273
 
264
274
  case MD_SPAN_LATEXMATH: {
265
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathInline));
275
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathInline), off);
266
276
  break;
267
277
  }
268
278
 
269
279
  case MD_SPAN_LATEXMATH_DISPLAY: {
270
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathBlock));
280
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::MathBlock), off);
271
281
  break;
272
282
  }
273
283
 
274
284
  case MD_SPAN_U: {
275
- impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic));
285
+ impl->pushNode(std::make_shared<MarkdownNode>(NodeType::Italic), off);
276
286
  break;
277
287
  }
278
288
 
279
289
  case MD_SPAN_WIKILINK: {
280
290
  auto node = std::make_shared<MarkdownNode>(NodeType::Link);
281
- impl->pushNode(node);
291
+ impl->pushNode(node, off);
282
292
  break;
283
293
  }
284
294
  }
@@ -286,7 +296,7 @@ public:
286
296
  return 0;
287
297
  }
288
298
 
289
- static int leaveSpan(MD_SPANTYPE type, void* detail, void* userdata) {
299
+ static int leaveSpan(MD_SPANTYPE type, void* detail, MD_OFFSET off, void* userdata) {
290
300
  (void)detail;
291
301
  auto* impl = static_cast<Impl*>(userdata);
292
302
 
@@ -309,7 +319,7 @@ public:
309
319
  }
310
320
  }
311
321
 
312
- impl->popNode();
322
+ impl->popNode(off);
313
323
  return 0;
314
324
  }
315
325
 
@@ -320,6 +330,7 @@ public:
320
330
 
321
331
  switch (type) {
322
332
  case MD_TEXT_NULLCHAR:
333
+ if (impl->currentText.empty()) impl->currentTextBeg = impl->lastTextEnd;
323
334
  impl->currentText += '\0';
324
335
  break;
325
336
 
@@ -345,15 +356,37 @@ public:
345
356
  break;
346
357
 
347
358
  case MD_TEXT_ENTITY:
348
- impl->currentText.append(text, size);
359
+ if (text && size > 0) {
360
+ MD_OFFSET off = impl->lastTextEnd;
361
+ ptrdiff_t diff = text - impl->inputText;
362
+ if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
363
+ off = static_cast<MD_OFFSET>(diff);
364
+ }
365
+ if (impl->currentText.empty()) impl->currentTextBeg = off;
366
+ impl->currentText.append(text, size);
367
+ impl->lastTextEnd = off + size;
368
+ }
349
369
  break;
350
370
 
351
371
  case MD_TEXT_NORMAL:
352
372
  case MD_TEXT_CODE:
353
373
  case MD_TEXT_LATEXMATH:
354
- default:
355
- impl->currentText.append(text, size);
374
+ default: {
375
+ if (text && size > 0) {
376
+ MD_OFFSET off = impl->lastTextEnd;
377
+ ptrdiff_t diff = text - impl->inputText;
378
+ if (diff >= 0 && static_cast<size_t>(diff) <= impl->inputTextSize) {
379
+ off = static_cast<MD_OFFSET>(diff);
380
+ }
381
+
382
+ if (impl->currentText.empty()) {
383
+ impl->currentTextBeg = off;
384
+ }
385
+ impl->currentText.append(text, size);
386
+ impl->lastTextEnd = off + size;
387
+ }
356
388
  break;
389
+ }
357
390
  }
358
391
 
359
392
  return 0;
@@ -367,6 +400,7 @@ MD4CParser::~MD4CParser() = default;
367
400
  std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, const ParserOptions& options) {
368
401
  impl_->reset();
369
402
  impl_->inputText = markdown.c_str();
403
+ impl_->inputTextSize = markdown.size();
370
404
 
371
405
  unsigned int flags = MD_FLAG_NOHTML;
372
406
 
@@ -7,6 +7,9 @@
7
7
 
8
8
  namespace NitroMarkdown {
9
9
 
10
+ typedef unsigned MD_OFFSET;
11
+ typedef MD_OFFSET OFF;
12
+
10
13
  enum class NodeType {
11
14
  Document,
12
15
  Heading,
@@ -99,9 +102,11 @@ struct MarkdownNode {
99
102
  std::optional<bool> checked;
100
103
  std::optional<bool> isHeader;
101
104
  std::optional<TextAlign> align;
105
+ OFF beg;
106
+ OFF end;
102
107
  std::vector<std::shared_ptr<MarkdownNode>> children;
103
108
 
104
- explicit MarkdownNode(NodeType t) : type(t) {}
109
+ explicit MarkdownNode(NodeType t) : type(t), beg(0), end(0) {}
105
110
 
106
111
  void addChild(std::shared_ptr<MarkdownNode> child) {
107
112
  if (child) {