diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js
index 0f5b64c57..33e80b2af 100644
--- a/app/components/emoji/emoji.js
+++ b/app/components/emoji/emoji.js
@@ -15,13 +15,15 @@ export default class Emoji extends React.PureComponent {
customEmojis: PropTypes.object,
emojiName: PropTypes.string.isRequired,
literal: PropTypes.string,
+ padding: PropTypes.number,
size: PropTypes.number.isRequired,
textStyle: CustomPropTypes.Style
}
static defaultProps = {
customEmojis: new Map(),
- literal: ''
+ literal: '',
+ padding: 10
}
render() {
@@ -29,6 +31,7 @@ export default class Emoji extends React.PureComponent {
customEmojis,
emojiName,
literal,
+ padding,
size,
textStyle
} = this.props;
@@ -48,7 +51,7 @@ export default class Emoji extends React.PureComponent {
return (
);
diff --git a/app/components/post_body/index.js b/app/components/post_body/index.js
index f0ddaa9aa..a5d3001ba 100644
--- a/app/components/post_body/index.js
+++ b/app/components/post_body/index.js
@@ -23,6 +23,7 @@ function mapStateToProps(state, ownProps) {
attachments: post.props && post.props.attachments,
fileIds: post.file_ids,
hasBeenDeleted: post.state === Posts.POST_DELETED,
+ hasReactions: post.has_reactions,
isFailed: post.failed,
isFlagged: isPostFlagged(post.id, myPreferences),
isPending: post.id === post.pending_post_id,
diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js
index 8ae34006d..367f43dc1 100644
--- a/app/components/post_body/post_body.js
+++ b/app/components/post_body/post_body.js
@@ -21,6 +21,7 @@ import SlackAttachments from 'app/components/slack_attachments';
import {emptyFunction} from 'app/utils/general';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
+import Reactions from 'app/components/reactions';
class PostBody extends PureComponent {
static propTypes = {
@@ -33,6 +34,7 @@ class PostBody extends PureComponent {
canEdit: PropTypes.bool,
fileIds: PropTypes.array,
hasBeenDeleted: PropTypes.bool,
+ hasReactions: PropTypes.bool,
intl: intlShape.isRequired,
isFailed: PropTypes.bool,
isFlagged: PropTypes.bool,
@@ -132,6 +134,7 @@ class PostBody extends PureComponent {
canDelete,
canEdit,
hasBeenDeleted,
+ hasReactions,
isFailed,
isFlagged,
isPending,
@@ -143,6 +146,7 @@ class PostBody extends PureComponent {
onPostDelete,
onPostEdit,
onPress,
+ postId,
renderReplyBar,
theme,
toggleSelected
@@ -219,6 +223,7 @@ class PostBody extends PureComponent {
{messageComponent}
{this.renderSlackAttachments(messageStyle, blockStyles, textStyles)}
{this.renderFileAttachments()}
+ {hasReactions && }
{isFailed &&
diff --git a/app/components/reactions/index.js b/app/components/reactions/index.js
new file mode 100644
index 000000000..5c156e462
--- /dev/null
+++ b/app/components/reactions/index.js
@@ -0,0 +1,54 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+
+import {addReaction, getReactionsForPost, removeReaction} from 'mattermost-redux/actions/posts';
+import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
+import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import Reactions from './reactions';
+
+function makeMapStateToProps() {
+ const getReactionsForPostSelector = makeGetReactionsForPost();
+ return function mapStateToProps(state, ownProps) {
+ const currentUserId = getCurrentUser(state);
+ const reactionsForPost = getReactionsForPostSelector(state, ownProps.postId);
+
+ const highlightedReactions = [];
+ const reactionsByName = reactionsForPost.reduce((reactions, reaction) => {
+ if (reactions.has(reaction.emoji_name)) {
+ reactions.get(reaction.emoji_name).push(reaction);
+ } else {
+ reactions.set(reaction.emoji_name, [reaction]);
+ }
+
+ if (reaction.user_id === currentUserId) {
+ highlightedReactions.push(reaction.emoji_name);
+ }
+
+ return reactions;
+ }, new Map());
+
+ return {
+ highlightedReactions,
+ reactions: reactionsByName,
+ theme: getTheme(state)
+ };
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ addReaction,
+ getReactionsForPost,
+ removeReaction
+ }, dispatch)
+ };
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(Reactions);
diff --git a/app/components/reactions/reaction.js b/app/components/reactions/reaction.js
new file mode 100644
index 000000000..2aa429526
--- /dev/null
+++ b/app/components/reactions/reaction.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {
+ StyleSheet,
+ Text,
+ TouchableOpacity
+} from 'react-native';
+
+import Emoji from 'app/components/emoji';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class Reaction extends PureComponent {
+ static propTypes = {
+ count: PropTypes.number.isRequired,
+ emojiName: PropTypes.string.isRequired,
+ highlight: PropTypes.bool.isRequired,
+ onPress: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired
+ }
+
+ handlePress = () => {
+ const {emojiName, highlight, onPress} = this.props;
+ onPress(emojiName, highlight);
+ }
+
+ render() {
+ const {count, emojiName, highlight, theme} = this.props;
+ const styles = getStyleSheet(theme);
+
+ return (
+
+
+ {count}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return StyleSheet.create({
+ count: {
+ color: theme.linkColor,
+ marginLeft: 6
+ },
+ highlight: {
+ backgroundColor: changeOpacity(theme.linkColor, 0.1)
+ },
+ reaction: {
+ alignItems: 'center',
+ borderRadius: 2,
+ borderColor: changeOpacity(theme.linkColor, 0.4),
+ borderWidth: 1,
+ flexDirection: 'row',
+ marginRight: 6,
+ marginVertical: 5,
+ paddingVertical: 2,
+ paddingHorizontal: 6
+ }
+ });
+});
diff --git a/app/components/reactions/reactions.js b/app/components/reactions/reactions.js
new file mode 100644
index 000000000..1772e4499
--- /dev/null
+++ b/app/components/reactions/reactions.js
@@ -0,0 +1,72 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {
+ StyleSheet,
+ View
+} from 'react-native';
+
+import Reaction from './reaction';
+
+export default class Reactions extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ addReaction: PropTypes.func.isRequired,
+ getReactionsForPost: PropTypes.func.isRequired,
+ removeReaction: PropTypes.func.isRequired
+ }).isRequired,
+ highlightedReactions: PropTypes.array.isRequired,
+ postId: PropTypes.string.isRequired,
+ reactions: PropTypes.object.isRequired,
+ theme: PropTypes.object.isRequired
+ }
+
+ componentDidMount() {
+ const {actions, postId} = this.props;
+ actions.getReactionsForPost(postId);
+ }
+
+ handleReactionPress = (emoji, remove) => {
+ const {actions, postId} = this.props;
+ if (remove) {
+ actions.removeReaction(postId, emoji);
+ } else {
+ actions.addReaction(postId, emoji);
+ }
+ }
+
+ renderReactions = () => {
+ const {highlightedReactions, reactions, theme} = this.props;
+
+ return Array.from(reactions.keys()).map((r) => {
+ return (
+
+ );
+ });
+ }
+
+ render() {
+ return (
+
+ {this.renderReactions()}
+
+ );
+ }
+}
+
+const style = StyleSheet.create({
+ reactions: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ alignItems: 'flex-start'
+ }
+});
diff --git a/yarn.lock b/yarn.lock
index e2b5d6cc2..7868c0487 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3645,7 +3645,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux#master:
version "0.0.1"
- resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/e3b029e3df3d894485a27a9ca3cc7cbe7711ddb9"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/0d2b682cabc3893a0124f6674bcbfb653eead4a5"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"