// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {PropTypes} from 'prop-types'; import React from 'react'; import {intlShape} from 'react-intl'; import { Clipboard, Keyboard, StyleSheet, Text, View, } from 'react-native'; import CustomPropTypes from 'app/constants/custom_prop_types'; import FormattedText from 'app/components/formatted_text'; import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import BottomSheet from 'app/utils/bottom_sheet'; import {getDisplayNameForLanguage} from 'app/utils/markdown'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import mattermostManaged from 'app/mattermost_managed'; import {goToScreen} from 'app/actions/navigation'; const MAX_LINES = 4; export default class MarkdownCodeBlock extends React.PureComponent { static propTypes = { theme: PropTypes.object.isRequired, language: PropTypes.string, content: PropTypes.string.isRequired, textStyle: CustomPropTypes.Style, }; static defaultProps = { language: '', }; static contextTypes = { intl: intlShape, }; handlePress = preventDoubleTap(() => { const {language, content} = this.props; const {intl} = this.context; const screen = 'Code'; const passProps = { content, }; const languageDisplayName = getDisplayNameForLanguage(language); let title; if (languageDisplayName) { title = intl.formatMessage( { id: 'mobile.routes.code', defaultMessage: '{language} Code', }, { language: languageDisplayName, }, ); } else { title = intl.formatMessage({ id: 'mobile.routes.code.noLanguage', defaultMessage: 'Code', }); } Keyboard.dismiss(); requestAnimationFrame(() => { goToScreen(screen, title, passProps); }); }); handleLongPress = async () => { const {formatMessage} = this.context.intl; const config = mattermostManaged.getCachedConfig(); if (config?.copyAndPasteProtection !== 'true') { const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}); const actionText = formatMessage({id: 'mobile.markdown.code.copy_code', defaultMessage: 'Copy Code'}); BottomSheet.showBottomSheetWithOptions({ options: [actionText, cancelText], cancelButtonIndex: 1, }, (value) => { if (value !== 1) { this.handleCopyCode(); } }); } }; handleCopyCode = () => { Clipboard.setString(this.props.content); }; trimContent = (content) => { const lines = content.split('\n'); const numberOfLines = lines.length; if (numberOfLines > MAX_LINES) { return { content: lines.slice(0, MAX_LINES).join('\n'), numberOfLines, }; } return { content, numberOfLines, }; }; render() { const style = getStyleSheet(this.props.theme); let language = null; if (this.props.language) { const languageDisplayName = getDisplayNameForLanguage(this.props.language); if (languageDisplayName) { language = ( {languageDisplayName} ); } } const {content, numberOfLines} = this.trimContent(this.props.content); let lineNumbers = '1'; for (let i = 1; i < Math.min(numberOfLines, MAX_LINES); i++) { const line = (i + 1).toString(); lineNumbers += '\n' + line; } let plusMoreLines = null; if (numberOfLines > MAX_LINES) { plusMoreLines = ( ); } return ( {lineNumbers} {content} {plusMoreLines} {language} ); } } const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { container: { borderColor: changeOpacity(theme.centerChannelColor, 0.15), borderRadius: 3, borderWidth: StyleSheet.hairlineWidth, flexDirection: 'row', }, lineNumbers: { alignItems: 'center', backgroundColor: changeOpacity(theme.centerChannelColor, 0.05), borderRightColor: changeOpacity(theme.centerChannelColor, 0.15), borderRightWidth: StyleSheet.hairlineWidth, flexDirection: 'column', justifyContent: 'flex-start', paddingVertical: 4, width: 21, }, lineNumbersText: { color: changeOpacity(theme.centerChannelColor, 0.5), fontSize: 12, lineHeight: 18, }, rightColumn: { flexDirection: 'column', flex: 1, paddingHorizontal: 6, paddingVertical: 4, }, code: { flexDirection: 'row', overflow: 'scroll', // Doesn't actually cause a scrollbar, but stops text from wrapping }, codeText: { color: changeOpacity(theme.centerChannelColor, 0.65), fontSize: 12, lineHeight: 18, }, plusMoreLinesText: { color: changeOpacity(theme.centerChannelColor, 0.4), fontSize: 11, marginTop: 2, }, language: { alignItems: 'center', backgroundColor: theme.sidebarHeaderBg, justifyContent: 'center', opacity: 0.8, padding: 6, position: 'absolute', right: 0, top: 0, }, languageText: { color: theme.sidebarHeaderTextColor, fontSize: 12, }, }; });