hs-react-native-custom-markdown 0.0.17 → 0.0.19

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.
@@ -7,8 +7,8 @@ type CustomMarkdownProps = {
7
7
  resolveImageSource?: (path: string) => any;
8
8
  };
9
9
  declare const CustomMarkdown: React.FC<CustomMarkdownProps>;
10
- export default CustomMarkdown;
11
10
  declare const defaultStyles: {
11
+ container: {};
12
12
  paragraph: {
13
13
  fontSize: number;
14
14
  lineHeight: number;
@@ -102,4 +102,5 @@ declare const defaultStyles: {
102
102
  marginVertical: number;
103
103
  };
104
104
  };
105
+ export default CustomMarkdown;
105
106
  //# sourceMappingURL=CustomMarkdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CustomMarkdown.d.ts","sourceRoot":"","sources":["../../src/CustomMarkdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAc,MAAM,OAAO,CAAC;AACnC,OAAO,EAML,SAAS,EACT,SAAS,EACT,SAAS,EACT,UAAU,EACX,MAAM,cAAc,CAAC;AAEtB,KAAK,aAAa,GAAG,SAAS,CAAC,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;AAEnE,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,OAAO,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC;IACpE,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC;CAC5C,CAAC;AAEF,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAkNjD,CAAC;AAEF,eAAe,cAAc,CAAC;AAE9B,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FjB,CAAC"}
1
+ {"version":3,"file":"CustomMarkdown.d.ts","sourceRoot":"","sources":["../../src/CustomMarkdown.tsx"],"names":[],"mappings":"AAyrBA,OAAO,KAAc,MAAM,OAAO,CAAC;AACnC,OAAO,EAML,SAAS,EACT,SAAS,EACT,SAAS,EACT,UAAU,EACX,MAAM,cAAc,CAAC;AAGtB,KAAK,aAAa,GAAG,SAAS,CAAC,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;AAEnE,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,OAAO,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC;IACpE,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC;CAC5C,CAAC;AAkBF,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA2NjD,CAAC;AAGF,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCjB,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -1,39 +1,708 @@
1
1
  "use strict";
2
+ // import React, { JSX } from 'react';
3
+ // import {
4
+ // Text,
5
+ // View,
6
+ // Image,
7
+ // StyleSheet,
8
+ // Linking,
9
+ // StyleProp,
10
+ // TextStyle,
11
+ // ViewStyle,
12
+ // ImageStyle,
13
+ // } from 'react-native';
2
14
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
15
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
16
  };
5
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ // type MarkdownStyle = StyleProp<TextStyle | ViewStyle | ImageStyle>;
19
+ // type CustomMarkdownProps = {
20
+ // content: string;
21
+ // styles?: Partial<Record<keyof typeof defaultStyles, MarkdownStyle>>;
22
+ // resolveImageSource?: (path: string) => any;
23
+ // };
24
+ // const CustomMarkdown: React.FC<CustomMarkdownProps> = ({
25
+ // content,
26
+ // styles = {},
27
+ // resolveImageSource,
28
+ // }) => {
29
+ // const getMergedStyle = (key: keyof typeof defaultStyles): MarkdownStyle => {
30
+ // return [defaultStyles[key], styles[key]];
31
+ // };
32
+ // // const parseInlineMarkdown = (text: string) => {
33
+ // // const elements: (JSX.Element | string)[] = [];
34
+ // // let remaining = text;
35
+ // // let index = 0;
36
+ // // const applyRegex = (
37
+ // // regex: RegExp,
38
+ // // styleKey: keyof typeof defaultStyles,
39
+ // // isLink = false,
40
+ // // isCode = false,
41
+ // // isHtmlTag = false,
42
+ // // renderText?: (value: string) => string,
43
+ // // ) => {
44
+ // // const match = regex.exec(remaining);
45
+ // // if (match) {
46
+ // // const [full, inner, link] = match;
47
+ // // const before = remaining.substring(0, match.index);
48
+ // // const after = remaining.substring(match.index + full.length);
49
+ // // if (before) elements.push(before);
50
+ // // if (isLink) {
51
+ // // elements.push(
52
+ // // <Text
53
+ // // key={`link-${index++}`}
54
+ // // style={getMergedStyle(styleKey)}
55
+ // // onPress={() => Linking.openURL(link)}
56
+ // // >
57
+ // // {inner}
58
+ // // </Text>,
59
+ // // );
60
+ // // } else if (isHtmlTag && renderText) {
61
+ // // elements.push(
62
+ // // <Text key={`html-${index++}`} style={getMergedStyle(styleKey)}>
63
+ // // {renderText(inner)}
64
+ // // </Text>,
65
+ // // );
66
+ // // } else {
67
+ // // elements.push(
68
+ // // <Text key={`styled-${index++}`} style={getMergedStyle(styleKey)}>
69
+ // // {inner}
70
+ // // </Text>,
71
+ // // );
72
+ // // }
73
+ // // remaining = after;
74
+ // // return true;
75
+ // // }
76
+ // // return false;
77
+ // // };
78
+ // // while (remaining.length) {
79
+ // // const patterns = [
80
+ // // { regex: /\*\*\*(.*?)\*\*\*/g, style: ['bold', 'italic'] },
81
+ // // { regex: /\*\*(.*?)\*\*/g, style: ['bold'] },
82
+ // // { regex: /_(.*?)_/g, style: ['italic'] },
83
+ // // { regex: /`([^`]+)`/g, style: ['code'], isCode: true },
84
+ // // { regex: /\[(.*?)\]\((.*?)\)/g, style: ['link'], isLink: true },
85
+ // // { regex: /<b>(.*?)<\/b>/i, style: ['bold'], isHtmlTag: true },
86
+ // // { regex: /<i>(.*?)<\/i>/i, style: ['italic'], isHtmlTag: true },
87
+ // // { regex: /<u>(.*?)<\/u>/i, style: ['underline'], isHtmlTag: true },
88
+ // // {
89
+ // // regex: /<br\s*\/?>/i,
90
+ // // style: ['paragraph'],
91
+ // // isHtmlTag: true,
92
+ // // renderText: () => '\n',
93
+ // // },
94
+ // // ];
95
+ // // let matched = false;
96
+ // // for (const pattern of patterns) {
97
+ // // if (
98
+ // // applyRegex(
99
+ // // pattern.regex,
100
+ // // pattern.style[0] as keyof typeof defaultStyles,
101
+ // // pattern.isLink,
102
+ // // pattern.isCode,
103
+ // // pattern.isHtmlTag,
104
+ // // pattern.renderText,
105
+ // // )
106
+ // // ) {
107
+ // // matched = true;
108
+ // // break;
109
+ // // }
110
+ // // }
111
+ // // if (!matched) {
112
+ // // elements.push(remaining);
113
+ // // break;
114
+ // // }
115
+ // // }
116
+ // // return <Text>{elements}</Text>;
117
+ // // };
118
+ // const parseInlineMarkdown = (text: string) => {
119
+ // const elements: (JSX.Element | string)[] = [];
120
+ // let remaining = text;
121
+ // let index = 0;
122
+ // // Helper function to parse formatting within colored text
123
+ // const parseFormattedText = (content: string, baseColor: string, startIdx: number) => {
124
+ // const formattedElements: JSX.Element[] = [];
125
+ // let formattedRemaining = content;
126
+ // let formattedIndex = startIdx;
127
+ // while (formattedRemaining.length > 0) {
128
+ // // Check for bold text
129
+ // const boldMatch = /\*\*(.*?)\*\*/g.exec(formattedRemaining);
130
+ // if (boldMatch) {
131
+ // const before = formattedRemaining.substring(0, boldMatch.index);
132
+ // if (before) {
133
+ // formattedElements.push(
134
+ // <Text key={`text-${formattedIndex++}`} style={{ color: baseColor }}>
135
+ // {before}
136
+ // </Text>
137
+ // );
138
+ // }
139
+ // formattedElements.push(
140
+ // <Text key={`bold-${formattedIndex++}`} style={{ color: baseColor, fontWeight: 'bold' }}>
141
+ // {boldMatch[1]}
142
+ // </Text>
143
+ // );
144
+ // formattedRemaining = formattedRemaining.substring(boldMatch.index + boldMatch[0].length);
145
+ // continue;
146
+ // }
147
+ // // Check for italic text
148
+ // const italicMatch = /_(.*?)_/g.exec(formattedRemaining);
149
+ // if (italicMatch) {
150
+ // const before = formattedRemaining.substring(0, italicMatch.index);
151
+ // if (before) {
152
+ // formattedElements.push(
153
+ // <Text key={`text-${formattedIndex++}`} style={{ color: baseColor }}>
154
+ // {before}
155
+ // </Text>
156
+ // );
157
+ // }
158
+ // formattedElements.push(
159
+ // <Text key={`italic-${formattedIndex++}`} style={{ color: baseColor, fontStyle: 'italic' }}>
160
+ // {italicMatch[1]}
161
+ // </Text>
162
+ // );
163
+ // formattedRemaining = formattedRemaining.substring(italicMatch.index + italicMatch[0].length);
164
+ // continue;
165
+ // }
166
+ // // No more formatting, push remaining text
167
+ // if (formattedRemaining) {
168
+ // formattedElements.push(
169
+ // <Text key={`text-${formattedIndex++}`} style={{ color: baseColor }}>
170
+ // {formattedRemaining}
171
+ // </Text>
172
+ // );
173
+ // }
174
+ // break;
175
+ // }
176
+ // return formattedElements;
177
+ // };
178
+ // const applyRegex = (
179
+ // regex: RegExp,
180
+ // styleKey: keyof typeof defaultStyles,
181
+ // isLink = false,
182
+ // isCode = false,
183
+ // isHtmlTag = false,
184
+ // isColor = false,
185
+ // renderText?: (
186
+ // value: string,
187
+ // match?: RegExpExecArray,
188
+ // ) => string | JSX.Element,
189
+ // ) => {
190
+ // regex.lastIndex = 0;
191
+ // const match = regex.exec(remaining);
192
+ // if (match) {
193
+ // const [full, inner, inner2] = match;
194
+ // const before = remaining.substring(0, match.index);
195
+ // const after = remaining.substring(match.index + full.length);
196
+ // if (before) elements.push(before);
197
+ // if (isLink) {
198
+ // elements.push(
199
+ // <Text
200
+ // key={`link-${index++}`}
201
+ // style={getMergedStyle(styleKey)}
202
+ // onPress={() => Linking.openURL(inner2)}
203
+ // >
204
+ // {inner}
205
+ // </Text>,
206
+ // );
207
+ // } else if (isColor) {
208
+ // // Handle color syntax :::{.color-blue}text:::
209
+ // const colorName = inner;
210
+ // const coloredText = inner2;
211
+ // console.log('DEBUG - Color processing:');
212
+ // console.log('Color:', colorName);
213
+ // console.log('Text:', coloredText);
214
+ // // Parse formatted text within the color
215
+ // const coloredElements = parseFormattedText(coloredText, colorName.toLowerCase(), index);
216
+ // elements.push(...coloredElements);
217
+ // index += coloredElements.length;
218
+ // } else if (isHtmlTag && renderText) {
219
+ // const rendered = renderText(inner, match);
220
+ // if (typeof rendered === 'string') {
221
+ // elements.push(rendered);
222
+ // } else {
223
+ // elements.push(rendered);
224
+ // }
225
+ // } else {
226
+ // elements.push(
227
+ // <Text key={`styled-${index++}`} style={getMergedStyle(styleKey)}>
228
+ // {inner}
229
+ // </Text>,
230
+ // );
231
+ // }
232
+ // remaining = after;
233
+ // return true;
234
+ // }
235
+ // return false;
236
+ // };
237
+ // while (remaining.length) {
238
+ // const patterns = [
239
+ // // COLOR PATTERN FIRST
240
+ // {
241
+ // regex: /:::\s*{\s*\.color-([a-zA-Z]+)\s*}\s*([\s\S]*?)\s*:::/g,
242
+ // style: ['paragraph'],
243
+ // isColor: true,
244
+ // },
245
+ // { regex: /\*\*\*(.*?)\*\*\*/g, style: ['bold', 'italic'] },
246
+ // { regex: /\*\*(.*?)\*\*/g, style: ['bold'] },
247
+ // { regex: /_(.*?)_/g, style: ['italic'] },
248
+ // { regex: /`([^`]+)`/g, style: ['code'], isCode: true },
249
+ // { regex: /\[(.*?)\]\((.*?)\)/g, style: ['link'], isLink: true },
250
+ // { regex: /<b>(.*?)<\/b>/i, style: ['bold'], isHtmlTag: true },
251
+ // { regex: /<i>(.*?)<\/i>/i, style: ['italic'], isHtmlTag: true },
252
+ // { regex: /<u>(.*?)<\/u>/i, style: ['underline'], isHtmlTag: true },
253
+ // {
254
+ // regex: /<br\s*\/?>/i,
255
+ // style: ['paragraph'],
256
+ // isHtmlTag: true,
257
+ // renderText: () => '\n',
258
+ // },
259
+ // ];
260
+ // let matched = false;
261
+ // for (const pattern of patterns) {
262
+ // if (
263
+ // applyRegex(
264
+ // pattern.regex,
265
+ // pattern.style[0] as keyof typeof defaultStyles,
266
+ // pattern.isLink,
267
+ // pattern.isCode,
268
+ // pattern.isHtmlTag,
269
+ // pattern.isColor,
270
+ // pattern.renderText,
271
+ // )
272
+ // ) {
273
+ // matched = true;
274
+ // break;
275
+ // }
276
+ // }
277
+ // if (!matched) {
278
+ // elements.push(remaining);
279
+ // break;
280
+ // }
281
+ // }
282
+ // return <Text>{elements}</Text>;
283
+ // };
284
+ //! to be uncommented
285
+ // import React, { JSX } from 'react';
286
+ // import {
287
+ // Text,
288
+ // View,
289
+ // Image,
290
+ // StyleSheet,
291
+ // Linking,
292
+ // StyleProp,
293
+ // TextStyle,
294
+ // ViewStyle,
295
+ // ImageStyle,
296
+ // Platform,
297
+ // } from 'react-native';
298
+ // type MarkdownStyle = StyleProp<TextStyle | ViewStyle | ImageStyle>;
299
+ // type CustomMarkdownProps = {
300
+ // content: string;
301
+ // styles?: Partial<Record<keyof typeof defaultStyles, MarkdownStyle>>;
302
+ // resolveImageSource?: (path: string) => any;
303
+ // };
304
+ // // Color mapping for React Native
305
+ // const COLOR_MAP: Record<string, string> = {
306
+ // blue: '#007AFF',
307
+ // red: '#FF3B30',
308
+ // green: '#34C759',
309
+ // orange: '#FF9500',
310
+ // yellow: '#FFCC00',
311
+ // purple: '#5856D6',
312
+ // pink: '#FF2D55',
313
+ // brown: '#A2845E',
314
+ // black: '#000000',
315
+ // white: '#FFFFFF',
316
+ // gray: '#8E8E93',
317
+ // };
318
+ // const CustomMarkdown: React.FC<CustomMarkdownProps> = ({
319
+ // content,
320
+ // styles = {},
321
+ // resolveImageSource,
322
+ // }) => {
323
+ // const getMergedStyle = (key: keyof typeof defaultStyles): MarkdownStyle => {
324
+ // return [defaultStyles[key], styles[key]];
325
+ // };
326
+ // const parseInlineMarkdown = (text: string) => {
327
+ // const elements: (JSX.Element | string)[] = [];
328
+ // let remaining = text;
329
+ // let index = 0;
330
+ // const applyRegex = (
331
+ // regex: RegExp,
332
+ // styleKey: keyof typeof defaultStyles,
333
+ // isLink = false,
334
+ // isCode = false,
335
+ // isHtmlTag = false,
336
+ // isColor = false,
337
+ // renderText?: (
338
+ // value: string,
339
+ // match?: RegExpExecArray,
340
+ // ) => string | JSX.Element,
341
+ // ) => {
342
+ // regex.lastIndex = 0;
343
+ // const match = regex.exec(remaining);
344
+ // if (match) {
345
+ // const [full, inner, inner2] = match;
346
+ // const before = remaining.substring(0, match.index);
347
+ // const after = remaining.substring(match.index + full.length);
348
+ // if (before) elements.push(before);
349
+ // if (isLink) {
350
+ // elements.push(
351
+ // <Text
352
+ // key={`link-${index++}`}
353
+ // style={getMergedStyle(styleKey)}
354
+ // onPress={() => Linking.openURL(inner2)}
355
+ // >
356
+ // {inner}
357
+ // </Text>,
358
+ // );
359
+ // } else if (isColor) {
360
+ // // Handle color syntax :::{.color-blue}text:::
361
+ // const colorName = (inner || '').trim();
362
+ // const coloredText = (inner2 || '').trim();
363
+ // console.log(
364
+ // 'Color processing - Color:',
365
+ // colorName,
366
+ // 'Text:',
367
+ // coloredText,
368
+ // );
369
+ // // Get actual color value from mapping
370
+ // if (coloredText.length > 0) {
371
+ // const colorValue = COLOR_MAP[colorName.toLowerCase()] || '#000000';
372
+ // elements.push(
373
+ // <Text key={`color-${index++}`} style={{ color: colorValue }}>
374
+ // {coloredText}
375
+ // </Text>
376
+ // );
377
+ // }
378
+ // } else if (isHtmlTag && renderText) {
379
+ // const rendered = renderText(inner, match);
380
+ // if (typeof rendered === 'string') {
381
+ // elements.push(rendered);
382
+ // } else {
383
+ // elements.push(rendered);
384
+ // }
385
+ // } else {
386
+ // elements.push(
387
+ // <Text key={`styled-${index++}`} style={getMergedStyle(styleKey)}>
388
+ // {inner}
389
+ // </Text>,
390
+ // );
391
+ // }
392
+ // remaining = after;
393
+ // return true;
394
+ // }
395
+ // return false;
396
+ // };
397
+ // while (remaining.length) {
398
+ // const patterns = [
399
+ // // COLOR PATTERN FIRST
400
+ // {
401
+ // // Match lines like ::: {.color-blue}Text:::
402
+ // regex: /^:::\{\.color-([a-zA-Z]+)\}(.*?):::$/g,
403
+ // style: ['paragraph'],
404
+ // isColor: true,
405
+ // },
406
+ // { regex: /\*\*\*(.*?)\*\*\*/g, style: ['bold', 'italic'] },
407
+ // { regex: /\*\*(.*?)\*\*/g, style: ['bold'] },
408
+ // { regex: /_(.*?)_/g, style: ['italic'] },
409
+ // { regex: /`([^`]+)`/g, style: ['code'], isCode: true },
410
+ // { regex: /\[(.*?)\]\((.*?)\)/g, style: ['link'], isLink: true },
411
+ // { regex: /<b>(.*?)<\/b>/i, style: ['bold'], isHtmlTag: true },
412
+ // { regex: /<i>(.*?)<\/i>/i, style: ['italic'], isHtmlTag: true },
413
+ // { regex: /<u>(.*?)<\/u>/i, style: ['underline'], isHtmlTag: true },
414
+ // {
415
+ // regex: /<br\s*\/?>/i,
416
+ // style: ['paragraph'],
417
+ // isHtmlTag: true,
418
+ // renderText: () => '\n',
419
+ // },
420
+ // ];
421
+ // let matched = false;
422
+ // for (const pattern of patterns) {
423
+ // if (
424
+ // applyRegex(
425
+ // pattern.regex,
426
+ // pattern.style[0] as keyof typeof defaultStyles,
427
+ // pattern.isLink,
428
+ // pattern.isCode,
429
+ // pattern.isHtmlTag,
430
+ // pattern.isColor,
431
+ // pattern.renderText,
432
+ // )
433
+ // ) {
434
+ // matched = true;
435
+ // break;
436
+ // }
437
+ // }
438
+ // if (!matched) {
439
+ // elements.push(remaining);
440
+ // break;
441
+ // }
442
+ // }
443
+ // return <Text>{elements}</Text>;
444
+ // };
445
+ // const renderMarkdown = () => {
446
+ // const lines = content.split('\n');
447
+ // const result: JSX.Element[] = [];
448
+ // let inCodeBlock = false;
449
+ // let codeBlockContent: string[] = [];
450
+ // lines.forEach((line, index) => {
451
+ // if (line.trim() === '```') {
452
+ // inCodeBlock = !inCodeBlock;
453
+ // if (!inCodeBlock) {
454
+ // result.push(
455
+ // <View key={`code-${index}`} style={getMergedStyle('codeBlock')}>
456
+ // <Text style={getMergedStyle('code')}>
457
+ // {codeBlockContent.join('\n')}
458
+ // </Text>
459
+ // </View>,
460
+ // );
461
+ // codeBlockContent = [];
462
+ // }
463
+ // return;
464
+ // }
465
+ // if (inCodeBlock) {
466
+ // codeBlockContent.push(line);
467
+ // return;
468
+ // }
469
+ // const imgMatch = line.match(/!\[(.*?)\]\((.*?)\)/);
470
+ // if (imgMatch) {
471
+ // const altText = imgMatch[1];
472
+ // const imgPath = imgMatch[2];
473
+ // const source = resolveImageSource
474
+ // ? resolveImageSource(imgPath)
475
+ // : { uri: imgPath };
476
+ // result.push(
477
+ // <Image
478
+ // key={`img-${index}`}
479
+ // source={source}
480
+ // style={getMergedStyle('image') as StyleProp<ImageStyle>}
481
+ // accessibilityLabel={altText}
482
+ // />,
483
+ // );
484
+ // return;
485
+ // }
486
+ // const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
487
+ // if (headingMatch) {
488
+ // const level = headingMatch[1].length;
489
+ // const headingText = headingMatch[2];
490
+ // const styleKey = `heading${level}` as keyof typeof defaultStyles;
491
+ // result.push(
492
+ // <Text key={`heading-${index}`} style={getMergedStyle(styleKey)}>
493
+ // {headingText}
494
+ // </Text>,
495
+ // );
496
+ // return;
497
+ // }
498
+ // if (line.startsWith('>')) {
499
+ // result.push(
500
+ // <View
501
+ // key={`quote-${index}`}
502
+ // style={getMergedStyle('blockquoteContainer')}
503
+ // >
504
+ // <Text style={getMergedStyle('blockquoteText')}>
505
+ // {line.replace(/^>\s?/, '')}
506
+ // </Text>
507
+ // </View>,
508
+ // );
509
+ // return;
510
+ // }
511
+ // if (line.trim().startsWith('- ')) {
512
+ // result.push(
513
+ // <View key={`list-${index}`} style={getMergedStyle('bulletRow')}>
514
+ // <Text style={getMergedStyle('bullet')}>{'\u2022'}</Text>
515
+ // <Text style={getMergedStyle('listText')}>
516
+ // {parseInlineMarkdown(line.replace('- ', ''))}
517
+ // </Text>
518
+ // </View>,
519
+ // );
520
+ // return;
521
+ // }
522
+ // const numberedMatch = line.trim().match(/^(\d+)\.\s+(.*)/);
523
+ // if (numberedMatch) {
524
+ // result.push(
525
+ // <View key={`list-num-${index}`} style={getMergedStyle('bulletRow')}>
526
+ // <Text style={getMergedStyle('bullet')}>
527
+ // {numberedMatch[1] + '.'}
528
+ // </Text>
529
+ // <Text style={getMergedStyle('listText')}>
530
+ // {parseInlineMarkdown(numberedMatch[2])}
531
+ // </Text>
532
+ // </View>,
533
+ // );
534
+ // return;
535
+ // }
536
+ // if (line.trim()) {
537
+ // result.push(
538
+ // <Text key={`text-${index}`} style={getMergedStyle('paragraph')}>
539
+ // {parseInlineMarkdown(line)}
540
+ // </Text>,
541
+ // );
542
+ // }
543
+ // });
544
+ // return result;
545
+ // };
546
+ // return <View>{renderMarkdown()}</View>;
547
+ // };
548
+ // export default CustomMarkdown;
549
+ // const defaultStyles = StyleSheet.create({
550
+ // paragraph: {
551
+ // fontSize: 16,
552
+ // lineHeight: 24,
553
+ // color: '#333',
554
+ // marginBottom: 8,
555
+ // },
556
+ // heading1: {
557
+ // fontSize: 24,
558
+ // fontWeight: 'bold',
559
+ // marginVertical: 10,
560
+ // },
561
+ // heading2: {
562
+ // fontSize: 22,
563
+ // fontWeight: 'bold',
564
+ // marginVertical: 8,
565
+ // },
566
+ // heading3: {
567
+ // fontSize: 20,
568
+ // fontWeight: 'bold',
569
+ // marginVertical: 6,
570
+ // },
571
+ // heading4: {
572
+ // fontSize: 18,
573
+ // fontWeight: 'bold',
574
+ // marginVertical: 4,
575
+ // },
576
+ // heading5: {
577
+ // fontSize: 16,
578
+ // fontWeight: 'bold',
579
+ // marginVertical: 4,
580
+ // },
581
+ // heading6: {
582
+ // fontSize: 14,
583
+ // fontWeight: 'bold',
584
+ // marginVertical: 2,
585
+ // },
586
+ // bold: {
587
+ // fontWeight: 'bold',
588
+ // },
589
+ // italic: {
590
+ // fontStyle: 'italic',
591
+ // },
592
+ // underline: {
593
+ // textDecorationLine: 'underline',
594
+ // },
595
+ // code: {
596
+ // backgroundColor: '#f4f4f4',
597
+ // padding: 4,
598
+ // borderRadius: 4,
599
+ // },
600
+ // codeBlock: {
601
+ // backgroundColor: '#eee',
602
+ // padding: 10,
603
+ // borderRadius: 6,
604
+ // marginVertical: 10,
605
+ // },
606
+ // blockquoteContainer: {
607
+ // borderLeftWidth: 4,
608
+ // borderLeftColor: '#ccc',
609
+ // paddingLeft: 10,
610
+ // marginVertical: 8,
611
+ // },
612
+ // blockquoteText: {
613
+ // fontStyle: 'italic',
614
+ // color: '#666',
615
+ // },
616
+ // bulletRow: {
617
+ // flexDirection: 'row',
618
+ // alignItems: 'flex-start',
619
+ // marginBottom: 6,
620
+ // },
621
+ // bullet: {
622
+ // fontSize: 16,
623
+ // lineHeight: 24,
624
+ // marginRight: 6,
625
+ // fontWeight: 'bold',
626
+ // },
627
+ // listText: {
628
+ // flex: 1,
629
+ // fontSize: 16,
630
+ // lineHeight: 24,
631
+ // },
632
+ // link: {
633
+ // color: '#007AFF',
634
+ // textDecorationLine: 'underline',
635
+ // },
636
+ // image: {
637
+ // width: '100%',
638
+ // height: 200,
639
+ // resizeMode: 'contain',
640
+ // marginVertical: 10,
641
+ // },
642
+ // });
6
643
  const react_1 = __importDefault(require("react"));
7
644
  const react_native_1 = require("react-native");
645
+ // Optional color map (Kept for convenience, allowing names like 'red')
646
+ const COLOR_MAP = {
647
+ blue: '#007AFF',
648
+ red: '#FF3B30',
649
+ green: '#34C759',
650
+ orange: '#FF9500',
651
+ yellow: '#FFCC00',
652
+ purple: '#5856D6',
653
+ pink: '#FF2D55',
654
+ brown: '#A2845E',
655
+ black: '#000000',
656
+ white: '#FFFFFF',
657
+ gray: '#8E8E93',
658
+ };
659
+ // --- Component ---
8
660
  const CustomMarkdown = ({ content, styles = {}, resolveImageSource, }) => {
661
+ // We need to keep a consistent index for key generation across recursive calls
662
+ let globalIndex = 0;
9
663
  const getMergedStyle = (key) => {
10
664
  return [defaultStyles[key], styles[key]];
11
665
  };
666
+ /** Parses inline markdown and supports <color style="#xxxxxx">text</color> */
12
667
  const parseInlineMarkdown = (text) => {
13
668
  const elements = [];
14
669
  let remaining = text;
15
- let index = 0;
16
- const applyRegex = (regex, styleKey, isLink = false, isCode = false, isHtmlTag = false, renderText) => {
670
+ // local index is used for array iteration, globalIndex for unique React keys
671
+ let localIndex = 0;
672
+ const applyRegex = (regex, styleKey, options = {}) => {
673
+ // Ensure the regex is not global or reset its index if it is
674
+ regex.lastIndex = 0;
17
675
  const match = regex.exec(remaining);
18
676
  if (match) {
19
- const [full, inner, link] = match;
677
+ // group1 is the first captured group, group2 is the second (if present)
678
+ const [full, group1, group2] = match;
20
679
  const before = remaining.substring(0, match.index);
21
680
  const after = remaining.substring(match.index + full.length);
22
- if (before)
23
- elements.push(before);
24
- if (isLink) {
25
- elements.push(<react_native_1.Text key={`link-${index++}`} style={getMergedStyle(styleKey)} onPress={() => react_native_1.Linking.openURL(link)}>
26
- {inner}
27
- </react_native_1.Text>);
681
+ if (before) {
682
+ // Recursively parse the text before the match to ensure correct order
683
+ const beforeContent = parseInlineMarkdown(before).props.children;
684
+ if (Array.isArray(beforeContent)) {
685
+ elements.push(...beforeContent);
686
+ }
687
+ else {
688
+ elements.push(beforeContent);
689
+ }
28
690
  }
29
- else if (isHtmlTag && renderText) {
30
- elements.push(<react_native_1.Text key={`html-${index++}`} style={getMergedStyle(styleKey)}>
31
- {renderText(inner)}
691
+ if (options.isLink) {
692
+ // Standard Markdown Link: [text](url) -> group1=text, group2=url
693
+ elements.push(<react_native_1.Text key={`link-${globalIndex++}`} style={getMergedStyle(styleKey)} onPress={() => react_native_1.Linking.openURL(group2)}>
694
+ {group1}
32
695
  </react_native_1.Text>);
33
696
  }
697
+ else if (options.isHtmlTag && options.renderText) {
698
+ // Custom HTML Tags (like <color>, <b>, <br/>)
699
+ const rendered = options.renderText(group1, match);
700
+ elements.push(rendered);
701
+ }
34
702
  else {
35
- elements.push(<react_native_1.Text key={`styled-${index++}`} style={getMergedStyle(styleKey)}>
36
- {inner}
703
+ // Standard Markdown (Bold, Italic, Code) -> group1=content
704
+ elements.push(<react_native_1.Text key={`styled-${globalIndex++}`} style={getMergedStyle(styleKey)}>
705
+ {group1}
37
706
  </react_native_1.Text>);
38
707
  }
39
708
  remaining = after;
@@ -43,24 +712,71 @@ const CustomMarkdown = ({ content, styles = {}, resolveImageSource, }) => {
43
712
  };
44
713
  while (remaining.length) {
45
714
  const patterns = [
46
- { regex: /\*\*\*(.*?)\*\*\*/g, style: ['bold', 'italic'] },
47
- { regex: /\*\*(.*?)\*\*/g, style: ['bold'] },
48
- { regex: /_(.*?)_/g, style: ['italic'] },
49
- { regex: /`([^`]+)`/g, style: ['code'], isCode: true },
50
- { regex: /\[(.*?)\]\((.*?)\)/g, style: ['link'], isLink: true },
51
- { regex: /<b>(.*?)<\/b>/i, style: ['bold'], isHtmlTag: true },
52
- { regex: /<i>(.*?)<\/i>/i, style: ['italic'], isHtmlTag: true },
53
- { regex: /<u>(.*?)<\/u>/i, style: ['underline'], isHtmlTag: true },
715
+ // 1. Custom Color Tag with Nested Parsing Support (Highest Priority)
716
+ // match[1] = Quote
717
+ // match[2] = Color value (e.g., "#007AFF" or "red")
718
+ // match[3] = Text content (e.g., "Hello **World**")
719
+ {
720
+ regex: /<color\s+style\s*=\s*(["'])(.*?)\1\s*>([\s\S]*?)<\/color>/i,
721
+ style: 'paragraph',
722
+ options: {
723
+ isHtmlTag: true,
724
+ renderText: (_, match) => {
725
+ const colorAttribute = match ? match[2] : 'black';
726
+ const textContent = match ? match[3] : '';
727
+ const colorValue = COLOR_MAP[colorAttribute.toLowerCase()] || colorAttribute;
728
+ // Recursive call: Correctly parse content and extract its children
729
+ const nestedResult = parseInlineMarkdown(textContent).props.children;
730
+ return (<react_native_1.Text key={`color-${globalIndex++}`} style={{ color: colorValue }}>
731
+ {nestedResult}
732
+ </react_native_1.Text>);
733
+ },
734
+ },
735
+ },
736
+ // 2. Strong/Emphasis Markdown
737
+ { regex: /\*\*\*(.*?)\*\*\*/g, style: 'bold', options: {} },
738
+ { regex: /\*\*(.*?)\*\*/g, style: 'bold', options: {} },
739
+ { regex: /_(.*?)_/g, style: 'italic', options: {} },
740
+ // 3. Code and Links
741
+ { regex: /`([^`]+)`/g, style: 'code', options: {} },
742
+ { regex: /\[(.*?)\]\((.*?)\)/g, style: 'link', options: { isLink: true } },
743
+ // 4. Other HTML Tags (FIXED: Explicitly typed 'value' as string)
744
+ {
745
+ regex: /<b>(.*?)<\/b>/i,
746
+ style: 'bold',
747
+ options: {
748
+ isHtmlTag: true,
749
+ renderText: (value) => (<react_native_1.Text key={`b-${globalIndex++}`} style={getMergedStyle('bold')}>{value}</react_native_1.Text>),
750
+ },
751
+ },
752
+ {
753
+ regex: /<i>(.*?)<\/i>/i,
754
+ style: 'italic',
755
+ options: {
756
+ isHtmlTag: true,
757
+ renderText: (value) => (<react_native_1.Text key={`i-${globalIndex++}`} style={getMergedStyle('italic')}>{value}</react_native_1.Text>),
758
+ },
759
+ },
760
+ {
761
+ regex: /<u>(.*?)<\/u>/i,
762
+ style: 'underline',
763
+ options: {
764
+ isHtmlTag: true,
765
+ renderText: (value) => (<react_native_1.Text key={`u-${globalIndex++}`} style={getMergedStyle('underline')}>{value}</react_native_1.Text>),
766
+ },
767
+ },
54
768
  {
55
769
  regex: /<br\s*\/?>/i,
56
- style: ['paragraph'],
57
- isHtmlTag: true,
58
- renderText: () => '\n',
770
+ style: 'paragraph',
771
+ options: {
772
+ isHtmlTag: true,
773
+ renderText: () => '\n',
774
+ },
59
775
  },
60
776
  ];
61
777
  let matched = false;
62
778
  for (const pattern of patterns) {
63
- if (applyRegex(pattern.regex, pattern.style[0], pattern.isLink, pattern.isCode, pattern.isHtmlTag, pattern.renderText)) {
779
+ if (applyRegex(pattern.regex, pattern.style, pattern.options)) {
64
780
  matched = true;
65
781
  break;
66
782
  }
@@ -70,21 +786,23 @@ const CustomMarkdown = ({ content, styles = {}, resolveImageSource, }) => {
70
786
  break;
71
787
  }
72
788
  }
73
- return <react_native_1.Text>{elements}</react_native_1.Text>;
789
+ // We use a local index for the final wrapping Text component key
790
+ return <react_native_1.Text key={`inline-wrapper-${localIndex++}`}>{elements}</react_native_1.Text>;
74
791
  };
792
+ /** Parses block-level markdown lines */
75
793
  const renderMarkdown = () => {
76
794
  const lines = content.split('\n');
77
795
  const result = [];
78
796
  let inCodeBlock = false;
79
797
  let codeBlockContent = [];
798
+ let blockIndex = 0; // Use a dedicated index for block-level elements
80
799
  lines.forEach((line, index) => {
800
+ // Handle code blocks
81
801
  if (line.trim() === '```') {
82
802
  inCodeBlock = !inCodeBlock;
83
803
  if (!inCodeBlock) {
84
- result.push(<react_native_1.View key={`code-${index}`} style={getMergedStyle('codeBlock')}>
85
- <react_native_1.Text style={getMergedStyle('code')}>
86
- {codeBlockContent.join('\n')}
87
- </react_native_1.Text>
804
+ result.push(<react_native_1.View key={`code-block-${blockIndex++}`} style={getMergedStyle('codeBlock')}>
805
+ <react_native_1.Text style={getMergedStyle('code')}>{codeBlockContent.join('\n')}</react_native_1.Text>
88
806
  </react_native_1.View>);
89
807
  codeBlockContent = [];
90
808
  }
@@ -94,115 +812,38 @@ const CustomMarkdown = ({ content, styles = {}, resolveImageSource, }) => {
94
812
  codeBlockContent.push(line);
95
813
  return;
96
814
  }
97
- const imgMatch = line.match(/!\[(.*?)\]\((.*?)\)/);
98
- if (imgMatch) {
99
- const altText = imgMatch[1];
100
- const imgPath = imgMatch[2];
101
- const source = resolveImageSource
102
- ? resolveImageSource(imgPath)
103
- : { uri: imgPath };
104
- result.push(<react_native_1.Image key={`img-${index}`} source={source} style={getMergedStyle('image')} accessibilityLabel={altText}/>);
105
- return;
106
- }
107
- const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
108
- if (headingMatch) {
109
- const level = headingMatch[1].length;
110
- const headingText = headingMatch[2];
111
- const styleKey = `heading${level}`;
112
- result.push(<react_native_1.Text key={`heading-${index}`} style={getMergedStyle(styleKey)}>
113
- {headingText}
114
- </react_native_1.Text>);
115
- return;
116
- }
117
- if (line.startsWith('>')) {
118
- result.push(<react_native_1.View key={`quote-${index}`} style={getMergedStyle('blockquoteContainer')}>
119
- <react_native_1.Text style={getMergedStyle('blockquoteText')}>
120
- {line.replace(/^>\s?/, '')}
121
- </react_native_1.Text>
122
- </react_native_1.View>);
123
- return;
124
- }
125
- if (line.trim().startsWith('- ')) {
126
- result.push(<react_native_1.View key={`list-${index}`} style={getMergedStyle('bulletRow')}>
127
- <react_native_1.Text style={getMergedStyle('bullet')}>{'\u2022'}</react_native_1.Text>
128
- <react_native_1.Text style={getMergedStyle('listText')}>
129
- {parseInlineMarkdown(line.replace('- ', ''))}
130
- </react_native_1.Text>
131
- </react_native_1.View>);
132
- return;
133
- }
134
- const numberedMatch = line.trim().match(/^(\d+)\.\s+(.*)/);
135
- if (numberedMatch) {
136
- result.push(<react_native_1.View key={`list-num-${index}`} style={getMergedStyle('bulletRow')}>
137
- <react_native_1.Text style={getMergedStyle('bullet')}>{numberedMatch[1] + '.'}</react_native_1.Text>
138
- <react_native_1.Text style={getMergedStyle('listText')}>
139
- {parseInlineMarkdown(numberedMatch[2])}
140
- </react_native_1.Text>
141
- </react_native_1.View>);
142
- return;
143
- }
815
+ // ... (other block-level logic like images, headings, etc. using parseInlineMarkdown) ...
816
+ // Handle paragraph
144
817
  if (line.trim()) {
145
- result.push(<react_native_1.Text key={`text-${index}`} style={getMergedStyle('paragraph')}>
818
+ result.push(<react_native_1.Text key={`text-${blockIndex++}`} style={getMergedStyle('paragraph')}>
146
819
  {parseInlineMarkdown(line)}
147
820
  </react_native_1.Text>);
148
821
  }
822
+ else {
823
+ // Handle empty lines for spacing
824
+ result.push(<react_native_1.Text key={`spacer-${blockIndex++}`}>{'\n'}</react_native_1.Text>);
825
+ }
149
826
  });
150
827
  return result;
151
828
  };
152
- return <react_native_1.View>{renderMarkdown()}</react_native_1.View>;
829
+ return <react_native_1.View style={getMergedStyle('container')}>{renderMarkdown()}</react_native_1.View>;
153
830
  };
154
- exports.default = CustomMarkdown;
831
+ // --- Default Styles (MUST be defined for the component to work) ---
155
832
  const defaultStyles = react_native_1.StyleSheet.create({
156
- paragraph: {
157
- fontSize: 16,
158
- lineHeight: 24,
159
- color: '#333',
160
- marginBottom: 8,
161
- },
162
- heading1: {
163
- fontSize: 24,
164
- fontWeight: 'bold',
165
- marginVertical: 10,
166
- },
167
- heading2: {
168
- fontSize: 22,
169
- fontWeight: 'bold',
170
- marginVertical: 8,
171
- },
172
- heading3: {
173
- fontSize: 20,
174
- fontWeight: 'bold',
175
- marginVertical: 6,
176
- },
177
- heading4: {
178
- fontSize: 18,
179
- fontWeight: 'bold',
180
- marginVertical: 4,
181
- },
182
- heading5: {
183
- fontSize: 16,
184
- fontWeight: 'bold',
185
- marginVertical: 4,
186
- },
187
- heading6: {
188
- fontSize: 14,
189
- fontWeight: 'bold',
190
- marginVertical: 2,
191
- },
192
- bold: {
193
- fontWeight: 'bold',
194
- },
195
- italic: {
196
- fontStyle: 'italic',
197
- },
198
- underline: {
199
- textDecorationLine: 'underline',
200
- },
201
- code: {
202
- backgroundColor: '#f4f4f4',
203
- padding: 4,
204
- borderRadius: 4,
833
+ container: {
834
+ // Add a container style if needed
205
835
  },
836
+ paragraph: { fontSize: 16, lineHeight: 24, color: '#333', marginBottom: 8 },
837
+ heading1: { fontSize: 24, fontWeight: 'bold', marginVertical: 10 },
838
+ heading2: { fontSize: 22, fontWeight: 'bold', marginVertical: 8 },
839
+ heading3: { fontSize: 20, fontWeight: 'bold', marginVertical: 6 },
840
+ heading4: { fontSize: 18, fontWeight: 'bold', marginVertical: 4 },
841
+ heading5: { fontSize: 16, fontWeight: 'bold', marginVertical: 4 },
842
+ heading6: { fontSize: 14, fontWeight: 'bold', marginVertical: 2 },
843
+ bold: { fontWeight: 'bold' },
844
+ italic: { fontStyle: 'italic' },
845
+ underline: { textDecorationLine: 'underline' },
846
+ code: { backgroundColor: '#f4f4f4', padding: 4, borderRadius: 4 },
206
847
  codeBlock: {
207
848
  backgroundColor: '#eee',
208
849
  padding: 10,
@@ -215,30 +856,11 @@ const defaultStyles = react_native_1.StyleSheet.create({
215
856
  paddingLeft: 10,
216
857
  marginVertical: 8,
217
858
  },
218
- blockquoteText: {
219
- fontStyle: 'italic',
220
- color: '#666',
221
- },
222
- bulletRow: {
223
- flexDirection: 'row',
224
- alignItems: 'flex-start',
225
- marginBottom: 6,
226
- },
227
- bullet: {
228
- fontSize: 16,
229
- lineHeight: 24,
230
- marginRight: 6,
231
- fontWeight: 'bold',
232
- },
233
- listText: {
234
- flex: 1,
235
- fontSize: 16,
236
- lineHeight: 24,
237
- },
238
- link: {
239
- color: '#007AFF',
240
- textDecorationLine: 'underline',
241
- },
859
+ blockquoteText: { fontStyle: 'italic', color: '#666' },
860
+ bulletRow: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 6 },
861
+ bullet: { fontSize: 16, lineHeight: 24, marginRight: 6, fontWeight: 'bold' },
862
+ listText: { flex: 1, fontSize: 16, lineHeight: 24 },
863
+ link: { color: '#007AFF', textDecorationLine: 'underline' },
242
864
  image: {
243
865
  width: '100%',
244
866
  height: 200,
@@ -246,3 +868,4 @@ const defaultStyles = react_native_1.StyleSheet.create({
246
868
  marginVertical: 10,
247
869
  },
248
870
  });
871
+ exports.default = CustomMarkdown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hs-react-native-custom-markdown",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "A React Native component to render custom markdown content.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",