PLT-3647 Email Batching (#3718)
* PLT-3647 Added config settings for email batching * PLT-3647 Refactored generation of email notification * PLT-3647 Added serverside code for email batching * PLT-3647 Updated settings UI to enable email batching * PLT-3647 Removed debug code * PLT-3647 Fixed 0-padding of minutes in batched notification * PLT-3647 Updated clientside UI for when email batching is disabled * Go fmt * PLT-3647 Changed email batching to be disabled by default * Updated batched email message * Added email batching toggle to system console * Changed Email Notifications > Immediate setting to a 30 second batch interval * Go fmt * Fixed link to Mattermost icon in batched email notification * Updated users to use 30 second email batching by default * Fully disabled email batching when clustering is enabled * Fixed email batching setting in the system console * Fixed casing of 'Send Email notifications' -> 'Send email notifications' * Updating UI Improvements for email batching (#3736) * Updated text for notification settings and SiteURL. * Prevented enabling email batching when SiteURL isn't set in the system console * Re-added a couple debug messages * Added warning text when clustering is enabled
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
dde158c57f
Коммит
8203fd16ce
@@ -69,7 +69,7 @@ export default class ConfigurationSettings extends AdminSettings {
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.service.siteURLDescription'
|
||||
defaultMessage='The URL, including port number and protocol, from which users will access Mattermost. Leave blank to automatically configure based on incoming traffic.'
|
||||
defaultMessage='The URL, including post number and protocol, that users will use to access Mattermost. Leave blank to automatically configure based on incoming traffic.'
|
||||
/>
|
||||
}
|
||||
value={this.state.siteURL}
|
||||
|
||||
@@ -14,13 +14,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityNone'
|
||||
defaultMessage='None'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityNoneDescription'
|
||||
defaultMessage='Mattermost will connect over an unsecure connection.'
|
||||
@@ -28,13 +28,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityTls'
|
||||
defaultMessage='TLS'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityTlsDescription'
|
||||
defaultMessage='Encrypts the communication between Mattermost and your server.'
|
||||
@@ -42,13 +42,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityStart'
|
||||
defaultMessage='STARTTLS'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.connectionSecurityStartDescription'
|
||||
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
|
||||
|
||||
@@ -32,6 +32,7 @@ export default class EmailSettings extends AdminSettings {
|
||||
config.EmailSettings.SMTPServer = this.state.smtpServer;
|
||||
config.EmailSettings.SMTPPort = this.state.smtpPort;
|
||||
config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity;
|
||||
config.EmailSettings.EnableEmailBatching = this.state.enableEmailBatching;
|
||||
config.ServiceSettings.EnableSecurityFixAlert = this.state.enableSecurityFixAlert;
|
||||
|
||||
return config;
|
||||
@@ -48,6 +49,7 @@ export default class EmailSettings extends AdminSettings {
|
||||
smtpServer: config.EmailSettings.SMTPServer,
|
||||
smtpPort: config.EmailSettings.SMTPPort,
|
||||
connectionSecurity: config.EmailSettings.ConnectionSecurity,
|
||||
enableEmailBatching: config.EmailSettings.EnableEmailBatching,
|
||||
enableSecurityFixAlert: config.ServiceSettings.EnableSecurityFixAlert
|
||||
};
|
||||
}
|
||||
@@ -64,6 +66,34 @@ export default class EmailSettings extends AdminSettings {
|
||||
}
|
||||
|
||||
renderSettings() {
|
||||
let enableEmailBatchingDisabledText = null;
|
||||
|
||||
if (this.props.config.ClusterSettings.Enable) {
|
||||
enableEmailBatchingDisabledText = (
|
||||
<span
|
||||
key='admin.email.enableEmailBatching.clusterEnabled'
|
||||
className='help-text'
|
||||
>
|
||||
<FormattedHTMLMessage
|
||||
id='admin.email.enableEmailBatching.clusterEnabled'
|
||||
defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
} else if (!this.props.config.ServiceSettings.SiteURL) {
|
||||
enableEmailBatchingDisabledText = (
|
||||
<span
|
||||
key='admin.email.enableEmailBatching.siteURL'
|
||||
className='help-text'
|
||||
>
|
||||
<FormattedHTMLMessage
|
||||
id='admin.email.enableEmailBatching.siteURL'
|
||||
defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsGroup>
|
||||
<BooleanSetting
|
||||
@@ -83,6 +113,26 @@ export default class EmailSettings extends AdminSettings {
|
||||
value={this.state.sendEmailNotifications}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id='enableEmailBatching'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.email.enableEmailBatchingTitle'
|
||||
defaultMessage='Enable Email Batching: '
|
||||
/>
|
||||
}
|
||||
helpText={[
|
||||
<FormattedHTMLMessage
|
||||
key='admin.email.enableEmailBatchingDesc'
|
||||
id='admin.email.enableEmailBatchingDesc'
|
||||
defaultMessage='When true, users can have email notifications for multiple direct messages and mentions combined into a single email, configurable in <b>Account Settings > Notifications</b>.'
|
||||
/>,
|
||||
enableEmailBatchingDisabledText
|
||||
]}
|
||||
value={this.state.enableEmailBatching && !this.props.config.ClusterSettings.Enable && this.props.config.ServiceSettings.SiteURL}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.sendEmailNotifications || this.props.config.ClusterSettings.Enable || !this.props.config.ServiceSettings.SiteURL}
|
||||
/>
|
||||
<TextSetting
|
||||
id='feedbackName'
|
||||
label={
|
||||
|
||||
@@ -204,7 +204,8 @@ export default class LogSettings extends AdminSettings {
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='help-text'>{'%T'}</td><td className='help-text'>
|
||||
<td>{'%T'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatTime'
|
||||
defaultMessage='Time (15:04:05 MST)'
|
||||
@@ -212,7 +213,8 @@ export default class LogSettings extends AdminSettings {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>{'%D'}</td><td className='help-text'>
|
||||
<td>{'%D'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatDateLong'
|
||||
defaultMessage='Date (2006/01/02)'
|
||||
@@ -220,7 +222,8 @@ export default class LogSettings extends AdminSettings {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>{'%d'}</td><td className='help-text'>
|
||||
<td>{'%d'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatDateShort'
|
||||
defaultMessage='Date (01/02/06)'
|
||||
@@ -228,7 +231,8 @@ export default class LogSettings extends AdminSettings {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>{'%L'}</td><td className='help-text'>
|
||||
<td>{'%L'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatLevel'
|
||||
defaultMessage='Level (DEBG, INFO, EROR)'
|
||||
@@ -236,7 +240,8 @@ export default class LogSettings extends AdminSettings {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>{'%S'}</td><td className='help-text'>
|
||||
<td>{'%S'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatSource'
|
||||
defaultMessage='Source'
|
||||
@@ -244,7 +249,8 @@ export default class LogSettings extends AdminSettings {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>{'%M'}</td><td className='help-text'>
|
||||
<td>{'%M'}</td>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.log.formatMessage'
|
||||
defaultMessage='Message'
|
||||
|
||||
@@ -15,13 +15,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeGzip'
|
||||
defaultMessage='gzip'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeGzipDescription'
|
||||
defaultMessage='The Mattermost server will serve static files compressed with gzip.'
|
||||
@@ -29,13 +29,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeUncompressed'
|
||||
defaultMessage='Uncompressed'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeUncompressedDescription'
|
||||
defaultMessage='The Mattermost server will serve static files uncompressed.'
|
||||
@@ -43,13 +43,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeDisabled'
|
||||
defaultMessage='Disabled'
|
||||
/>
|
||||
</td>
|
||||
<td className='help-text'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='admin.webserverModeDisabledDescription'
|
||||
defaultMessage='The Mattermost server will not serve static files.'
|
||||
|
||||
211
webapp/components/user_settings/email_notification_setting.jsx
Обычный файл
211
webapp/components/user_settings/email_notification_setting.jsx
Обычный файл
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {savePreference} from 'utils/async_client.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import {localizeMessage} from 'utils/utils.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import SettingItemMin from 'components/setting_item_min.jsx';
|
||||
import SettingItemMax from 'components/setting_item_max.jsx';
|
||||
|
||||
import {Preferences} from 'utils/constants.jsx';
|
||||
|
||||
const INTERVAL_IMMEDIATE = 30; // "immediate" is a 30 second interval
|
||||
const INTERVAL_FIFTEEN_MINUTES = 15 * 60;
|
||||
const INTERVAL_HOUR = 60 * 60;
|
||||
|
||||
export default class EmailNotificationSetting extends React.Component {
|
||||
static propTypes = {
|
||||
activeSection: React.PropTypes.string.isRequired,
|
||||
updateSection: React.PropTypes.func.isRequired,
|
||||
enableEmail: React.PropTypes.bool.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
serverError: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.submit = this.submit.bind(this);
|
||||
|
||||
this.expand = this.expand.bind(this);
|
||||
this.collapse = this.collapse.bind(this);
|
||||
|
||||
this.state = {
|
||||
emailInterval: PreferenceStore.getInt(Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, INTERVAL_IMMEDIATE)
|
||||
};
|
||||
}
|
||||
|
||||
handleChange(enableEmail, emailInterval) {
|
||||
this.props.onChange(enableEmail);
|
||||
this.setState({emailInterval});
|
||||
}
|
||||
|
||||
submit() {
|
||||
// until the rest of the notification settings are moved to preferences, we have to do this separately
|
||||
savePreference(Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, this.state.emailInterval.toString());
|
||||
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
expand() {
|
||||
this.props.updateSection('email');
|
||||
}
|
||||
|
||||
collapse() {
|
||||
this.props.updateSection('');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.activeSection !== 'email') {
|
||||
let description;
|
||||
|
||||
if (this.props.enableEmail === 'true') {
|
||||
switch (this.state.emailInterval) {
|
||||
case INTERVAL_IMMEDIATE:
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.immediately'
|
||||
defaultMessage='Immediately'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case INTERVAL_HOUR:
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.everyHour'
|
||||
defaultMessage='Every hour'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.everyXMinutes'
|
||||
defaultMessage='Every {count, plural, one {minute} other {{count, number} minutes}}'
|
||||
values={{count: this.state.emailInterval / 60}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={localizeMessage('user.settings.notifications.emailNotifications', 'Send Email notifications')}
|
||||
describe={description}
|
||||
updateSection={this.expand}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let batchingOptions = null;
|
||||
let batchingInfo = null;
|
||||
if (window.mm_config.EnableEmailBatching === 'true') {
|
||||
batchingOptions = (
|
||||
<div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_FIFTEEN_MINUTES}
|
||||
onChange={this.handleChange.bind(this, 'true', INTERVAL_FIFTEEN_MINUTES)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.everyXMinutes'
|
||||
defaultMessage='Every {count} minutes'
|
||||
values={{count: INTERVAL_FIFTEEN_MINUTES / 60}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_HOUR}
|
||||
onChange={this.handleChange.bind(this, 'true', INTERVAL_HOUR)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.everyHour'
|
||||
defaultMessage='Every hour'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
batchingInfo = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.emailBatchingInfo'
|
||||
defaultMessage='Notifications are combined into a single email and sent at the maximum frequency selected here.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={localizeMessage('user.settings.notifications.emailNotifications', 'Send email notifications')}
|
||||
inputs={[
|
||||
<div key='userNotificationEmailOptions'>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_IMMEDIATE}
|
||||
onChange={this.handleChange.bind(this, 'true', INTERVAL_IMMEDIATE)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.email.immediately'
|
||||
defaultMessage='Immediately'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{batchingOptions}
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={this.props.enableEmail === 'false'}
|
||||
onChange={this.handleChange.bind(this, 'false', INTERVAL_IMMEDIATE)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.emailInfo'
|
||||
defaultMessage='Email notifications that are sent for mentions and direct messages when you are offline or away from {siteName} for more than 5 minutes.'
|
||||
values={{
|
||||
siteName: global.window.mm_config.SiteName
|
||||
}}
|
||||
/>
|
||||
{' '}
|
||||
{batchingInfo}
|
||||
</div>
|
||||
</div>
|
||||
]}
|
||||
submit={this.submit}
|
||||
server_error={this.props.serverError}
|
||||
updateSection={this.collapse}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import SettingItemMin from '../setting_item_min.jsx';
|
||||
import SettingItemMax from '../setting_item_max.jsx';
|
||||
import SettingItemMin from 'components/setting_item_min.jsx';
|
||||
import SettingItemMax from 'components/setting_item_max.jsx';
|
||||
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
@@ -12,7 +12,8 @@ import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import * as UserAgent from 'utils/user_agent.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
||||
import EmailNotificationSetting from './email_notification_setting.jsx';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
function getNotificationsStateFromStores() {
|
||||
var user = UserStore.getCurrentUser();
|
||||
@@ -30,9 +31,9 @@ function getNotificationsStateFromStores() {
|
||||
if (user.notify_props && user.notify_props.comments) {
|
||||
comments = user.notify_props.comments;
|
||||
}
|
||||
var email = 'true';
|
||||
var enableEmail = 'true';
|
||||
if (user.notify_props && user.notify_props.email) {
|
||||
email = user.notify_props.email;
|
||||
enableEmail = user.notify_props.email;
|
||||
}
|
||||
var push = 'mention';
|
||||
if (user.notify_props && user.notify_props.push) {
|
||||
@@ -78,7 +79,7 @@ function getNotificationsStateFromStores() {
|
||||
return {
|
||||
notifyLevel: desktop,
|
||||
notifyPushLevel: push,
|
||||
enableEmail: email,
|
||||
enableEmail,
|
||||
soundNeeded,
|
||||
enableSound: sound,
|
||||
usernameKey,
|
||||
@@ -91,36 +92,9 @@ function getNotificationsStateFromStores() {
|
||||
};
|
||||
}
|
||||
|
||||
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'
|
||||
},
|
||||
comments: {
|
||||
id: 'user.settings.notifications.comments',
|
||||
defaultMessage: 'Comment threads notifications'
|
||||
},
|
||||
close: {
|
||||
id: 'user.settings.notifications.close',
|
||||
defaultMessage: 'Close'
|
||||
}
|
||||
});
|
||||
|
||||
import React from 'react';
|
||||
|
||||
class NotificationsTab extends React.Component {
|
||||
export default class NotificationsTab extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -142,6 +116,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
this.state = getNotificationsStateFromStores();
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
var data = {};
|
||||
data.user_id = this.props.user.id;
|
||||
@@ -178,31 +153,38 @@ class NotificationsTab extends React.Component {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleCancel(e) {
|
||||
this.updateState();
|
||||
this.props.updateSection('');
|
||||
e.preventDefault();
|
||||
$('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
|
||||
}
|
||||
|
||||
updateSection(section) {
|
||||
this.updateState();
|
||||
this.props.updateSection(section);
|
||||
}
|
||||
|
||||
updateState() {
|
||||
const newState = getNotificationsStateFromStores();
|
||||
if (!Utils.areObjectsEqual(newState, this.state)) {
|
||||
this.setState(newState);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.addChangeListener(this.onListenerChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.removeChangeListener(this.onListenerChange);
|
||||
}
|
||||
|
||||
onListenerChange() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
handleNotifyRadio(notifyLevel) {
|
||||
this.setState({notifyLevel});
|
||||
this.refs.wrapper.focus();
|
||||
@@ -222,22 +204,28 @@ class NotificationsTab extends React.Component {
|
||||
this.setState({enableEmail});
|
||||
this.refs.wrapper.focus();
|
||||
}
|
||||
|
||||
handleSoundRadio(enableSound) {
|
||||
this.setState({enableSound});
|
||||
this.refs.wrapper.focus();
|
||||
}
|
||||
|
||||
updateUsernameKey(val) {
|
||||
this.setState({usernameKey: val});
|
||||
}
|
||||
|
||||
updateMentionKey(val) {
|
||||
this.setState({mentionKey: val});
|
||||
}
|
||||
|
||||
updateFirstNameKey(val) {
|
||||
this.setState({firstNameKey: val});
|
||||
}
|
||||
|
||||
updateChannelKey(val) {
|
||||
this.setState({channelKey: val});
|
||||
}
|
||||
|
||||
updateCustomMentionKeys() {
|
||||
var checked = this.refs.customcheck.checked;
|
||||
|
||||
@@ -250,10 +238,12 @@ class NotificationsTab extends React.Component {
|
||||
this.setState({customKeys: '', customKeysChecked: false});
|
||||
}
|
||||
}
|
||||
|
||||
onCustomChange() {
|
||||
this.refs.customcheck.checked = true;
|
||||
this.updateCustomMentionKeys();
|
||||
}
|
||||
|
||||
createPushNotificationSection() {
|
||||
var handleUpdateDesktopSection;
|
||||
if (this.props.activeSection === 'push') {
|
||||
@@ -312,8 +302,8 @@ class NotificationsTab extends React.Component {
|
||||
onChange={this.handlePushRadio.bind(this, 'none')}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.push_notification.off'
|
||||
defaultMessage='Off'
|
||||
id='user.settings.notifications.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -346,7 +336,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
|
||||
title={Utils.localizeMessage('user.settings.notifications.push', 'Send mobile push notifications')}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={submit}
|
||||
@@ -367,8 +357,8 @@ class NotificationsTab extends React.Component {
|
||||
} else if (this.state.notifyPushLevel === 'none') {
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.push_notification.off'
|
||||
defaultMessage='Off'
|
||||
id='user.settings.notifications.never'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
);
|
||||
} else if (global.window.mm_config.SendPushNotifications === 'false') {
|
||||
@@ -393,14 +383,14 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
|
||||
title={Utils.localizeMessage('user.settings.notifications.push', 'Send mobile push notifications')}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateDesktopSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const serverError = this.state.serverError;
|
||||
|
||||
var user = this.props.user;
|
||||
@@ -479,7 +469,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
desktopSection = (
|
||||
<SettingItemMax
|
||||
title={formatMessage(holders.desktop)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktop', 'Send desktop notifications')}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@@ -518,7 +508,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
desktopSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.desktop)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktop', 'Send desktop notifications')}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateDesktopSection}
|
||||
/>
|
||||
@@ -583,7 +573,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
soundSection = (
|
||||
<SettingItemMax
|
||||
title={formatMessage(holders.desktopSounds)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktopSounds', 'Desktop notification sounds')}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@@ -622,7 +612,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
soundSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.desktopSounds)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktopSounds', 'Desktop notification sounds')}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateSoundSection}
|
||||
disableOpen={!this.state.soundNeeded}
|
||||
@@ -630,102 +620,6 @@ class NotificationsTab extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
var emailSection;
|
||||
var handleUpdateEmailSection;
|
||||
if (this.props.activeSection === 'email') {
|
||||
var emailActive = [false, false];
|
||||
if (this.state.enableEmail === 'false') {
|
||||
emailActive[1] = true;
|
||||
} else {
|
||||
emailActive[0] = true;
|
||||
}
|
||||
|
||||
let inputs = [];
|
||||
|
||||
inputs.push(
|
||||
<div key='userNotificationEmailOptions'>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={emailActive[0]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'true')}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name='emailNotifications'
|
||||
checked={emailActive[1]}
|
||||
onChange={this.handleEmailRadio.bind(this, 'false')}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</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={formatMessage(holders.emailNotifications)}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
updateSection={this.handleCancel}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
let describe = '';
|
||||
if (this.state.enableEmail === 'false') {
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
describe = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleUpdateEmailSection = function updateEmailSection() {
|
||||
this.props.updateSection('email');
|
||||
}.bind(this);
|
||||
|
||||
emailSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.emailNotifications)}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateEmailSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
var keysSection;
|
||||
var handleUpdateKeysSection;
|
||||
if (this.props.activeSection === 'keys') {
|
||||
@@ -859,7 +753,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
keysSection = (
|
||||
<SettingItemMax
|
||||
title={formatMessage(holders.wordsTrigger)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
server_error={serverError}
|
||||
@@ -910,7 +804,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
keysSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.wordsTrigger)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateKeysSection}
|
||||
/>
|
||||
@@ -991,7 +885,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
commentsSection = (
|
||||
<SettingItemMax
|
||||
title={formatMessage(holders.comments)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.comments', 'Comment threads notifications')}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@@ -1030,7 +924,7 @@ class NotificationsTab extends React.Component {
|
||||
|
||||
commentsSection = (
|
||||
<SettingItemMin
|
||||
title={formatMessage(holders.comments)}
|
||||
title={Utils.localizeMessage('user.settings.notifications.comments', 'Comment threads notifications')}
|
||||
describe={describe}
|
||||
updateSection={handleUpdateCommentsSection}
|
||||
/>
|
||||
@@ -1046,7 +940,6 @@ class NotificationsTab extends React.Component {
|
||||
type='button'
|
||||
className='close'
|
||||
data-dismiss='modal'
|
||||
aria-label={formatMessage(holders.close)}
|
||||
onClick={this.props.closeModal}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
@@ -1082,7 +975,14 @@ class NotificationsTab extends React.Component {
|
||||
<div className='divider-light'/>
|
||||
{soundSection}
|
||||
<div className='divider-light'/>
|
||||
{emailSection}
|
||||
<EmailNotificationSetting
|
||||
activeSection={this.props.activeSection}
|
||||
updateSection={this.props.updateSection}
|
||||
enableEmail={this.state.enableEmail}
|
||||
onChange={this.handleEmailRadio}
|
||||
onSubmit={this.handleSubmit}
|
||||
serverError={this.state.serverError}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
{pushNotificationSection}
|
||||
<div className='divider-light'/>
|
||||
@@ -1103,7 +1003,6 @@ NotificationsTab.defaultProps = {
|
||||
activeTab: ''
|
||||
};
|
||||
NotificationsTab.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
updateSection: React.PropTypes.func,
|
||||
updateTab: React.PropTypes.func,
|
||||
@@ -1112,5 +1011,3 @@ NotificationsTab.propTypes = {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(NotificationsTab);
|
||||
|
||||
@@ -234,6 +234,10 @@
|
||||
"admin.email.easHelp": "Learn more about compiling and deploying your own mobile apps from an <a href=\"http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas\" target=\"_blank\">Enterprise App Store</a>.",
|
||||
"admin.email.emailFail": "Connection unsuccessful: {error}",
|
||||
"admin.email.emailSuccess": "No errors were reported while sending an email. Please check your inbox to make sure.",
|
||||
"admin.email.enableEmailBatching.clusterEnabled": "Email batching cannot be enabled when High Availability mode is enabled.",
|
||||
"admin.email.enableEmailBatching.siteURL": "Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.",
|
||||
"admin.email.enableEmailBatchingTitle": "Enable Email Batching:",
|
||||
"admin.email.enableEmailBatchingDesc": "When true, users can have email notifications for multiple direct messages and mentions combined into a single email, configurable in <b>Account Settings > Notifications</b>.",
|
||||
"admin.email.fullPushNotification": "Send full message snippet",
|
||||
"admin.email.genericPushNotification": "Send generic description with user and channel names",
|
||||
"admin.email.inviteSaltDescription": "32-character salt added to signing of email invites. Randomly generated on install. Click \"Regenerate\" to create new salt.",
|
||||
@@ -649,7 +653,7 @@
|
||||
"admin.service.sessionCacheDesc": "The number of minutes to cache a session in memory.",
|
||||
"admin.service.sessionDaysEx": "Ex \"30\"",
|
||||
"admin.service.siteURL": "Site URL:",
|
||||
"admin.service.siteURLDescription": "The URL, including port number and protocol, from which users will access Mattermost. Leave blank to automatically configure based on incoming traffic.",
|
||||
"admin.service.siteURLDescription": "The URL, including post number and protocol, that users will use to access Mattermost. Leave blank to automatically configure based on incoming traffic.",
|
||||
"admin.service.siteURLExample": "Ex \"https://mattermost.example.com:1234\"",
|
||||
"admin.service.ssoSessionDays": "Session length SSO (days):",
|
||||
"admin.service.ssoSessionDaysDesc": "The number of days from the last time a user entered their credentials to the expiry of the user's session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials.",
|
||||
@@ -1764,16 +1768,20 @@
|
||||
"user.settings.notifications.commentsRoot": "Mention any comments on your post",
|
||||
"user.settings.notifications.desktop": "Send desktop notifications",
|
||||
"user.settings.notifications.desktopSounds": "Desktop notification sounds",
|
||||
"user.settings.notifications.emailInfo": "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.",
|
||||
"user.settings.notifications.emailNotifications": "Email notifications",
|
||||
"user.settings.notifications.emailBatchingInfo": "Notifications are combined into a single email and sent at the maximum frequency selected here.",
|
||||
"user.settings.notifications.emailInfo": "Email notifications that are sent for mentions and direct messages when you are offline or away from {siteName} for more than 5 minutes.",
|
||||
"user.settings.notifications.emailNotifications": "Send email notifications",
|
||||
"user.settings.notifications.everyXMinutes": "Every {count, plural, one {minute} other {{count, number} minutes}}",
|
||||
"user.settings.notifications.everyHour": "Every hour",
|
||||
"user.settings.notifications.header": "Notifications",
|
||||
"user.settings.notifications.immediately": "Immediately",
|
||||
"user.settings.notifications.info": "Desktop notifications are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.",
|
||||
"user.settings.notifications.never": "Never",
|
||||
"user.settings.notifications.noWords": "No words configured",
|
||||
"user.settings.notifications.off": "Off",
|
||||
"user.settings.notifications.on": "On",
|
||||
"user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
|
||||
"user.settings.notifications.push": "Mobile push notifications",
|
||||
"user.settings.notifications.push": "Send mobile push notifications",
|
||||
"user.settings.notifications.sensitiveName": "Your case sensitive first name \"{first_name}\"",
|
||||
"user.settings.notifications.sensitiveUsername": "Your non-case sensitive username \"{username}\"",
|
||||
"user.settings.notifications.sensitiveWords": "Other non-case sensitive words, separated by commas:",
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
|
||||
.help-text {
|
||||
color: alpha-color($black, .5);
|
||||
display: block;
|
||||
margin: 10px 0 0;
|
||||
|
||||
&.no-margin {
|
||||
|
||||
@@ -52,7 +52,9 @@ export const Preferences = {
|
||||
COLLAPSE_DISPLAY_DEFAULT: 'false',
|
||||
USE_MILITARY_TIME: 'use_military_time',
|
||||
CATEGORY_THEME: 'theme',
|
||||
CATEGORY_FLAGGED_POST: 'flagged_post'
|
||||
CATEGORY_FLAGGED_POST: 'flagged_post',
|
||||
CATEGORY_NOTIFICATIONS: 'notifications',
|
||||
EMAIL_INTERVAL: 'email_interval'
|
||||
};
|
||||
|
||||
export const ActionTypes = keyMirror({
|
||||
|
||||
Ссылка в новой задаче
Block a user