diff options
author | Saturnino Abril <saturnino.abril@gmail.com> | 2017-05-09 21:54:45 +0900 |
---|---|---|
committer | Joram Wilander <jwawilander@gmail.com> | 2017-05-09 07:54:45 -0500 |
commit | f88769d1f2ba7f2097e9a545e18fb0c5eb4ea2e9 (patch) | |
tree | 3d378c5e7bc60b04e0892a1ca9d555652b83098b | |
parent | 882172b298a1e11768772b95031968d75a19af94 (diff) | |
download | chat-f88769d1f2ba7f2097e9a545e18fb0c5eb4ea2e9.tar.gz chat-f88769d1f2ba7f2097e9a545e18fb0c5eb4ea2e9.tar.bz2 chat-f88769d1f2ba7f2097e9a545e18fb0c5eb4ea2e9.zip |
[PLT-6363-1] Create PostFlagIcon component and add flag IDs to posts (#6271)
* [UI-AUTO] add IDs to last 10 posts' text
* create FlagPost component and add flag IDs for posts at center, RHS and
search results
-rw-r--r-- | webapp/components/common/post_flag_icon.jsx | 87 | ||||
-rw-r--r-- | webapp/components/post_view/components/post.jsx | 1 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_header.jsx | 2 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_info.jsx | 76 | ||||
-rw-r--r-- | webapp/components/rhs_comment.jsx | 80 | ||||
-rw-r--r-- | webapp/components/rhs_root_post.jsx | 62 | ||||
-rw-r--r-- | webapp/components/rhs_thread.jsx | 15 | ||||
-rw-r--r-- | webapp/components/search_results.jsx | 6 | ||||
-rw-r--r-- | webapp/components/search_results_item.jsx | 67 |
9 files changed, 158 insertions, 238 deletions
diff --git a/webapp/components/common/post_flag_icon.jsx b/webapp/components/common/post_flag_icon.jsx new file mode 100644 index 000000000..5f714f76b --- /dev/null +++ b/webapp/components/common/post_flag_icon.jsx @@ -0,0 +1,87 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; +import {Tooltip, OverlayTrigger} from 'react-bootstrap'; + +import {flagPost, unflagPost} from 'actions/post_actions.jsx'; +import Constants from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +function flagToolTip(isFlagged) { + return ( + <Tooltip id='flagTooltip'> + <FormattedMessage + id={isFlagged ? 'flag_post.unflag' : 'flag_post.flag'} + defaultMessage={isFlagged ? 'Unflag' : 'Flag for follow up'} + /> + </Tooltip> + ); +} + +function flagIcon() { + return ( + <span + className='icon' + dangerouslySetInnerHTML={{__html: Constants.FLAG_ICON_SVG}} + /> + ); +} + +export default function PostFlagIcon(props) { + function onFlagPost(e) { + e.preventDefault(); + flagPost(props.postId); + } + + function onUnflagPost(e) { + e.preventDefault(); + unflagPost(props.postId); + } + + const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost; + const flagVisible = props.isFlagged ? 'visible' : ''; + + let flagIconId = null; + if (props.idCount > -1) { + flagIconId = Utils.createSafeId(props.idPrefix + props.idCount); + } + + if (!props.isEphemeral) { + return ( + <OverlayTrigger + key={'flagtooltipkey' + flagVisible} + delayShow={Constants.OVERLAY_TIME_DELAY} + placement='top' + overlay={flagToolTip(props.isFlagged)} + > + <a + id={flagIconId} + href='#' + className={'flag-icon__container ' + flagVisible} + onClick={flagFunc} + > + {flagIcon()} + </a> + </OverlayTrigger> + ); + } + + return ''; +} + +PostFlagIcon.propTypes = { + idPrefix: React.PropTypes.string.isRequired, + idCount: React.PropTypes.number, + postId: React.PropTypes.string.isRequired, + isFlagged: React.PropTypes.bool.isRequired, + isEphemeral: React.PropTypes.bool +}; + +PostFlagIcon.defaultProps = { + idCount: -1, + postId: '', + isFlagged: false, + isEphemeral: false +}; diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index 1959d5cad..38f95a85b 100644 --- a/webapp/components/post_view/components/post.jsx +++ b/webapp/components/post_view/components/post.jsx @@ -292,6 +292,7 @@ export default class Post extends Component { ref='header' post={post} sameRoot={this.props.sameRoot} + lastPostCount={this.props.lastPostCount} commentCount={this.props.commentCount} handleCommentClick={this.handleCommentClick} handleDropdownOpened={this.handleDropdownOpened} diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx index 9de0b7e79..eccd092b5 100644 --- a/webapp/components/post_view/components/post_header.jsx +++ b/webapp/components/post_view/components/post_header.jsx @@ -79,6 +79,7 @@ export default class PostHeader extends React.Component { <li className='col'> <PostInfo post={post} + lastPostCount={this.props.lastPostCount} commentCount={this.props.commentCount} handleCommentClick={this.props.handleCommentClick} handleDropdownOpened={this.props.handleDropdownOpened} @@ -105,6 +106,7 @@ PostHeader.propTypes = { post: React.PropTypes.object.isRequired, user: React.PropTypes.object, currentUser: React.PropTypes.object.isRequired, + lastPostCount: React.PropTypes.number, commentCount: React.PropTypes.number.isRequired, isLastComment: React.PropTypes.bool.isRequired, handleCommentClick: React.PropTypes.func.isRequired, diff --git a/webapp/components/post_view/components/post_info.jsx b/webapp/components/post_view/components/post_info.jsx index 0cb8ff5ac..1496429b3 100644 --- a/webapp/components/post_view/components/post_info.jsx +++ b/webapp/components/post_view/components/post_info.jsx @@ -5,6 +5,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import PostTime from './post_time.jsx'; +import PostFlagIcon from 'components/common/post_flag_icon.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import * as PostActions from 'actions/post_actions.jsx'; @@ -13,7 +14,7 @@ import * as Utils from 'utils/utils.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; +import {Overlay} from 'react-bootstrap'; import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; import React from 'react'; @@ -325,7 +326,11 @@ export default class PostInfo extends React.Component { render() { var post = this.props.post; - const flagIcon = Constants.FLAG_ICON_SVG; + + let idCount = -1; + if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) { + idCount = this.props.lastPostCount; + } this.canDelete = PostUtils.canDeletePost(post); this.canEdit = PostUtils.canEditPost(post, this.editDisableAction); @@ -420,64 +425,6 @@ export default class PostInfo extends React.Component { } } - let flag; - let flagFunc; - let flagVisible = ''; - let flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.flag' - defaultMessage='Flag for follow up' - /> - </Tooltip> - ); - if (this.props.isFlagged) { - flagVisible = 'visible'; - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.unflagPost; - flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.unflag' - defaultMessage='Unflag' - /> - </Tooltip> - ); - } else { - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.flagPost; - } - - let flagTrigger; - if (!isEphemeral) { - flagTrigger = ( - <OverlayTrigger - key={'flagtooltipkey' + flagVisible} - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={flagTooltip} - > - <a - href='#' - className={'flag-icon__container ' + flagVisible} - onClick={flagFunc} - > - {flag} - </a> - </OverlayTrigger> - ); - } - let pinnedBadge; if (post.is_pinned) { pinnedBadge = ( @@ -502,7 +449,13 @@ export default class PostInfo extends React.Component { /> {pinnedBadge} {this.state.showEmojiPicker} - {flagTrigger} + <PostFlagIcon + idPrefix={'centerPostFlag'} + idCount={idCount} + postId={post.id} + isFlagged={this.props.isFlagged} + isEphemeral={isEphemeral} + /> </li> {options} </ul> @@ -518,6 +471,7 @@ PostInfo.defaultProps = { }; PostInfo.propTypes = { post: React.PropTypes.object.isRequired, + lastPostCount: React.PropTypes.number, commentCount: React.PropTypes.number.isRequired, isLastComment: React.PropTypes.bool.isRequired, handleCommentClick: React.PropTypes.func.isRequired, diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index fb0972804..88e8c1ca6 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -8,6 +8,7 @@ import PostMessageContainer from 'components/post_view/components/post_message_c import ProfilePicture from 'components/profile_picture.jsx'; import ReactionListContainer from 'components/post_view/components/reaction_list_container.jsx'; import RhsDropdown from 'components/rhs_dropdown.jsx'; +import PostFlagIcon from 'components/common/post_flag_icon.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import {flagPost, unflagPost, pinPost, unpinPost, addReaction} from 'actions/post_actions.jsx'; @@ -19,7 +20,7 @@ import * as PostUtils from 'utils/post_utils.jsx'; import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; +import {Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; @@ -128,6 +129,10 @@ export default class RhsComment extends React.Component { return true; } + if (nextProps.lastPostCount !== this.props.lastPostCount) { + return true; + } + return false; } @@ -384,9 +389,13 @@ export default class RhsComment extends React.Component { render() { const post = this.props.post; - const flagIcon = Constants.FLAG_ICON_SVG; const mattermostLogo = Constants.MATTERMOST_ICON_SVG; + let idCount = -1; + if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) { + idCount = this.props.lastPostCount; + } + const isEphemeral = Utils.isPostEphemeral(post); const isPending = post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING; const isSystemMessage = PostUtils.isSystemMessage(post); @@ -523,64 +532,6 @@ export default class RhsComment extends React.Component { ); } - let flag; - let flagFunc; - let flagVisible = ''; - let flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.flag' - defaultMessage='Flag for follow up' - /> - </Tooltip> - ); - if (this.props.isFlagged) { - flagVisible = 'visible'; - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.unflagPost; - flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.unflag' - defaultMessage='Unflag' - /> - </Tooltip> - ); - } else { - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.flagPost; - } - - let flagTrigger; - if (!isEphemeral) { - flagTrigger = ( - <OverlayTrigger - key={'commentflagtooltipkey' + flagVisible} - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={flagTooltip} - > - <a - href='#' - className={'flag-icon__container ' + flagVisible} - onClick={flagFunc} - > - {flag} - </a> - </OverlayTrigger> - ); - } - let react; let reactOverlay; @@ -668,7 +619,13 @@ export default class RhsComment extends React.Component { <li className='col'> {this.renderTimeTag(post, timeOptions)} {pinnedBadge} - {flagTrigger} + <PostFlagIcon + idPrefix={'rhsCommentFlag'} + idCount={idCount} + postId={post.id} + isFlagged={this.props.isFlagged} + isEphemeral={isEphemeral} + /> </li> {options} </ul> @@ -689,6 +646,7 @@ export default class RhsComment extends React.Component { RhsComment.propTypes = { post: React.PropTypes.object, + lastPostCount: React.PropTypes.number, user: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object.isRequired, compactDisplay: React.PropTypes.bool, diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index 65bc52f73..bf9748636 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -8,6 +8,7 @@ import FileAttachmentListContainer from './file_attachment_list_container.jsx'; import ProfilePicture from 'components/profile_picture.jsx'; import ReactionListContainer from 'components/post_view/components/reaction_list_container.jsx'; import RhsDropdown from 'components/rhs_dropdown.jsx'; +import PostFlagIcon from 'components/common/post_flag_icon.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; @@ -24,7 +25,7 @@ import ReactDOM from 'react-dom'; import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; +import {Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; @@ -203,7 +204,6 @@ export default class RhsRootPost extends React.Component { const mattermostLogo = Constants.MATTERMOST_ICON_SVG; var timestamp = user ? user.last_picture_update : 0; var channel = ChannelStore.get(post.channel_id); - const flagIcon = Constants.FLAG_ICON_SVG; this.canDelete = PostUtils.canDeletePost(post); this.canEdit = PostUtils.canEditPost(post, this.editDisableAction); @@ -530,44 +530,6 @@ export default class RhsRootPost extends React.Component { const profilePicContainer = (<div className='post__img'>{profilePic}</div>); - let flag; - let flagFunc; - let flagVisible = ''; - let flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.flag' - defaultMessage='Flag for follow up' - /> - </Tooltip> - ); - if (this.props.isFlagged) { - flagVisible = 'visible'; - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.unflagPost; - flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.unflag' - defaultMessage='Unflag' - /> - </Tooltip> - ); - } else { - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.flagPost; - } - let pinnedBadge; if (post.is_pinned) { pinnedBadge = ( @@ -601,20 +563,11 @@ export default class RhsRootPost extends React.Component { <li className='col'> {this.renderTimeTag(post, timeOptions)} {pinnedBadge} - <OverlayTrigger - key={'rootpostflagtooltipkey' + flagVisible} - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={flagTooltip} - > - <a - href='#' - className={'flag-icon__container ' + flagVisible} - onClick={flagFunc} - > - {flag} - </a> - </OverlayTrigger> + <PostFlagIcon + idPrefix={'rhsRootPostFlag'} + postId={post.id} + isFlagged={this.props.isFlagged} + /> </li> <li className='col col__reply'> {reactOverlay} @@ -645,6 +598,7 @@ RhsRootPost.defaultProps = { }; RhsRootPost.propTypes = { post: React.PropTypes.object.isRequired, + lastPostCount: React.PropTypes.number, user: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object.isRequired, commentCount: React.PropTypes.number, diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx index 174799878..82e54f6ff 100644 --- a/webapp/components/rhs_thread.jsx +++ b/webapp/components/rhs_thread.jsx @@ -352,7 +352,8 @@ export default class RhsThread extends React.Component { let previousPostDay = rootPostDay; const commentsLists = []; - for (let i = 0; i < postsArray.length; i++) { + const postsLength = postsArray.length; + for (let i = 0; i < postsLength; i++) { const comPost = postsArray[i]; let p; if (UserStore.getCurrentId() === comPost.user_id) { @@ -371,10 +372,7 @@ export default class RhsThread extends React.Component { status = this.state.statuses[p.id] || 'offline'; } - const keyPrefix = comPost.id ? comPost.id : comPost.pending_post_id; - const currentPostDay = Utils.getDateForUnixTicks(comPost.create_at); - if (currentPostDay.toDateString() !== previousPostDay.toDateString()) { previousPostDay = currentPostDay; commentsLists.push( @@ -383,11 +381,14 @@ export default class RhsThread extends React.Component { />); } + const keyPrefix = comPost.id ? comPost.id : comPost.pending_post_id; + const reverseCount = postsLength - i - 1; commentsLists.push( <div key={keyPrefix + 'commentKey'}> <Comment ref={comPost.id} post={comPost} + lastPostCount={(reverseCount >= 0 && reverseCount < Constants.TEST_ID_COUNT) ? reverseCount : -1} user={p} currentUser={this.props.currentUser} compactDisplay={this.state.compactDisplay} @@ -431,12 +432,12 @@ export default class RhsThread extends React.Component { className='post-right__scroll' > <DateSeparator - date={rootPostDay.toDateString()} + date={rootPostDay} /> <RootPost ref={selected.id} post={selected} - commentCount={postsArray.length} + commentCount={postsLength} user={profile} currentUser={this.props.currentUser} compactDisplay={this.state.compactDisplay} @@ -456,7 +457,7 @@ export default class RhsThread extends React.Component { <CreateComment channelId={selected.channel_id} rootId={selected.id} - latestPostId={postsArray.length > 0 ? postsArray[postsArray.length - 1].id : selected.id} + latestPostId={postsLength > 0 ? postsArray[postsLength - 1].id : selected.id} /> </div> </div> diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx index 682b04e2a..64e5a7c93 100644 --- a/webapp/components/search_results.jsx +++ b/webapp/components/search_results.jsx @@ -267,7 +267,7 @@ export default class SearchResults extends React.Component { </div> ); } else { - ctls = results.order.map(function mymap(id) { + ctls = results.order.map(function searchResults(id, idx, arr) { const post = results.posts[id]; let profile; if (UserStore.getCurrentId() === post.user_id) { @@ -285,12 +285,16 @@ export default class SearchResults extends React.Component { if (this.state.flaggedPosts) { isFlagged = this.state.flaggedPosts.get(post.id) === 'true'; } + + const reverseCount = arr.length - idx - 1; + return ( <SearchResultsItem key={post.id} channel={this.state.channels.get(post.channel_id)} compactDisplay={this.state.compactDisplay} post={post} + lastPostCount={(reverseCount >= 0 && reverseCount < Constants.TEST_ID_COUNT) ? reverseCount : -1} user={profile} term={searchTerm} isMentionSearch={this.props.isMentionSearch} diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx index 09ea8c427..9e0eb51b0 100644 --- a/webapp/components/search_results_item.jsx +++ b/webapp/components/search_results_item.jsx @@ -13,12 +13,12 @@ import UserStore from 'stores/user_store.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import {flagPost, unflagPost} from 'actions/post_actions.jsx'; +import PostFlagIcon from 'components/common/post_flag_icon.jsx'; import * as Utils from 'utils/utils.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; import Constants from 'utils/constants.jsx'; -import {Tooltip, OverlayTrigger} from 'react-bootstrap'; const ActionTypes = Constants.ActionTypes; import React from 'react'; @@ -114,7 +114,11 @@ export default class SearchResultsItem extends React.Component { const timestamp = UserStore.getCurrentUser().last_picture_update; const user = this.props.user || {}; const post = this.props.post; - const flagIcon = Constants.FLAG_ICON_SVG; + + let idCount = -1; + if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) { + idCount = this.props.lastPostCount; + } if (channel) { channelName = channel.display_name; @@ -185,59 +189,13 @@ export default class SearchResultsItem extends React.Component { </p> ); } else { - let flag; - let flagFunc; - let flagVisible = ''; - let flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.flag' - defaultMessage='Flag for follow up' - /> - </Tooltip> - ); - - if (this.props.isFlagged) { - flagVisible = 'visible'; - flagTooltip = ( - <Tooltip id='flagTooltip'> - <FormattedMessage - id='flag_post.unflag' - defaultMessage='Unflag' - /> - </Tooltip> - ); - flagFunc = this.unflagPost; - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - } else { - flag = ( - <span - className='icon' - dangerouslySetInnerHTML={{__html: flagIcon}} - /> - ); - flagFunc = this.flagPost; - } - flagContent = ( - <OverlayTrigger - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={flagTooltip} - > - <a - href='#' - className={'flag-icon__container ' + flagVisible} - onClick={flagFunc} - > - {flag} - </a> - </OverlayTrigger> + <PostFlagIcon + idPrefix={'searchPostFlag'} + idCount={idCount} + postId={post.id} + isFlagged={this.props.isFlagged} + /> ); rhsControls = ( @@ -364,6 +322,7 @@ export default class SearchResultsItem extends React.Component { SearchResultsItem.propTypes = { post: React.PropTypes.object, + lastPostCount: React.PropTypes.number, user: React.PropTypes.object, channel: React.PropTypes.object, compactDisplay: React.PropTypes.bool, |