PLT-7: Refactoring frontend (chunk 6)
- User settings
Этот коммит содержится в:
@@ -8,7 +8,188 @@ import * as AsyncClient from '../utils/async_client.jsx';
|
||||
import LoadingScreen from './loading_screen.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
export default class AccessHistoryModal extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
sessionRevoked: {
|
||||
id: 'access_history.sessionRevoked',
|
||||
defaultMessage: 'The session with id {sessionId} was revoked'
|
||||
},
|
||||
channelCreated: {
|
||||
id: 'access_history.channelCreated',
|
||||
defaultMessage: 'Created the {channelName} channel/group'
|
||||
},
|
||||
establishedDM: {
|
||||
id: 'access_history.establishedDM',
|
||||
defaultMessage: 'Established a direct message channel with {username}'
|
||||
},
|
||||
nameUpdated: {
|
||||
id: 'access_history.nameUpdated',
|
||||
defaultMessage: 'Updated the {channelName} channel/group name'
|
||||
},
|
||||
headerUpdated: {
|
||||
id: 'access_history.headerUpdated',
|
||||
defaultMessage: 'Updated the {channelName} channel/group header'
|
||||
},
|
||||
channelDeleted: {
|
||||
id: 'access_history.channelDeleted',
|
||||
defaultMessage: 'Deleted the channel/group with the URL {url}'
|
||||
},
|
||||
userAdded: {
|
||||
id: 'access_history.userAdded',
|
||||
defaultMessage: 'Added {username} to the {channelName} channel/group'
|
||||
},
|
||||
userRemoved: {
|
||||
id: 'access_history.userRemoved',
|
||||
defaultMessage: 'Removed {username} to the {channelName} channel/group'
|
||||
},
|
||||
attemptedRegisterApp: {
|
||||
id: 'access_history.attemptedRegisterApp',
|
||||
defaultMessage: 'Attempted to register a new OAuth Application with ID {id}'
|
||||
},
|
||||
attemptedAllowOAuthAccess: {
|
||||
id: 'access_history.attemptedAllowOAuthAccess',
|
||||
defaultMessage: 'Attempted to allow a new OAuth service access'
|
||||
},
|
||||
successfullOAuthAccess: {
|
||||
id: 'access_history.successfullOAuthAccess',
|
||||
defaultMessage: 'Successfully gave a new OAuth service access'
|
||||
},
|
||||
failedOAuthAccess: {
|
||||
id: 'access_history.failedOAuthAccess',
|
||||
defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'
|
||||
},
|
||||
attemptedOAuthToken: {
|
||||
id: 'access_history.attemptedOAuthToken',
|
||||
defaultMessage: 'Attempted to get an OAuth access token'
|
||||
},
|
||||
successfullOAuthToken: {
|
||||
id: 'access_history.successfullOAuthToken',
|
||||
defaultMessage: 'Successfully added a new OAuth service'
|
||||
},
|
||||
oauthTokenFailed: {
|
||||
id: 'access_history.oauthTokenFailed',
|
||||
defaultMessage: 'Failed to get an OAuth access token - {token}'
|
||||
},
|
||||
attemptedLogin: {
|
||||
id: 'access_history.attemptedLogin',
|
||||
defaultMessage: 'Attempted to login'
|
||||
},
|
||||
successfullLogin: {
|
||||
id: 'access_history.successfullLogin',
|
||||
defaultMessage: 'Successfully logged in'
|
||||
},
|
||||
failedLogin: {
|
||||
id: 'access_history.failedLogin',
|
||||
defaultMessage: 'FAILED login attempt'
|
||||
},
|
||||
updatePicture: {
|
||||
id: 'access_history.updatePicture',
|
||||
defaultMessage: 'Updated your profile picture'
|
||||
},
|
||||
updateGeneral: {
|
||||
id: 'access_history.updateGeneral',
|
||||
defaultMessage: 'Updated the general settings of your account'
|
||||
},
|
||||
attemptedPassword: {
|
||||
id: 'access_history.attemptedPassword',
|
||||
defaultMessage: 'Attempted to change password'
|
||||
},
|
||||
successfullPassword: {
|
||||
id: 'access_history.successfullPassword',
|
||||
defaultMessage: 'Successfully changed password'
|
||||
},
|
||||
failedPassword: {
|
||||
id: 'access_history.failedPassword',
|
||||
defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth'
|
||||
},
|
||||
updatedRol: {
|
||||
id: 'access_history.updatedRol',
|
||||
defaultMessage: 'Updated user role(s) to '
|
||||
},
|
||||
member: {
|
||||
id: 'access_history.member',
|
||||
defaultMessage: 'member'
|
||||
},
|
||||
accountActive: {
|
||||
id: 'access_history.accountActive',
|
||||
defaultMessage: 'Account made active'
|
||||
},
|
||||
accountInactive: {
|
||||
id: 'access_history.accountInactive',
|
||||
defaultMessage: 'Account made inactive'
|
||||
},
|
||||
by: {
|
||||
id: 'access_history.by',
|
||||
defaultMessage: ' by {username}'
|
||||
},
|
||||
byAdmin: {
|
||||
id: 'access_history.byAdmin',
|
||||
defaultMessage: ' by an admin'
|
||||
},
|
||||
sentEmail: {
|
||||
id: 'access_history.sentEmail',
|
||||
defaultMessage: 'Sent an email to {email} to reset your password'
|
||||
},
|
||||
attemptedReset: {
|
||||
id: 'access_history.attemptedReset',
|
||||
defaultMessage: 'Attempted to reset password'
|
||||
},
|
||||
successfullReset: {
|
||||
id: 'access_history.successfullReset',
|
||||
defaultMessage: 'Successfully reset password'
|
||||
},
|
||||
updateGlobalNotifications: {
|
||||
id: 'access_history.updateGlobalNotifications',
|
||||
defaultMessage: 'Updated your global notification settings'
|
||||
},
|
||||
attemptedWebhookCreate: {
|
||||
id: 'access_history.attemptedWebhookCreate',
|
||||
defaultMessage: 'Attempted to create a webhook'
|
||||
},
|
||||
succcessfullWebhookCreate: {
|
||||
id: 'access_history.successfullWebhookCreate',
|
||||
defaultMessage: 'Successfully created a webhook'
|
||||
},
|
||||
failedWebhookCreate: {
|
||||
id: 'access_history.failedWebhookCreate',
|
||||
defaultMessage: 'Failed to create a webhook - bad channel permissions'
|
||||
},
|
||||
attemptedWebhookDelete: {
|
||||
id: 'access_history.attemptedWebhookDelete',
|
||||
defaultMessage: 'Attempted to delete a webhook'
|
||||
},
|
||||
successfullWebhookDelete: {
|
||||
id: 'access_history.successfullWebhookDelete',
|
||||
defaultMessage: 'Successfully deleted a webhook'
|
||||
},
|
||||
failedWebhookDelete: {
|
||||
id: 'access_history.failedWebhookDelete',
|
||||
defaultMessage: 'Failed to delete a webhook - inappropriate conditions'
|
||||
},
|
||||
logout: {
|
||||
id: 'access_history.logout',
|
||||
defaultMessage: 'Logged out of your account'
|
||||
},
|
||||
verified: {
|
||||
id: 'access_history.verified',
|
||||
defaultMessage: 'Sucessfully verified your email address'
|
||||
},
|
||||
revokedAll: {
|
||||
id: 'access_history.revokedAll',
|
||||
defaultMessage: 'Revoked all current sessions for the team'
|
||||
},
|
||||
loginAttempt: {
|
||||
id: 'access_history.loginAttempt',
|
||||
defaultMessage: ' (Login attempt)'
|
||||
},
|
||||
loginFailure: {
|
||||
id: 'access_history.loginFailure',
|
||||
defaultMessage: ' (Login failure)'
|
||||
}
|
||||
});
|
||||
|
||||
class AccessHistoryModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -70,11 +251,12 @@ export default class AccessHistoryModal extends React.Component {
|
||||
this.setState({moreInfo: newMoreInfo});
|
||||
}
|
||||
handleRevokedSession(sessionId) {
|
||||
return 'The session with id ' + sessionId + ' was revoked';
|
||||
return this.props.intl.formatMessage(holders.sessionRevoked, {sessionId: sessionId});
|
||||
}
|
||||
formatAuditInfo(currentAudit) {
|
||||
const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, '');
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
let currentAuditDesc = '';
|
||||
|
||||
if (currentActionURL.indexOf('/channels') === 0) {
|
||||
@@ -96,17 +278,17 @@ export default class AccessHistoryModal extends React.Component {
|
||||
|
||||
switch (currentActionURL) {
|
||||
case '/channels/create':
|
||||
currentAuditDesc = 'Created the ' + channelName + ' channel/group';
|
||||
currentAuditDesc = formatMessage(holders.channelCreated, {channelName: channelName});
|
||||
break;
|
||||
case '/channels/create_direct':
|
||||
currentAuditDesc = 'Established a direct message channel with ' + Utils.getDirectTeammate(channelObj.id).username;
|
||||
currentAuditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
|
||||
break;
|
||||
case '/channels/update':
|
||||
currentAuditDesc = 'Updated the ' + channelName + ' channel/group name';
|
||||
currentAuditDesc = formatMessage(holders.nameUpdated, {channelName: channelName});
|
||||
break;
|
||||
case '/channels/update_desc': // support the old path
|
||||
case '/channels/update_header':
|
||||
currentAuditDesc = 'Updated the ' + channelName + ' channel/group header';
|
||||
currentAuditDesc = formatMessage(holders.headerUpdated, {channelName: channelName});
|
||||
break;
|
||||
default: {
|
||||
let userIdField = [];
|
||||
@@ -123,11 +305,11 @@ export default class AccessHistoryModal extends React.Component {
|
||||
}
|
||||
|
||||
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) {
|
||||
currentAuditDesc = 'Deleted the channel/group with the URL ' + channelURL;
|
||||
currentAuditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
|
||||
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) {
|
||||
currentAuditDesc = 'Added ' + username + ' to the ' + channelName + ' channel/group';
|
||||
currentAuditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName});
|
||||
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) {
|
||||
currentAuditDesc = 'Removed ' + username + ' from the ' + channelName + ' channel/group';
|
||||
currentAuditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName});
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -141,31 +323,31 @@ export default class AccessHistoryModal extends React.Component {
|
||||
const clientIdField = oauthInfo[0].split('=');
|
||||
|
||||
if (clientIdField[0] === 'client_id') {
|
||||
currentAuditDesc = 'Attempted to register a new OAuth Application with ID ' + clientIdField[1];
|
||||
currentAuditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case '/oauth/allow':
|
||||
if (oauthInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to allow a new OAuth service access';
|
||||
currentAuditDesc = formatMessage(holders.attemptedAllowOAuthAccess);
|
||||
} else if (oauthInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully gave a new OAuth service access';
|
||||
currentAuditDesc = formatMessage(holders.successfullOAuthAccess);
|
||||
} else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
|
||||
currentAuditDesc = 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback';
|
||||
currentAuditDesc = formatMessage(holders.failedOAuthAccess);
|
||||
}
|
||||
|
||||
break;
|
||||
case '/oauth/access_token':
|
||||
if (oauthInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to get an OAuth access token';
|
||||
currentAuditDesc = formatMessage(holders.attemptedOAuthToken);
|
||||
} else if (oauthInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully added a new OAuth service';
|
||||
currentAuditDesc = formatMessage(holders.successfullOAuthToken);
|
||||
} else {
|
||||
const oauthTokenFailure = oauthInfo[0].split('-');
|
||||
|
||||
if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
|
||||
currentAuditDesc = 'Failed to get an OAuth access token - ' + oauthTokenFailure[1].trim();
|
||||
currentAuditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,11 +361,11 @@ export default class AccessHistoryModal extends React.Component {
|
||||
switch (currentActionURL) {
|
||||
case '/users/login':
|
||||
if (userInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to login';
|
||||
currentAuditDesc = formatMessage(holders.attemptedLogin);
|
||||
} else if (userInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully logged in';
|
||||
currentAuditDesc = formatMessage(holders.successfullLogin);
|
||||
} else if (userInfo[0]) {
|
||||
currentAuditDesc = 'FAILED login attempt';
|
||||
currentAuditDesc = formatMessage(holders.failedLogin);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -191,29 +373,29 @@ export default class AccessHistoryModal extends React.Component {
|
||||
currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]);
|
||||
break;
|
||||
case '/users/newimage':
|
||||
currentAuditDesc = 'Updated your profile picture';
|
||||
currentAuditDesc = formatMessage(holders.updatePicture);
|
||||
break;
|
||||
case '/users/update':
|
||||
currentAuditDesc = 'Updated the general settings of your account';
|
||||
currentAuditDesc = formatMessage(holders.updateGeneral);
|
||||
break;
|
||||
case '/users/newpassword':
|
||||
if (userInfo[0] === 'attempted') {
|
||||
currentAuditDesc = 'Attempted to change password';
|
||||
currentAuditDesc = formatMessage(holders.attemptedPassword);
|
||||
} else if (userInfo[0] === 'completed') {
|
||||
currentAuditDesc = 'Successfully changed password';
|
||||
currentAuditDesc = formatMessage(holders.successfullPassword);
|
||||
} else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
|
||||
currentAuditDesc = 'Failed to change password - tried to update user password who was logged in through oauth';
|
||||
currentAuditDesc = formatMessage(holders.failedPassword);
|
||||
}
|
||||
|
||||
break;
|
||||
case '/users/update_roles': {
|
||||
const userRoles = userInfo[0].split('=')[1];
|
||||
|
||||
currentAuditDesc = 'Updated user role(s) to ';
|
||||
currentAuditDesc = formatMessage(holders.updatedRol);
|
||||
if (userRoles.trim()) {
|
||||
currentAuditDesc += userRoles;
|
||||
} else {
|
||||
currentAuditDesc += 'member';
|
||||
currentAuditDesc += formatMessage(holders.member);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -225,9 +407,9 @@ export default class AccessHistoryModal extends React.Component {
|
||||
/* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
|
||||
if (updateType === 'active') {
|
||||
if (updateField === 'true') {
|
||||
currentAuditDesc = 'Account made active';
|
||||
currentAuditDesc = formatMessage(holders.accountActive);
|
||||
} else if (updateField === 'false') {
|
||||
currentAuditDesc = 'Account made inactive';
|
||||
currentAuditDesc = formatMessage(holders.accountInactive);
|
||||
}
|
||||
|
||||
const actingUserInfo = userInfo[1].split('=');
|
||||
@@ -235,9 +417,9 @@ export default class AccessHistoryModal extends React.Component {
|
||||
const actingUser = UserStore.getProfile(actingUserInfo[1]);
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) {
|
||||
currentAuditDesc += ' by ' + actingUser.username;
|
||||
currentAuditDesc += formatMessage(holders.by, {username: actingUser.username});
|
||||
} else if (currentUser && actingUser) {
|
||||
currentAuditDesc += ' by an admin';
|
||||
currentAuditDesc += formatMessage(holders.byAdmin);
|
||||
}
|
||||
}
|
||||
} else if (updateType === 'session_id') {
|
||||
@@ -247,18 +429,18 @@ export default class AccessHistoryModal extends React.Component {
|
||||
break;
|
||||
}
|
||||
case '/users/send_password_reset':
|
||||
currentAuditDesc = 'Sent an email to ' + userInfo[0].split('=')[1] + ' to reset your password';
|
||||
currentAuditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]});
|
||||
break;
|
||||
case '/users/reset_password':
|
||||
if (userInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to reset password';
|
||||
currentAuditDesc = formatMessage(holders.attemptedReset);
|
||||
} else if (userInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully reset password';
|
||||
currentAuditDesc = formatMessage(holders.successfullReset);
|
||||
}
|
||||
|
||||
break;
|
||||
case '/users/update_notify':
|
||||
currentAuditDesc = 'Updated your global notification settings';
|
||||
currentAuditDesc = formatMessage(holders.updateGlobalNotifications);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -269,21 +451,21 @@ export default class AccessHistoryModal extends React.Component {
|
||||
switch (currentActionURL) {
|
||||
case '/hooks/incoming/create':
|
||||
if (webhookInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to create a webhook';
|
||||
currentAuditDesc = formatMessage(holders.attemptedWebhookCreate);
|
||||
} else if (webhookInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully created a webhook';
|
||||
currentAuditDesc = formatMessage(holders.succcessfullWebhookCreate);
|
||||
} else if (webhookInfo[0] === 'fail - bad channel permissions') {
|
||||
currentAuditDesc = 'Failed to create a webhook - bad channel permissions';
|
||||
currentAuditDesc = formatMessage(holders.failedWebhookCreate);
|
||||
}
|
||||
|
||||
break;
|
||||
case '/hooks/incoming/delete':
|
||||
if (webhookInfo[0] === 'attempt') {
|
||||
currentAuditDesc = 'Attempted to delete a webhook';
|
||||
currentAuditDesc = formatMessage(holders.attemptedWebhookDelete);
|
||||
} else if (webhookInfo[0] === 'success') {
|
||||
currentAuditDesc = 'Successfully deleted a webhook';
|
||||
currentAuditDesc = formatMessage(holders.successfullWebhookDelete);
|
||||
} else if (webhookInfo[0] === 'fail - inappropriate conditions') {
|
||||
currentAuditDesc = 'Failed to delete a webhook - inappropriate conditions';
|
||||
currentAuditDesc = formatMessage(holders.failedWebhookDelete);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -293,10 +475,10 @@ export default class AccessHistoryModal extends React.Component {
|
||||
} else {
|
||||
switch (currentActionURL) {
|
||||
case '/logout':
|
||||
currentAuditDesc = 'Logged out of your account';
|
||||
currentAuditDesc = formatMessage(holders.logout);
|
||||
break;
|
||||
case '/verify_email':
|
||||
currentAuditDesc = 'Sucessfully verified your email address';
|
||||
currentAuditDesc = formatMessage(holders.verified);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -307,7 +489,7 @@ export default class AccessHistoryModal extends React.Component {
|
||||
if (!currentAuditDesc) {
|
||||
/* Currently not called anywhere */
|
||||
if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) {
|
||||
currentAuditDesc = 'Revoked all current sessions for the team';
|
||||
currentAuditDesc = formatMessage(holders.revokedAll);
|
||||
} else {
|
||||
let currentActionDesc = '';
|
||||
if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) {
|
||||
@@ -328,12 +510,14 @@ export default class AccessHistoryModal extends React.Component {
|
||||
}
|
||||
|
||||
const currentDate = new Date(currentAudit.create_at);
|
||||
const currentAuditInfo = currentDate.toDateString() + ' - ' + currentDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc;
|
||||
const currentAuditInfo = currentDate.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' +
|
||||
currentDate.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'}) + ' | ' + currentAuditDesc;
|
||||
return currentAuditInfo;
|
||||
}
|
||||
render() {
|
||||
var accessList = [];
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
for (var i = 0; i < this.state.audits.length; i++) {
|
||||
const currentAudit = this.state.audits[i];
|
||||
const currentAuditInfo = this.formatAuditInfo(currentAudit);
|
||||
@@ -344,7 +528,10 @@ export default class AccessHistoryModal extends React.Component {
|
||||
className='theme'
|
||||
onClick={this.handleMoreInfo.bind(this, i)}
|
||||
>
|
||||
{'More info'}
|
||||
<FormattedMessage
|
||||
id='access_history.moreInfo'
|
||||
defaultMessage='More info'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -354,17 +541,33 @@ export default class AccessHistoryModal extends React.Component {
|
||||
|
||||
if (currentAudit.action.search('/users/login') >= 0) {
|
||||
if (currentAudit.extra_info === 'attempt') {
|
||||
currentAudit.session_id += ' (Login attempt)';
|
||||
currentAudit.session_id += formatMessage(holders.loginAttempt);
|
||||
} else {
|
||||
currentAudit.session_id += ' (Login failure)';
|
||||
currentAudit.session_id += formatMessage(holders.loginFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moreInfo = (
|
||||
<div>
|
||||
<div>{'IP: ' + currentAudit.ip_address}</div>
|
||||
<div>{'Session ID: ' + currentAudit.session_id}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='access_history.ip'
|
||||
defaultMessage='IP: {ip}'
|
||||
values={{
|
||||
ip: currentAudit.ip_address
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='access_history.session'
|
||||
defaultMessage='Session ID: {id}'
|
||||
values={{
|
||||
id: currentAudit.session_id
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -404,7 +607,12 @@ export default class AccessHistoryModal extends React.Component {
|
||||
bsSize='large'
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Access History'}</Modal.Title>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='access_history.title'
|
||||
defaultMessage='Access History'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body ref='modalBody'>
|
||||
{content}
|
||||
@@ -415,6 +623,9 @@ export default class AccessHistoryModal extends React.Component {
|
||||
}
|
||||
|
||||
AccessHistoryModal.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
onHide: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(AccessHistoryModal);
|
||||
@@ -8,6 +8,8 @@ const Modal = ReactBootstrap.Modal;
|
||||
import LoadingScreen from './loading_screen.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class ActivityLogModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -102,16 +104,31 @@ export default class ActivityLogModal extends React.Component {
|
||||
devicePicture = 'fa fa-windows';
|
||||
} else if (currentSession.device_id && currentSession.device_id.indexOf('apple:') === 0) {
|
||||
devicePicture = 'fa fa-apple';
|
||||
devicePlatform = 'iPhone Native App';
|
||||
devicePlatform = (
|
||||
<FormattedMessage
|
||||
id='activity_log_modal.iphoneNativeApp'
|
||||
defaultMessage='iPhone Native App'
|
||||
/>
|
||||
);
|
||||
} else if (currentSession.device_id && currentSession.device_id.indexOf('android:') === 0) {
|
||||
devicePlatform = 'Android Native App';
|
||||
devicePlatform = (
|
||||
<FormattedMessage
|
||||
id='activity_log_modal.androidNativeApp'
|
||||
defaultMessage='Android Native App'
|
||||
/>
|
||||
);
|
||||
devicePicture = 'fa fa-android';
|
||||
} else if (currentSession.props.platform === 'Macintosh' ||
|
||||
currentSession.props.platform === 'iPhone') {
|
||||
devicePicture = 'fa fa-apple';
|
||||
} else if (currentSession.props.platform === 'Linux') {
|
||||
if (currentSession.props.os.indexOf('Android') >= 0) {
|
||||
devicePlatform = 'Android';
|
||||
devicePlatform = (
|
||||
<FormattedMessage
|
||||
id='activity_log_modal.android'
|
||||
defaultMessage='Android'
|
||||
/>
|
||||
);
|
||||
devicePicture = 'fa fa-android';
|
||||
} else {
|
||||
devicePicture = 'fa fa-linux';
|
||||
@@ -122,10 +139,43 @@ export default class ActivityLogModal extends React.Component {
|
||||
if (this.state.moreInfo[i]) {
|
||||
moreInfo = (
|
||||
<div>
|
||||
<div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
|
||||
<div>{`OS: ${currentSession.props.os}`}</div>
|
||||
<div>{`Browser: ${currentSession.props.browser}`}</div>
|
||||
<div>{`Session ID: ${currentSession.id}`}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='activity_log.firstTime'
|
||||
defaultMessage='First time active: {date}, {time}'
|
||||
values={{
|
||||
date: firstAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
|
||||
time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='activity_log.os'
|
||||
defaultMessage='OS: {os}'
|
||||
values={{
|
||||
os: currentSession.props.os
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='activity_log.browser'
|
||||
defaultMessage='Browser: {browser}'
|
||||
values={{
|
||||
browser: currentSession.props.browser
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='activity_log.sessionId'
|
||||
defaultMessage='Session ID: {id}'
|
||||
values={{
|
||||
id: currentSession.id
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
@@ -135,7 +185,10 @@ export default class ActivityLogModal extends React.Component {
|
||||
href='#'
|
||||
onClick={this.handleMoreInfo.bind(this, i)}
|
||||
>
|
||||
More info
|
||||
<FormattedMessage
|
||||
id='activity_log.moreInfo'
|
||||
defaultMessage='More info'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -148,7 +201,16 @@ export default class ActivityLogModal extends React.Component {
|
||||
<div className='activity-log__report'>
|
||||
<div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
|
||||
<div className='report__info'>
|
||||
<div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='activity_log.lastActivity'
|
||||
defaultMessage='Last activity: {date}, {time}'
|
||||
values={{
|
||||
date: lastAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
|
||||
time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{moreInfo}
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,7 +219,10 @@ export default class ActivityLogModal extends React.Component {
|
||||
onClick={this.submitRevoke.bind(this, currentSession.id)}
|
||||
className='btn btn-primary'
|
||||
>
|
||||
Logout
|
||||
<FormattedMessage
|
||||
id='activity_log.logout'
|
||||
defaultMessage='Logout'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,10 +243,20 @@ export default class ActivityLogModal extends React.Component {
|
||||
bsSize='large'
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Active Sessions'}</Modal.Title>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='activity_log.activeSessions'
|
||||
defaultMessage='Active Sessions'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body ref='modalBody'>
|
||||
<p className='session-help-text'>{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}</p>
|
||||
<p className='session-help-text'>
|
||||
<FormattedMessage
|
||||
id='activity_log.sessionsDescription'
|
||||
defaultMessage="Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session."
|
||||
/>
|
||||
</p>
|
||||
{content}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
|
||||
export default class ConfirmModal extends React.Component {
|
||||
@@ -33,7 +34,10 @@ export default class ConfirmModal extends React.Component {
|
||||
className='btn btn-default'
|
||||
onClick={this.props.onCancel}
|
||||
>
|
||||
{'Cancel'}
|
||||
<FormattedMessage
|
||||
id='confirm_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
|
||||
@@ -7,9 +7,22 @@ import ModalStore from '../stores/modal_store.jsx';
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
|
||||
import Constants from '../utils/constants.jsx';
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
export default class RegisterAppModal extends React.Component {
|
||||
const holders = defineMessages({
|
||||
required: {
|
||||
id: 'register_app.required',
|
||||
defaultMessage: 'Required'
|
||||
},
|
||||
optional: {
|
||||
id: 'register_app.optional',
|
||||
defaultMessage: 'Optional'
|
||||
}
|
||||
});
|
||||
|
||||
class RegisterAppModal extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -60,7 +73,7 @@ export default class RegisterAppModal extends React.Component {
|
||||
|
||||
var name = this.refs.name.value;
|
||||
if (!name || name.length === 0) {
|
||||
state.nameError = 'Application name must be filled in.';
|
||||
state.nameError = true;
|
||||
this.setState(state);
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +82,7 @@ export default class RegisterAppModal extends React.Component {
|
||||
|
||||
var homepage = this.refs.homepage.value;
|
||||
if (!homepage || homepage.length === 0) {
|
||||
state.homepageError = 'Homepage must be filled in.';
|
||||
state.homepageError = true;
|
||||
this.setState(state);
|
||||
return;
|
||||
}
|
||||
@@ -81,7 +94,7 @@ export default class RegisterAppModal extends React.Component {
|
||||
|
||||
var rawCallbacks = this.refs.callback.value.trim();
|
||||
if (!rawCallbacks || rawCallbacks.length === 0) {
|
||||
state.callbackError = 'At least one callback URL must be filled in.';
|
||||
state.callbackError = true;
|
||||
this.setState(state);
|
||||
return;
|
||||
}
|
||||
@@ -112,17 +125,45 @@ export default class RegisterAppModal extends React.Component {
|
||||
this.setState({saved: this.refs.save.checked});
|
||||
}
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
var nameError;
|
||||
if (this.state.nameError) {
|
||||
nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>;
|
||||
nameError = (
|
||||
<div className='form-group has-error'>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.nameError'
|
||||
defaultMessage='Application name must be filled in.'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var homepageError;
|
||||
if (this.state.homepageError) {
|
||||
homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>;
|
||||
homepageError = (
|
||||
<div className='form-group has-error'>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.homepageError'
|
||||
defaultMessage='Homepage must be filled in.'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var callbackError;
|
||||
if (this.state.callbackError) {
|
||||
callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>;
|
||||
callbackError = (
|
||||
<div className='form-group has-error'>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.callbackError'
|
||||
defaultMessage='At least one callback URL must be filled in.'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var serverError;
|
||||
if (this.state.serverError) {
|
||||
@@ -135,50 +176,75 @@ export default class RegisterAppModal extends React.Component {
|
||||
body = (
|
||||
<div className='settings-modal'>
|
||||
<div className='form-horizontal user-settings'>
|
||||
<h4 className='padding-bottom x3'>{'Register a New Application'}</h4>
|
||||
<h4 className='padding-bottom x3'>
|
||||
<FormattedMessage
|
||||
id='register_app.title'
|
||||
defaultMessage='Register a New Application'
|
||||
/>
|
||||
</h4>
|
||||
<div className='row'>
|
||||
<label className='col-sm-4 control-label'>{'Application Name'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.name'
|
||||
defaultMessage='Application Name'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
ref='name'
|
||||
className='form-control'
|
||||
type='text'
|
||||
placeholder='Required'
|
||||
placeholder={formatMessage(holders.required)}
|
||||
/>
|
||||
{nameError}
|
||||
</div>
|
||||
</div>
|
||||
<div className='row padding-top x2'>
|
||||
<label className='col-sm-4 control-label'>{'Homepage URL'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.homepage'
|
||||
defaultMessage='Homepage URL'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
ref='homepage'
|
||||
className='form-control'
|
||||
type='text'
|
||||
placeholder='Required'
|
||||
placeholder={formatMessage(holders.required)}
|
||||
/>
|
||||
{homepageError}
|
||||
</div>
|
||||
</div>
|
||||
<div className='row padding-top x2'>
|
||||
<label className='col-sm-4 control-label'>{'Description'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.description'
|
||||
defaultMessage='Description'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
ref='desc'
|
||||
className='form-control'
|
||||
type='text'
|
||||
placeholder='Optional'
|
||||
placeholder={formatMessage(holders.optional)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row padding-top padding-bottom x2'>
|
||||
<label className='col-sm-4 control-label'>{'Callback URL'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.callback'
|
||||
defaultMessage='Callback URL'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<textarea
|
||||
ref='callback'
|
||||
className='form-control'
|
||||
type='text'
|
||||
placeholder='Required'
|
||||
placeholder={formatMessage(holders.required)}
|
||||
rows='5'
|
||||
/>
|
||||
{callbackError}
|
||||
@@ -196,7 +262,10 @@ export default class RegisterAppModal extends React.Component {
|
||||
className='btn btn-default'
|
||||
onClick={() => this.updateShow(false)}
|
||||
>
|
||||
{'Cancel'}
|
||||
<FormattedMessage
|
||||
id='register_app.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.handleSubmit}
|
||||
@@ -204,7 +273,10 @@ export default class RegisterAppModal extends React.Component {
|
||||
className='btn btn-primary'
|
||||
tabIndex='3'
|
||||
>
|
||||
{'Register'}
|
||||
<FormattedMessage
|
||||
id='register_app.register'
|
||||
defaultMessage='Register'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -216,10 +288,20 @@ export default class RegisterAppModal extends React.Component {
|
||||
|
||||
body = (
|
||||
<div className='form-horizontal user-settings'>
|
||||
<h4 className='padding-bottom x3'>{'Your Application Credentials'}</h4>
|
||||
<h4 className='padding-bottom x3'>
|
||||
<FormattedMessage
|
||||
id='register_app.credentialsTitle'
|
||||
defaultMessage='Your Application Credentials'
|
||||
/>
|
||||
</h4>
|
||||
<br/>
|
||||
<div className='row'>
|
||||
<label className='col-sm-4 control-label'>{'Client ID'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.clientId'
|
||||
defaultMessage='Client ID'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -231,7 +313,11 @@ export default class RegisterAppModal extends React.Component {
|
||||
</div>
|
||||
<br/>
|
||||
<div className='row padding-top x2'>
|
||||
<label className='col-sm-4 control-label'>{'Client Secret'}</label>
|
||||
<label className='col-sm-4 control-label'>
|
||||
<FormattedMessage
|
||||
id='register_app.clientSecret'
|
||||
defaultMessage='Client Secret'
|
||||
/></label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -243,7 +329,12 @@ export default class RegisterAppModal extends React.Component {
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<strong>{'Save these somewhere SAFE and SECURE. Treat your Client ID as your app\'s username and your Client Secret as the app\'s password.'}</strong>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='register_app.credentialsDescription'
|
||||
defaultMessage="Save these somewhere SAFE and SECURE. Treat your Client ID as your app's username and your Client Secret as the app's password."
|
||||
/>
|
||||
</strong>
|
||||
<br/>
|
||||
<br/>
|
||||
<div className='checkbox'>
|
||||
@@ -254,7 +345,10 @@ export default class RegisterAppModal extends React.Component {
|
||||
checked={this.state.saved}
|
||||
onChange={this.save}
|
||||
/>
|
||||
{'I have saved both my Client Id and Client Secret somewhere safe'}
|
||||
<FormattedMessage
|
||||
id='register_app.credentialsSave'
|
||||
defaultMessage='I have saved both my Client Id and Client Secret somewhere safe'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,7 +363,10 @@ export default class RegisterAppModal extends React.Component {
|
||||
this.updateShow(false);
|
||||
}}
|
||||
>
|
||||
{'Close'}
|
||||
<FormattedMessage
|
||||
id='register_app.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -281,7 +378,12 @@ export default class RegisterAppModal extends React.Component {
|
||||
onHide={() => this.updateShow(false)}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Developer Applications'}</Modal.Title>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='register_app.dev'
|
||||
defaultMessage='Developer Applications'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<form
|
||||
role='form'
|
||||
@@ -300,3 +402,8 @@ export default class RegisterAppModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppModal.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(RegisterAppModal);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class SettingItemMax extends React.Component {
|
||||
render() {
|
||||
var clientError = null;
|
||||
@@ -26,7 +28,10 @@ export default class SettingItemMax extends React.Component {
|
||||
href='#'
|
||||
onClick={this.props.submit}
|
||||
>
|
||||
Save
|
||||
<FormattedMessage
|
||||
id='setting_item_max.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -60,7 +65,10 @@ export default class SettingItemMax extends React.Component {
|
||||
href='#'
|
||||
onClick={this.props.updateSection}
|
||||
>
|
||||
Cancel
|
||||
<FormattedMessage
|
||||
id='setting_item_max.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class SettingItemMin extends React.Component {
|
||||
render() {
|
||||
let editButton = null;
|
||||
@@ -13,7 +15,10 @@ export default class SettingItemMin extends React.Component {
|
||||
onClick={this.props.updateSection}
|
||||
>
|
||||
<i className='fa fa-pencil'/>
|
||||
{'Edit'}
|
||||
<FormattedMessage
|
||||
id='setting_item_min.edit'
|
||||
defaultMessage='Edit'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class SettingPicture extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -76,10 +78,24 @@ export default class SettingPicture extends React.Component {
|
||||
<a
|
||||
className={confirmButtonClass}
|
||||
onClick={this.props.submit}
|
||||
>Save</a>
|
||||
>
|
||||
<FormattedMessage
|
||||
id='setting_picture.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.mm_config.ProfileWidth + 'px in width and ' + global.window.mm_config.ProfileHeight + 'px height.';
|
||||
var helpText = (
|
||||
<FormattedMessage
|
||||
id='setting_picture.help'
|
||||
defaultMessage='Upload a profile picture in either JPG or PNG format, at least {width}px in width and {height}px height.'
|
||||
values={{
|
||||
width: global.window.mm_config.ProfileWidth,
|
||||
height: global.window.mm_config.ProfileHeight
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
var self = this;
|
||||
return (
|
||||
@@ -97,7 +113,10 @@ export default class SettingPicture extends React.Component {
|
||||
{serverError}
|
||||
{clientError}
|
||||
<span className='btn btn-sm btn-primary btn-file sel-btn'>
|
||||
Select
|
||||
<FormattedMessage
|
||||
id='setting_picture.select'
|
||||
defaultMessage='Select'
|
||||
/>
|
||||
<input
|
||||
ref='input'
|
||||
accept='.jpg,.png,.bmp'
|
||||
@@ -110,7 +129,12 @@ export default class SettingPicture extends React.Component {
|
||||
className='btn btn-sm theme'
|
||||
href='#'
|
||||
onClick={self.props.updateSection}
|
||||
>Cancel</a>
|
||||
>
|
||||
<FormattedMessage
|
||||
id='setting_picture.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,96 @@ import Constants from '../../utils/constants.jsx';
|
||||
const OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||
const Popover = ReactBootstrap.Popover;
|
||||
|
||||
export default class CustomThemeChooser extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
sidebarBg: {
|
||||
id: 'user.settings.custom_theme.sidebarBg',
|
||||
defaultMessage: 'Sidebar BG'
|
||||
},
|
||||
sidebarText: {
|
||||
id: 'user.settings.custom_theme.sidebarText',
|
||||
defaultMessage: 'Sidebar Text'
|
||||
},
|
||||
sidebarHeaderBg: {
|
||||
id: 'user.settings.custom_theme.sidebarHeaderBg',
|
||||
defaultMessage: 'Sidebar Header BG'
|
||||
},
|
||||
sidebarHeaderTextColor: {
|
||||
id: 'user.settings.custom_theme.sidebarHeaderTextColor',
|
||||
defaultMessage: 'Sidebar Header Text'
|
||||
},
|
||||
sidebarUnreadText: {
|
||||
id: 'user.settings.custom_theme.sidebarUnreadText',
|
||||
defaultMessage: 'Sidebar Unread Text'
|
||||
},
|
||||
sidebarTextHoverBg: {
|
||||
id: 'user.settings.custom_theme.sidebarTextHoverBg',
|
||||
defaultMessage: 'Sidebar Text Hover BG'
|
||||
},
|
||||
sidebarTextActiveBorder: {
|
||||
id: 'user.settings.custom_theme.sidebarTextActiveBorder',
|
||||
defaultMessage: 'Sidebar Text Active Border'
|
||||
},
|
||||
sidebarTextActiveColor: {
|
||||
id: 'user.settings.custom_theme.sidebarTextActiveColor',
|
||||
defaultMessage: 'Sidebar Text Active Color'
|
||||
},
|
||||
onlineIndicator: {
|
||||
id: 'user.settings.custom_theme.onlineIndicator',
|
||||
defaultMessage: 'Online Indicator'
|
||||
},
|
||||
awayIndicator: {
|
||||
id: 'user.settings.custom_theme.awayIndicator',
|
||||
defaultMessage: 'Away Indicator'
|
||||
},
|
||||
mentionBj: {
|
||||
id: 'user.settings.custom_theme.mentionBj',
|
||||
defaultMessage: 'Mention Jewel BG'
|
||||
},
|
||||
mentionColor: {
|
||||
id: 'user.settings.custom_theme.mentionColor',
|
||||
defaultMessage: 'Mention Jewel Text'
|
||||
},
|
||||
centerChannelBg: {
|
||||
id: 'user.settings.custom_theme.centerChannelBg',
|
||||
defaultMessage: 'Center Channel BG'
|
||||
},
|
||||
centerChannelColor: {
|
||||
id: 'user.settings.custom_theme.centerChannelColor',
|
||||
defaultMessage: 'Center Channel Text'
|
||||
},
|
||||
newMessageSeparator: {
|
||||
id: 'user.settings.custom_theme.newMessageSeparator',
|
||||
defaultMessage: 'New Message Separator'
|
||||
},
|
||||
linkColor: {
|
||||
id: 'user.settings.custom_theme.linkColor',
|
||||
defaultMessage: 'Link Color'
|
||||
},
|
||||
buttonBg: {
|
||||
id: 'user.settings.custom_theme.buttonBg',
|
||||
defaultMessage: 'Button BG'
|
||||
},
|
||||
buttonColor: {
|
||||
id: 'user.settings.custom_theme.buttonColor',
|
||||
defaultMessage: 'Button Text'
|
||||
},
|
||||
mentionHighlightBg: {
|
||||
id: 'user.settings.custom_theme.mentionHighlightBg',
|
||||
defaultMessage: 'Mention Highlight BG'
|
||||
},
|
||||
mentionHighlightLink: {
|
||||
id: 'user.settings.custom_theme.mentionHighlightLink',
|
||||
defaultMessage: 'Mention Highlight Link'
|
||||
},
|
||||
codeTheme: {
|
||||
id: 'user.settings.custom_theme.codeTheme',
|
||||
defaultMessage: 'Code Theme'
|
||||
}
|
||||
});
|
||||
|
||||
class CustomThemeChooser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -65,6 +154,7 @@ export default class CustomThemeChooser extends React.Component {
|
||||
this.props.updateTheme(theme);
|
||||
}
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const theme = this.props.theme;
|
||||
|
||||
const elements = [];
|
||||
@@ -102,7 +192,7 @@ export default class CustomThemeChooser extends React.Component {
|
||||
className='col-sm-4 form-group'
|
||||
key={'custom-theme-key' + index}
|
||||
>
|
||||
<label className='custom-label'>{element.uiName}</label>
|
||||
<label className='custom-label'>{formatMessage(messages[element.id])}</label>
|
||||
<div
|
||||
className='input-group theme-group group--code dropdown'
|
||||
id={element.id}
|
||||
@@ -135,7 +225,7 @@ export default class CustomThemeChooser extends React.Component {
|
||||
className='col-sm-4 form-group'
|
||||
key={'custom-theme-key' + index}
|
||||
>
|
||||
<label className='custom-label'>{element.uiName}</label>
|
||||
<label className='custom-label'>{formatMessage(messages[element.id])}</label>
|
||||
<div
|
||||
className='input-group color-picker'
|
||||
id={element.id}
|
||||
@@ -160,7 +250,10 @@ export default class CustomThemeChooser extends React.Component {
|
||||
const pasteBox = (
|
||||
<div className='col-sm-12'>
|
||||
<label className='custom-label'>
|
||||
{'Copy and paste to share theme colors:'}
|
||||
<FormattedMessage
|
||||
id='user.settings.custom_theme.copyPaste'
|
||||
defaultMessage='Copy and paste to share theme colors:'
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
@@ -185,6 +278,9 @@ export default class CustomThemeChooser extends React.Component {
|
||||
}
|
||||
|
||||
CustomThemeChooser.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
updateTheme: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(CustomThemeChooser);
|
||||
@@ -9,9 +9,19 @@ const Modal = ReactBootstrap.Modal;
|
||||
|
||||
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
submitError: {
|
||||
id: 'user.settings.import_theme.submitError',
|
||||
defaultMessage: 'Invalid format, please try copying and pasting in again.'
|
||||
}
|
||||
});
|
||||
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
export default class ImportThemeModal extends React.Component {
|
||||
class ImportThemeModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -39,7 +49,7 @@ export default class ImportThemeModal extends React.Component {
|
||||
const text = ReactDOM.findDOMNode(this.refs.input).value;
|
||||
|
||||
if (!this.isInputValid(text)) {
|
||||
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
|
||||
this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,7 +135,7 @@ export default class ImportThemeModal extends React.Component {
|
||||
if (this.isInputValid(e.target.value)) {
|
||||
this.setState({inputError: null});
|
||||
} else {
|
||||
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
|
||||
this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
@@ -136,7 +146,12 @@ export default class ImportThemeModal extends React.Component {
|
||||
onHide={() => this.setState({show: false})}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Import Slack Theme'}</Modal.Title>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='user.settings.import_theme.importHeader'
|
||||
defaultMessage='Import Slack Theme'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<form
|
||||
role='form'
|
||||
@@ -144,7 +159,10 @@ export default class ImportThemeModal extends React.Component {
|
||||
>
|
||||
<Modal.Body>
|
||||
<p>
|
||||
{'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
|
||||
<FormattedMessage
|
||||
id='user.settings.import_theme.importBody'
|
||||
defaultMessage='To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'
|
||||
/>
|
||||
</p>
|
||||
<div className='form-group less'>
|
||||
<div className='col-sm-9'>
|
||||
@@ -166,7 +184,10 @@ export default class ImportThemeModal extends React.Component {
|
||||
className='btn btn-default'
|
||||
onClick={() => this.setState({show: false})}
|
||||
>
|
||||
{'Cancel'}
|
||||
<FormattedMessage
|
||||
id='user.settings.import_theme.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.handleSubmit}
|
||||
@@ -174,7 +195,10 @@ export default class ImportThemeModal extends React.Component {
|
||||
className='btn btn-primary'
|
||||
tabIndex='3'
|
||||
>
|
||||
{'Submit'}
|
||||
<FormattedMessage
|
||||
id='user.settings.import_theme.submit'
|
||||
defaultMessage='Submit'
|
||||
/>
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</form>
|
||||
@@ -183,3 +207,9 @@ export default class ImportThemeModal extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportThemeModal.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(ImportThemeModal);
|
||||
@@ -7,6 +7,8 @@ import Constants from '../../utils/constants.jsx';
|
||||
import ChannelStore from '../../stores/channel_store.jsx';
|
||||
import LoadingScreen from '../loading_screen.jsx';
|
||||
|
||||
import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
|
||||
|
||||
export default class ManageIncomingHooks extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -126,7 +128,12 @@ export default class ManageIncomingHooks extends React.Component {
|
||||
<span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
|
||||
</div>
|
||||
<div className='padding-top'>
|
||||
<strong>{'Channel: '}</strong>{c.display_name}
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_in.channel'
|
||||
defaultMessage='Channel: '
|
||||
/>
|
||||
</strong>{c.display_name}
|
||||
</div>
|
||||
<a
|
||||
className={'webhook__remove'}
|
||||
@@ -147,12 +154,24 @@ export default class ManageIncomingHooks extends React.Component {
|
||||
} else if (hooks.length > 0) {
|
||||
displayHooks = hooks;
|
||||
} else {
|
||||
displayHooks = <div className='padding-top x2'>{'None'}</div>;
|
||||
displayHooks = (
|
||||
<div className='padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_in.none'
|
||||
defaultMessage='None'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const existingHooks = (
|
||||
<div className='webhooks__container'>
|
||||
<label className='control-label padding-top x2'>{'Existing incoming webhooks'}</label>
|
||||
<label className='control-label padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_in.existing'
|
||||
defaultMessage='Existing incoming webhooks'
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top divider-light'></div>
|
||||
<div className='webhooks__list'>
|
||||
{displayHooks}
|
||||
@@ -162,15 +181,16 @@ export default class ManageIncomingHooks extends React.Component {
|
||||
|
||||
return (
|
||||
<div key='addIncomingHook'>
|
||||
{'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.'}
|
||||
<div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div>
|
||||
<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.'
|
||||
/>
|
||||
<div><label className='control-label padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_in.addTitle'
|
||||
defaultMessage='Add a new incoming webhook'
|
||||
/>
|
||||
</label></div>
|
||||
<div className='row padding-top'>
|
||||
<div className='col-sm-10 padding-bottom'>
|
||||
<select
|
||||
@@ -189,7 +209,10 @@ export default class ManageIncomingHooks extends React.Component {
|
||||
href='#'
|
||||
onClick={this.addNewHook}
|
||||
>
|
||||
{'Add'}
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_in.add'
|
||||
defaultMessage='Add'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import * as Client from '../../utils/client.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class ManageLanguage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -70,7 +72,12 @@ export default class ManageLanguage extends React.Component {
|
||||
return (
|
||||
<div key='changeLanguage'>
|
||||
<br/>
|
||||
<label className='control-label'>{'Change interface language'}</label>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.languages.change'
|
||||
defaultMessage='Change interface language'
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top'>
|
||||
<select
|
||||
ref='language'
|
||||
@@ -87,7 +94,10 @@ export default class ManageLanguage extends React.Component {
|
||||
href='#'
|
||||
onClick={this.changeLanguage}
|
||||
>
|
||||
{'Set language'}
|
||||
<FormattedMessage
|
||||
id='user.settings.languages'
|
||||
defaultMessage='Set language'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,20 @@ import ChannelStore from '../../stores/channel_store.jsx';
|
||||
import * as Client from '../../utils/client.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
|
||||
export default class ManageOutgoingHooks extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
optional: {
|
||||
id: 'user.settings.hooks_out.optional',
|
||||
defaultMessage: 'Optional if channel selected'
|
||||
},
|
||||
callbackHolder: {
|
||||
id: 'user.settings.hooks_out.callbackHolder',
|
||||
defaultMessage: 'Each URL must start with http:// or https://'
|
||||
}
|
||||
});
|
||||
|
||||
class ManageOutgoingHooks extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -140,7 +153,10 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
key='select-channel'
|
||||
value=''
|
||||
>
|
||||
{'--- Select a channel ---'}
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.select'
|
||||
defaultMessage='--- Select a channel ---'
|
||||
/>
|
||||
</option>
|
||||
);
|
||||
|
||||
@@ -169,7 +185,12 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
if (c) {
|
||||
channelDiv = (
|
||||
<div className='padding-top'>
|
||||
<strong>{'Channel: '}</strong>{c.display_name}
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.channel'
|
||||
defaultMessage='Channel: '
|
||||
/>
|
||||
</strong>{c.display_name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -178,7 +199,12 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
if (hook.trigger_words && hook.trigger_words.length !== 0) {
|
||||
triggerDiv = (
|
||||
<div className='padding-top'>
|
||||
<strong>{'Trigger Words: '}</strong>{hook.trigger_words.join(', ')}
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.trigger'
|
||||
defaultMessage='Trigger Words: '
|
||||
/>
|
||||
</strong>{hook.trigger_words.join(', ')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -202,7 +228,10 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
href='#'
|
||||
onClick={this.regenToken.bind(this, hook.id)}
|
||||
>
|
||||
{'Regen Token'}
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.regen'
|
||||
defaultMessage='Regen Token'
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
className='webhook__remove'
|
||||
@@ -223,12 +252,24 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
} else if (hooks.length > 0) {
|
||||
displayHooks = hooks;
|
||||
} else {
|
||||
displayHooks = <div className='padding-top x2'>{'None'}</div>;
|
||||
displayHooks = (
|
||||
<div className='padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.none'
|
||||
defaultMessage='None'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const existingHooks = (
|
||||
<div className='webhooks__container'>
|
||||
<label className='control-label padding-top x2'>{'Existing outgoing webhooks'}</label>
|
||||
<label className='control-label padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.existing'
|
||||
defaultMessage='Existing outgoing webhooks'
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top divider-light'></div>
|
||||
<div className='webhooks__list'>
|
||||
{displayHooks}
|
||||
@@ -240,19 +281,25 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
|
||||
return (
|
||||
<div key='addOutgoingHook'>
|
||||
{'Create webhooks to send new message events to an external integration. Please see '}
|
||||
<a
|
||||
href='http://mattermost.org/webhooks'
|
||||
target='_blank'
|
||||
>
|
||||
{'http://mattermost.org/webhooks'}
|
||||
</a>
|
||||
{' to learn more.'}
|
||||
<div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div>
|
||||
<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.'
|
||||
/>
|
||||
<div><label className='control-label padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.addTitle'
|
||||
defaultMessage='Add a new outgoing webhook'
|
||||
/>
|
||||
</label></div>
|
||||
<div className='padding-top divider-light'></div>
|
||||
<div className='padding-top'>
|
||||
<div>
|
||||
<label className='control-label'>{'Channel'}</label>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.channel'
|
||||
defaultMessage='Channel: '
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top'>
|
||||
<select
|
||||
ref='channelName'
|
||||
@@ -263,23 +310,43 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
<div className='padding-top'>{'Only public channels can be used'}</div>
|
||||
<div className='padding-top'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.only'
|
||||
defaultMessage='Only public channels can be used'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='padding-top x2'>
|
||||
<label className='control-label'>{'Trigger Words:'}</label>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.trigger'
|
||||
defaultMessage='Trigger Words: '
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top'>
|
||||
<input
|
||||
ref='triggerWords'
|
||||
className='form-control'
|
||||
value={this.state.triggerWords}
|
||||
onChange={this.updateTriggerWords}
|
||||
placeholder='Optional if channel selected'
|
||||
placeholder={this.props.intl.formatMessage(holders.optional)}
|
||||
/>
|
||||
</div>
|
||||
<div className='padding-top'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.comma'
|
||||
defaultMessage='Comma separated words to trigger on'
|
||||
/>
|
||||
</div>
|
||||
<div className='padding-top'>{'Comma separated words to trigger on'}</div>
|
||||
</div>
|
||||
<div className='padding-top x2'>
|
||||
<label className='control-label'>{'Callback URLs:'}</label>
|
||||
<label className='control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.callback'
|
||||
defaultMessage='Callback URLs: '
|
||||
/>
|
||||
</label>
|
||||
<div className='padding-top'>
|
||||
<textarea
|
||||
ref='callbackURLs'
|
||||
@@ -288,10 +355,15 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
resize={false}
|
||||
rows={3}
|
||||
onChange={this.updateCallbackURLs}
|
||||
placeholder='Each URL must start with http:// or https://'
|
||||
placeholder={this.props.intl.formatMessage(holders.callbackHolder)}
|
||||
/>
|
||||
</div>
|
||||
<div className='padding-top'>{'New line separated URLs that will receive the HTTP POST event'}</div>
|
||||
<div className='padding-top'>
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.callbackDesc'
|
||||
defaultMessage='New line separated URLs that will receive the HTTP POST event'
|
||||
/>
|
||||
</div>
|
||||
{addError}
|
||||
</div>
|
||||
<div className='padding-top padding-bottom'>
|
||||
@@ -301,7 +373,10 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
disabled={disableButton}
|
||||
onClick={this.addNewHook}
|
||||
>
|
||||
{'Add'}
|
||||
<FormattedMessage
|
||||
id='user.settings.hooks_out.add'
|
||||
defaultMessage='Add'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -311,3 +386,9 @@ export default class ManageOutgoingHooks extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ManageOutgoingHooks.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(ManageOutgoingHooks);
|
||||
@@ -6,9 +6,55 @@ import SettingItemMin from '../setting_item_min.jsx';
|
||||
import SettingItemMax from '../setting_item_max.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
import PreferenceStore from '../../stores/preference_store.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
|
||||
|
||||
export default class AdvancedSettingsDisplay extends React.Component {
|
||||
const holders = defineMessages({
|
||||
sendTitle: {
|
||||
id: 'user.settings.advance.sendTitle',
|
||||
defaultMessage: 'Send messages on Ctrl + Enter'
|
||||
},
|
||||
on: {
|
||||
id: 'user.settings.advance.on',
|
||||
defaultMessage: 'On'
|
||||
},
|
||||
off: {
|
||||
id: 'user.settings.advance.off',
|
||||
defaultMessage: 'Off'
|
||||
},
|
||||
preReleaseTitle: {
|
||||
id: 'user.settings.advance.preReleaseTitle',
|
||||
defaultMessage: 'Preview pre-release features'
|
||||
},
|
||||
feature: {
|
||||
id: 'user.settings.advance.feature',
|
||||
defaultMessage: ' Feature '
|
||||
},
|
||||
features: {
|
||||
id: 'user.settings.advance.features',
|
||||
defaultMessage: ' Features '
|
||||
},
|
||||
enabled: {
|
||||
id: 'user.settings.advance.enabled',
|
||||
defaultMessage: 'enabled'
|
||||
},
|
||||
MARKDOWN_PREVIEW: {
|
||||
id: 'user.settings.advance.markdown_preview',
|
||||
defaultMessage: 'Show markdown preview option in message input box'
|
||||
},
|
||||
EMBED_PREVIEW: {
|
||||
id: 'user.settings.advance.embed_preview',
|
||||
defaultMessage: 'Show preview snippet of links below message'
|
||||
},
|
||||
LOC_PREVIEW: {
|
||||
id: 'user.settings.advance.loc_preview',
|
||||
defaultMessage: 'Show user language in display settings'
|
||||
}
|
||||
});
|
||||
|
||||
class AdvancedSettingsDisplay extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -104,6 +150,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
|
||||
render() {
|
||||
const serverError = this.state.serverError || null;
|
||||
const {formatMessage} = this.props.intl;
|
||||
let ctrlSendSection;
|
||||
|
||||
if (this.props.activeSection === 'advancedCtrlSend') {
|
||||
@@ -121,7 +168,10 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
checked={ctrlSendActive[0]}
|
||||
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')}
|
||||
/>
|
||||
{'On'}
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -132,17 +182,26 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
checked={ctrlSendActive[1]}
|
||||
onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')}
|
||||
/>
|
||||
{'Off'}
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div><br/>{'If enabled \'Enter\' inserts a new line and \'Ctrl + Enter\' submits the message.'}</div>
|
||||
<div>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.sendDesc'
|
||||
defaultMessage="If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
|
||||
ctrlSendSection = (
|
||||
<SettingItemMax
|
||||
title='Send messages on Ctrl + Enter'
|
||||
title={formatMessage(holders.sendTitle)}
|
||||
inputs={inputs}
|
||||
submit={() => this.handleSubmit('send_on_ctrl_enter')}
|
||||
server_error={serverError}
|
||||
@@ -155,8 +214,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
} else {
|
||||
ctrlSendSection = (
|
||||
<SettingItemMin
|
||||
title='Send messages on Ctrl + Enter'
|
||||
describe={this.state.settings.send_on_ctrl_enter === 'true' ? 'On' : 'Off'}
|
||||
title={formatMessage(holders.sendTitle)}
|
||||
describe={this.state.settings.send_on_ctrl_enter === 'true' ? formatMessage(holders.on) : formatMessage(holders.off)}
|
||||
updateSection={() => this.props.updateSection('advancedCtrlSend')}
|
||||
/>
|
||||
);
|
||||
@@ -185,7 +244,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
this.toggleFeature(feature.label, e.target.checked);
|
||||
}}
|
||||
/>
|
||||
{feature.description}
|
||||
{formatMessage({id: 'user.settings.advance.' + feature.label})}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,13 +254,16 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
inputs.push(
|
||||
<div key='advancedPreviewFeatures_helptext'>
|
||||
<br/>
|
||||
{'Check any pre-released features you\'d like to preview. You may also need to refresh the page before the setting will take effect.'}
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.preReleaseDesc'
|
||||
defaultMessage="Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
previewFeaturesSection = (
|
||||
<SettingItemMax
|
||||
title='Preview pre-release features'
|
||||
title={formatMessage(holders.preReleaseTitle)}
|
||||
inputs={inputs}
|
||||
submit={this.saveEnabledFeatures}
|
||||
server_error={serverError}
|
||||
@@ -214,8 +276,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
} else {
|
||||
previewFeaturesSection = (
|
||||
<SettingItemMin
|
||||
title='Preview pre-release features'
|
||||
describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'}
|
||||
title={formatMessage(holders.preReleaseTitle)}
|
||||
describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? formatMessage(holders.feature) : formatMessage(holders.features)) + formatMessage(holders.enabled)}
|
||||
updateSection={() => this.props.updateSection('advancedPreviewFeatures')}
|
||||
/>
|
||||
);
|
||||
@@ -242,11 +304,19 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Advanced Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.title'
|
||||
defaultMessage='Advanced Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Advanced Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.advance.title'
|
||||
defaultMessage='Advanced Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{ctrlSendSection}
|
||||
{previewFeaturesSectionDivider}
|
||||
@@ -259,6 +329,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
|
||||
}
|
||||
|
||||
AdvancedSettingsDisplay.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
@@ -266,3 +337,5 @@ AdvancedSettingsDisplay.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(AdvancedSettingsDisplay);
|
||||
@@ -11,6 +11,9 @@ import * as Client from '../../utils/client.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
export default class UserSettingsAppearance extends React.Component {
|
||||
@@ -180,7 +183,10 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
checked={!displayCustom}
|
||||
onChange={this.updateType.bind(this, 'premade')}
|
||||
/>
|
||||
{'Theme Colors'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.themeColors'
|
||||
defaultMessage='Theme Colors'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -191,7 +197,10 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
checked={displayCustom}
|
||||
onChange={this.updateType.bind(this, 'custom')}
|
||||
/>
|
||||
{'Custom Theme'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.customTheme'
|
||||
defaultMessage='Custom Theme'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -203,14 +212,20 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
href='#'
|
||||
onClick={this.submitTheme}
|
||||
>
|
||||
{'Save'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
className='btn btn-sm theme'
|
||||
href='#'
|
||||
onClick={this.resetFields}
|
||||
>
|
||||
{'Cancel'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -235,11 +250,19 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Appearance Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.title'
|
||||
defaultMessage='Appearance Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Appearance Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.title'
|
||||
defaultMessage='Appearance Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{themeUI}
|
||||
<div className='divider-dark'/>
|
||||
@@ -248,7 +271,10 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
className='theme'
|
||||
onClick={this.handleImportModal}
|
||||
>
|
||||
{'Import theme colors from Slack'}
|
||||
<FormattedMessage
|
||||
id='user.settings.appearance.import'
|
||||
defaultMessage='Import theme colors from Slack'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,20 @@ import SettingItemMin from '../setting_item_min.jsx';
|
||||
import SettingItemMax from '../setting_item_max.jsx';
|
||||
import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
|
||||
|
||||
export default class DeveloperTab extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
applicationsPreview: {
|
||||
id: 'user.settings.developer.applicationsPreview',
|
||||
defaultMessage: 'Applications (Preview)'
|
||||
},
|
||||
thirdParty: {
|
||||
id: 'user.settings.developer.thirdParty',
|
||||
defaultMessage: 'Open to register a new third-party application'
|
||||
}
|
||||
});
|
||||
|
||||
class DeveloperTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -20,6 +33,7 @@ export default class DeveloperTab extends React.Component {
|
||||
render() {
|
||||
var appSection;
|
||||
var self = this;
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (this.props.activeSection === 'app') {
|
||||
var inputs = [];
|
||||
|
||||
@@ -33,7 +47,10 @@ export default class DeveloperTab extends React.Component {
|
||||
className='btn btn-sm btn-primary'
|
||||
onClick={this.register}
|
||||
>
|
||||
{'Register New Application'}
|
||||
<FormattedMessage
|
||||
id='user.settings.developer.register'
|
||||
defaultMessage='Register New Application'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +58,7 @@ export default class DeveloperTab extends React.Component {
|
||||
|
||||
appSection = (
|
||||
<SettingItemMax
|
||||
title='Applications (Preview)'
|
||||
title={formatMessage(holders.applicationsPreview)}
|
||||
inputs={inputs}
|
||||
updateSection={function updateSection(e) {
|
||||
self.props.updateSection('');
|
||||
@@ -52,8 +69,8 @@ export default class DeveloperTab extends React.Component {
|
||||
} else {
|
||||
appSection = (
|
||||
<SettingItemMin
|
||||
title='Applications (Preview)'
|
||||
describe='Open to register a new third-party application'
|
||||
title={formatMessage(holders.applicationsPreview)}
|
||||
describe={formatMessage(holders.thirdParty)}
|
||||
updateSection={function updateSection() {
|
||||
self.props.updateSection('app');
|
||||
}}
|
||||
@@ -81,11 +98,19 @@ export default class DeveloperTab extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Developer Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.developer.title'
|
||||
defaultMessage='Developer Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Developer Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.developer.title'
|
||||
defaultMessage='Developer Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{appSection}
|
||||
<div className='divider-dark'/>
|
||||
@@ -99,8 +124,11 @@ DeveloperTab.defaultProps = {
|
||||
activeSection: ''
|
||||
};
|
||||
DeveloperTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
activeSection: React.PropTypes.string,
|
||||
updateSection: React.PropTypes.func,
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(DeveloperTab);
|
||||
@@ -10,6 +10,47 @@ import PreferenceStore from '../../stores/preference_store.jsx';
|
||||
import ManageLanguages from './manage_languages.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
normalClock: {
|
||||
id: 'user.settings.display.normalClock',
|
||||
defaultMessage: '12-hour clock (example: 4:00 PM)'
|
||||
},
|
||||
militaryClock: {
|
||||
id: 'user.settings.display.militaryClock',
|
||||
defaultMessage: '24-hour clock (example: 16:00)'
|
||||
},
|
||||
clockDisplay: {
|
||||
id: 'user.settings.display.clockDisplay',
|
||||
defaultMessage: 'Clock Display'
|
||||
},
|
||||
teammateDisplay: {
|
||||
id: 'user.settings.display.teammateDisplay',
|
||||
defaultMessage: 'Teammate Name Display'
|
||||
},
|
||||
showNickname: {
|
||||
id: 'user.settings.display.showNickname',
|
||||
defaultMessage: 'Show nickname if one exists, otherwise show first and last name'
|
||||
},
|
||||
showUsername: {
|
||||
id: 'user.settings.display.showUsername',
|
||||
defaultMessage: 'Show username (team default)'
|
||||
},
|
||||
showFullname: {
|
||||
id: 'user.settings.display.showFullname',
|
||||
defaultMessage: 'Show first and last name'
|
||||
},
|
||||
fontTitle: {
|
||||
id: 'user.settings.display.fontTitle',
|
||||
defaultMessage: 'Display Font'
|
||||
},
|
||||
language: {
|
||||
id: 'user.settings.display.language',
|
||||
defaultMessage: 'Language'
|
||||
}
|
||||
});
|
||||
|
||||
function getDisplayStateFromStores() {
|
||||
const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'});
|
||||
const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'});
|
||||
@@ -22,7 +63,7 @@ function getDisplayStateFromStores() {
|
||||
};
|
||||
}
|
||||
|
||||
export default class UserSettingsDisplay extends React.Component {
|
||||
class UserSettingsDisplay extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -76,6 +117,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
this.updateState();
|
||||
}
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const serverError = this.state.serverError || null;
|
||||
let clockSection;
|
||||
let nameFormatSection;
|
||||
@@ -104,7 +146,10 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
checked={clockFormat[0]}
|
||||
onChange={this.handleClockRadio.bind(this, 'false')}
|
||||
/>
|
||||
{'12-hour clock (example: 4:00 PM)'}
|
||||
<FormattedMessage
|
||||
id='user.settings.display.normalClock'
|
||||
defaultMessage='12-hour clock (example: 4:00 PM)'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -115,17 +160,26 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
checked={clockFormat[1]}
|
||||
onChange={this.handleClockRadio.bind(this, 'true')}
|
||||
/>
|
||||
{'24-hour clock (example: 16:00)'}
|
||||
<FormattedMessage
|
||||
id='user.settings.display.militaryClock'
|
||||
defaultMessage='24-hour clock (example: 16:00)'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div><br/>{'Select how you prefer time displayed.'}</div>
|
||||
<div>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.display.preferTime'
|
||||
defaultMessage='Select how you prefer time displayed.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
|
||||
clockSection = (
|
||||
<SettingItemMax
|
||||
title='Clock Display'
|
||||
title={formatMessage(holders.clockDisplay)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -135,9 +189,9 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.militaryTime === 'true') {
|
||||
describe = '24-hour clock (example: 16:00)';
|
||||
describe = formatMessage(holders.militaryClock);
|
||||
} else {
|
||||
describe = '12-hour clock (example: 4:00 PM)';
|
||||
describe = formatMessage(holders.normalClock);
|
||||
}
|
||||
|
||||
const handleUpdateClockSection = () => {
|
||||
@@ -146,16 +200,31 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
|
||||
clockSection = (
|
||||
<SettingItemMin
|
||||
title='Clock Display'
|
||||
title={formatMessage(holders.clockDisplay)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateClockSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const showUsername = 'Show username (team default)';
|
||||
const showNickname = 'Show nickname if one exists, otherwise show first and last name';
|
||||
const showFullName = 'Show first and last name';
|
||||
const showUsername = (
|
||||
<FormattedMessage
|
||||
id='user.settings.display.showUsername'
|
||||
defaultMessage='Show username (team default)'
|
||||
/>
|
||||
);
|
||||
const showNickname = (
|
||||
<FormattedMessage
|
||||
id='user.settings.display.showNickname'
|
||||
defaultMessage='Show nickname if one exists, otherwise show first and last name'
|
||||
/>
|
||||
);
|
||||
const showFullName = (
|
||||
<FormattedMessage
|
||||
id='user.settings.display.showFullname'
|
||||
defaultMessage='Show first and last name'
|
||||
/>
|
||||
);
|
||||
if (this.props.activeSection === 'name_format') {
|
||||
const nameFormat = [false, false, false];
|
||||
if (this.state.nameFormat === 'nickname_full_name') {
|
||||
@@ -201,13 +270,19 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div><br/>{'Set how to display other user\'s names in posts and the Direct Messages list.'}</div>
|
||||
<div>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.display.nameOptsDesc'
|
||||
defaultMessage="Set how to display other user's names in posts and the Direct Messages list."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
|
||||
nameFormatSection = (
|
||||
<SettingItemMax
|
||||
title='Teammate Name Display'
|
||||
title={formatMessage(holders.teammateDisplay)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -220,16 +295,16 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.nameFormat === 'username') {
|
||||
describe = showUsername;
|
||||
describe = formatMessage(holders.showUsername);
|
||||
} else if (this.state.nameFormat === 'full_name') {
|
||||
describe = showFullName;
|
||||
describe = formatMessage(holders.showFullName);
|
||||
} else {
|
||||
describe = showNickname;
|
||||
describe = formatMessage(holders.showNickname);
|
||||
}
|
||||
|
||||
nameFormatSection = (
|
||||
<SettingItemMin
|
||||
title='Teammate Name Display'
|
||||
title={formatMessage(holders.teammateDisplay)}
|
||||
describe={describe}
|
||||
updateSection={() => {
|
||||
this.props.updateSection('name_format');
|
||||
@@ -267,13 +342,19 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
<div><br/>{'Select the font displayed in the Mattermost user interface.'}</div>
|
||||
<div>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.display.fontDesc'
|
||||
defaultMessage='Select the font displayed in the Mattermost user interface.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
|
||||
fontSection = (
|
||||
<SettingItemMax
|
||||
title='Display Font'
|
||||
title={formatMessage(holders.fontTitle)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -286,7 +367,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
} else {
|
||||
fontSection = (
|
||||
<SettingItemMin
|
||||
title='Display Font'
|
||||
title={formatMessage(holders.fontTitle)}
|
||||
describe={this.state.selectedFont}
|
||||
updateSection={() => {
|
||||
this.props.updateSection('font');
|
||||
@@ -307,7 +388,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
|
||||
languagesSection = (
|
||||
<SettingItemMax
|
||||
title={'Language'}
|
||||
title={formatMessage(holders.language)}
|
||||
width='medium'
|
||||
inputs={inputs}
|
||||
updateSection={(e) => {
|
||||
@@ -326,7 +407,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
|
||||
languagesSection = (
|
||||
<SettingItemMin
|
||||
title={'Language'}
|
||||
title={formatMessage(holders.language)}
|
||||
width='medium'
|
||||
describe={locale}
|
||||
updateSection={() => {
|
||||
@@ -357,11 +438,19 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Display Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.display.title'
|
||||
defaultMessage='Display Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Display Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.display.title'
|
||||
defaultMessage='Display Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{fontSection}
|
||||
<div className='divider-dark'/>
|
||||
@@ -377,6 +466,7 @@ export default class UserSettingsDisplay extends React.Component {
|
||||
}
|
||||
|
||||
UserSettingsDisplay.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
@@ -384,3 +474,5 @@ UserSettingsDisplay.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserSettingsDisplay);
|
||||
@@ -13,7 +13,84 @@ import Constants from '../../utils/constants.jsx';
|
||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
export default class UserSettingsGeneralTab extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
usernameReserved: {
|
||||
id: 'user.settings.general.usernameReserved',
|
||||
defaultMessage: 'This username is reserved, please choose a new one.'
|
||||
},
|
||||
usernameRestrictions: {
|
||||
id: 'user.settings.general.usernameRestrictions',
|
||||
defaultMessage: "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."
|
||||
},
|
||||
validEmail: {
|
||||
id: 'user.settings.general.validEmail',
|
||||
defaultMessage: 'Please enter a valid email address'
|
||||
},
|
||||
emailMatch: {
|
||||
id: 'user.settings.general.emailMatch',
|
||||
defaultMessage: 'The new emails you entered do not match.'
|
||||
},
|
||||
checkEmail: {
|
||||
id: 'user.settings.general.checkEmail',
|
||||
defaultMessage: 'Check your email at {email} to verify the address.'
|
||||
},
|
||||
newAddress: {
|
||||
id: 'user.settings.general.newAddress',
|
||||
defaultMessage: 'New Address: {email}<br />Check your email to verify the above address.'
|
||||
},
|
||||
checkEmailNoAddress: {
|
||||
id: 'user.settings.general.checkEmailNoAddress',
|
||||
defaultMessage: 'Check your email to verify your new address'
|
||||
},
|
||||
loginGitlab: {
|
||||
id: 'user.settings.general.loginGitlab',
|
||||
defaultMessage: 'Log in done through GitLab'
|
||||
},
|
||||
validImage: {
|
||||
id: 'user.settings.general.validImage',
|
||||
defaultMessage: 'Only JPG or PNG images may be used for profile pictures'
|
||||
},
|
||||
imageTooLarge: {
|
||||
id: 'user.settings.general.imageTooLarge',
|
||||
defaultMessage: 'Unable to upload profile image. File is too large.'
|
||||
},
|
||||
uploadImage: {
|
||||
id: 'user.settings.general.uploadImage',
|
||||
defaultMessage: "Click 'Edit' to upload an image."
|
||||
},
|
||||
imageUpdated: {
|
||||
id: 'user.settings.general.imageUpdated',
|
||||
defaultMessage: 'Image last updated {date}'
|
||||
},
|
||||
fullName: {
|
||||
id: 'user.settings.general.fullName',
|
||||
defaultMessage: 'Full Name'
|
||||
},
|
||||
nickname: {
|
||||
id: 'user.settings.general.nickname',
|
||||
defaultMessage: 'Nickname'
|
||||
},
|
||||
username: {
|
||||
id: 'user.settings.general.username',
|
||||
defaultMessage: 'Username'
|
||||
},
|
||||
email: {
|
||||
id: 'user.settings.general.email',
|
||||
defaultMessage: 'Email'
|
||||
},
|
||||
profilePicture: {
|
||||
id: 'user.settings.general.profilePicture',
|
||||
defaultMessage: 'Profile Picture'
|
||||
},
|
||||
close: {
|
||||
id: 'user.settings.general.close',
|
||||
defaultMessage: 'Close'
|
||||
}
|
||||
});
|
||||
|
||||
class UserSettingsGeneralTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.submitActive = false;
|
||||
@@ -42,12 +119,13 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
const user = Object.assign({}, this.props.user);
|
||||
const username = this.state.username.trim().toLowerCase();
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
const usernameError = Utils.isValidUsername(username);
|
||||
if (usernameError === 'Cannot use a reserved word as a username.') {
|
||||
this.setState({clientError: 'This username is reserved, please choose a new one.'});
|
||||
this.setState({clientError: formatMessage(holders.usernameReserved)});
|
||||
return;
|
||||
} else if (usernameError) {
|
||||
this.setState({clientError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."});
|
||||
this.setState({clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,13 +177,14 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
const email = this.state.email.trim().toLowerCase();
|
||||
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (email === '' || !Utils.isEmail(email)) {
|
||||
this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
|
||||
this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
if (email !== confirmEmail) {
|
||||
this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''});
|
||||
this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,7 +204,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
|
||||
|
||||
if (verificationEnabled) {
|
||||
ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'});
|
||||
ErrorStore.storeLastError({message: this.props.intl.formatMessage(holders.checkEmail, {email: user.email})});
|
||||
ErrorStore.emitChange();
|
||||
this.setState({emailChangeInProgress: true});
|
||||
}
|
||||
@@ -152,13 +231,14 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
const picture = this.state.picture;
|
||||
|
||||
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
|
||||
this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'});
|
||||
this.setState({clientError: formatMessage(holders.validImage)});
|
||||
return;
|
||||
} else if (picture.size > Constants.MAX_FILE_SIZE) {
|
||||
this.setState({clientError: 'Unable to upload profile image. File is too large.'});
|
||||
this.setState({clientError: formatMessage(holders.imageTooLarge)});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,6 +301,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
}
|
||||
render() {
|
||||
const user = this.props.user;
|
||||
const {formatMessage, formatHTMLMessage} = this.props.intl;
|
||||
|
||||
let clientError = null;
|
||||
if (this.state.clientError) {
|
||||
@@ -244,7 +325,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
key='firstNameSetting'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{'First Name'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.firstName'
|
||||
defaultMessage='First Name'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -261,7 +347,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
key='lastNameSetting'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{'Last Name'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.lastName'
|
||||
defaultMessage='Last Name'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -284,20 +375,36 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
href='#'
|
||||
onClick={notifClick.bind(this)}
|
||||
>
|
||||
{'Notifications'}
|
||||
<FormattedMessage
|
||||
id='user.settings.general.notificationsLink'
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
const extraInfo = (
|
||||
<span>
|
||||
{'By default, you will receive mention notifications when someone types your first name. '}
|
||||
{'Go to '} {notifLink} {'settings to change this default.'}
|
||||
<FormattedMessage
|
||||
id='user.settings.general.notificationsExtra1'
|
||||
defaultMessage='By default, you will receive mention notifications when someone types your first name. Go to '
|
||||
>
|
||||
{(intro) => (
|
||||
<span key='intro'>
|
||||
{intro}
|
||||
{notifLink}
|
||||
<FormattedMessage
|
||||
id='user.settings.general.notificationsExtra2'
|
||||
defaultMessage=' settings to change this default.'
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</span>
|
||||
);
|
||||
|
||||
nameSection = (
|
||||
<SettingItemMax
|
||||
title='Full Name'
|
||||
title={formatMessage(holders.fullName)}
|
||||
inputs={inputs}
|
||||
submit={this.submitName}
|
||||
server_error={serverError}
|
||||
@@ -322,7 +429,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
|
||||
nameSection = (
|
||||
<SettingItemMin
|
||||
title='Full Name'
|
||||
title={formatMessage(holders.fullName)}
|
||||
describe={fullName}
|
||||
updateSection={() => {
|
||||
this.updateSection('name');
|
||||
@@ -333,7 +440,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
|
||||
let nicknameSection;
|
||||
if (this.props.activeSection === 'nickname') {
|
||||
let nicknameLabel = 'Nickname';
|
||||
let nicknameLabel = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.nickname'
|
||||
defaultMessage='Nickname'
|
||||
/>
|
||||
);
|
||||
if (Utils.isMobile()) {
|
||||
nicknameLabel = '';
|
||||
}
|
||||
@@ -357,13 +469,16 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
|
||||
const extraInfo = (
|
||||
<span>
|
||||
{'Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'}
|
||||
<FormattedMessage
|
||||
id='user.settings.general.nicknameExtra'
|
||||
defaultMessage='Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
nicknameSection = (
|
||||
<SettingItemMax
|
||||
title='Nickname'
|
||||
title={formatMessage(holders.nickname)}
|
||||
inputs={inputs}
|
||||
submit={this.submitNickname}
|
||||
server_error={serverError}
|
||||
@@ -378,7 +493,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
} else {
|
||||
nicknameSection = (
|
||||
<SettingItemMin
|
||||
title='Nickname'
|
||||
title={formatMessage(holders.nickname)}
|
||||
describe={UserStore.getCurrentUser().nickname}
|
||||
updateSection={() => {
|
||||
this.updateSection('nickname');
|
||||
@@ -389,7 +504,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
|
||||
let usernameSection;
|
||||
if (this.props.activeSection === 'username') {
|
||||
let usernameLabel = 'Username';
|
||||
let usernameLabel = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.username'
|
||||
defaultMessage='Username'
|
||||
/>
|
||||
);
|
||||
if (Utils.isMobile()) {
|
||||
usernameLabel = '';
|
||||
}
|
||||
@@ -411,11 +531,18 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
const extraInfo = (<span>{'Pick something easy for teammates to recognize and recall.'}</span>);
|
||||
const extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.usernameInfo'
|
||||
defaultMessage='Pick something easy for teammates to recognize and recall.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
usernameSection = (
|
||||
<SettingItemMax
|
||||
title='Username'
|
||||
title={formatMessage(holders.username)}
|
||||
inputs={inputs}
|
||||
submit={this.submitUsername}
|
||||
server_error={serverError}
|
||||
@@ -430,7 +557,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
} else {
|
||||
usernameSection = (
|
||||
<SettingItemMin
|
||||
title='Username'
|
||||
title={formatMessage(holders.username)}
|
||||
describe={UserStore.getCurrentUser().username}
|
||||
updateSection={() => {
|
||||
this.updateSection('username');
|
||||
@@ -443,16 +570,41 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
if (this.props.activeSection === 'email') {
|
||||
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
|
||||
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
|
||||
let helpText = 'Email is used for sign-in, notifications, and password reset. Email requires verification if changed.';
|
||||
let helpText = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emailHelp1'
|
||||
defaultMessage='Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'
|
||||
/>
|
||||
);
|
||||
|
||||
if (!emailEnabled) {
|
||||
helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>;
|
||||
helpText = (
|
||||
<div className='setting-list__hint text-danger'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emailHelp2'
|
||||
defaultMessage='Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (!emailVerificationEnabled) {
|
||||
helpText = 'Email is used for sign-in, notifications, and password reset.';
|
||||
helpText = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emailHelp3'
|
||||
defaultMessage='Email is used for sign-in, notifications, and password reset.'
|
||||
/>
|
||||
);
|
||||
} else if (this.state.emailChangeInProgress) {
|
||||
const newEmail = UserStore.getCurrentUser().email;
|
||||
if (newEmail) {
|
||||
helpText = 'A verification email was sent to ' + newEmail + '.';
|
||||
helpText = (
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emailHelp4'
|
||||
defaultMessage='A verification email was sent to {email}.'
|
||||
values={{
|
||||
email: newEmail
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +614,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
inputs.push(
|
||||
<div key='emailSetting'>
|
||||
<div className='form-group'>
|
||||
<label className='col-sm-5 control-label'>{'Primary Email'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.primaryEmail'
|
||||
defaultMessage='Primary Email'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -478,7 +635,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
inputs.push(
|
||||
<div key='confirmEmailSetting'>
|
||||
<div className='form-group'>
|
||||
<label className='col-sm-5 control-label'>{'Confirm Email'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.confirmEmail'
|
||||
defaultMessage='Confirm Email'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -499,7 +661,12 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
key='oauthEmailInfo'
|
||||
className='form-group'
|
||||
>
|
||||
<div className='setting-list__hint'>{'Log in occurs through GitLab. Email cannot be updated.'}</div>
|
||||
<div className='setting-list__hint'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.emailCantUpdate'
|
||||
defaultMessage='Log in occurs through GitLab. Email cannot be updated.'
|
||||
/>
|
||||
</div>
|
||||
{helpText}
|
||||
</div>
|
||||
);
|
||||
@@ -524,20 +691,20 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
if (this.state.emailChangeInProgress) {
|
||||
const newEmail = UserStore.getCurrentUser().email;
|
||||
if (newEmail) {
|
||||
describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.';
|
||||
describe = formatHTMLMessage(holders.newAddress, {email: newEmail});
|
||||
} else {
|
||||
describe = 'Check your email to verify your new address';
|
||||
describe = formatMessage(holders.checkEmailNoAddress);
|
||||
}
|
||||
} else {
|
||||
describe = UserStore.getCurrentUser().email;
|
||||
}
|
||||
} else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
|
||||
describe = 'Log in done through GitLab';
|
||||
describe = formatMessage(holders.loginGitlab);
|
||||
}
|
||||
|
||||
emailSection = (
|
||||
<SettingItemMin
|
||||
title='Email'
|
||||
title={formatMessage(holders.email)}
|
||||
describe={describe}
|
||||
updateSection={() => {
|
||||
this.updateSection('email');
|
||||
@@ -550,7 +717,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
if (this.props.activeSection === 'picture') {
|
||||
pictureSection = (
|
||||
<SettingPicture
|
||||
title='Profile Picture'
|
||||
title={formatMessage(holders.profilePicture)}
|
||||
submit={this.submitPicture}
|
||||
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
|
||||
server_error={serverError}
|
||||
@@ -566,13 +733,15 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
let minMessage = 'Click \'Edit\' to upload an image.';
|
||||
let minMessage = formatMessage(holders.uploadImage);
|
||||
if (user.last_picture_update) {
|
||||
minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update);
|
||||
minMessage = formatMessage(holders.imageUpdated, {
|
||||
date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'})
|
||||
});
|
||||
}
|
||||
pictureSection = (
|
||||
<SettingItemMin
|
||||
title='Profile Picture'
|
||||
title={formatMessage(holders.profilePicture)}
|
||||
describe={minMessage}
|
||||
updateSection={() => {
|
||||
this.updateSection('picture');
|
||||
@@ -588,7 +757,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
type='button'
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
aria-label={formatMessage(holders.close)}
|
||||
onClick={this.props.closeModal}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
@@ -601,11 +770,19 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'General Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.general.title'
|
||||
defaultMessage='General Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'General Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.general.title'
|
||||
defaultMessage='General Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{nameSection}
|
||||
<div className='divider-light'/>
|
||||
@@ -624,6 +801,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
||||
}
|
||||
|
||||
UserSettingsGeneralTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object.isRequired,
|
||||
updateSection: React.PropTypes.func.isRequired,
|
||||
updateTab: React.PropTypes.func.isRequired,
|
||||
@@ -631,3 +809,5 @@ UserSettingsGeneralTab.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserSettingsGeneralTab);
|
||||
@@ -6,7 +6,28 @@ import SettingItemMax from '../setting_item_max.jsx';
|
||||
import ManageIncomingHooks from './manage_incoming_hooks.jsx';
|
||||
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
|
||||
|
||||
export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
inName: {
|
||||
id: 'user.settings.integrations.incomingWebhooks',
|
||||
defaultMessage: 'Incoming Webhooks'
|
||||
},
|
||||
inDesc: {
|
||||
id: 'user.settings.integrations.incomingWebhooksDescription',
|
||||
defaultMessage: 'Manage your incoming webhooks'
|
||||
},
|
||||
outName: {
|
||||
id: 'user.settings.integrations.outWebhooks',
|
||||
defaultMessage: 'Outgoing Webhooks'
|
||||
},
|
||||
outDesc: {
|
||||
id: 'user.settings.integrations.outWebhooksDescription',
|
||||
defaultMessage: 'Manage your outgoing webhooks'
|
||||
}
|
||||
});
|
||||
|
||||
class UserSettingsIntegrationsTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -21,6 +42,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
let incomingHooksSection;
|
||||
let outgoingHooksSection;
|
||||
var inputs = [];
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
|
||||
if (this.props.activeSection === 'incoming-hooks') {
|
||||
@@ -30,7 +52,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
|
||||
incomingHooksSection = (
|
||||
<SettingItemMax
|
||||
title='Incoming Webhooks'
|
||||
title={formatMessage(holders.inName)}
|
||||
width='medium'
|
||||
inputs={inputs}
|
||||
updateSection={(e) => {
|
||||
@@ -42,9 +64,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
} else {
|
||||
incomingHooksSection = (
|
||||
<SettingItemMin
|
||||
title='Incoming Webhooks'
|
||||
title={formatMessage(holders.inName)}
|
||||
width='medium'
|
||||
describe='Manage your incoming webhooks'
|
||||
describe={formatMessage(holders.inDesc)}
|
||||
updateSection={() => {
|
||||
this.updateSection('incoming-hooks');
|
||||
}}
|
||||
@@ -61,7 +83,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
|
||||
outgoingHooksSection = (
|
||||
<SettingItemMax
|
||||
title='Outgoing Webhooks'
|
||||
title={formatMessage(holders.outName)}
|
||||
width='medium'
|
||||
inputs={inputs}
|
||||
updateSection={(e) => {
|
||||
@@ -73,9 +95,9 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
} else {
|
||||
outgoingHooksSection = (
|
||||
<SettingItemMin
|
||||
title='Outgoing Webhooks'
|
||||
title={formatMessage(holders.outName)}
|
||||
width='medium'
|
||||
describe='Manage your outgoing webhooks'
|
||||
describe={formatMessage(holders.outDesc)}
|
||||
updateSection={() => {
|
||||
this.updateSection('outgoing-hooks');
|
||||
}}
|
||||
@@ -104,11 +126,19 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Integration Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.integrations.title'
|
||||
defaultMessage='Integration Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Integration Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.integrations.title'
|
||||
defaultMessage='Integration Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{incomingHooksSection}
|
||||
<div className='divider-light'/>
|
||||
@@ -121,6 +151,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
||||
}
|
||||
|
||||
UserSettingsIntegrationsTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
@@ -128,3 +159,5 @@ UserSettingsIntegrationsTab.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserSettingsIntegrationsTab);
|
||||
@@ -6,7 +6,56 @@ const Modal = ReactBootstrap.Modal;
|
||||
import SettingsSidebar from '../settings_sidebar.jsx';
|
||||
import UserSettings from './user_settings.jsx';
|
||||
|
||||
export default class UserSettingsModal extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
general: {
|
||||
id: 'user.settings.modal.general',
|
||||
defaultMessage: 'General'
|
||||
},
|
||||
security: {
|
||||
id: 'user.settings.modal.security',
|
||||
defaultMessage: 'Security'
|
||||
},
|
||||
notifications: {
|
||||
id: 'user.settings.modal.notifications',
|
||||
defaultMessage: 'Notifications'
|
||||
},
|
||||
appearance: {
|
||||
id: 'user.settings.modal.appearance',
|
||||
defaultMessage: 'Appearance'
|
||||
},
|
||||
developer: {
|
||||
id: 'user.settings.modal.developer',
|
||||
defaultMessage: 'Developer'
|
||||
},
|
||||
integrations: {
|
||||
id: 'user.settings.modal.integrations',
|
||||
defaultMessage: 'Integrations'
|
||||
},
|
||||
display: {
|
||||
id: 'user.settings.modal.display',
|
||||
defaultMessage: 'Display'
|
||||
},
|
||||
advanced: {
|
||||
id: 'user.settings.modal.advanced',
|
||||
defaultMessage: 'Advanced'
|
||||
},
|
||||
confirmTitle: {
|
||||
id: 'user.settings.modal.confirmTitle',
|
||||
defaultMessage: 'Discard Changes?'
|
||||
},
|
||||
confirmMsg: {
|
||||
id: 'user.settings.modal.confirmMsg',
|
||||
defaultMessage: 'You have unsaved changes, are you sure you want to discard them?'
|
||||
},
|
||||
confirmBtns: {
|
||||
id: 'user.settings.modal.confirmBtns',
|
||||
defaultMessage: 'Yes, Discard'
|
||||
}
|
||||
});
|
||||
|
||||
class UserSettingsModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -170,20 +219,21 @@ export default class UserSettingsModal extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
var tabs = [];
|
||||
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
|
||||
tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
|
||||
tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
|
||||
tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
|
||||
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'});
|
||||
tabs.push({name: 'appearance', uiName: formatMessage(holders.appearance), icon: 'glyphicon glyphicon-wrench'});
|
||||
if (global.window.mm_config.EnableOAuthServiceProvider === 'true') {
|
||||
tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
|
||||
tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'});
|
||||
}
|
||||
|
||||
if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
|
||||
tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
|
||||
}
|
||||
tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
|
||||
tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
|
||||
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'});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -194,7 +244,12 @@ export default class UserSettingsModal extends React.Component {
|
||||
enforceFocus={this.state.enforceFocus}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{'Account Settings'}</Modal.Title>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id='user.settings.modal.title'
|
||||
defaultMessage='Account Settings'
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body ref='modalBody'>
|
||||
<div className='settings-table'>
|
||||
@@ -221,9 +276,9 @@ export default class UserSettingsModal extends React.Component {
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<ConfirmModal
|
||||
title='Discard Changes?'
|
||||
message='You have unsaved changes, are you sure you want to discard them?'
|
||||
confirm_button='Yes, Discard'
|
||||
title={formatMessage(holders.confirmTitle)}
|
||||
message={formatMessage(holders.confirmMsg)}
|
||||
confirm_button={formatMessage(holders.confirmBtns)}
|
||||
show={this.state.showConfirmModal}
|
||||
onConfirm={this.handleConfirm}
|
||||
onCancel={this.handleCancelConfirmation}
|
||||
@@ -234,6 +289,9 @@ export default class UserSettingsModal extends React.Component {
|
||||
}
|
||||
|
||||
UserSettingsModal.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
onModalDismissed: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserSettingsModal);
|
||||
@@ -10,6 +10,8 @@ import * as Client from '../../utils/client.jsx';
|
||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
function getNotificationsStateFromStores() {
|
||||
var user = UserStore.getCurrentUser();
|
||||
var soundNeeded = !Utils.isBrowserFirefox();
|
||||
@@ -73,7 +75,30 @@ function getNotificationsStateFromStores() {
|
||||
firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
|
||||
}
|
||||
|
||||
export default class NotificationsTab extends React.Component {
|
||||
const holders = defineMessages({
|
||||
desktop: {
|
||||
id: 'user.settings.notifications.desktop',
|
||||
defaultMessage: 'Send desktop notifications'
|
||||
},
|
||||
desktopSounds: {
|
||||
id: 'user.settings.notifications.desktopSounds',
|
||||
defaultMessage: 'Desktop notification sounds'
|
||||
},
|
||||
emailNotifications: {
|
||||
id: 'user.settings.notifications.emailNotifications',
|
||||
defaultMessage: 'Email notifications'
|
||||
},
|
||||
wordsTrigger: {
|
||||
id: 'user.settings.notifications.wordsTrigger',
|
||||
defaultMessage: 'Words that trigger mentions'
|
||||
},
|
||||
close: {
|
||||
id: 'user.settings.notifications.close',
|
||||
defaultMessage: 'Close'
|
||||
}
|
||||
});
|
||||
|
||||
class NotificationsTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -198,6 +223,7 @@ export default class NotificationsTab extends React.Component {
|
||||
this.updateCustomMentionKeys();
|
||||
}
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
var serverError = null;
|
||||
if (this.state.serverError) {
|
||||
serverError = this.state.serverError;
|
||||
@@ -227,7 +253,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={notifyActive[0]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'all')}
|
||||
/>
|
||||
{'For all activity'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notification.allActivity'
|
||||
defaultMessage='For all activity'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -238,7 +267,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={notifyActive[1]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'mention')}
|
||||
/>
|
||||
{'Only for mentions and direct messages'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions and direct messages'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -249,17 +281,27 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={notifyActive[2]}
|
||||
onChange={this.handleNotifyRadio.bind(this, 'none')}
|
||||
/>
|
||||
{'Never'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>;
|
||||
const extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.info'
|
||||
defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
desktopSection = (
|
||||
<SettingItemMax
|
||||
title='Send desktop notifications'
|
||||
title={formatMessage(holders.desktop)}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@@ -270,11 +312,26 @@ export default class NotificationsTab extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.notifyLevel === 'mention') {
|
||||
describe = 'Only for mentions and direct messages';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions and direct messages'
|
||||
/>
|
||||
);
|
||||
} else if (this.state.notifyLevel === 'none') {
|
||||
describe = 'Never';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
describe = 'For all activity';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notification.allActivity'
|
||||
defaultMessage='For all activity'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleUpdateDesktopSection = function updateDesktopSection() {
|
||||
@@ -283,7 +340,7 @@ export default class NotificationsTab extends React.Component {
|
||||
|
||||
desktopSection = (
|
||||
<SettingItemMin
|
||||
title='Send desktop notifications'
|
||||
title={formatMessage(holders.desktop)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateDesktopSection}
|
||||
/>
|
||||
@@ -311,7 +368,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={soundActive[0]}
|
||||
onChange={this.handleSoundRadio.bind(this, 'true')}
|
||||
/>
|
||||
{'On'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -322,18 +382,28 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={soundActive[1]}
|
||||
onChange={this.handleSoundRadio.bind(this, 'false')}
|
||||
/>
|
||||
{'Off'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>;
|
||||
const extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.info'
|
||||
defaultMessage='Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
soundSection = (
|
||||
<SettingItemMax
|
||||
title='Desktop notification sounds'
|
||||
title={formatMessage(holders.desktopSounds)}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@@ -344,11 +414,26 @@ export default class NotificationsTab extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (!this.state.soundNeeded) {
|
||||
describe = 'Please configure notification sounds in your browser settings';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notification.soundConfig'
|
||||
defaultMessage='Please configure notification sounds in your browser settings'
|
||||
/>
|
||||
);
|
||||
} else if (this.state.enableSound === 'false') {
|
||||
describe = 'Off';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
describe = 'On';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleUpdateSoundSection = function updateSoundSection() {
|
||||
@@ -357,7 +442,7 @@ export default class NotificationsTab extends React.Component {
|
||||
|
||||
soundSection = (
|
||||
<SettingItemMin
|
||||
title='Desktop notification sounds'
|
||||
title={formatMessage(holders.desktopSounds)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateSoundSection}
|
||||
disableOpen = {!this.state.soundNeeded}
|
||||
@@ -386,7 +471,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={emailActive[0]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'true')}
|
||||
/>
|
||||
{'On'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -397,17 +485,28 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={emailActive[1]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'false')}
|
||||
/>
|
||||
{'Off'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.mm_config.SiteName + ' for more than 5 minutes.'}</div>
|
||||
<div><br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.emailInfo'
|
||||
defaultMessage='Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.'
|
||||
values={{
|
||||
siteName: global.window.mm_config.SiteName
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
emailSection = (
|
||||
<SettingItemMax
|
||||
title='Email notifications'
|
||||
title={formatMessage(holders.emailNotifications)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -417,9 +516,19 @@ export default class NotificationsTab extends React.Component {
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.enableEmail === 'false') {
|
||||
describe = 'Off';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
describe = 'On';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleUpdateEmailSection = function updateEmailSection() {
|
||||
@@ -428,7 +537,7 @@ export default class NotificationsTab extends React.Component {
|
||||
|
||||
emailSection = (
|
||||
<SettingItemMin
|
||||
title='Email notifications'
|
||||
title={formatMessage(holders.emailNotifications)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateEmailSection}
|
||||
/>
|
||||
@@ -459,7 +568,13 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.firstNameKey}
|
||||
onChange={handleUpdateFirstNameKey}
|
||||
/>
|
||||
{'Your case sensitive first name "' + user.first_name + '"'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.sensitiveName'
|
||||
defaultMessage='Your case sensitive first name "{first_name}"'
|
||||
values={{
|
||||
first_name: user.first_name
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -478,7 +593,13 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.usernameKey}
|
||||
onChange={handleUpdateUsernameKey}
|
||||
/>
|
||||
{'Your non-case sensitive username "' + user.username + '"'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.sensitiveUsername'
|
||||
defaultMessage='Your non-case sensitive username "{username}"'
|
||||
values={{
|
||||
username: user.username
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -496,7 +617,13 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.mentionKey}
|
||||
onChange={handleUpdateMentionKey}
|
||||
/>
|
||||
{'Your username mentioned "@' + user.username + '"'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.usernameMention'
|
||||
defaultMessage='Your username mentioned "@{username}"'
|
||||
values={{
|
||||
username: user.username
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -514,7 +641,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.allKey}
|
||||
onChange={handleUpdateAllKey}
|
||||
/>
|
||||
{'Team-wide mentions "@all"'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.teamWide'
|
||||
defaultMessage='Team-wide mentions "@all"'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -532,7 +662,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.channelKey}
|
||||
onChange={handleUpdateChannelKey}
|
||||
/>
|
||||
{'Channel-wide mentions "@channel"'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelWide'
|
||||
defaultMessage='Channel-wide mentions "@channel"'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -548,7 +681,10 @@ export default class NotificationsTab extends React.Component {
|
||||
checked={this.state.customKeysChecked}
|
||||
onChange={this.updateCustomMentionKeys}
|
||||
/>
|
||||
{'Other non-case sensitive words, separated by commas:'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.sensitiveWords'
|
||||
defaultMessage='Other non-case sensitive words, separated by commas:'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
@@ -563,7 +699,7 @@ export default class NotificationsTab extends React.Component {
|
||||
|
||||
keysSection = (
|
||||
<SettingItemMax
|
||||
title='Words that trigger mentions'
|
||||
title={formatMessage(holders.wordsTrigger)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -601,7 +737,12 @@ export default class NotificationsTab extends React.Component {
|
||||
if (describe.length > 0) {
|
||||
describe = describe.substring(0, describe.length - 2);
|
||||
} else {
|
||||
describe = 'No words configured';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.noWords'
|
||||
defaultMessage='No words configured'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleUpdateKeysSection = function updateKeysSection() {
|
||||
@@ -610,7 +751,7 @@ export default class NotificationsTab extends React.Component {
|
||||
|
||||
keysSection = (
|
||||
<SettingItemMin
|
||||
title='Words that trigger mentions'
|
||||
title={formatMessage(holders.wordsTrigger)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateKeysSection}
|
||||
/>
|
||||
@@ -624,7 +765,7 @@ export default class NotificationsTab extends React.Component {
|
||||
type='button'
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
aria-label={formatMessage(holders.close)}
|
||||
onClick={this.props.closeModal}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
@@ -637,14 +778,22 @@ export default class NotificationsTab extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Notification Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.title'
|
||||
defaultMessage='Notification Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div
|
||||
ref='wrapper'
|
||||
className='user-settings'
|
||||
>
|
||||
<h3 className='tab-header'>{'Notifications'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.header'
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{desktopSection}
|
||||
<div className='divider-light'/>
|
||||
@@ -667,6 +816,7 @@ NotificationsTab.defaultProps = {
|
||||
activeTab: ''
|
||||
};
|
||||
NotificationsTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
@@ -675,3 +825,5 @@ NotificationsTab.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(NotificationsTab);
|
||||
@@ -13,7 +13,40 @@ import * as Client from '../../utils/client.jsx';
|
||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
|
||||
export default class SecurityTab extends React.Component {
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
currentPasswordError: {
|
||||
id: 'user.settings.security.currentPasswordError',
|
||||
defaultMessage: 'Please enter your current password'
|
||||
},
|
||||
passwordLengthError: {
|
||||
id: 'user.settings.security.passwordLengthError',
|
||||
defaultMessage: 'New passwords must be at least {chars} characters'
|
||||
},
|
||||
passwordMatchError: {
|
||||
id: 'user.settings.security.passwordMatchError',
|
||||
defaultMessage: 'The new passwords you entered do not match'
|
||||
},
|
||||
password: {
|
||||
id: 'user.settings.security.password',
|
||||
defaultMessage: 'Password'
|
||||
},
|
||||
lastUpdated: {
|
||||
id: 'user.settings.security.lastUpdated',
|
||||
defaultMessage: 'Last updated {date} at {time}'
|
||||
},
|
||||
method: {
|
||||
id: 'user.settings.security.method',
|
||||
defaultMessage: 'Sign-in Method'
|
||||
},
|
||||
close: {
|
||||
id: 'user.settings.security.close',
|
||||
defaultMessage: 'Close'
|
||||
}
|
||||
});
|
||||
|
||||
class SecurityTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -43,18 +76,19 @@ export default class SecurityTab extends React.Component {
|
||||
var newPassword = this.state.newPassword;
|
||||
var confirmPassword = this.state.confirmPassword;
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (currentPassword === '') {
|
||||
this.setState({passwordError: 'Please enter your current password', serverError: ''});
|
||||
this.setState({passwordError: formatMessage(holders.currentPasswordError), serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) {
|
||||
this.setState({passwordError: 'New passwords must be at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''});
|
||||
this.setState({passwordError: formatMessage(holders.passwordLengthError, {chars: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
this.setState({passwordError: 'The new passwords you entered do not match', serverError: ''});
|
||||
this.setState({passwordError: formatMessage(holders.passwordMatchError), serverError: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,6 +126,7 @@ export default class SecurityTab extends React.Component {
|
||||
}
|
||||
createPasswordSection() {
|
||||
let updateSectionStatus;
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
|
||||
const inputs = [];
|
||||
@@ -101,7 +136,12 @@ export default class SecurityTab extends React.Component {
|
||||
key='currentPasswordUpdateForm'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{'Current Password'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.currentPassword'
|
||||
defaultMessage='Current Password'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -117,7 +157,12 @@ export default class SecurityTab extends React.Component {
|
||||
key='newPasswordUpdateForm'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{'New Password'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.newPassword'
|
||||
defaultMessage='New Password'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -133,7 +178,12 @@ export default class SecurityTab extends React.Component {
|
||||
key='retypeNewPasswordUpdateForm'
|
||||
className='form-group'
|
||||
>
|
||||
<label className='col-sm-5 control-label'>{'Retype New Password'}</label>
|
||||
<label className='col-sm-5 control-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.retypePassword'
|
||||
defaultMessage='Retype New Password'
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-7'>
|
||||
<input
|
||||
className='form-control'
|
||||
@@ -153,7 +203,7 @@ export default class SecurityTab extends React.Component {
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title='Password'
|
||||
title={formatMessage(holders.password)}
|
||||
inputs={inputs}
|
||||
submit={this.submitPassword}
|
||||
server_error={this.state.serverError}
|
||||
@@ -165,20 +215,16 @@ export default class SecurityTab extends React.Component {
|
||||
|
||||
var describe;
|
||||
var d = new Date(this.props.user.last_password_update);
|
||||
var hour = '12';
|
||||
if (d.getHours() % 12) {
|
||||
hour = String(d.getHours() % 12);
|
||||
}
|
||||
var min = String(d.getMinutes());
|
||||
if (d.getMinutes() < 10) {
|
||||
min = '0' + d.getMinutes();
|
||||
}
|
||||
var timeOfDay = ' am';
|
||||
if (d.getHours() >= 12) {
|
||||
timeOfDay = ' pm';
|
||||
}
|
||||
|
||||
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
|
||||
const locale = global.window.mm_locale;
|
||||
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
|
||||
});
|
||||
|
||||
updateSectionStatus = function updateSection() {
|
||||
this.props.updateSection('password');
|
||||
@@ -186,7 +232,7 @@ export default class SecurityTab extends React.Component {
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title='Password'
|
||||
title={formatMessage(holders.password)}
|
||||
describe={describe}
|
||||
updateSection={updateSectionStatus}
|
||||
/>
|
||||
@@ -208,7 +254,10 @@ export default class SecurityTab extends React.Component {
|
||||
className='btn btn-primary'
|
||||
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)}
|
||||
>
|
||||
{'Switch to using email and password'}
|
||||
<FormattedMessage
|
||||
id='user.settings.security.switchEmail'
|
||||
defaultMessage='Switch to using email and password'
|
||||
/>
|
||||
</a>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -223,7 +272,10 @@ export default class SecurityTab extends React.Component {
|
||||
className='btn btn-primary'
|
||||
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE}
|
||||
>
|
||||
{'Switch to using GitLab SSO'}
|
||||
<FormattedMessage
|
||||
id='user.settings.security.switchGitlab'
|
||||
defaultMessage='Switch to using GitLab SSO'
|
||||
/>
|
||||
</a>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -238,7 +290,10 @@ export default class SecurityTab extends React.Component {
|
||||
className='btn btn-primary'
|
||||
href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE}
|
||||
>
|
||||
{'Switch to using Google SSO'}
|
||||
<FormattedMessage
|
||||
id='user.settings.security.switchGoogle'
|
||||
defaultMessage='Switch to using Google SSO'
|
||||
/>
|
||||
</a>
|
||||
<br/>
|
||||
</div>
|
||||
@@ -260,11 +315,18 @@ export default class SecurityTab extends React.Component {
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
|
||||
const extraInfo = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.oneSignin'
|
||||
defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title='Sign-in Method'
|
||||
title={this.props.intl.formatMessage(holders.method)}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
server_error={this.state.serverError}
|
||||
@@ -277,14 +339,24 @@ export default class SecurityTab extends React.Component {
|
||||
this.props.updateSection('signin');
|
||||
}.bind(this);
|
||||
|
||||
let describe = 'Email and Password';
|
||||
let describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.security.emailPwd'
|
||||
defaultMessage='Email and Password'
|
||||
/>
|
||||
);
|
||||
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
|
||||
describe = 'GitLab SSO';
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.security.gitlab'
|
||||
defaultMessage='GitLab SSO'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title='Sign-in Method'
|
||||
title={this.props.intl.formatMessage(holders.method)}
|
||||
describe={describe}
|
||||
updateSection={updateSectionStatus}
|
||||
/>
|
||||
@@ -309,7 +381,7 @@ export default class SecurityTab extends React.Component {
|
||||
type='button'
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
aria-label={this.props.intl.formatMessage(holders.close)}
|
||||
onClick={this.props.closeModal}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
@@ -322,11 +394,19 @@ export default class SecurityTab extends React.Component {
|
||||
className='modal-back'
|
||||
onClick={this.props.collapseModal}
|
||||
/>
|
||||
{'Security Settings'}
|
||||
<FormattedMessage
|
||||
id='user.settings.security.title'
|
||||
defaultMessage='Security Settings'
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<div className='user-settings'>
|
||||
<h3 className='tab-header'>{'Security Settings'}</h3>
|
||||
<h3 className='tab-header'>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.title'
|
||||
defaultMessage='Security Settings'
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{passwordSection}
|
||||
<div className='divider-light'/>
|
||||
@@ -337,14 +417,22 @@ export default class SecurityTab extends React.Component {
|
||||
className='security-links theme'
|
||||
dialogType={AccessHistoryModal}
|
||||
>
|
||||
<i className='fa fa-clock-o'></i>{'View Access History'}
|
||||
<i className='fa fa-clock-o'></i>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.viewHistory'
|
||||
defaultMessage='View Access History'
|
||||
/>
|
||||
</ToggleModalButton>
|
||||
<b> </b>
|
||||
<ToggleModalButton
|
||||
className='security-links theme'
|
||||
dialogType={ActivityLogModal}
|
||||
>
|
||||
<i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'}
|
||||
<i className='fa fa-clock-o'></i>
|
||||
<FormattedMessage
|
||||
id='user.settings.security.logoutActiveSessions'
|
||||
defaultMessage='View and Logout of Active Sessions'
|
||||
/>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -357,6 +445,7 @@ SecurityTab.defaultProps = {
|
||||
activeSection: ''
|
||||
};
|
||||
SecurityTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
activeSection: React.PropTypes.string,
|
||||
updateSection: React.PropTypes.func,
|
||||
@@ -365,3 +454,5 @@ SecurityTab.propTypes = {
|
||||
collapseModal: React.PropTypes.func.isRequired,
|
||||
setEnforceFocus: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(SecurityTab);
|
||||
Ссылка в новой задаче
Block a user