react-native-nitro-markdown 0.5.1 → 0.5.3

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 (116) hide show
  1. package/README.md +257 -682
  2. package/android/CMakeLists.txt +8 -1
  3. package/android/build.gradle +9 -2
  4. package/android/consumer-rules.pro +31 -0
  5. package/android/gradle.properties +2 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +4 -1
  7. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +61 -21
  8. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +6 -18
  9. package/cpp/bindings/HybridMarkdownParser.cpp +38 -12
  10. package/cpp/bindings/HybridMarkdownParser.hpp +4 -4
  11. package/cpp/bindings/HybridMarkdownSession.cpp +2 -0
  12. package/cpp/core/MD4CParser.cpp +128 -85
  13. package/cpp/core/MarkdownSessionCore.cpp +2 -0
  14. package/ios/HybridMarkdownSession.swift +89 -46
  15. package/lib/commonjs/headless.js +33 -7
  16. package/lib/commonjs/headless.js.map +1 -1
  17. package/lib/commonjs/index.js +48 -38
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/markdown-stream.js +1 -1
  20. package/lib/commonjs/markdown-stream.js.map +1 -1
  21. package/lib/commonjs/markdown.js +47 -10
  22. package/lib/commonjs/markdown.js.map +1 -1
  23. package/lib/commonjs/renderers/code.js +1 -1
  24. package/lib/commonjs/renderers/code.js.map +1 -1
  25. package/lib/commonjs/renderers/image.js +6 -1
  26. package/lib/commonjs/renderers/image.js.map +1 -1
  27. package/lib/commonjs/renderers/link.js +7 -2
  28. package/lib/commonjs/renderers/link.js.map +1 -1
  29. package/lib/commonjs/renderers/list.js +2 -0
  30. package/lib/commonjs/renderers/list.js.map +1 -1
  31. package/lib/commonjs/renderers/math.js +4 -2
  32. package/lib/commonjs/renderers/math.js.map +1 -1
  33. package/lib/commonjs/renderers/table/cell-content.js +1 -1
  34. package/lib/commonjs/renderers/table/cell-content.js.map +1 -1
  35. package/lib/commonjs/renderers/table/index.js +10 -2
  36. package/lib/commonjs/renderers/table/index.js.map +1 -1
  37. package/lib/commonjs/theme.js +7 -7
  38. package/lib/commonjs/theme.js.map +1 -1
  39. package/lib/commonjs/utils/code-highlight.js +24 -25
  40. package/lib/commonjs/utils/code-highlight.js.map +1 -1
  41. package/lib/module/headless.js +34 -6
  42. package/lib/module/headless.js.map +1 -1
  43. package/lib/module/index.js +1 -1
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/module/markdown-stream.js +1 -1
  46. package/lib/module/markdown-stream.js.map +1 -1
  47. package/lib/module/markdown.js +48 -11
  48. package/lib/module/markdown.js.map +1 -1
  49. package/lib/module/renderers/code.js +1 -1
  50. package/lib/module/renderers/code.js.map +1 -1
  51. package/lib/module/renderers/image.js +6 -1
  52. package/lib/module/renderers/image.js.map +1 -1
  53. package/lib/module/renderers/link.js +7 -2
  54. package/lib/module/renderers/link.js.map +1 -1
  55. package/lib/module/renderers/list.js +2 -0
  56. package/lib/module/renderers/list.js.map +1 -1
  57. package/lib/module/renderers/math.js +4 -2
  58. package/lib/module/renderers/math.js.map +1 -1
  59. package/lib/module/renderers/table/cell-content.js +1 -1
  60. package/lib/module/renderers/table/cell-content.js.map +1 -1
  61. package/lib/module/renderers/table/index.js +10 -2
  62. package/lib/module/renderers/table/index.js.map +1 -1
  63. package/lib/module/theme.js +7 -7
  64. package/lib/module/theme.js.map +1 -1
  65. package/lib/module/utils/code-highlight.js +24 -25
  66. package/lib/module/utils/code-highlight.js.map +1 -1
  67. package/lib/typescript/commonjs/headless.d.ts +9 -1
  68. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/index.d.ts +3 -2
  70. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/markdown.d.ts +7 -2
  73. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +4 -3
  80. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/utils/code-highlight.d.ts +1 -1
  84. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -1
  85. package/lib/typescript/module/headless.d.ts +9 -1
  86. package/lib/typescript/module/headless.d.ts.map +1 -1
  87. package/lib/typescript/module/index.d.ts +3 -2
  88. package/lib/typescript/module/index.d.ts.map +1 -1
  89. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  90. package/lib/typescript/module/markdown.d.ts +7 -2
  91. package/lib/typescript/module/markdown.d.ts.map +1 -1
  92. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  93. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  94. package/lib/typescript/module/renderers/link.d.ts.map +1 -1
  95. package/lib/typescript/module/renderers/list.d.ts.map +1 -1
  96. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  97. package/lib/typescript/module/renderers/table/cell-content.d.ts +4 -3
  98. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -1
  99. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -1
  100. package/lib/typescript/module/theme.d.ts.map +1 -1
  101. package/lib/typescript/module/utils/code-highlight.d.ts +1 -1
  102. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -1
  103. package/package.json +5 -3
  104. package/src/headless.ts +57 -7
  105. package/src/index.ts +16 -2
  106. package/src/markdown-stream.tsx +1 -0
  107. package/src/markdown.tsx +98 -31
  108. package/src/renderers/code.tsx +23 -16
  109. package/src/renderers/image.tsx +9 -1
  110. package/src/renderers/link.tsx +8 -2
  111. package/src/renderers/list.tsx +2 -0
  112. package/src/renderers/math.tsx +6 -2
  113. package/src/renderers/table/cell-content.tsx +15 -4
  114. package/src/renderers/table/index.tsx +15 -3
  115. package/src/theme.ts +34 -14
  116. package/src/utils/code-highlight.ts +133 -44
@@ -23,7 +23,12 @@ try {
23
23
  const mathJaxModule = require("react-native-mathjax-svg");
24
24
  MathJaxComponent = mathJaxModule.default || mathJaxModule;
25
25
  } catch {
26
- // ignored
26
+ if (__DEV__) {
27
+ // eslint-disable-next-line no-console
28
+ console.warn(
29
+ "[NitroMarkdown] react-native-mathjax-svg not found — math will render as plain text.",
30
+ );
31
+ }
27
32
  }
28
33
 
29
34
  type MathInlineProps = {
@@ -35,7 +40,6 @@ const createMathStyles = (theme: MarkdownTheme) =>
35
40
  StyleSheet.create({
36
41
  mathInlineContainer: {
37
42
  marginHorizontal: 2,
38
- // Ensure the inline view has layout alignment
39
43
  justifyContent: "center",
40
44
  },
41
45
  mathInlineFallbackContainer: {
@@ -1,5 +1,11 @@
1
- import { type FC, type ComponentType } from "react";
2
- import { View, Text, type StyleProp, type TextStyle } from "react-native";
1
+ import type { FC, ComponentType } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ type StyleProp,
6
+ type TextStyle,
7
+ type ViewStyle,
8
+ } from "react-native";
3
9
  import type { MarkdownNode } from "../../headless";
4
10
  import type { NodeRendererProps } from "../../MarkdownContext";
5
11
 
@@ -7,7 +13,8 @@ type CellContentProps = {
7
13
  node: MarkdownNode;
8
14
  Renderer: ComponentType<NodeRendererProps>;
9
15
  styles: {
10
- cellContentWrapper: object;
16
+ cellContentWrapper: StyleProp<ViewStyle>;
17
+ [key: string]: StyleProp<ViewStyle | TextStyle> | undefined;
11
18
  };
12
19
  textStyle?: StyleProp<TextStyle>;
13
20
  };
@@ -26,7 +33,11 @@ export const CellContent: FC<CellContentProps> = ({
26
33
  <View style={styles.cellContentWrapper}>
27
34
  {node.children.map((child, idx) => (
28
35
  <Renderer
29
- key={idx}
36
+ key={
37
+ child.beg != null
38
+ ? `${child.type}-${child.beg}`
39
+ : `${child.type}-${idx}`
40
+ }
30
41
  node={child}
31
42
  depth={0}
32
43
  inListItem={false}
@@ -15,15 +15,18 @@ import {
15
15
  type ViewStyle,
16
16
  type LayoutChangeEvent,
17
17
  } from "react-native";
18
- import { useMarkdownContext, type NodeRendererProps } from "../../MarkdownContext";
19
- import type { MarkdownTheme } from "../../theme";
20
18
  import { CellContent } from "./cell-content";
21
- import { extractTableData, estimateColumnWidths } from "./table-utils";
22
19
  import {
23
20
  columnWidthsReducer,
24
21
  DEFAULT_MIN_COLUMN_WIDTH,
25
22
  DEFAULT_MEASUREMENT_STABILIZE_MS,
26
23
  } from "./table-reducer";
24
+ import { extractTableData, estimateColumnWidths } from "./table-utils";
25
+ import {
26
+ useMarkdownContext,
27
+ type NodeRendererProps,
28
+ } from "../../MarkdownContext";
29
+ import type { MarkdownTheme } from "../../theme";
27
30
 
28
31
  type TableRendererProps = {
29
32
  node: import("../../headless").MarkdownNode;
@@ -147,6 +150,15 @@ export const TableRenderer: FC<TableRendererProps> = ({
147
150
  };
148
151
  }, [estimatedColumnWidths, expectedCellKeySignature, measurementStabilizeMs]);
149
152
 
153
+ useEffect(() => {
154
+ const widthsMap = measuredWidths.current;
155
+ const cellsSet = measuredCells.current;
156
+ return () => {
157
+ widthsMap.clear();
158
+ cellsSet.clear();
159
+ };
160
+ }, []);
161
+
150
162
  const onCellLayout = useCallback(
151
163
  (cellKey: string, width: number) => {
152
164
  if (width <= 0 || widthsCalculated.current || !needsMeasurement) return;
package/src/theme.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Platform, type TextStyle, type ViewStyle } from "react-native";
2
- import type { MarkdownNode } from "./headless";
3
2
 
4
3
  export type MarkdownTheme = {
5
4
  colors: {
@@ -85,13 +84,13 @@ export const defaultMarkdownTheme: MarkdownTheme = {
85
84
  tableRowEven: "transparent",
86
85
  tableRowOdd: "#f8fafc",
87
86
  codeTokenColors: {
88
- keyword: '#c792ea',
89
- string: '#c3e88d',
90
- comment: '#546e7a',
91
- number: '#f78c6c',
92
- operator: '#89ddff',
93
- punctuation: '#89ddff',
94
- type: '#ffcb6b',
87
+ keyword: "#c792ea",
88
+ string: "#c3e88d",
89
+ comment: "#546e7a",
90
+ number: "#f78c6c",
91
+ operator: "#89ddff",
92
+ punctuation: "#89ddff",
93
+ type: "#ffcb6b",
95
94
  },
96
95
  },
97
96
  spacing: {
@@ -149,13 +148,34 @@ export type PartialMarkdownTheme = {
149
148
  };
150
149
 
151
150
  type TextNodeType =
152
- | "text" | "bold" | "italic" | "strikethrough" | "link"
153
- | "code_inline" | "heading" | "paragraph" | "math_inline" | "html_inline";
151
+ | "text"
152
+ | "bold"
153
+ | "italic"
154
+ | "strikethrough"
155
+ | "link"
156
+ | "code_inline"
157
+ | "heading"
158
+ | "paragraph"
159
+ | "math_inline"
160
+ | "html_inline";
154
161
  type ViewNodeType =
155
- | "document" | "blockquote" | "code_block" | "horizontal_rule"
156
- | "image" | "list" | "list_item" | "task_list_item" | "table"
157
- | "table_head" | "table_body" | "table_row" | "table_cell"
158
- | "math_block" | "html_block" | "line_break" | "soft_break";
162
+ | "document"
163
+ | "blockquote"
164
+ | "code_block"
165
+ | "horizontal_rule"
166
+ | "image"
167
+ | "list"
168
+ | "list_item"
169
+ | "task_list_item"
170
+ | "table"
171
+ | "table_head"
172
+ | "table_body"
173
+ | "table_row"
174
+ | "table_cell"
175
+ | "math_block"
176
+ | "html_block"
177
+ | "line_break"
178
+ | "soft_break";
159
179
 
160
180
  export type NodeStyleOverrides = Partial<
161
181
  { [K in TextNodeType]: TextStyle } & { [K in ViewNodeType]: ViewStyle }
@@ -1,44 +1,121 @@
1
1
  export type TokenType =
2
- | 'keyword'
3
- | 'string'
4
- | 'comment'
5
- | 'number'
6
- | 'operator'
7
- | 'punctuation'
8
- | 'type'
9
- | 'default';
2
+ | "keyword"
3
+ | "string"
4
+ | "comment"
5
+ | "number"
6
+ | "operator"
7
+ | "punctuation"
8
+ | "type"
9
+ | "default";
10
10
 
11
11
  export type HighlightedToken = { text: string; type: TokenType };
12
- export type CodeHighlighter = (language: string, code: string) => HighlightedToken[];
12
+ export type CodeHighlighter = (
13
+ language: string,
14
+ code: string,
15
+ ) => HighlightedToken[];
13
16
 
14
17
  const JS_KEYWORDS = new Set([
15
- 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while',
16
- 'class', 'extends', 'import', 'export', 'from', 'default', 'new', 'this',
17
- 'typeof', 'instanceof', 'null', 'undefined', 'true', 'false', 'async',
18
- 'await', 'try', 'catch', 'throw', 'switch', 'case', 'break', 'continue',
19
- 'type', 'interface', 'enum', 'as', 'in', 'of', 'void',
18
+ "const",
19
+ "let",
20
+ "var",
21
+ "function",
22
+ "return",
23
+ "if",
24
+ "else",
25
+ "for",
26
+ "while",
27
+ "class",
28
+ "extends",
29
+ "import",
30
+ "export",
31
+ "from",
32
+ "default",
33
+ "new",
34
+ "this",
35
+ "typeof",
36
+ "instanceof",
37
+ "null",
38
+ "undefined",
39
+ "true",
40
+ "false",
41
+ "async",
42
+ "await",
43
+ "try",
44
+ "catch",
45
+ "throw",
46
+ "switch",
47
+ "case",
48
+ "break",
49
+ "continue",
50
+ "type",
51
+ "interface",
52
+ "enum",
53
+ "as",
54
+ "in",
55
+ "of",
56
+ "void",
20
57
  ]);
21
58
 
22
59
  const PYTHON_KEYWORDS = new Set([
23
- 'def', 'class', 'import', 'from', 'return', 'if', 'elif', 'else', 'for',
24
- 'while', 'try', 'except', 'finally', 'with', 'as', 'pass', 'break',
25
- 'continue', 'and', 'or', 'not', 'in', 'is', 'None', 'True', 'False',
26
- 'lambda', 'yield', 'async', 'await',
60
+ "def",
61
+ "class",
62
+ "import",
63
+ "from",
64
+ "return",
65
+ "if",
66
+ "elif",
67
+ "else",
68
+ "for",
69
+ "while",
70
+ "try",
71
+ "except",
72
+ "finally",
73
+ "with",
74
+ "as",
75
+ "pass",
76
+ "break",
77
+ "continue",
78
+ "and",
79
+ "or",
80
+ "not",
81
+ "in",
82
+ "is",
83
+ "None",
84
+ "True",
85
+ "False",
86
+ "lambda",
87
+ "yield",
88
+ "async",
89
+ "await",
27
90
  ]);
28
91
 
29
92
  const SHELL_KEYWORDS = new Set([
30
- 'if', 'then', 'else', 'elif', 'fi', 'for', 'do', 'done', 'while',
31
- 'case', 'esac', 'function', 'return', 'exit', 'echo', 'export',
93
+ "if",
94
+ "then",
95
+ "else",
96
+ "elif",
97
+ "fi",
98
+ "for",
99
+ "do",
100
+ "done",
101
+ "while",
102
+ "case",
103
+ "esac",
104
+ "function",
105
+ "return",
106
+ "exit",
107
+ "echo",
108
+ "export",
32
109
  ]);
33
110
 
34
111
  function getKeywords(language: string): Set<string> {
35
112
  const lang = language.toLowerCase();
36
- if (lang === 'python' || lang === 'py') return PYTHON_KEYWORDS;
37
- if (lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') return SHELL_KEYWORDS;
113
+ if (lang === "python" || lang === "py") return PYTHON_KEYWORDS;
114
+ if (lang === "bash" || lang === "sh" || lang === "shell" || lang === "zsh")
115
+ return SHELL_KEYWORDS;
38
116
  return JS_KEYWORDS; // default for js/ts/jsx/tsx/java/c/cpp etc.
39
117
  }
40
118
 
41
- // Tokenize a single line into tokens
42
119
  function tokenizeLine(line: string, language: string): HighlightedToken[] {
43
120
  const keywords = getKeywords(language);
44
121
  const tokens: HighlightedToken[] = [];
@@ -46,56 +123,68 @@ function tokenizeLine(line: string, language: string): HighlightedToken[] {
46
123
  // Full-line comment detection
47
124
  const trimmed = line.trimStart();
48
125
  const lang = language.toLowerCase();
49
- const isPythonLike = lang === 'python' || lang === 'py';
50
- const isShellLike = lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh';
126
+ const isPythonLike = lang === "python" || lang === "py";
127
+ const isShellLike =
128
+ lang === "bash" || lang === "sh" || lang === "shell" || lang === "zsh";
51
129
 
52
- if (trimmed.startsWith('//') || trimmed.startsWith('#') && (isShellLike || isPythonLike)) {
53
- return [{ text: line, type: 'comment' }];
130
+ if (
131
+ trimmed.startsWith("//") ||
132
+ (trimmed.startsWith("#") && (isShellLike || isPythonLike))
133
+ ) {
134
+ return [{ text: line, type: "comment" }];
54
135
  }
55
136
 
56
- // Simple token regex: strings, numbers, operators, identifiers, punctuation
57
- const tokenRegex = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d+(?:\.\d+)?\b)|(\/\/[^\n]*|#[^\n]*)|([a-zA-Z_$][a-zA-Z0-9_$]*)|([+\-*/%=<>!&|^~?:]+)|([()\[\]{},;.])|(\s+)|(.)/g;
137
+ // Only include # as comment for shell/python languages
138
+ const commentPattern =
139
+ isShellLike || isPythonLike ? "\/\/[^\\n]*|#[^\\n]*" : "\/\/[^\\n]*";
140
+ const tokenRegex = new RegExp(
141
+ `("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*'|\`(?:[^\`\\\\]|\\\\.)*\`)|(\\b\\d+(?:\\.\\d+)?\\b)|(${commentPattern})|([a-zA-Z_$][a-zA-Z0-9_$]*)|([+\\-*/%=<>!&|^~?:]+)|([(\\)\\[\\]{},;.])|(\\s+)|(.)`,
142
+ "g",
143
+ );
58
144
 
59
145
  let match: RegExpExecArray | null;
60
146
  while ((match = tokenRegex.exec(line)) !== null) {
61
147
  const [full, str, num, comment, ident, op, punct] = match;
62
148
  if (str) {
63
- tokens.push({ text: full, type: 'string' });
149
+ tokens.push({ text: full, type: "string" });
64
150
  } else if (num) {
65
- tokens.push({ text: full, type: 'number' });
151
+ tokens.push({ text: full, type: "number" });
66
152
  } else if (comment) {
67
- tokens.push({ text: full, type: 'comment' });
153
+ tokens.push({ text: full, type: "comment" });
68
154
  } else if (ident) {
69
155
  const type: TokenType = keywords.has(ident)
70
- ? 'keyword'
156
+ ? "keyword"
71
157
  : /^[A-Z][A-Za-z0-9]*$/.test(ident)
72
- ? 'type'
73
- : 'default';
158
+ ? "type"
159
+ : "default";
74
160
  tokens.push({ text: full, type });
75
161
  } else if (op) {
76
- tokens.push({ text: full, type: 'operator' });
162
+ tokens.push({ text: full, type: "operator" });
77
163
  } else if (punct) {
78
- tokens.push({ text: full, type: 'punctuation' });
164
+ tokens.push({ text: full, type: "punctuation" });
79
165
  } else {
80
- tokens.push({ text: full, type: 'default' });
166
+ tokens.push({ text: full, type: "default" });
81
167
  }
82
168
  }
83
169
 
84
170
  return tokens;
85
171
  }
86
172
 
87
- export function defaultHighlighter(language: string, code: string): HighlightedToken[] {
88
- if (!language || language === 'text' || language === 'plain') {
89
- return [{ text: code, type: 'default' }];
173
+ export function defaultHighlighter(
174
+ language: string,
175
+ code: string,
176
+ ): HighlightedToken[] {
177
+ if (!language || language === "text" || language === "plain") {
178
+ return [{ text: code, type: "default" }];
90
179
  }
91
180
 
92
181
  // Process line by line, re-inserting newlines as default tokens
93
- const lines = code.split('\n');
182
+ const lines = code.split("\n");
94
183
  const result: HighlightedToken[] = [];
95
184
  for (let i = 0; i < lines.length; i++) {
96
- result.push(...tokenizeLine(lines[i]!, language));
185
+ result.push(...tokenizeLine(lines[i], language));
97
186
  if (i < lines.length - 1) {
98
- result.push({ text: '\n', type: 'default' });
187
+ result.push({ text: "\n", type: "default" });
99
188
  }
100
189
  }
101
190
  return result;