react-native-nitro-markdown 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +276 -0
  2. package/android/CMakeLists.txt +40 -0
  3. package/android/build.gradle +92 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +7 -0
  7. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +23 -0
  8. package/cpp/CMakeLists.txt +46 -0
  9. package/cpp/bindings/HybridMarkdownParser.cpp +112 -0
  10. package/cpp/bindings/HybridMarkdownParser.hpp +28 -0
  11. package/cpp/core/MD4CParser.cpp +442 -0
  12. package/cpp/core/MD4CParser.hpp +21 -0
  13. package/cpp/core/MarkdownTypes.hpp +119 -0
  14. package/cpp/md4c/md4c.c +6492 -0
  15. package/cpp/md4c/md4c.h +407 -0
  16. package/ios/NitroMarkdown-Bridging-Header.h +14 -0
  17. package/lib/commonjs/Markdown.nitro.js +6 -0
  18. package/lib/commonjs/Markdown.nitro.js.map +1 -0
  19. package/lib/commonjs/MarkdownJS.nitro.js +114 -0
  20. package/lib/commonjs/MarkdownJS.nitro.js.map +1 -0
  21. package/lib/commonjs/index.js +19 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/module/Markdown.nitro.js +4 -0
  24. package/lib/module/Markdown.nitro.js.map +1 -0
  25. package/lib/module/MarkdownJS.nitro.js +107 -0
  26. package/lib/module/MarkdownJS.nitro.js.map +1 -0
  27. package/lib/module/index.js +13 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/typescript/Markdown.nitro.d.ts +13 -0
  30. package/lib/typescript/Markdown.nitro.d.ts.map +1 -0
  31. package/lib/typescript/MarkdownJS.nitro.d.ts +33 -0
  32. package/lib/typescript/MarkdownJS.nitro.d.ts.map +1 -0
  33. package/lib/typescript/index.d.ts +22 -0
  34. package/lib/typescript/index.d.ts.map +1 -0
  35. package/nitro.json +16 -0
  36. package/nitrogen/generated/.gitattributes +1 -0
  37. package/nitrogen/generated/android/NitroMarkdown+autolinking.cmake +81 -0
  38. package/nitrogen/generated/android/NitroMarkdown+autolinking.gradle +27 -0
  39. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +44 -0
  40. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +25 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/NitroMarkdownOnLoad.kt +35 -0
  42. package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +60 -0
  43. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +17 -0
  44. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +27 -0
  45. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Umbrella.hpp +38 -0
  46. package/nitrogen/generated/ios/NitroMarkdownAutolinking.mm +35 -0
  47. package/nitrogen/generated/ios/NitroMarkdownAutolinking.swift +12 -0
  48. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +22 -0
  49. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +65 -0
  50. package/nitrogen/generated/shared/c++/ParserOptions.hpp +79 -0
  51. package/package.json +101 -0
  52. package/react-native-nitro-markdown.podspec +42 -0
  53. package/src/Markdown.nitro.ts +12 -0
  54. package/src/MarkdownJS.nitro.ts +113 -0
  55. package/src/index.ts +65 -0
@@ -0,0 +1,22 @@
1
+ ///
2
+ /// HybridMarkdownParserSpec.cpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #include "HybridMarkdownParserSpec.hpp"
9
+
10
+ namespace margelo::nitro::NitroMarkdown {
11
+
12
+ void HybridMarkdownParserSpec::loadHybridMethods() {
13
+ // load base methods/properties
14
+ HybridObject::loadHybridMethods();
15
+ // load custom methods/properties
16
+ registerHybrids(this, [](Prototype& prototype) {
17
+ prototype.registerHybridMethod("parse", &HybridMarkdownParserSpec::parse);
18
+ prototype.registerHybridMethod("parseWithOptions", &HybridMarkdownParserSpec::parseWithOptions);
19
+ });
20
+ }
21
+
22
+ } // namespace margelo::nitro::NitroMarkdown
@@ -0,0 +1,65 @@
1
+ ///
2
+ /// HybridMarkdownParserSpec.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/HybridObject.hpp>)
11
+ #include <NitroModules/HybridObject.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+
16
+ // Forward declaration of `ParserOptions` to properly resolve imports.
17
+ namespace margelo::nitro::NitroMarkdown { struct ParserOptions; }
18
+
19
+ #include <string>
20
+ #include "ParserOptions.hpp"
21
+
22
+ namespace margelo::nitro::NitroMarkdown {
23
+
24
+ using namespace margelo::nitro;
25
+
26
+ /**
27
+ * An abstract base class for `MarkdownParser`
28
+ * Inherit this class to create instances of `HybridMarkdownParserSpec` in C++.
29
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
30
+ * @example
31
+ * ```cpp
32
+ * class HybridMarkdownParser: public HybridMarkdownParserSpec {
33
+ * public:
34
+ * HybridMarkdownParser(...): HybridObject(TAG) { ... }
35
+ * // ...
36
+ * };
37
+ * ```
38
+ */
39
+ class HybridMarkdownParserSpec: public virtual HybridObject {
40
+ public:
41
+ // Constructor
42
+ explicit HybridMarkdownParserSpec(): HybridObject(TAG) { }
43
+
44
+ // Destructor
45
+ ~HybridMarkdownParserSpec() override = default;
46
+
47
+ public:
48
+ // Properties
49
+
50
+
51
+ public:
52
+ // Methods
53
+ virtual std::string parse(const std::string& text) = 0;
54
+ virtual std::string parseWithOptions(const std::string& text, const ParserOptions& options) = 0;
55
+
56
+ protected:
57
+ // Hybrid Setup
58
+ void loadHybridMethods() override;
59
+
60
+ protected:
61
+ // Tag for logging
62
+ static constexpr auto TAG = "MarkdownParser";
63
+ };
64
+
65
+ } // namespace margelo::nitro::NitroMarkdown
@@ -0,0 +1,79 @@
1
+ ///
2
+ /// ParserOptions.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
11
+ #include <NitroModules/JSIConverter.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
16
+ #include <NitroModules/NitroDefines.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/JSIHelpers.hpp>)
21
+ #include <NitroModules/JSIHelpers.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+
26
+
27
+
28
+ #include <optional>
29
+
30
+ namespace margelo::nitro::NitroMarkdown {
31
+
32
+ /**
33
+ * A struct which can be represented as a JavaScript object (ParserOptions).
34
+ */
35
+ struct ParserOptions {
36
+ public:
37
+ std::optional<bool> gfm SWIFT_PRIVATE;
38
+ std::optional<bool> math SWIFT_PRIVATE;
39
+
40
+ public:
41
+ ParserOptions() = default;
42
+ explicit ParserOptions(std::optional<bool> gfm, std::optional<bool> math): gfm(gfm), math(math) {}
43
+ };
44
+
45
+ } // namespace margelo::nitro::NitroMarkdown
46
+
47
+ namespace margelo::nitro {
48
+
49
+ // C++ ParserOptions <> JS ParserOptions (object)
50
+ template <>
51
+ struct JSIConverter<margelo::nitro::NitroMarkdown::ParserOptions> final {
52
+ static inline margelo::nitro::NitroMarkdown::ParserOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
53
+ jsi::Object obj = arg.asObject(runtime);
54
+ return margelo::nitro::NitroMarkdown::ParserOptions(
55
+ JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "gfm")),
56
+ JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "math"))
57
+ );
58
+ }
59
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::NitroMarkdown::ParserOptions& arg) {
60
+ jsi::Object obj(runtime);
61
+ obj.setProperty(runtime, "gfm", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.gfm));
62
+ obj.setProperty(runtime, "math", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.math));
63
+ return obj;
64
+ }
65
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
66
+ if (!value.isObject()) {
67
+ return false;
68
+ }
69
+ jsi::Object obj = value.getObject(runtime);
70
+ if (!nitro::isPlainObject(runtime, obj)) {
71
+ return false;
72
+ }
73
+ if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "gfm"))) return false;
74
+ if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "math"))) return false;
75
+ return true;
76
+ }
77
+ };
78
+
79
+ } // namespace margelo::nitro
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "react-native-nitro-markdown",
3
+ "version": "0.1.0",
4
+ "description": "High-performance Markdown parser for React Native using Nitro Modules and md4c",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "cpp",
14
+ "android",
15
+ "ios",
16
+ "nitrogen",
17
+ "nitro.json",
18
+ "*.podspec",
19
+ "!**/__tests__",
20
+ "!**/__fixtures__",
21
+ "!**/__mocks__",
22
+ "!cpp/core/*Test.cpp",
23
+ "!cpp/build",
24
+ "!scripts"
25
+ ],
26
+ "scripts": {
27
+ "build": "bob build",
28
+ "clean": "rimraf lib nitrogen/generated",
29
+ "codegen": "nitrogen --logLevel=\"debug\"",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "jest",
32
+ "test:coverage": "jest --coverage",
33
+ "benchmark": "node ../../scripts/benchmark-comparison.js",
34
+ "prepack": "node -e \"require('fs').copyFileSync('../../README.md','./README.md')\"",
35
+ "postpack": "node -e \"const fs=require('fs');if(fs.existsSync('./README.md'))fs.unlinkSync('./README.md')\"",
36
+ "test:cpp": "node scripts/test-cpp.js"
37
+ },
38
+ "keywords": [
39
+ "react-native",
40
+ "markdown",
41
+ "md4c",
42
+ "nitro",
43
+ "jsi",
44
+ "native-module",
45
+ "gfm",
46
+ "parser"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/JoaoPauloCMarra/react-native-nitro-markdown.git"
51
+ },
52
+ "author": "",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/JoaoPauloCMarra/react-native-nitro-markdown/issues"
56
+ },
57
+ "homepage": "https://github.com/JoaoPauloCMarra/react-native-nitro-markdown#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@types/jest": "^29.5.14",
63
+ "rimraf": "^6.0.1",
64
+ "@types/node": "^20.0.0",
65
+ "jest": "^29.7.0",
66
+ "nitrogen": "^0.31.10",
67
+ "react-native-builder-bob": "^0.31.0",
68
+ "react-native-nitro-modules": "^0.31.10",
69
+ "ts-jest": "^29.2.5",
70
+ "typescript": "^5.6.3"
71
+ },
72
+ "peerDependencies": {
73
+ "react": "*",
74
+ "react-native": ">=0.75.0",
75
+ "react-native-nitro-modules": ">=0.31.0"
76
+ },
77
+ "codegenConfig": {
78
+ "name": "NitroMarkdownSpec",
79
+ "type": "all",
80
+ "jsSrcsDir": "src"
81
+ },
82
+ "react-native-builder-bob": {
83
+ "source": "src",
84
+ "output": "lib",
85
+ "targets": [
86
+ [
87
+ "commonjs",
88
+ {
89
+ "esm": true
90
+ }
91
+ ],
92
+ [
93
+ "module",
94
+ {
95
+ "esm": true
96
+ }
97
+ ],
98
+ "typescript"
99
+ ]
100
+ }
101
+ }
@@ -0,0 +1,42 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-nitro-markdown"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => "13.0" }
14
+ s.source = { :git => "https://github.com/JoaoPauloCMarra/react-native-nitro-markdown.git", :tag => "#{s.version}" }
15
+
16
+ # All source files including md4c and our C++ implementation
17
+ s.source_files = [
18
+ "ios/**/*.{h,m,mm,swift}",
19
+ "cpp/**/*.{h,hpp,c,cpp}"
20
+ ]
21
+
22
+ # Ensure md4c.c is compiled as C, not C++
23
+ s.pod_target_xcconfig = {
24
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
25
+ "CLANG_CXX_LIBRARY" => "libc++",
26
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) MD4C_USE_UTF8=1",
27
+ "HEADER_SEARCH_PATHS" => [
28
+ "\"$(PODS_TARGET_SRCROOT)/cpp/md4c\"",
29
+ "\"$(PODS_TARGET_SRCROOT)/cpp/core\"",
30
+ "\"$(PODS_TARGET_SRCROOT)/cpp/bindings\"",
31
+ "\"$(PODS_TARGET_SRCROOT)/nitrogen/generated/shared/c++\"",
32
+ "\"$(PODS_TARGET_SRCROOT)/nitrogen/generated/ios\""
33
+ ].join(" ")
34
+ }
35
+
36
+ # React Native dependency
37
+ s.dependency "React-Core"
38
+
39
+ # Add Nitro autolinking
40
+ load 'nitrogen/generated/ios/NitroMarkdown+autolinking.rb'
41
+ add_nitrogen_files(s)
42
+ end
@@ -0,0 +1,12 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+
3
+ export interface ParserOptions {
4
+ gfm?: boolean;
5
+ math?: boolean;
6
+ }
7
+
8
+ export interface MarkdownParser
9
+ extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
10
+ parse(text: string): string;
11
+ parseWithOptions(text: string, options: ParserOptions): string;
12
+ }
@@ -0,0 +1,113 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+
3
+ export interface MarkdownNode {
4
+ type: string;
5
+ content?: string;
6
+ level?: number;
7
+ href?: string;
8
+ title?: string;
9
+ alt?: string;
10
+ language?: string;
11
+ ordered?: boolean;
12
+ start?: number;
13
+ checked?: boolean;
14
+ isHeader?: boolean;
15
+ align?: string;
16
+ children?: MarkdownNode[];
17
+ }
18
+
19
+ export interface ParserOptions {
20
+ gfm?: boolean;
21
+ math?: boolean;
22
+ }
23
+
24
+ // Pure JavaScript implementation using JSI
25
+ export class JSMarkdownParser {
26
+ private parseImpl(text: string, options: ParserOptions): MarkdownNode {
27
+ // Simple regex-based parser for comparison
28
+ // This is much slower than the C++ version but demonstrates JSI usage
29
+ const root: MarkdownNode = { type: 'document', children: [] };
30
+ const lines = text.split('\n');
31
+ let i = 0;
32
+
33
+ while (i < lines.length) {
34
+ const line = lines[i].trim();
35
+ if (!line) {
36
+ i++;
37
+ continue;
38
+ }
39
+
40
+ // Headers
41
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
42
+ if (headerMatch) {
43
+ root.children!.push({
44
+ type: 'heading',
45
+ level: headerMatch[1].length,
46
+ children: [{ type: 'text', content: headerMatch[2] }]
47
+ });
48
+ i++;
49
+ continue;
50
+ }
51
+
52
+ // Bold
53
+ const boldMatch = line.match(/\*\*(.+?)\*\*/);
54
+ if (boldMatch) {
55
+ root.children!.push({
56
+ type: 'paragraph',
57
+ children: [{
58
+ type: 'bold',
59
+ children: [{ type: 'text', content: boldMatch[1] }]
60
+ }]
61
+ });
62
+ i++;
63
+ continue;
64
+ }
65
+
66
+ // Default paragraph
67
+ root.children!.push({
68
+ type: 'paragraph',
69
+ children: [{ type: 'text', content: line }]
70
+ });
71
+ i++;
72
+ }
73
+
74
+ return root;
75
+ }
76
+
77
+ parse(text: string, options: ParserOptions = { gfm: true, math: true }): MarkdownNode {
78
+ return this.parseImpl(text, options);
79
+ }
80
+ }
81
+
82
+ // JSI-enabled version using Nitro but with JS implementation
83
+ export class JSINitroMarkdownParser {
84
+ parse(text: string, options: ParserOptions = { gfm: true, math: true }): MarkdownNode {
85
+ // This would use JSI to call into JavaScriptCore
86
+ // For now, we'll simulate it
87
+ const parser = new JSMarkdownParser();
88
+ return parser.parse(text, options);
89
+ }
90
+ }
91
+
92
+ // Hybrid approach: C++ for complex parsing, JS for simple cases
93
+ export class HybridMarkdownParser {
94
+ private cppParser: any;
95
+ private jsParser: JSMarkdownParser;
96
+
97
+ constructor() {
98
+ // In real implementation, this would be the Nitro C++ parser
99
+ this.cppParser = null;
100
+ this.jsParser = new JSMarkdownParser();
101
+ }
102
+
103
+ parse(text: string, options: ParserOptions = { gfm: true, math: true }): MarkdownNode {
104
+ // Use C++ parser for complex cases, JS for simple
105
+ if (text.length > 1000 || options.gfm || options.math) {
106
+ // Would call C++ parser via Nitro
107
+ return this.jsParser.parse(text, options);
108
+ } else {
109
+ // Use JS parser for simple cases
110
+ return this.jsParser.parse(text, options);
111
+ }
112
+ }
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ import type { MarkdownParser, ParserOptions } from './Markdown.nitro';
3
+
4
+ export type { ParserOptions } from './Markdown.nitro';
5
+
6
+ export interface MarkdownNode {
7
+ type:
8
+ | 'document'
9
+ | 'heading'
10
+ | 'paragraph'
11
+ | 'text'
12
+ | 'bold'
13
+ | 'italic'
14
+ | 'strikethrough'
15
+ | 'link'
16
+ | 'image'
17
+ | 'code_inline'
18
+ | 'code_block'
19
+ | 'blockquote'
20
+ | 'horizontal_rule'
21
+ | 'line_break'
22
+ | 'soft_break'
23
+ | 'table'
24
+ | 'table_head'
25
+ | 'table_body'
26
+ | 'table_row'
27
+ | 'table_cell'
28
+ | 'list'
29
+ | 'list_item'
30
+ | 'task_list_item'
31
+ | 'math_inline'
32
+ | 'math_block'
33
+ | 'html_block'
34
+ | 'html_inline';
35
+ content?: string;
36
+ level?: number;
37
+ href?: string;
38
+ title?: string;
39
+ alt?: string;
40
+ language?: string;
41
+ ordered?: boolean;
42
+ start?: number;
43
+ checked?: boolean;
44
+ isHeader?: boolean;
45
+ align?: string;
46
+ children?: MarkdownNode[];
47
+ }
48
+
49
+ export const MarkdownParserModule =
50
+ NitroModules.createHybridObject<MarkdownParser>('MarkdownParser');
51
+
52
+ export function parseMarkdown(text: string): MarkdownNode {
53
+ const jsonStr = MarkdownParserModule.parse(text);
54
+ return JSON.parse(jsonStr) as MarkdownNode;
55
+ }
56
+
57
+ export function parseMarkdownWithOptions(
58
+ text: string,
59
+ options: ParserOptions
60
+ ): MarkdownNode {
61
+ const jsonStr = MarkdownParserModule.parseWithOptions(text, options);
62
+ return JSON.parse(jsonStr) as MarkdownNode;
63
+ }
64
+
65
+ export { MarkdownParser };