| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- import {
- useEffect,
- useState,
- type FC
- } from "react";
- import {
- Text as NativeText,
- TouchableOpacity,
- Linking,
- Image,
- View
- } from "react-native";
- import type IMarkdownViewerProps from "./type";
- import type {
- MarkdownObject,
- TextMarkdownTypes
- } from "./type";
- import stylesheet, {
- useStyles
- } from "./stylesheet";
- import {
- parseMarkdown
- } from "./util";
- import {
- NCoreUIKitTheme
- } from "../../core/hooks";
- import type ITextProps from "../text/type";
- import {
- Quote as QuoteIcon
- } from "lucide-react-native";
- import Text from "../text";
- const MarkdownViewer: FC<IMarkdownViewerProps> = ({
- blockquoteIconSize = "titleLargeSize",
- codeTextColor = "emphasized",
- blockquoteIconColor = "mid",
- blockquoteTextColor = "mid",
- blockquoteContainerProps,
- centerContainerProps,
- blockquoteTextStyle,
- rightContainerProps,
- blockquoteIconStyle,
- centerMarkdownProps,
- leftContainerProps,
- rightMarkdownProps,
- listContainerProps,
- linkContainerProps,
- codeContainerProps,
- leftMarkdownProps,
- linkMarkdownProps,
- customTextStyles,
- customTextProps,
- italicTextProps,
- enterTextProps,
- boldTextProps,
- listIconProps,
- codeTextProps,
- listTextProps,
- italicStyle,
- imageProps,
- boldStyle,
- content,
- style,
- ...props
- }) => {
- const {
- typography,
- radiuses,
- colors,
- spaces
- } = NCoreUIKitTheme.useContext();
- const [
- containerWidth,
- setContainerWidth
- ] = useState<null | number>(null);
- const {
- blockquoteContainer: blockquoteContainerDynamicStyle,
- blockquoteIcon: blockquoteIconDynamicStyle,
- codeContainer: codeContainerDynamicStyle
- } = useStyles({
- radiuses,
- colors,
- spaces
- });
- const nodes = parseMarkdown(content);
- const renderBold = ({
- part,
- i
- }: {
- part: string;
- i: number;
- }) => {
- return <NativeText
- {...boldTextProps}
- key={`bold-${i}`}
- style={{
- ...stylesheet.bold,
- ...boldStyle
- }}
- >
- {part.replace(/\*\*/g, "")}
- </NativeText>;
- };
- const renderItalic = ({
- part,
- i
- }: {
- part: string;
- i: number;
- }) => {
- return <NativeText
- {...italicTextProps}
- key={`italic-${i}`}
- style={{
- ...stylesheet.italic,
- ...italicStyle
- }}
- >
- {part.replace(/__/g, "")}
- </NativeText>;
- };
- const renderInlineStyles = (_content: string) => {
- const parts = _content.split(/(\*\*.*?\*\*|__.*?__)/g);
- return parts.map((part, i) => {
- if (part.startsWith("**") && part.endsWith("**")) return renderBold({
- part,
- i
- });
- if (part.startsWith("__") && part.endsWith("__")) return renderItalic({
- part,
- i
- });
- return part;
- });
- };
- const renderText = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- let textProps: ITextProps = {
- variant: node.variant
- };
- if(customTextStyles && customTextStyles[node.nativeType as TextMarkdownTypes]) {
- const currentTextStyle = customTextStyles[node.nativeType as TextMarkdownTypes];
- textProps.style = currentTextStyle;
- }
- if(customTextProps && customTextProps[node.nativeType as TextMarkdownTypes]) {
- const currentTextProps = customTextProps[node.nativeType as TextMarkdownTypes];
- textProps = {
- ...textProps,
- ...currentTextProps
- };
- }
- return <Text
- {...textProps}
- key={node.key}
- >{renderInlineStyles(node.content)}</Text>;
- };
- const renderEnter = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...enterTextProps}
- key={node.key}
- style={[
- stylesheet.enter,
- {
- height: typography[node.variant as keyof NCoreUIKit.Typography].fontSize
- },
- enterTextProps?.style
- ]}
- />;
- };
- const renderBlockquote = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...blockquoteContainerProps}
- key={node.key}
- style={[
- blockquoteContainerDynamicStyle,
- blockquoteContainerProps?.style
- ]}
- >
- <QuoteIcon
- color={colors.content.icon[blockquoteIconColor]}
- size={typography[blockquoteIconSize].fontSize}
- style={{
- ...blockquoteIconDynamicStyle,
- ...blockquoteIconStyle
- }}
- />
- <Text
- color={blockquoteTextColor}
- variant={node.variant}
- style={{
- ...stylesheet.blockquoteText,
- ...blockquoteTextStyle
- }}
- >
- {renderInlineStyles(node.content)}
- </Text>
- </View>;
- };
- const renderImage = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- const [
- imgSize,
- setImgSize
- ] = useState<{
- height: number | string;
- width: number | string;
- isSetted: boolean;
- }>({
- isSetted: false,
- width: "100%",
- height: 0
- });
- useEffect(() => {
- if(!imgSize.isSetted && node.size) {
- setImgSize({
- height: node.size[1] as number | string,
- width: node.size[0] as number | string,
- isSetted: true
- });
- }
- }, [imgSize]);
- const currentWidth = typeof imgSize.width === "number" || imgSize.width.indexOf("%") === -1 || !containerWidth
- ? imgSize.width
- : (containerWidth * Number(imgSize.width.split("%")[0])) / 100;
- const currentHeight = typeof imgSize.height === "number" || imgSize.height.indexOf("%") === -1 || !containerWidth
- ? imgSize.height
- : (containerWidth * Number(imgSize.height.split("%")[0])) / 100;
- return <Image
- {...imageProps}
- alt={node.content}
- key={node.key}
- source={{
- uri: node.url
- }}
- resizeMode={node.resizeMode ? node.resizeMode : "contain"}
- onLoad={(event) => {
- if(!node.size) {
- const {
- height,
- width
- } = event.nativeEvent.source;
- setImgSize({
- isSetted: true,
- height,
- width
- });
- }
- }}
- style={{
- ...stylesheet.image,
- height: currentHeight,
- width: currentWidth,
- ...imageProps?.style
- }}
- />;
- };
- const renderList = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...listContainerProps}
- key={node.key}
- style={[
- stylesheet.listContainer,
- listContainerProps?.style
- ]}
- >
- <Text
- {...listIconProps}
- variant={node.variant}
- >
- •{" "}
- </Text>
- <Text
- {...listTextProps}
- variant={node.variant}
- >
- {renderInlineStyles(node.content)}
- </Text>
- </View>;
- };
- const renderCode = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...codeContainerProps}
- key={node.key}
- style={[
- codeContainerDynamicStyle,
- codeContainerProps?.style
- ]}
- >
- <Text
- {...codeTextProps}
- variant={node.variant}
- color={codeTextColor}
- >
- {node.content}
- </Text>
- </View>;
- };
- const renderLink = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <TouchableOpacity
- {...linkContainerProps}
- key={node.key}
- onPress={() => {
- Linking.canOpenURL(node.url as string);
- }}
- >
- <MarkdownViewer
- {...linkMarkdownProps}
- content={node.content}
- />
- </TouchableOpacity>;
- };
- const renderCenter = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...centerContainerProps}
- key={node.key}
- style={[
- stylesheet.centerContainer,
- centerContainerProps?.style
- ]}
- >
- <MarkdownViewer
- {...centerMarkdownProps}
- content={node.content}
- />
- </View>;
- };
- const renderLeft = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...leftContainerProps}
- key={node.key}
- style={[
- stylesheet.leftContainer,
- leftContainerProps?.style
- ]}
- >
- <MarkdownViewer
- {...leftMarkdownProps}
- content={node.content}
- />
- </View>;
- };
- const renderRight = ({
- node
- }: {
- node: MarkdownObject
- }) => {
- return <View
- {...rightContainerProps}
- key={node.key}
- style={[
- stylesheet.rightContainer,
- rightContainerProps?.style
- ]}
- >
- <MarkdownViewer
- {...rightMarkdownProps}
- content={node.content}
- />
- </View>;
- };
- return <View
- {...props}
- onLayout={({
- nativeEvent
- }) => {
- setContainerWidth(nativeEvent.layout.width);
- }}
- style={[
- stylesheet.container,
- style
- ]}
- >
- {nodes.map((node) => {
- if (!node.content.trim() && node.nativeType === "<p>") return null;
- switch (node.type) {
- case undefined:
- return null;
- case "text": {
- return renderText({
- node
- });
- }
- case "space":
- return renderEnter({
- node
- });
- case "blockquote": {
- return renderBlockquote({
- node
- });
- }
- case "right": {
- return renderRight({
- node
- });
- }
- case "left": {
- return renderLeft({
- node
- });
- }
- case "center": {
- return renderCenter({
- node
- });
- }
- case "link": {
- return renderLink({
- node
- });
- }
- case "code": {
- return renderCode({
- node
- });
- }
- case "image": {
- return renderImage({
- node
- });
- }
- case "list":
- return renderList({
- node
- });
- default:
- return renderText({
- node
- });
- }
- })}
- </View>;
- };
- export default MarkdownViewer;
|