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 = ({ 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); const { blockquoteContainer: blockquoteContainerDynamicStyle, blockquoteIcon: blockquoteIconDynamicStyle, codeContainer: codeContainerDynamicStyle } = useStyles({ radiuses, colors, spaces }); const nodes = parseMarkdown(content); const renderBold = ({ part, i }: { part: string; i: number; }) => { return {part.replace(/\*\*/g, "")} ; }; const renderItalic = ({ part, i }: { part: string; i: number; }) => { return {part.replace(/__/g, "")} ; }; 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 {renderInlineStyles(node.content)}; }; const renderEnter = ({ node }: { node: MarkdownObject }) => { return ; }; const renderBlockquote = ({ node }: { node: MarkdownObject }) => { return {renderInlineStyles(node.content)} ; }; 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 {node.content} { 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 •{" "} {renderInlineStyles(node.content)} ; }; const renderCode = ({ node }: { node: MarkdownObject }) => { return {node.content} ; }; const renderLink = ({ node }: { node: MarkdownObject }) => { return { Linking.canOpenURL(node.url as string); }} > ; }; const renderCenter = ({ node }: { node: MarkdownObject }) => { return ; }; const renderLeft = ({ node }: { node: MarkdownObject }) => { return ; }; const renderRight = ({ node }: { node: MarkdownObject }) => { return ; }; return { setContainerWidth(nativeEvent.layout.width); }} style={[ stylesheet.container, style ]} > {nodes.map((node) => { if (!node.content.trim() && node.nativeType === "

") 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 }); } })} ; }; export default MarkdownViewer;