mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-30432]: Allow users to specify different desktop notification sounds per channel (#21671)
This commit is contained in:
@@ -245,7 +245,7 @@ function setCRTDesktopNotification(type) {
|
||||
cy.get('#desktopTitle').
|
||||
scrollIntoView().
|
||||
should('be.visible').
|
||||
and('contain', 'Send desktop notifications').click();
|
||||
and('contain', 'Desktop notifications').click();
|
||||
|
||||
// # Select mentions category for messages.
|
||||
cy.get('#channelNotificationMentions').scrollIntoView().check();
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('Desktop notifications', () => {
|
||||
|
||||
// # Set channel notifications to show on mention only
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.findByText('Send desktop notifications').click();
|
||||
cy.findByText('Desktop notifications').click();
|
||||
cy.findByRole('radio', {name: 'Only for mentions'}).click();
|
||||
cy.uiSaveAndClose();
|
||||
|
||||
|
||||
@@ -1334,6 +1334,14 @@ func (a *App) UpdateChannelMemberNotifyProps(c request.CTX, data map[string]stri
|
||||
filteredProps[model.DesktopNotifyProp] = desktop
|
||||
}
|
||||
|
||||
if desktop_sound, exists := data[model.DesktopSoundNotifyProp]; exists {
|
||||
filteredProps[model.DesktopSoundNotifyProp] = desktop_sound
|
||||
}
|
||||
|
||||
if desktop_notification_sound, exists := data["desktop_notification_sound"]; exists {
|
||||
filteredProps["desktop_notification_sound"] = desktop_notification_sound
|
||||
}
|
||||
|
||||
if desktop_threads, exists := data[model.DesktopThreadsNotifyProp]; exists {
|
||||
filteredProps[model.DesktopThreadsNotifyProp] = desktop_threads
|
||||
}
|
||||
|
||||
@@ -28,6 +28,22 @@ const NOTIFY_TEXT_MAX_LENGTH = 50;
|
||||
// windows notification length is based windows chrome which supports 128 characters and is the lowest length of windows browsers
|
||||
const WINDOWS_NOTIFY_TEXT_MAX_LENGTH = 120;
|
||||
|
||||
const getSoundFromChannelMemberAndUser = (member, user) => {
|
||||
if (member?.notify_props?.desktop_sound) {
|
||||
return member.notify_props.desktop_sound === 'on';
|
||||
}
|
||||
|
||||
return !user.notify_props || user.notify_props.desktop_sound === 'true';
|
||||
};
|
||||
|
||||
const getNotificationSoundFromChannelMemberAndUser = (member, user) => {
|
||||
if (member?.notify_props?.desktop_notification_sound) {
|
||||
return member.notify_props.desktop_notification_sound;
|
||||
}
|
||||
|
||||
return user.notify_props?.desktop_notification_sound ? user.notify_props.desktop_notification_sound : 'Bing';
|
||||
};
|
||||
|
||||
export function sendDesktopNotification(post, msgProps) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@@ -159,7 +175,7 @@ export function sendDesktopNotification(post, msgProps) {
|
||||
}
|
||||
|
||||
//Play a sound if explicitly set in settings
|
||||
const sound = !user.notify_props || user.notify_props.desktop_sound === 'true';
|
||||
const sound = getSoundFromChannelMemberAndUser(member, user);
|
||||
|
||||
// Notify if you're not looking in the right channel or when
|
||||
// the window itself is not active
|
||||
@@ -174,7 +190,7 @@ export function sendDesktopNotification(post, msgProps) {
|
||||
}
|
||||
notify = notify || !state.views.browser.focused;
|
||||
|
||||
const soundName = user.notify_props !== undefined && user.notify_props.desktop_notification_sound !== undefined ? user.notify_props.desktop_notification_sound : 'Bing';
|
||||
const soundName = getNotificationSoundFromChannelMemberAndUser(member, user);
|
||||
|
||||
if (notify) {
|
||||
const updatedState = getState();
|
||||
|
||||
@@ -97,10 +97,17 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
globalNotificationLevel="all"
|
||||
globalNotificationSound="Bing"
|
||||
isNotificationsSettingSameAsGlobal={true}
|
||||
memberDesktopNotificationSound="Bing"
|
||||
memberDesktopSound="on"
|
||||
memberNotificationLevel="all"
|
||||
memberThreadsNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="desktop"
|
||||
@@ -111,10 +118,12 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
memberNotificationLevel="default"
|
||||
memberThreadsNotificationLevel="default"
|
||||
isNotificationsSettingSameAsGlobal={true}
|
||||
memberNotificationLevel="all"
|
||||
memberThreadsNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="push"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {ComponentProps} from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ChannelNotificationsModal from 'components/channel_notifications_modal/channel_notifications_modal';
|
||||
@@ -22,6 +22,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
channelMember: {
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
desktop_sound: DesktopSound.ON,
|
||||
desktop_notification_sound: 'Bing',
|
||||
mark_unread: NotificationLevels.ALL,
|
||||
push: NotificationLevels.DEFAULT,
|
||||
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
|
||||
@@ -59,9 +61,11 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
expect(wrapper.state('desktopSound')).toEqual(DesktopSound.ON);
|
||||
expect(wrapper.state('desktopNotifySound')).toEqual('Bing');
|
||||
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.OFF);
|
||||
expect(wrapper.state('channelAutoFollowThreads')).toEqual(ChannelAutoFollowThreads.OFF);
|
||||
});
|
||||
@@ -186,7 +190,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
wrapper.instance().handleExit();
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(3);
|
||||
expect(wrapper.state('activeSection')).toEqual(NotificationSections.NONE);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
});
|
||||
|
||||
test('should match state on updateSection', () => {
|
||||
@@ -214,7 +218,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(baseProps.channelMember?.notify_props.desktop);
|
||||
});
|
||||
|
||||
test('should match state on handleSubmitDesktopNotifyLevel', () => {
|
||||
test('should match state on handleSubmitDesktopNotification', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
@@ -223,12 +227,12 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
instance.handleUpdateChannelNotifyProps = jest.fn();
|
||||
instance.updateSection = jest.fn();
|
||||
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.DEFAULT});
|
||||
instance.handleSubmitDesktopNotifyLevel();
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.MENTION});
|
||||
instance.handleSubmitDesktopNotification();
|
||||
expect(instance.handleUpdateChannelNotifyProps).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.ALL});
|
||||
instance.handleSubmitDesktopNotifyLevel();
|
||||
instance.handleSubmitDesktopNotification();
|
||||
expect(instance.updateSection).toHaveBeenCalledTimes(1);
|
||||
expect(instance.updateSection).toBeCalledWith('');
|
||||
});
|
||||
|
||||
@@ -6,9 +6,11 @@ import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import * as NotificationSounds from 'utils/notification_sounds';
|
||||
|
||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
|
||||
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
import NotificationSection from 'components/channel_notifications_modal/components/notification_section.jsx';
|
||||
|
||||
@@ -42,6 +44,8 @@ type State = {
|
||||
activeSection: string;
|
||||
serverError: string | null;
|
||||
desktopNotifyLevel: ChannelNotifyProps['desktop'];
|
||||
desktopSound: ChannelNotifyProps['desktop_sound'];
|
||||
desktopNotifySound: ChannelNotifyProps['desktop_notification_sound'];
|
||||
desktopThreadsNotifyLevel: UserNotifyProps['desktop_threads'];
|
||||
markUnreadNotifyLevel: ChannelNotifyProps['mark_unread'];
|
||||
pushNotifyLevel: ChannelNotifyProps['push'];
|
||||
@@ -50,6 +54,59 @@ type State = {
|
||||
channelAutoFollowThreads: ChannelNotifyProps['channel_auto_follow_threads'];
|
||||
};
|
||||
|
||||
export type DesktopNotificationProps = Pick<State, 'desktopNotifyLevel' | 'desktopNotifySound' | 'desktopSound' | 'desktopThreadsNotifyLevel'>
|
||||
export type PushNotificationProps = Pick<State, 'pushNotifyLevel' | 'pushThreadsNotifyLevel'>
|
||||
|
||||
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop) {
|
||||
if (currentUserNotifyProps.desktop === 'default') {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
return currentUserNotifyProps.desktop;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
const getDefaultDesktopSound = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_sound'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop_sound) {
|
||||
return currentUserNotifyProps.desktop_sound === 'true' ? DesktopSound.ON : DesktopSound.OFF;
|
||||
}
|
||||
return DesktopSound.ON;
|
||||
};
|
||||
|
||||
const getDefaultDesktopNotificationSound = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_notification_sound'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop_notification_sound) {
|
||||
return currentUserNotifyProps.desktop_notification_sound;
|
||||
}
|
||||
return 'Bing';
|
||||
};
|
||||
const getDefaultDesktopThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_threads'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop_threads) {
|
||||
return currentUserNotifyProps.desktop_threads;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
||||
if (currentUserNotifyProps?.push) {
|
||||
if (currentUserNotifyProps.push === 'default') {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
return currentUserNotifyProps.push;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
const getDefaultPushThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push_threads'], undefined> => {
|
||||
if (currentUserNotifyProps?.push_threads) {
|
||||
if (currentUserNotifyProps.push_threads === 'default') {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
return currentUserNotifyProps.push_threads;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
export default class ChannelNotificationsModal extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -77,9 +134,63 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
this.setState(this.getStateFromNotifyProps(currentUserNotifyProps, channelMemberNotifyProps));
|
||||
}
|
||||
|
||||
verifyNotificationsSettingSameAsGlobal({
|
||||
desktopNotifyLevel,
|
||||
desktopNotifySound,
|
||||
desktopSound,
|
||||
desktopThreadsNotifyLevel,
|
||||
}: DesktopNotificationProps) {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps) &&
|
||||
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
|
||||
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
|
||||
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
verifyPushNotificationsSettingSameAsGlobal({
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
}: PushNotificationProps) {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps) &&
|
||||
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
||||
|
||||
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps);
|
||||
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps);
|
||||
let pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps);
|
||||
|
||||
if (channelMemberNotifyProps?.desktop) {
|
||||
if (channelMemberNotifyProps.desktop !== 'default') {
|
||||
desktopNotifyLevelDefault = channelMemberNotifyProps.desktop;
|
||||
}
|
||||
}
|
||||
if (channelMemberNotifyProps?.push) {
|
||||
if (channelMemberNotifyProps.push !== 'default') {
|
||||
pushNotifyLevelDefault = channelMemberNotifyProps.push;
|
||||
}
|
||||
}
|
||||
if (channelMemberNotifyProps?.push_threads) {
|
||||
if (channelMemberNotifyProps.push_threads !== 'default') {
|
||||
pushThreadsNotifyLevelDefault = channelMemberNotifyProps.push_threads;
|
||||
}
|
||||
}
|
||||
|
||||
if (channelMemberNotifyProps?.mark_unread === NotificationLevels.MENTION || (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false')) {
|
||||
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
|
||||
}
|
||||
@@ -90,11 +201,13 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
}
|
||||
|
||||
return {
|
||||
desktopNotifyLevel: channelMemberNotifyProps?.desktop || NotificationLevels.DEFAULT,
|
||||
desktopThreadsNotifyLevel: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
|
||||
desktopNotifyLevel: desktopNotifyLevelDefault,
|
||||
desktopSound: channelMemberNotifyProps?.desktop_sound || getDefaultDesktopSound(currentUserNotifyProps),
|
||||
desktopNotifySound: channelMemberNotifyProps?.desktop_notification_sound || getDefaultDesktopNotificationSound(currentUserNotifyProps),
|
||||
desktopThreadsNotifyLevel: channelMemberNotifyProps?.desktop_threads || getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
|
||||
markUnreadNotifyLevel: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
|
||||
pushNotifyLevel: channelMemberNotifyProps?.push || NotificationLevels.DEFAULT,
|
||||
pushThreadsNotifyLevel: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
|
||||
pushNotifyLevel: pushNotifyLevelDefault,
|
||||
pushThreadsNotifyLevel: pushThreadsNotifyLevelDefault,
|
||||
ignoreChannelMentions,
|
||||
channelAutoFollowThreads: channelMemberNotifyProps?.channel_auto_follow_threads || ChannelAutoFollowThreads.OFF,
|
||||
};
|
||||
@@ -130,20 +243,45 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
}
|
||||
};
|
||||
handleResetDesktopNotification = () => {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
handleSubmitDesktopNotifyLevel = () => {
|
||||
const userDesktopNotificationDefaults = {
|
||||
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps),
|
||||
desktopSound: getDefaultDesktopSound(currentUserNotifyProps),
|
||||
desktopNotifySound: getDefaultDesktopNotificationSound(currentUserNotifyProps),
|
||||
desktopThreadsNotifyLevel: getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
|
||||
};
|
||||
|
||||
this.setState(userDesktopNotificationDefaults);
|
||||
};
|
||||
|
||||
handleResetPushNotification = () => {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
const userPushNotificationDefaults = {
|
||||
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps),
|
||||
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps),
|
||||
};
|
||||
|
||||
this.setState(userPushNotificationDefaults);
|
||||
};
|
||||
|
||||
handleSubmitDesktopNotification = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props as ChannelMemberNotifyProps;
|
||||
const {desktopNotifyLevel, desktopThreadsNotifyLevel} = this.state;
|
||||
const {desktopNotifyLevel, desktopNotifySound, desktopSound, desktopThreadsNotifyLevel} = this.state;
|
||||
|
||||
if (
|
||||
channelNotifyProps?.desktop === desktopNotifyLevel &&
|
||||
channelNotifyProps?.desktop_threads === desktopThreadsNotifyLevel
|
||||
channelNotifyProps?.desktop_threads === desktopThreadsNotifyLevel &&
|
||||
channelNotifyProps?.desktop_sound === desktopSound &&
|
||||
channelNotifyProps?.desktop_notification_sound === desktopNotifySound
|
||||
) {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {desktop: desktopNotifyLevel, desktop_threads: desktopThreadsNotifyLevel};
|
||||
const props = {desktop: desktopNotifyLevel, desktop_threads: desktopThreadsNotifyLevel, desktop_sound: desktopSound, desktop_notification_sound: desktopNotifySound};
|
||||
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
@@ -152,6 +290,15 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
|
||||
handleUpdateDesktopThreadsNotifyLevel = (desktopThreadsNotifyLevel: UserNotifyProps['desktop_threads']) => this.setState({desktopThreadsNotifyLevel});
|
||||
|
||||
handleUpdateDesktopSound = (desktopSound: ChannelNotifyProps['desktop_sound']) => this.setState({desktopSound});
|
||||
|
||||
handleUpdateDesktopNotifySound = (desktopNotifySound: ChannelNotifyProps['desktop_notification_sound']) => {
|
||||
if (desktopNotifySound) {
|
||||
NotificationSounds.tryNotificationSound(desktopNotifySound);
|
||||
}
|
||||
this.setState({desktopNotifySound});
|
||||
};
|
||||
|
||||
handleSubmitMarkUnreadLevel = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
|
||||
const {markUnreadNotifyLevel} = this.state;
|
||||
@@ -220,6 +367,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
activeSection,
|
||||
desktopNotifyLevel,
|
||||
desktopThreadsNotifyLevel,
|
||||
desktopSound,
|
||||
desktopNotifySound,
|
||||
markUnreadNotifyLevel,
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
@@ -235,6 +384,18 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
sendPushNotifications,
|
||||
} = this.props;
|
||||
|
||||
const isNotificationsSettingSameAsGlobal = this.verifyNotificationsSettingSameAsGlobal({
|
||||
desktopNotifyLevel,
|
||||
desktopNotifySound,
|
||||
desktopSound,
|
||||
desktopThreadsNotifyLevel,
|
||||
});
|
||||
|
||||
const isPushNotificationsSettingSameAsGlobal = this.verifyPushNotificationsSettingSameAsGlobal({
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
});
|
||||
|
||||
let serverErrorTag = null;
|
||||
if (serverError) {
|
||||
serverErrorTag = <div className='form-group has-error'><label className='control-label'>{serverError}</label></div>;
|
||||
@@ -295,10 +456,17 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
expand={activeSection === NotificationSections.DESKTOP}
|
||||
memberNotificationLevel={desktopNotifyLevel}
|
||||
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
|
||||
memberDesktopSound={desktopSound}
|
||||
memberDesktopNotificationSound={desktopNotifySound}
|
||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.desktop : NotificationLevels.ALL}
|
||||
globalNotificationSound={(currentUser.notify_props && currentUser.notify_props.desktop_notification_sound) ? currentUser.notify_props.desktop_notification_sound : 'Bing'}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdateDesktopNotifyLevel}
|
||||
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
|
||||
onSubmit={this.handleSubmitDesktopNotifyLevel}
|
||||
onChangeDesktopSound={this.handleUpdateDesktopSound}
|
||||
onChangeNotificationSound={this.handleUpdateDesktopNotifySound}
|
||||
onReset={this.handleResetDesktopNotification}
|
||||
onSubmit={this.handleSubmitDesktopNotification}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
/>
|
||||
@@ -310,7 +478,9 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
memberNotificationLevel={pushNotifyLevel}
|
||||
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
|
||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.push : NotificationLevels.ALL}
|
||||
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdatePushNotificationLevel}
|
||||
onReset={this.handleResetPushNotification}
|
||||
onChangeThreads={this.handleUpdatePushThreadsNotificationLevel}
|
||||
onSubmit={this.handleSubmitPushNotificationLevel}
|
||||
onUpdateSection={this.updateSection}
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="For all activity"
|
||||
defaultMessage="For all activity ({isDefault})"
|
||||
id="channel_notifications.allActivity"
|
||||
values={
|
||||
Object {
|
||||
"isDefault": <React.Fragment />,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -16,15 +21,25 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Only for mentions"
|
||||
defaultMessage="Only for mentions ({isDefault})"
|
||||
id="channel_notifications.onlyMentions"
|
||||
values={
|
||||
Object {
|
||||
"isDefault": <React.Fragment />,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on NONE 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Never"
|
||||
defaultMessage="Never ({isDefault})"
|
||||
id="channel_notifications.never"
|
||||
values={
|
||||
Object {
|
||||
"isDefault": <React.Fragment />,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,27 +8,14 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<div
|
||||
className="radio"
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationGlobalDefault"
|
||||
name="channelDesktopNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="default"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="default"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
@@ -38,12 +25,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
@@ -58,12 +46,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationMentions"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
@@ -76,12 +65,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
@@ -95,6 +85,63 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
section="desktop"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sound"
|
||||
id="channel_notifications.sound"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOn"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="on"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="On"
|
||||
id="channel_notifications.sound.on.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOff"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="off"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Off"
|
||||
id="channel_notifications.sound.off.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
|
||||
id="channel_notifications.sound_info"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
@@ -104,6 +151,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
@@ -176,6 +225,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="markUnread"
|
||||
/>
|
||||
}
|
||||
@@ -191,27 +242,14 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<div
|
||||
className="radio"
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationGlobalDefault"
|
||||
name="channelDesktopNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="default"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="default"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
@@ -221,12 +259,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
@@ -241,12 +280,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationMentions"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
@@ -259,12 +299,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelDesktopNotifications"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
@@ -287,6 +328,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Selecting an option other than \\"Default\\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome."
|
||||
defaultMessage="Selecting an option other than the \\"default\\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome."
|
||||
id="channel_notifications.override"
|
||||
/>
|
||||
</span>
|
||||
@@ -21,7 +21,7 @@ exports[`components/channel_notifications_modal/ExtraInfo should match snapshot,
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Selecting an option other than \\"Global default\\" will override the global notification settings for mobile push notifications in Settings. Push notifications must be enabled by the System Admin."
|
||||
defaultMessage="Selecting an option other than the \\"default\\" will override the global notification settings for mobile push notifications."
|
||||
id="channel_notifications.overridePush"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -24,8 +24,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="desktop"
|
||||
serverError=""
|
||||
@@ -47,8 +50,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="markUnread"
|
||||
serverError=""
|
||||
@@ -70,8 +76,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="push"
|
||||
serverError=""
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
<div
|
||||
className="SectionTitle__wrapper"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Desktop notifications"
|
||||
id="channel_notifications.desktopNotifications"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on MARK_UNREAD 1`] = `
|
||||
@@ -15,8 +19,12 @@ exports[`components/channel_notifications_modal/ExtraInfo should match snapshot,
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.push"
|
||||
/>
|
||||
<div
|
||||
className="SectionTitle__wrapper"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Mobile push notifications"
|
||||
id="channel_notifications.push"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -16,6 +16,13 @@ type Props = {
|
||||
isCollapsed?: boolean;
|
||||
}
|
||||
|
||||
const defaultOption = (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.defaultOption'
|
||||
defaultMessage='(default)'
|
||||
/>
|
||||
);
|
||||
|
||||
export default function Describe({section, isCollapsed, memberNotifyLevel, globalNotifyLevel, ignoreChannelMentions, channelAutoFollowThreads}: Props) {
|
||||
if (memberNotifyLevel === NotificationLevels.DEFAULT && globalNotifyLevel) {
|
||||
t('channel_notifications.levels.default');
|
||||
@@ -95,7 +102,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions'
|
||||
defaultMessage='Only for mentions ({isDefault})'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
@@ -105,7 +113,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.allActivity'
|
||||
defaultMessage='For all activity'
|
||||
defaultMessage='For all activity ({isDefault})'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
@@ -123,7 +132,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.never'
|
||||
defaultMessage='Never'
|
||||
defaultMessage='Never ({isDefault})'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('components/channel_notifications_modal/ExpandView', () => {
|
||||
onChangeThreads: jest.fn(),
|
||||
onCollapseSection: jest.fn(),
|
||||
onSubmit: jest.fn(),
|
||||
onReset: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||
|
||||
@@ -1,41 +1,74 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ChangeEvent} from 'react';
|
||||
import React, {ChangeEvent, useMemo, useRef} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import ReactSelect, {ValueType} from 'react-select';
|
||||
|
||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
|
||||
import {ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
|
||||
import {notificationSounds} from 'utils/notification_sounds';
|
||||
|
||||
import Describe from './describe';
|
||||
import ExtraInfo from './extra_info';
|
||||
import SectionTitle from './section_title';
|
||||
|
||||
type SelectedOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
ignoreChannelMentions?: string;
|
||||
channelAutoFollowThreads?: string;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onChangeThreads?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onChangeDesktopSound?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onChangeNotificationSound?: (selectedOption: ValueType<SelectedOption>) => void;
|
||||
onCollapseSection: (section: string) => void;
|
||||
onReset: () => void;
|
||||
onSubmit: (setting?: string) => void;
|
||||
isNotificationsSettingSameAsGlobal?: boolean;
|
||||
globalNotifyLevel?: string;
|
||||
globalNotificationSound?: ChannelNotifyProps['desktop_notification_sound'];
|
||||
memberNotifyLevel: string;
|
||||
memberThreadsNotifyLevel?: string;
|
||||
memberDesktopSound?: string;
|
||||
memberDesktopNotificationSound?: string;
|
||||
section: string;
|
||||
serverError?: string;
|
||||
}
|
||||
|
||||
const sounds = Array.from(notificationSounds.keys());
|
||||
|
||||
const makeDefaultOptionLabel = (option: string) => `${option} (default)`;
|
||||
|
||||
const makeReactSelectValue = (option: string, isDefault: boolean) => {
|
||||
return {value: option, label: isDefault ? makeDefaultOptionLabel(option) : option};
|
||||
};
|
||||
|
||||
export default function ExpandView({
|
||||
section,
|
||||
memberNotifyLevel,
|
||||
memberThreadsNotifyLevel,
|
||||
memberDesktopSound,
|
||||
memberDesktopNotificationSound,
|
||||
globalNotifyLevel,
|
||||
globalNotificationSound,
|
||||
isNotificationsSettingSameAsGlobal,
|
||||
onChange,
|
||||
onChangeThreads,
|
||||
onChangeDesktopSound,
|
||||
onChangeNotificationSound,
|
||||
onReset,
|
||||
onSubmit,
|
||||
serverError,
|
||||
onCollapseSection,
|
||||
@@ -44,32 +77,33 @@ export default function ExpandView({
|
||||
}: Props) {
|
||||
const isCRTEnabled = useSelector(isCollapsedThreadsEnabled);
|
||||
|
||||
const soundOptions = useMemo(() => sounds.map((sound) => {
|
||||
return {value: sound, label: sound === globalNotificationSound ? makeDefaultOptionLabel(sound) : sound};
|
||||
}), [globalNotificationSound]);
|
||||
|
||||
const dropdownSoundRef = useRef<ReactSelect>(null);
|
||||
|
||||
const inputs = [(
|
||||
<div key='channel-notification-level-radio'>
|
||||
{(section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) &&
|
||||
<fieldset>
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationGlobalDefault'
|
||||
name='channelDesktopNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.DEFAULT}
|
||||
checked={memberNotifyLevel === NotificationLevels.DEFAULT}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.DEFAULT}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{ section === NotificationSections.DESKTOP && <legend className='form-legend'>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sendDesktop'
|
||||
defaultMessage='Send desktop notifications'
|
||||
/>
|
||||
</legend>}
|
||||
{ section === NotificationSections.PUSH && <legend className='form-legend'>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sendMobilePush'
|
||||
defaultMessage='Send mobile push notifications'
|
||||
/>
|
||||
</legend>}
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationAllActivity'
|
||||
name='channelDesktopNotifications'
|
||||
name='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.ALL}
|
||||
checked={memberNotifyLevel === NotificationLevels.ALL}
|
||||
@@ -78,6 +112,7 @@ export default function ExpandView({
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.ALL}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -85,7 +120,7 @@ export default function ExpandView({
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationMentions'
|
||||
name='channelDesktopNotifications'
|
||||
name='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.MENTION}
|
||||
checked={memberNotifyLevel === NotificationLevels.MENTION}
|
||||
@@ -94,6 +129,7 @@ export default function ExpandView({
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.MENTION}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -101,7 +137,7 @@ export default function ExpandView({
|
||||
<label>
|
||||
<input
|
||||
id='channelNotificationNever'
|
||||
name='channelDesktopNotifications'
|
||||
name='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.NONE}
|
||||
checked={memberNotifyLevel === NotificationLevels.NONE}
|
||||
@@ -110,6 +146,7 @@ export default function ExpandView({
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.NONE}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -272,6 +309,72 @@ export default function ExpandView({
|
||||
</fieldset>
|
||||
</>
|
||||
}
|
||||
{(section === NotificationSections.DESKTOP) && memberNotifyLevel !== NotificationLevels.NONE &&
|
||||
|
||||
<>
|
||||
<hr/>
|
||||
<fieldset>
|
||||
<legend className='form-legend'>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sound'
|
||||
defaultMessage='Notification sound'
|
||||
/>
|
||||
</legend>
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelDesktopSoundOn'
|
||||
name='channelDesktopSound'
|
||||
type='radio'
|
||||
value={DesktopSound.ON}
|
||||
checked={memberDesktopSound === DesktopSound.ON}
|
||||
onChange={onChangeDesktopSound}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sound.on.title'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelDesktopSoundOff'
|
||||
name='channelDesktopSound'
|
||||
type='radio'
|
||||
value={DesktopSound.OFF}
|
||||
checked={memberDesktopSound === DesktopSound.OFF}
|
||||
onChange={onChangeDesktopSound}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sound.off.title'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{memberDesktopSound === DesktopSound.ON &&
|
||||
<div className='pt-2'>
|
||||
<ReactSelect
|
||||
className='react-select notification-sound-dropdown'
|
||||
classNamePrefix='react-select'
|
||||
id='channelSoundNotification'
|
||||
options={soundOptions}
|
||||
clearable={false}
|
||||
onChange={onChangeNotificationSound}
|
||||
value={makeReactSelectValue(memberDesktopNotificationSound ?? '', memberDesktopNotificationSound === globalNotificationSound)}
|
||||
isSearchable={false}
|
||||
ref={dropdownSoundRef}
|
||||
/>
|
||||
</div>}
|
||||
<div className='mt-5'>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sound_info'
|
||||
defaultMessage='Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.'
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</>
|
||||
}
|
||||
{isCRTEnabled &&
|
||||
section === NotificationSections.PUSH &&
|
||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||
@@ -314,7 +417,13 @@ export default function ExpandView({
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={<SectionTitle section={section}/>}
|
||||
title={
|
||||
<SectionTitle
|
||||
section={section}
|
||||
isExpanded={true}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
onClickResetButton={onReset}
|
||||
/>}
|
||||
inputs={inputs}
|
||||
submit={onSubmit}
|
||||
serverError={serverError}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function ExtraInfo({section}: Props) {
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.override'
|
||||
defaultMessage='Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
|
||||
defaultMessage='Selecting an option other than the "default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
@@ -26,7 +26,7 @@ export default function ExtraInfo({section}: Props) {
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.overridePush'
|
||||
defaultMessage='Selecting an option other than "Global default" will override the global notification settings for mobile push notifications in Settings. Push notifications must be enabled by the System Admin.'
|
||||
defaultMessage='Selecting an option other than the "default" will override the global notification settings for mobile push notifications.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,10 @@ export default class NotificationSection extends React.PureComponent {
|
||||
*/
|
||||
memberNotificationLevel: PropTypes.string.isRequired,
|
||||
|
||||
memberDesktopSound: PropTypes.string,
|
||||
|
||||
memberDesktopNotificationSound: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Member's desktop_threads notification level
|
||||
*/
|
||||
@@ -47,6 +51,11 @@ export default class NotificationSection extends React.PureComponent {
|
||||
*/
|
||||
globalNotificationLevel: PropTypes.string,
|
||||
|
||||
/**
|
||||
* User's global notification sound
|
||||
*/
|
||||
globalNotificationSound: PropTypes.string,
|
||||
|
||||
/**
|
||||
* onChange handles update of desktop notification level
|
||||
*/
|
||||
@@ -57,6 +66,14 @@ export default class NotificationSection extends React.PureComponent {
|
||||
*/
|
||||
onChangeThreads: PropTypes.func,
|
||||
|
||||
onChangeDesktopSound: PropTypes.func,
|
||||
|
||||
onChangeNotificationSound: PropTypes.func,
|
||||
|
||||
onReset: PropTypes.func,
|
||||
|
||||
isNotificationsSettingSameAsGlobal: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Submit function to save notification level
|
||||
*/
|
||||
@@ -83,6 +100,16 @@ export default class NotificationSection extends React.PureComponent {
|
||||
this.props.onChangeThreads(value);
|
||||
};
|
||||
|
||||
handleOnChangeDesktopSound = (e) => {
|
||||
this.props.onChangeDesktopSound(e.target.value);
|
||||
};
|
||||
|
||||
handleOnChangeNotificationSound = (selectedOption) => {
|
||||
if (selectedOption && 'value' in selectedOption) {
|
||||
this.props.onChangeNotificationSound(selectedOption.value);
|
||||
}
|
||||
};
|
||||
|
||||
handleExpandSection = () => {
|
||||
this.props.onUpdateSection(this.props.section);
|
||||
};
|
||||
@@ -95,11 +122,16 @@ export default class NotificationSection extends React.PureComponent {
|
||||
const {
|
||||
expand,
|
||||
globalNotificationLevel,
|
||||
globalNotificationSound,
|
||||
memberNotificationLevel,
|
||||
memberThreadsNotificationLevel,
|
||||
memberDesktopSound,
|
||||
memberDesktopNotificationSound,
|
||||
ignoreChannelMentions,
|
||||
isNotificationsSettingSameAsGlobal,
|
||||
channelAutoFollowThreads,
|
||||
onSubmit,
|
||||
onReset,
|
||||
section,
|
||||
serverError,
|
||||
} = this.props;
|
||||
@@ -110,11 +142,18 @@ export default class NotificationSection extends React.PureComponent {
|
||||
section={section}
|
||||
memberNotifyLevel={memberNotificationLevel}
|
||||
memberThreadsNotifyLevel={memberThreadsNotificationLevel}
|
||||
memberDesktopSound={memberDesktopSound}
|
||||
memberDesktopNotificationSound={memberDesktopNotificationSound}
|
||||
globalNotifyLevel={globalNotificationLevel}
|
||||
globalNotificationSound={globalNotificationSound}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
onChange={this.handleOnChange}
|
||||
onReset={onReset}
|
||||
onChangeThreads={this.handleOnChangeThreads}
|
||||
onChangeDesktopSound={this.handleOnChangeDesktopSound}
|
||||
onChangeNotificationSound={this.handleOnChangeNotificationSound}
|
||||
onSubmit={onSubmit}
|
||||
serverError={serverError}
|
||||
onCollapseSection={this.handleCollapseSection}
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('components/channel_notifications_modal/NotificationSection', () => {
|
||||
globalNotificationLevel: NotificationLevels.DEFAULT,
|
||||
onChange: () => {}, //eslint-disable-line no-empty-function
|
||||
onChangeThreads: () => {}, //eslint-disable-line no-empty-function
|
||||
onReset: () => {},
|
||||
onSubmit: () => {}, //eslint-disable-line no-empty-function
|
||||
onUpdateSection: () => {}, //eslint-disable-line no-empty-function
|
||||
serverError: '',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@import 'sass/utils/mixins';
|
||||
|
||||
.SectionTitle {
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__resetButton {
|
||||
@include button-style--none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@@ -6,24 +6,43 @@ import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {NotificationSections} from 'utils/constants';
|
||||
|
||||
import './section_title.scss';
|
||||
|
||||
type Props = {
|
||||
section: string;
|
||||
isExpanded?: boolean;
|
||||
isNotificationsSettingSameAsGlobal?: boolean;
|
||||
onClickResetButton?: () => void;
|
||||
}
|
||||
|
||||
export default function SectionTitle({section}: Props) {
|
||||
if (section === NotificationSections.DESKTOP) {
|
||||
export default function SectionTitle({section, isExpanded, isNotificationsSettingSameAsGlobal, onClickResetButton}: Props) {
|
||||
if (section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.sendDesktop'
|
||||
defaultMessage='Send desktop notifications'
|
||||
/>
|
||||
);
|
||||
} else if (section === NotificationSections.PUSH) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.push'
|
||||
defaultMessage='Send mobile push notifications'
|
||||
/>
|
||||
<div className='SectionTitle__wrapper'>
|
||||
{section === NotificationSections.DESKTOP &&
|
||||
<FormattedMessage
|
||||
id='channel_notifications.desktopNotifications'
|
||||
defaultMessage='Desktop notifications'
|
||||
/>}
|
||||
|
||||
{section === NotificationSections.PUSH &&
|
||||
<FormattedMessage
|
||||
id='channel_notifications.push'
|
||||
defaultMessage='Mobile push notifications'
|
||||
/>}
|
||||
{isExpanded && !isNotificationsSettingSameAsGlobal &&
|
||||
<button
|
||||
className='SectionTitle__resetButton color--link'
|
||||
onClick={onClickResetButton}
|
||||
>
|
||||
<i className='icon icon-refresh'/>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.resetToDefaults'
|
||||
defaultMessage='Reset to defaults'
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else if (section === NotificationSections.MARK_UNREAD) {
|
||||
return (
|
||||
|
||||
@@ -31,11 +31,17 @@ jest.mock('actions/global_actions', () => ({
|
||||
redirectUserToDefaultTeam: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('utils/utils', () => ({
|
||||
localizeMessage: () => {},
|
||||
applyTheme: jest.fn(),
|
||||
makeIsEligibleForClick: jest.fn(),
|
||||
}));
|
||||
jest.mock('utils/utils', () => {
|
||||
const original = jest.requireActual('utils/utils');
|
||||
|
||||
return {
|
||||
...original,
|
||||
localizeMessage: () => {},
|
||||
applyTheme: jest.fn(),
|
||||
makeIsEligibleForClick: jest.fn(),
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/actions/general', () => ({
|
||||
setUrl: () => {},
|
||||
|
||||
@@ -2995,6 +2995,8 @@
|
||||
"channel_notifications.channelAutoFollowThreads.help": "When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.",
|
||||
"channel_notifications.channelAutoFollowThreads.off.title": "Off",
|
||||
"channel_notifications.channelAutoFollowThreads.on.title": "On",
|
||||
"channel_notifications.defaultOption": "(default)",
|
||||
"channel_notifications.desktopNotifications": "Desktop notifications",
|
||||
"channel_notifications.globalDefault": "Global default ({notifyLevel})",
|
||||
"channel_notifications.ignoreChannelMentions": "Ignore mentions for @channel, @here and @all",
|
||||
"channel_notifications.ignoreChannelMentions.help": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel.",
|
||||
@@ -3009,13 +3011,19 @@
|
||||
"channel_notifications.muteChannel.on.title": "On",
|
||||
"channel_notifications.muteChannel.on.title.collapse": "Mute is enabled. Desktop, email and push notifications will not be sent for this channel.",
|
||||
"channel_notifications.muteChannel.settings": "Mute channel",
|
||||
"channel_notifications.never": "Never",
|
||||
"channel_notifications.onlyMentions": "Only for mentions",
|
||||
"channel_notifications.override": "Selecting an option other than \"Default\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.",
|
||||
"channel_notifications.overridePush": "Selecting an option other than \"Global default\" will override the global notification settings for mobile push notifications in settings. Push notifications must be enabled by the System Admin.",
|
||||
"channel_notifications.never": "Never {isDefault}",
|
||||
"channel_notifications.onlyMentions": "Only for mentions {isDefault}",
|
||||
"channel_notifications.override": "Selecting an option other than the \"default\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.",
|
||||
"channel_notifications.overridePush": "Selecting an option other than the \"default\" will override the global notification settings for mobile push notifications. ",
|
||||
"channel_notifications.preferences": "Notification Preferences for ",
|
||||
"channel_notifications.push": "Send mobile push notifications",
|
||||
"channel_notifications.sendDesktop": "Send desktop notifications",
|
||||
"channel_notifications.push": "Mobile push notifications",
|
||||
"channel_notifications.resetToDefaults": "Reset to defaults",
|
||||
"channel_notifications.sendDesktop": "Send Desktop notifications",
|
||||
"channel_notifications.sendMobilePush": "Send mobile push notifications",
|
||||
"channel_notifications.sound": "Notification sound",
|
||||
"channel_notifications.sound_info": "Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.",
|
||||
"channel_notifications.sound.off.title": "Off",
|
||||
"channel_notifications.sound.on.title": "On",
|
||||
"channel_select.placeholder": "--- Select a channel ---",
|
||||
"channel_switch_modal.deactivated": "Deactivated",
|
||||
"channel_toggle_button.private": "Private",
|
||||
|
||||
@@ -449,6 +449,7 @@ class TestHelper {
|
||||
fakeChannelNotifyProps = (override: Partial<ChannelNotifyProps>): ChannelNotifyProps => {
|
||||
return {
|
||||
desktop: 'default',
|
||||
desktop_sound: 'off',
|
||||
email: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
|
||||
@@ -998,6 +998,11 @@ export const NotificationLevels = {
|
||||
NONE: 'none',
|
||||
} as const;
|
||||
|
||||
export const DesktopSound = {
|
||||
ON: 'on',
|
||||
OFF: 'off',
|
||||
} as const;
|
||||
|
||||
export const IgnoreChannelMentions = {
|
||||
ON: 'on',
|
||||
OFF: 'off',
|
||||
|
||||
@@ -21,6 +21,8 @@ export type ChannelStats = {
|
||||
|
||||
export type ChannelNotifyProps = {
|
||||
desktop: 'default' | 'all' | 'mention' | 'none';
|
||||
desktop_sound: 'on' | 'off';
|
||||
desktop_notification_sound?: 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
|
||||
email: 'default' | 'all' | 'mention' | 'none';
|
||||
mark_unread: 'all' | 'mention';
|
||||
push: 'default' | 'all' | 'mention' | 'none';
|
||||
|
||||
Reference in New Issue
Block a user