react-native-nitro-markdown 0.4.3 → 0.5.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.
Files changed (175) hide show
  1. package/README.md +417 -25
  2. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +46 -8
  3. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +2 -1
  4. package/cpp/bindings/HybridMarkdownParser.cpp +216 -66
  5. package/cpp/bindings/HybridMarkdownParser.hpp +2 -0
  6. package/ios/HybridMarkdownSession.swift +51 -7
  7. package/lib/commonjs/MarkdownContext.js.map +1 -1
  8. package/lib/commonjs/headless.js +61 -5
  9. package/lib/commonjs/headless.js.map +1 -1
  10. package/lib/commonjs/index.js +9 -1
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/markdown-stream.js +107 -13
  13. package/lib/commonjs/markdown-stream.js.map +1 -1
  14. package/lib/commonjs/markdown.js +191 -26
  15. package/lib/commonjs/markdown.js.map +1 -1
  16. package/lib/commonjs/renderers/code.js +21 -2
  17. package/lib/commonjs/renderers/code.js.map +1 -1
  18. package/lib/commonjs/renderers/table/cell-content.js +32 -0
  19. package/lib/commonjs/renderers/table/cell-content.js.map +1 -0
  20. package/lib/commonjs/renderers/table/index.js +310 -0
  21. package/lib/commonjs/renderers/table/index.js.map +1 -0
  22. package/lib/commonjs/renderers/table/table-reducer.js +29 -0
  23. package/lib/commonjs/renderers/table/table-reducer.js.map +1 -0
  24. package/lib/commonjs/renderers/table/table-utils.js +68 -0
  25. package/lib/commonjs/renderers/table/table-utils.js.map +1 -0
  26. package/lib/commonjs/renderers/table/types.js +6 -0
  27. package/lib/commonjs/renderers/table/types.js.map +1 -0
  28. package/lib/commonjs/renderers/table.js +6 -306
  29. package/lib/commonjs/renderers/table.js.map +1 -1
  30. package/lib/commonjs/theme.js +10 -1
  31. package/lib/commonjs/theme.js.map +1 -1
  32. package/lib/commonjs/use-markdown-stream.js +9 -1
  33. package/lib/commonjs/use-markdown-stream.js.map +1 -1
  34. package/lib/commonjs/utils/code-highlight.js +101 -0
  35. package/lib/commonjs/utils/code-highlight.js.map +1 -0
  36. package/lib/commonjs/utils/incremental-ast.js +153 -0
  37. package/lib/commonjs/utils/incremental-ast.js.map +1 -0
  38. package/lib/module/MarkdownContext.js.map +1 -1
  39. package/lib/module/headless.js +56 -4
  40. package/lib/module/headless.js.map +1 -1
  41. package/lib/module/index.js +1 -0
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/markdown-stream.js +108 -14
  44. package/lib/module/markdown-stream.js.map +1 -1
  45. package/lib/module/markdown.js +193 -28
  46. package/lib/module/markdown.js.map +1 -1
  47. package/lib/module/renderers/code.js +21 -2
  48. package/lib/module/renderers/code.js.map +1 -1
  49. package/lib/module/renderers/table/cell-content.js +27 -0
  50. package/lib/module/renderers/table/cell-content.js.map +1 -0
  51. package/lib/module/renderers/table/index.js +305 -0
  52. package/lib/module/renderers/table/index.js.map +1 -0
  53. package/lib/module/renderers/table/table-reducer.js +24 -0
  54. package/lib/module/renderers/table/table-reducer.js.map +1 -0
  55. package/lib/module/renderers/table/table-utils.js +62 -0
  56. package/lib/module/renderers/table/table-utils.js.map +1 -0
  57. package/lib/module/renderers/table/types.js +4 -0
  58. package/lib/module/renderers/table/types.js.map +1 -0
  59. package/lib/module/renderers/table.js +1 -305
  60. package/lib/module/renderers/table.js.map +1 -1
  61. package/lib/module/theme.js +10 -1
  62. package/lib/module/theme.js.map +1 -1
  63. package/lib/module/use-markdown-stream.js +9 -1
  64. package/lib/module/use-markdown-stream.js.map +1 -1
  65. package/lib/module/utils/code-highlight.js +97 -0
  66. package/lib/module/utils/code-highlight.js.map +1 -0
  67. package/lib/module/utils/incremental-ast.js +147 -0
  68. package/lib/module/utils/incremental-ast.js.map +1 -0
  69. package/lib/typescript/commonjs/Markdown.nitro.d.ts +2 -0
  70. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/MarkdownContext.d.ts +6 -0
  72. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/headless.d.ts +18 -0
  74. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/index.d.ts +4 -0
  76. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/markdown-stream.d.ts +6 -1
  78. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/markdown.d.ts +77 -1
  80. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +15 -0
  83. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/renderers/table/index.d.ts +11 -0
  85. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts +5 -0
  87. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts +10 -0
  89. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/renderers/table/types.d.ts +24 -0
  91. package/lib/typescript/commonjs/renderers/table/types.d.ts.map +1 -0
  92. package/lib/typescript/commonjs/renderers/table.d.ts +1 -11
  93. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +14 -2
  95. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/theme.d.ts +18 -2
  97. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/use-markdown-stream.d.ts +4 -0
  99. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/utils/code-highlight.d.ts +8 -0
  101. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/utils/incremental-ast.d.ts +12 -0
  103. package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -0
  104. package/lib/typescript/module/Markdown.nitro.d.ts +2 -0
  105. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
  106. package/lib/typescript/module/MarkdownContext.d.ts +6 -0
  107. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  108. package/lib/typescript/module/headless.d.ts +18 -0
  109. package/lib/typescript/module/headless.d.ts.map +1 -1
  110. package/lib/typescript/module/index.d.ts +4 -0
  111. package/lib/typescript/module/index.d.ts.map +1 -1
  112. package/lib/typescript/module/markdown-stream.d.ts +6 -1
  113. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  114. package/lib/typescript/module/markdown.d.ts +77 -1
  115. package/lib/typescript/module/markdown.d.ts.map +1 -1
  116. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  117. package/lib/typescript/module/renderers/table/cell-content.d.ts +15 -0
  118. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -0
  119. package/lib/typescript/module/renderers/table/index.d.ts +11 -0
  120. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -0
  121. package/lib/typescript/module/renderers/table/table-reducer.d.ts +5 -0
  122. package/lib/typescript/module/renderers/table/table-reducer.d.ts.map +1 -0
  123. package/lib/typescript/module/renderers/table/table-utils.d.ts +10 -0
  124. package/lib/typescript/module/renderers/table/table-utils.d.ts.map +1 -0
  125. package/lib/typescript/module/renderers/table/types.d.ts +24 -0
  126. package/lib/typescript/module/renderers/table/types.d.ts.map +1 -0
  127. package/lib/typescript/module/renderers/table.d.ts +1 -11
  128. package/lib/typescript/module/renderers/table.d.ts.map +1 -1
  129. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +14 -2
  130. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -1
  131. package/lib/typescript/module/theme.d.ts +18 -2
  132. package/lib/typescript/module/theme.d.ts.map +1 -1
  133. package/lib/typescript/module/use-markdown-stream.d.ts +4 -0
  134. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
  135. package/lib/typescript/module/utils/code-highlight.d.ts +8 -0
  136. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -0
  137. package/lib/typescript/module/utils/incremental-ast.d.ts +12 -0
  138. package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -0
  139. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +38 -26
  140. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +13 -4
  141. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
  142. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +49 -34
  143. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +25 -24
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/Func_void_double_double.kt +80 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +34 -21
  146. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +8 -0
  147. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +31 -0
  148. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +34 -2
  149. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +46 -0
  150. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +6 -2
  151. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +57 -9
  152. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +2 -0
  153. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +2 -0
  154. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +4 -0
  155. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +6 -2
  156. package/package.json +9 -5
  157. package/react-native-nitro-markdown.podspec +1 -1
  158. package/src/Markdown.nitro.ts +2 -0
  159. package/src/MarkdownContext.ts +6 -0
  160. package/src/headless.ts +54 -4
  161. package/src/index.ts +10 -0
  162. package/src/markdown-stream.tsx +163 -15
  163. package/src/markdown.tsx +381 -26
  164. package/src/renderers/code.tsx +32 -3
  165. package/src/renderers/table/cell-content.tsx +38 -0
  166. package/src/renderers/table/index.tsx +419 -0
  167. package/src/renderers/table/table-reducer.ts +36 -0
  168. package/src/renderers/table/table-utils.ts +81 -0
  169. package/src/renderers/table/types.ts +24 -0
  170. package/src/renderers/table.tsx +1 -401
  171. package/src/specs/MarkdownSession.nitro.ts +16 -2
  172. package/src/theme.ts +29 -1
  173. package/src/use-markdown-stream.ts +10 -0
  174. package/src/utils/code-highlight.ts +102 -0
  175. package/src/utils/incremental-ast.ts +224 -0
@@ -1,6 +1,54 @@
1
- import { useState, useEffect, useRef, startTransition, type FC } from "react";
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useCallback,
6
+ startTransition,
7
+ type FC,
8
+ } from "react";
9
+ import type { MarkdownNode } from "./headless";
2
10
  import { Markdown, type MarkdownProps } from "./markdown";
3
11
  import type { MarkdownSession } from "./specs/MarkdownSession.nitro";
12
+ import { getNextStreamAst, parseMarkdownAst } from "./utils/incremental-ast";
13
+
14
+ const normalizeOffset = (value: number): number | null => {
15
+ if (!Number.isFinite(value)) return null;
16
+ if (value <= 0) return 0;
17
+ return Math.floor(value);
18
+ };
19
+
20
+ const resolveStreamText = ({
21
+ forceFullSync,
22
+ pendingFrom,
23
+ pendingTo,
24
+ previousText,
25
+ session,
26
+ }: {
27
+ forceFullSync: boolean;
28
+ pendingFrom: number | null;
29
+ pendingTo: number | null;
30
+ previousText: string;
31
+ session: MarkdownSession;
32
+ }): string => {
33
+ if (forceFullSync || pendingFrom === null || pendingTo === null) {
34
+ return session.getAllText();
35
+ }
36
+
37
+ if (pendingTo < pendingFrom) {
38
+ return session.getAllText();
39
+ }
40
+
41
+ if (pendingFrom === previousText.length) {
42
+ const appendedChunk = session.getTextRange(pendingFrom, pendingTo);
43
+ return `${previousText}${appendedChunk}`;
44
+ }
45
+
46
+ if (pendingFrom === 0) {
47
+ return session.getTextRange(0, pendingTo);
48
+ }
49
+
50
+ return session.getAllText();
51
+ };
4
52
 
5
53
  export type MarkdownStreamProps = {
6
54
  /**
@@ -23,7 +71,12 @@ export type MarkdownStreamProps = {
23
71
  * Useful when you want to prioritize user interactions over stream renders.
24
72
  */
25
73
  useTransitionUpdates?: boolean;
26
- } & Omit<MarkdownProps, "children">;
74
+ /**
75
+ * Enable incremental AST updates for append-only streams.
76
+ * Automatically falls back to full parse when updates are not safely mergeable.
77
+ */
78
+ incrementalParsing?: boolean;
79
+ } & Omit<MarkdownProps, "children" | "sourceAst">;
27
80
 
28
81
  /**
29
82
  * A component that renders streaming Markdown from a MarkdownSession.
@@ -34,20 +87,55 @@ export const MarkdownStream: FC<MarkdownStreamProps> = ({
34
87
  updateIntervalMs = 50,
35
88
  updateStrategy = "interval",
36
89
  useTransitionUpdates = false,
90
+ incrementalParsing = true,
91
+ options,
92
+ plugins,
37
93
  ...props
38
94
  }) => {
39
- const [text, setText] = useState(() => session.getAllText());
95
+ const parseText = useCallback(
96
+ (text: string): MarkdownNode => parseMarkdownAst(text, options),
97
+ [options],
98
+ );
99
+ const createEmptyAst = (): MarkdownNode => ({
100
+ type: "document",
101
+ children: [],
102
+ });
103
+ const initialText = session.getAllText();
104
+ const hasBeforeParsePlugins =
105
+ plugins?.some((plugin) => typeof plugin.beforeParse === "function") ??
106
+ false;
107
+ const [renderState, setRenderState] = useState(() => ({
108
+ text: initialText,
109
+ ast: hasBeforeParsePlugins ? createEmptyAst() : parseText(initialText),
110
+ }));
111
+ const renderStateRef = useRef(renderState);
40
112
  const pendingUpdateRef = useRef(false);
113
+ const pendingFromRef = useRef<number | null>(null);
114
+ const pendingToRef = useRef<number | null>(null);
115
+ const forceFullSyncRef = useRef(false);
41
116
  const updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
42
117
  const rafRef = useRef<number | null>(null);
43
- const lastEmittedRef = useRef(text);
118
+ const allowIncremental = incrementalParsing && !hasBeforeParsePlugins;
119
+
120
+ useEffect(() => {
121
+ renderStateRef.current = renderState;
122
+ }, [renderState]);
44
123
 
45
124
  useEffect(() => {
46
- // Ensure initial state is synced
47
125
  const initialText = session.getAllText();
48
- setText(initialText);
49
- lastEmittedRef.current = initialText;
126
+ const initialState = {
127
+ text: initialText,
128
+ ast: hasBeforeParsePlugins ? createEmptyAst() : parseText(initialText),
129
+ };
130
+ pendingUpdateRef.current = false;
131
+ pendingFromRef.current = null;
132
+ pendingToRef.current = null;
133
+ forceFullSyncRef.current = false;
134
+ renderStateRef.current = initialState;
135
+ setRenderState(initialState);
136
+ }, [hasBeforeParsePlugins, parseText, session]);
50
137
 
138
+ useEffect(() => {
51
139
  const flushUpdate = () => {
52
140
  updateTimerRef.current = null;
53
141
  if (rafRef.current !== null) {
@@ -57,16 +145,44 @@ export const MarkdownStream: FC<MarkdownStreamProps> = ({
57
145
  if (!pendingUpdateRef.current) return;
58
146
  pendingUpdateRef.current = false;
59
147
 
60
- const latest = session.getAllText();
61
- if (latest === lastEmittedRef.current) return;
62
- lastEmittedRef.current = latest;
148
+ const previousState = renderStateRef.current;
149
+ const pendingFrom = pendingFromRef.current;
150
+ const pendingTo = pendingToRef.current;
151
+ const forceFullSync = forceFullSyncRef.current;
152
+ pendingFromRef.current = null;
153
+ pendingToRef.current = null;
154
+ forceFullSyncRef.current = false;
155
+
156
+ const latest = resolveStreamText({
157
+ forceFullSync,
158
+ pendingFrom,
159
+ pendingTo,
160
+ previousText: previousState.text,
161
+ session,
162
+ });
163
+ if (latest === previousState.text) return;
164
+
165
+ const nextAst = hasBeforeParsePlugins
166
+ ? previousState.ast
167
+ : getNextStreamAst({
168
+ allowIncremental,
169
+ nextText: latest,
170
+ options,
171
+ previousAst: previousState.ast,
172
+ previousText: previousState.text,
173
+ });
174
+ const nextState = {
175
+ text: latest,
176
+ ast: nextAst,
177
+ };
178
+ renderStateRef.current = nextState;
63
179
 
64
180
  if (useTransitionUpdates) {
65
181
  startTransition(() => {
66
- setText(latest);
182
+ setRenderState(nextState);
67
183
  });
68
184
  } else {
69
- setText(latest);
185
+ setRenderState(nextState);
70
186
  }
71
187
  };
72
188
 
@@ -83,7 +199,22 @@ export const MarkdownStream: FC<MarkdownStreamProps> = ({
83
199
  }
84
200
  };
85
201
 
86
- const unsubscribe = session.addListener(() => {
202
+ const unsubscribe = session.addListener((from, to) => {
203
+ const nextFrom = normalizeOffset(from);
204
+ const nextTo = normalizeOffset(to);
205
+
206
+ if (nextFrom === null || nextTo === null || nextTo < nextFrom) {
207
+ forceFullSyncRef.current = true;
208
+ } else {
209
+ const currentFrom = pendingFromRef.current;
210
+ const currentTo = pendingToRef.current;
211
+
212
+ pendingFromRef.current =
213
+ currentFrom === null ? nextFrom : Math.min(currentFrom, nextFrom);
214
+ pendingToRef.current =
215
+ currentTo === null ? nextTo : Math.max(currentTo, nextTo);
216
+ }
217
+
87
218
  pendingUpdateRef.current = true;
88
219
  scheduleFlush();
89
220
  });
@@ -99,7 +230,24 @@ export const MarkdownStream: FC<MarkdownStreamProps> = ({
99
230
  rafRef.current = null;
100
231
  }
101
232
  };
102
- }, [session, updateIntervalMs, updateStrategy, useTransitionUpdates]);
233
+ }, [
234
+ allowIncremental,
235
+ hasBeforeParsePlugins,
236
+ options,
237
+ session,
238
+ updateIntervalMs,
239
+ updateStrategy,
240
+ useTransitionUpdates,
241
+ ]);
103
242
 
104
- return <Markdown {...props}>{text}</Markdown>;
243
+ return (
244
+ <Markdown
245
+ {...props}
246
+ options={options}
247
+ plugins={plugins}
248
+ sourceAst={hasBeforeParsePlugins ? undefined : renderState.ast}
249
+ >
250
+ {renderState.text}
251
+ </Markdown>
252
+ );
105
253
  };