// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import {Parser} from 'commonmark'; import Renderer from 'commonmark-react-renderer'; import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { Platform, StyleSheet, Text, View } from 'react-native'; import AtMention from 'app/components/at_mention'; import ChannelLink from 'app/components/channel_link'; import Emoji from 'app/components/emoji'; import CustomPropTypes from 'app/constants/custom_prop_types'; import {concatStyles} from 'app/utils/theme'; import MarkdownBlockQuote from './markdown_block_quote'; import MarkdownCodeBlock from './markdown_code_block'; import MarkdownLink from './markdown_link'; import MarkdownList from './markdown_list'; import MarkdownListItem from './markdown_list_item'; export default class Markdown extends PureComponent { static propTypes = { baseTextStyle: CustomPropTypes.Style, blockStyles: PropTypes.object, emojiSizes: PropTypes.object, fontSizes: PropTypes.object, isSearchResult: PropTypes.bool, navigator: PropTypes.object.isRequired, onLongPress: PropTypes.func, onPostPress: PropTypes.func, textStyles: PropTypes.object, value: PropTypes.string.isRequired }; static defaultProps = { textStyles: {}, blockStyles: {}, emojiSizes: { ...Platform.select({ ios: { heading1: 25, heading2: 25, heading3: 25, heading4: 25, heading5: 25, heading6: 25, text: 20 }, android: { heading1: 60, heading2: 60, heading3: 60, heading4: 60, heading5: 60, heading6: 60, text: 45 } }) }, fontSizes: { heading1: 17, heading2: 17, heading3: 17, heading4: 17, heading5: 17, heading6: 17, text: 15 }, onLongPress: () => true }; constructor(props) { super(props); this.parser = new Parser(); this.renderer = this.createRenderer(); } createRenderer = () => { return new Renderer({ renderers: { text: this.renderText, emph: Renderer.forwardChildren, strong: Renderer.forwardChildren, del: Renderer.forwardChildren, code: this.renderCodeSpan, link: this.renderLink, image: this.renderImage, atMention: this.renderAtMention, channelLink: this.renderChannelLink, emoji: this.renderEmoji, paragraph: this.renderParagraph, heading: this.renderHeading, codeBlock: this.renderCodeBlock, blockQuote: this.renderBlockQuote, list: this.renderList, item: this.renderListItem, hardBreak: this.renderHardBreak, thematicBreak: this.renderThematicBreak, softBreak: this.renderSoftBreak, htmlBlock: this.renderHtml, htmlInline: this.renderHtml }, renderParagraphsInLists: true }); } computeTextStyle = (baseStyle, context) => { return concatStyles(baseStyle, context.map((type) => this.props.textStyles[type])); } renderText = ({context, literal}) => { // Construct the text style based off of the parents of this node since RN's inheritance is limited return {literal}; } renderCodeSpan = ({context, literal}) => { return {literal}; } renderImage = ({children, context, src}) => { // TODO This will be properly implemented for PLT-5736 return ( {'!['} {children} {']('} {src} {')'} ); } renderAtMention = ({context, mentionName}) => { return ( ); } renderChannelLink = ({context, channelName}) => { return ( ); } renderEmoji = ({context, emojiName, literal}) => { let size; let fontSize; const headingType = context.find((type) => type.startsWith('heading')); if (headingType) { size = this.props.emojiSizes[headingType]; fontSize = this.props.fontSizes[headingType]; } else { size = this.props.emojiSizes.text; fontSize = this.props.fontSizes.text; } return ( ); } renderParagraph = ({children, first}) => { const blockStyle = [style.block]; if (!first) { blockStyle.push(this.props.blockStyles.adjacentParagraph); } return ( {children} ); } renderHeading = ({children, level}) => { return ( {children} ); } renderCodeBlock = (props) => { // These sometimes include a trailing newline const content = props.literal.replace(/\n$/, ''); return ( ); } renderBlockQuote = ({children, ...otherProps}) => { return ( {children} ); } renderList = ({children, start, tight, type}) => { return ( {children} ); } renderListItem = ({children, context, ...otherProps}) => { const level = context.filter((type) => type === 'list').length; return ( {children} ); } renderHardBreak = () => { return {'\n'}; } renderThematicBreak = () => { return ; } renderSoftBreak = () => { return {'\n'}; } renderHtml = (props) => { let rendered = this.renderText(props); if (props.isBlock) { rendered = ( {rendered} ); } return rendered; } renderLink = ({children, href}) => { return ( {children} ); } render() { const ast = this.parser.parse(this.props.value); return {this.renderer.render(ast)}; } } const style = StyleSheet.create({ block: { alignItems: 'flex-start', flexDirection: 'row', flexWrap: 'wrap' } });