From c3a1e3561e2ab481cea457d7452da77700517ed1 Mon Sep 17 00:00:00 2001 From: Courtney Pattison Date: Mon, 25 Mar 2019 13:29:46 -0700 Subject: [PATCH] [MM-13381] Added reply to post options (#2665) * [MM-13381] Added reply to post options * [MM-13381] Fixes for adding reply to post options * [MM-13381] Fixed logic for adding reply to post options * [MM-13381] Fixed canReply logic for post options --- app/components/post/post.js | 3 + app/components/post_body/post_body.js | 3 + app/components/post_list/post_list_base.js | 2 + .../__snapshots__/post_options.test.js.snap | 80 +++++++--- app/screens/post_options/index.js | 43 +++++- app/screens/post_options/post_option.js | 2 + app/screens/post_options/post_options.js | 139 +++++++++++------- app/screens/post_options/post_options.test.js | 52 ++++--- assets/base/i18n/en.json | 1 + assets/base/images/post_menu/reply.png | Bin 0 -> 380 bytes assets/base/images/post_menu/reply@2x.png | Bin 0 -> 737 bytes assets/base/images/post_menu/reply@3x.png | Bin 0 -> 1046 bytes 12 files changed, 237 insertions(+), 88 deletions(-) create mode 100644 assets/base/images/post_menu/reply.png create mode 100644 assets/base/images/post_menu/reply@2x.png create mode 100644 assets/base/images/post_menu/reply@3x.png 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 0000000000000000000000000000000000000000..cbb8f6f5a49107228d68146910181e8fa344fd39 GIT binary patch literal 380 zcmV-?0fYXDP)3XMl?|2|7KEvNWeffZW{s8wQDzth!C<#7kFhvK zi%l?S8jQh=qQN|5OcYc^n9mDdINQ#5uM=N*!uj6sd+)jT+~b_BS*!!wp3G6$1vDoc zb8adyps;gT3V4a7(ZDF|lgaJG=|ID@SM$R*si&a`FYp0Rkf;3_UmkvsXcvaSeFB7Fjgl7W7%hv`(c9xiKfcu#f!!nNZtb zLra8pVD2}e1)2uXhda1|Q`iH4S9R7E2+H57PFFWYC%X)5umM%jCu+kEEc~o>XhB7x zySUcXOX2AOSI~kQoPa5XzJnF$4{ovOfo_*}Yf7QlraNrtS9$~;Qmj^D-C;A!Qm_86 a%646TR4l{;38=q_poM}U2%?=$5-k*>g|;f#2p)~7 zg^i^0086n@gn$UKOA!c&TG%K-#7+>j@G1oH!0UM-AAD@~n3?SC2M;?t$2ZUJzdJjB zUtf9*F!V(F@UW5d!@AAtCAlfsGOMNKiWEVU+uX8CX}Va!PUU1ABk1M+lwNLz%1}Ws z_X3a63@)W9Lj;R*d&%^4Q`QNF$n{rd&fFlm*YFRX!wT?mniI8& zcW@t6w-XjY)@YK`6um@U?5V_){8>u(~H{cm)2{Ku|@yB2Z%sEoz z$|mhmc^Te>h!RG1Fds!;3Z7JO;Dfxuo~*ZUYw_NT3FZ7w>8~K z6V%Vsgf3zl^rabw1>j>bC%Q1@x#%03%5{<^c#R}WVHNaXE9`?)AR(;^y2Jh=v|?O= zO<F=DvFY}*ro{D#2YBw1Kml9>N$KeQ@+kII=rMkS91|GfgKe5 zPcK>RGm|#HUNZG3HZgzHi*_57(>Ulm#FV{c8+XvpeVh~9@ILenv1OBNTTmYQPU{o+ z3zk80Nb+3r==y<&Xx5z^oLAqku~p;s%;bpkBwb$o?BtjzHRodnZciQD)wuruWns+y Tq9J1o00000NkvXXu0mjfVGU7y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..306e7de334e78c9cced99ab36976edb0bd3d29c8 GIT binary patch literal 1046 zcmV+x1nK*UP)F;5++QIg@wj~$zYT>=0#z3%;b9_rVuXe-IyfU!kumbpS2!kLxT|2ApiOv@%MoO#(Kg>xaB zlyGijlMoKdzNOPnb)40RbJGfwU5e=QtjcO$zQToMpJrLsS2(w_KEk<{jVhdbSqWF` zPYDr4wvqm3_xCxTv=(aM3w(#nhh#s&zkc43)d5k{W*>-^9~KI;u<&v?I1}L zcGE|cuZ1B{mPkW3Y~pnk?ty3E3R}L5a$O~wSZN2udW>BUba{4ZI>R(sQx<2)5F9$q(}~rcqv`S25Q=VA8Q}wt11>>j>+^aSq1O*jSnU>(c{jpq{=Xk@Fb&%|{;omd+mX;;`LQfjIRUxw|V zM@}hkOrxmR2{uMMdEc+GG>!h7jw5W7wzQA&)n5|~w58Oe=?7T5X7aHj%1XP!>lt-4 zOoADpRa*dh(cc2Ca0PU!e+8RxSo(qU$6y|W#Ssdv-3#&~!YV~6D^=PRZj3Sjb!Wj= zxCr{)n?Nvq%(;!=UhK~vxaiNa{ot#_dG}=tw{yExuYTFz; zib*Fa>`qo62~NW|Feyu~Ql_7aZdO`-D{mp6NDL&Mmat7+V;_W|_;1Sg!xAuIeG8ue zi9{jkw1sW*`s{N8Bx-_6Z$Yn9wwrun5hmS7?>2#`H!Yuu>34p|8PmqLw9BW{f!at~ zyC)mKP0JyyxmCjf(9X^s-|~eXWmNZzLek|DwplMG^9oGV(xX-($uvy5+`{sy1-&ks ztWrIqhC|{Jl&*xZq-sEWYFD~JC(;O4x>CaO(%$H1VhD6Mn7M09|pr;M1&07*qoM6N<$g2b@&9smFU literal 0 HcmV?d00001