mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-53478 : Improve design of keyword that trigger notification (#23934)
This commit is contained in:
parent
5e7d1c6f9e
commit
060a63a3ed
@ -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: [
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
}
|
||||
`;
|
@ -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);
|
||||
|
@ -0,0 +1,5 @@
|
||||
.customKeywordsWithNotificationSubsection {
|
||||
& .multiInput {
|
||||
margin-block-start: 10px;
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user