react-native-marked 1.1.1 → 1.1.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.
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { marked } from 'marked';
2
3
  import Renderer from './Renderer';
3
4
  import type { MarkedStyles } from '../theme/types';
@@ -21,35 +22,17 @@ class Parser {
21
22
  }
22
23
 
23
24
  parse(tokens: marked.Token[]) {
24
- const elements: React.ReactNode[] = tokens.map((token) => {
25
+ const elements: ReactNode[] = tokens.map((token) => {
25
26
  switch (token.type) {
26
27
  case 'space': {
27
28
  return null;
28
29
  }
29
30
  case 'paragraph': {
30
- let tempTokens: marked.Token[] = [];
31
- const paragraphChildren: React.ReactNode[] = [];
32
- token.tokens.forEach((t) => {
33
- /* To avoid inlining of image */
34
- if (t.type === 'image') {
35
- paragraphChildren.push(
36
- this.renderer.getTextNode(
37
- this.parseInline(tempTokens),
38
- this.styles.text
39
- )
40
- );
41
- paragraphChildren.push(this.parseInline([t]));
42
- tempTokens = [];
43
- return;
44
- }
45
- tempTokens = [...tempTokens, t];
46
- });
47
-
48
- if (tempTokens.length > 0) {
49
- paragraphChildren.push(
50
- this.renderer.getTextNode(this.parseInline(tempTokens), {})
31
+ const paragraphChildren =
32
+ this.getNormalizedSiblingNodesForBlockAndInlineTokens(
33
+ token.tokens,
34
+ this.styles.text
51
35
  );
52
- }
53
36
 
54
37
  return this.renderer.getViewNode(
55
38
  paragraphChildren,
@@ -64,7 +47,10 @@ class Parser {
64
47
  }
65
48
  case 'heading': {
66
49
  const styles = this.headingStylesMap[token.depth] ?? this.styles.text;
67
- return this.renderer.getTextNode(token.text, styles);
50
+ return this.renderer.getTextNode(
51
+ this.parseInline(token.tokens, styles),
52
+ styles
53
+ );
68
54
  }
69
55
  case 'code': {
70
56
  return this.renderer.getCodeBlockNode(
@@ -78,19 +64,22 @@ class Parser {
78
64
  }
79
65
  case 'list': {
80
66
  const li = token.items.map((item) => {
81
- if (
82
- item.tokens.length > 0 &&
83
- item.tokens[0]?.type === 'text' &&
84
- // @ts-ignore
85
- Array.isArray(item.tokens[0]?.tokens)
86
- ) {
87
- return this.renderer.getTextNode(
88
- // @ts-ignore
89
- this.parseInline(item.tokens[0]?.tokens),
90
- this.styles.li
91
- );
92
- }
93
- return this.renderer.getTextNode(item.text, this.styles.li);
67
+ const children = item.tokens.map((cItem) => {
68
+ if (cItem.type === 'text') {
69
+ /* getViewNode since tokens could contain a block like elements (i.e. img) */
70
+ const listChildren =
71
+ this.getNormalizedSiblingNodesForBlockAndInlineTokens(
72
+ // @ts-ignore
73
+ cItem.tokens,
74
+ this.styles.li
75
+ );
76
+ return this.renderer.getViewNode(listChildren, this.styles.li);
77
+ }
78
+
79
+ /* Parse the nested token */
80
+ return this.parse([cItem]);
81
+ });
82
+ return this.renderer.getViewNode(children, this.styles.li);
94
83
  });
95
84
  return this.renderer.getListNode(
96
85
  token.ordered,
@@ -99,51 +88,63 @@ class Parser {
99
88
  this.styles.li
100
89
  );
101
90
  }
102
- case 'html': {
103
- return this.renderer.getTextNode(token.text.trim(), this.styles.text);
104
- }
105
91
  default:
106
- console.warn(`Token with '${token.type}' type was not found.`);
107
- return null;
92
+ return this.parseInline([token]);
108
93
  }
109
94
  });
110
- return elements;
95
+ return elements.filter((element) => element !== null);
111
96
  }
112
97
 
113
98
  parseInline(tokens: marked.Token[], styles?: CustomStyleProp) {
114
- const elements: React.ReactNode[] = tokens.map((token) => {
99
+ const elements: ReactNode[] = tokens.map((token) => {
115
100
  if (!token) return null;
116
101
 
117
102
  switch (token.type) {
118
103
  case 'escape': {
119
104
  return this.renderer.getTextNode(token.text, {
120
- ...styles,
121
105
  ...this.styles.text,
106
+ ...styles,
122
107
  });
123
108
  }
124
109
  case 'link': {
125
- return this.renderer.getLinkNode(token.text, token.href, {
110
+ const linkStyle = {
126
111
  ...styles,
127
- ...this.styles.link,
128
- });
112
+ ...this.styles.link, // To override color property
113
+ };
114
+ return this.renderer.getLinkNode(
115
+ this.parseInline(token.tokens, linkStyle),
116
+ token.href,
117
+ linkStyle
118
+ );
129
119
  }
130
120
  case 'image': {
131
121
  return this.renderer.getImageNode(token.href);
132
122
  }
133
123
  case 'strong': {
124
+ const boldStyle = {
125
+ ...this.styles.strong,
126
+ ...styles,
127
+ };
134
128
  return this.renderer.getTextNode(
135
- this.parseInline(token.tokens, this.styles.strong),
136
- { ...styles, ...this.styles.strong }
129
+ this.parseInline(token.tokens, boldStyle),
130
+ boldStyle
137
131
  );
138
132
  }
139
133
  case 'em': {
134
+ const italicStyle = {
135
+ ...this.styles.em,
136
+ ...styles,
137
+ };
140
138
  return this.renderer.getTextNode(
141
- this.parseInline(token.tokens, this.styles.em),
142
- { ...styles, ...this.styles.em }
139
+ this.parseInline(token.tokens, italicStyle),
140
+ italicStyle
143
141
  );
144
142
  }
145
143
  case 'codespan': {
146
- return this.renderer.getTextNode(token.text, this.styles.codespan);
144
+ return this.renderer.getTextNode(token.text, {
145
+ ...this.styles.codespan,
146
+ ...styles,
147
+ });
147
148
  }
148
149
  case 'br': {
149
150
  return this.renderer.getTextNode('\n', {});
@@ -151,10 +152,15 @@ class Parser {
151
152
  case 'del': {
152
153
  return null;
153
154
  }
154
- case 'text': {
155
+ case 'text':
156
+ case 'html': {
157
+ if (token.raw.trim().length < 1) {
158
+ return null;
159
+ }
160
+
155
161
  return this.renderer.getTextNode(token.raw, {
156
- ...styles,
157
162
  ...this.styles.text,
163
+ ...styles,
158
164
  });
159
165
  }
160
166
  default: {
@@ -163,8 +169,42 @@ class Parser {
163
169
  }
164
170
  }
165
171
  });
166
- return elements;
172
+ return elements.filter((element) => element !== null);
167
173
  }
174
+
175
+ private getNormalizedSiblingNodesForBlockAndInlineTokens = (
176
+ tokens: marked.Token[],
177
+ textStyle: TextStyleProp
178
+ ): ReactNode[] => {
179
+ let tempTokens: marked.Token[] = [];
180
+ const siblingNodes: ReactNode[] = [];
181
+ tokens.forEach((t) => {
182
+ /**
183
+ * To avoid inlining images
184
+ * Note: to be extend for other token types
185
+ */
186
+ if (t.type === 'image') {
187
+ const parsed = this.parseInline(tempTokens);
188
+ if (parsed.length > 0) {
189
+ siblingNodes.push(
190
+ this.renderer.getTextNode(this.parseInline(tempTokens), textStyle)
191
+ );
192
+ }
193
+ siblingNodes.push(this.parseInline([t]));
194
+ tempTokens = [];
195
+ return;
196
+ }
197
+ tempTokens = [...tempTokens, t];
198
+ });
199
+
200
+ /* Remaining temp tokens if any */
201
+ if (tempTokens.length > 0) {
202
+ siblingNodes.push(
203
+ this.renderer.getTextNode(this.parseInline(tempTokens), {})
204
+ );
205
+ }
206
+ return siblingNodes;
207
+ };
168
208
  }
169
209
 
170
210
  export default Parser;
@@ -1,19 +1,15 @@
1
- import * as React from 'react';
2
- import { Linking, ScrollView, View, Text } from 'react-native';
1
+ import React, { ReactNode } from 'react';
2
+ import { ScrollView, View, Text } from 'react-native';
3
3
  import MarkedList from '@jsamr/react-native-li';
4
4
  import Disc from '@jsamr/counter-style/presets/disc';
5
5
  import Decimal from '@jsamr/counter-style/presets/decimal';
6
6
  import MDImage from './../components/MDImage';
7
7
  import { generateRandomString } from '../utils/string';
8
8
  import type { TextStyleProp, ViewStyleProp } from './types';
9
+ import { onLinkPress } from '../utils/handlers';
9
10
 
10
11
  class Renderer {
11
- private onLinkPress = (href: string) => () => Linking.openURL(href);
12
-
13
- getTextNode = (
14
- children: string | React.ReactNode[],
15
- styles: TextStyleProp
16
- ) => {
12
+ getTextNode = (children: string | ReactNode[], styles: TextStyleProp) => {
17
13
  return (
18
14
  <Text key={generateRandomString()} style={styles}>
19
15
  {children}
@@ -21,19 +17,23 @@ class Renderer {
21
17
  );
22
18
  };
23
19
 
24
- getLinkNode = (text: string, href: string, styles: TextStyleProp) => {
20
+ getLinkNode = (
21
+ children: string | ReactNode[],
22
+ href: string,
23
+ styles: TextStyleProp
24
+ ) => {
25
25
  return (
26
26
  <Text
27
27
  key={generateRandomString()}
28
- onPress={this.onLinkPress(href)}
28
+ onPress={onLinkPress(href)}
29
29
  style={styles}
30
30
  >
31
- {text}
31
+ {children}
32
32
  </Text>
33
33
  );
34
34
  };
35
35
 
36
- getViewNode(children: React.ReactNode[] | null, styles: ViewStyleProp) {
36
+ getViewNode(children: ReactNode[] | null, styles: ViewStyleProp) {
37
37
  return (
38
38
  <View key={generateRandomString()} style={styles}>
39
39
  {children}
@@ -47,13 +47,17 @@ class Renderer {
47
47
  textStyle: TextStyleProp
48
48
  ) {
49
49
  return (
50
- <ScrollView contentContainerStyle={containerStyle} horizontal>
50
+ <ScrollView
51
+ key={generateRandomString()}
52
+ contentContainerStyle={containerStyle}
53
+ horizontal
54
+ >
51
55
  <Text style={textStyle}>{text}</Text>
52
56
  </ScrollView>
53
57
  );
54
58
  }
55
59
 
56
- getBlockquoteNode(children: React.ReactNode[], styles: ViewStyleProp) {
60
+ getBlockquoteNode(children: ReactNode[], styles: ViewStyleProp) {
57
61
  return (
58
62
  <View key={generateRandomString()} style={styles}>
59
63
  {children}
@@ -67,7 +71,7 @@ class Renderer {
67
71
 
68
72
  getListNode(
69
73
  ordered: boolean,
70
- li: React.ReactNode[],
74
+ li: ReactNode[],
71
75
  listStyle: ViewStyleProp,
72
76
  textStyle: TextStyleProp
73
77
  ) {
@@ -76,6 +80,7 @@ class Renderer {
76
80
  counterRenderer={ordered ? Decimal : Disc}
77
81
  markerTextStyle={textStyle}
78
82
  markerBoxStyle={listStyle}
83
+ key={generateRandomString()}
79
84
  >
80
85
  {li.map((node) => node)}
81
86
  </MarkedList>
@@ -34,7 +34,9 @@ const getStyles = (
34
34
  ...fontStyle.regular,
35
35
  fontWeight: 'bold',
36
36
  },
37
- text: { ...fontStyle.regular },
37
+ text: {
38
+ ...fontStyle.regular,
39
+ },
38
40
  paragraph: {
39
41
  ...fontStyle.regular,
40
42
  paddingVertical: 8,
@@ -0,0 +1,9 @@
1
+ import { Linking } from 'react-native';
2
+
3
+ export const onLinkPress = (url: string) => () => {
4
+ try {
5
+ Linking.openURL(url);
6
+ } catch (e) {
7
+ console.warn("URL can't be opened", e);
8
+ }
9
+ };