react-native-nitro-markdown 0.5.3 → 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/README.md +89 -10
- package/android/CMakeLists.txt +1 -1
- package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +9 -3
- package/cpp/CMakeLists.txt +2 -1
- package/cpp/bindings/HybridMarkdownParser.cpp +4 -2
- package/cpp/core/MD4CParser.cpp +69 -2
- package/cpp/core/MD4CParser.hpp +17 -0
- package/cpp/core/MarkdownTypes.hpp +1 -1
- package/lib/commonjs/headless.js +2 -2
- package/lib/commonjs/markdown.js +56 -44
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/blockquote.js +15 -13
- package/lib/commonjs/renderers/blockquote.js.map +1 -1
- package/lib/commonjs/renderers/code.js +57 -53
- package/lib/commonjs/renderers/code.js.map +1 -1
- package/lib/commonjs/renderers/heading.js +48 -46
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/horizontal-rule.js +10 -8
- package/lib/commonjs/renderers/horizontal-rule.js.map +1 -1
- package/lib/commonjs/renderers/image.js +12 -3
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/list.js +75 -70
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/math.js +4 -3
- package/lib/commonjs/renderers/math.js.map +1 -1
- package/lib/commonjs/renderers/paragraph.js +15 -13
- package/lib/commonjs/renderers/paragraph.js.map +1 -1
- package/lib/commonjs/renderers/style-cache.js +14 -0
- package/lib/commonjs/renderers/style-cache.js.map +1 -0
- package/lib/commonjs/renderers/table/index.js +7 -4
- package/lib/commonjs/renderers/table/index.js.map +1 -1
- package/lib/module/headless.js +2 -2
- package/lib/module/markdown.js +56 -44
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/blockquote.js +15 -13
- package/lib/module/renderers/blockquote.js.map +1 -1
- package/lib/module/renderers/code.js +57 -53
- package/lib/module/renderers/code.js.map +1 -1
- package/lib/module/renderers/heading.js +48 -46
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/horizontal-rule.js +10 -8
- package/lib/module/renderers/horizontal-rule.js.map +1 -1
- package/lib/module/renderers/image.js +13 -4
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/list.js +75 -70
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/math.js +4 -3
- package/lib/module/renderers/math.js.map +1 -1
- package/lib/module/renderers/paragraph.js +15 -13
- package/lib/module/renderers/paragraph.js.map +1 -1
- package/lib/module/renderers/style-cache.js +10 -0
- package/lib/module/renderers/style-cache.js.map +1 -0
- package/lib/module/renderers/table/index.js +7 -4
- package/lib/module/renderers/table/index.js.map +1 -1
- package/lib/typescript/commonjs/Markdown.nitro.d.ts +1 -0
- package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/headless.d.ts +2 -2
- package/lib/typescript/commonjs/markdown.d.ts +7 -1
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/blockquote.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/paragraph.d.ts +1 -1
- package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/style-cache.d.ts +3 -0
- package/lib/typescript/commonjs/renderers/style-cache.d.ts.map +1 -0
- package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme.d.ts +2 -2
- package/lib/typescript/commonjs/theme.d.ts.map +1 -1
- package/lib/typescript/module/Markdown.nitro.d.ts +1 -0
- package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
- package/lib/typescript/module/headless.d.ts +2 -2
- package/lib/typescript/module/markdown.d.ts +7 -1
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/blockquote.d.ts +1 -1
- package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -1
- package/lib/typescript/module/renderers/code.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.d.ts +1 -1
- package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/module/renderers/horizontal-rule.d.ts +1 -1
- package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -1
- package/lib/typescript/module/renderers/image.d.ts.map +1 -1
- package/lib/typescript/module/renderers/list.d.ts +1 -1
- package/lib/typescript/module/renderers/list.d.ts.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts +1 -1
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/renderers/paragraph.d.ts +1 -1
- package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
- package/lib/typescript/module/renderers/style-cache.d.ts +3 -0
- package/lib/typescript/module/renderers/style-cache.d.ts.map +1 -0
- package/lib/typescript/module/renderers/table/index.d.ts.map +1 -1
- package/lib/typescript/module/theme.d.ts +2 -2
- package/lib/typescript/module/theme.d.ts.map +1 -1
- package/nitro.json +12 -3
- package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +2 -2
- package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +2 -2
- package/nitrogen/generated/ios/NitroMarkdown+autolinking.rb +2 -0
- package/nitrogen/generated/shared/c++/ParserOptions.hpp +6 -2
- package/package.json +6 -5
- package/react-native-nitro-markdown.podspec +3 -0
- package/src/Markdown.nitro.ts +1 -0
- package/src/headless.ts +2 -2
- package/src/markdown.tsx +102 -58
- package/src/renderers/blockquote.tsx +22 -17
- package/src/renderers/code.tsx +75 -63
- package/src/renderers/heading.tsx +60 -54
- package/src/renderers/horizontal-rule.tsx +17 -12
- package/src/renderers/image.tsx +15 -4
- package/src/renderers/list.tsx +93 -76
- package/src/renderers/math.tsx +8 -3
- package/src/renderers/paragraph.tsx +22 -17
- package/src/renderers/style-cache.ts +14 -0
- package/src/renderers/table/index.tsx +15 -10
- package/src/theme.ts +2 -2
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,9 +100,15 @@ function safeOnError<P extends string>(
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
const baseStylesCache = new WeakMap<MarkdownTheme, BaseStyles>();
|
|
104
|
-
|
|
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;
|
|
111
|
+
const EMPTY_RENDERERS: CustomRenderers = {};
|
|
107
112
|
|
|
108
113
|
export type AstTransform = (ast: MarkdownNode) => MarkdownNode;
|
|
109
114
|
export type MarkdownVirtualizationOptions = Pick<
|
|
@@ -166,12 +171,14 @@ const cloneMarkdownNode = (node: MarkdownNode): MarkdownNode => {
|
|
|
166
171
|
};
|
|
167
172
|
|
|
168
173
|
const getParserOptionsKey = (options?: ParserOptions): string => {
|
|
169
|
-
if (!options) return "gfm:default|math:default";
|
|
174
|
+
if (!options) return "gfm:default|math:default|html:default";
|
|
170
175
|
|
|
171
176
|
const gfm = options.gfm === undefined ? "default" : options.gfm ? "1" : "0";
|
|
172
177
|
const math =
|
|
173
178
|
options.math === undefined ? "default" : options.math ? "1" : "0";
|
|
174
|
-
|
|
179
|
+
const html =
|
|
180
|
+
options.html === undefined ? "default" : options.html ? "1" : "0";
|
|
181
|
+
return `gfm:${gfm}|math:${math}|html:${html}`;
|
|
175
182
|
};
|
|
176
183
|
|
|
177
184
|
const normalizeParserOptions = (
|
|
@@ -181,14 +188,16 @@ const normalizeParserOptions = (
|
|
|
181
188
|
|
|
182
189
|
const gfm = options.gfm;
|
|
183
190
|
const math = options.math;
|
|
191
|
+
const html = options.html;
|
|
184
192
|
|
|
185
|
-
if (gfm === undefined && math === undefined) {
|
|
193
|
+
if (gfm === undefined && math === undefined && html === undefined) {
|
|
186
194
|
return undefined;
|
|
187
195
|
}
|
|
188
196
|
|
|
189
197
|
return {
|
|
190
198
|
gfm,
|
|
191
199
|
math,
|
|
200
|
+
html,
|
|
192
201
|
};
|
|
193
202
|
};
|
|
194
203
|
|
|
@@ -211,15 +220,18 @@ const getCachedParsedAst = (
|
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
const cacheKey = `${getParserOptionsKey(options)}|${text.length}|${hashString(text)}`;
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
223
|
+
const cachedEntry = parseAstCache.get(cacheKey);
|
|
224
|
+
if (cachedEntry?.text === text) {
|
|
216
225
|
parseAstCache.delete(cacheKey);
|
|
217
|
-
parseAstCache.set(cacheKey,
|
|
218
|
-
return cloneMarkdownNode(
|
|
226
|
+
parseAstCache.set(cacheKey, cachedEntry);
|
|
227
|
+
return cloneMarkdownNode(cachedEntry.ast);
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
const parsedNode = parseWithNativeParser(text, options);
|
|
222
|
-
parseAstCache.set(cacheKey,
|
|
231
|
+
parseAstCache.set(cacheKey, {
|
|
232
|
+
text,
|
|
233
|
+
ast: parsedNode,
|
|
234
|
+
});
|
|
223
235
|
if (parseAstCache.size > MAX_PARSE_CACHE_ENTRIES) {
|
|
224
236
|
const oldestCacheKey = parseAstCache.keys().next().value;
|
|
225
237
|
if (typeof oldestCacheKey === "string") {
|
|
@@ -230,20 +242,27 @@ const getCachedParsedAst = (
|
|
|
230
242
|
return cloneMarkdownNode(parsedNode);
|
|
231
243
|
};
|
|
232
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
|
+
|
|
233
255
|
const applyBeforeParsePlugins = (
|
|
234
256
|
markdown: string,
|
|
235
|
-
|
|
257
|
+
sortedPlugins?: MarkdownPlugin[],
|
|
236
258
|
onError?: (error: Error, phase: "before-plugin", pluginName?: string) => void,
|
|
237
259
|
): string => {
|
|
238
|
-
if (!
|
|
260
|
+
if (!sortedPlugins || sortedPlugins.length === 0) {
|
|
239
261
|
return markdown;
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
const sorted = [...plugins].sort(
|
|
243
|
-
(a, b) => (b.priority ?? 0) - (a.priority ?? 0),
|
|
244
|
-
);
|
|
245
264
|
let nextMarkdown = markdown;
|
|
246
|
-
for (const plugin of
|
|
265
|
+
for (const plugin of sortedPlugins) {
|
|
247
266
|
if (!plugin.beforeParse) continue;
|
|
248
267
|
|
|
249
268
|
try {
|
|
@@ -266,18 +285,15 @@ const applyBeforeParsePlugins = (
|
|
|
266
285
|
|
|
267
286
|
const applyAfterParsePlugins = (
|
|
268
287
|
ast: MarkdownNode,
|
|
269
|
-
|
|
288
|
+
sortedPlugins?: MarkdownPlugin[],
|
|
270
289
|
onError?: (error: Error, phase: "after-plugin", pluginName?: string) => void,
|
|
271
290
|
): MarkdownNode => {
|
|
272
|
-
if (!
|
|
291
|
+
if (!sortedPlugins || sortedPlugins.length === 0) {
|
|
273
292
|
return ast;
|
|
274
293
|
}
|
|
275
294
|
|
|
276
|
-
const sorted = [...plugins].sort(
|
|
277
|
-
(a, b) => (b.priority ?? 0) - (a.priority ?? 0),
|
|
278
|
-
);
|
|
279
295
|
let nextAst = ast;
|
|
280
|
-
for (const plugin of
|
|
296
|
+
for (const plugin of sortedPlugins) {
|
|
281
297
|
if (!plugin.afterParse) continue;
|
|
282
298
|
|
|
283
299
|
try {
|
|
@@ -304,7 +320,7 @@ export type MarkdownProps = {
|
|
|
304
320
|
*/
|
|
305
321
|
children: string;
|
|
306
322
|
/**
|
|
307
|
-
* Parser options to enable GFM or
|
|
323
|
+
* Parser options to enable GFM, math, or raw HTML AST support.
|
|
308
324
|
*/
|
|
309
325
|
options?: ParserOptions;
|
|
310
326
|
/**
|
|
@@ -316,6 +332,12 @@ export type MarkdownProps = {
|
|
|
316
332
|
* When provided, native parse is skipped and this tree is rendered instead.
|
|
317
333
|
*/
|
|
318
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;
|
|
319
341
|
/**
|
|
320
342
|
* Optional transform applied after parsing and before rendering.
|
|
321
343
|
* The transformed AST is also returned in `onParseComplete`.
|
|
@@ -420,8 +442,9 @@ export const Markdown: FC<MarkdownProps> = ({
|
|
|
420
442
|
options,
|
|
421
443
|
plugins,
|
|
422
444
|
sourceAst,
|
|
445
|
+
parseCache = true,
|
|
423
446
|
astTransform,
|
|
424
|
-
renderers =
|
|
447
|
+
renderers = EMPTY_RENDERERS,
|
|
425
448
|
theme: userTheme,
|
|
426
449
|
styles: nodeStyles,
|
|
427
450
|
stylingStrategy = "opinionated",
|
|
@@ -438,31 +461,31 @@ export const Markdown: FC<MarkdownProps> = ({
|
|
|
438
461
|
}) => {
|
|
439
462
|
const parserOptionGfm = options?.gfm;
|
|
440
463
|
const parserOptionMath = options?.math;
|
|
464
|
+
const parserOptionHtml = options?.html;
|
|
441
465
|
|
|
442
466
|
/* eslint-disable react-hooks/refs -- Refs updated/read intentionally to avoid re-parsing on callback identity changes */
|
|
443
467
|
const onErrorRef = useRef(onError);
|
|
444
468
|
onErrorRef.current = onError;
|
|
445
469
|
|
|
446
|
-
const pluginsRef = useRef(plugins);
|
|
447
|
-
pluginsRef.current = plugins;
|
|
448
|
-
|
|
449
470
|
const parseResult = useMemo(() => {
|
|
450
471
|
try {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
onErrorRef.current
|
|
455
|
-
);
|
|
472
|
+
const sortedPlugins = sortPluginsByPriority(plugins);
|
|
473
|
+
const markdownToParse = sourceAst
|
|
474
|
+
? children
|
|
475
|
+
: applyBeforeParsePlugins(children, sortedPlugins, onErrorRef.current);
|
|
456
476
|
const parserOptions = normalizeParserOptions({
|
|
457
477
|
gfm: parserOptionGfm,
|
|
458
478
|
math: parserOptionMath,
|
|
479
|
+
html: parserOptionHtml,
|
|
459
480
|
});
|
|
460
481
|
let parsedAst = sourceAst
|
|
461
482
|
? cloneMarkdownNode(sourceAst)
|
|
462
|
-
:
|
|
483
|
+
: parseCache
|
|
484
|
+
? getCachedParsedAst(markdownToParse, parserOptions)
|
|
485
|
+
: parseWithNativeParser(markdownToParse, parserOptions);
|
|
463
486
|
parsedAst = applyAfterParsePlugins(
|
|
464
487
|
parsedAst,
|
|
465
|
-
|
|
488
|
+
sortedPlugins,
|
|
466
489
|
onErrorRef.current,
|
|
467
490
|
);
|
|
468
491
|
|
|
@@ -491,12 +514,27 @@ export const Markdown: FC<MarkdownProps> = ({
|
|
|
491
514
|
ast: null,
|
|
492
515
|
};
|
|
493
516
|
}
|
|
494
|
-
}, [
|
|
517
|
+
}, [
|
|
518
|
+
children,
|
|
519
|
+
parserOptionGfm,
|
|
520
|
+
parserOptionMath,
|
|
521
|
+
parserOptionHtml,
|
|
522
|
+
sourceAst,
|
|
523
|
+
parseCache,
|
|
524
|
+
astTransform,
|
|
525
|
+
plugins,
|
|
526
|
+
]);
|
|
495
527
|
/* eslint-enable react-hooks/refs */
|
|
496
528
|
|
|
497
529
|
useEffect(() => {
|
|
498
530
|
onParsingInProgress?.();
|
|
499
|
-
}, [
|
|
531
|
+
}, [
|
|
532
|
+
children,
|
|
533
|
+
parserOptionGfm,
|
|
534
|
+
parserOptionMath,
|
|
535
|
+
parserOptionHtml,
|
|
536
|
+
onParsingInProgress,
|
|
537
|
+
]);
|
|
500
538
|
|
|
501
539
|
useEffect(() => {
|
|
502
540
|
if (!parseResult.ast || !onParseComplete) return;
|
|
@@ -749,28 +787,24 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
749
787
|
}
|
|
750
788
|
}
|
|
751
789
|
|
|
752
|
-
const nodeStyleOverride = nodeStyles?.[node.type] as
|
|
753
|
-
| (ViewStyle & TextStyle)
|
|
754
|
-
| undefined;
|
|
755
|
-
|
|
756
790
|
switch (node.type) {
|
|
757
791
|
case "document":
|
|
758
792
|
return (
|
|
759
|
-
<View style={[baseStyles.document,
|
|
793
|
+
<View style={[baseStyles.document, nodeStyles?.document]}>
|
|
760
794
|
{renderChildren(node.children, false, false)}
|
|
761
795
|
</View>
|
|
762
796
|
);
|
|
763
797
|
|
|
764
798
|
case "heading":
|
|
765
799
|
return (
|
|
766
|
-
<Heading level={node.level ?? 1} style={
|
|
800
|
+
<Heading level={node.level ?? 1} style={nodeStyles?.heading}>
|
|
767
801
|
{renderChildren(node.children, inListItem, true)}
|
|
768
802
|
</Heading>
|
|
769
803
|
);
|
|
770
804
|
|
|
771
805
|
case "paragraph":
|
|
772
806
|
return (
|
|
773
|
-
<Paragraph inListItem={inListItem} style={
|
|
807
|
+
<Paragraph inListItem={inListItem} style={nodeStyles?.paragraph}>
|
|
774
808
|
{renderChildren(node.children, inListItem, false)}
|
|
775
809
|
</Paragraph>
|
|
776
810
|
);
|
|
@@ -780,33 +814,33 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
780
814
|
return <Text>{node.content}</Text>;
|
|
781
815
|
}
|
|
782
816
|
return (
|
|
783
|
-
<Text style={[baseStyles.text,
|
|
817
|
+
<Text style={[baseStyles.text, nodeStyles?.text]}>{node.content}</Text>
|
|
784
818
|
);
|
|
785
819
|
|
|
786
820
|
case "bold":
|
|
787
821
|
return (
|
|
788
|
-
<Text style={[baseStyles.bold,
|
|
822
|
+
<Text style={[baseStyles.bold, nodeStyles?.bold]}>
|
|
789
823
|
{renderChildren(node.children, inListItem, true)}
|
|
790
824
|
</Text>
|
|
791
825
|
);
|
|
792
826
|
|
|
793
827
|
case "italic":
|
|
794
828
|
return (
|
|
795
|
-
<Text style={[baseStyles.italic,
|
|
829
|
+
<Text style={[baseStyles.italic, nodeStyles?.italic]}>
|
|
796
830
|
{renderChildren(node.children, inListItem, true)}
|
|
797
831
|
</Text>
|
|
798
832
|
);
|
|
799
833
|
|
|
800
834
|
case "strikethrough":
|
|
801
835
|
return (
|
|
802
|
-
<Text style={[baseStyles.strikethrough,
|
|
836
|
+
<Text style={[baseStyles.strikethrough, nodeStyles?.strikethrough]}>
|
|
803
837
|
{renderChildren(node.children, inListItem, true)}
|
|
804
838
|
</Text>
|
|
805
839
|
);
|
|
806
840
|
|
|
807
841
|
case "link":
|
|
808
842
|
return (
|
|
809
|
-
<Link href={node.href ?? ""} style={
|
|
843
|
+
<Link href={node.href ?? ""} style={nodeStyles?.link}>
|
|
810
844
|
{renderChildren(node.children, inListItem, true)}
|
|
811
845
|
</Link>
|
|
812
846
|
);
|
|
@@ -818,31 +852,33 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
818
852
|
title={node.title}
|
|
819
853
|
alt={node.alt}
|
|
820
854
|
Renderer={NodeRenderer}
|
|
821
|
-
style={
|
|
855
|
+
style={nodeStyles?.image}
|
|
822
856
|
/>
|
|
823
857
|
);
|
|
824
858
|
|
|
825
859
|
case "code_inline":
|
|
826
|
-
return
|
|
860
|
+
return (
|
|
861
|
+
<InlineCode style={nodeStyles?.code_inline}>{node.content}</InlineCode>
|
|
862
|
+
);
|
|
827
863
|
|
|
828
864
|
case "code_block":
|
|
829
865
|
return (
|
|
830
866
|
<CodeBlock
|
|
831
867
|
language={node.language}
|
|
832
868
|
content={getTextContent(node)}
|
|
833
|
-
style={
|
|
869
|
+
style={nodeStyles?.code_block}
|
|
834
870
|
/>
|
|
835
871
|
);
|
|
836
872
|
|
|
837
873
|
case "blockquote":
|
|
838
874
|
return (
|
|
839
|
-
<Blockquote style={
|
|
875
|
+
<Blockquote style={nodeStyles?.blockquote}>
|
|
840
876
|
{renderChildren(node.children, inListItem, false)}
|
|
841
877
|
</Blockquote>
|
|
842
878
|
);
|
|
843
879
|
|
|
844
880
|
case "horizontal_rule":
|
|
845
|
-
return <HorizontalRule style={
|
|
881
|
+
return <HorizontalRule style={nodeStyles?.horizontal_rule} />;
|
|
846
882
|
|
|
847
883
|
case "line_break":
|
|
848
884
|
return <Text>{"\n"}</Text>;
|
|
@@ -854,12 +890,17 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
854
890
|
let mathContent = getTextContent(node);
|
|
855
891
|
if (!mathContent) return null;
|
|
856
892
|
mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
|
|
857
|
-
return
|
|
893
|
+
return (
|
|
894
|
+
<MathInline content={mathContent} style={nodeStyles?.math_inline} />
|
|
895
|
+
);
|
|
858
896
|
}
|
|
859
897
|
|
|
860
898
|
case "math_block":
|
|
861
899
|
return (
|
|
862
|
-
<MathBlock
|
|
900
|
+
<MathBlock
|
|
901
|
+
content={getTextContent(node)}
|
|
902
|
+
style={nodeStyles?.math_block}
|
|
903
|
+
/>
|
|
863
904
|
);
|
|
864
905
|
|
|
865
906
|
case "list":
|
|
@@ -868,7 +909,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
868
909
|
ordered={node.ordered ?? false}
|
|
869
910
|
start={node.start}
|
|
870
911
|
depth={depth}
|
|
871
|
-
style={
|
|
912
|
+
style={nodeStyles?.list}
|
|
872
913
|
>
|
|
873
914
|
{node.children?.map((child, index) => {
|
|
874
915
|
if (child.type === "task_list_item") {
|
|
@@ -906,7 +947,10 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
906
947
|
|
|
907
948
|
case "task_list_item":
|
|
908
949
|
return (
|
|
909
|
-
<TaskListItem
|
|
950
|
+
<TaskListItem
|
|
951
|
+
checked={node.checked ?? false}
|
|
952
|
+
style={nodeStyles?.task_list_item}
|
|
953
|
+
>
|
|
910
954
|
{renderChildren(node.children, true, false)}
|
|
911
955
|
</TaskListItem>
|
|
912
956
|
);
|
|
@@ -916,7 +960,7 @@ const NodeRendererComponent: FC<NodeRendererProps> = ({
|
|
|
916
960
|
<TableRenderer
|
|
917
961
|
node={node}
|
|
918
962
|
Renderer={NodeRenderer}
|
|
919
|
-
style={
|
|
963
|
+
style={nodeStyles?.table}
|
|
920
964
|
/>
|
|
921
965
|
);
|
|
922
966
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FC, ReactNode } from "react";
|
|
2
2
|
import { View, StyleSheet, type ViewStyle } from "react-native";
|
|
3
|
+
import { getCachedStyles } from "./style-cache";
|
|
3
4
|
import { useMarkdownContext } from "../MarkdownContext";
|
|
5
|
+
import type { MarkdownTheme } from "../theme";
|
|
4
6
|
|
|
5
7
|
type BlockquoteProps = {
|
|
6
8
|
children: ReactNode;
|
|
@@ -9,22 +11,25 @@ type BlockquoteProps = {
|
|
|
9
11
|
|
|
10
12
|
export const Blockquote: FC<BlockquoteProps> = ({ children, style }) => {
|
|
11
13
|
const { theme } = useMarkdownContext();
|
|
12
|
-
const styles =
|
|
13
|
-
() =>
|
|
14
|
-
StyleSheet.create({
|
|
15
|
-
blockquote: {
|
|
16
|
-
borderLeftWidth: 4,
|
|
17
|
-
borderLeftColor: theme.colors.blockquote,
|
|
18
|
-
paddingLeft: theme.spacing.l,
|
|
19
|
-
marginVertical: theme.spacing.m,
|
|
20
|
-
backgroundColor: theme.colors.surfaceLight,
|
|
21
|
-
paddingVertical: theme.spacing.m,
|
|
22
|
-
paddingRight: theme.spacing.m,
|
|
23
|
-
borderRadius: theme.borderRadius.s,
|
|
24
|
-
},
|
|
25
|
-
}),
|
|
26
|
-
[theme],
|
|
27
|
-
);
|
|
14
|
+
const styles = getCachedStyles(stylesCache, theme, createStyles);
|
|
28
15
|
|
|
29
16
|
return <View style={[styles.blockquote, style]}>{children}</View>;
|
|
30
17
|
};
|
|
18
|
+
|
|
19
|
+
type BlockquoteStyles = ReturnType<typeof createStyles>;
|
|
20
|
+
|
|
21
|
+
const stylesCache = new WeakMap<MarkdownTheme, BlockquoteStyles>();
|
|
22
|
+
|
|
23
|
+
const createStyles = (theme: MarkdownTheme) =>
|
|
24
|
+
StyleSheet.create({
|
|
25
|
+
blockquote: {
|
|
26
|
+
borderLeftWidth: 4,
|
|
27
|
+
borderLeftColor: theme.colors.blockquote,
|
|
28
|
+
paddingLeft: theme.spacing.l,
|
|
29
|
+
marginVertical: theme.spacing.m,
|
|
30
|
+
backgroundColor: theme.colors.surfaceLight,
|
|
31
|
+
paddingVertical: theme.spacing.m,
|
|
32
|
+
paddingRight: theme.spacing.m,
|
|
33
|
+
borderRadius: theme.borderRadius.s,
|
|
34
|
+
},
|
|
35
|
+
});
|
package/src/renderers/code.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ViewStyle,
|
|
9
9
|
type TextStyle,
|
|
10
10
|
} from "react-native";
|
|
11
|
+
import { getCachedStyles } from "./style-cache";
|
|
11
12
|
import { getTextContent } from "../headless";
|
|
12
13
|
import { useMarkdownContext } from "../MarkdownContext";
|
|
13
14
|
import {
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
type HighlightedToken,
|
|
16
17
|
} from "../utils/code-highlight";
|
|
17
18
|
import type { MarkdownNode } from "../headless";
|
|
19
|
+
import type { MarkdownTheme } from "../theme";
|
|
18
20
|
|
|
19
21
|
type CodeBlockProps = {
|
|
20
22
|
language?: string;
|
|
@@ -40,41 +42,14 @@ export const CodeBlock: FC<CodeBlockProps> = ({
|
|
|
40
42
|
: null;
|
|
41
43
|
|
|
42
44
|
const displayContent = content ?? (node ? getTextContent(node) : "");
|
|
43
|
-
|
|
44
|
-
const styles = useMemo(
|
|
45
|
+
const highlightedTokens = useMemo(
|
|
45
46
|
() =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
backgroundColor: theme.colors.codeBackground,
|
|
49
|
-
borderRadius: theme.borderRadius.m,
|
|
50
|
-
padding: theme.spacing.l,
|
|
51
|
-
marginVertical: theme.spacing.m,
|
|
52
|
-
borderWidth: 1,
|
|
53
|
-
borderColor: theme.colors.border,
|
|
54
|
-
},
|
|
55
|
-
codeLanguage: {
|
|
56
|
-
color: theme.colors.codeLanguage,
|
|
57
|
-
fontSize: theme.fontSizes.xs,
|
|
58
|
-
fontWeight: "600",
|
|
59
|
-
marginBottom: theme.spacing.s,
|
|
60
|
-
textTransform: "uppercase",
|
|
61
|
-
letterSpacing: 0.5,
|
|
62
|
-
fontFamily: theme.fontFamilies.mono,
|
|
63
|
-
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
64
|
-
},
|
|
65
|
-
codeBlockText: {
|
|
66
|
-
fontFamily:
|
|
67
|
-
theme.fontFamilies.mono ??
|
|
68
|
-
Platform.select({ ios: "Courier", android: "monospace" }),
|
|
69
|
-
fontSize: theme.fontSizes.s,
|
|
70
|
-
color: theme.colors.text,
|
|
71
|
-
lineHeight: theme.fontSizes.s * 1.5,
|
|
72
|
-
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
73
|
-
},
|
|
74
|
-
}),
|
|
75
|
-
[theme],
|
|
47
|
+
highlighter && language ? highlighter(language, displayContent) : null,
|
|
48
|
+
[displayContent, highlighter, language],
|
|
76
49
|
);
|
|
77
50
|
|
|
51
|
+
const styles = getCachedStyles(codeBlockStylesCache, theme, createCodeStyles);
|
|
52
|
+
|
|
78
53
|
const showLanguage = theme.showCodeLanguage && language;
|
|
79
54
|
|
|
80
55
|
return (
|
|
@@ -87,21 +62,18 @@ export const CodeBlock: FC<CodeBlockProps> = ({
|
|
|
87
62
|
showsHorizontalScrollIndicator={false}
|
|
88
63
|
bounces={false}
|
|
89
64
|
>
|
|
90
|
-
{
|
|
65
|
+
{highlightedTokens ? (
|
|
91
66
|
<Text style={styles.codeBlockText} selectable>
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
)}
|
|
67
|
+
{highlightedTokens.map((token: HighlightedToken, i: number) => {
|
|
68
|
+
const tokenColor = ctx.theme.colors.codeTokenColors?.[token.type];
|
|
69
|
+
return tokenColor ? (
|
|
70
|
+
<Text key={i} style={{ color: tokenColor }}>
|
|
71
|
+
{token.text}
|
|
72
|
+
</Text>
|
|
73
|
+
) : (
|
|
74
|
+
<Text key={i}>{token.text}</Text>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
105
77
|
</Text>
|
|
106
78
|
) : (
|
|
107
79
|
<Text style={styles.codeBlockText} selectable>
|
|
@@ -131,23 +103,63 @@ export const InlineCode: FC<InlineCodeProps> = ({
|
|
|
131
103
|
const displayContent =
|
|
132
104
|
content ?? children ?? (node ? getTextContent(node) : "");
|
|
133
105
|
|
|
134
|
-
const styles =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
fontFamily:
|
|
139
|
-
theme.fontFamilies.mono ??
|
|
140
|
-
Platform.select({ ios: "Courier", android: "monospace" }),
|
|
141
|
-
fontSize: theme.fontSizes.s,
|
|
142
|
-
color: theme.colors.code,
|
|
143
|
-
backgroundColor: theme.colors.codeBackground,
|
|
144
|
-
paddingHorizontal: theme.spacing.xs,
|
|
145
|
-
paddingVertical: 2,
|
|
146
|
-
borderRadius: theme.borderRadius.s,
|
|
147
|
-
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
148
|
-
},
|
|
149
|
-
}),
|
|
150
|
-
[theme],
|
|
106
|
+
const styles = getCachedStyles(
|
|
107
|
+
inlineCodeStylesCache,
|
|
108
|
+
theme,
|
|
109
|
+
createInlineStyles,
|
|
151
110
|
);
|
|
152
111
|
return <Text style={[styles.codeInline, style]}>{displayContent}</Text>;
|
|
153
112
|
};
|
|
113
|
+
|
|
114
|
+
type CodeBlockStyles = ReturnType<typeof createCodeStyles>;
|
|
115
|
+
type InlineCodeStyles = ReturnType<typeof createInlineStyles>;
|
|
116
|
+
|
|
117
|
+
const codeBlockStylesCache = new WeakMap<MarkdownTheme, CodeBlockStyles>();
|
|
118
|
+
const inlineCodeStylesCache = new WeakMap<MarkdownTheme, InlineCodeStyles>();
|
|
119
|
+
|
|
120
|
+
const getMonoFontFamily = (theme: MarkdownTheme) =>
|
|
121
|
+
theme.fontFamilies.mono ??
|
|
122
|
+
Platform.select({ ios: "Courier", android: "monospace" });
|
|
123
|
+
|
|
124
|
+
const createCodeStyles = (theme: MarkdownTheme) =>
|
|
125
|
+
StyleSheet.create({
|
|
126
|
+
codeBlock: {
|
|
127
|
+
backgroundColor: theme.colors.codeBackground,
|
|
128
|
+
borderRadius: theme.borderRadius.m,
|
|
129
|
+
padding: theme.spacing.l,
|
|
130
|
+
marginVertical: theme.spacing.m,
|
|
131
|
+
borderWidth: 1,
|
|
132
|
+
borderColor: theme.colors.border,
|
|
133
|
+
},
|
|
134
|
+
codeLanguage: {
|
|
135
|
+
color: theme.colors.codeLanguage,
|
|
136
|
+
fontSize: theme.fontSizes.xs,
|
|
137
|
+
fontWeight: "600",
|
|
138
|
+
marginBottom: theme.spacing.s,
|
|
139
|
+
textTransform: "uppercase",
|
|
140
|
+
letterSpacing: 0.5,
|
|
141
|
+
fontFamily: theme.fontFamilies.mono,
|
|
142
|
+
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
143
|
+
},
|
|
144
|
+
codeBlockText: {
|
|
145
|
+
fontFamily: getMonoFontFamily(theme),
|
|
146
|
+
fontSize: theme.fontSizes.s,
|
|
147
|
+
color: theme.colors.text,
|
|
148
|
+
lineHeight: theme.fontSizes.s * 1.5,
|
|
149
|
+
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const createInlineStyles = (theme: MarkdownTheme) =>
|
|
154
|
+
StyleSheet.create({
|
|
155
|
+
codeInline: {
|
|
156
|
+
fontFamily: getMonoFontFamily(theme),
|
|
157
|
+
fontSize: theme.fontSizes.s,
|
|
158
|
+
color: theme.colors.code,
|
|
159
|
+
backgroundColor: theme.colors.codeBackground,
|
|
160
|
+
paddingHorizontal: theme.spacing.xs,
|
|
161
|
+
paddingVertical: 2,
|
|
162
|
+
borderRadius: theme.borderRadius.s,
|
|
163
|
+
...(Platform.OS === "android" && { includeFontPadding: false }),
|
|
164
|
+
},
|
|
165
|
+
});
|