MM-53478 : Improve design of keyword that trigger notification (#23934)

This commit is contained in:
M-ZubairAhmed 2023-09-09 14:45:50 +05:30 committed by GitHub
parent 5e7d1c6f9e
commit 060a63a3ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 844 additions and 341 deletions

View File

@ -34,7 +34,7 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
{key: 'desktop', label: 'Desktop Notifications', type: 'radio'},
{key: 'email', label: 'Email Notifications', type: 'radio'},
{key: 'push', label: 'Mobile Push Notifications', type: 'radio'},
{key: 'keys', label: 'Words That Trigger Mentions', type: 'checkbox'},
{key: 'keysWithNotification', label: 'Keywords that trigger Notifications', type: 'checkbox'},
{key: 'comments', label: 'Reply notifications', type: 'radio'},
],
display: [

View File

@ -23,12 +23,12 @@ describe('Notifications', () => {
// # Open 'Settings' modal
cy.uiOpenSettingsModal().within(() => {
// # Open 'Words That Trigger Mentions' setting and uncheck all the checkboxes
cy.findByRole('heading', {name: 'Words That Trigger Mentions'}).should('be.visible').click();
// # Open 'Keywords that trigger Notifications' setting and uncheck all the checkboxes
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
cy.findByRole('checkbox', {name: `Your case-sensitive first name "${otherUser.first_name}"`}).should('not.be.checked');
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
cy.findByRole('checkbox', {name: 'Channel-wide mentions "@channel", "@all", "@here"'}).click().should('not.be.checked');
cy.findByRole('checkbox', {name: 'Other non case-sensitive words, separated by commas:'}).should('not.be.checked');
cy.findByRole('checkbox', {name: 'Other non case-sensitive words, press Tab or use commas to separate keywords:'}).should('not.be.checked');
// # Save then close the modal
cy.uiSaveAndClose();

View File

@ -243,14 +243,16 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
// Navigate to settings modal
cy.uiOpenSettingsModal();
// Notifications header should be visible
cy.get('#notificationSettingsTitle').
scrollIntoView().
// # Click on notifications tab
cy.findByRoleExtended('tab', {name: 'Notifications'}).
should('be.visible').
and('contain', 'Notifications');
click();
// Notifications header should be visible
cy.findAllByText('Notifications').should('be.visible');
// Open up 'Words that trigger mentions' sub-section
cy.get('#keysTitle').
cy.findByText('Keywords that trigger Notifications').
scrollIntoView().
click();
@ -270,8 +272,8 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
// Set Custom field
if (desiredSettings.custom && desiredSettings.customText) {
cy.get('#notificationTriggerCustomText').
clear().
type(desiredSettings.customText);
type(desiredSettings.customText, {force: true}).
tab();
}
// Click “Save” and close modal

View File

@ -29,14 +29,16 @@ describe('Notifications', () => {
// # Open 'Settings' modal
cy.uiOpenSettingsModal().within(() => {
cy.get('#keysEdit').should('be.visible').click();
// # Open 'Keywords that trigger Notifications' setting
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
// * As otherUser, ensure that 'Your non-case sensitive username' is not checked
cy.get('#notificationTriggerUsername').should('not.be.checked');
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
// # Close the modal
cy.get('#accountSettingsHeader').find('button').should('be.visible').click();
// # Save then close the modal
cy.uiSaveAndClose();
});
cy.apiLogout();
// # Login as sysadmin

View File

@ -0,0 +1,476 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<div>
<div
id="notificationSettings"
>
<div
class="modal-header"
>
<button
class="close"
data-dismiss="modal"
id="closeButton"
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<h4
class="modal-title"
>
<div
class="modal-back"
>
<i
aria-label="Collapse Icon"
class="fa fa-angle-left"
/>
</div>
Notification Settings
</h4>
</div>
<div
class="user-settings"
>
<h3
class="tab-header"
id="notificationSettingsTitle"
>
Notifications
</h3>
<div
class="divider-dark first"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="desktopTitle"
>
Desktop Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
class="color--link cursor--pointer style--none text-left"
id="desktopEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="desktopDesc"
>
For all activity, without sound
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="emailTitle"
>
Email Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="emailTitle emailEdit"
class="color--link cursor--pointer style--none text-left"
id="emailEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="emailDesc"
>
Email notifications are not enabled
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="pushTitle"
>
Mobile Push Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
class="color--link cursor--pointer style--none text-left"
id="pushEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords that trigger Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link cursor--pointer style--none text-left"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
>
"@some-user"
</div>
</div>
<div
class="divider-light"
/>
<div
class="divider-dark"
/>
</div>
</div>
</div>
</body>,
"container": <div>
<div
id="notificationSettings"
>
<div
class="modal-header"
>
<button
class="close"
data-dismiss="modal"
id="closeButton"
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<h4
class="modal-title"
>
<div
class="modal-back"
>
<i
aria-label="Collapse Icon"
class="fa fa-angle-left"
/>
</div>
Notification Settings
</h4>
</div>
<div
class="user-settings"
>
<h3
class="tab-header"
id="notificationSettingsTitle"
>
Notifications
</h3>
<div
class="divider-dark first"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="desktopTitle"
>
Desktop Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
class="color--link cursor--pointer style--none text-left"
id="desktopEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="desktopDesc"
>
For all activity, without sound
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="emailTitle"
>
Email Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="emailTitle emailEdit"
class="color--link cursor--pointer style--none text-left"
id="emailEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="emailDesc"
>
Email notifications are not enabled
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="pushTitle"
>
Mobile Push Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
class="color--link cursor--pointer style--none text-left"
id="pushEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="d-flex"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords that trigger Notifications
</h4>
<div
class="section-min__edit"
>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link cursor--pointer style--none text-left"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
>
"@some-user"
</div>
</div>
<div
class="divider-light"
/>
<div
class="divider-dark"
/>
</div>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"replaceStoreState": [Function],
"rerender": [Function],
"unmount": [Function],
"updateStoreState": [Function],
}
`;

View File

@ -1,42 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {ActionCreatorsMapObject, Dispatch} from 'redux';
import {connect, type ConnectedProps} from 'react-redux';
import {updateMe} from 'mattermost-redux/actions/users';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import type {ActionFunc} from 'mattermost-redux/types/actions';
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
import type {GlobalState} from 'types/store';
import UserSettingsNotifications from './user_settings_notifications';
import type {Props} from './user_settings_notifications';
function mapStateToProps(state: GlobalState) {
const mapStateToProps = (state: GlobalState) => {
const config = getConfig(state);
const sendPushNotifications = config.SendPushNotifications === 'true';
const enableAutoResponder = config.ExperimentalEnableAutomaticReplies === 'true';
return {
sendPushNotifications,
enableAutoResponder,
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
};
}
};
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators<ActionCreatorsMapObject<ActionFunc>, Props['actions']>({
updateMe,
}, dispatch),
};
}
const mapDispatchToProps = {
updateMe,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export type PropsFromRedux = ConnectedProps<typeof connector>;
export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsNotifications);

View File

@ -0,0 +1,5 @@
.customKeywordsWithNotificationSubsection {
& .multiInput {
margin-block-start: 10px;
}
}

View File

@ -1,89 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {ComponentProps} from 'react';
import type {UserNotifyProps} from '@mattermost/types/users';
import {type IntlShape} from 'react-intl';
import {renderWithFullContext, screen} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import UserSettingsNotifications from './user_settings_notifications';
describe('components/user_settings/display/UserSettingsDisplay', () => {
const user = TestHelper.getUserMock({
id: 'user_id',
});
const requiredProps: ComponentProps<typeof UserSettingsNotifications> = {
user,
const defaultProps = {
user: TestHelper.getUserMock({id: 'user_id'}),
updateSection: jest.fn(),
activeSection: '',
closeModal: jest.fn(),
collapseModal: jest.fn(),
actions: {
updateMe: jest.fn(() => Promise.resolve({})),
},
isCollapsedThreadsEnabled: false,
updateMe: jest.fn(() => Promise.resolve({})),
isCollapsedThreadsEnabled: true,
sendPushNotifications: false,
enableAutoResponder: false,
isCallsRingingEnabled: true,
intl: {} as IntlShape,
};
test('should have called handleSubmit', async () => {
const props = {...requiredProps, actions: {...requiredProps.actions}};
const wrapper = shallow<UserSettingsNotifications>(
<UserSettingsNotifications {...props}/>,
test('should match snapshot', () => {
const wrapper = renderWithFullContext(
<UserSettingsNotifications {...defaultProps}/>,
);
await wrapper.instance().handleSubmit();
expect(requiredProps.actions.updateMe).toHaveBeenCalled();
});
test('should have called handleSubmit', async () => {
const updateMe = jest.fn(() => Promise.resolve({data: true}));
const props = {...requiredProps, actions: {...requiredProps.actions, updateMe}};
const wrapper = shallow<UserSettingsNotifications>(
<UserSettingsNotifications {...props}/>,
);
await wrapper.instance().handleSubmit();
expect(requiredProps.updateSection).toHaveBeenCalled();
expect(requiredProps.updateSection).toHaveBeenCalledWith('');
});
test('should reset state when handleUpdateSection is called', () => {
const newUpdateSection = jest.fn();
const updateArg = 'unreadChannels';
const props = {...requiredProps, updateSection: newUpdateSection, user: {...user, notify_props: {desktop: 'on'} as unknown as UserNotifyProps}};
const wrapper = shallow<UserSettingsNotifications>(
<UserSettingsNotifications {...props}/>,
);
wrapper.setState({isSaving: true, desktopActivity: 'off' as unknown as UserNotifyProps['desktop']});
wrapper.instance().handleUpdateSection(updateArg);
expect(wrapper.state('isSaving')).toEqual(false);
expect(wrapper.state('desktopActivity')).toEqual('on');
expect(newUpdateSection).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
});
test('should show reply notifications section when CRT off', () => {
const wrapper = shallow<UserSettingsNotifications>(
<UserSettingsNotifications {...requiredProps}/>,
);
expect(wrapper.exists('SettingItem[section="comments"]')).toBe(true);
});
const props = {...defaultProps, isCollapsedThreadsEnabled: false};
test('should not show reply notifications section when CRT on', () => {
const wrapper = shallow<UserSettingsNotifications>(
<UserSettingsNotifications
{...requiredProps}
isCollapsedThreadsEnabled={true}
/>,
);
expect(wrapper.exists('SettingItem[section="comments"]')).toBe(false);
renderWithFullContext(<UserSettingsNotifications {...props}/>);
expect(screen.getByText('Reply notifications')).toBeInTheDocument();
});
});

View File

@ -5,8 +5,12 @@
import React from 'react';
import type {ChangeEvent, RefObject} from 'react';
import {FormattedMessage} from 'react-intl';
import type {WrappedComponentProps} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {Styles as ReactSelectStyles, ValueType} from 'react-select';
import CreatableReactSelect from 'react-select/creatable';
import type {ServerError} from '@mattermost/types/errors';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
@ -17,28 +21,34 @@ import SettingItemMax from 'components/setting_item_max';
import Constants, {NotificationLevels} from 'utils/constants';
import {t} from 'utils/i18n';
import * as NotificationSounds from 'utils/notification_sounds';
import {a11yFocus, localizeMessage, moveCursorToEnd} from 'utils/utils';
import {stopTryNotificationRing} from 'utils/notification_sounds';
import {a11yFocus} from 'utils/utils';
import DesktopNotificationSettings from './desktop_notification_setting/desktop_notification_settings';
import EmailNotificationSetting from './email_notification_setting';
import ManageAutoResponder from './manage_auto_responder/manage_auto_responder';
export type Props = {
import type {PropsFromRedux} from './index';
import './user_settings_notifications.scss';
const WHITE_SPACE_REGEX = /\s+/g;
const COMMA_REGEX = /,/g;
type MultiInputValue = {
label: string;
value: string;
}
type OwnProps = {
user: UserProfile;
updateSection: (section: string) => void;
activeSection: string;
closeModal: () => void;
collapseModal: () => void;
sendPushNotifications: boolean;
enableAutoResponder: boolean;
actions: {
updateMe: (user: UserProfile) => Promise<ActionResult>;
};
isCollapsedThreadsEnabled: boolean;
isCallsRingingEnabled: boolean;
}
type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
type State = {
enableEmail: UserNotifyProps['email'];
desktopActivity: UserNotifyProps['desktop'];
@ -52,8 +62,9 @@ type State = {
desktopNotificationSound: UserNotifyProps['desktop_notification_sound'];
callsNotificationSound: UserNotifyProps['calls_notification_sound'];
usernameKey: boolean;
customKeys: string;
customKeysChecked: boolean;
isCustomKeysWithNotificationInputChecked: boolean;
customKeysWithNotification: MultiInputValue[];
customKeysWithNotificationInputValue: string;
firstNameKey: boolean;
channelKey: boolean;
autoResponderActive: boolean;
@ -63,9 +74,7 @@ type State = {
serverError: string;
};
function getNotificationsStateFromProps(props: Props): State {
const user = props.user;
function getDefaultStateFromProps(props: Props): State {
let desktop: UserNotifyProps['desktop'] = NotificationLevels.MENTION;
let desktopThreads: UserNotifyProps['desktop_threads'] = NotificationLevels.ALL;
let pushThreads: UserNotifyProps['push_threads'] = NotificationLevels.ALL;
@ -79,87 +88,86 @@ function getNotificationsStateFromProps(props: Props): State {
let pushActivity: UserNotifyProps['push'] = NotificationLevels.MENTION;
let pushStatus: UserNotifyProps['push_status'] = Constants.UserStatuses.AWAY;
let autoResponderActive = false;
let autoResponderMessage: UserNotifyProps['auto_responder_message'] = localizeMessage(
'user.settings.notifications.autoResponderDefault',
'Hello, I am out of office and unable to respond to messages.',
);
let autoResponderMessage: UserNotifyProps['auto_responder_message'] = props.intl.formatMessage({
id: 'user.settings.notifications.autoResponderDefault',
defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
});
if (user.notify_props) {
if (user.notify_props.desktop) {
desktop = user.notify_props.desktop;
if (props.user.notify_props) {
if (props.user.notify_props.desktop) {
desktop = props.user.notify_props.desktop;
}
if (user.notify_props.desktop_threads) {
desktopThreads = user.notify_props.desktop_threads;
if (props.user.notify_props.desktop_threads) {
desktopThreads = props.user.notify_props.desktop_threads;
}
if (user.notify_props.push_threads) {
pushThreads = user.notify_props.push_threads;
if (props.user.notify_props.push_threads) {
pushThreads = props.user.notify_props.push_threads;
}
if (user.notify_props.email_threads) {
emailThreads = user.notify_props.email_threads;
if (props.user.notify_props.email_threads) {
emailThreads = props.user.notify_props.email_threads;
}
if (user.notify_props.desktop_sound) {
sound = user.notify_props.desktop_sound;
if (props.user.notify_props.desktop_sound) {
sound = props.user.notify_props.desktop_sound;
}
if (user.notify_props.calls_desktop_sound) {
callsSound = user.notify_props.calls_desktop_sound;
if (props.user.notify_props.calls_desktop_sound) {
callsSound = props.user.notify_props.calls_desktop_sound;
}
if (user.notify_props.desktop_notification_sound) {
desktopNotificationSound = user.notify_props.desktop_notification_sound;
if (props.user.notify_props.desktop_notification_sound) {
desktopNotificationSound = props.user.notify_props.desktop_notification_sound;
}
if (user.notify_props.calls_notification_sound) {
callsNotificationSound = user.notify_props.calls_notification_sound;
if (props.user.notify_props.calls_notification_sound) {
callsNotificationSound = props.user.notify_props.calls_notification_sound;
}
if (user.notify_props.comments) {
comments = user.notify_props.comments;
if (props.user.notify_props.comments) {
comments = props.user.notify_props.comments;
}
if (user.notify_props.email) {
enableEmail = user.notify_props.email;
if (props.user.notify_props.email) {
enableEmail = props.user.notify_props.email;
}
if (user.notify_props.push) {
pushActivity = user.notify_props.push;
if (props.user.notify_props.push) {
pushActivity = props.user.notify_props.push;
}
if (user.notify_props.push_status) {
pushStatus = user.notify_props.push_status;
if (props.user.notify_props.push_status) {
pushStatus = props.user.notify_props.push_status;
}
if (user.notify_props.auto_responder_active) {
autoResponderActive = user.notify_props.auto_responder_active === 'true';
if (props.user.notify_props.auto_responder_active) {
autoResponderActive = props.user.notify_props.auto_responder_active === 'true';
}
if (user.notify_props.auto_responder_message) {
autoResponderMessage = user.notify_props.auto_responder_message;
if (props.user.notify_props.auto_responder_message) {
autoResponderMessage = props.user.notify_props.auto_responder_message;
}
}
let usernameKey = false;
let customKeys = '';
let firstNameKey = false;
let channelKey = false;
let isCustomKeysWithNotificationInputChecked = false;
const customKeysWithNotification: MultiInputValue[] = [];
if (user.notify_props) {
if (user.notify_props.mention_keys) {
const keys = user.notify_props.mention_keys.split(',');
if (keys.indexOf(user.username) === -1) {
usernameKey = false;
} else {
usernameKey = true;
keys.splice(keys.indexOf(user.username), 1);
if (keys.indexOf(`@${user.username}`) !== -1) {
keys.splice(keys.indexOf(`@${user.username}`), 1);
if (props.user.notify_props) {
if (props.user.notify_props.mention_keys) {
const mentionKeys = props.user.notify_props.mention_keys.split(',').filter((key) => key.length > 0);
mentionKeys.forEach((mentionKey) => {
// Remove username(s) from list of keys
if (mentionKey !== props.user.username && mentionKey !== `@${props.user.username}`) {
customKeysWithNotification.push({
label: mentionKey,
value: mentionKey,
});
}
}
});
customKeys = keys.join(',');
// Check if username is in list of keys, if so, set the checkbox to true
usernameKey = mentionKeys.includes(props.user.username);
// Check if there are any keys in the list, if so, set the checkbox of custom keys to true
isCustomKeysWithNotificationInputChecked = customKeysWithNotification.length > 0;
}
if (user.notify_props.first_name) {
firstNameKey = user.notify_props.first_name === 'true';
}
if (user.notify_props.channel) {
channelKey = user.notify_props.channel === 'true';
}
firstNameKey = props.user.notify_props?.first_name === 'true';
channelKey = props.user.notify_props?.channel === 'true';
}
return {
@ -175,8 +183,9 @@ function getNotificationsStateFromProps(props: Props): State {
desktopNotificationSound,
callsNotificationSound,
usernameKey,
customKeys,
customKeysChecked: customKeys.length > 0,
customKeysWithNotification,
isCustomKeysWithNotificationInputChecked,
customKeysWithNotificationInputValue: '',
firstNameKey,
channelKey,
autoResponderActive,
@ -187,9 +196,7 @@ function getNotificationsStateFromProps(props: Props): State {
};
}
export default class NotificationsTab extends React.PureComponent<Props, State> {
customCheckRef: RefObject<HTMLInputElement>;
customMentionsRef: RefObject<HTMLInputElement>;
class NotificationsTab extends React.PureComponent<Props, State> {
drawerRef: RefObject<HTMLHeadingElement>;
wrapperRef: RefObject<HTMLDivElement>;
@ -200,14 +207,12 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
constructor(props: Props) {
super(props);
this.state = getNotificationsStateFromProps(props);
this.customCheckRef = React.createRef();
this.customMentionsRef = React.createRef();
this.state = getDefaultStateFromProps(props);
this.drawerRef = React.createRef();
this.wrapperRef = React.createRef();
}
handleSubmit = (): void => {
handleSubmit = async () => {
const data: UserNotifyProps = {} as UserNotifyProps;
data.email = this.state.enableEmail;
data.desktop_sound = this.state.desktopSound;
@ -221,47 +226,46 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
data.push = this.state.pushActivity;
data.push_status = this.state.pushStatus;
data.comments = this.state.notifyCommentsLevel;
data.auto_responder_active = this.state.autoResponderActive.toString() as UserNotifyProps['auto_responder_active'];
data.auto_responder_active = this.state.autoResponderActive ? 'true' : 'false';
data.auto_responder_message = this.state.autoResponderMessage;
data.first_name = this.state.firstNameKey ? 'true' : 'false';
data.channel = this.state.channelKey ? 'true' : 'false';
if (!data.auto_responder_message || data.auto_responder_message === '') {
data.auto_responder_message = localizeMessage(
'user.settings.notifications.autoResponderDefault',
'Hello, I am out of office and unable to respond to messages.',
);
data.auto_responder_message = this.props.intl.formatMessage({
id: 'user.settings.notifications.autoResponderDefault',
defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
});
}
const mentionKeys = [];
const mentionKeys: string[] = [];
if (this.state.usernameKey) {
mentionKeys.push(this.props.user.username);
}
let stringKeys = mentionKeys.join(',');
if (this.state.customKeys.length > 0 && this.state.customKeysChecked) {
stringKeys += ',' + this.state.customKeys;
if (this.state.isCustomKeysWithNotificationInputChecked && this.state.customKeysWithNotification.length > 0) {
this.state.customKeysWithNotification.forEach((key) => {
mentionKeys.push(key.value);
});
}
data.mention_keys = stringKeys;
data.first_name = this.state.firstNameKey.toString() as UserNotifyProps['first_name'];
data.channel = this.state.channelKey.toString() as UserNotifyProps['channel'];
data.mention_keys = mentionKeys.join(',');
this.setState({isSaving: true});
NotificationSounds.stopTryNotificationRing();
stopTryNotificationRing();
this.props.actions.updateMe({notify_props: data} as UserProfile).
then(({data: result, error: err}) => {
if (result) {
this.handleUpdateSection('');
this.setState(getNotificationsStateFromProps(this.props));
} else if (err) {
this.setState({serverError: err.message, isSaving: false});
}
});
const {data: updatedUser, error} = await this.props.updateMe({notify_props: data}) as ActionResult<Partial<UserProfile>, ServerError>; // Fix in MM-46907
if (updatedUser) {
this.handleUpdateSection('');
this.setState(getDefaultStateFromProps(this.props));
} else if (error) {
this.setState({serverError: error.message, isSaving: false});
} else {
this.setState({serverError: '', isSaving: false});
}
};
handleCancel = (): void => {
this.setState(getNotificationsStateFromProps(this.props));
NotificationSounds.stopTryNotificationRing();
this.setState(getDefaultStateFromProps(this.props));
stopTryNotificationRing();
};
handleUpdateSection = (section: string): void => {
@ -302,30 +306,92 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
handleEmailRadio = (enableEmail: UserNotifyProps['email']): void => this.setState({enableEmail});
updateUsernameKey = (val: boolean): void => this.setState({usernameKey: val});
handleChangeForUsernameKeyCheckbox = (event: ChangeEvent<HTMLInputElement>) => {
const {target: {checked}} = event;
this.setState({usernameKey: checked});
};
updateFirstNameKey = (val: boolean): void => this.setState({firstNameKey: val});
handleChangeForFirstNameKeyCheckbox = (event: ChangeEvent<HTMLInputElement>) => {
const {target: {checked}} = event;
this.setState({firstNameKey: checked});
};
updateChannelKey = (val: boolean): void => this.setState({channelKey: val});
handleChangeForChannelKeyCheckbox = (event: ChangeEvent<HTMLInputElement>) => {
const {target: {checked}} = event;
this.setState({channelKey: checked});
};
updateCustomMentionKeys = (): void => {
const checked = this.customCheckRef.current?.checked;
handleChangeForCustomKeysWithNotificationCheckbox = (event: ChangeEvent<HTMLInputElement>) => {
const {target: {checked}} = event;
this.setState({isCustomKeysWithNotificationInputChecked: checked});
};
if (checked) {
const text = this.customMentionsRef.current?.value || '';
handleChangeForCustomKeysWithNotificationInput = (values: ValueType<{ value: string }>) => {
if (values && Array.isArray(values) && values.length > 0) {
// Check the custom keys input checkbox when atleast a single key is entered
if (this.state.isCustomKeysWithNotificationInputChecked === false) {
this.setState({
isCustomKeysWithNotificationInputChecked: true,
});
}
// remove all spaces and split string into individual keys
this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
const customKeysWithNotification = values.
map((value: MultiInputValue) => {
// Remove all spaces from the value
const formattedValue = value.value.trim().replace(WHITE_SPACE_REGEX, '');
return {value: formattedValue, label: formattedValue};
}).
filter((value) => value.value.length > 0);
this.setState({customKeysWithNotification});
} else {
this.setState({customKeys: '', customKeysChecked: false});
this.setState({
isCustomKeysWithNotificationInputChecked: false,
customKeysWithNotification: [],
});
}
};
onCustomChange = (): void => {
if (this.customCheckRef.current) {
this.customCheckRef.current.checked = true;
updateCustomKeysWithNotificationWithInputValue = (newValue: string) => {
const customKeysWithNotification = [
...this.state.customKeysWithNotification,
{
value: newValue,
label: newValue,
},
];
this.setState({
customKeysWithNotification,
customKeysWithNotificationInputValue: '', // Clear the input field
});
if (!this.state.isCustomKeysWithNotificationInputChecked) {
this.setState({isCustomKeysWithNotificationInputChecked: true});
}
};
handleOnKeydownForCustomKeysWithNotificationInput = (event: React.KeyboardEvent) => {
if (event.key === Constants.KeyCodes.COMMA[0] || event.key === Constants.KeyCodes.TAB[0]) {
const unsavedCustomKeyWithNotification = this.state.customKeysWithNotificationInputValue?.trim()?.replace(WHITE_SPACE_REGEX, '')?.replace(COMMA_REGEX, '') ?? '';
if (unsavedCustomKeyWithNotification.length > 0) {
this.updateCustomKeysWithNotificationWithInputValue(unsavedCustomKeyWithNotification);
}
}
};
handleChangeForCustomKeysWithNotificationInputValue = (value: string) => {
// Check if input contains comma, if so, add the value to the list of custom keys
if (!value.includes(Constants.KeyCodes.COMMA[0])) {
const formattedValue = value.trim().replace(WHITE_SPACE_REGEX, '');
this.setState({customKeysWithNotificationInputValue: formattedValue});
}
};
handleBlurForCustomKeysWithNotificationInput = () => {
const unsavedCustomKeyWithNotification = this.state.customKeysWithNotificationInputValue?.trim()?.replace(WHITE_SPACE_REGEX, '')?.replace(COMMA_REGEX, '') ?? '';
if (unsavedCustomKeyWithNotification.length > 0) {
this.updateCustomKeysWithNotificationWithInputValue(unsavedCustomKeyWithNotification);
}
this.updateCustomMentionKeys();
};
createPushNotificationSection = () => {
@ -544,7 +610,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
}
max = (
<SettingItemMax
title={localizeMessage('user.settings.notifications.push', 'Mobile Push Notifications')}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.push', defaultMessage: 'Mobile Push Notifications'})}
inputs={inputs}
submit={submit}
serverError={this.state.serverError}
@ -618,9 +684,9 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.push', defaultMessage: 'Mobile Push Notifications'})}
active={active}
areAllSectionsInactive={this.props.activeSection === ''}
title={localizeMessage('user.settings.notifications.push', 'Mobile Push Notifications')}
describe={describe}
section={'push'}
updateSection={this.handleUpdateSection}
@ -629,17 +695,16 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
);
};
createKeysSection = () => {
createKeywordsWithNotificationSection = () => {
const serverError = this.state.serverError;
const user = this.props.user;
const active = this.props.activeSection === 'keys';
const isSectionExpanded = this.props.activeSection === 'keysWithNotification';
let max = null;
if (active) {
let expandedSection = null;
if (isSectionExpanded) {
const inputs = [];
if (user.first_name) {
const handleUpdateFirstNameKey = (e: ChangeEvent<HTMLInputElement>): void => this.updateFirstNameKey(e.target.checked);
inputs.push(
<div key='userNotificationFirstNameOption'>
<div className='checkbox'>
@ -648,11 +713,11 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
id='notificationTriggerFirst'
type='checkbox'
checked={this.state.firstNameKey}
onChange={handleUpdateFirstNameKey}
onChange={this.handleChangeForFirstNameKeyCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.sensitiveName'
defaultMessage='Your case sensitive first name "{first_name}"'
defaultMessage='Your case-sensitive first name "{first_name}"'
values={{
first_name: user.first_name,
}}
@ -663,7 +728,6 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
);
}
const handleUpdateUsernameKey = (e: ChangeEvent<HTMLInputElement>): void => this.updateUsernameKey(e.target.checked);
inputs.push(
<div key='userNotificationUsernameOption'>
<div className='checkbox'>
@ -672,7 +736,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
id='notificationTriggerUsername'
type='checkbox'
checked={this.state.usernameKey}
onChange={handleUpdateUsernameKey}
onChange={this.handleChangeForUsernameKeyCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.sensitiveUsername'
@ -686,7 +750,6 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
</div>,
);
const handleUpdateChannelKey = (e: ChangeEvent<HTMLInputElement>): void => this.updateChannelKey(e.target.checked);
inputs.push(
<div key='userNotificationChannelOption'>
<div className='checkbox'>
@ -695,7 +758,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
id='notificationTriggerShouts'
type='checkbox'
checked={this.state.channelKey}
onChange={handleUpdateChannelKey}
onChange={this.handleChangeForChannelKeyCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.channelWide'
@ -707,51 +770,61 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
);
inputs.push(
<div key='userNotificationCustomOption'>
<div
key='userNotificationCustomOption'
className='customKeywordsWithNotificationSubsection'
>
<div className='checkbox'>
<label>
<input
id='notificationTriggerCustom'
ref={this.customCheckRef}
type='checkbox'
checked={this.state.customKeysChecked}
onChange={this.updateCustomMentionKeys}
checked={this.state.isCustomKeysWithNotificationInputChecked}
onChange={this.handleChangeForCustomKeysWithNotificationCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.sensitiveWords'
defaultMessage='Other non-case sensitive words, separated by commas:'
id='user.settings.notifications.sensitiveCustomWords'
defaultMessage='Other non case-sensitive words, press Tab or use commas to separate keywords:'
/>
</label>
</div>
<input
id='notificationTriggerCustomText'
autoFocus={this.state.customKeysChecked}
ref={this.customMentionsRef}
className='form-control mentions-input'
type='text'
defaultValue={this.state.customKeys}
onChange={this.onCustomChange}
onFocus={moveCursorToEnd}
<CreatableReactSelect
inputId='notificationTriggerCustomText'
autoFocus={true}
isClearable={false}
isMulti={true}
styles={customKeywordsWithNotificationStyles}
className='multiInput'
placeholder=''
components={{
DropdownIndicator: () => null,
Menu: () => null,
MenuList: () => null,
}}
aria-labelledby='notificationTriggerCustom'
onChange={this.handleChangeForCustomKeysWithNotificationInput}
value={this.state.customKeysWithNotification}
inputValue={this.state.customKeysWithNotificationInputValue}
onInputChange={this.handleChangeForCustomKeysWithNotificationInputValue}
onBlur={this.handleBlurForCustomKeysWithNotificationInput}
onKeyDown={this.handleOnKeydownForCustomKeysWithNotificationInput}
/>
</div>,
);
const extraInfo = (
<span>
<FormattedMessage
id='user.settings.notifications.mentionsInfo'
defaultMessage='Mentions trigger when someone sends a message that includes your username (@{username}) or any of the options selected above.'
values={{
username: user.username,
}}
/>
</span>
<FormattedMessage
id='user.settings.notifications.keywordsWithNotification.extraInfo'
defaultMessage='Notifications are triggered when someone sends a message that includes your username ("@{username}") or any of the options selected above.'
values={{
username: user.username,
}}
/>
);
max = (
expandedSection = (
<SettingItemMax
title={localizeMessage('user.settings.notifications.wordsTrigger', 'Words That Trigger Mentions')}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger Notifications'})}
inputs={inputs}
submit={this.handleSubmit}
saving={this.state.isSaving}
@ -762,51 +835,33 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
);
}
let keys = ['@' + user.username];
const selectedMentionKeys = ['@' + user.username];
if (this.state.firstNameKey) {
keys.push(user.first_name);
selectedMentionKeys.push(user.first_name);
}
if (this.state.usernameKey) {
keys.push(user.username);
selectedMentionKeys.push(user.username);
}
if (this.state.channelKey) {
keys.push('@channel');
keys.push('@all');
keys.push('@here');
selectedMentionKeys.push('@channel');
selectedMentionKeys.push('@all');
selectedMentionKeys.push('@here');
}
if (this.state.customKeys.length > 0) {
keys = keys.concat(this.state.customKeys.split(','));
}
let describe: JSX.Element | string = '';
for (let i = 0; i < keys.length; i++) {
if (keys[i] !== '') {
describe += '"' + keys[i] + '", ';
}
}
if (describe.length > 0) {
describe = describe.substring(0, describe.length - 2);
} else {
describe = (
<FormattedMessage
id='user.settings.notifications.noWords'
defaultMessage='No words configured'
/>
);
if (this.state.customKeysWithNotification.length > 0) {
const customKeysWithNotificationStringArray = this.state.customKeysWithNotification.map((key) => key.value);
selectedMentionKeys.push(...customKeysWithNotificationStringArray);
}
const collapsedDescription = selectedMentionKeys.filter((key) => key.trim().length !== 0).map((key) => `"${key}"`).join(', ');
return (
<SettingItem
active={active}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger Notifications'})}
section='keysWithNotification'
active={isSectionExpanded}
areAllSectionsInactive={this.props.activeSection === ''}
title={localizeMessage('user.settings.notifications.wordsTrigger', 'Words That Trigger Mentions')}
describe={describe}
section={'keys'}
describe={collapsedDescription}
updateSection={this.handleUpdateSection}
max={max}
max={expandedSection}
/>);
};
@ -830,7 +885,10 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
inputs.push(
<fieldset key='userNotificationLevelOption'>
<legend className='form-legend hidden-label'>
{localizeMessage('user.settings.notifications.comments', 'Reply notifications')}
<FormattedMessage
id='user.settings.notifications.comments'
defaultMessage='Reply notifications'
/>
</legend>
<div className='radio'>
<label>
@ -893,7 +951,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
max = (
<SettingItemMax
title={localizeMessage('user.settings.notifications.comments', 'Reply notifications')}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply notifications'})}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@ -930,8 +988,8 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply notifications'})}
active={active}
title={localizeMessage('user.settings.notifications.comments', 'Reply notifications')}
describe={describe}
section={'comments'}
updateSection={this.handleUpdateSection}
@ -942,59 +1000,54 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
};
createAutoResponderSection = () => {
if (this.props.enableAutoResponder) {
const describe = this.state.autoResponderActive ? (
<FormattedMessage
id='user.settings.notifications.autoResponderEnabled'
defaultMessage='Enabled'
/>
) : (
<FormattedMessage
id='user.settings.notifications.autoResponderDisabled'
defaultMessage='Disabled'
/>
);
const describe = this.state.autoResponderActive ? (
<FormattedMessage
id='user.settings.notifications.autoResponderEnabled'
defaultMessage='Enabled'
/>
) : (
<FormattedMessage
id='user.settings.notifications.autoResponderDisabled'
defaultMessage='Disabled'
/>
);
return (
<SettingItem
active={this.props.activeSection === 'auto-responder'}
areAllSectionsInactive={this.props.activeSection === ''}
title={
<FormattedMessage
id='user.settings.notifications.autoResponder'
defaultMessage='Automatic Direct Message Replies'
return (
<SettingItem
active={this.props.activeSection === 'auto-responder'}
areAllSectionsInactive={this.props.activeSection === ''}
title={
<FormattedMessage
id='user.settings.notifications.autoResponder'
defaultMessage='Automatic Direct Message Replies'
/>
}
describe={describe}
section={'auto-responder'}
updateSection={this.handleUpdateSection}
max={(
<div>
<ManageAutoResponder
autoResponderActive={this.state.autoResponderActive}
autoResponderMessage={this.state.autoResponderMessage || ''}
updateSection={this.handleUpdateSection}
setParentState={this.setStateValue}
submit={this.handleSubmit}
error={this.state.serverError}
saving={this.state.isSaving}
/>
}
describe={describe}
section={'auto-responder'}
updateSection={this.handleUpdateSection}
max={(
<div>
<ManageAutoResponder
autoResponderActive={this.state.autoResponderActive}
autoResponderMessage={this.state.autoResponderMessage || ''}
updateSection={this.handleUpdateSection}
setParentState={this.setStateValue}
submit={this.handleSubmit}
error={this.state.serverError}
saving={this.state.isSaving}
/>
<div className='divider-dark'/>
</div>
)}
/>
);
}
return null;
<div className='divider-dark'/>
</div>
)}
/>
);
};
render() {
const autoResponderSection = this.createAutoResponderSection();
const commentsSection = this.createCommentsSection();
const keysSection = this.createKeysSection();
const pushNotificationSection = this.createPushNotificationSection();
const enableEmailProp = this.state.enableEmail === 'true';
const keywordsWithNotificationSection = this.createKeywordsWithNotificationSection();
const commentsSection = this.createCommentsSection();
const autoResponderSection = this.createAutoResponderSection();
return (
<div id='notificationSettings'>
@ -1064,7 +1117,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
<EmailNotificationSetting
activeSection={this.props.activeSection}
updateSection={this.handleUpdateSection}
enableEmail={enableEmailProp}
enableEmail={this.state.enableEmail === 'true'}
onSubmit={this.handleSubmit}
onCancel={this.handleCancel}
onChange={this.handleEmailRadio}
@ -1077,7 +1130,7 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
<div className='divider-light'/>
{pushNotificationSection}
<div className='divider-light'/>
{keysSection}
{keywordsWithNotificationSection}
<div className='divider-light'/>
{!this.props.isCollapsedThreadsEnabled && (
<>
@ -1085,7 +1138,9 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
<div className='divider-light'/>
</>
)}
{autoResponderSection}
{this.props.enableAutoResponder && (
autoResponderSection
)}
<div className='divider-dark'/>
</div>
</div>
@ -1093,3 +1148,19 @@ export default class NotificationsTab extends React.PureComponent<Props, State>
);
}
}
const customKeywordsWithNotificationStyles: ReactSelectStyles = {
indicatorSeparator: ((indicatorSeperatorStyles) => ({
...indicatorSeperatorStyles,
display: 'none',
})),
multiValueRemove: ((multiValueRemoveStyles) => ({
...multiValueRemoveStyles,
cursor: 'pointer',
':hover': {
backgroundColor: 'rgba(var(--center-channel-color-rgb), 0.16)',
},
})),
};
export default injectIntl(NotificationsTab);

View File

@ -5422,9 +5422,9 @@
"user.settings.notifications.header": "Notifications",
"user.settings.notifications.icon": "Notification Settings Icon",
"user.settings.notifications.info": "Desktop notifications are available on Edge, Firefox, Safari, Chrome and Mattermost Desktop Apps.",
"user.settings.notifications.mentionsInfo": "Mentions trigger when someone sends a message that includes your username (\"@{username}\") or any of the options selected above.",
"user.settings.notifications.keywordsWithNotification.extraInfo": "Notifications are triggered when someone sends a message that includes your username (\"@{username}\") or any of the options selected above.",
"user.settings.notifications.keywordsWithNotification.title": "Keywords that trigger Notifications",
"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",
@ -5432,9 +5432,9 @@
"user.settings.notifications.push_notification.status": "Trigger push notifications when",
"user.settings.notifications.push_threads": "When enabled, any reply to a thread you're following will send a mobile push notification.",
"user.settings.notifications.push_threads.allActivity": "Notify me about threads I'm following",
"user.settings.notifications.sensitiveCustomWords": "Other non case-sensitive words, press Tab or use commas to separate keywords:",
"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:",
"user.settings.notifications.soundConfig": "Please configure notification sounds in your browser settings",
"user.settings.notifications.sounds_info": "Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.",
"user.settings.notifications.threads": "When enabled, any reply to a thread you're following will send a desktop notification.",
@ -5442,7 +5442,6 @@
"user.settings.notifications.threads.desktop": "Thread reply notifications",
"user.settings.notifications.threads.push": "Thread reply notifications",
"user.settings.notifications.title": "Notification Settings",
"user.settings.notifications.wordsTrigger": "Words That Trigger Mentions",
"user.settings.profile.icon": "Profile Settings Icon",
"user.settings.push_notification.allActivity": "For all activity",
"user.settings.push_notification.allActivityAway": "For all activity when away or offline",

View File

@ -988,7 +988,7 @@ export function stopPeriodicStatusUpdates(): ActionFunc {
};
}
export function updateMe(user: UserProfile): ActionFunc {
export function updateMe(user: Partial<UserProfile>): ActionFunc<Partial<UserProfile>, ServerError> {
return async (dispatch: DispatchFunc) => {
dispatch({type: UserTypes.UPDATE_ME_REQUEST, data: null});