react-native-marked 8.2.0 → 8.3.1
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 +374 -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 +4 -2
- package/dist/commonjs/hooks/useMarkdown.js.map +1 -1
- package/dist/commonjs/hooks/useMarkdownWithComponents.js +7 -1
- package/dist/commonjs/hooks/useMarkdownWithComponents.js.map +1 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/lib/Markdown.js +4 -2
- package/dist/commonjs/lib/Markdown.js.map +1 -1
- package/dist/commonjs/lib/Parser.js +7 -7
- 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 +7 -5
- 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 +4 -0
- 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 +4 -2
- package/dist/module/hooks/useMarkdown.js.map +1 -1
- package/dist/module/hooks/useMarkdownWithComponents.js +7 -1
- package/dist/module/hooks/useMarkdownWithComponents.js.map +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/Markdown.js +4 -2
- package/dist/module/lib/Markdown.js.map +1 -1
- package/dist/module/lib/Parser.js +7 -7
- 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 +7 -5
- 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 +4 -0
- 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/dist/typescript/hooks/useMarkdown.d.ts +3 -0
- package/dist/typescript/hooks/useMarkdown.d.ts.map +1 -1
- package/dist/typescript/hooks/useMarkdownWithComponents.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +2 -2
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/lib/Markdown.d.ts +1 -1
- package/dist/typescript/lib/Markdown.d.ts.map +1 -1
- package/dist/typescript/lib/Renderer.d.ts +8 -1
- package/dist/typescript/lib/Renderer.d.ts.map +1 -1
- package/dist/typescript/lib/types.d.ts +6 -0
- package/dist/typescript/lib/types.d.ts.map +1 -1
- package/dist/typescript/theme/styles.d.ts.map +1 -1
- package/dist/typescript/theme/types.d.ts +1 -0
- package/dist/typescript/theme/types.d.ts.map +1 -1
- package/package.json +178 -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 +56 -51
- package/src/hooks/useMarkdownWithComponents.tsx +94 -89
- package/src/index.ts +44 -43
- package/src/lib/Markdown.tsx +55 -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 +247 -238
- package/src/lib/types.ts +93 -87
- package/src/theme/colors.ts +31 -31
- package/src/theme/spacing.ts +10 -10
- package/src/theme/styles.ts +206 -198
- package/src/theme/types.ts +34 -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/components/MDSvg.tsx
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
type FunctionComponent,
|
|
3
|
-
memo,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from "react";
|
|
8
|
-
import { ActivityIndicator, type LayoutChangeEvent, View } from "react-native";
|
|
9
|
-
import { SvgFromXml } from "react-native-svg";
|
|
10
|
-
import { getSvgDimensions } from "./../utils/svg";
|
|
11
|
-
|
|
12
|
-
type MDSvgProps = {
|
|
13
|
-
uri: string;
|
|
14
|
-
alt?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type MdSvgState = {
|
|
18
|
-
viewBox: string;
|
|
19
|
-
width: number;
|
|
20
|
-
height: number;
|
|
21
|
-
svg: string;
|
|
22
|
-
isLoading: boolean;
|
|
23
|
-
error: boolean;
|
|
24
|
-
aspectRatio?: number;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const MDSvg: FunctionComponent<MDSvgProps> = ({ uri, alt = "image" }) => {
|
|
28
|
-
const isFirstLoad = useRef<boolean>(false);
|
|
29
|
-
const [layoutWidth, setLayoutWidth] = useState<number>(0);
|
|
30
|
-
const [svgState, setSvgState] = useState<MdSvgState>({
|
|
31
|
-
viewBox: "",
|
|
32
|
-
width: 0,
|
|
33
|
-
height: 0,
|
|
34
|
-
svg: "",
|
|
35
|
-
isLoading: true,
|
|
36
|
-
error: false,
|
|
37
|
-
aspectRatio: undefined,
|
|
38
|
-
});
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
const fetchSvg = async () => {
|
|
41
|
-
try {
|
|
42
|
-
const res = await fetch(uri);
|
|
43
|
-
const text = await res.text();
|
|
44
|
-
if (res.status !== 200) {
|
|
45
|
-
throw new Error("Status is not 200");
|
|
46
|
-
}
|
|
47
|
-
const { viewBox, width, height } = getSvgDimensions(text);
|
|
48
|
-
|
|
49
|
-
setSvgState({
|
|
50
|
-
width,
|
|
51
|
-
height,
|
|
52
|
-
viewBox,
|
|
53
|
-
svg: text,
|
|
54
|
-
isLoading: false,
|
|
55
|
-
error: false,
|
|
56
|
-
aspectRatio: width / height,
|
|
57
|
-
});
|
|
58
|
-
} catch (_e) {
|
|
59
|
-
setSvgState((state) => ({
|
|
60
|
-
...state,
|
|
61
|
-
error: true,
|
|
62
|
-
isLoading: false,
|
|
63
|
-
}));
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
fetchSvg();
|
|
68
|
-
}, [uri]);
|
|
69
|
-
|
|
70
|
-
const onLayout = (event: LayoutChangeEvent) => {
|
|
71
|
-
if (!isFirstLoad.current) {
|
|
72
|
-
setLayoutWidth(event.nativeEvent.layout.width ?? 0);
|
|
73
|
-
isFirstLoad.current = true;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const getWidth = () => {
|
|
78
|
-
if (layoutWidth && svgState.width) {
|
|
79
|
-
return Math.min(layoutWidth, svgState.width);
|
|
80
|
-
}
|
|
81
|
-
return "100%";
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<View
|
|
86
|
-
style={{ width: getWidth(), aspectRatio: svgState.aspectRatio }}
|
|
87
|
-
onLayout={onLayout}
|
|
88
|
-
>
|
|
89
|
-
{svgState.isLoading ? (
|
|
90
|
-
<ActivityIndicator size={"small"} />
|
|
91
|
-
) : (
|
|
92
|
-
<SvgFromXml
|
|
93
|
-
xml={svgState.svg}
|
|
94
|
-
width={"100%"}
|
|
95
|
-
height={"100%"}
|
|
96
|
-
viewBox={svgState.viewBox}
|
|
97
|
-
aria-label={alt}
|
|
98
|
-
accessibilityRole="image"
|
|
99
|
-
accessibilityLabel={alt}
|
|
100
|
-
accessibilityHint={undefined}
|
|
101
|
-
testID="react-native-marked-md-svg"
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
</View>
|
|
105
|
-
);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
export default memo(MDSvg);
|
|
1
|
+
import React, {
|
|
2
|
+
type FunctionComponent,
|
|
3
|
+
memo,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { ActivityIndicator, type LayoutChangeEvent, View } from "react-native";
|
|
9
|
+
import { SvgFromXml } from "react-native-svg";
|
|
10
|
+
import { getSvgDimensions } from "./../utils/svg";
|
|
11
|
+
|
|
12
|
+
type MDSvgProps = {
|
|
13
|
+
uri: string;
|
|
14
|
+
alt?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type MdSvgState = {
|
|
18
|
+
viewBox: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
svg: string;
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
error: boolean;
|
|
24
|
+
aspectRatio?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const MDSvg: FunctionComponent<MDSvgProps> = ({ uri, alt = "image" }) => {
|
|
28
|
+
const isFirstLoad = useRef<boolean>(false);
|
|
29
|
+
const [layoutWidth, setLayoutWidth] = useState<number>(0);
|
|
30
|
+
const [svgState, setSvgState] = useState<MdSvgState>({
|
|
31
|
+
viewBox: "",
|
|
32
|
+
width: 0,
|
|
33
|
+
height: 0,
|
|
34
|
+
svg: "",
|
|
35
|
+
isLoading: true,
|
|
36
|
+
error: false,
|
|
37
|
+
aspectRatio: undefined,
|
|
38
|
+
});
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const fetchSvg = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(uri);
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
if (res.status !== 200) {
|
|
45
|
+
throw new Error("Status is not 200");
|
|
46
|
+
}
|
|
47
|
+
const { viewBox, width, height } = getSvgDimensions(text);
|
|
48
|
+
|
|
49
|
+
setSvgState({
|
|
50
|
+
width,
|
|
51
|
+
height,
|
|
52
|
+
viewBox,
|
|
53
|
+
svg: text,
|
|
54
|
+
isLoading: false,
|
|
55
|
+
error: false,
|
|
56
|
+
aspectRatio: width / height,
|
|
57
|
+
});
|
|
58
|
+
} catch (_e) {
|
|
59
|
+
setSvgState((state) => ({
|
|
60
|
+
...state,
|
|
61
|
+
error: true,
|
|
62
|
+
isLoading: false,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
fetchSvg();
|
|
68
|
+
}, [uri]);
|
|
69
|
+
|
|
70
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
71
|
+
if (!isFirstLoad.current) {
|
|
72
|
+
setLayoutWidth(event.nativeEvent.layout.width ?? 0);
|
|
73
|
+
isFirstLoad.current = true;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const getWidth = () => {
|
|
78
|
+
if (layoutWidth && svgState.width) {
|
|
79
|
+
return Math.min(layoutWidth, svgState.width);
|
|
80
|
+
}
|
|
81
|
+
return "100%";
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<View
|
|
86
|
+
style={{ width: getWidth(), aspectRatio: svgState.aspectRatio }}
|
|
87
|
+
onLayout={onLayout}
|
|
88
|
+
>
|
|
89
|
+
{svgState.isLoading ? (
|
|
90
|
+
<ActivityIndicator size={"small"} />
|
|
91
|
+
) : (
|
|
92
|
+
<SvgFromXml
|
|
93
|
+
xml={svgState.svg}
|
|
94
|
+
width={"100%"}
|
|
95
|
+
height={"100%"}
|
|
96
|
+
viewBox={svgState.viewBox}
|
|
97
|
+
aria-label={alt}
|
|
98
|
+
accessibilityRole="image"
|
|
99
|
+
accessibilityLabel={alt}
|
|
100
|
+
accessibilityHint={undefined}
|
|
101
|
+
testID="react-native-marked-md-svg"
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
</View>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default memo(MDSvg);
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import React, { type FunctionComponent, memo, type ReactNode } from "react";
|
|
2
|
-
import { ScrollView, View, type ViewStyle } from "react-native";
|
|
3
|
-
import { Cell, Table, TableWrapper } from "react-native-reanimated-table";
|
|
4
|
-
|
|
5
|
-
type MDTableProps = {
|
|
6
|
-
header: ReactNode[][];
|
|
7
|
-
rows: ReactNode[][][];
|
|
8
|
-
widthArr: number[];
|
|
9
|
-
rowStyle?: ViewStyle;
|
|
10
|
-
cellStyle?: ViewStyle;
|
|
11
|
-
borderColor?: string;
|
|
12
|
-
borderWidth?: number;
|
|
13
|
-
tableStyle?: ViewStyle;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const MDTable: FunctionComponent<MDTableProps> = ({
|
|
17
|
-
header,
|
|
18
|
-
rows,
|
|
19
|
-
widthArr,
|
|
20
|
-
cellStyle,
|
|
21
|
-
rowStyle,
|
|
22
|
-
tableStyle,
|
|
23
|
-
borderColor,
|
|
24
|
-
borderWidth,
|
|
25
|
-
}) => {
|
|
26
|
-
return (
|
|
27
|
-
<ScrollView horizontal={true}>
|
|
28
|
-
<Table borderStyle={{ borderColor, borderWidth }} style={tableStyle}>
|
|
29
|
-
<TableWrapper style={rowStyle}>
|
|
30
|
-
{header.map((headerCol, index) => {
|
|
31
|
-
return (
|
|
32
|
-
<Cell
|
|
33
|
-
width={widthArr[index]}
|
|
34
|
-
key={`${index}`}
|
|
35
|
-
data={<View style={cellStyle}>{headerCol}</View>}
|
|
36
|
-
/>
|
|
37
|
-
);
|
|
38
|
-
})}
|
|
39
|
-
</TableWrapper>
|
|
40
|
-
{rows.map((rowData, index) => {
|
|
41
|
-
return (
|
|
42
|
-
<TableWrapper key={`${index}`} style={rowStyle}>
|
|
43
|
-
{rowData.map((cellData, cellIndex) => {
|
|
44
|
-
return (
|
|
45
|
-
<Cell
|
|
46
|
-
width={widthArr[cellIndex]}
|
|
47
|
-
key={`${cellIndex}`}
|
|
48
|
-
data={<View style={cellStyle}>{cellData}</View>}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
51
|
-
})}
|
|
52
|
-
</TableWrapper>
|
|
53
|
-
);
|
|
54
|
-
})}
|
|
55
|
-
</Table>
|
|
56
|
-
</ScrollView>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default memo(MDTable);
|
|
1
|
+
import React, { type FunctionComponent, memo, type ReactNode } from "react";
|
|
2
|
+
import { ScrollView, View, type ViewStyle } from "react-native";
|
|
3
|
+
import { Cell, Table, TableWrapper } from "react-native-reanimated-table";
|
|
4
|
+
|
|
5
|
+
type MDTableProps = {
|
|
6
|
+
header: ReactNode[][];
|
|
7
|
+
rows: ReactNode[][][];
|
|
8
|
+
widthArr: number[];
|
|
9
|
+
rowStyle?: ViewStyle;
|
|
10
|
+
cellStyle?: ViewStyle;
|
|
11
|
+
borderColor?: string;
|
|
12
|
+
borderWidth?: number;
|
|
13
|
+
tableStyle?: ViewStyle;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const MDTable: FunctionComponent<MDTableProps> = ({
|
|
17
|
+
header,
|
|
18
|
+
rows,
|
|
19
|
+
widthArr,
|
|
20
|
+
cellStyle,
|
|
21
|
+
rowStyle,
|
|
22
|
+
tableStyle,
|
|
23
|
+
borderColor,
|
|
24
|
+
borderWidth,
|
|
25
|
+
}) => {
|
|
26
|
+
return (
|
|
27
|
+
<ScrollView horizontal={true}>
|
|
28
|
+
<Table borderStyle={{ borderColor, borderWidth }} style={tableStyle}>
|
|
29
|
+
<TableWrapper style={rowStyle}>
|
|
30
|
+
{header.map((headerCol, index) => {
|
|
31
|
+
return (
|
|
32
|
+
<Cell
|
|
33
|
+
width={widthArr[index]}
|
|
34
|
+
key={`${index}`}
|
|
35
|
+
data={<View style={cellStyle}>{headerCol}</View>}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
})}
|
|
39
|
+
</TableWrapper>
|
|
40
|
+
{rows.map((rowData, index) => {
|
|
41
|
+
return (
|
|
42
|
+
<TableWrapper key={`${index}`} style={rowStyle}>
|
|
43
|
+
{rowData.map((cellData, cellIndex) => {
|
|
44
|
+
return (
|
|
45
|
+
<Cell
|
|
46
|
+
width={widthArr[cellIndex]}
|
|
47
|
+
key={`${cellIndex}`}
|
|
48
|
+
data={<View style={cellStyle}>{cellData}</View>}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</TableWrapper>
|
|
53
|
+
);
|
|
54
|
+
})}
|
|
55
|
+
</Table>
|
|
56
|
+
</ScrollView>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default memo(MDTable);
|
package/src/hooks/useMarkdown.ts
CHANGED
|
@@ -1,51 +1,56 @@
|
|
|
1
|
-
import { type Hooks, lexer, type Tokenizer } from "marked";
|
|
2
|
-
import { type ReactNode, useMemo } from "react";
|
|
3
|
-
import type { ColorSchemeName } from "react-native";
|
|
4
|
-
import Parser from "../lib/Parser";
|
|
5
|
-
import Renderer from "../lib/Renderer";
|
|
6
|
-
import type { RendererInterface } from "../lib/types";
|
|
7
|
-
import getStyles from "./../theme/styles";
|
|
8
|
-
import type { MarkedStyles, UserTheme } from "./../theme/types";
|
|
9
|
-
|
|
10
|
-
export interface useMarkdownHookOptions {
|
|
11
|
-
colorScheme?: ColorSchemeName;
|
|
12
|
-
renderer
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
import { type Hooks, lexer, type Tokenizer } from "marked";
|
|
2
|
+
import { type ReactNode, useMemo } from "react";
|
|
3
|
+
import type { ColorSchemeName } from "react-native";
|
|
4
|
+
import Parser from "../lib/Parser";
|
|
5
|
+
import Renderer from "../lib/Renderer";
|
|
6
|
+
import type { RendererInterface } from "../lib/types";
|
|
7
|
+
import getStyles from "./../theme/styles";
|
|
8
|
+
import type { MarkedStyles, UserTheme } from "./../theme/types";
|
|
9
|
+
|
|
10
|
+
export interface useMarkdownHookOptions {
|
|
11
|
+
colorScheme?: ColorSchemeName;
|
|
12
|
+
/** Custom renderer. When provided, `selectable` is ignored — configure via `new Renderer({ selectable })`. */
|
|
13
|
+
renderer?: RendererInterface;
|
|
14
|
+
theme?: UserTheme;
|
|
15
|
+
styles?: MarkedStyles;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
tokenizer?: Tokenizer;
|
|
18
|
+
hooks?: Hooks;
|
|
19
|
+
/** Whether Text elements are selectable. Defaults to `true`. Ignored when `renderer` is provided. */
|
|
20
|
+
selectable?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const useMarkdown = (
|
|
24
|
+
value: string,
|
|
25
|
+
options?: useMarkdownHookOptions,
|
|
26
|
+
): ReactNode[] => {
|
|
27
|
+
const styles = useMemo(
|
|
28
|
+
() => getStyles(options?.styles, options?.colorScheme, options?.theme),
|
|
29
|
+
[options?.styles, options?.theme, options?.colorScheme],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const parser = useMemo(
|
|
33
|
+
() =>
|
|
34
|
+
new Parser({
|
|
35
|
+
styles: styles,
|
|
36
|
+
baseUrl: options?.baseUrl,
|
|
37
|
+
renderer:
|
|
38
|
+
options?.renderer ??
|
|
39
|
+
new Renderer({ selectable: options?.selectable }),
|
|
40
|
+
}),
|
|
41
|
+
[options?.renderer, options?.baseUrl, styles, options?.selectable],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const elements = useMemo(() => {
|
|
45
|
+
const tokens = lexer(value, {
|
|
46
|
+
gfm: true,
|
|
47
|
+
tokenizer: options?.tokenizer,
|
|
48
|
+
hooks: options?.hooks,
|
|
49
|
+
});
|
|
50
|
+
return parser.parse(tokens);
|
|
51
|
+
}, [value, parser, options?.tokenizer, options?.hooks]);
|
|
52
|
+
|
|
53
|
+
return elements;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default useMarkdown;
|
|
@@ -1,89 +1,94 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import React, { useMemo } from "react";
|
|
3
|
-
import { useReactComponentRegistry } from "../lib/ReactComponentRegistry";
|
|
4
|
-
import type { ReactComponentToken } from "../lib/ReactComponentTokenizer";
|
|
5
|
-
import {
|
|
6
|
-
isReactComponentToken,
|
|
7
|
-
ReactComponentTokenizer,
|
|
8
|
-
} from "../lib/ReactComponentTokenizer";
|
|
9
|
-
import Renderer from "../lib/Renderer";
|
|
10
|
-
import type { RendererInterface } from "../lib/types";
|
|
11
|
-
import type { useMarkdownHookOptions } from "./useMarkdown";
|
|
12
|
-
import useMarkdown from "./useMarkdown";
|
|
13
|
-
|
|
14
|
-
export interface useMarkdownWithComponentsOptions
|
|
15
|
-
extends Omit<useMarkdownHookOptions, "tokenizer"> {}
|
|
16
|
-
|
|
17
|
-
interface ComponentData {
|
|
18
|
-
token: ReactComponentToken;
|
|
19
|
-
id: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function useMarkdownWithComponents(
|
|
23
|
-
value: string,
|
|
24
|
-
options?: useMarkdownWithComponentsOptions,
|
|
25
|
-
): ReactNode[] {
|
|
26
|
-
const registry = useReactComponentRegistry();
|
|
27
|
-
|
|
28
|
-
const { tokenizer, componentMap } = useMemo(() => {
|
|
29
|
-
const map = new Map<string, ComponentData>();
|
|
30
|
-
|
|
31
|
-
const customTokenizer = new ReactComponentTokenizer();
|
|
32
|
-
const originalHtml = customTokenizer.html.bind(customTokenizer);
|
|
33
|
-
|
|
34
|
-
let componentCounter = 0;
|
|
35
|
-
|
|
36
|
-
customTokenizer.html = (src: string) => {
|
|
37
|
-
const token = originalHtml(src);
|
|
38
|
-
if (token && isReactComponentToken(token)) {
|
|
39
|
-
const id = `${token.componentName}-${componentCounter++}`;
|
|
40
|
-
map.set(token.raw, { token, id });
|
|
41
|
-
}
|
|
42
|
-
return token;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return { tokenizer: customTokenizer, componentMap: map };
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
const baseRenderer = useMemo(
|
|
49
|
-
() =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
|
+
import { useReactComponentRegistry } from "../lib/ReactComponentRegistry";
|
|
4
|
+
import type { ReactComponentToken } from "../lib/ReactComponentTokenizer";
|
|
5
|
+
import {
|
|
6
|
+
isReactComponentToken,
|
|
7
|
+
ReactComponentTokenizer,
|
|
8
|
+
} from "../lib/ReactComponentTokenizer";
|
|
9
|
+
import Renderer from "../lib/Renderer";
|
|
10
|
+
import type { RendererInterface } from "../lib/types";
|
|
11
|
+
import type { useMarkdownHookOptions } from "./useMarkdown";
|
|
12
|
+
import useMarkdown from "./useMarkdown";
|
|
13
|
+
|
|
14
|
+
export interface useMarkdownWithComponentsOptions
|
|
15
|
+
extends Omit<useMarkdownHookOptions, "tokenizer"> {}
|
|
16
|
+
|
|
17
|
+
interface ComponentData {
|
|
18
|
+
token: ReactComponentToken;
|
|
19
|
+
id: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useMarkdownWithComponents(
|
|
23
|
+
value: string,
|
|
24
|
+
options?: useMarkdownWithComponentsOptions,
|
|
25
|
+
): ReactNode[] {
|
|
26
|
+
const registry = useReactComponentRegistry();
|
|
27
|
+
|
|
28
|
+
const { tokenizer, componentMap } = useMemo(() => {
|
|
29
|
+
const map = new Map<string, ComponentData>();
|
|
30
|
+
|
|
31
|
+
const customTokenizer = new ReactComponentTokenizer();
|
|
32
|
+
const originalHtml = customTokenizer.html.bind(customTokenizer);
|
|
33
|
+
|
|
34
|
+
let componentCounter = 0;
|
|
35
|
+
|
|
36
|
+
customTokenizer.html = (src: string) => {
|
|
37
|
+
const token = originalHtml(src);
|
|
38
|
+
if (token && isReactComponentToken(token)) {
|
|
39
|
+
const id = `${token.componentName}-${componentCounter++}`;
|
|
40
|
+
map.set(token.raw, { token, id });
|
|
41
|
+
}
|
|
42
|
+
return token;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return { tokenizer: customTokenizer, componentMap: map };
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const baseRenderer = useMemo(
|
|
49
|
+
() =>
|
|
50
|
+
options?.renderer ?? new Renderer({ selectable: options?.selectable }),
|
|
51
|
+
[options?.renderer, options?.selectable],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const renderer = useMemo<RendererInterface>(() => {
|
|
55
|
+
// Use prototype delegation so all Renderer methods (text, link, getTextNode -> selectable,
|
|
56
|
+
// getKey -> slugger) resolve via baseRenderer's prototype. Only `html` is overridden here
|
|
57
|
+
// for component registry handling. Avoids copying private fields and keeps `this` binding
|
|
58
|
+
// consistent. If Renderer ever uses #private fields this pattern will need revisiting.
|
|
59
|
+
const wrappedRenderer = Object.create(baseRenderer) as typeof baseRenderer;
|
|
60
|
+
|
|
61
|
+
wrappedRenderer.html = (text: string | ReactNode[], styles): ReactNode => {
|
|
62
|
+
if (typeof text === "string") {
|
|
63
|
+
const data = componentMap.get(text);
|
|
64
|
+
if (data) {
|
|
65
|
+
if (registry?.hasComponent(data.token.componentName)) {
|
|
66
|
+
const Component = registry.getComponent(data.token.componentName);
|
|
67
|
+
if (Component) {
|
|
68
|
+
return (
|
|
69
|
+
<Component key={data.id} props={data.token.componentProps}>
|
|
70
|
+
{data.token.componentChildren || undefined}
|
|
71
|
+
</Component>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return baseRenderer.html(text, styles);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return wrappedRenderer;
|
|
83
|
+
}, [baseRenderer, componentMap, registry]);
|
|
84
|
+
|
|
85
|
+
const elements = useMarkdown(value, {
|
|
86
|
+
...options,
|
|
87
|
+
tokenizer,
|
|
88
|
+
renderer,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return elements;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default useMarkdownWithComponents;
|