mattermost-mobile/app/components/markdown/markdown_emoji/markdown_emoji.js
Saturnino Abril d8abeabd70 [MM-12353] Add feature to render jumbo emoji and unwrapped Emoji component with Text when rendering message containing emoji/s only (#2356)
* add feature to render jumbo emoji/s

* fix bottom clipping of emoji on Android

* updated per feedback
2018-11-22 11:56:27 -05:00

148 lines
4.2 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Node, Parser} from 'commonmark';
import Renderer from 'commonmark-react-renderer';
import React, {PureComponent} from 'react';
import {Platform, Text, View} from 'react-native';
import PropTypes from 'prop-types';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {blendColors, concatStyles, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class MarkdownEmoji extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style,
isEdited: PropTypes.bool,
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
value: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.parser = this.createParser();
this.renderer = this.createRenderer();
}
createParser = () => {
return new Parser();
}
createRenderer = () => {
return new Renderer({
renderers: {
editedIndicator: this.renderEditedIndicator,
emoji: this.renderEmoji,
paragraph: this.renderParagraph,
text: this.renderText,
},
});
};
computeTextStyle = (baseStyle) => {
if (!this.props.shouldRenderJumboEmoji) {
return baseStyle;
}
const style = getStyleSheet(this.props.theme);
return concatStyles(baseStyle, style.jumboEmoji);
};
renderEmoji = ({context, emojiName, literal}) => {
return (
<Emoji
emojiName={emojiName}
literal={literal}
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
/>
);
};
renderParagraph = ({children}) => {
const style = getStyleSheet(this.props.theme);
return (
<View style={style.block}>{children}</View>
);
};
renderText = ({context, literal}) => {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{literal}</Text>;
};
renderEditedIndicator = ({context}) => {
let spacer = '';
if (context[0] === 'paragraph') {
spacer = ' ';
}
const style = getStyleSheet(this.props.theme);
const styles = [
this.props.baseTextStyle,
style.editedIndicatorText,
];
return (
<Text style={styles}>
{spacer}
<FormattedText
id='post_message_view.edited'
defaultMessage='(edited)'
/>
</Text>
);
};
render() {
const ast = this.parser.parse(this.props.value);
if (this.props.isEdited) {
const editIndicatorNode = new Node('edited_indicator');
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
ast.lastChild.appendChild(editIndicatorNode);
} else {
const node = new Node('paragraph');
node.appendChild(editIndicatorNode);
ast.appendChild(node);
}
}
return this.renderer.render(ast);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
// Android has trouble giving text transparency depending on how it's nested,
// so we calculate the resulting colour manually
const editedOpacity = Platform.select({
ios: 0.3,
android: 1.0,
});
const editedColor = Platform.select({
ios: theme.centerChannelColor,
android: blendColors(theme.centerChannelBg, theme.centerChannelColor, 0.3),
});
return {
block: {
alignItems: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap',
},
editedIndicatorText: {
color: editedColor,
opacity: editedOpacity,
},
jumboEmoji: {
fontSize: 40,
lineHeight: 50,
},
};
});