diff --git a/app/components/post/post.js b/app/components/post/post.js
index b4136e4e8..89e515f69 100644
--- a/app/components/post/post.js
+++ b/app/components/post/post.js
@@ -63,6 +63,7 @@ export default class Post extends PureComponent {
skipFlaggedHeader: PropTypes.bool,
skipPinnedHeader: PropTypes.bool,
isCommentMention: PropTypes.bool,
+ location: PropTypes.string,
};
static defaultProps = {
@@ -255,6 +256,7 @@ export default class Post extends PureComponent {
highlightPinnedOrFlagged,
skipFlaggedHeader,
skipPinnedHeader,
+ location,
} = this.props;
if (!post) {
@@ -348,6 +350,7 @@ export default class Post extends PureComponent {
isReplyPost={isReplyPost}
showAddReaction={showAddReaction}
showLongPost={showLongPost}
+ location={location}
/>
diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js
index 93b019483..a9756cf4c 100644
--- a/app/components/post_body/post_body.js
+++ b/app/components/post_body/post_body.js
@@ -66,6 +66,7 @@ export default class PostBody extends PureComponent {
isEmojiOnly: PropTypes.bool.isRequired,
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
theme: PropTypes.object,
+ location: PropTypes.string,
};
static defaultProps = {
@@ -149,6 +150,7 @@ export default class PostBody extends PureComponent {
navigator,
postId,
showAddReaction,
+ location,
} = this.props;
if (isSystemMessage && (!canDelete || hasBeenDeleted)) {
@@ -178,6 +180,7 @@ export default class PostBody extends PureComponent {
postId,
managedConfig,
showAddReaction,
+ location,
},
};
diff --git a/app/components/post_list/post_list_base.js b/app/components/post_list/post_list_base.js
index 33911377e..3328a7b35 100644
--- a/app/components/post_list/post_list_base.js
+++ b/app/components/post_list/post_list_base.js
@@ -171,6 +171,7 @@ export default class PostListBase extends PureComponent {
onPostPress,
renderReplies,
shouldRenderReplyButton,
+ location,
} = this.props;
const {managedConfig} = this.state;
@@ -190,6 +191,7 @@ export default class PostListBase extends PureComponent {
onPress={onPostPress}
navigator={navigator}
managedConfig={managedConfig}
+ location={location}
/>
);
};
diff --git a/app/screens/post_options/__snapshots__/post_options.test.js.snap b/app/screens/post_options/__snapshots__/post_options.test.js.snap
index eeb433783..569b96074 100644
--- a/app/screens/post_options/__snapshots__/post_options.test.js.snap
+++ b/app/screens/post_options/__snapshots__/post_options.test.js.snap
@@ -1,6 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`PostOptions should match snapshot, no option for system message to user who doesn't have the permission to delete 1`] = `null`;
+exports[`PostOptions should match snapshot, no option for system message to user who doesn't have the permission to delete 1`] = `
+
+
+
+
+
+
+
+
+`;
exports[`PostOptions should match snapshot, showing Delete option only for system message to user who has permission to delete 1`] = `
+
+
+
+
-
-
{
const {formatMessage} = this.context.intl;
- const {canAddReaction, channelIsReadOnly, isSystemMessage, showAddReaction} = this.props;
+ const {canAddReaction} = this.props;
- if (!isSystemMessage && showAddReaction && canAddReaction && !channelIsReadOnly) {
+ if (canAddReaction) {
return (
{
- if (this.props.isSystemMessage) {
- return null;
+ getReplyOption = () => {
+ const {formatMessage} = this.context.intl;
+ const {canReply} = this.props;
+
+ if (canReply) {
+ return (
+
+ );
}
- const {formatMessage} = this.context.intl;
+ return null;
+ }
- return (
-
- );
+ getCopyPermalink = () => {
+ const {formatMessage} = this.context.intl;
+ const {canCopyPermalink} = this.props;
+
+ if (canCopyPermalink) {
+ return (
+
+ );
+ }
+
+ return null;
};
getCopyText = () => {
const {formatMessage} = this.context.intl;
- const {isSystemMessage, managedConfig, post} = this.props;
+ const {canCopyText} = this.props;
- if (!isSystemMessage && managedConfig.copyAndPasteProtection !== 'true' && post.message) {
+ if (canCopyText) {
return (
{
const {formatMessage} = this.context.intl;
- const {canDelete, hasBeenDeleted} = this.props;
+ const {canDelete} = this.props;
- if (canDelete && !hasBeenDeleted) {
+ if (canDelete) {
return (
{
const {formatMessage} = this.context.intl;
- const {canEdit, canEditUntil, isSystemMessage} = this.props;
+ const {canEdit, canEditUntil} = this.props;
- if (!isSystemMessage && canEdit && (canEditUntil === -1 || canEditUntil > Date.now())) {
+ if (canEdit && (canEditUntil === -1 || canEditUntil > Date.now())) {
return (
{
const {formatMessage} = this.context.intl;
- const {channelIsReadOnly, isFlagged, isSystemMessage} = this.props;
+ const {canFlag, isFlagged} = this.props;
- if (isSystemMessage || channelIsReadOnly) {
+ if (!canFlag) {
return null;
}
@@ -182,9 +202,9 @@ export default class PostOptions extends PureComponent {
getPinOption = () => {
const {formatMessage} = this.context.intl;
- const {channelIsReadOnly, isSystemMessage, post} = this.props;
+ const {canPin, post} = this.props;
- if (isSystemMessage || channelIsReadOnly) {
+ if (!canPin) {
return null;
}
@@ -212,51 +232,37 @@ export default class PostOptions extends PureComponent {
getMyPostOptions = () => {
const actions = [
this.getEditOption(),
+ this.getReplyOption(),
this.getFlagOption(),
+ this.getPinOption(),
this.getAddReactionOption(),
this.getCopyPermalink(),
this.getCopyText(),
+ this.getDeleteOption(),
];
- const {canDelete, canPin} = this.props;
- if (canPin) {
- actions.splice(2, 0, this.getPinOption());
- }
- if (canDelete) {
- actions.push(this.getDeleteOption());
- }
-
return actions.filter((a) => a !== null);
};
getOthersPostOptions = () => {
const actions = [
+ this.getReplyOption(),
this.getFlagOption(),
this.getAddReactionOption(),
+ this.getPinOption(),
this.getCopyPermalink(),
this.getCopyText(),
this.getEditOption(),
+ this.getDeleteOption(),
];
- const {canDelete, canPin} = this.props;
- if (canPin) {
- actions.splice(2, 0, this.getPinOption());
- }
- if (canDelete) {
- actions.push(this.getDeleteOption());
- }
-
return actions.filter((a) => a !== null);
};
getPostOptions = () => {
const {isMyPost} = this.props;
- if (isMyPost) {
- return this.getMyPostOptions();
- }
-
- return this.getOthersPostOptions();
+ return isMyPost ? this.getMyPostOptions() : this.getOthersPostOptions();
};
handleAddReaction = () => {
@@ -285,6 +291,37 @@ export default class PostOptions extends PureComponent {
});
};
+ handleReply = () => {
+ const {actions, post, navigator, theme} = this.props;
+ const rootId = (post.root_id || post.id);
+ const channelId = post.channel_id;
+
+ actions.loadThreadIfNecessary(rootId, channelId);
+ actions.selectPost(rootId);
+
+ const options = {
+ screen: 'Thread',
+ animated: true,
+ backButtonTitle: '',
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg,
+ },
+ passProps: {
+ channelId,
+ rootId,
+ },
+ };
+
+ if (Platform.OS === 'android') {
+ navigator.showModal(options);
+ } else {
+ navigator.push(options);
+ }
+ }
+
handleAddReactionToPost = (emoji) => {
const {actions, post} = this.props;
diff --git a/app/screens/post_options/post_options.test.js b/app/screens/post_options/post_options.test.js
index 57503d8be..2cad038d2 100644
--- a/app/screens/post_options/post_options.test.js
+++ b/app/screens/post_options/post_options.test.js
@@ -14,6 +14,7 @@ describe('PostOptions', () => {
const navigator = {
showModal: jest.fn(),
dismissModal: jest.fn(),
+ push: jest.fn(),
};
const actions = {
@@ -24,16 +25,22 @@ describe('PostOptions', () => {
removePost: jest.fn(),
unflagPost: jest.fn(),
unpinPost: jest.fn(),
+ selectPost: jest.fn(),
+ loadThreadIfNecessary: jest.fn(),
};
const post = {
+ root_id: 'root_id',
id: 'post_id',
message: 'message',
+ is_pinned: false,
+ channel_id: 'channel_id',
};
const baseProps = {
actions,
canAddReaction: true,
+ canReply: true,
canDelete: true,
canPin: true,
canEdit: true,
@@ -52,37 +59,44 @@ describe('PostOptions', () => {
theme: Preferences.THEMES.default,
};
- test('should match snapshot, showing all possible options', () => {
- const wrapper = shallow(
- ,
- {context: {intl: {formatMessage: jest.fn((format) => format.defaultMessage)}}},
+ function getWrapper(props = {}) {
+ return shallow(
+ ,
+ {context: {intl: {formatMessage: ({defaultMessage}) => defaultMessage}}}
);
+ }
+
+ test('should match snapshot, showing all possible options', () => {
+ const wrapper = getWrapper();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot, showing Delete option only for system message to user who has permission to delete', () => {
- const wrapper = shallow(
- ,
- {context: {intl: {formatMessage: jest.fn((format) => format.defaultMessage)}}},
- );
+ const wrapper = getWrapper({isSystemMessage: true});
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot, no option for system message to user who doesn\'t have the permission to delete', () => {
- const wrapper = shallow(
- ,
- {context: {intl: {formatMessage: jest.fn((format) => format.defaultMessage)}}},
- );
+ const wrapper = getWrapper({isSystemMessage: true, canDelete: false});
expect(wrapper.getElement()).toMatchSnapshot();
});
+
+ test('should load thread', () => {
+ const wrapper = getWrapper();
+
+ wrapper.findWhere((node) => node.key() === 'reply').simulate('press');
+ expect(actions.loadThreadIfNecessary).toBeCalled();
+ });
+
+ test('should not show reply option', () => {
+ const wrapper = getWrapper({canReply: false});
+
+ expect(wrapper.findWhere((node) => node.key() === 'reply')).toMatchObject({});
+ });
});
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 81da5d375..019e3824a 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -339,6 +339,7 @@
"mobile.post_info.flag": "Flag",
"mobile.post_info.unflag": "Unflag",
"mobile.post_info.pin": "Pin to Channel",
+ "mobile.post_info.reply": "Reply",
"mobile.post_info.unpin": "Unpin from Channel",
"mobile.post_pre_header.flagged": "Flagged",
"mobile.post_pre_header.pinned_flagged": "Pinned and Flagged",
diff --git a/assets/base/images/post_menu/reply.png b/assets/base/images/post_menu/reply.png
new file mode 100644
index 000000000..cbb8f6f5a
Binary files /dev/null and b/assets/base/images/post_menu/reply.png differ
diff --git a/assets/base/images/post_menu/reply@2x.png b/assets/base/images/post_menu/reply@2x.png
new file mode 100644
index 000000000..57234c194
Binary files /dev/null and b/assets/base/images/post_menu/reply@2x.png differ
diff --git a/assets/base/images/post_menu/reply@3x.png b/assets/base/images/post_menu/reply@3x.png
new file mode 100644
index 000000000..306e7de33
Binary files /dev/null and b/assets/base/images/post_menu/reply@3x.png differ