summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx1
-rw-r--r--web/react/components/admin_console/user_item.jsx2
-rw-r--r--web/react/components/audit_table.jsx66
-rw-r--r--web/react/components/channel_info_modal.jsx2
-rw-r--r--web/react/components/channel_loader.jsx4
-rw-r--r--web/react/components/claim/claim_account.jsx6
-rw-r--r--web/react/components/claim/email_to_sso.jsx111
-rw-r--r--web/react/components/claim/sso_to_email.jsx116
-rw-r--r--web/react/components/create_comment.jsx55
-rw-r--r--web/react/components/create_post.jsx36
-rw-r--r--web/react/components/file_upload.jsx3
-rw-r--r--web/react/components/member_list_team_item.jsx127
-rw-r--r--web/react/components/navbar.jsx2
-rw-r--r--web/react/components/post_deleted_modal.jsx122
-rw-r--r--web/react/components/post_info.jsx44
-rw-r--r--web/react/components/rhs_comment.jsx24
-rw-r--r--web/react/components/rhs_root_post.jsx38
-rw-r--r--web/react/components/team_general_tab.jsx8
-rw-r--r--web/react/components/team_signup_email_item.jsx2
-rw-r--r--web/react/components/team_signup_send_invites_page.jsx4
-rw-r--r--web/react/components/user_settings/custom_theme_chooser.jsx113
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx2
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx13
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx14
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx8
-rw-r--r--web/react/components/user_settings/user_settings_theme.jsx2
26 files changed, 627 insertions, 298 deletions
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index eadd8d412..795b19eec 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -50,6 +50,7 @@ export default class AdminSidebar extends React.Component {
removeTeam(teamId, e) {
e.preventDefault();
+ e.stopPropagation();
Reflect.deleteProperty(this.props.selectedTeams, teamId);
this.props.removeSelectedTeam(teamId);
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 0c1a55cc1..009a9f004 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -353,7 +353,7 @@ export default class UserItem extends React.Component {
return (
<tr>
- <td className='row member-div'>
+ <td className='row member-div padding--equal'>
<img
className='post-profile-img pull-left'
src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
diff --git a/web/react/components/audit_table.jsx b/web/react/components/audit_table.jsx
index 49892ff98..31d04f19b 100644
--- a/web/react/components/audit_table.jsx
+++ b/web/react/components/audit_table.jsx
@@ -183,6 +183,26 @@ const holders = defineMessages({
loginFailure: {
id: 'audit_table.loginFailure',
defaultMessage: ' (Login failure)'
+ },
+ attemptedLicenseAdd: {
+ id: 'audit_table.attemptedLicenseAdd',
+ defaultMessage: 'Attempted to add new license'
+ },
+ successfullLicenseAdd: {
+ id: 'audit_table.successfullLicenseAdd',
+ defaultMessage: 'Successfully added new license'
+ },
+ failedExpiredLicenseAdd: {
+ id: 'audit_table.failedExpiredLicenseAdd',
+ defaultMessage: 'Failed to add a new license as it has either expired or not yet been started'
+ },
+ failedInvalidLicenseAdd: {
+ id: 'audit_table.failedInvalidLicenseAdd',
+ defaultMessage: 'Failed to add an invalid license'
+ },
+ licenseRemoved: {
+ id: 'audit_table.licenseRemoved',
+ defaultMessage: 'Successfully removed a license'
}
});
@@ -327,17 +347,17 @@ export function formatAuditInfo(audit, formatMessage) {
switch (actionURL) {
case '/channels/create':
- auditDesc = formatMessage(holders.channelCreated, {channelName: channelName});
+ auditDesc = formatMessage(holders.channelCreated, {channelName});
break;
case '/channels/create_direct':
auditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
break;
case '/channels/update':
- auditDesc = formatMessage(holders.nameUpdated, {channelName: channelName});
+ auditDesc = formatMessage(holders.nameUpdated, {channelName});
break;
case '/channels/update_desc': // support the old path
case '/channels/update_header':
- auditDesc = formatMessage(holders.headerUpdated, {channelName: channelName});
+ auditDesc = formatMessage(holders.headerUpdated, {channelName});
break;
default: {
let userIdField = [];
@@ -356,9 +376,9 @@ export function formatAuditInfo(audit, formatMessage) {
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(actionURL)) {
auditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(actionURL)) {
- auditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName});
+ auditDesc = formatMessage(holders.userAdded, {username, channelName});
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(actionURL)) {
- auditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName});
+ auditDesc = formatMessage(holders.userRemoved, {username, channelName});
}
break;
@@ -495,25 +515,25 @@ export function formatAuditInfo(audit, formatMessage) {
break;
}
} else if (actionURL.indexOf('/hooks') === 0) {
- const webhookInfo = audit.extra_info.split(' ');
+ const webhookInfo = audit.extra_info;
switch (actionURL) {
case '/hooks/incoming/create':
- if (webhookInfo[0] === 'attempt') {
+ if (webhookInfo === 'attempt') {
auditDesc = formatMessage(holders.attemptedWebhookCreate);
- } else if (webhookInfo[0] === 'success') {
+ } else if (webhookInfo === 'success') {
auditDesc = formatMessage(holders.succcessfullWebhookCreate);
- } else if (webhookInfo[0] === 'fail - bad channel permissions') {
+ } else if (webhookInfo === 'fail - bad channel permissions') {
auditDesc = formatMessage(holders.failedWebhookCreate);
}
break;
case '/hooks/incoming/delete':
- if (webhookInfo[0] === 'attempt') {
+ if (webhookInfo === 'attempt') {
auditDesc = formatMessage(holders.attemptedWebhookDelete);
- } else if (webhookInfo[0] === 'success') {
+ } else if (webhookInfo === 'success') {
auditDesc = formatMessage(holders.successfullWebhookDelete);
- } else if (webhookInfo[0] === 'fail - inappropriate conditions') {
+ } else if (webhookInfo === 'fail - inappropriate conditions') {
auditDesc = formatMessage(holders.failedWebhookDelete);
}
@@ -521,6 +541,28 @@ export function formatAuditInfo(audit, formatMessage) {
default:
break;
}
+ } else if (actionURL.indexOf('/license') === 0) {
+ const licenseInfo = audit.extra_info;
+
+ switch (actionURL) {
+ case '/license/add':
+ if (licenseInfo === 'attempt') {
+ auditDesc = formatMessage(holders.attemptedLicenseAdd);
+ } else if (licenseInfo === 'success') {
+ auditDesc = formatMessage(holders.successfullLicenseAdd);
+ } else if (licenseInfo === 'failed - expired or non-started license') {
+ auditDesc = formatMessage(holders.failedExpiredLicenseAdd);
+ } else if (licenseInfo === 'failed - invalid license') {
+ auditDesc = formatMessage(holders.failedInvalidLicenseAdd);
+ }
+
+ break;
+ case '/license/remove':
+ auditDesc = formatMessage(holders.licenseRemoved);
+ break;
+ default:
+ break;
+ }
} else {
switch (actionURL) {
case '/logout':
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index 5067f5913..83f5aba65 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -56,7 +56,7 @@ class ChannelInfoModal extends React.Component {
</div>
<div className='col-sm-9'>{channelURL}</div>
</div>
- <div className='row'>
+ <div className='row form-group'>
<div className='col-sm-3 info__label'>
<FormattedMessage
id='channel_info.id'
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 174c8c4e1..f3000ee05 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -95,6 +95,8 @@ class ChannelLoader extends React.Component {
$(window).on('focus', function windowFocus() {
AsyncClient.updateLastViewedAt();
+ ChannelStore.resetCounts(ChannelStore.getCurrentId());
+ ChannelStore.emitChange();
window.isActive = true;
});
@@ -185,4 +187,4 @@ ChannelLoader.propTypes = {
intl: intlShape.isRequired
};
-export default injectIntl(ChannelLoader); \ No newline at end of file
+export default injectIntl(ChannelLoader);
diff --git a/web/react/components/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx
index 87026b762..5b3b584ee 100644
--- a/web/react/components/claim/claim_account.jsx
+++ b/web/react/components/claim/claim_account.jsx
@@ -43,11 +43,7 @@ export default class ClaimAccount extends React.Component {
);
}
- return (
- <div>
- {content}
- </div>
- );
+ return content;
}
}
diff --git a/web/react/components/claim/email_to_sso.jsx b/web/react/components/claim/email_to_sso.jsx
index 3d4b9e91f..87e86697c 100644
--- a/web/react/components/claim/email_to_sso.jsx
+++ b/web/react/components/claim/email_to_sso.jsx
@@ -70,62 +70,69 @@ class EmailToSSO extends React.Component {
const uiType = Utils.toTitleCase(this.props.type) + ' SSO';
return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
+ <div>
+ <h3>
+ <FormattedMessage
+ id='claim.email_to_sso.title'
+ defaultMessage='Switch Email/Password Account to {uiType}'
+ values={{
+ uiType: uiType
+ }}
+ />
+ </h3>
+ <form onSubmit={this.submit}>
+ <p>
<FormattedMessage
- id='claim.email_to_sso.title'
- defaultMessage='Switch Email/Password Account to {uiType}'
+ id='claim.email_to_sso.ssoType'
+ defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO. You must already have a valid {type} account'
+ values={{
+ type: Utils.toTitleCase(this.props.type)
+ }}
+ />
+ </p>
+ <p>
+ <FormattedMessage
+ id='claim.email_to_sso.ssoNote'
+ defaultMessage='You must already have a valid {type} account'
+ values={{
+ type: Utils.toTitleCase(this.props.type)
+ }}
+ />
+ </p>
+ <p>
+ <FormattedMessage
+ id='claim.email_to_sso.enterPwd'
+ defaultMessage='Enter the password for your {team} {site} account'
+ values={{
+ team: this.props.teamDisplayName,
+ site: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder={this.props.intl.formatMessage(holders.pwd)}
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='claim.email_to_sso.switchTo'
+ defaultMessage='Switch account to {uiType}'
values={{
uiType: uiType
}}
/>
- </h3>
- <form onSubmit={this.submit}>
- <p>
- <FormattedMessage
- id='claim.email_to_sso.ssoType'
- defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO'
- values={{
- type: Utils.toTitleCase(this.props.type)
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.email_to_sso.enterPwd'
- defaultMessage='Enter the password for your {team} {site} account'
- values={{
- team: this.props.teamDisplayName,
- site: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={this.props.intl.formatMessage(holders.pwd)}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.email_to_sso.switchTo'
- defaultMessage='Switch account to {uiType}'
- values={{
- uiType: uiType
- }}
- />
- </button>
- </form>
- </div>
+ </button>
+ </form>
</div>
);
}
@@ -141,4 +148,4 @@ EmailToSSO.propTypes = {
teamDisplayName: React.PropTypes.string.isRequired
};
-export default injectIntl(EmailToSSO); \ No newline at end of file
+export default injectIntl(EmailToSSO);
diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx
index 73ff13cc9..74137082a 100644
--- a/web/react/components/claim/sso_to_email.jsx
+++ b/web/react/components/claim/sso_to_email.jsx
@@ -86,69 +86,67 @@ class SSOToEmail extends React.Component {
const uiType = Utils.toTitleCase(this.props.currentType) + ' SSO';
return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
+ <div>
+ <h3>
+ <FormattedMessage
+ id='claim.sso_to_email.title'
+ defaultMessage='Switch {type} Account to Email'
+ values={{
+ type: uiType
+ }}
+ />
+ </h3>
+ <form onSubmit={this.submit}>
+ <p>
<FormattedMessage
- id='claim.sso_to_email.title'
- defaultMessage='Switch {type} Account to Email'
+ id='claim.sso_to_email.description'
+ defaultMessage='Upon changing your account type, you will only be able to login with your email and password.'
+ />
+ </p>
+ <p>
+ <FormattedMessage
+ id='claim.sso_to_email_newPwd'
+ defaultMessage='Enter a new password for your {team} {site} account'
+ values={{
+ team: this.props.teamDisplayName,
+ site: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder={formatMessage(holders.newPwd)}
+ spellCheck='false'
+ />
+ </div>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='passwordconfirm'
+ ref='passwordconfirm'
+ placeholder={formatMessage(holders.confirm)}
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='claim.sso_to_email.switchTo'
+ defaultMessage='Switch {type} to email and password'
values={{
type: uiType
}}
/>
- </h3>
- <form onSubmit={this.submit}>
- <p>
- <FormattedMessage
- id='claim.sso_to_email.description'
- defaultMessage='Upon changing your account type, you will only be able to login with your email and password.'
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.sso_to_email_newPwd'
- defaultMessage='Enter a new password for your {team} {site} account'
- values={{
- team: this.props.teamDisplayName,
- site: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={formatMessage(holders.newPwd)}
- spellCheck='false'
- />
- </div>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='passwordconfirm'
- ref='passwordconfirm'
- placeholder={formatMessage(holders.confirm)}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.sso_to_email.switchTo'
- defaultMessage='Switch {type} to email and password'
- values={{
- type: uiType
- }}
- />
- </button>
- </form>
- </div>
+ </button>
+ </form>
</div>
);
}
@@ -164,4 +162,4 @@ SSOToEmail.propTypes = {
teamDisplayName: React.PropTypes.string.isRequired
};
-export default injectIntl(SSOToEmail); \ No newline at end of file
+export default injectIntl(SSOToEmail);
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 55dd8276c..24e0ff6e9 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -7,6 +7,7 @@ import * as AsyncClient from '../utils/async_client.jsx';
import SocketStore from '../stores/socket_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import UserStore from '../stores/user_store.jsx';
+import PostDeletedModal from './post_deleted_modal.jsx';
import PostStore from '../stores/post_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
import Textbox from './textbox.jsx';
@@ -60,6 +61,8 @@ class CreateComment extends React.Component {
this.handleResize = this.handleResize.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.focusTextbox = this.focusTextbox.bind(this);
+ this.showPostDeletedModal = this.showPostDeletedModal.bind(this);
+ this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this);
PostStore.clearCommentDraftUploads();
@@ -70,7 +73,8 @@ class CreateComment extends React.Component {
previews: draft.previews,
submitting: false,
windowWidth: Utils.windowWidth(),
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
+ ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
+ showPostDeletedModal: false
};
}
componentDidMount() {
@@ -141,8 +145,10 @@ class CreateComment extends React.Component {
PostStore.storePendingPost(post);
PostStore.storeCommentDraft(this.props.rootId, null);
- Client.createPost(post, ChannelStore.getCurrent(),
- function handlePostSuccess(data) {
+ Client.createPost(
+ post,
+ ChannelStore.getCurrent(),
+ (data) => {
AsyncClient.getPosts(this.props.channelId);
const channel = ChannelStore.get(this.props.channelId);
@@ -155,27 +161,30 @@ class CreateComment extends React.Component {
type: ActionTypes.RECEIVED_POST,
post: data
});
- }.bind(this),
- function handlePostError(err) {
- let state = {};
-
+ },
+ (err) => {
if (err.id === 'api.post.create_post.root_id.app_error') {
- PostStore.removePendingPost(post.channel_id, post.pending_post_id);
+ this.showPostDeletedModal();
- if ($('#post_deleted').length > 0) {
- $('#post_deleted').modal('show');
- }
+ PostStore.removePendingPost(post.channel_id, post.pending_post_id);
} else {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
}
- state.submitting = false;
- this.setState(state);
- }.bind(this)
+ this.setState({
+ submitting: false
+ });
+ }
);
- this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
+ this.setState({
+ messageText: '',
+ submitting: false,
+ postError: null,
+ previews: [],
+ serverError: null
+ });
}
commentMsgKeyPress(e) {
if (this.state.ctrlSend && e.ctrlKey || !this.state.ctrlSend) {
@@ -285,7 +294,7 @@ class CreateComment extends React.Component {
if (index !== -1) {
uploadsInProgress.splice(index, 1);
- this.refs.fileUpload.cancelUpload(id);
+ this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
}
} else {
previews.splice(index, 1);
@@ -312,6 +321,16 @@ class CreateComment extends React.Component {
this.refs.textbox.focus();
}
}
+ showPostDeletedModal() {
+ this.setState({
+ showPostDeletedModal: true
+ });
+ }
+ hidePostDeletedModal() {
+ this.setState({
+ showPostDeletedModal: false
+ });
+ }
render() {
let serverError = null;
if (this.state.serverError) {
@@ -411,6 +430,10 @@ class CreateComment extends React.Component {
{serverError}
</div>
</div>
+ <PostDeletedModal
+ show={this.state.showPostDeletedModal}
+ onHide={this.hidePostDeletedModal}
+ />
</form>
);
}
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index b9fbf09b5..48b6594a1 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -5,6 +5,7 @@ import MsgTyping from './msg_typing.jsx';
import Textbox from './textbox.jsx';
import FileUpload from './file_upload.jsx';
import FilePreview from './file_preview.jsx';
+import PostDeletedModal from './post_deleted_modal.jsx';
import TutorialTip from './tutorial/tutorial_tip.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
@@ -64,6 +65,8 @@ class CreatePost extends React.Component {
this.handleKeyDown = this.handleKeyDown.bind(this);
this.sendMessage = this.sendMessage.bind(this);
this.focusTextbox = this.focusTextbox.bind(this);
+ this.showPostDeletedModal = this.showPostDeletedModal.bind(this);
+ this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this);
PostStore.clearDraftUploads();
@@ -77,7 +80,8 @@ class CreatePost extends React.Component {
submitting: false,
initialText: draft.messageText,
ctrlSend: false,
- showTutorialTip: false
+ showTutorialTip: false,
+ showPostDeletedModal: false
};
}
getCurrentDraft() {
@@ -157,7 +161,6 @@ class CreatePost extends React.Component {
post.pending_post_id = `${userId}:${time}`;
post.user_id = userId;
post.create_at = time;
- post.root_id = this.state.rootId;
post.parent_id = this.state.parentId;
const channel = ChannelStore.get(this.state.channelId);
@@ -177,20 +180,19 @@ class CreatePost extends React.Component {
EventHelpers.emitPostRecievedEvent(data);
},
(err) => {
- const state = {};
-
if (err.id === 'api.post.create_post.root_id.app_error') {
- if ($('#post_deleted').length > 0) {
- $('#post_deleted').modal('show');
- }
+ // this should never actually happen since you can't reply from this textbox
+ this.showPostDeletedModal();
+
PostStore.removePendingPost(post.pending_post_id);
} else {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
}
- state.submitting = false;
- this.setState(state);
+ this.setState({
+ submitting: false
+ });
}
);
}
@@ -286,7 +288,7 @@ class CreatePost extends React.Component {
if (index !== -1) {
uploadsInProgress.splice(index, 1);
- this.refs.fileUpload.cancelUpload(id);
+ this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
}
} else {
previews.splice(index, 1);
@@ -374,6 +376,16 @@ class CreatePost extends React.Component {
});
}
}
+ showPostDeletedModal() {
+ this.setState({
+ showPostDeletedModal: true
+ });
+ }
+ hidePostDeletedModal() {
+ this.setState({
+ showPostDeletedModal: false
+ });
+ }
createTutorialTip() {
const screens = [];
@@ -479,6 +491,10 @@ class CreatePost extends React.Component {
{serverError}
</div>
</div>
+ <PostDeletedModal
+ show={this.state.showPostDeletedModal}
+ onHide={this.hidePostDeletedModal}
+ />
</form>
);
}
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index f5c32c825..0454fe510 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -34,6 +34,7 @@ class FileUpload extends React.Component {
this.uploadFiles = this.uploadFiles.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleDrop = this.handleDrop.bind(this);
+ this.cancelUpload = this.cancelUpload.bind(this);
this.state = {
requests: {}
@@ -331,4 +332,4 @@ FileUpload.propTypes = {
postType: React.PropTypes.string
};
-export default injectIntl(FileUpload);
+export default injectIntl(FileUpload, {withRef: true});
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 30086d1b2..7b1f6170d 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -5,8 +5,29 @@ import UserStore from '../stores/user_store.jsx';
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Utils from '../utils/utils.jsx';
+import ConfirmModal from './confirm_modal.jsx';
+import TeamStore from '../stores/team_store.jsx';
-import {FormattedMessage} from 'mm-intl';
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+var holders = defineMessages({
+ confirmDemoteRoleTitle: {
+ id: 'member_team_item.confirmDemoteRoleTitle',
+ defaultMessage: 'Confirm demotion from System Admin role'
+ },
+ confirmDemotion: {
+ id: 'member_team_item.confirmDemotion',
+ defaultMessage: 'Confirm Demotion'
+ },
+ confirmDemoteDescription: {
+ id: 'member_team_item.confirmDemoteDescription',
+ defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.'
+ },
+ confirmDemotionCmd: {
+ id: 'member_team_item.confirmDemotionCmd',
+ defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
+ }
+});
export default class MemberListTeamItem extends React.Component {
constructor(props) {
@@ -16,23 +37,35 @@ export default class MemberListTeamItem extends React.Component {
this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
+ this.handleDemote = this.handleDemote.bind(this);
+ this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
+ this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
- this.state = {};
+ this.state = {
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ };
}
handleMakeMember() {
- const data = {
- user_id: this.props.user.id,
- new_roles: ''
- };
-
- Client.updateRoles(data,
- () => {
- AsyncClient.getProfiles();
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(this.props.user, '');
+ } else {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: ''
+ };
+ Client.updateRoles(data,
+ () => {
+ AsyncClient.getProfiles();
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
}
handleMakeActive() {
Client.updateActive(this.props.user.id, true,
@@ -55,14 +88,55 @@ export default class MemberListTeamItem extends React.Component {
);
}
handleMakeAdmin() {
+ const me = UserStore.getCurrentUser();
+ if (this.props.user.id === me.id) {
+ this.handleDemote(this.props.user, 'admin');
+ } else {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: 'admin'
+ };
+
+ Client.updateRoles(data,
+ () => {
+ AsyncClient.getProfiles();
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+ }
+ handleDemote(user, role) {
+ this.setState({
+ serverError: this.state.serverError,
+ showDemoteModal: true,
+ user,
+ role
+ });
+ }
+ handleDemoteCancel() {
+ this.setState({
+ serverError: null,
+ showDemoteModal: false,
+ user: null,
+ role: null
+ });
+ }
+ handleDemoteSubmit() {
const data = {
user_id: this.props.user.id,
- new_roles: 'admin'
+ new_roles: this.state.role
};
Client.updateRoles(data,
() => {
- AsyncClient.getProfiles();
+ const teamUrl = TeamStore.getCurrentTeamUrl();
+ if (teamUrl) {
+ window.location.href = teamUrl;
+ } else {
+ window.location.href = '/';
+ }
},
(err) => {
this.setState({serverError: err.message});
@@ -198,6 +272,21 @@ export default class MemberListTeamItem extends React.Component {
</li>
);
}
+ const me = UserStore.getCurrentUser();
+ const {formatMessage} = this.props.intl;
+ let makeDemoteModal = null;
+ if (this.props.user.id === me.id) {
+ makeDemoteModal = (
+ <ConfirmModal
+ show={this.state.showDemoteModal}
+ title={formatMessage(holders.confirmDemoteRoleTitle)}
+ message={[formatMessage(holders.confirmDemoteDescription), React.createElement('br'), React.createElement('br'), formatMessage(holders.confirmDemotionCmd), serverError]}
+ confirm_button={formatMessage(holders.confirmDemotion)}
+ onConfirm={this.handleDemoteSubmit}
+ onCancel={this.handleDemoteCancel}
+ />
+ );
+ }
return (
<tr>
@@ -231,6 +320,7 @@ export default class MemberListTeamItem extends React.Component {
{makeNotActive}
</ul>
</div>
+ {makeDemoteModal}
{serverError}
</td>
</tr>
@@ -239,5 +329,8 @@ export default class MemberListTeamItem extends React.Component {
}
MemberListTeamItem.propTypes = {
+ intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired
};
+
+export default injectIntl(MemberListTeamItem);
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index e6a9fbd25..835298635 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -25,6 +25,7 @@ const ActionTypes = Constants.ActionTypes;
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import {FormattedMessage} from 'mm-intl';
+import attachFastClick from 'fastclick';
const Popover = ReactBootstrap.Popover;
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
@@ -59,6 +60,7 @@ export default class Navbar extends React.Component {
ChannelStore.addChangeListener(this.onChange);
ChannelStore.addExtraInfoChangeListener(this.onChange);
$('.inner__wrap').click(this.hideSidebars);
+ attachFastClick(document.body);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index 642befeab..be22989a6 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from '../stores/user_store.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from '../utils/constants.jsx';
@@ -9,20 +8,22 @@ import {FormattedMessage} from 'mm-intl';
var ActionTypes = Constants.ActionTypes;
+const Modal = ReactBootstrap.Modal;
+
export default class PostDeletedModal extends React.Component {
constructor(props) {
super(props);
- this.handleClose = this.handleClose.bind(this);
-
- this.state = {};
+ this.handleHide = this.handleHide.bind(this);
}
- componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => {
- this.handleClose();
- });
+
+ shouldComponentUpdate(nextProps) {
+ return nextProps.show !== this.props.show;
}
- handleClose() {
+
+ handleHide(e) {
+ e.preventDefault();
+
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
results: null
@@ -39,67 +40,50 @@ export default class PostDeletedModal extends React.Component {
type: ActionTypes.RECEIVED_POST_SELECTED,
results: null
});
- }
- render() {
- var currentUser = UserStore.getCurrentUser();
- if (currentUser != null) {
- return (
- <div
- className='modal fade'
- ref='modal'
- id='post_deleted'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- <FormattedMessage
- id='post_delete.notPosted'
- defaultMessage='Comment could not be posted'
- />
- </h4>
- </div>
- <div className='modal-body'>
- <p>
- <FormattedMessage
- id='post_delete.someone'
- defaultMessage='Someone deleted the message on which you tried to post a comment.'
- />
- </p>
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-primary'
- data-dismiss='modal'
- >
- <FormattedMessage
- id='post_delete.okay'
- defaultMessage='Okay'
- />
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
+ this.props.onHide();
+ }
- return <div/>;
+ render() {
+ return (
+ <Modal
+ show={this.props.show}
+ onHide={this.handleHide}
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>
+ <FormattedMessage
+ id='post_delete.notPosted'
+ defaultMessage='Comment could not be posted'
+ />
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <p>
+ <FormattedMessage
+ id='post_delete.someone'
+ defaultMessage='Someone deleted the message on which you tried to post a comment.'
+ />
+ </p>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleHide}
+ >
+ <FormattedMessage
+ id='post_delete.okay'
+ defaultMessage='Okay'
+ />
+ </button>
+ </Modal.Footer>
+ </Modal>
+ );
}
}
+
+PostDeletedModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onHide: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 6d82423d5..ffac6eaef 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -14,9 +14,17 @@ export default class PostInfo extends React.Component {
constructor(props) {
super(props);
+ this.dropdownPosition = this.dropdownPosition.bind(this);
this.handlePermalink = this.handlePermalink.bind(this);
this.removePost = this.removePost.bind(this);
}
+ dropdownPosition(e) {
+ var position = $('#post-list').height() - $(e.target).offset().top;
+ var dropdown = $(e.target).next('.dropdown-menu');
+ if (position < dropdown.height()) {
+ dropdown.addClass('bottom');
+ }
+ }
createDropdown() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() === post.user_id;
@@ -57,22 +65,24 @@ export default class PostInfo extends React.Component {
);
}
- dropdownContents.push(
- <li
- key='copyLink'
- role='presentation'
- >
- <a
- href='#'
- onClick={this.handlePermalink}
+ if (!Utils.isMobile()) {
+ dropdownContents.push(
+ <li
+ key='copyLink'
+ role='presentation'
>
- <FormattedMessage
- id='post_info.permalink'
- defaultMessage='Permalink'
- />
- </a>
- </li>
- );
+ <a
+ href='#'
+ onClick={this.handlePermalink}
+ >
+ <FormattedMessage
+ id='post_info.permalink'
+ defaultMessage='Permalink'
+ />
+ </a>
+ </li>
+ );
+ }
if (isOwner || isAdmin) {
dropdownContents.push(
@@ -133,6 +143,7 @@ export default class PostInfo extends React.Component {
type='button'
data-toggle='dropdown'
aria-expanded='false'
+ onClick={this.dropdownPosition}
/>
<ul
className='dropdown-menu'
@@ -144,7 +155,8 @@ export default class PostInfo extends React.Component {
);
}
- handlePermalink() {
+ handlePermalink(e) {
+ e.preventDefault();
EventHelpers.showGetPostLinkModal(this.props.post);
}
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 9c85e9940..201a4c569 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -31,6 +31,7 @@ class RhsComment extends React.Component {
this.retryComment = this.retryComment.bind(this);
this.parseEmojis = this.parseEmojis.bind(this);
+ this.handlePermalink = this.handlePermalink.bind(this);
this.state = {};
}
@@ -67,6 +68,10 @@ class RhsComment extends React.Component {
parseEmojis() {
twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE});
}
+ handlePermalink(e) {
+ e.preventDefault();
+ EventHelpers.showGetPostLinkModal(this.props.post);
+ }
componentDidMount() {
this.parseEmojis();
}
@@ -92,6 +97,25 @@ class RhsComment extends React.Component {
var dropdownContents = [];
+ if (!Utils.isMobile()) {
+ dropdownContents.push(
+ <li
+ key='rhs-root-permalink'
+ role='presentation'
+ >
+ <a
+ href='#'
+ onClick={this.handlePermalink}
+ >
+ <FormattedMessage
+ id='rhs_comment.permalink'
+ defaultMessage='Permalink'
+ />
+ </a>
+ </li>
+ );
+ }
+
if (isOwner) {
dropdownContents.push(
<li
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index f9f7f8f81..adf66da85 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -5,7 +5,7 @@ import ChannelStore from '../stores/channel_store.jsx';
import UserProfile from './user_profile.jsx';
import UserStore from '../stores/user_store.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as Utils from '../utils/utils.jsx';
import * as Emoji from '../utils/emoticons.jsx';
import FileAttachmentList from './file_attachment_list.jsx';
import twemoji from 'twemoji';
@@ -21,6 +21,7 @@ export default class RhsRootPost extends React.Component {
super(props);
this.parseEmojis = this.parseEmojis.bind(this);
+ this.handlePermalink = this.handlePermalink.bind(this);
this.state = {};
}
@@ -31,11 +32,15 @@ export default class RhsRootPost extends React.Component {
folder: Emoji.getImagePathForEmoticon()
});
}
+ handlePermalink(e) {
+ e.preventDefault();
+ EventHelpers.showGetPostLinkModal(this.props.post);
+ }
componentDidMount() {
this.parseEmojis();
}
shouldComponentUpdate(nextProps) {
- if (!utils.areObjectsEqual(nextProps.post, this.props.post)) {
+ if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
@@ -48,7 +53,7 @@ export default class RhsRootPost extends React.Component {
var post = this.props.post;
var currentUser = UserStore.getCurrentUser();
var isOwner = currentUser.id === post.user_id;
- var isAdmin = utils.isAdmin(currentUser.roles);
+ var isAdmin = Utils.isAdmin(currentUser.roles);
var timestamp = UserStore.getProfile(post.user_id).update_at;
var channel = ChannelStore.get(post.channel_id);
@@ -63,7 +68,7 @@ export default class RhsRootPost extends React.Component {
}
var systemMessageClass = '';
- if (utils.isSystemMessage(post)) {
+ if (Utils.isSystemMessage(post)) {
systemMessageClass = 'post--system';
}
@@ -83,6 +88,25 @@ export default class RhsRootPost extends React.Component {
var dropdownContents = [];
+ if (!Utils.isMobile()) {
+ dropdownContents.push(
+ <li
+ key='rhs-root-permalink'
+ role='presentation'
+ >
+ <a
+ href='#'
+ onClick={this.handlePermalink}
+ >
+ <FormattedMessage
+ id='rhs_root.permalink'
+ defaultMessage='Permalink'
+ />
+ </a>
+ </li>
+ );
+ }
+
if (isOwner) {
dropdownContents.push(
<li
@@ -176,7 +200,7 @@ export default class RhsRootPost extends React.Component {
}
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
- } else if (utils.isSystemMessage(post)) {
+ } else if (Utils.isSystemMessage(post)) {
userProfile = (
<UserProfile
userId={''}
@@ -187,12 +211,12 @@ export default class RhsRootPost extends React.Component {
);
}
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
}
- } else if (utils.isSystemMessage(post)) {
+ } else if (Utils.isSystemMessage(post)) {
src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
}
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 0a1b02853..c1b2a2e7f 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -486,13 +486,9 @@ class GeneralTab extends React.Component {
inputs.push(
<div key='teamInviteSetting'>
<div className='row'>
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='general_tab.codeTitle'
- defaultMessage='Invite Code'
- />
+ <label className='col-sm-5 control-label visible-xs-block'>
</label>
- <div className='col-sm-7'>
+ <div className='col-sm-12'>
<input
className='form-control'
type='text'
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx
index feb70dc71..790ec2e5d 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/team_signup_email_item.jsx
@@ -83,4 +83,4 @@ TeamSignupEmailItem.propTypes = {
email: React.PropTypes.string
};
-export default injectIntl(TeamSignupEmailItem); \ No newline at end of file
+export default injectIntl(TeamSignupEmailItem, {withRef: true});
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx
index 46a6bc68e..343db13e8 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/team_signup_send_invites_page.jsx
@@ -33,8 +33,8 @@ export default class TeamSignupSendInvitesPage extends React.Component {
var emails = [];
for (var i = 0; i < this.props.state.invites.length; i++) {
- if (this.refs['email_' + i].validate(this.props.state.team.email)) {
- emails.push(this.refs['email_' + i].getValue());
+ if (this.refs['email_' + i].getWrappedInstance().validate(this.props.state.team.email)) {
+ emails.push(this.refs['email_' + i].getWrappedInstance().getValue());
} else {
valid = false;
}
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx
index 2d88a3650..1e724bb6e 100644
--- a/web/react/components/user_settings/custom_theme_chooser.jsx
+++ b/web/react/components/user_settings/custom_theme_chooser.jsx
@@ -102,6 +102,7 @@ class CustomThemeChooser extends React.Component {
this.onPickerChange = this.onPickerChange.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.pasteBoxChange = this.pasteBoxChange.bind(this);
+ this.toggleContent = this.toggleContent.bind(this);
this.state = {};
}
@@ -153,11 +154,23 @@ class CustomThemeChooser extends React.Component {
this.props.updateTheme(theme);
}
+ toggleContent(e) {
+ e.stopPropagation();
+ if ($(e.target).hasClass('theme-elements__header')) {
+ $(e.target).next().slideToggle();
+ $(e.target).toggleClass('open');
+ } else {
+ $(e.target).closest('.theme-elements__header').next().slideToggle();
+ $(e.target).closest('.theme-elements__header').toggleClass('open');
+ }
+ }
render() {
const {formatMessage} = this.props.intl;
const theme = this.props.theme;
- const elements = [];
+ const sidebarElements = [];
+ const centerChannelElements = [];
+ const linkAndButtonElements = [];
let colors = '';
Constants.THEME_ELEMENTS.forEach((element, index) => {
if (element.id === 'codeTheme') {
@@ -187,9 +200,9 @@ class CustomThemeChooser extends React.Component {
</Popover>
);
- elements.push(
+ centerChannelElements.push(
<div
- className='col-sm-4 form-group'
+ className='col-sm-6 form-group'
key={'custom-theme-key' + index}
>
<label className='custom-label'>{formatMessage(messages[element.id])}</label>
@@ -219,10 +232,54 @@ class CustomThemeChooser extends React.Component {
</div>
</div>
);
+ } else if (element.group === 'centerChannelElements') {
+ centerChannelElements.push(
+ <div
+ className='col-sm-6 form-group element'
+ key={'custom-theme-key' + index}
+ >
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
+ <div
+ className='input-group color-picker'
+ id={element.id}
+ >
+ <input
+ className='form-control'
+ type='text'
+ value={theme[element.id]}
+ onChange={this.onInputChange}
+ />
+ <span className='input-group-addon'><i></i></span>
+ </div>
+ </div>
+ );
+ } else if (element.group === 'sidebarElements') {
+ sidebarElements.push(
+ <div
+ className='col-sm-6 form-group element'
+ key={'custom-theme-key' + index}
+ >
+ <label className='custom-label'>{formatMessage(messages[element.id])}</label>
+ <div
+ className='input-group color-picker'
+ id={element.id}
+ >
+ <input
+ className='form-control'
+ type='text'
+ value={theme[element.id]}
+ onChange={this.onInputChange}
+ />
+ <span className='input-group-addon'><i></i></span>
+ </div>
+ </div>
+ );
+
+ colors += theme[element.id] + ',';
} else {
- elements.push(
+ linkAndButtonElements.push(
<div
- className='col-sm-4 form-group element'
+ className='col-sm-6 form-group element'
key={'custom-theme-key' + index}
>
<label className='custom-label'>{formatMessage(messages[element.id])}</label>
@@ -265,9 +322,51 @@ class CustomThemeChooser extends React.Component {
);
return (
- <div className='appearance-section'>
+ <div className='appearance-section padding-top'>
+ <div className='theme-elements row'>
+ <div
+ className='theme-elements__header'
+ onClick={this.toggleContent}
+ >
+ {'Sidebar Styles'}
+ <div className='header__icon'>
+ <i className='fa fa-plus'></i>
+ <i className='fa fa-minus'></i>
+ </div>
+ </div>
+ <div className='theme-elements__body'>
+ {sidebarElements}
+ </div>
+ </div>
+ <div className='theme-elements row'>
+ <div
+ className='theme-elements__header'
+ onClick={this.toggleContent}
+ >
+ {'Center Channel Styles'}
+ <div className='header__icon'>
+ <i className='fa fa-plus'></i>
+ <i className='fa fa-minus'></i>
+ </div>
+ </div>
+ <div className='theme-elements__body'>
+ {centerChannelElements}
+ </div>
+ </div>
<div className='theme-elements row form-group'>
- {elements}
+ <div
+ className='theme-elements__header'
+ onClick={this.toggleContent}
+ >
+ {'Link and Button Styles'}
+ <div className='header__icon'>
+ <i className='fa fa-plus'></i>
+ <i className='fa fa-minus'></i>
+ </div>
+ </div>
+ <div className='theme-elements__body'>
+ {linkAndButtonElements}
+ </div>
</div>
<div className='row'>
{pasteBox}
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index c6532b018..68e99be7d 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -183,7 +183,7 @@ export default class ManageIncomingHooks extends React.Component {
<div key='addIncomingHook'>
<FormattedHTMLMessage
id='user.settings.hooks_in.description'
- defaultMessage='Create webhook URLs for use in external integrations. Please see<a href="http://mattermost.org/webhooks" target="_blank">http://mattermost.org/webhooks</a> to learn more.'
+ defaultMessage='Create webhook URLs for use in external integrations. Please see <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">incoming webhooks documentation</a> to learn more.'
/>
<div><label className='control-label padding-top x2'>
<FormattedMessage
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 3f88e9f41..9c3a60ed5 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -18,6 +18,10 @@ const holders = defineMessages({
callbackHolder: {
id: 'user.settings.hooks_out.callbackHolder',
defaultMessage: 'Each URL must start with http:// or https://'
+ },
+ select: {
+ id: 'user.settings.hooks_out.select',
+ defaultMessage: '--- Select a channel ---'
}
});
@@ -153,10 +157,7 @@ class ManageOutgoingHooks extends React.Component {
key='select-channel'
value=''
>
- <FormattedMessage
- id='user.settings.hooks_out.select'
- defaultMessage='--- Select a channel ---'
- />
+ {this.props.intl.formatMessage(holders.select)}
</option>
);
@@ -283,7 +284,7 @@ class ManageOutgoingHooks extends React.Component {
<div key='addOutgoingHook'>
<FormattedHTMLMessage
id='user.settings.hooks_out.addDescription'
- defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://mattermost.org/webhooks">http://mattermost.org/webhooks</a> to learn more.'
+ defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">outgoing webhooks documentation</a> to learn more.'
/>
<div><label className='control-label padding-top x2'>
<FormattedMessage
@@ -391,4 +392,4 @@ ManageOutgoingHooks.propTypes = {
intl: intlShape.isRequired
};
-export default injectIntl(ManageOutgoingHooks); \ No newline at end of file
+export default injectIntl(ManageOutgoingHooks);
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 7e911f09a..5442f7ac4 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -234,7 +234,10 @@ class UserSettingsModal extends React.Component {
render() {
const {formatMessage} = this.props.intl;
+ var currentUser = UserStore.getCurrentUser();
+ var isAdmin = Utils.isAdmin(currentUser.roles);
var tabs = [];
+
tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'glyphicon glyphicon-lock'});
tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'glyphicon glyphicon-exclamation-sign'});
@@ -243,8 +246,17 @@ class UserSettingsModal extends React.Component {
}
if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true' || global.window.mm_config.EnableCommands === 'true') {
- tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
+ var show = global.window.mm_config.EnableOnlyAdminIntegrations !== 'true';
+
+ if (global.window.mm_config.EnableOnlyAdminIntegrations === 'true' && isAdmin) {
+ show = true;
+ }
+
+ if (show) {
+ tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
+ }
}
+
tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'});
tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'glyphicon glyphicon-list-alt'});
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 5693047c2..53d79906f 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -11,6 +11,7 @@ import TeamStore from '../../stores/team_store.jsx';
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+import * as Utils from '../../utils/utils.jsx';
import Constants from '../../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -216,15 +217,12 @@ class SecurityTab extends React.Component {
var describe;
var d = new Date(this.props.user.last_password_update);
- var timeOfDay = ' am';
- if (d.getHours() >= 12) {
- timeOfDay = ' pm';
- }
const locale = global.window.mm_locale;
+ const hours12 = !Utils.isMilitaryTime();
describe = formatMessage(holders.lastUpdated, {
date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: d.toLocaleTimeString(locale, {hours12: true, hour: '2-digit', minute: '2-digit'}) + timeOfDay
+ time: d.toLocaleTimeString(locale, {hour12: hours12, hour: '2-digit', minute: '2-digit'})
});
updateSectionStatus = function updateSection() {
diff --git a/web/react/components/user_settings/user_settings_theme.jsx b/web/react/components/user_settings/user_settings_theme.jsx
index 34c688db1..74975d115 100644
--- a/web/react/components/user_settings/user_settings_theme.jsx
+++ b/web/react/components/user_settings/user_settings_theme.jsx
@@ -182,7 +182,6 @@ export default class ThemeSetting extends React.Component {
if (displayCustom) {
custom = (
<div key='customThemeChooser'>
- <br/>
<CustomThemeChooser
theme={this.state.theme}
updateTheme={this.updateTheme}
@@ -241,7 +240,6 @@ export default class ThemeSetting extends React.Component {
defaultMessage='Custom Theme'
/>
</label>
- <br/>
</div>
);