summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webapp/components/add_users_to_team/add_users_to_team.jsx2
-rw-r--r--webapp/components/more_direct_channels/more_direct_channels.jsx2
-rw-r--r--webapp/components/popover_list_members/popover_list_members.jsx2
-rw-r--r--webapp/components/post_view/components/post.jsx2
-rw-r--r--webapp/components/post_view/components/post_header.jsx2
-rw-r--r--webapp/components/post_view/components/post_message_view.jsx40
-rw-r--r--webapp/components/profile_popover/atmention_profile_popover.jsx95
-rw-r--r--webapp/components/profile_popover/picture_profile_popover.jsx (renamed from webapp/components/profile_picture.jsx)3
-rw-r--r--webapp/components/profile_popover/profile_popover.jsx (renamed from webapp/components/profile_popover.jsx)152
-rw-r--r--webapp/components/profile_popover/username_profile_popover.jsx (renamed from webapp/components/user_profile.jsx)0
-rw-r--r--webapp/components/rhs_comment.jsx4
-rw-r--r--webapp/components/rhs_root_post.jsx4
-rw-r--r--webapp/components/search_results_item.jsx4
-rw-r--r--webapp/components/user_list_row.jsx2
-rw-r--r--webapp/package.json1
-rw-r--r--webapp/tests/utils/formatting_at_mentions.test.jsx30
-rw-r--r--webapp/tests/utils/formatting_hashtags.test.jsx2
-rw-r--r--webapp/utils/channel_intro_messages.jsx4
-rw-r--r--webapp/utils/text_formatting.jsx10
-rw-r--r--webapp/utils/utils.jsx16
20 files changed, 278 insertions, 99 deletions
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 ae6fd8c4e..ee22bbed5 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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 50e2c4e48..743236ce6 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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 cf6042943..458ae8f24 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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 f5c96d2bc..cf25b28e4 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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 9de0b7e79..e19285963 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/user_profile.jsx';
+import UserProfile from 'components/profile_popover/username_profile_popover.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 5b0790f36..db522c974 100644
--- a/webapp/components/post_view/components/post_message_view.jsx
+++ b/webapp/components/post_view/components/post_message_view.jsx
@@ -3,6 +3,9 @@
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';
@@ -88,6 +91,38 @@ 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 (
+ <AtMentionProfile
+ user={this.props.usernameMap[username]}
+ username={username}
+ />
+ );
+ }
+ },
+ {
+ shouldProcessNode: () => true,
+ processNode: processNodeDefinitions.processDefaultNode
+ }
+ ];
+
+ return parser.parseWithInstructions(html, isValidNode, processingInstructions);
+ }
+
render() {
if (this.props.post.state === Constants.POST_DELETED) {
return this.renderDeletedPost();
@@ -111,14 +146,17 @@ export default class PostMessageView extends React.Component {
return <div>{renderedSystemMessage}</div>;
}
+ const htmlFormattedText = TextFormatting.formatText(this.props.post.message, options);
+ const postMessageComponent = this.postMessageHtmlToComponent(htmlFormattedText);
+
return (
<div>
<span
id={this.props.isLastPost ? 'lastPostMessageText' : null}
className='post-message__text'
onClick={Utils.handleFormattedTextClick}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, options)}}
/>
+ {postMessageComponent}
{this.renderEditedIndicator()}
</div>
);
diff --git a/webapp/components/profile_popover/atmention_profile_popover.jsx b/webapp/components/profile_popover/atmention_profile_popover.jsx
new file mode 100644
index 000000000..47c625f64
--- /dev/null
+++ b/webapp/components/profile_popover/atmention_profile_popover.jsx
@@ -0,0 +1,95 @@
+// 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 <a className='mention-link'>{'@' + this.props.username}</a>;
+ }
+
+ return (
+ <OverlayTrigger
+ ref='overlay'
+ trigger='click'
+ placement='right'
+ rootClose={true}
+ overlay={
+ <ProfilePopover
+ user={this.props.user}
+ src={profileImg}
+ status={this.props.status}
+ isBusy={this.props.isBusy}
+ hide={this.hideProfilePopover}
+ />
+ }
+ >
+ <a className='mention-link'>{'@' + this.props.username}</a>
+ </OverlayTrigger>
+ );
+ }
+}
+
+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_picture.jsx b/webapp/components/profile_popover/picture_profile_popover.jsx
index b7ee08785..2c2b91b25 100644
--- a/webapp/components/profile_picture.jsx
+++ b/webapp/components/profile_popover/picture_profile_popover.jsx
@@ -1,10 +1,11 @@
// 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 StatusIcon from 'components/status_icon.jsx';
import {OverlayTrigger} from 'react-bootstrap';
export default class ProfilePicture extends React.Component {
diff --git a/webapp/components/profile_popover.jsx b/webapp/components/profile_popover/profile_popover.jsx
index 63bd99ac4..a32b7904b 100644
--- a/webapp/components/profile_popover.jsx
+++ b/webapp/components/profile_popover/profile_popover.jsx
@@ -23,11 +23,19 @@ export default class ProfilePopover extends React.Component {
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;
@@ -102,19 +110,63 @@ export default class ProfilePopover extends React.Component {
}
}
- 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;
+ generateImage(src) {
+ return (
+ <img
+ className='user-popover__image'
+ src={src}
+ height='128'
+ width='128'
+ key='user-popover-image'
+ />
+ );
+ }
- const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
+ generateFullname() {
+ const fullname = Utils.getFullName(this.props.user);
+ if (fullname) {
+ return (
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={<Tooltip id='fullNameTooltip'>{fullname}</Tooltip>}
+ >
+ <div
+ className='overflow--ellipsis text-nowrap padding-bottom'
+ >
+ {fullname}
+ </div>
+ </OverlayTrigger>
+ );
+ }
+
+ return '';
+ }
+ generatePosition() {
+ if (this.props.user.hasOwnProperty('position')) {
+ const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
+ return (
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={<Tooltip id='positionTooltip'>{position}</Tooltip>}
+ >
+ <div
+ className='overflow--ellipsis text-nowrap padding-bottom'
+ >
+ {position}
+ </div>
+ </OverlayTrigger>
+ );
+ }
+
+ 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;
@@ -141,7 +193,7 @@ export default class ProfilePopover extends React.Component {
);
}
- webrtc = (
+ return (
<div
data-toggle='tooltip'
key='makeCall'
@@ -160,54 +212,15 @@ export default class ProfilePopover extends React.Component {
);
}
- var dataContent = [];
- dataContent.push(
- <img
- className='user-popover__image'
- src={this.props.src}
- height='128'
- width='128'
- key='user-popover-image'
- />
- );
-
- const fullname = Utils.getFullName(this.props.user);
- if (fullname) {
- dataContent.push(
- <OverlayTrigger
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='top'
- overlay={<Tooltip id='fullNameTooltip'>{fullname}</Tooltip>}
- >
- <div
- className='overflow--ellipsis text-nowrap padding-bottom'
- >
- {fullname}
- </div>
- </OverlayTrigger>
- );
- }
+ return '';
+ }
- if (this.props.user.position) {
- const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
- dataContent.push(
- <OverlayTrigger
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='top'
- overlay={<Tooltip id='positionTooltip'>{position}</Tooltip>}
- >
- <div
- className='overflow--ellipsis text-nowrap padding-bottom'
- >
- {position}
- </div>
- </OverlayTrigger>
- );
- }
+ 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());
- const email = this.props.user.email;
- if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
- dataContent.push(
+ if (email !== '' && showEmail) {
+ return (
<div
data-toggle='tooltip'
title={email}
@@ -223,8 +236,12 @@ export default class ProfilePopover extends React.Component {
);
}
+ return '';
+ }
+
+ generateDirectMessage() {
if (this.props.user.id !== UserStore.getCurrentId()) {
- dataContent.push(
+ return (
<div
data-toggle='tooltip'
key='user-popover-dm'
@@ -243,16 +260,27 @@ export default class ProfilePopover extends React.Component {
</a>
</div>
);
- dataContent.push(webrtc);
}
+ return '';
+ }
+
+ render() {
return (
<Popover
- {...popoverProps}
+ arrowOffsetLeft={this.props.arrowOffsetLeft}
+ arrowOffsetTop={this.props.arrowOffsetTop}
+ positionLeft={this.props.positionLeft}
+ positionTop={this.props.positionTop}
title={'@' + this.props.user.username}
id='user-profile-popover'
>
- {dataContent}
+ {this.generateImage(this.props.src)}
+ {this.generateFullname()}
+ {this.generatePosition()}
+ {this.generateEmail()}
+ {this.generateDirectMessage()}
+ {this.generateWebrtc()}
</Popover>
);
}
diff --git a/webapp/components/user_profile.jsx b/webapp/components/profile_popover/username_profile_popover.jsx
index 37993094b..37993094b 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/profile_popover/username_profile_popover.jsx
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 10cd5fb55..d7b899b33 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 './user_profile.jsx';
+import UserProfile from './profile_popover/username_profile_popover.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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 41dd92e91..32d03e524 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 './user_profile.jsx';
+import UserProfile from './profile_popover/username_profile_popover.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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.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 09ea8c427..846840e40 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 './user_profile.jsx';
+import UserProfile from './profile_popover/username_profile_popover.jsx';
import FileAttachmentListContainer from './file_attachment_list_container.jsx';
-import ProfilePicture from './profile_picture.jsx';
+import ProfilePicture from './profile_popover/picture_profile_popover.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 3a7fc5d1c..e4e937432 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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
diff --git a/webapp/package.json b/webapp/package.json
index e7203f0d6..d3d12a1fb 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -15,6 +15,7 @@
"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 d64b42c3f..51f9bef65 100644
--- a/webapp/tests/utils/formatting_at_mentions.test.jsx
+++ b/webapp/tests/utils/formatting_at_mentions.test.jsx
@@ -50,30 +50,38 @@ describe('TextFormatting.AtMentions', function() {
);
});
- it('Implied at mentions', function() {
- // PLT-4454 Assume users exist for things that look like at mentions until we support the new mention syntax
+ 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'
+ );
+
assert.equal(
TextFormatting.autolinkAtMentions('@user', new Map(), {}),
- '$MM_ATMENTION0',
- 'should imply user exists and replace mention with token'
+ '@user'
);
assert.equal(
TextFormatting.autolinkAtMentions('@user.', new Map(), {}),
- '$MM_ATMENTION0.',
+ '@user.',
'should assume username doesn\'t end in punctuation'
);
- });
- it('Not at mentions', function() {
assert.equal(
- TextFormatting.autolinkAtMentions('user@host', new Map(), {user: {}, host: {}}),
- 'user@host'
+ TextFormatting.autolinkAtMentions('@will', new Map(), {william: {}}),
+ '@will',
+ 'should return same text without token'
);
assert.equal(
- TextFormatting.autolinkAtMentions('user@email.com', new Map(), {user: {}, email: {}}),
- 'user@email.com'
+ TextFormatting.autolinkAtMentions('@william', new Map(), {will: {}}),
+ '@william',
+ 'should return same text without token'
);
});
});
diff --git a/webapp/tests/utils/formatting_hashtags.test.jsx b/webapp/tests/utils/formatting_hashtags.test.jsx
index 1740a8ce7..0aadd6626 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(),
- "<p>#<a class='mention-link' href='#' data-mention='test'>@test</a></p>"
+ "<p>#<span data-mention='test'><a class='mention-link' href='#'>@test</a></span></p>"
);
assert.equal(
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 31ba8708d..cc5071047 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/user_profile.jsx';
+import UserProfile from 'components/profile_popover/username_profile_popover.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_picture.jsx';
+import ProfilePicture from 'components/profile_popover/picture_profile_popover.jsx';
import {showManagementOptions} from './channel_utils.jsx';
diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx
index c2c71a4e1..bd718b363 100644
--- a/webapp/utils/text_formatting.jsx
+++ b/webapp/utils/text_formatting.jsx
@@ -166,8 +166,13 @@ export function autolinkAtMentions(text, tokens, usernameMap) {
const index = tokens.size;
const alias = `$MM_ATMENTION${index}`;
+ let tokenValue = `<span data-mention='${username}'><a class='mention-link' href='#'>${mention}</a></span>`;
+ if (Constants.SPECIAL_MENTIONS.indexOf(username) >= 0) {
+ tokenValue = mention;
+ }
+
tokens.set(alias, {
- value: `<a class='mention-link' href='#' data-mention='${username}'>${mention}</a>`,
+ value: tokenValue,
originalText: mention
});
return alias;
@@ -181,8 +186,7 @@ export function autolinkAtMentions(text, tokens, usernameMap) {
const truncated = usernameLower.substring(0, c);
const suffix = usernameLower.substring(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])) {
+ if (mentionExists(truncated) && (c === usernameLower.length || punctuation.test(usernameLower[c]))) {
const alias = addToken(truncated, '@' + truncated);
return prefix + alias + suffix;
}
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index f56b9bb09..9b29c5362 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -989,12 +989,16 @@ export function changeOpacity(oldColor, opacity) {
}
export function getFullName(user) {
- 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;
+ 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;
+ }
}
return '';