// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Parser} from 'commonmark';
import Renderer from 'commonmark-react-renderer';
import React, {type ReactElement, useCallback, useMemo, useRef} from 'react';
import {StyleSheet, Text, type TextStyle, View} from 'react-native';
import Emoji from '@components/emoji';
import {computeTextStyle, getMarkdownTextStyles} from '@utils/markdown';
import type {MarkdownBaseRenderer, MarkdownChannelMentionRenderer, MarkdownEmojiRenderer} from '@typings/global/markdown';
type ButtonMarkdownTextProps = {
baseStyle: TextStyle;
theme: Theme;
value: string;
maxNodes: number;
testID?: string;
};
const styles = StyleSheet.create({
container: {
alignItems: 'flex-start',
flexDirection: 'row',
flexShrink: 1,
flexWrap: 'wrap',
},
});
const ButtonMarkdownText = ({baseStyle, theme, value, maxNodes, testID}: ButtonMarkdownTextProps) => {
const markdownTextStyles = useMemo(() => getMarkdownTextStyles(theme), [theme]);
const renderText = useCallback(({context, literal}: MarkdownBaseRenderer) => {
return (
{literal}
);
}, [baseStyle, markdownTextStyles]);
const renderEmoji = useCallback(({emojiName, literal}: MarkdownEmojiRenderer) => {
return (
);
}, [baseStyle]);
const renderCodeSpan = useCallback(({context, literal}: MarkdownBaseRenderer) => {
return (
{literal}
);
}, [baseStyle, markdownTextStyles]);
const renderAtMention = useCallback(({context, mentionName}: {context: string[]; mentionName: string}) => {
return renderText({context, literal: `@${mentionName}`});
}, [renderText]);
const renderChannelLink = useCallback(({context, channelName}: MarkdownChannelMentionRenderer) => {
return renderText({context, literal: `~${channelName}`});
}, [renderText]);
const renderHashtag = useCallback(({context, hashtag}: {context: string[]; hashtag: string}) => {
return renderText({context, literal: hashtag});
}, [renderText]);
const renderLink = useCallback(({children}: {children: ReactElement}) => {
return children;
}, []);
const renderInlineBreak = useCallback(() => {
return {' '};
}, []);
const renderParagraph = useCallback(({children}: {children: ReactElement}) => {
return {children};
}, []);
const renderNull = useCallback(() => {
return null;
}, []);
const renderer = useMemo(() => {
return new Renderer({
renderers: {
text: renderText,
emph: Renderer.forwardChildren,
strong: Renderer.forwardChildren,
del: Renderer.forwardChildren,
code: renderCodeSpan,
link: renderLink,
emoji: renderEmoji,
atMention: renderAtMention,
channelLink: renderChannelLink,
hashtag: renderHashtag,
hardBreak: renderInlineBreak,
softBreak: renderInlineBreak,
paragraph: renderParagraph,
image: renderNull,
heading: renderNull,
codeBlock: renderNull,
blockQuote: renderNull,
list: renderNull,
item: renderNull,
thematicBreak: renderNull,
htmlBlock: renderNull,
htmlInline: renderNull,
table: renderNull,
table_row: renderNull,
table_cell: renderNull,
latexInline: renderNull,
inline_entity_link: renderNull,
mention_highlight: Renderer.forwardChildren,
search_highlight: Renderer.forwardChildren,
highlight_without_notification: Renderer.forwardChildren,
checkbox: renderNull,
editedIndicator: renderNull,
} as Record,
maxNodes,
});
}, [
renderAtMention,
renderChannelLink,
renderCodeSpan,
renderEmoji,
renderHashtag,
renderInlineBreak,
renderLink,
renderNull,
renderParagraph,
renderText,
maxNodes,
]);
const parserRef = useRef(null);
if (parserRef.current === null) {
parserRef.current = new Parser();
}
const output = useMemo(() => {
const ast = parserRef.current!.parse(value);
return renderer.render(ast) as ReactElement;
}, [renderer, value]);
return (
{output}
);
};
export default ButtonMarkdownText;