react-native-nitro-markdown 0.5.3 → 0.5.5
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 +89 -10
- package/android/CMakeLists.txt +1 -1
- package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +9 -3
- package/cpp/CMakeLists.txt +2 -1
- package/cpp/bindings/HybridMarkdownParser.cpp +4 -2
- package/cpp/core/MD4CParser.cpp +69 -2
- package/cpp/core/MD4CParser.hpp +17 -0
- package/cpp/core/MarkdownTypes.hpp +1 -1
- package/lib/commonjs/headless.js +2 -2
- package/lib/commonjs/markdown.js +56 -44
- 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 +57 -53
- 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 +12 -3
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/list.js +75 -70
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/math.js +4 -3
- 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/index.js +7 -4
- package/lib/commonjs/renderers/table/index.js.map +1 -1
- package/lib/module/headless.js +2 -2
- package/lib/module/markdown.js +56 -44
- 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 +57 -53
- 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 +13 -4
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/list.js +75 -70
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/math.js +4 -3
- 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/index.js +7 -4
- package/lib/module/renderers/table/index.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 +2 -2
- package/lib/typescript/commonjs/markdown.d.ts +7 -1
- 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/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/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts +2 -2
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/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 +2 -2
- package/lib/typescript/module/markdown.d.ts +7 -1
- 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/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/index.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts +2 -2
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/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 +6 -5
- package/react-native-nitro-markdown.podspec +3 -0
- package/src/Markdown.nitro.ts +1 -0
- package/src/headless.ts +2 -2
- package/src/markdown.tsx +102 -58
- package/src/renderers/blockquote.tsx +22 -17
- package/src/renderers/code.tsx +75 -63
- package/src/renderers/heading.tsx +60 -54
- package/src/renderers/horizontal-rule.tsx +17 -12
- package/src/renderers/image.tsx +15 -4
- package/src/renderers/list.tsx +93 -76
- package/src/renderers/math.tsx +8 -3
- package/src/renderers/paragraph.tsx +22 -17
- package/src/renderers/style-cache.ts +14 -0
- package/src/renderers/table/index.tsx +15 -10
- package/src/theme.ts +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ Native Markdown parsing and rendering for React Native, powered by [md4c](https:
|
|
|
12
12
|
- **Full rendering pipeline** -- parser + renderer + streaming session in one package
|
|
13
13
|
- **GFM support** -- tables, strikethrough, task lists, autolinks
|
|
14
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
|
|
15
18
|
- **Headless API** -- parse markdown and extract text without any UI
|
|
16
19
|
- **Streaming** -- incremental rendering for chat/LLM token streams
|
|
17
20
|
- **Customizable** -- themes, per-node style overrides, custom renderers, AST transforms, plugin pipeline
|
|
@@ -21,17 +24,26 @@ Native Markdown parsing and rendering for React Native, powered by [md4c](https:
|
|
|
21
24
|
| Dependency | Version |
|
|
22
25
|
|---|---|
|
|
23
26
|
| React Native | `>=0.75.0` |
|
|
24
|
-
| react-native-nitro-modules | `>=0.35.
|
|
27
|
+
| react-native-nitro-modules | `>=0.35.4` |
|
|
25
28
|
| react-native-mathjax-svg *(optional)* | `>=0.9.0` |
|
|
26
29
|
| react-native-svg *(optional, for math)* | `>=13.0.0` |
|
|
27
30
|
|
|
28
31
|
## Installation
|
|
29
32
|
|
|
33
|
+
With npm:
|
|
34
|
+
|
|
30
35
|
```bash
|
|
31
36
|
npm install react-native-nitro-markdown react-native-nitro-modules
|
|
32
37
|
cd ios && pod install
|
|
33
38
|
```
|
|
34
39
|
|
|
40
|
+
With Bun:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bun add react-native-nitro-markdown react-native-nitro-modules
|
|
44
|
+
cd ios && pod install
|
|
45
|
+
```
|
|
46
|
+
|
|
35
47
|
For math rendering:
|
|
36
48
|
|
|
37
49
|
```bash
|
|
@@ -61,6 +73,22 @@ export function Example() {
|
|
|
61
73
|
|
|
62
74
|
## API Reference
|
|
63
75
|
|
|
76
|
+
### Parser Options
|
|
77
|
+
|
|
78
|
+
`ParserOptions` controls native parser extensions. Defaults are conservative for HTML and feature-complete for Markdown extensions.
|
|
79
|
+
|
|
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 |
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<Markdown options={{ gfm: true, math: true, html: false }}>
|
|
88
|
+
{content}
|
|
89
|
+
</Markdown>
|
|
90
|
+
```
|
|
91
|
+
|
|
64
92
|
### `<Markdown>`
|
|
65
93
|
|
|
66
94
|
The main component. Parses a markdown string and renders it.
|
|
@@ -72,9 +100,10 @@ import { Markdown } from "react-native-nitro-markdown";
|
|
|
72
100
|
| Prop | Type | Default | Description |
|
|
73
101
|
|---|---|---|---|
|
|
74
102
|
| `children` | `string` | required | Markdown input string |
|
|
75
|
-
| `options` | `ParserOptions` | -- | Parser flags (`gfm`, `math`) |
|
|
103
|
+
| `options` | `ParserOptions` | -- | Parser flags (`gfm`, `math`, `html`) |
|
|
76
104
|
| `plugins` | `MarkdownPlugin[]` | -- | Plugin hooks (`beforeParse`, `afterParse`) |
|
|
77
105
|
| `sourceAst` | `MarkdownNode` | -- | Pre-parsed AST; skips native parse when provided |
|
|
106
|
+
| `parseCache` | `boolean` | `true` | Enable internal parse result caching for repeated inputs |
|
|
78
107
|
| `astTransform` | `AstTransform` | -- | Post-parse AST rewrite before render |
|
|
79
108
|
| `renderers` | `CustomRenderers` | `{}` | Per-node custom renderer overrides |
|
|
80
109
|
| `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
|
|
@@ -93,6 +122,10 @@ import { Markdown } from "react-native-nitro-markdown";
|
|
|
93
122
|
|
|
94
123
|
**Pipeline order:** `beforeParse` plugins (by priority desc) -> parse/sourceAst -> `afterParse` plugins (by priority desc) -> `astTransform` -> render.
|
|
95
124
|
|
|
125
|
+
When `sourceAst` is provided, `beforeParse` plugins are skipped because no source string is parsed. `afterParse` plugins still run on the provided AST.
|
|
126
|
+
|
|
127
|
+
`parseCache` defaults to `true` and caches parse results for repeated markdown inputs. Set `parseCache={false}` to force a fresh native parse on each input change. This flag has no effect when `sourceAst` is provided.
|
|
128
|
+
|
|
96
129
|
### `<MarkdownStream>`
|
|
97
130
|
|
|
98
131
|
Renders markdown from a streaming session. Extends `MarkdownProps` (minus `children`).
|
|
@@ -174,7 +207,7 @@ import {
|
|
|
174
207
|
|
|
175
208
|
| Function | Description |
|
|
176
209
|
|---|---|
|
|
177
|
-
| `parseMarkdown(text, options?)` | Parse to AST (options: `{ gfm?, math? }`) |
|
|
210
|
+
| `parseMarkdown(text, options?)` | Parse to AST (options: `{ gfm?, math?, html? }`) |
|
|
178
211
|
| `parseMarkdownWithOptions(text, options)` | Parse with explicit options |
|
|
179
212
|
| `extractPlainText(text)` | Parse and return plain text from native parser |
|
|
180
213
|
| `extractPlainTextWithOptions(text, options)` | Same with parser flags |
|
|
@@ -218,9 +251,41 @@ Renderers receive `EnhancedRendererProps` with `node`, `children`, and `Renderer
|
|
|
218
251
|
| `code_inline` | `content` |
|
|
219
252
|
| `list` | `ordered`, `start` |
|
|
220
253
|
| `task_list_item` | `checked` |
|
|
254
|
+
| `html_inline`, `html_block` | Read raw HTML from `node.content` |
|
|
221
255
|
|
|
222
256
|
Return `undefined` to fall back to the built-in renderer, or `null` to render nothing.
|
|
223
257
|
|
|
258
|
+
#### Rendering HTML Nodes
|
|
259
|
+
|
|
260
|
+
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.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { Text, View } from "react-native";
|
|
264
|
+
import { Markdown } from "react-native-nitro-markdown";
|
|
265
|
+
|
|
266
|
+
<Markdown
|
|
267
|
+
options={{ html: true }}
|
|
268
|
+
renderers={{
|
|
269
|
+
html_inline: ({ node }) => {
|
|
270
|
+
if (node.content === "<br>") return <Text>{"\n"}</Text>;
|
|
271
|
+
return null;
|
|
272
|
+
},
|
|
273
|
+
html_block: ({ node }) => {
|
|
274
|
+
if (!node.content?.includes('data-kind="release-note"')) return null;
|
|
275
|
+
return (
|
|
276
|
+
<View>
|
|
277
|
+
<Text>Release note</Text>
|
|
278
|
+
</View>
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{content}
|
|
284
|
+
</Markdown>;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Do not pass untrusted HTML directly into a WebView from a renderer. Sanitize or map known tags/attributes to native components.
|
|
288
|
+
|
|
224
289
|
### Theme API
|
|
225
290
|
|
|
226
291
|
```tsx
|
|
@@ -337,11 +402,17 @@ const plugins: MarkdownPlugin[] = [
|
|
|
337
402
|
```tsx
|
|
338
403
|
import { Markdown, parseMarkdownWithOptions } from "react-native-nitro-markdown";
|
|
339
404
|
|
|
340
|
-
const ast = parseMarkdownWithOptions(content, {
|
|
405
|
+
const ast = parseMarkdownWithOptions(content, {
|
|
406
|
+
gfm: true,
|
|
407
|
+
math: true,
|
|
408
|
+
html: false,
|
|
409
|
+
});
|
|
341
410
|
|
|
342
411
|
<Markdown sourceAst={ast}>{"ignored when sourceAst is provided"}</Markdown>;
|
|
343
412
|
```
|
|
344
413
|
|
|
414
|
+
With `sourceAst`, `beforeParse` plugins are skipped, while `afterParse` and `astTransform` still apply.
|
|
415
|
+
|
|
345
416
|
### Virtualization (large documents)
|
|
346
417
|
|
|
347
418
|
```tsx
|
|
@@ -363,6 +434,8 @@ Keep `Markdown` as the primary vertical scroller when virtualization is enabled
|
|
|
363
434
|
### Syntax Highlighting
|
|
364
435
|
|
|
365
436
|
```tsx
|
|
437
|
+
import type { CodeHighlighter } from "react-native-nitro-markdown";
|
|
438
|
+
|
|
366
439
|
// Built-in highlighter (JS/TS, Python, Bash)
|
|
367
440
|
<Markdown highlightCode>{"```typescript\nconst x: number = 42;\n```"}</Markdown>
|
|
368
441
|
|
|
@@ -395,7 +468,7 @@ Default link behavior: trims href, calls `onLinkPress`, validates scheme (`http:
|
|
|
395
468
|
|
|
396
469
|
`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`
|
|
397
470
|
|
|
398
|
-
`html_inline` and `html_block` are parsed
|
|
471
|
+
`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`.
|
|
399
472
|
|
|
400
473
|
## Package Exports
|
|
401
474
|
|
|
@@ -409,10 +482,11 @@ Parser and text utilities only -- no React dependencies. Use this for server-sid
|
|
|
409
482
|
|
|
410
483
|
## Performance Tips
|
|
411
484
|
|
|
412
|
-
- Use `updateStrategy="raf"` for streaming
|
|
413
|
-
- Batch `session.append()` calls
|
|
414
|
-
- Enable `virtualize` for
|
|
415
|
-
-
|
|
485
|
+
- Use `updateStrategy="raf"` for streaming UI updates.
|
|
486
|
+
- Batch `session.append()` calls at 50-100ms intervals rather than per-character.
|
|
487
|
+
- Enable `virtualize="auto"` for long documents and keep `Markdown` as the primary vertical scroller.
|
|
488
|
+
- Memoize custom `plugins`, `renderers`, `theme`, and `styles` objects when they are created inside a component.
|
|
489
|
+
- Use the headless API when you only need parsing, plain text extraction, search indexing, or a custom renderer.
|
|
416
490
|
|
|
417
491
|
## Troubleshooting
|
|
418
492
|
|
|
@@ -432,9 +506,14 @@ The `apps/example` directory contains a full demo app with these screens:
|
|
|
432
506
|
| Bench | `app/index.tsx` | Smoke tests + benchmark vs JS parsers |
|
|
433
507
|
| Default | `app/render-default.tsx` | Built-in renderer defaults |
|
|
434
508
|
| Styles | `app/render-default-styles.tsx` | Theme and style overrides |
|
|
435
|
-
| Custom | `app/render-custom.tsx` |
|
|
509
|
+
| Custom | `app/render-custom.tsx` | HTML AST rendering, custom renderers, AST transform |
|
|
436
510
|
| Stream | `app/render-stream.tsx` | Live streaming with token append |
|
|
437
511
|
|
|
512
|
+
## Release Checks
|
|
513
|
+
|
|
514
|
+
- `bun run harness` runs lint, typecheck, JS coverage, benchmark checks, and C++ coverage.
|
|
515
|
+
- `bun run publish-package -- --dry-run --yes` validates release docs, runs publish verification, builds the package, and performs an npm dry run.
|
|
516
|
+
|
|
438
517
|
## Contributing
|
|
439
518
|
|
|
440
519
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/android/CMakeLists.txt
CHANGED
|
@@ -13,6 +13,7 @@ set(CPP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../cpp")
|
|
|
13
13
|
file(GLOB MD4C_SOURCES "${CPP_ROOT}/md4c/*.c")
|
|
14
14
|
file(GLOB CORE_SOURCES "${CPP_ROOT}/core/*.cpp")
|
|
15
15
|
file(GLOB BINDING_SOURCES "${CPP_ROOT}/bindings/*.cpp")
|
|
16
|
+
list(FILTER CORE_SOURCES EXCLUDE REGEX ".*Test\\.cpp$")
|
|
16
17
|
|
|
17
18
|
# Create the shared library with our sources
|
|
18
19
|
add_library(${PROJECT_NAME} SHARED
|
|
@@ -44,4 +45,3 @@ target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
|
|
|
44
45
|
$<$<CONFIG:Release>:-O3 -DNDEBUG>
|
|
45
46
|
$<$<CONFIG:Debug>:-O0 -g>
|
|
46
47
|
)
|
|
47
|
-
|
|
@@ -62,7 +62,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
override fun getTextRange(from: Double, to: Double): String {
|
|
65
|
-
if (from.
|
|
65
|
+
if (!from.isFinite() || !to.isFinite() || from < 0.0 || to < 0.0 || from > to) return ""
|
|
66
66
|
synchronized(lock) {
|
|
67
67
|
val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
|
|
68
68
|
val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
|
|
@@ -88,15 +88,21 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
override fun replace(from: Double, to: Double, text: String): Double {
|
|
91
|
-
require(from
|
|
91
|
+
require(from.isFinite() && to.isFinite() && from >= 0.0 && to >= 0.0 && to >= from) {
|
|
92
|
+
"Invalid range: from=$from and to=$to must be finite, from must be >= 0, and to must be >= from"
|
|
93
|
+
}
|
|
92
94
|
val newLength: Double
|
|
95
|
+
val notifyFrom: Double
|
|
96
|
+
val notifyTo: Double
|
|
93
97
|
synchronized(lock) {
|
|
94
98
|
val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
|
|
95
99
|
val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
|
|
96
100
|
buffer.replace(start, end, text)
|
|
97
101
|
newLength = buffer.length.toDouble()
|
|
102
|
+
notifyFrom = start.toDouble()
|
|
103
|
+
notifyTo = start.toDouble() + text.length.toDouble()
|
|
98
104
|
}
|
|
99
|
-
notifyListeners(
|
|
105
|
+
notifyListeners(notifyFrom, notifyTo)
|
|
100
106
|
return newLength
|
|
101
107
|
}
|
|
102
108
|
|
package/cpp/CMakeLists.txt
CHANGED
|
@@ -30,6 +30,7 @@ target_include_directories(MD4CCore PUBLIC
|
|
|
30
30
|
# Preprocessor definitions
|
|
31
31
|
target_compile_definitions(MD4CCore PRIVATE
|
|
32
32
|
MD4C_USE_UTF8=1
|
|
33
|
+
NITRO_MARKDOWN_TESTING=1
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
# Create the test executable
|
|
@@ -43,4 +44,4 @@ target_link_libraries(MD4CParserTest PRIVATE MD4CCore)
|
|
|
43
44
|
# Include directories for the test
|
|
44
45
|
target_include_directories(MD4CParserTest PRIVATE
|
|
45
46
|
"${CPP_ROOT}/core"
|
|
46
|
-
)
|
|
47
|
+
)
|
|
@@ -249,7 +249,7 @@ std::string flattenNodeText(const std::shared_ptr<InternalMarkdownNode>& node) {
|
|
|
249
249
|
} // namespace
|
|
250
250
|
|
|
251
251
|
std::string HybridMarkdownParser::parse(const std::string& text) {
|
|
252
|
-
InternalParserOptions opts{.gfm = true, .math = true};
|
|
252
|
+
InternalParserOptions opts{.gfm = true, .math = true, .html = false};
|
|
253
253
|
|
|
254
254
|
auto ast = parser_->parse(text, opts);
|
|
255
255
|
return nodeToJson(ast);
|
|
@@ -259,13 +259,14 @@ std::string HybridMarkdownParser::parseWithOptions(const std::string& text, cons
|
|
|
259
259
|
InternalParserOptions internalOpts;
|
|
260
260
|
internalOpts.gfm = options.gfm.value_or(true);
|
|
261
261
|
internalOpts.math = options.math.value_or(true);
|
|
262
|
+
internalOpts.html = options.html.value_or(false);
|
|
262
263
|
|
|
263
264
|
auto ast = parser_->parse(text, internalOpts);
|
|
264
265
|
return nodeToJson(ast);
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
std::string HybridMarkdownParser::extractPlainText(const std::string& text) {
|
|
268
|
-
InternalParserOptions opts{.gfm = true, .math = true};
|
|
269
|
+
InternalParserOptions opts{.gfm = true, .math = true, .html = false};
|
|
269
270
|
|
|
270
271
|
auto ast = parser_->parse(text, opts);
|
|
271
272
|
return flattenNodeText(ast);
|
|
@@ -275,6 +276,7 @@ std::string HybridMarkdownParser::extractPlainTextWithOptions(const std::string&
|
|
|
275
276
|
InternalParserOptions internalOpts;
|
|
276
277
|
internalOpts.gfm = options.gfm.value_or(true);
|
|
277
278
|
internalOpts.math = options.math.value_or(true);
|
|
279
|
+
internalOpts.html = options.html.value_or(false);
|
|
278
280
|
|
|
279
281
|
auto ast = parser_->parse(text, internalOpts);
|
|
280
282
|
return flattenNodeText(ast);
|
package/cpp/core/MD4CParser.cpp
CHANGED
|
@@ -424,10 +424,28 @@ public:
|
|
|
424
424
|
|
|
425
425
|
case MD_TEXT_HTML:
|
|
426
426
|
impl->flushText();
|
|
427
|
-
if (!impl->nodeStack.empty()) {
|
|
427
|
+
if (!impl->nodeStack.empty() && text && size > 0) {
|
|
428
|
+
MD_OFFSET off = safeOffset(text, impl->inputText, impl->inputTextSize);
|
|
429
|
+
if (off == 0 && text != impl->inputText) off = impl->lastTextEnd;
|
|
430
|
+
|
|
431
|
+
if (impl->nodeStack.top()->type == NodeType::HtmlBlock) {
|
|
432
|
+
auto htmlBlock = impl->nodeStack.top();
|
|
433
|
+
if (htmlBlock->content.has_value()) {
|
|
434
|
+
htmlBlock->content->append(text, size);
|
|
435
|
+
} else {
|
|
436
|
+
htmlBlock->content = std::string(text, size);
|
|
437
|
+
}
|
|
438
|
+
htmlBlock->end = off + size;
|
|
439
|
+
impl->lastTextEnd = off + size;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
|
|
428
443
|
auto node = std::make_shared<MarkdownNode>(NodeType::HtmlInline);
|
|
429
444
|
node->content = std::string(text, size);
|
|
445
|
+
node->beg = off;
|
|
446
|
+
node->end = off + size;
|
|
430
447
|
impl->nodeStack.top()->addChild(node);
|
|
448
|
+
impl->lastTextEnd = off + size;
|
|
431
449
|
}
|
|
432
450
|
break;
|
|
433
451
|
|
|
@@ -471,12 +489,20 @@ MD4CParser::MD4CParser() : impl_(std::make_unique<Impl>()) {}
|
|
|
471
489
|
MD4CParser::~MD4CParser() = default;
|
|
472
490
|
|
|
473
491
|
std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, const ParserOptions& options) {
|
|
492
|
+
return parseWithFlags(markdown, options, 0);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
std::shared_ptr<MarkdownNode> MD4CParser::parseWithFlags(
|
|
496
|
+
const std::string& markdown,
|
|
497
|
+
const ParserOptions& options,
|
|
498
|
+
unsigned int extraFlags
|
|
499
|
+
) {
|
|
474
500
|
impl_->reset();
|
|
475
501
|
impl_->inputText = markdown.c_str();
|
|
476
502
|
size_t inputSize = clampInputSize(markdown.size());
|
|
477
503
|
impl_->inputTextSize = inputSize;
|
|
478
504
|
|
|
479
|
-
unsigned int flags = MD_FLAG_NOHTML;
|
|
505
|
+
unsigned int flags = options.html ? 0 : MD_FLAG_NOHTML;
|
|
480
506
|
|
|
481
507
|
if (options.gfm) {
|
|
482
508
|
flags |= MD_FLAG_TABLES;
|
|
@@ -488,6 +514,7 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
|
|
|
488
514
|
if (options.math) {
|
|
489
515
|
flags |= MD_FLAG_LATEXMATHSPANS;
|
|
490
516
|
}
|
|
517
|
+
flags |= extraFlags;
|
|
491
518
|
|
|
492
519
|
MD_PARSER parser = {
|
|
493
520
|
0,
|
|
@@ -515,4 +542,44 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
|
|
|
515
542
|
return impl_->root;
|
|
516
543
|
}
|
|
517
544
|
|
|
545
|
+
#ifdef NITRO_MARKDOWN_TESTING
|
|
546
|
+
std::shared_ptr<MarkdownNode> MD4CParser::parseWithExtraFlagsForTest(
|
|
547
|
+
const std::string& markdown,
|
|
548
|
+
const ParserOptions& options,
|
|
549
|
+
unsigned int extraFlags
|
|
550
|
+
) {
|
|
551
|
+
return parseWithFlags(markdown, options, extraFlags);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
int MD4CParser::enterBlockNullUserdataForTest() {
|
|
555
|
+
return Impl::enterBlock(MD_BLOCK_DOC, nullptr, 0, nullptr);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
int MD4CParser::leaveBlockNullUserdataForTest() {
|
|
559
|
+
return Impl::leaveBlock(MD_BLOCK_DOC, nullptr, 0, nullptr);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
int MD4CParser::enterSpanNullUserdataForTest() {
|
|
563
|
+
return Impl::enterSpan(MD_SPAN_EM, nullptr, 0, nullptr);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
int MD4CParser::leaveSpanNullUserdataForTest() {
|
|
567
|
+
return Impl::leaveSpan(MD_SPAN_EM, nullptr, 0, nullptr);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
int MD4CParser::textNullUserdataForTest() {
|
|
571
|
+
return Impl::text(MD_TEXT_NORMAL, "x", 1, nullptr);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
int MD4CParser::offsetBeforeBaseForTest() {
|
|
575
|
+
char buffer[2] = {'a', 'b'};
|
|
576
|
+
return safeOffset(buffer, buffer + 1, 1);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
int MD4CParser::offsetPastBaseForTest() {
|
|
580
|
+
char buffer[2] = {'a', 'b'};
|
|
581
|
+
return safeOffset(buffer + 1, buffer, 0);
|
|
582
|
+
}
|
|
583
|
+
#endif
|
|
584
|
+
|
|
518
585
|
} // namespace NitroMarkdown
|
package/cpp/core/MD4CParser.hpp
CHANGED
|
@@ -19,6 +19,18 @@ public:
|
|
|
19
19
|
std::shared_ptr<MarkdownNode> parse(const std::string& markdown, const ParserOptions& options);
|
|
20
20
|
|
|
21
21
|
#ifdef NITRO_MARKDOWN_TESTING
|
|
22
|
+
std::shared_ptr<MarkdownNode> parseWithExtraFlagsForTest(
|
|
23
|
+
const std::string& markdown,
|
|
24
|
+
const ParserOptions& options,
|
|
25
|
+
unsigned int extraFlags
|
|
26
|
+
);
|
|
27
|
+
static int enterBlockNullUserdataForTest();
|
|
28
|
+
static int leaveBlockNullUserdataForTest();
|
|
29
|
+
static int enterSpanNullUserdataForTest();
|
|
30
|
+
static int leaveSpanNullUserdataForTest();
|
|
31
|
+
static int textNullUserdataForTest();
|
|
32
|
+
static int offsetBeforeBaseForTest();
|
|
33
|
+
static int offsetPastBaseForTest();
|
|
22
34
|
static size_t clampInputSizeForTest(size_t inputSize) {
|
|
23
35
|
size_t maxSize = static_cast<size_t>(std::numeric_limits<MD_SIZE>::max());
|
|
24
36
|
return inputSize > maxSize ? maxSize : inputSize;
|
|
@@ -27,6 +39,11 @@ public:
|
|
|
27
39
|
|
|
28
40
|
private:
|
|
29
41
|
class Impl;
|
|
42
|
+
std::shared_ptr<MarkdownNode> parseWithFlags(
|
|
43
|
+
const std::string& markdown,
|
|
44
|
+
const ParserOptions& options,
|
|
45
|
+
unsigned int extraFlags
|
|
46
|
+
);
|
|
30
47
|
std::unique_ptr<Impl> impl_;
|
|
31
48
|
};
|
|
32
49
|
|
package/lib/commonjs/headless.js
CHANGED
|
@@ -47,7 +47,7 @@ try {
|
|
|
47
47
|
/**
|
|
48
48
|
* Parse markdown text with custom options.
|
|
49
49
|
* @param text - The markdown text to parse
|
|
50
|
-
* @param options - Parser options (gfm, math)
|
|
50
|
+
* @param options - Parser options (gfm, math, html)
|
|
51
51
|
* @returns The root node of the parsed AST
|
|
52
52
|
*/
|
|
53
53
|
|
|
@@ -72,7 +72,7 @@ function parseMarkdown(text, options) {
|
|
|
72
72
|
/**
|
|
73
73
|
* Parse markdown text with custom options.
|
|
74
74
|
* @param text - The markdown text to parse
|
|
75
|
-
* @param options - Parser options (gfm, math)
|
|
75
|
+
* @param options - Parser options (gfm, math, html)
|
|
76
76
|
* @returns The root node of the parsed AST
|
|
77
77
|
*/
|
|
78
78
|
function parseMarkdownWithOptions(text, options) {
|