react-native-marked 8.1.1 → 8.2.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.
- package/LICENSE +20 -20
- package/README.md +354 -354
- package/dist/commonjs/components/MDImage.js +3 -3
- package/dist/commonjs/components/MDImage.js.map +1 -1
- package/dist/commonjs/components/MDList.js.map +1 -1
- package/dist/commonjs/components/MDSvg.js.map +1 -1
- package/dist/commonjs/components/MDTable.js.map +1 -1
- package/dist/commonjs/hooks/useMarkdown.js.map +1 -1
- package/dist/commonjs/hooks/useMarkdownWithComponents.js.map +1 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/lib/Markdown.js.map +1 -1
- package/dist/commonjs/lib/Parser.js +4 -4
- package/dist/commonjs/lib/Parser.js.map +1 -1
- package/dist/commonjs/lib/ReactComponentRegistry.js.map +1 -1
- package/dist/commonjs/lib/ReactComponentTokenizer.js.map +1 -1
- package/dist/commonjs/lib/Renderer.js.map +1 -1
- package/dist/commonjs/lib/types.js.map +1 -1
- package/dist/commonjs/theme/colors.js.map +1 -1
- package/dist/commonjs/theme/spacing.js.map +1 -1
- package/dist/commonjs/theme/styles.js.map +1 -1
- package/dist/commonjs/theme/types.js.map +1 -1
- package/dist/commonjs/utils/handlers.js.map +1 -1
- package/dist/commonjs/utils/svg.js.map +1 -1
- package/dist/commonjs/utils/table.js.map +1 -1
- package/dist/commonjs/utils/url.js.map +1 -1
- package/dist/module/components/MDImage.js +3 -3
- package/dist/module/components/MDImage.js.map +1 -1
- package/dist/module/components/MDList.js.map +1 -1
- package/dist/module/components/MDSvg.js.map +1 -1
- package/dist/module/components/MDTable.js.map +1 -1
- package/dist/module/hooks/useMarkdown.js.map +1 -1
- package/dist/module/hooks/useMarkdownWithComponents.js.map +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/Markdown.js.map +1 -1
- package/dist/module/lib/Parser.js +4 -4
- package/dist/module/lib/Parser.js.map +1 -1
- package/dist/module/lib/ReactComponentRegistry.js.map +1 -1
- package/dist/module/lib/ReactComponentTokenizer.js.map +1 -1
- package/dist/module/lib/Renderer.js.map +1 -1
- package/dist/module/lib/types.js.map +1 -1
- package/dist/module/theme/colors.js.map +1 -1
- package/dist/module/theme/spacing.js.map +1 -1
- package/dist/module/theme/styles.js.map +1 -1
- package/dist/module/theme/types.js.map +1 -1
- package/dist/module/utils/handlers.js.map +1 -1
- package/dist/module/utils/svg.js.map +1 -1
- package/dist/module/utils/table.js.map +1 -1
- package/dist/module/utils/url.js.map +1 -1
- package/package.json +175 -175
- package/src/components/MDImage.tsx +90 -90
- package/src/components/MDList.tsx +46 -46
- package/src/components/MDSvg.tsx +108 -108
- package/src/components/MDTable.tsx +60 -60
- package/src/hooks/useMarkdown.ts +51 -51
- package/src/hooks/useMarkdownWithComponents.tsx +89 -89
- package/src/index.ts +43 -43
- package/src/lib/Markdown.tsx +53 -53
- package/src/lib/Parser.tsx +316 -316
- package/src/lib/ReactComponentRegistry.tsx +52 -52
- package/src/lib/ReactComponentTokenizer.ts +149 -149
- package/src/lib/Renderer.tsx +238 -238
- package/src/lib/types.ts +87 -87
- package/src/theme/colors.ts +31 -31
- package/src/theme/spacing.ts +10 -10
- package/src/theme/styles.ts +198 -198
- package/src/theme/types.ts +33 -33
- package/src/utils/handlers.ts +9 -9
- package/src/utils/svg.ts +14 -14
- package/src/utils/table.ts +29 -29
- package/src/utils/url.ts +22 -22
package/src/lib/Parser.tsx
CHANGED
|
@@ -1,316 +1,316 @@
|
|
|
1
|
-
import { decode } from "html-entities";
|
|
2
|
-
import type { Token, Tokens } from "marked";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
|
|
5
|
-
import type { MarkedStyles } from "../theme/types";
|
|
6
|
-
import { getTableColAlignmentStyle } from "./../utils/table";
|
|
7
|
-
import { getValidURL } from "./../utils/url";
|
|
8
|
-
import type { ParserOptions, RendererInterface } from "./types";
|
|
9
|
-
|
|
10
|
-
class Parser {
|
|
11
|
-
private renderer: RendererInterface;
|
|
12
|
-
private styles: MarkedStyles;
|
|
13
|
-
private headingStylesMap: Record<number, TextStyle | undefined>;
|
|
14
|
-
private baseUrl: string;
|
|
15
|
-
|
|
16
|
-
constructor(options: ParserOptions) {
|
|
17
|
-
this.styles = { ...options.styles };
|
|
18
|
-
this.baseUrl = options.baseUrl ?? "";
|
|
19
|
-
this.renderer = options.renderer;
|
|
20
|
-
this.headingStylesMap = {
|
|
21
|
-
1: this.styles.h1,
|
|
22
|
-
2: this.styles.h2,
|
|
23
|
-
3: this.styles.h3,
|
|
24
|
-
4: this.styles.h4,
|
|
25
|
-
5: this.styles.h5,
|
|
26
|
-
6: this.styles.h6,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
parse(tokens?: Token[]) {
|
|
31
|
-
return this._parse(tokens);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private _parse(
|
|
35
|
-
tokens?: Token[],
|
|
36
|
-
styles?: ViewStyle | TextStyle | ImageStyle,
|
|
37
|
-
): ReactNode[] {
|
|
38
|
-
if (!tokens) return [];
|
|
39
|
-
|
|
40
|
-
const elements: ReactNode[] = tokens.map((token) => {
|
|
41
|
-
return this._parseToken(token, styles);
|
|
42
|
-
});
|
|
43
|
-
return elements.filter((element) => element !== null);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private _parseToken(
|
|
47
|
-
token: Token,
|
|
48
|
-
styles?: ViewStyle | TextStyle | ImageStyle,
|
|
49
|
-
): ReactNode {
|
|
50
|
-
switch (token.type) {
|
|
51
|
-
case "paragraph": {
|
|
52
|
-
const children = this.getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
53
|
-
token.tokens ?? [],
|
|
54
|
-
this.styles.text,
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
return this.renderer.paragraph(children, this.styles.paragraph);
|
|
58
|
-
}
|
|
59
|
-
case "blockquote": {
|
|
60
|
-
const children = this.parse(token.tokens);
|
|
61
|
-
return this.renderer.blockquote(children, this.styles.blockquote);
|
|
62
|
-
}
|
|
63
|
-
case "heading": {
|
|
64
|
-
const styles = this.headingStylesMap[token.depth];
|
|
65
|
-
|
|
66
|
-
if (this.hasDuplicateTextChildToken(token)) {
|
|
67
|
-
return this.renderer.heading(token.text, styles, token.depth);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const children = this._parse(token.tokens, styles);
|
|
71
|
-
return this.renderer.heading(children, styles, token.depth);
|
|
72
|
-
}
|
|
73
|
-
case "code": {
|
|
74
|
-
return this.renderer.code(
|
|
75
|
-
token.text,
|
|
76
|
-
token.lang,
|
|
77
|
-
this.styles.code,
|
|
78
|
-
this.styles.em,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
case "hr": {
|
|
82
|
-
return this.renderer.hr(this.styles.hr);
|
|
83
|
-
}
|
|
84
|
-
case "list": {
|
|
85
|
-
let startIndex = Number.parseInt(token.start.toString(), 10);
|
|
86
|
-
if (Number.isNaN(startIndex)) {
|
|
87
|
-
startIndex = 1;
|
|
88
|
-
}
|
|
89
|
-
const li = (token as Tokens.List).items.map((item) => {
|
|
90
|
-
const children = item.tokens.flatMap((cItem) => {
|
|
91
|
-
if (cItem.type === "text") {
|
|
92
|
-
/* getViewNode since tokens could contain a block like elements (i.e. img) */
|
|
93
|
-
const childTokens = (cItem as Tokens.Text).tokens || [];
|
|
94
|
-
const listChildren =
|
|
95
|
-
this.getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
96
|
-
childTokens,
|
|
97
|
-
this.styles.li,
|
|
98
|
-
);
|
|
99
|
-
// return this.renderer.listItem(listChildren, this.styles.li);
|
|
100
|
-
return listChildren;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/* Parse the nested token */
|
|
104
|
-
return this._parseToken(cItem);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return this.renderer.listItem(children, this.styles.li);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return this.renderer.list(
|
|
111
|
-
token.ordered,
|
|
112
|
-
li,
|
|
113
|
-
this.styles.list,
|
|
114
|
-
this.styles.li,
|
|
115
|
-
startIndex,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
case "escape": {
|
|
119
|
-
return this.renderer.escape(token.text, {
|
|
120
|
-
...this.styles.text,
|
|
121
|
-
...styles,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
case "link": {
|
|
125
|
-
// Don't render anchors without text and children
|
|
126
|
-
if (token.text.trim().length < 1 || !token.tokens) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Note: Linking Images (https://www.markdownguide.org/basic-syntax/#linking-images) are wrapped
|
|
131
|
-
// in paragraph token, so will be handled via `getNormalizedSiblingNodesForBlockAndInlineTokens`
|
|
132
|
-
const linkStyle = {
|
|
133
|
-
...this.styles.link,
|
|
134
|
-
...styles,
|
|
135
|
-
// To override color and fontStyle properties
|
|
136
|
-
color: this.styles.link?.color,
|
|
137
|
-
fontStyle: this.styles.link?.fontStyle,
|
|
138
|
-
};
|
|
139
|
-
const href = getValidURL(this.baseUrl, token.href);
|
|
140
|
-
|
|
141
|
-
if (this.hasDuplicateTextChildToken(token)) {
|
|
142
|
-
return this.renderer.link(token.text, href, linkStyle, token.title);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const children = this._parse(token.tokens, linkStyle);
|
|
146
|
-
return this.renderer.link(children, href, linkStyle, token.title);
|
|
147
|
-
}
|
|
148
|
-
case "image": {
|
|
149
|
-
return this.renderer.image(
|
|
150
|
-
token.href,
|
|
151
|
-
token.text,
|
|
152
|
-
this.styles.image,
|
|
153
|
-
token.title,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
case "strong": {
|
|
157
|
-
const boldStyle = {
|
|
158
|
-
...this.styles.strong,
|
|
159
|
-
...styles,
|
|
160
|
-
};
|
|
161
|
-
if (this.hasDuplicateTextChildToken(token)) {
|
|
162
|
-
return this.renderer.strong(token.text, boldStyle);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const children = this._parse(token.tokens, boldStyle);
|
|
166
|
-
return this.renderer.strong(children, boldStyle);
|
|
167
|
-
}
|
|
168
|
-
case "em": {
|
|
169
|
-
const italicStyle = {
|
|
170
|
-
...this.styles.em,
|
|
171
|
-
...styles,
|
|
172
|
-
};
|
|
173
|
-
if (this.hasDuplicateTextChildToken(token)) {
|
|
174
|
-
return this.renderer.em(token.text, italicStyle);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const children = this._parse(token.tokens, italicStyle);
|
|
178
|
-
return this.renderer.em(children, italicStyle);
|
|
179
|
-
}
|
|
180
|
-
case "codespan": {
|
|
181
|
-
return this.renderer.codespan(decode(token.text), {
|
|
182
|
-
...this.styles.codespan,
|
|
183
|
-
...styles,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
case "br": {
|
|
187
|
-
return this.renderer.br();
|
|
188
|
-
}
|
|
189
|
-
case "del": {
|
|
190
|
-
const strikethroughStyle = {
|
|
191
|
-
...this.styles.strikethrough,
|
|
192
|
-
...styles,
|
|
193
|
-
};
|
|
194
|
-
if (this.hasDuplicateTextChildToken(token)) {
|
|
195
|
-
return this.renderer.del(token.text, strikethroughStyle);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const children = this._parse(token.tokens, strikethroughStyle);
|
|
199
|
-
return this.renderer.del(children, strikethroughStyle);
|
|
200
|
-
}
|
|
201
|
-
case "text":
|
|
202
|
-
return this.renderer.text(token.raw, {
|
|
203
|
-
...this.styles.text,
|
|
204
|
-
...styles,
|
|
205
|
-
});
|
|
206
|
-
case "html": {
|
|
207
|
-
return this.renderer.html(token.raw, {
|
|
208
|
-
...this.styles.text,
|
|
209
|
-
...styles,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
case "table": {
|
|
213
|
-
const header = (token as Tokens.Table).header.map((row, i) =>
|
|
214
|
-
this._parse(row.tokens, {
|
|
215
|
-
...getTableColAlignmentStyle(token.align[i]),
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const rows = (token as Tokens.Table).rows.map((cols) =>
|
|
220
|
-
cols.map((col, i) =>
|
|
221
|
-
this._parse(col.tokens, {
|
|
222
|
-
...getTableColAlignmentStyle(token.align[i]),
|
|
223
|
-
}),
|
|
224
|
-
),
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
return this.renderer.table(
|
|
228
|
-
header,
|
|
229
|
-
rows,
|
|
230
|
-
this.styles.table,
|
|
231
|
-
this.styles.tableRow,
|
|
232
|
-
this.styles.tableCell,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
default: {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
242
|
-
tokens: Token[],
|
|
243
|
-
textStyle?: TextStyle,
|
|
244
|
-
): ReactNode[] {
|
|
245
|
-
let tokenRenderQueue: Token[] = [];
|
|
246
|
-
const siblingNodes: ReactNode[] = [];
|
|
247
|
-
for (const t of tokens) {
|
|
248
|
-
/**
|
|
249
|
-
* To avoid inlining images
|
|
250
|
-
* Currently supports images, link images
|
|
251
|
-
* Note: to be extend for other token types
|
|
252
|
-
*/
|
|
253
|
-
if (
|
|
254
|
-
t.type === "image" ||
|
|
255
|
-
(t.type === "link" &&
|
|
256
|
-
t.tokens &&
|
|
257
|
-
t.tokens[0] &&
|
|
258
|
-
t.tokens[0].type === "image")
|
|
259
|
-
) {
|
|
260
|
-
// Render existing inline tokens in the queue
|
|
261
|
-
const parsed = this._parse(tokenRenderQueue);
|
|
262
|
-
if (parsed.length > 0) {
|
|
263
|
-
siblingNodes.push(this.renderer.text(parsed, textStyle));
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Render the current block token
|
|
267
|
-
if (t.type === "image") {
|
|
268
|
-
siblingNodes.push(this._parseToken(t));
|
|
269
|
-
} else if (t.type === "link" && t.tokens && t.tokens[0]) {
|
|
270
|
-
const imageToken = t.tokens[0] as Tokens.Image;
|
|
271
|
-
const href = getValidURL(this.baseUrl, t.href);
|
|
272
|
-
siblingNodes.push(
|
|
273
|
-
this.renderer.linkImage(
|
|
274
|
-
href,
|
|
275
|
-
imageToken.href,
|
|
276
|
-
imageToken.text ?? imageToken.title ?? "",
|
|
277
|
-
this.styles.image,
|
|
278
|
-
imageToken.title,
|
|
279
|
-
),
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
tokenRenderQueue = [];
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
tokenRenderQueue = [...tokenRenderQueue, t];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/* Remaining temp tokens if any */
|
|
290
|
-
if (tokenRenderQueue.length > 0) {
|
|
291
|
-
siblingNodes.push(this.renderer.text(this.parse(tokenRenderQueue), {}));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return siblingNodes;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// To avoid duplicate text node nesting when there are no child tokens with text emphasis (i.e., italic)
|
|
298
|
-
// ref: https://github.com/gmsgowtham/react-native-marked/issues/522
|
|
299
|
-
private hasDuplicateTextChildToken(token: Token): boolean {
|
|
300
|
-
if (!("tokens" in token)) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
token.tokens &&
|
|
306
|
-
token.tokens.length === 1 &&
|
|
307
|
-
token.tokens[0]?.type === "text"
|
|
308
|
-
) {
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export default Parser;
|
|
1
|
+
import { decode } from "html-entities";
|
|
2
|
+
import type { Token, Tokens } from "marked";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
|
|
5
|
+
import type { MarkedStyles } from "../theme/types";
|
|
6
|
+
import { getTableColAlignmentStyle } from "./../utils/table";
|
|
7
|
+
import { getValidURL } from "./../utils/url";
|
|
8
|
+
import type { ParserOptions, RendererInterface } from "./types";
|
|
9
|
+
|
|
10
|
+
class Parser {
|
|
11
|
+
private renderer: RendererInterface;
|
|
12
|
+
private styles: MarkedStyles;
|
|
13
|
+
private headingStylesMap: Record<number, TextStyle | undefined>;
|
|
14
|
+
private baseUrl: string;
|
|
15
|
+
|
|
16
|
+
constructor(options: ParserOptions) {
|
|
17
|
+
this.styles = { ...options.styles };
|
|
18
|
+
this.baseUrl = options.baseUrl ?? "";
|
|
19
|
+
this.renderer = options.renderer;
|
|
20
|
+
this.headingStylesMap = {
|
|
21
|
+
1: this.styles.h1,
|
|
22
|
+
2: this.styles.h2,
|
|
23
|
+
3: this.styles.h3,
|
|
24
|
+
4: this.styles.h4,
|
|
25
|
+
5: this.styles.h5,
|
|
26
|
+
6: this.styles.h6,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parse(tokens?: Token[]) {
|
|
31
|
+
return this._parse(tokens);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private _parse(
|
|
35
|
+
tokens?: Token[],
|
|
36
|
+
styles?: ViewStyle | TextStyle | ImageStyle,
|
|
37
|
+
): ReactNode[] {
|
|
38
|
+
if (!tokens) return [];
|
|
39
|
+
|
|
40
|
+
const elements: ReactNode[] = tokens.map((token) => {
|
|
41
|
+
return this._parseToken(token, styles);
|
|
42
|
+
});
|
|
43
|
+
return elements.filter((element) => element !== null);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private _parseToken(
|
|
47
|
+
token: Token,
|
|
48
|
+
styles?: ViewStyle | TextStyle | ImageStyle,
|
|
49
|
+
): ReactNode {
|
|
50
|
+
switch (token.type) {
|
|
51
|
+
case "paragraph": {
|
|
52
|
+
const children = this.getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
53
|
+
token.tokens ?? [],
|
|
54
|
+
this.styles.text,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return this.renderer.paragraph(children, this.styles.paragraph);
|
|
58
|
+
}
|
|
59
|
+
case "blockquote": {
|
|
60
|
+
const children = this.parse(token.tokens);
|
|
61
|
+
return this.renderer.blockquote(children, this.styles.blockquote);
|
|
62
|
+
}
|
|
63
|
+
case "heading": {
|
|
64
|
+
const styles = this.headingStylesMap[token.depth];
|
|
65
|
+
|
|
66
|
+
if (this.hasDuplicateTextChildToken(token)) {
|
|
67
|
+
return this.renderer.heading(token.text, styles, token.depth);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const children = this._parse(token.tokens, styles);
|
|
71
|
+
return this.renderer.heading(children, styles, token.depth);
|
|
72
|
+
}
|
|
73
|
+
case "code": {
|
|
74
|
+
return this.renderer.code(
|
|
75
|
+
token.text,
|
|
76
|
+
token.lang,
|
|
77
|
+
this.styles.code,
|
|
78
|
+
this.styles.em,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
case "hr": {
|
|
82
|
+
return this.renderer.hr(this.styles.hr);
|
|
83
|
+
}
|
|
84
|
+
case "list": {
|
|
85
|
+
let startIndex = Number.parseInt(token.start.toString(), 10);
|
|
86
|
+
if (Number.isNaN(startIndex)) {
|
|
87
|
+
startIndex = 1;
|
|
88
|
+
}
|
|
89
|
+
const li = (token as Tokens.List).items.map((item) => {
|
|
90
|
+
const children = item.tokens.flatMap((cItem) => {
|
|
91
|
+
if (cItem.type === "text") {
|
|
92
|
+
/* getViewNode since tokens could contain a block like elements (i.e. img) */
|
|
93
|
+
const childTokens = (cItem as Tokens.Text).tokens || [];
|
|
94
|
+
const listChildren =
|
|
95
|
+
this.getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
96
|
+
childTokens,
|
|
97
|
+
this.styles.li,
|
|
98
|
+
);
|
|
99
|
+
// return this.renderer.listItem(listChildren, this.styles.li);
|
|
100
|
+
return listChildren;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Parse the nested token */
|
|
104
|
+
return this._parseToken(cItem);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return this.renderer.listItem(children, this.styles.li);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return this.renderer.list(
|
|
111
|
+
token.ordered,
|
|
112
|
+
li,
|
|
113
|
+
this.styles.list,
|
|
114
|
+
this.styles.li,
|
|
115
|
+
startIndex,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
case "escape": {
|
|
119
|
+
return this.renderer.escape(token.text, {
|
|
120
|
+
...this.styles.text,
|
|
121
|
+
...styles,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
case "link": {
|
|
125
|
+
// Don't render anchors without text and children
|
|
126
|
+
if (token.text.trim().length < 1 || !token.tokens) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Note: Linking Images (https://www.markdownguide.org/basic-syntax/#linking-images) are wrapped
|
|
131
|
+
// in paragraph token, so will be handled via `getNormalizedSiblingNodesForBlockAndInlineTokens`
|
|
132
|
+
const linkStyle = {
|
|
133
|
+
...this.styles.link,
|
|
134
|
+
...styles,
|
|
135
|
+
// To override color and fontStyle properties
|
|
136
|
+
color: this.styles.link?.color,
|
|
137
|
+
fontStyle: this.styles.link?.fontStyle,
|
|
138
|
+
};
|
|
139
|
+
const href = getValidURL(this.baseUrl, token.href);
|
|
140
|
+
|
|
141
|
+
if (this.hasDuplicateTextChildToken(token)) {
|
|
142
|
+
return this.renderer.link(token.text, href, linkStyle, token.title);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const children = this._parse(token.tokens, linkStyle);
|
|
146
|
+
return this.renderer.link(children, href, linkStyle, token.title);
|
|
147
|
+
}
|
|
148
|
+
case "image": {
|
|
149
|
+
return this.renderer.image(
|
|
150
|
+
token.href,
|
|
151
|
+
token.text,
|
|
152
|
+
this.styles.image,
|
|
153
|
+
token.title,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
case "strong": {
|
|
157
|
+
const boldStyle = {
|
|
158
|
+
...this.styles.strong,
|
|
159
|
+
...styles,
|
|
160
|
+
};
|
|
161
|
+
if (this.hasDuplicateTextChildToken(token)) {
|
|
162
|
+
return this.renderer.strong(token.text, boldStyle);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const children = this._parse(token.tokens, boldStyle);
|
|
166
|
+
return this.renderer.strong(children, boldStyle);
|
|
167
|
+
}
|
|
168
|
+
case "em": {
|
|
169
|
+
const italicStyle = {
|
|
170
|
+
...this.styles.em,
|
|
171
|
+
...styles,
|
|
172
|
+
};
|
|
173
|
+
if (this.hasDuplicateTextChildToken(token)) {
|
|
174
|
+
return this.renderer.em(token.text, italicStyle);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const children = this._parse(token.tokens, italicStyle);
|
|
178
|
+
return this.renderer.em(children, italicStyle);
|
|
179
|
+
}
|
|
180
|
+
case "codespan": {
|
|
181
|
+
return this.renderer.codespan(decode(token.text), {
|
|
182
|
+
...this.styles.codespan,
|
|
183
|
+
...styles,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
case "br": {
|
|
187
|
+
return this.renderer.br();
|
|
188
|
+
}
|
|
189
|
+
case "del": {
|
|
190
|
+
const strikethroughStyle = {
|
|
191
|
+
...this.styles.strikethrough,
|
|
192
|
+
...styles,
|
|
193
|
+
};
|
|
194
|
+
if (this.hasDuplicateTextChildToken(token)) {
|
|
195
|
+
return this.renderer.del(token.text, strikethroughStyle);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const children = this._parse(token.tokens, strikethroughStyle);
|
|
199
|
+
return this.renderer.del(children, strikethroughStyle);
|
|
200
|
+
}
|
|
201
|
+
case "text":
|
|
202
|
+
return this.renderer.text(token.raw, {
|
|
203
|
+
...this.styles.text,
|
|
204
|
+
...styles,
|
|
205
|
+
});
|
|
206
|
+
case "html": {
|
|
207
|
+
return this.renderer.html(token.raw, {
|
|
208
|
+
...this.styles.text,
|
|
209
|
+
...styles,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
case "table": {
|
|
213
|
+
const header = (token as Tokens.Table).header.map((row, i) =>
|
|
214
|
+
this._parse(row.tokens, {
|
|
215
|
+
...getTableColAlignmentStyle(token.align[i]),
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const rows = (token as Tokens.Table).rows.map((cols) =>
|
|
220
|
+
cols.map((col, i) =>
|
|
221
|
+
this._parse(col.tokens, {
|
|
222
|
+
...getTableColAlignmentStyle(token.align[i]),
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return this.renderer.table(
|
|
228
|
+
header,
|
|
229
|
+
rows,
|
|
230
|
+
this.styles.table,
|
|
231
|
+
this.styles.tableRow,
|
|
232
|
+
this.styles.tableCell,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
default: {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private getNormalizedSiblingNodesForBlockAndInlineTokens(
|
|
242
|
+
tokens: Token[],
|
|
243
|
+
textStyle?: TextStyle,
|
|
244
|
+
): ReactNode[] {
|
|
245
|
+
let tokenRenderQueue: Token[] = [];
|
|
246
|
+
const siblingNodes: ReactNode[] = [];
|
|
247
|
+
for (const t of tokens) {
|
|
248
|
+
/**
|
|
249
|
+
* To avoid inlining images
|
|
250
|
+
* Currently supports images, link images
|
|
251
|
+
* Note: to be extend for other token types
|
|
252
|
+
*/
|
|
253
|
+
if (
|
|
254
|
+
t.type === "image" ||
|
|
255
|
+
(t.type === "link" &&
|
|
256
|
+
t.tokens &&
|
|
257
|
+
t.tokens[0] &&
|
|
258
|
+
t.tokens[0].type === "image")
|
|
259
|
+
) {
|
|
260
|
+
// Render existing inline tokens in the queue
|
|
261
|
+
const parsed = this._parse(tokenRenderQueue);
|
|
262
|
+
if (parsed.length > 0) {
|
|
263
|
+
siblingNodes.push(this.renderer.text(parsed, textStyle));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Render the current block token
|
|
267
|
+
if (t.type === "image") {
|
|
268
|
+
siblingNodes.push(this._parseToken(t));
|
|
269
|
+
} else if (t.type === "link" && t.tokens && t.tokens[0]) {
|
|
270
|
+
const imageToken = t.tokens[0] as Tokens.Image;
|
|
271
|
+
const href = getValidURL(this.baseUrl, t.href);
|
|
272
|
+
siblingNodes.push(
|
|
273
|
+
this.renderer.linkImage(
|
|
274
|
+
href,
|
|
275
|
+
imageToken.href,
|
|
276
|
+
imageToken.text ?? imageToken.title ?? "",
|
|
277
|
+
this.styles.image,
|
|
278
|
+
imageToken.title,
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
tokenRenderQueue = [];
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
tokenRenderQueue = [...tokenRenderQueue, t];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* Remaining temp tokens if any */
|
|
290
|
+
if (tokenRenderQueue.length > 0) {
|
|
291
|
+
siblingNodes.push(this.renderer.text(this.parse(tokenRenderQueue), {}));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return siblingNodes;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// To avoid duplicate text node nesting when there are no child tokens with text emphasis (i.e., italic)
|
|
298
|
+
// ref: https://github.com/gmsgowtham/react-native-marked/issues/522
|
|
299
|
+
private hasDuplicateTextChildToken(token: Token): boolean {
|
|
300
|
+
if (!("tokens" in token)) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (
|
|
305
|
+
token.tokens &&
|
|
306
|
+
token.tokens.length === 1 &&
|
|
307
|
+
token.tokens[0]?.type === "text"
|
|
308
|
+
) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default Parser;
|