react-native-nitro-markdown 0.5.4 → 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/src/markdown.tsx CHANGED
@@ -18,7 +18,6 @@ import {
18
18
  type ListRenderItemInfo,
19
19
  type FlatListProps,
20
20
  type StyleProp,
21
- type TextStyle,
22
21
  type ViewStyle,
23
22
  } from "react-native";
24
23
  import {
@@ -101,7 +100,12 @@ function safeOnError<P extends string>(
101
100
  }
102
101
 
103
102
  const baseStylesCache = new WeakMap<MarkdownTheme, BaseStyles>();
104
- const parseAstCache = new Map<string, MarkdownNode>();
103
+ type ParseAstCacheEntry = {
104
+ text: string;
105
+ ast: MarkdownNode;
106
+ };
107
+
108
+ const parseAstCache = new Map<string, ParseAstCacheEntry>();
105
109
  const MAX_PARSE_CACHE_ENTRIES = 32;
106
110
  const MAX_CACHEABLE_TEXT_LENGTH = 24_000;
107
111
  const EMPTY_RENDERERS: CustomRenderers = {};
@@ -216,15 +220,18 @@ const getCachedParsedAst = (
216
220
  }
217
221
 
218
222
  const cacheKey = `${getParserOptionsKey(options)}|${text.length}|${hashString(text)}`;
219
- const cachedNode = parseAstCache.get(cacheKey);
220
- if (cachedNode) {
223
+ const cachedEntry = parseAstCache.get(cacheKey);
224
+ if (cachedEntry?.text === text) {
221
225
  parseAstCache.delete(cacheKey);
222
- parseAstCache.set(cacheKey, cachedNode);
223
- return cloneMarkdownNode(cachedNode);
226
+ parseAstCache.set(cacheKey, cachedEntry);
227
+ return cloneMarkdownNode(cachedEntry.ast);
224
228
  }
225
229
 
226
230
  const parsedNode = parseWithNativeParser(text, options);
227
- parseAstCache.set(cacheKey, parsedNode);
231
+ parseAstCache.set(cacheKey, {
232
+ text,
233
+ ast: parsedNode,
234
+ });
228
235
  if (parseAstCache.size > MAX_PARSE_CACHE_ENTRIES) {
229
236
  const oldestCacheKey = parseAstCache.keys().next().value;
230
237
  if (typeof oldestCacheKey === "string") {
@@ -235,20 +242,27 @@ const getCachedParsedAst = (
235
242
  return cloneMarkdownNode(parsedNode);
236
243
  };
237
244
 
245
+ const sortPluginsByPriority = (
246
+ plugins?: MarkdownPlugin[],
247
+ ): MarkdownPlugin[] | undefined => {
248
+ if (!plugins || plugins.length === 0) {
249
+ return undefined;
250
+ }
251
+
252
+ return [...plugins].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
253
+ };
254
+
238
255
  const applyBeforeParsePlugins = (
239
256
  markdown: string,
240
- plugins?: MarkdownPlugin[],
257
+ sortedPlugins?: MarkdownPlugin[],
241
258
  onError?: (error: Error, phase: "before-plugin", pluginName?: string) => void,
242
259
  ): string => {
243
- if (!plugins || plugins.length === 0) {
260
+ if (!sortedPlugins || sortedPlugins.length === 0) {
244
261
  return markdown;
245
262
  }
246
263
 
247
- const sorted = [...plugins].sort(
248
- (a, b) => (b.priority ?? 0) - (a.priority ?? 0),
249
- );
250
264
  let nextMarkdown = markdown;
251
- for (const plugin of sorted) {
265
+ for (const plugin of sortedPlugins) {
252
266
  if (!plugin.beforeParse) continue;
253
267
 
254
268
  try {
@@ -271,18 +285,15 @@ const applyBeforeParsePlugins = (
271
285
 
272
286
  const applyAfterParsePlugins = (
273
287
  ast: MarkdownNode,
274
- plugins?: MarkdownPlugin[],
288
+ sortedPlugins?: MarkdownPlugin[],
275
289
  onError?: (error: Error, phase: "after-plugin", pluginName?: string) => void,
276
290
  ): MarkdownNode => {
277
- if (!plugins || plugins.length === 0) {
291
+ if (!sortedPlugins || sortedPlugins.length === 0) {
278
292
  return ast;
279
293
  }
280
294
 
281
- const sorted = [...plugins].sort(
282
- (a, b) => (b.priority ?? 0) - (a.priority ?? 0),
283
- );
284
295
  let nextAst = ast;
285
- for (const plugin of sorted) {
296
+ for (const plugin of sortedPlugins) {
286
297
  if (!plugin.afterParse) continue;
287
298
 
288
299
  try {
@@ -321,6 +332,12 @@ export type MarkdownProps = {
321
332
  * When provided, native parse is skipped and this tree is rendered instead.
322
333
  */
323
334
  sourceAst?: MarkdownNode;
335
+ /**
336
+ * Enables internal parse AST cache keyed by parser options and markdown.
337
+ * Disable to force native parse on each parse cycle.
338
+ * @default true
339
+ */
340
+ parseCache?: boolean;
324
341
  /**
325
342
  * Optional transform applied after parsing and before rendering.
326
343
  * The transformed AST is also returned in `onParseComplete`.
@@ -425,6 +442,7 @@ export const Markdown: FC<MarkdownProps> = ({
425
442
  options,
426
443
  plugins,
427
444
  sourceAst,
445
+ parseCache = true,
428
446
  astTransform,
429
447
  renderers = EMPTY_RENDERERS,
430
448
  theme: userTheme,
@@ -451,11 +469,10 @@ export const Markdown: FC<MarkdownProps> = ({
451
469
 
452
470
  const parseResult = useMemo(() => {
453
471
  try {
454
- const markdownToParse = applyBeforeParsePlugins(
455
- children,
456
- plugins,
457
- onErrorRef.current,
458
- );
472
+ const sortedPlugins = sortPluginsByPriority(plugins);
473
+ const markdownToParse = sourceAst
474
+ ? children
475
+ : applyBeforeParsePlugins(children, sortedPlugins, onErrorRef.current);
459
476
  const parserOptions = normalizeParserOptions({
460
477
  gfm: parserOptionGfm,
461
478
  math: parserOptionMath,
@@ -463,10 +480,12 @@ export const Markdown: FC<MarkdownProps> = ({
463
480
  });
464
481
  let parsedAst = sourceAst
465
482
  ? cloneMarkdownNode(sourceAst)
466
- : getCachedParsedAst(markdownToParse, parserOptions);
483
+ : parseCache
484
+ ? getCachedParsedAst(markdownToParse, parserOptions)
485
+ : parseWithNativeParser(markdownToParse, parserOptions);
467
486
  parsedAst = applyAfterParsePlugins(
468
487
  parsedAst,
469
- plugins,
488
+ sortedPlugins,
470
489
  onErrorRef.current,
471
490
  );
472
491
 
@@ -501,6 +520,7 @@ export const Markdown: FC<MarkdownProps> = ({
501
520
  parserOptionMath,
502
521
  parserOptionHtml,
503
522
  sourceAst,
523
+ parseCache,
504
524
  astTransform,
505
525
  plugins,
506
526
  ]);
@@ -767,28 +787,24 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
767
787
  }
768
788
  }
769
789
 
770
- const nodeStyleOverride = nodeStyles?.[node.type] as
771
- | (ViewStyle & TextStyle)
772
- | undefined;
773
-
774
790
  switch (node.type) {
775
791
  case "document":
776
792
  return (
777
- <View style={[baseStyles.document, nodeStyleOverride]}>
793
+ <View style={[baseStyles.document, nodeStyles?.document]}>
778
794
  {renderChildren(node.children, false, false)}
779
795
  </View>
780
796
  );
781
797
 
782
798
  case "heading":
783
799
  return (
784
- <Heading level={node.level ?? 1} style={nodeStyleOverride}>
800
+ <Heading level={node.level ?? 1} style={nodeStyles?.heading}>
785
801
  {renderChildren(node.children, inListItem, true)}
786
802
  </Heading>
787
803
  );
788
804
 
789
805
  case "paragraph":
790
806
  return (
791
- <Paragraph inListItem={inListItem} style={nodeStyleOverride}>
807
+ <Paragraph inListItem={inListItem} style={nodeStyles?.paragraph}>
792
808
  {renderChildren(node.children, inListItem, false)}
793
809
  </Paragraph>
794
810
  );
@@ -798,33 +814,33 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
798
814
  return <Text>{node.content}</Text>;
799
815
  }
800
816
  return (
801
- <Text style={[baseStyles.text, nodeStyleOverride]}>{node.content}</Text>
817
+ <Text style={[baseStyles.text, nodeStyles?.text]}>{node.content}</Text>
802
818
  );
803
819
 
804
820
  case "bold":
805
821
  return (
806
- <Text style={[baseStyles.bold, nodeStyleOverride]}>
822
+ <Text style={[baseStyles.bold, nodeStyles?.bold]}>
807
823
  {renderChildren(node.children, inListItem, true)}
808
824
  </Text>
809
825
  );
810
826
 
811
827
  case "italic":
812
828
  return (
813
- <Text style={[baseStyles.italic, nodeStyleOverride]}>
829
+ <Text style={[baseStyles.italic, nodeStyles?.italic]}>
814
830
  {renderChildren(node.children, inListItem, true)}
815
831
  </Text>
816
832
  );
817
833
 
818
834
  case "strikethrough":
819
835
  return (
820
- <Text style={[baseStyles.strikethrough, nodeStyleOverride]}>
836
+ <Text style={[baseStyles.strikethrough, nodeStyles?.strikethrough]}>
821
837
  {renderChildren(node.children, inListItem, true)}
822
838
  </Text>
823
839
  );
824
840
 
825
841
  case "link":
826
842
  return (
827
- <Link href={node.href ?? ""} style={nodeStyleOverride}>
843
+ <Link href={node.href ?? ""} style={nodeStyles?.link}>
828
844
  {renderChildren(node.children, inListItem, true)}
829
845
  </Link>
830
846
  );
@@ -836,31 +852,33 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
836
852
  title={node.title}
837
853
  alt={node.alt}
838
854
  Renderer={NodeRenderer}
839
- style={nodeStyleOverride}
855
+ style={nodeStyles?.image}
840
856
  />
841
857
  );
842
858
 
843
859
  case "code_inline":
844
- return <InlineCode style={nodeStyleOverride}>{node.content}</InlineCode>;
860
+ return (
861
+ <InlineCode style={nodeStyles?.code_inline}>{node.content}</InlineCode>
862
+ );
845
863
 
846
864
  case "code_block":
847
865
  return (
848
866
  <CodeBlock
849
867
  language={node.language}
850
868
  content={getTextContent(node)}
851
- style={nodeStyleOverride}
869
+ style={nodeStyles?.code_block}
852
870
  />
853
871
  );
854
872
 
855
873
  case "blockquote":
856
874
  return (
857
- <Blockquote style={nodeStyleOverride}>
875
+ <Blockquote style={nodeStyles?.blockquote}>
858
876
  {renderChildren(node.children, inListItem, false)}
859
877
  </Blockquote>
860
878
  );
861
879
 
862
880
  case "horizontal_rule":
863
- return <HorizontalRule style={nodeStyleOverride} />;
881
+ return <HorizontalRule style={nodeStyles?.horizontal_rule} />;
864
882
 
865
883
  case "line_break":
866
884
  return <Text>{"\n"}</Text>;
@@ -872,12 +890,17 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
872
890
  let mathContent = getTextContent(node);
873
891
  if (!mathContent) return null;
874
892
  mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
875
- return <MathInline content={mathContent} style={nodeStyleOverride} />;
893
+ return (
894
+ <MathInline content={mathContent} style={nodeStyles?.math_inline} />
895
+ );
876
896
  }
877
897
 
878
898
  case "math_block":
879
899
  return (
880
- <MathBlock content={getTextContent(node)} style={nodeStyleOverride} />
900
+ <MathBlock
901
+ content={getTextContent(node)}
902
+ style={nodeStyles?.math_block}
903
+ />
881
904
  );
882
905
 
883
906
  case "list":
@@ -886,7 +909,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
886
909
  ordered={node.ordered ?? false}
887
910
  start={node.start}
888
911
  depth={depth}
889
- style={nodeStyleOverride}
912
+ style={nodeStyles?.list}
890
913
  >
891
914
  {node.children?.map((child, index) => {
892
915
  if (child.type === "task_list_item") {
@@ -924,7 +947,10 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
924
947
 
925
948
  case "task_list_item":
926
949
  return (
927
- <TaskListItem checked={node.checked ?? false} style={nodeStyleOverride}>
950
+ <TaskListItem
951
+ checked={node.checked ?? false}
952
+ style={nodeStyles?.task_list_item}
953
+ >
928
954
  {renderChildren(node.children, true, false)}
929
955
  </TaskListItem>
930
956
  );
@@ -934,7 +960,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
934
960
  <TableRenderer
935
961
  node={node}
936
962
  Renderer={NodeRenderer}
937
- style={nodeStyleOverride}
963
+ style={nodeStyles?.table}
938
964
  />
939
965
  );
940
966
 
package/src/theme.ts CHANGED
@@ -155,8 +155,6 @@ type TextNodeType =
155
155
  | "link"
156
156
  | "code_inline"
157
157
  | "heading"
158
- | "paragraph"
159
- | "math_inline"
160
158
  | "html_inline";
161
159
  type ViewNodeType =
162
160
  | "document"
@@ -172,6 +170,8 @@ type ViewNodeType =
172
170
  | "table_body"
173
171
  | "table_row"
174
172
  | "table_cell"
173
+ | "paragraph"
174
+ | "math_inline"
175
175
  | "math_block"
176
176
  | "html_block"
177
177
  | "line_break"