summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp')
-rw-r--r--webapp/actions/team_actions.jsx2
-rw-r--r--webapp/client/client.jsx9
-rw-r--r--webapp/components/admin_console/generated_setting.jsx8
-rw-r--r--webapp/components/admin_console/password_settings.jsx14
-rw-r--r--webapp/components/admin_console/select_team_modal.jsx7
-rw-r--r--webapp/components/admin_console/user_item.jsx14
-rw-r--r--webapp/components/edit_post_modal.jsx3
-rw-r--r--webapp/components/help/components/composing.jsx2
-rw-r--r--webapp/components/navbar_dropdown.jsx17
-rw-r--r--webapp/components/post_view/components/post.jsx2
-rw-r--r--webapp/components/post_view/components/post_body.jsx14
-rw-r--r--webapp/components/post_view/components/post_header.jsx2
-rw-r--r--webapp/components/post_view/components/post_info.jsx8
-rw-r--r--webapp/components/post_view/components/post_list.jsx39
-rw-r--r--webapp/components/sidebar_header.jsx10
-rw-r--r--webapp/components/suggestion/search_channel_provider.jsx2
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx2
-rw-r--r--webapp/components/team_import_tab.jsx4
-rw-r--r--webapp/components/team_members_dropdown.jsx14
-rw-r--r--webapp/i18n/en.json4
-rw-r--r--webapp/root.html3
-rw-r--r--webapp/sass/layout/_post.scss5
-rw-r--r--webapp/tests/client_admin.test.jsx22
-rw-r--r--webapp/tests/client_user.test.jsx15
-rw-r--r--webapp/utils/utils.jsx8
25 files changed, 130 insertions, 100 deletions
diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx
index ea6be8504..3bf25c193 100644
--- a/webapp/actions/team_actions.jsx
+++ b/webapp/actions/team_actions.jsx
@@ -24,7 +24,7 @@ export function createTeam(team, onSuccess, onError) {
AppDispatcher.handleServerAction({
type: ActionTypes.CREATED_TEAM,
team: rteam,
- member: {team_id: rteam.id, user_id: UserStore.getCurrentId(), roles: 'admin'}
+ member: {team_id: rteam.id, user_id: UserStore.getCurrentId(), roles: 'team_admin team_user'}
});
browserHistory.push('/' + rteam.name + '/channels/town-square');
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
index 6aabee080..a5d179a0d 100644
--- a/webapp/client/client.jsx
+++ b/webapp/client/client.jsx
@@ -856,6 +856,15 @@ export default class Client {
end(this.handleResponse.bind(this, 'getMe', success, error));
}
+ getUser(userId, success, error) {
+ request.
+ get(`${this.getUserNeededRoute(userId)}/get`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getUser', success, error));
+ }
+
login(loginId, password, mfaToken, success, error) {
this.doLogin({login_id: loginId, password, token: mfaToken}, success, error);
diff --git a/webapp/components/admin_console/generated_setting.jsx b/webapp/components/admin_console/generated_setting.jsx
index ba6c5924b..d4feb9332 100644
--- a/webapp/components/admin_console/generated_setting.jsx
+++ b/webapp/components/admin_console/generated_setting.jsx
@@ -37,14 +37,9 @@ export default class GeneratedSetting extends React.Component {
constructor(props) {
super(props);
- this.handleChange = this.handleChange.bind(this);
this.regenerate = this.regenerate.bind(this);
}
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value === 'true');
- }
-
regenerate(e) {
e.preventDefault();
@@ -76,8 +71,7 @@ export default class GeneratedSetting extends React.Component {
id={this.props.id}
placeholder={this.props.placeholder}
value={this.props.value}
- onChange={this.handleChange}
- disabled={this.props.disabled}
+ disabled={true}
/>
{disabledText}
<div className='help-text'>
diff --git a/webapp/components/admin_console/password_settings.jsx b/webapp/components/admin_console/password_settings.jsx
index ad805b38c..6fa1dc9c4 100644
--- a/webapp/components/admin_console/password_settings.jsx
+++ b/webapp/components/admin_console/password_settings.jsx
@@ -23,6 +23,7 @@ export default class PasswordSettings extends AdminSettings {
this.getSampleErrorMsg = this.getSampleErrorMsg.bind(this);
this.handlePasswordLengthChange = this.handlePasswordLengthChange.bind(this);
+ this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
this.state = Object.assign(this.state, {
passwordMinimumLength: props.config.PasswordSettings.MinimumLength,
@@ -136,6 +137,11 @@ export default class PasswordSettings extends AdminSettings {
this.handleChange(id, value);
}
+ handleCheckboxChange(id, value) {
+ this.sampleErrorMsg = this.getSampleErrorMsg(this.state.passwordMinimumLength);
+ this.handleChange(id, value);
+ }
+
renderTitle() {
return (
<h3>
@@ -212,7 +218,7 @@ export default class PasswordSettings extends AdminSettings {
ref='lowercase'
defaultChecked={this.state.passwordLowercase}
name='admin.password.lowercase'
- onChange={this.handleChange}
+ onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.lowercase'
@@ -227,7 +233,7 @@ export default class PasswordSettings extends AdminSettings {
ref='uppercase'
defaultChecked={this.state.passwordUppercase}
name='admin.password.uppercase'
- onChange={this.handleChange}
+ onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.uppercase'
@@ -242,7 +248,7 @@ export default class PasswordSettings extends AdminSettings {
ref='number'
defaultChecked={this.state.passwordNumber}
name='admin.password.number'
- onChange={this.handleChange}
+ onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.number'
@@ -257,7 +263,7 @@ export default class PasswordSettings extends AdminSettings {
ref='symbol'
defaultChecked={this.state.passwordSymbol}
name='admin.password.symbol'
- onChange={this.handleChange}
+ onChange={this.handleCheckboxChange}
/>
<FormattedMessage
id='admin.password.symbol'
diff --git a/webapp/components/admin_console/select_team_modal.jsx b/webapp/components/admin_console/select_team_modal.jsx
index 2ea6cc58c..a661dd2f0 100644
--- a/webapp/components/admin_console/select_team_modal.jsx
+++ b/webapp/components/admin_console/select_team_modal.jsx
@@ -25,10 +25,13 @@ export default class SelectTeamModal extends React.Component {
this.props.onModalDismissed();
}
compare(a, b) {
- if (a.display_name < b.display_name) {
+ const teamA = a.display_name.toLowerCase();
+ const teamB = b.display_name.toLowerCase();
+
+ if (teamA < teamB) {
return -1;
}
- if (a.display_name > b.display_name) {
+ if (teamA > teamB) {
return 1;
}
return 0;
diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx
index 974ef8bc9..78fdb085c 100644
--- a/webapp/components/admin_console/user_item.jsx
+++ b/webapp/components/admin_console/user_item.jsx
@@ -97,12 +97,12 @@ export default class UserItem extends React.Component {
e.preventDefault();
const me = UserStore.getCurrentUser();
if (this.props.user.id === me.id) {
- this.handleDemote(this.props.user, 'admin');
+ this.handleDemote(this.props.user, 'team_user team_admin');
} else {
Client.updateRoles(
this.props.team.id,
this.props.user.id,
- 'admin',
+ 'team_user team_admin',
() => {
this.props.refreshProfiles();
},
@@ -119,7 +119,7 @@ export default class UserItem extends React.Component {
Client.updateRoles(
this.props.team.id,
this.props.user.id,
- 'system_admin',
+ 'system_user system_admin',
() => {
this.props.refreshProfiles();
},
@@ -238,11 +238,11 @@ export default class UserItem extends React.Component {
const me = UserStore.getCurrentUser();
const email = user.email;
- let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin';
- let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
- let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin';
+ let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
+ let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
+ let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
let showMakeActive = false;
- let showMakeNotActive = user.roles !== 'system_admin';
+ let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
const showMfaReset = mfaEnabled && user.mfa_active;
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index 00c8e0f09..24b1b5c3d 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -4,6 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'client/web_client.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import Textbox from './textbox.jsx';
@@ -94,7 +95,7 @@ export default class EditPostModal extends React.Component {
}
handleEditKeyPress(e) {
- if (!this.state.ctrlSend && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
+ if (!UserAgent.isMobileApp() && !this.state.ctrlSend && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
e.preventDefault();
ReactDOM.findDOMNode(this.refs.editbox).blur();
this.handleEdit();
diff --git a/webapp/components/help/components/composing.jsx b/webapp/components/help/components/composing.jsx
index 398580ffa..9092027b7 100644
--- a/webapp/components/help/components/composing.jsx
+++ b/webapp/components/help/components/composing.jsx
@@ -15,7 +15,7 @@ export default class HelpComposing extends React.Component {
message.push(localizeMessage('help.composing.types', '## Message Types\nReply to posts to keep conversations organized in threads.'));
message.push(localizeMessage('help.composing.posts', '#### Posts\nPosts can be considered parent messages. They are the messages that often start a thread of replies. Posts are composed and sent from the text input box at the bottom of the center pane.'));
message.push(localizeMessage('help.composing.replies', '#### Replies\nReply to a message by clicking the reply icon next to any message text. This action opens the right-hand-side (RHS) where you can see the message thread, then compose and send your reply. Replies are indented slightly in the center pane to indicate that they are child messages of a parent post.\n\nWhen composing a reply in the right-hand side, click the expand/collapse icon with two arrows at the top of the sidebar to make things easier to read.'));
- message.push(localizeMessage('help.composing.posting', '## Posting a Message\nWrite a message by typing into the text input box, then press **ENTER** to send it. Use **Shift + ENTER** to create a new line without sending a message. To send messages by pressing **Ctrl+Enter** go to **Main Menu > Account Settings > Send messages on Ctrl + Enter**.'));
+ message.push(localizeMessage('help.composing.posting', '## Posting a Message\nWrite a message by typing into the text input box, then press **ENTER** to send it. Use **Shift + ENTER** to create a new line without sending a message. To send messages by pressing **Ctrl + ENTER** go to **Main Menu > Account Settings > Send messages on Ctrl + ENTER**.'));
message.push(localizeMessage('help.composing.editing', '## Editing a Message\nEdit a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Edit**. After making modifications to the message text, press **ENTER** to save the modifications. Message edits do not trigger new @mention notifications, desktop notifications or notification sounds.'));
message.push(localizeMessage('help.composing.deleting', '## Deleting a message\nDelete a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Delete**. System and Team Admins can delete any message on their system or team.'));
message.push(localizeMessage('help.composing.linking', '## Linking to a message\nThe **Permalink** feature creates a link to any message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives. Users who are not a member of the channel where the message was posted cannot view the permalink. Get the permalink to any message by clicking the **[...]** icon next to the message text > **Permalink** > **Copy Link**.'));
diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx
index f7547f803..413942865 100644
--- a/webapp/components/navbar_dropdown.jsx
+++ b/webapp/components/navbar_dropdown.jsx
@@ -1,8 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -49,14 +47,6 @@ export default class NavbarDropdown extends React.Component {
}
componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
- $('.sidebar--left .dropdown-menu').scrollTop(0);
- this.blockToggle = true;
- setTimeout(() => {
- this.blockToggle = false;
- }, 100);
- });
-
TeamStore.addChangeListener(this.onTeamChange);
document.addEventListener('keydown', this.openAccountSettings);
}
@@ -69,7 +59,6 @@ export default class NavbarDropdown extends React.Component {
}
componentWillUnmount() {
- $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
TeamStore.removeChangeListener(this.onTeamChange);
document.removeEventListener('keydown', this.openAccountSettings);
}
@@ -363,9 +352,8 @@ export default class NavbarDropdown extends React.Component {
<a
href='#'
className='dropdown-toggle'
- data-toggle='dropdown'
role='button'
- aria-expanded='false'
+ onClick={this.props.toggleDropdown}
>
<span
className='dropdown__icon'
@@ -449,5 +437,6 @@ NavbarDropdown.propTypes = {
teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string,
- currentUser: React.PropTypes.object
+ currentUser: React.PropTypes.object,
+ toggleDropdown: React.PropTypes.function
};
diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx
index e9019bf38..7aa0c028e 100644
--- a/webapp/components/post_view/components/post.jsx
+++ b/webapp/components/post_view/components/post.jsx
@@ -236,7 +236,6 @@ export default class Post extends React.Component {
post={post}
sameRoot={this.props.sameRoot}
commentCount={commentCount}
- isCommentMention={this.props.isCommentMention}
handleCommentClick={this.handleCommentClick}
handleDropdownOpened={this.handleDropdownOpened}
isLastComment={this.props.isLastComment}
@@ -255,6 +254,7 @@ export default class Post extends React.Component {
handleCommentClick={this.handleCommentClick}
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
+ isCommentMention={this.props.isCommentMention}
/>
</div>
</div>
diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx
index 8c3b3009f..3295f5bac 100644
--- a/webapp/components/post_view/components/post_body.jsx
+++ b/webapp/components/post_view/components/post_body.jsx
@@ -23,6 +23,10 @@ export default class PostBody extends React.Component {
this.removePost = this.removePost.bind(this);
}
shouldComponentUpdate(nextProps) {
+ if (nextProps.isCommentMention !== this.props.isCommentMention) {
+ return true;
+ }
+
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
@@ -190,10 +194,15 @@ export default class PostBody extends React.Component {
);
}
+ let mentionHighlightClass = '';
+ if (this.props.isCommentMention) {
+ mentionHighlightClass = 'mention-comment';
+ }
+
return (
<div>
{comment}
- <div className='post__body'>
+ <div className={'post__body ' + mentionHighlightClass}>
{messageWithAdditionalContent}
{fileAttachmentHolder}
</div>
@@ -208,5 +217,6 @@ PostBody.propTypes = {
retryPost: React.PropTypes.func.isRequired,
handleCommentClick: React.PropTypes.func.isRequired,
compactDisplay: React.PropTypes.bool,
- previewCollapsed: React.PropTypes.string
+ previewCollapsed: React.PropTypes.string,
+ isCommentMention: React.PropTypes.bool
};
diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx
index 27715e5f5..5900c8281 100644
--- a/webapp/components/post_view/components/post_header.jsx
+++ b/webapp/components/post_view/components/post_header.jsx
@@ -64,7 +64,6 @@ export default class PostHeader extends React.Component {
<PostInfo
post={post}
commentCount={this.props.commentCount}
- isCommentMention={this.props.isCommentMention}
handleCommentClick={this.props.handleCommentClick}
handleDropdownOpened={this.props.handleDropdownOpened}
allowReply={!isSystemMessage}
@@ -92,7 +91,6 @@ PostHeader.propTypes = {
user: React.PropTypes.object,
currentUser: React.PropTypes.object.isRequired,
commentCount: React.PropTypes.number.isRequired,
- isCommentMention: React.PropTypes.bool.isRequired,
isLastComment: React.PropTypes.bool.isRequired,
handleCommentClick: React.PropTypes.func.isRequired,
handleDropdownOpened: 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 3c0eb9880..c95a121da 100644
--- a/webapp/components/post_view/components/post_info.jsx
+++ b/webapp/components/post_view/components/post_info.jsx
@@ -250,7 +250,6 @@ export default class PostInfo extends React.Component {
var post = this.props.post;
var comments = '';
var showCommentClass = '';
- var highlightMentionClass = '';
var commentCountText = this.props.commentCount;
const flagIcon = Constants.FLAG_ICON_SVG;
@@ -260,15 +259,11 @@ export default class PostInfo extends React.Component {
commentCountText = '';
}
- if (this.props.isCommentMention) {
- highlightMentionClass = ' mention--highlight';
- }
-
if (post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING && !Utils.isPostEphemeral(post) && this.props.allowReply) {
comments = (
<a
href='#'
- className={'comment-icon__container' + showCommentClass + highlightMentionClass}
+ className={'comment-icon__container' + showCommentClass}
onClick={this.props.handleCommentClick}
>
<span
@@ -386,7 +381,6 @@ PostInfo.defaultProps = {
PostInfo.propTypes = {
post: React.PropTypes.object.isRequired,
commentCount: React.PropTypes.number.isRequired,
- isCommentMention: React.PropTypes.bool.isRequired,
isLastComment: React.PropTypes.bool.isRequired,
allowReply: React.PropTypes.bool.isRequired,
handleCommentClick: React.PropTypes.func.isRequired,
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index b008245f6..a05507703 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -253,35 +253,34 @@ export default class PostList extends React.Component {
}
let commentCount = 0;
- let nonOwnCommentsExists = false;
let isCommentMention = false;
+ let lastCommentOnThreadTime = Number.MAX_SAFE_INTEGER;
let commentRootId;
if (parentPost) {
commentRootId = post.root_id;
} else {
commentRootId = post.id;
}
- if (commentRootId) {
- const commentsNotifyLevel = this.props.currentUser.notify_props.comments || 'never';
- for (const postId in posts) {
- if (posts[postId].root_id === commentRootId) {
- commentCount += 1;
- if (posts[postId].user_id !== this.props.currentUser.id) {
- nonOwnCommentsExists = true;
- }
- if (posts[postId].user_id === this.props.currentUser.id && commentsNotifyLevel === 'any' && !isCommentMention) {
- for (const nextPostId in posts) {
- if (posts[nextPostId].root_id === commentRootId && posts[nextPostId].user_id !== this.props.currentUser.id &&
- posts[postId].create_at < posts[nextPostId].create_at) {
- isCommentMention = true;
- break;
- }
- }
- }
+
+ for (const postId in posts) {
+ if (posts[postId].root_id === commentRootId) {
+ commentCount += 1;
+ if (posts[postId].user_id === userId && (lastCommentOnThreadTime === Number.MAX_SAFE_INTEGER || lastCommentOnThreadTime < posts[postId].create_at)) {
+ lastCommentOnThreadTime = posts[postId].create_at;
}
}
- if (nonOwnCommentsExists && posts[commentRootId].user_id === this.props.currentUser.id && commentsNotifyLevel !== 'never') {
- isCommentMention = true;
+ }
+
+ if (parentPost && commentRootId) {
+ const commentsNotifyLevel = this.props.currentUser.notify_props.comments || 'never';
+ const notCurrentUser = post.user_id !== userId || (post.props && post.props.from_webhook);
+ const notViewed = this.props.lastViewed !== 0 && post.create_at > this.props.lastViewed;
+ if (notCurrentUser && notViewed) {
+ if (commentsNotifyLevel === 'any' && (posts[commentRootId].user_id === userId || post.create_at > lastCommentOnThreadTime)) {
+ isCommentMention = true;
+ } else if (commentsNotifyLevel === 'root' && posts[commentRootId].user_id === userId) {
+ isCommentMention = true;
+ }
}
}
diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx
index 9a7db54f8..a57a809be 100644
--- a/webapp/components/sidebar_header.jsx
+++ b/webapp/components/sidebar_header.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
import NavbarDropdown from './navbar_dropdown.jsx';
import 'bootstrap';
@@ -49,7 +48,13 @@ export default class SidebarHeader extends React.Component {
this.refs.dropdown.blockToggle = false;
return;
}
- $('.team__header').find('.dropdown-toggle').dropdown('toggle');
+ const menu = document.querySelector('.team__header .dropdown-toggle');
+ const isOpen = menu.parentElement.classList.toggle('open');
+ menu.setAttribute('aria-expanded', isOpen);
+
+ if (!isOpen) {
+ document.querySelector('.sidebar--left .dropdown-menu').scrollTop = 0;
+ }
}
render() {
var me = this.props.currentUser;
@@ -100,6 +105,7 @@ export default class SidebarHeader extends React.Component {
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
currentUser={this.props.currentUser}
+ toggleDropdown={this.toggleDropdown}
/>
</div>
);
diff --git a/webapp/components/suggestion/search_channel_provider.jsx b/webapp/components/suggestion/search_channel_provider.jsx
index 2e60d579f..6513f0f2b 100644
--- a/webapp/components/suggestion/search_channel_provider.jsx
+++ b/webapp/components/suggestion/search_channel_provider.jsx
@@ -31,7 +31,7 @@ class SearchChannelSuggestion extends Suggestion {
export default class SearchChannelProvider {
handlePretextChanged(suggestionId, pretext) {
- const captured = (/\b(?:in|channel):\s*(\S*)$/i).exec(pretext);
+ const captured = (/\b(?:in|channel):\s*(\S*)$/i).exec(pretext.toLowerCase());
if (captured) {
const channelPrefix = captured[1];
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
index 7a5bdabf1..e33c206a7 100644
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ b/webapp/components/suggestion/search_user_provider.jsx
@@ -35,7 +35,7 @@ class SearchUserSuggestion extends Suggestion {
export default class SearchUserProvider {
handlePretextChanged(suggestionId, pretext) {
- const captured = (/\bfrom:\s*(\S*)$/i).exec(pretext);
+ const captured = (/\bfrom:\s*(\S*)$/i).exec(pretext.toLowerCase());
if (captured) {
const usernamePrefix = captured[1];
diff --git a/webapp/components/team_import_tab.jsx b/webapp/components/team_import_tab.jsx
index 49d7f7f1f..0fee6a3d8 100644
--- a/webapp/components/team_import_tab.jsx
+++ b/webapp/components/team_import_tab.jsx
@@ -29,8 +29,8 @@ class TeamImportTab extends React.Component {
};
}
- onImportFailure() {
- this.setState({status: 'fail', link: ''});
+ onImportFailure(e, err, res) {
+ this.setState({status: 'fail', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(res.text)});
}
onImportSuccess(data, res) {
diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx
index 527161103..6b9390ade 100644
--- a/webapp/components/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown.jsx
@@ -37,12 +37,12 @@ export default class TeamMembersDropdown extends React.Component {
handleMakeMember() {
const me = UserStore.getCurrentUser();
if (this.props.user.id === me.id) {
- this.handleDemote(this.props.user, '');
+ this.handleDemote(this.props.user, 'team_user');
} else {
Client.updateRoles(
this.props.teamMember.team_id,
this.props.user.id,
- '',
+ 'team_user',
() => {
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfiles();
@@ -93,12 +93,12 @@ export default class TeamMembersDropdown extends React.Component {
handleMakeAdmin() {
const me = UserStore.getCurrentUser();
if (this.props.user.id === me.id) {
- this.handleDemote(this.props.user, 'admin');
+ this.handleDemote(this.props.user, 'team_user team_admin');
} else {
Client.updateRoles(
this.props.teamMember.team_id,
this.props.user.id,
- 'admin',
+ 'team_user team_admin',
() => {
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
AsyncClient.getProfiles();
@@ -186,10 +186,10 @@ export default class TeamMembersDropdown extends React.Component {
}
const me = UserStore.getCurrentUser();
- let showMakeMember = teamMember.roles === 'admin' && user.roles !== 'system_admin';
- let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin';
+ let showMakeMember = Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
+ let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
let showMakeActive = false;
- let showMakeNotActive = user.roles !== 'system_admin';
+ let showMakeNotActive = Utils.isSystemAdmin(user.roles);
if (user.delete_at > 0) {
currentRoles = (
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 45afbd184..66e994893 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -1230,7 +1230,7 @@
"help.composing.deleting": "## Deleting a message\nDelete a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Delete**. System and Team Admins can delete any message on their system or team.",
"help.composing.editing": "## Editing a Message\nEdit a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Edit**. After making modifications to the message text, press **ENTER** to save the modifications. Message edits do not trigger new @mention notifications, desktop notifications or notification sounds.",
"help.composing.linking": "## Linking to a message\nThe **Permalink** feature creates a link to any message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives. Users who are not a member of the channel where the message was posted cannot view the permalink. Get the permalink to any message by clicking the **[...]** icon next to the message text > **Permalink** > **Copy Link**.",
- "help.composing.posting": "## Posting a Message\nWrite a message by typing into the text input box, then press **ENTER** to send it. Use **Shift + ENTER** to create a new line without sending a message. To send messages by pressing **CTRL+ENTER** go to **Main Menu > Account Settings > Send messages on CTRL + ENTER**.",
+ "help.composing.posting": "## Posting a Message\nWrite a message by typing into the text input box, then press **ENTER** to send it. Use **Shift + ENTER** to create a new line without sending a message. To send messages by pressing **CTRL + ENTER** go to **Main Menu > Account Settings > Send messages on CTRL + ENTER**.",
"help.composing.posts": "#### Posts\nPosts can be considered parent messages. They are the messages that often start a thread of replies. Posts are composed and sent from the text input box at the bottom of the center pane.",
"help.composing.replies": "#### Replies\nReply to a message by clicking the reply icon next to any message text. This action opens the right-hand-side (RHS) where you can see the message thread, then compose and send your reply. Replies are indented slightly in the center pane to indicate that they are child messages of a parent post.\n\nWhen composing a reply in the right-hand side, click the expand/collapse icon with two arrows at the top of the sidebar to make things easier to read.",
"help.composing.title": "# Sending Messages\n_____",
@@ -1238,7 +1238,7 @@
"help.formatting.checklist": "Make a task list by including square brackets:",
"help.formatting.checklistExample": "- [ ] Item one\n- [ ] Item two\n- [x] Completed item",
"help.formatting.code": "## Code Block\n\nCreate a code block by indenting each line by four spaces, or by placing ``` on the line above and below your code.",
- "help.formatting.codeBlock": "code block",
+ "help.formatting.codeBlock": "Code block",
"help.formatting.emojiExample": ":smile: :+1: :sheep:",
"help.formatting.emojis": "## Emojis\n\nOpen the emoji autocomplete by typing `:`. A full list of emojis can be found [here](http://www.emoji-cheat-sheet.com/). It is also possible to create your own [Custom Emoji](http://docs.mattermost.com/help/settings/custom-emoji.html) if the emoji you want to use doesn't exist.",
"help.formatting.example": "Example:",
diff --git a/webapp/root.html b/webapp/root.html
index 7987a52fc..bca775c8b 100644
--- a/webapp/root.html
+++ b/webapp/root.html
@@ -52,5 +52,8 @@
<script>
window.setup_root();
</script>
+ <noscript>
+ To use Mattermost, please enable JavaScript.
+ </noscript>
</body>
</html>
diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss
index c5901ca0e..41033a67c 100644
--- a/webapp/sass/layout/_post.scss
+++ b/webapp/sass/layout/_post.scss
@@ -681,6 +681,11 @@ body.ios {
.post__body {
border-left: 4px solid $gray;
padding-left: 7px;
+
+ &.mention-comment {
+ border-left: 4px solid $yellow;
+ border-color: $yellow;
+ }
}
}
diff --git a/webapp/tests/client_admin.test.jsx b/webapp/tests/client_admin.test.jsx
index a6a6e1a85..2ed08dc62 100644
--- a/webapp/tests/client_admin.test.jsx
+++ b/webapp/tests/client_admin.test.jsx
@@ -15,7 +15,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -30,7 +30,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -45,7 +45,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -69,7 +69,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -84,7 +84,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -99,7 +99,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -114,7 +114,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -131,7 +131,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -148,7 +148,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -167,7 +167,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
@@ -186,7 +186,7 @@ describe('Client.Admin', function() {
done(new Error('should need system admin permissions'));
},
function(err) {
- assert.equal(err.id, 'api.context.system_permissions.app_error');
+ assert.equal(err.id, 'api.context.permissions.app_error');
done();
}
);
diff --git a/webapp/tests/client_user.test.jsx b/webapp/tests/client_user.test.jsx
index 116eee4ae..6c65e8ef5 100644
--- a/webapp/tests/client_user.test.jsx
+++ b/webapp/tests/client_user.test.jsx
@@ -21,6 +21,21 @@ describe('Client.User', function() {
});
});
+ it('getUser', function(done) {
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().getUser(
+ TestHelper.basicUser().id,
+ function(data) {
+ assert.equal(data.id, TestHelper.basicUser().id);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
+ });
+
it('getInitialLoad', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().getInitialLoad(
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 2780196db..4dc9aab86 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -56,7 +56,7 @@ export function isInRole(roles, inRole) {
}
export function isAdmin(roles) {
- if (isInRole(roles, 'admin')) {
+ if (isInRole(roles, 'team_admin')) {
return true;
}
@@ -663,9 +663,7 @@ export function applyTheme(theme) {
if (theme.mentionHighlightBg) {
changeCss('.app__body .mention--highlight, .app__body .search-highlight', 'background:' + theme.mentionHighlightBg, 1);
- }
-
- if (theme.mentionHighlightBg) {
+ changeCss('.mention-comment', 'border-color:' + theme.mentionHighlightBg + ' !important', 1);
changeCss('.app__body .post.post--highlight', 'background:' + changeOpacity(theme.mentionHighlightBg, 0.5), 1);
}
@@ -1375,4 +1373,4 @@ export function handleFormattedTextClick(e) {
browserHistory.push(linkAttribute.value);
}
}
-} \ No newline at end of file
+}