react-native-nitro-markdown 0.7.1 → 0.8.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 (94) hide show
  1. package/README.md +179 -27
  2. package/ios/HybridMarkdownSession.swift +11 -4
  3. package/lib/commonjs/MarkdownContext.js +1 -3
  4. package/lib/commonjs/MarkdownContext.js.map +1 -1
  5. package/lib/commonjs/index.js +6 -0
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/markdown-stream.js +113 -29
  8. package/lib/commonjs/markdown-stream.js.map +1 -1
  9. package/lib/commonjs/markdown.js +96 -38
  10. package/lib/commonjs/markdown.js.map +1 -1
  11. package/lib/commonjs/renderers/image.js +11 -2
  12. package/lib/commonjs/renderers/image.js.map +1 -1
  13. package/lib/commonjs/renderers/math.js +22 -2
  14. package/lib/commonjs/renderers/math.js.map +1 -1
  15. package/lib/commonjs/renderers/table/index.js +4 -4
  16. package/lib/commonjs/renderers/table/index.js.map +1 -1
  17. package/lib/commonjs/renderers/table/table-utils.js +1 -1
  18. package/lib/commonjs/renderers/table/table-utils.js.map +1 -1
  19. package/lib/commonjs/use-markdown-stream.js +2 -6
  20. package/lib/commonjs/use-markdown-stream.js.map +1 -1
  21. package/lib/commonjs/utils/code-highlight.js +1 -1
  22. package/lib/commonjs/utils/code-highlight.js.map +1 -1
  23. package/lib/commonjs/utils/incremental-ast.js +67 -14
  24. package/lib/commonjs/utils/incremental-ast.js.map +1 -1
  25. package/lib/commonjs/utils/link-security.js +2 -2
  26. package/lib/commonjs/utils/link-security.js.map +1 -1
  27. package/lib/commonjs/utils/stream-timeline.js +13 -7
  28. package/lib/commonjs/utils/stream-timeline.js.map +1 -1
  29. package/lib/module/MarkdownContext.js +1 -3
  30. package/lib/module/MarkdownContext.js.map +1 -1
  31. package/lib/module/index.js +1 -1
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/markdown-stream.js +113 -30
  34. package/lib/module/markdown-stream.js.map +1 -1
  35. package/lib/module/markdown.js +96 -38
  36. package/lib/module/markdown.js.map +1 -1
  37. package/lib/module/renderers/image.js +12 -3
  38. package/lib/module/renderers/image.js.map +1 -1
  39. package/lib/module/renderers/math.js +22 -2
  40. package/lib/module/renderers/math.js.map +1 -1
  41. package/lib/module/renderers/table/index.js +4 -4
  42. package/lib/module/renderers/table/index.js.map +1 -1
  43. package/lib/module/renderers/table/table-utils.js +1 -1
  44. package/lib/module/renderers/table/table-utils.js.map +1 -1
  45. package/lib/module/use-markdown-stream.js +2 -6
  46. package/lib/module/use-markdown-stream.js.map +1 -1
  47. package/lib/module/utils/code-highlight.js +1 -1
  48. package/lib/module/utils/code-highlight.js.map +1 -1
  49. package/lib/module/utils/incremental-ast.js +65 -13
  50. package/lib/module/utils/incremental-ast.js.map +1 -1
  51. package/lib/module/utils/link-security.js +2 -2
  52. package/lib/module/utils/link-security.js.map +1 -1
  53. package/lib/module/utils/stream-timeline.js +13 -7
  54. package/lib/module/utils/stream-timeline.js.map +1 -1
  55. package/lib/typescript/commonjs/MarkdownContext.d.ts +1 -0
  56. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/index.d.ts +3 -3
  58. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/markdown-stream.d.ts +28 -7
  60. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/utils/incremental-ast.d.ts +1 -0
  66. package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/utils/stream-timeline.d.ts.map +1 -1
  68. package/lib/typescript/module/MarkdownContext.d.ts +1 -0
  69. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  70. package/lib/typescript/module/index.d.ts +3 -3
  71. package/lib/typescript/module/index.d.ts.map +1 -1
  72. package/lib/typescript/module/markdown-stream.d.ts +28 -7
  73. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  74. package/lib/typescript/module/markdown.d.ts.map +1 -1
  75. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  76. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  77. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
  78. package/lib/typescript/module/utils/incremental-ast.d.ts +1 -0
  79. package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -1
  80. package/lib/typescript/module/utils/stream-timeline.d.ts.map +1 -1
  81. package/package.json +1 -1
  82. package/src/MarkdownContext.ts +2 -2
  83. package/src/index.ts +10 -2
  84. package/src/markdown-stream.tsx +153 -36
  85. package/src/markdown.tsx +82 -42
  86. package/src/renderers/image.tsx +13 -2
  87. package/src/renderers/math.tsx +20 -2
  88. package/src/renderers/table/index.tsx +4 -4
  89. package/src/renderers/table/table-utils.ts +1 -1
  90. package/src/use-markdown-stream.ts +2 -6
  91. package/src/utils/code-highlight.ts +1 -1
  92. package/src/utils/incremental-ast.ts +104 -11
  93. package/src/utils/link-security.ts +3 -3
  94. package/src/utils/stream-timeline.ts +10 -7
@@ -21,13 +21,9 @@ export function useMarkdownSession(initialText?: string) {
21
21
  const session = sessionRef.current!;
22
22
  return () => {
23
23
  try {
24
- session.clear();
24
+ session.dispose();
25
25
  } finally {
26
- try {
27
- session.dispose();
28
- } finally {
29
- sessionRef.current = null;
30
- }
26
+ sessionRef.current = null;
31
27
  }
32
28
  };
33
29
  }, []);
@@ -182,7 +182,7 @@ export function defaultHighlighter(
182
182
  const lines = code.split("\n");
183
183
  const result: HighlightedToken[] = [];
184
184
  for (let i = 0; i < lines.length; i++) {
185
- result.push(...tokenizeLine(lines[i], language));
185
+ result.push(...tokenizeLine(lines[i] ?? "", language));
186
186
  if (i < lines.length - 1) {
187
187
  result.push({ text: "\n", type: "default" });
188
188
  }
@@ -24,7 +24,7 @@ const isInsideFencedCodeBlock = (text: string): boolean => {
24
24
  const fenceMatch = line.match(FENCE_LINE_PATTERN);
25
25
  if (!fenceMatch) continue;
26
26
 
27
- const marker = fenceMatch[1];
27
+ const marker = fenceMatch[1] ?? "";
28
28
  const markerChar = marker[0] as "`" | "~";
29
29
  const markerLength = marker.length;
30
30
 
@@ -94,7 +94,11 @@ const findTrailingLeafPath = (
94
94
  }
95
95
 
96
96
  const lastIndex = children.length - 1;
97
- return findTrailingLeafPath(children[lastIndex], [...path, lastIndex]);
97
+ const lastChild = children[lastIndex];
98
+ if (lastChild === undefined) {
99
+ return path;
100
+ }
101
+ return findTrailingLeafPath(lastChild, [...path, lastIndex]);
98
102
  };
99
103
 
100
104
  const getNodeAtPath = (
@@ -126,11 +130,14 @@ const appendPlainTextToAst = (
126
130
  const delta = appendedChunk.length;
127
131
  const update = (node: MarkdownNode, depth: number): MarkdownNode => {
128
132
  if (depth === path.length) {
129
- return {
133
+ const updatedNode: MarkdownNode = {
130
134
  ...node,
131
135
  content: (node.content ?? "") + appendedChunk,
132
- end: typeof node.end === "number" ? node.end + delta : node.end,
133
136
  };
137
+ if (typeof node.end === "number") {
138
+ updatedNode.end = node.end + delta;
139
+ }
140
+ return updatedNode;
134
141
  }
135
142
 
136
143
  const childIndex = path[depth];
@@ -138,11 +145,16 @@ const appendPlainTextToAst = (
138
145
  index === childIndex ? update(child, depth + 1) : child,
139
146
  );
140
147
 
141
- return {
148
+ const updatedNode: MarkdownNode = {
142
149
  ...node,
143
- end: typeof node.end === "number" ? node.end + delta : node.end,
144
- children,
145
150
  };
151
+ if (typeof node.end === "number") {
152
+ updatedNode.end = node.end + delta;
153
+ }
154
+ if (children) {
155
+ updatedNode.children = children;
156
+ }
157
+ return updatedNode;
146
158
  };
147
159
 
148
160
  return update(ast, 0);
@@ -152,6 +164,87 @@ const endsAtBlockBoundary = (text: string): boolean => {
152
164
  return text.endsWith("\n") || text.endsWith("\r");
153
165
  };
154
166
 
167
+ const nodesHaveMatchingMetadata = (
168
+ previousNode: MarkdownNode,
169
+ nextNode: MarkdownNode,
170
+ ): boolean => {
171
+ return (
172
+ previousNode.type === nextNode.type &&
173
+ previousNode.content === nextNode.content &&
174
+ previousNode.level === nextNode.level &&
175
+ previousNode.href === nextNode.href &&
176
+ previousNode.title === nextNode.title &&
177
+ previousNode.alt === nextNode.alt &&
178
+ previousNode.language === nextNode.language &&
179
+ previousNode.ordered === nextNode.ordered &&
180
+ previousNode.start === nextNode.start &&
181
+ previousNode.checked === nextNode.checked &&
182
+ previousNode.isHeader === nextNode.isHeader &&
183
+ previousNode.align === nextNode.align &&
184
+ previousNode.beg === nextNode.beg &&
185
+ previousNode.end === nextNode.end
186
+ );
187
+ };
188
+
189
+ export const reuseStableAstNodes = (
190
+ previousNode: MarkdownNode,
191
+ nextNode: MarkdownNode,
192
+ ): MarkdownNode => {
193
+ if (previousNode.type !== nextNode.type) {
194
+ return nextNode;
195
+ }
196
+
197
+ const hasMatchingMetadata = nodesHaveMatchingMetadata(previousNode, nextNode);
198
+ const previousChildren = previousNode.children;
199
+ const nextChildren = nextNode.children;
200
+
201
+ if (!previousChildren || !nextChildren) {
202
+ return hasMatchingMetadata && previousChildren === nextChildren
203
+ ? previousNode
204
+ : nextNode;
205
+ }
206
+
207
+ if (previousChildren.length !== nextChildren.length) {
208
+ return {
209
+ ...nextNode,
210
+ children: nextChildren.map((nextChild, index) => {
211
+ const previousChild = previousChildren[index];
212
+ return previousChild
213
+ ? reuseStableAstNodes(previousChild, nextChild)
214
+ : nextChild;
215
+ }),
216
+ };
217
+ }
218
+
219
+ let hasChildChange = false;
220
+ const children = nextChildren.map((nextChild, index) => {
221
+ const previousChild = previousChildren[index];
222
+ const child =
223
+ previousChild === undefined
224
+ ? nextChild
225
+ : reuseStableAstNodes(previousChild, nextChild);
226
+ hasChildChange ||= child !== previousChild;
227
+ return child;
228
+ });
229
+
230
+ if (hasMatchingMetadata && !hasChildChange) {
231
+ return previousNode;
232
+ }
233
+
234
+ return {
235
+ ...nextNode,
236
+ children,
237
+ };
238
+ };
239
+
240
+ const parseAstWithStableNodes = (
241
+ previousAst: MarkdownNode,
242
+ text: string,
243
+ options?: ParserOptions,
244
+ ): MarkdownNode => {
245
+ return reuseStableAstNodes(previousAst, parseAst(text, options));
246
+ };
247
+
155
248
  export type IncrementalAstInput = {
156
249
  allowIncremental?: boolean;
157
250
  nextText: string;
@@ -168,7 +261,7 @@ export const getNextStreamAst = ({
168
261
  previousText,
169
262
  }: IncrementalAstInput): MarkdownNode => {
170
263
  if (!allowIncremental || !nextText.startsWith(previousText)) {
171
- return parseAst(nextText, options);
264
+ return parseAstWithStableNodes(previousAst, nextText, options);
172
265
  }
173
266
 
174
267
  const appendedChunk = nextText.slice(previousText.length);
@@ -194,7 +287,7 @@ export const getNextStreamAst = ({
194
287
 
195
288
  if (!PLAIN_TEXT_APPEND_PATTERN.test(appendedChunk)) {
196
289
  if (endsAtBlockBoundary(previousText)) {
197
- return parseAst(nextText, options);
290
+ return parseAstWithStableNodes(previousAst, nextText, options);
198
291
  }
199
292
 
200
293
  const textAppendedAst = appendPlainTextToAst(
@@ -208,12 +301,12 @@ export const getNextStreamAst = ({
208
301
  }
209
302
 
210
303
  if (insideFencedCodeBlock) {
211
- return parseAst(nextText, options);
304
+ return parseAstWithStableNodes(previousAst, nextText, options);
212
305
  }
213
306
 
214
307
  // Correctness-first fallback: full reparse for all non-trivial appends.
215
308
  // Incremental append is only used for plain text chunks at the true trailing leaf.
216
- return parseAst(nextText, options);
309
+ return parseAstWithStableNodes(previousAst, nextText, options);
217
310
  };
218
311
 
219
312
  export const parseMarkdownAst = (
@@ -26,14 +26,14 @@ const parseAbsoluteHref = (
26
26
  const protocolMatch = href.match(/^([a-z][a-z0-9+.-]*):/i);
27
27
  if (!protocolMatch) return null;
28
28
 
29
- const protocol = normalizeProtocol(protocolMatch[1]);
29
+ const protocol = normalizeProtocol(protocolMatch[1] ?? "");
30
30
  const rest = href.slice(protocolMatch[0].length);
31
31
  const authorityMatch = rest.match(/^\/\/([^/?#]*)/);
32
32
  const rawHost = authorityMatch?.[1] ?? "";
33
- const hostname = rawHost
33
+ const hostname = (rawHost
34
34
  .replace(/^[^@]*@/, "")
35
35
  .replace(/^\[|\]$/g, "")
36
- .split(":")[0]
36
+ .split(":")[0] ?? "")
37
37
  .toLowerCase();
38
38
 
39
39
  return { protocol, hostname };
@@ -19,15 +19,17 @@ export const createTimestampTimeline = (
19
19
  .map(Number)
20
20
  .filter((index) => Number.isFinite(index))
21
21
  .sort((a, b) => a - b)
22
- .map((index) => ({
23
- index,
24
- time: timestamps[index],
25
- }))
22
+ .flatMap((index) => {
23
+ const time = timestamps[index];
24
+ return time === undefined ? [] : [{ index, time }];
25
+ })
26
26
  .filter((entry) => Number.isFinite(entry.time));
27
27
 
28
28
  let monotonic = true;
29
29
  for (let i = 1; i < entries.length; i += 1) {
30
- if (entries[i].time < entries[i - 1].time) {
30
+ const current = entries[i];
31
+ const previous = entries[i - 1];
32
+ if (current && previous && current.time < previous.time) {
31
33
  monotonic = false;
32
34
  break;
33
35
  }
@@ -59,7 +61,8 @@ export const resolveHighlightPosition = (
59
61
 
60
62
  while (left <= right) {
61
63
  const mid = (left + right) >> 1;
62
- if (entries[mid].time <= currentTimeMs) {
64
+ const entry = entries[mid];
65
+ if (entry !== undefined && entry.time <= currentTimeMs) {
63
66
  resolvedIndex = mid;
64
67
  left = mid + 1;
65
68
  } else {
@@ -68,5 +71,5 @@ export const resolveHighlightPosition = (
68
71
  }
69
72
 
70
73
  if (resolvedIndex === -1) return 0;
71
- return entries[resolvedIndex].index + 1;
74
+ return (entries[resolvedIndex]?.index ?? 0) + 1;
72
75
  };