From 581bd8637fa860fa26f60d8089c5d6e3f613d23f Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Mon, 1 May 2017 17:40:54 -0400 Subject: Revert "[GH-5915] Clicking on @mention of a user in a post shows the profile popover (#6129)" (#6289) This reverts commit 83f819451a80a767170b927eb2f0d5ed63f03239. --- .../add_users_to_team/add_users_to_team.jsx | 2 +- .../more_direct_channels/more_direct_channels.jsx | 2 +- .../popover_list_members/popover_list_members.jsx | 2 +- webapp/components/post_view/components/post.jsx | 2 +- .../post_view/components/post_header.jsx | 2 +- .../post_view/components/post_message_view.jsx | 40 +-- webapp/components/profile_picture.jsx | 103 +++++++ webapp/components/profile_popover.jsx | 268 +++++++++++++++++++ .../profile_popover/atmention_profile_popover.jsx | 95 ------- .../profile_popover/picture_profile_popover.jsx | 104 -------- .../components/profile_popover/profile_popover.jsx | 296 --------------------- .../profile_popover/username_profile_popover.jsx | 111 -------- webapp/components/rhs_comment.jsx | 4 +- webapp/components/rhs_root_post.jsx | 4 +- webapp/components/search_results_item.jsx | 4 +- webapp/components/user_list_row.jsx | 2 +- webapp/components/user_profile.jsx | 111 ++++++++ webapp/package.json | 1 - webapp/tests/utils/formatting_at_mentions.test.jsx | 30 +-- webapp/tests/utils/formatting_hashtags.test.jsx | 2 +- webapp/utils/channel_intro_messages.jsx | 4 +- webapp/utils/text_formatting.jsx | 10 +- webapp/utils/utils.jsx | 16 +- 23 files changed, 518 insertions(+), 697 deletions(-) create mode 100644 webapp/components/profile_picture.jsx create mode 100644 webapp/components/profile_popover.jsx delete mode 100644 webapp/components/profile_popover/atmention_profile_popover.jsx delete mode 100644 webapp/components/profile_popover/picture_profile_popover.jsx delete mode 100644 webapp/components/profile_popover/profile_popover.jsx delete mode 100644 webapp/components/profile_popover/username_profile_popover.jsx create mode 100644 webapp/components/user_profile.jsx diff --git a/webapp/components/add_users_to_team/add_users_to_team.jsx b/webapp/components/add_users_to_team/add_users_to_team.jsx index ee22bbed5..ae6fd8c4e 100644 --- a/webapp/components/add_users_to_team/add_users_to_team.jsx +++ b/webapp/components/add_users_to_team/add_users_to_team.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import MultiSelect from 'components/multiselect/multiselect.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import {addUsersToTeam} from 'actions/team_actions.jsx'; import {searchUsersNotInTeam} from 'actions/user_actions.jsx'; diff --git a/webapp/components/more_direct_channels/more_direct_channels.jsx b/webapp/components/more_direct_channels/more_direct_channels.jsx index 743236ce6..50e2c4e48 100644 --- a/webapp/components/more_direct_channels/more_direct_channels.jsx +++ b/webapp/components/more_direct_channels/more_direct_channels.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import MultiSelect from 'components/multiselect/multiselect.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import {searchUsers} from 'actions/user_actions.jsx'; import {openDirectChannelToUser, openGroupChannelToUsers} from 'actions/channel_actions.jsx'; diff --git a/webapp/components/popover_list_members/popover_list_members.jsx b/webapp/components/popover_list_members/popover_list_members.jsx index 458ae8f24..cf6042943 100644 --- a/webapp/components/popover_list_members/popover_list_members.jsx +++ b/webapp/components/popover_list_members/popover_list_members.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import TeamStore from 'stores/team_store.jsx'; import UserStore from 'stores/user_store.jsx'; diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index cf25b28e4..f5c96d2bc 100644 --- a/webapp/components/post_view/components/post.jsx +++ b/webapp/components/post_view/components/post.jsx @@ -3,7 +3,7 @@ import PostHeader from './post_header.jsx'; import PostBody from './post_body.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx index e19285963..9de0b7e79 100644 --- a/webapp/components/post_view/components/post_header.jsx +++ b/webapp/components/post_view/components/post_header.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import UserProfile from 'components/profile_popover/username_profile_popover.jsx'; +import UserProfile from 'components/user_profile.jsx'; import PostInfo from './post_info.jsx'; import {FormattedMessage} from 'react-intl'; diff --git a/webapp/components/post_view/components/post_message_view.jsx b/webapp/components/post_view/components/post_message_view.jsx index db522c974..5b0790f36 100644 --- a/webapp/components/post_view/components/post_message_view.jsx +++ b/webapp/components/post_view/components/post_message_view.jsx @@ -3,9 +3,6 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; -import {Parser, ProcessNodeDefinitions} from 'html-to-react'; - -import AtMentionProfile from 'components/profile_popover/atmention_profile_popover.jsx'; import Constants from 'utils/constants.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; @@ -91,38 +88,6 @@ export default class PostMessageView extends React.Component { ); } - postMessageHtmlToComponent(html) { - const parser = new Parser(); - const attrib = 'data-mention'; - const processNodeDefinitions = new ProcessNodeDefinitions(React); - - function isValidNode() { - return true; - } - - const processingInstructions = [ - { - replaceChildren: true, - shouldProcessNode: (node) => node.attribs && node.attribs[attrib] && this.props.usernameMap.hasOwnProperty(node.attribs[attrib]), - processNode: (node) => { - const username = node.attribs[attrib]; - return ( - - ); - } - }, - { - shouldProcessNode: () => true, - processNode: processNodeDefinitions.processDefaultNode - } - ]; - - return parser.parseWithInstructions(html, isValidNode, processingInstructions); - } - render() { if (this.props.post.state === Constants.POST_DELETED) { return this.renderDeletedPost(); @@ -146,17 +111,14 @@ export default class PostMessageView extends React.Component { return
{renderedSystemMessage}
; } - const htmlFormattedText = TextFormatting.formatText(this.props.post.message, options); - const postMessageComponent = this.postMessageHtmlToComponent(htmlFormattedText); - return (
- {postMessageComponent} {this.renderEditedIndicator()}
); diff --git a/webapp/components/profile_picture.jsx b/webapp/components/profile_picture.jsx new file mode 100644 index 000000000..b7ee08785 --- /dev/null +++ b/webapp/components/profile_picture.jsx @@ -0,0 +1,103 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. +import ProfilePopover from './profile_popover.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import React from 'react'; +import StatusIcon from './status_icon.jsx'; +import {OverlayTrigger} from 'react-bootstrap'; + +export default class ProfilePicture extends React.Component { + constructor(props) { + super(props); + + this.hideProfilePopover = this.hideProfilePopover.bind(this); + } + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.src !== this.props.src) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.width !== this.props.width) { + return true; + } + + if (nextProps.height !== this.props.height) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + return false; + } + + hideProfilePopover() { + this.refs.overlay.hide(); + } + + render() { + if (this.props.user) { + return ( + + } + > + + + + + + ); + } + return ( + + + + + ); + } +} + +ProfilePicture.defaultProps = { + width: '36', + height: '36' +}; +ProfilePicture.propTypes = { + src: React.PropTypes.string.isRequired, + status: React.PropTypes.string, + width: React.PropTypes.string, + height: React.PropTypes.string, + user: React.PropTypes.object, + isBusy: React.PropTypes.bool +}; diff --git a/webapp/components/profile_popover.jsx b/webapp/components/profile_popover.jsx new file mode 100644 index 000000000..63bd99ac4 --- /dev/null +++ b/webapp/components/profile_popover.jsx @@ -0,0 +1,268 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from 'utils/utils.jsx'; +import UserStore from 'stores/user_store.jsx'; +import WebrtcStore from 'stores/webrtc_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as GlobalActions from 'actions/global_actions.jsx'; +import * as WebrtcActions from 'actions/webrtc_actions.jsx'; +import {openDirectChannelToUser} from 'actions/channel_actions.jsx'; +import Constants from 'utils/constants.jsx'; +const UserStatuses = Constants.UserStatuses; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; + +import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; +import {browserHistory} from 'react-router/es6'; +import React from 'react'; + +export default class ProfilePopover extends React.Component { + constructor(props) { + super(props); + + this.initWebrtc = this.initWebrtc.bind(this); + this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this); + this.state = { + currentUserId: UserStore.getCurrentId(), + loadingDMChannel: -1 + }; + } + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.src !== this.props.src) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + // React-Bootstrap Forwarded Props from OverlayTrigger to Popover + if (nextProps.arrowOffsetLeft !== this.props.arrowOffsetLeft) { + return true; + } + + if (nextProps.arrowOffsetTop !== this.props.arrowOffsetTop) { + return true; + } + + if (nextProps.positionLeft !== this.props.positionLeft) { + return true; + } + + if (nextProps.positionTop !== this.props.positionTop) { + return true; + } + + return false; + } + + handleShowDirectChannel(e) { + e.preventDefault(); + + if (!this.props.user) { + return; + } + + const user = this.props.user; + + if (this.state.loadingDMChannel !== -1) { + return; + } + + this.setState({loadingDMChannel: user.id}); + + openDirectChannelToUser( + user.id, + (channel) => { + if (Utils.isMobile()) { + GlobalActions.emitCloseRightHandSide(); + } + this.setState({loadingDMChannel: -1}); + if (this.props.hide) { + this.props.hide(); + } + browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name); + } + ); + } + + initWebrtc() { + if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) { + GlobalActions.emitCloseRightHandSide(); + WebrtcActions.initWebrtc(this.props.user.id, true); + } + } + + render() { + const popoverProps = Object.assign({}, this.props); + delete popoverProps.user; + delete popoverProps.src; + delete popoverProps.status; + delete popoverProps.isBusy; + delete popoverProps.hide; + + let webrtc; + const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + + const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW); + + if (webrtcEnabled && this.props.user.id !== this.state.currentUserId) { + const isOnline = this.props.status !== UserStatuses.OFFLINE; + let webrtcMessage; + if (isOnline && !this.props.isBusy) { + webrtcMessage = ( + + ); + } else if (this.props.isBusy) { + webrtcMessage = ( + + ); + } else { + webrtcMessage = ( + + ); + } + + webrtc = ( + + ); + } + + var dataContent = []; + dataContent.push( + + ); + + const fullname = Utils.getFullName(this.props.user); + if (fullname) { + dataContent.push( + {fullname}} + > +
+ {fullname} +
+
+ ); + } + + if (this.props.user.position) { + const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH); + dataContent.push( + {position}} + > +
+ {position} +
+
+ ); + } + + const email = this.props.user.email; + if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) { + dataContent.push( + + ); + } + + if (this.props.user.id !== UserStore.getCurrentId()) { + dataContent.push( + + ); + dataContent.push(webrtc); + } + + return ( + + {dataContent} + + ); + } +} + +ProfilePopover.propTypes = Object.assign({ + src: React.PropTypes.string.isRequired, + user: React.PropTypes.object.isRequired, + status: React.PropTypes.string, + isBusy: React.PropTypes.bool, + hide: React.PropTypes.func +}, Popover.propTypes); +delete ProfilePopover.propTypes.id; diff --git a/webapp/components/profile_popover/atmention_profile_popover.jsx b/webapp/components/profile_popover/atmention_profile_popover.jsx deleted file mode 100644 index 47c625f64..000000000 --- a/webapp/components/profile_popover/atmention_profile_popover.jsx +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ProfilePopover from './profile_popover.jsx'; -import * as Utils from 'utils/utils.jsx'; -import Client from 'client/web_client.jsx'; - -import {OverlayTrigger} from 'react-bootstrap'; - -import React from 'react'; - -export default class AtMentionProfile extends React.Component { - constructor(props) { - super(props); - - this.hideProfilePopover = this.hideProfilePopover.bind(this); - } - - shouldComponentUpdate(nextProps) { - if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { - return true; - } - - if (nextProps.overwriteImage !== this.props.overwriteImage) { - return true; - } - - if (nextProps.disablePopover !== this.props.disablePopover) { - return true; - } - - if (nextProps.displayNameType !== this.props.displayNameType) { - return true; - } - - if (nextProps.status !== this.props.status) { - return true; - } - - if (nextProps.isBusy !== this.props.isBusy) { - return true; - } - - return false; - } - - hideProfilePopover() { - this.refs.overlay.hide(); - } - - render() { - let profileImg = ''; - if (this.props.user) { - profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update; - } - - if (this.props.disablePopover) { - return {'@' + this.props.username}; - } - - return ( - - } - > - {'@' + this.props.username} - - ); - } -} - -AtMentionProfile.defaultProps = { - overwriteImage: '', - disablePopover: false -}; -AtMentionProfile.propTypes = { - user: React.PropTypes.object.isRequired, - username: React.PropTypes.string.isRequired, - overwriteImage: React.PropTypes.string, - disablePopover: React.PropTypes.bool, - displayNameType: React.PropTypes.string, - status: React.PropTypes.string, - isBusy: React.PropTypes.bool -}; diff --git a/webapp/components/profile_popover/picture_profile_popover.jsx b/webapp/components/profile_popover/picture_profile_popover.jsx deleted file mode 100644 index 2c2b91b25..000000000 --- a/webapp/components/profile_popover/picture_profile_popover.jsx +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ProfilePopover from './profile_popover.jsx'; -import * as Utils from 'utils/utils.jsx'; - -import React from 'react'; -import StatusIcon from 'components/status_icon.jsx'; -import {OverlayTrigger} from 'react-bootstrap'; - -export default class ProfilePicture extends React.Component { - constructor(props) { - super(props); - - this.hideProfilePopover = this.hideProfilePopover.bind(this); - } - shouldComponentUpdate(nextProps) { - if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { - return true; - } - - if (nextProps.src !== this.props.src) { - return true; - } - - if (nextProps.status !== this.props.status) { - return true; - } - - if (nextProps.width !== this.props.width) { - return true; - } - - if (nextProps.height !== this.props.height) { - return true; - } - - if (nextProps.isBusy !== this.props.isBusy) { - return true; - } - - return false; - } - - hideProfilePopover() { - this.refs.overlay.hide(); - } - - render() { - if (this.props.user) { - return ( - - } - > - - - - - - ); - } - return ( - - - - - ); - } -} - -ProfilePicture.defaultProps = { - width: '36', - height: '36' -}; -ProfilePicture.propTypes = { - src: React.PropTypes.string.isRequired, - status: React.PropTypes.string, - width: React.PropTypes.string, - height: React.PropTypes.string, - user: React.PropTypes.object, - isBusy: React.PropTypes.bool -}; diff --git a/webapp/components/profile_popover/profile_popover.jsx b/webapp/components/profile_popover/profile_popover.jsx deleted file mode 100644 index a32b7904b..000000000 --- a/webapp/components/profile_popover/profile_popover.jsx +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import * as Utils from 'utils/utils.jsx'; -import UserStore from 'stores/user_store.jsx'; -import WebrtcStore from 'stores/webrtc_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; -import * as WebrtcActions from 'actions/webrtc_actions.jsx'; -import {openDirectChannelToUser} from 'actions/channel_actions.jsx'; -import Constants from 'utils/constants.jsx'; -const UserStatuses = Constants.UserStatuses; -const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; - -import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router/es6'; -import React from 'react'; - -export default class ProfilePopover extends React.Component { - constructor(props) { - super(props); - - this.initWebrtc = this.initWebrtc.bind(this); - this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this); - this.generateImage = this.generateImage.bind(this); - this.generateFullname = this.generateFullname.bind(this); - this.generatePosition = this.generatePosition.bind(this); - this.generateWebrtc = this.generateWebrtc.bind(this); - this.generateEmail = this.generateEmail.bind(this); - this.generateDirectMessage = this.generateDirectMessage.bind(this); - - this.state = { - currentUserId: UserStore.getCurrentId(), - loadingDMChannel: -1 - }; - } - - shouldComponentUpdate(nextProps) { - if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { - return true; - } - - if (nextProps.src !== this.props.src) { - return true; - } - - if (nextProps.status !== this.props.status) { - return true; - } - - if (nextProps.isBusy !== this.props.isBusy) { - return true; - } - - // React-Bootstrap Forwarded Props from OverlayTrigger to Popover - if (nextProps.arrowOffsetLeft !== this.props.arrowOffsetLeft) { - return true; - } - - if (nextProps.arrowOffsetTop !== this.props.arrowOffsetTop) { - return true; - } - - if (nextProps.positionLeft !== this.props.positionLeft) { - return true; - } - - if (nextProps.positionTop !== this.props.positionTop) { - return true; - } - - return false; - } - - handleShowDirectChannel(e) { - e.preventDefault(); - - if (!this.props.user) { - return; - } - - const user = this.props.user; - - if (this.state.loadingDMChannel !== -1) { - return; - } - - this.setState({loadingDMChannel: user.id}); - - openDirectChannelToUser( - user.id, - (channel) => { - if (Utils.isMobile()) { - GlobalActions.emitCloseRightHandSide(); - } - this.setState({loadingDMChannel: -1}); - if (this.props.hide) { - this.props.hide(); - } - browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name); - } - ); - } - - initWebrtc() { - if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) { - GlobalActions.emitCloseRightHandSide(); - WebrtcActions.initWebrtc(this.props.user.id, true); - } - } - - generateImage(src) { - return ( - - ); - } - - generateFullname() { - const fullname = Utils.getFullName(this.props.user); - if (fullname) { - return ( - {fullname}} - > -
- {fullname} -
-
- ); - } - - return ''; - } - - generatePosition() { - if (this.props.user.hasOwnProperty('position')) { - const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH); - return ( - {position}} - > -
- {position} -
-
- ); - } - - return ''; - } - - generateWebrtc() { - const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW); - if (webrtcEnabled && this.props.user.id !== this.state.currentUserId) { - const isOnline = this.props.status !== UserStatuses.OFFLINE; - let webrtcMessage; - if (isOnline && !this.props.isBusy) { - webrtcMessage = ( - - ); - } else if (this.props.isBusy) { - webrtcMessage = ( - - ); - } else { - webrtcMessage = ( - - ); - } - - return ( - - ); - } - - return ''; - } - - generateEmail() { - const email = this.props.user.hasOwnProperty('email') ? this.props.user.email : ''; - const showEmail = (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()); - - if (email !== '' && showEmail) { - return ( - - ); - } - - return ''; - } - - generateDirectMessage() { - if (this.props.user.id !== UserStore.getCurrentId()) { - return ( - - ); - } - - return ''; - } - - render() { - return ( - - {this.generateImage(this.props.src)} - {this.generateFullname()} - {this.generatePosition()} - {this.generateEmail()} - {this.generateDirectMessage()} - {this.generateWebrtc()} - - ); - } -} - -ProfilePopover.propTypes = Object.assign({ - src: React.PropTypes.string.isRequired, - user: React.PropTypes.object.isRequired, - status: React.PropTypes.string, - isBusy: React.PropTypes.bool, - hide: React.PropTypes.func -}, Popover.propTypes); -delete ProfilePopover.propTypes.id; diff --git a/webapp/components/profile_popover/username_profile_popover.jsx b/webapp/components/profile_popover/username_profile_popover.jsx deleted file mode 100644 index 37993094b..000000000 --- a/webapp/components/profile_popover/username_profile_popover.jsx +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ProfilePopover from './profile_popover.jsx'; -import * as Utils from 'utils/utils.jsx'; -import Client from 'client/web_client.jsx'; - -import {OverlayTrigger} from 'react-bootstrap'; - -import React from 'react'; - -export default class UserProfile extends React.Component { - constructor(props) { - super(props); - - this.hideProfilePopover = this.hideProfilePopover.bind(this); - } - shouldComponentUpdate(nextProps) { - if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { - return true; - } - - if (nextProps.overwriteName !== this.props.overwriteName) { - return true; - } - - if (nextProps.overwriteImage !== this.props.overwriteImage) { - return true; - } - - if (nextProps.disablePopover !== this.props.disablePopover) { - return true; - } - - if (nextProps.displayNameType !== this.props.displayNameType) { - return true; - } - - if (nextProps.status !== this.props.status) { - return true; - } - - if (nextProps.isBusy !== this.props.isBusy) { - return true; - } - - return false; - } - - hideProfilePopover() { - this.refs.overlay.hide(); - } - - render() { - let name = '...'; - let profileImg = ''; - if (this.props.user) { - name = Utils.displayUsername(this.props.user.id); - profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update; - } - - if (this.props.overwriteName) { - name = this.props.overwriteName; - } - - if (this.props.disablePopover) { - return
{name}
; - } - - return ( - - } - > -
- {name} -
-
- ); - } -} - -UserProfile.defaultProps = { - user: {}, - overwriteName: '', - overwriteImage: '', - disablePopover: false -}; -UserProfile.propTypes = { - user: React.PropTypes.object, - overwriteName: React.PropTypes.node, - overwriteImage: React.PropTypes.string, - disablePopover: React.PropTypes.bool, - displayNameType: React.PropTypes.string, - status: React.PropTypes.string, - isBusy: React.PropTypes.bool -}; diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index d7b899b33..10cd5fb55 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -1,11 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import UserProfile from './profile_popover/username_profile_popover.jsx'; +import UserProfile from './user_profile.jsx'; import FileAttachmentListContainer from './file_attachment_list_container.jsx'; import PendingPostOptions from 'components/post_view/components/pending_post_options.jsx'; import PostMessageContainer from 'components/post_view/components/post_message_container.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.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'; diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index 32d03e524..41dd92e91 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -1,11 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import UserProfile from './profile_popover/username_profile_popover.jsx'; +import UserProfile from './user_profile.jsx'; import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx'; import PostMessageContainer from 'components/post_view/components/post_message_container.jsx'; import FileAttachmentListContainer from './file_attachment_list_container.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.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'; diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx index 846840e40..09ea8c427 100644 --- a/webapp/components/search_results_item.jsx +++ b/webapp/components/search_results_item.jsx @@ -3,9 +3,9 @@ import $ from 'jquery'; import PostMessageContainer from 'components/post_view/components/post_message_container.jsx'; -import UserProfile from './profile_popover/username_profile_popover.jsx'; +import UserProfile from './user_profile.jsx'; import FileAttachmentListContainer from './file_attachment_list_container.jsx'; -import ProfilePicture from './profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from './profile_picture.jsx'; import TeamStore from 'stores/team_store.jsx'; import UserStore from 'stores/user_store.jsx'; diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx index e4e937432..3a7fc5d1c 100644 --- a/webapp/components/user_list_row.jsx +++ b/webapp/components/user_list_row.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import UserStore from 'stores/user_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx new file mode 100644 index 000000000..37993094b --- /dev/null +++ b/webapp/components/user_profile.jsx @@ -0,0 +1,111 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ProfilePopover from './profile_popover.jsx'; +import * as Utils from 'utils/utils.jsx'; +import Client from 'client/web_client.jsx'; + +import {OverlayTrigger} from 'react-bootstrap'; + +import React from 'react'; + +export default class UserProfile extends React.Component { + constructor(props) { + super(props); + + this.hideProfilePopover = this.hideProfilePopover.bind(this); + } + shouldComponentUpdate(nextProps) { + if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) { + return true; + } + + if (nextProps.overwriteName !== this.props.overwriteName) { + return true; + } + + if (nextProps.overwriteImage !== this.props.overwriteImage) { + return true; + } + + if (nextProps.disablePopover !== this.props.disablePopover) { + return true; + } + + if (nextProps.displayNameType !== this.props.displayNameType) { + return true; + } + + if (nextProps.status !== this.props.status) { + return true; + } + + if (nextProps.isBusy !== this.props.isBusy) { + return true; + } + + return false; + } + + hideProfilePopover() { + this.refs.overlay.hide(); + } + + render() { + let name = '...'; + let profileImg = ''; + if (this.props.user) { + name = Utils.displayUsername(this.props.user.id); + profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update; + } + + if (this.props.overwriteName) { + name = this.props.overwriteName; + } + + if (this.props.disablePopover) { + return
{name}
; + } + + return ( + + } + > +
+ {name} +
+
+ ); + } +} + +UserProfile.defaultProps = { + user: {}, + overwriteName: '', + overwriteImage: '', + disablePopover: false +}; +UserProfile.propTypes = { + user: React.PropTypes.object, + overwriteName: React.PropTypes.node, + overwriteImage: React.PropTypes.string, + disablePopover: React.PropTypes.bool, + displayNameType: React.PropTypes.string, + status: React.PropTypes.string, + isBusy: React.PropTypes.bool +}; diff --git a/webapp/package.json b/webapp/package.json index d3d12a1fb..e7203f0d6 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -15,7 +15,6 @@ "flux": "3.1.2", "font-awesome": "4.7.0", "highlight.js": "9.10.0", - "html-to-react": "1.2.7", "inobounce": "0.1.4", "intl": "1.2.5", "jasny-bootstrap": "3.1.3", diff --git a/webapp/tests/utils/formatting_at_mentions.test.jsx b/webapp/tests/utils/formatting_at_mentions.test.jsx index 51f9bef65..d64b42c3f 100644 --- a/webapp/tests/utils/formatting_at_mentions.test.jsx +++ b/webapp/tests/utils/formatting_at_mentions.test.jsx @@ -50,38 +50,30 @@ describe('TextFormatting.AtMentions', function() { ); }); - it('Not at mentions', function() { - assert.equal( - TextFormatting.autolinkAtMentions('user@host', new Map(), {user: {}, host: {}}), - 'user@host' - ); - - assert.equal( - TextFormatting.autolinkAtMentions('user@email.com', new Map(), {user: {}, email: {}}), - 'user@email.com' - ); - + it('Implied at mentions', function() { + // PLT-4454 Assume users exist for things that look like at mentions until we support the new mention syntax assert.equal( TextFormatting.autolinkAtMentions('@user', new Map(), {}), - '@user' + '$MM_ATMENTION0', + 'should imply user exists and replace mention with token' ); assert.equal( TextFormatting.autolinkAtMentions('@user.', new Map(), {}), - '@user.', + '$MM_ATMENTION0.', 'should assume username doesn\'t end in punctuation' ); + }); + it('Not at mentions', function() { assert.equal( - TextFormatting.autolinkAtMentions('@will', new Map(), {william: {}}), - '@will', - 'should return same text without token' + TextFormatting.autolinkAtMentions('user@host', new Map(), {user: {}, host: {}}), + 'user@host' ); assert.equal( - TextFormatting.autolinkAtMentions('@william', new Map(), {will: {}}), - '@william', - 'should return same text without token' + TextFormatting.autolinkAtMentions('user@email.com', new Map(), {user: {}, email: {}}), + 'user@email.com' ); }); }); diff --git a/webapp/tests/utils/formatting_hashtags.test.jsx b/webapp/tests/utils/formatting_hashtags.test.jsx index 0aadd6626..1740a8ce7 100644 --- a/webapp/tests/utils/formatting_hashtags.test.jsx +++ b/webapp/tests/utils/formatting_hashtags.test.jsx @@ -166,7 +166,7 @@ describe('TextFormatting.Hashtags', function() { }; assert.equal( TextFormatting.formatText('#@test', options).trim(), - "

#@test

" + "

#@test

" ); assert.equal( diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx index cc5071047..31ba8708d 100644 --- a/webapp/utils/channel_intro_messages.jsx +++ b/webapp/utils/channel_intro_messages.jsx @@ -5,14 +5,14 @@ import * as Utils from './utils.jsx'; import ChannelInviteModal from 'components/channel_invite_modal'; import EditChannelHeaderModal from 'components/edit_channel_header_modal.jsx'; import ToggleModalButton from 'components/toggle_modal_button.jsx'; -import UserProfile from 'components/profile_popover/username_profile_popover.jsx'; +import UserProfile from 'components/user_profile.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import Client from 'client/web_client.jsx'; -import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx'; +import ProfilePicture from 'components/profile_picture.jsx'; import {showManagementOptions} from './channel_utils.jsx'; diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx index bd718b363..c2c71a4e1 100644 --- a/webapp/utils/text_formatting.jsx +++ b/webapp/utils/text_formatting.jsx @@ -166,13 +166,8 @@ export function autolinkAtMentions(text, tokens, usernameMap) { const index = tokens.size; const alias = `$MM_ATMENTION${index}`; - let tokenValue = `${mention}`; - if (Constants.SPECIAL_MENTIONS.indexOf(username) >= 0) { - tokenValue = mention; - } - tokens.set(alias, { - value: tokenValue, + value: `${mention}`, originalText: mention }); return alias; @@ -186,7 +181,8 @@ export function autolinkAtMentions(text, tokens, usernameMap) { const truncated = usernameLower.substring(0, c); const suffix = usernameLower.substring(c); - if (mentionExists(truncated) && (c === usernameLower.length || punctuation.test(usernameLower[c]))) { + // If we've found a username or run out of punctuation to trim off, render it as an at mention + if (mentionExists(truncated) || !punctuation.test(truncated[truncated.length - 1])) { const alias = addToken(truncated, '@' + truncated); return prefix + alias + suffix; } diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index 9b29c5362..f56b9bb09 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -989,16 +989,12 @@ export function changeOpacity(oldColor, opacity) { } export function getFullName(user) { - if (user !== null && typeof user !== 'undefined' && typeof user === 'object') { - const firstName = user.hasOwnProperty('first_name') ? user.first_name : ''; - const lastName = user.hasOwnProperty('last_name') ? user.last_name : ''; - if (firstName && lastName) { - return firstName + ' ' + lastName; - } else if (firstName) { - return firstName; - } else if (lastName) { - return lastName; - } + if (user.first_name && user.last_name) { + return user.first_name + ' ' + user.last_name; + } else if (user.first_name) { + return user.first_name; + } else if (user.last_name) { + return user.last_name; } return ''; -- cgit v1.2.3-1-g7c22