mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-40384]: Channel notification settings modal UX revamp (#24984)
* Move notification preferences modal to new UI * fix lint issue * fix i18n * fix unit test * fix type issue and lint errors * fix test case * move common components to widget modals dir * fix css issue * feedback changes * fix lint and i18n issues * more feedback changes * fix issue with mobile notification ui * fix test * clean up * remove name * fix test --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
ec88ab4ee9
commit
448d442a0b
@ -211,7 +211,7 @@ describe('Channel Info RHS', () => {
|
||||
cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
|
||||
|
||||
// * Ensures the modal is there
|
||||
cy.get('.settings-modal').should('be.visible');
|
||||
cy.get('.channel-notifications-settings-modal').should('be.visible');
|
||||
});
|
||||
it('should be able to view files and come back', () => {
|
||||
// # Go to test channel
|
||||
@ -373,7 +373,7 @@ describe('Channel Info RHS', () => {
|
||||
cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
|
||||
|
||||
// * Ensures the modal is there
|
||||
cy.get('.settings-modal').should('be.visible');
|
||||
cy.get('.channel-notifications-settings-modal').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -52,11 +52,46 @@ describe('CRT Desktop notifications', () => {
|
||||
// # Visit channel
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.get('[data-testid="muteChannel"]').click().then(() => {
|
||||
cy.get('.AlertBanner--app').should('be.visible');
|
||||
});
|
||||
cy.get('.channel-notifications-settings-modal__save-btn').should('be.visible').click();
|
||||
|
||||
// Setup notification spy
|
||||
spyNotificationAs('notifySpy', 'granted');
|
||||
|
||||
// # Set users notification settings
|
||||
setCRTDesktopNotification('ALL');
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
|
||||
// # click on Mute Channel to Unmute Channel
|
||||
cy.get('[data-testid="muteChannel"]').click();
|
||||
|
||||
// # Click "Desktop Notifications"
|
||||
cy.findByText('Desktop Notifications').should('be.visible');
|
||||
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-all').should('be.visible').click();
|
||||
cy.get('.channel-notifications-settings-modal__body').get('#desktopNotification-all').should('be.checked');
|
||||
|
||||
cy.get('#desktopNotification-mention').should('be.visible').click().then(() => {
|
||||
cy.get('[data-testid="desktopReplyThreads"]').should('be.checked');
|
||||
cy.get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
||||
cy.get('[data-testid="desktopReplyThreads"]').should('not.be.checked');
|
||||
});
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.checked');
|
||||
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-none').should('be.visible').click();
|
||||
cy.get('.channel-notifications-settings-modal__body').get('#desktopNotification-none').should('be.checked');
|
||||
|
||||
// # click on Save button
|
||||
cy.get('.channel-notifications-settings-modal__save-btn').should('be.visible').click();
|
||||
|
||||
// # Set users notification settings
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-none').should('be.checked');
|
||||
cy.get('.channel-notifications-settings-modal__body').get('#desktopNotification-all').scrollIntoView().should('be.visible').click();
|
||||
|
||||
cy.get('.channel-notifications-settings-modal__save-btn').should('be.visible').click();
|
||||
|
||||
// # Post a root message as other user
|
||||
cy.postMessageAs({sender, message: 'This is a not followed root message', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
|
||||
@ -99,14 +134,41 @@ describe('CRT Desktop notifications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4417_2 Trigger notifications only on mention replies when channel setting is unchecked', () => {
|
||||
it('MM-T4417_2 Click on sameMobileSettingsDesktop and check if additional settings still appears', () => {
|
||||
cy.visit(testChannelUrl);
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click().then(() => {
|
||||
cy.get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
||||
});
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
||||
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click().should('be.checked').then(() => {
|
||||
cy.findByText('Notify me about…').should('not.be.visible');
|
||||
});
|
||||
|
||||
// check the box to see if the additional settings appears
|
||||
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click();
|
||||
cy.get('.mm-modal-generic-section-item__title').should('be.visible').and('contain', 'Notify me about');
|
||||
|
||||
cy.get('#MobileNotification-all').should('be.visible').click();
|
||||
cy.get('#MobileNotification-mention').should('be.visible').click().then(() => {
|
||||
cy.get('[data-testid="mobileReplyThreads"]').should('be.visible').click();
|
||||
});
|
||||
cy.get('#MobileNotification-none').should('be.visible').click();
|
||||
|
||||
cy.get('[data-testid="autoFollowThreads"]').should('be.visible').click();
|
||||
|
||||
// # click on Save button
|
||||
cy.get('.channel-notifications-settings-modal__save-btn').should('be.visible').click();
|
||||
});
|
||||
|
||||
it('MM-T4417_3 Trigger notifications only on mention replies when channel setting is unchecked', () => {
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// Setup notification spy
|
||||
spyNotificationAs('notifySpy', 'granted');
|
||||
|
||||
// # Set users notification settings
|
||||
setCRTDesktopNotification('MENTION');
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click();
|
||||
cy.get('.channel-notifications-settings-modal__save-btn').should('be.visible').click();
|
||||
|
||||
// # Post a root message as other user
|
||||
cy.postMessageAs({sender, message: 'This is a not followed root message', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
|
||||
@ -232,32 +294,3 @@ describe('CRT Desktop notifications', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setCRTDesktopNotification(type) {
|
||||
if (['ALL', 'MENTION'].indexOf(type) === -1) {
|
||||
throw new Error(`${type} is invalid`);
|
||||
}
|
||||
|
||||
// # Open settings modal
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
|
||||
// # Click "Desktop Notifications"
|
||||
cy.get('#desktopTitle').
|
||||
scrollIntoView().
|
||||
should('be.visible').
|
||||
and('contain', 'Desktop notifications').click();
|
||||
|
||||
// # Select mentions category for messages.
|
||||
cy.get('#channelNotificationMentions').scrollIntoView().check();
|
||||
|
||||
if (type === 'ALL') {
|
||||
// # Check notify for all replies.
|
||||
cy.get('#desktopThreadsNotificationAllActivity').scrollIntoView().check().should('be.checked');
|
||||
} else if (type === 'MENTION') {
|
||||
// # Check notify only for mentions.
|
||||
cy.get('#desktopThreadsNotificationAllActivity').scrollIntoView().uncheck().should('not.be.checked');
|
||||
}
|
||||
|
||||
// # Click "Save" and close the modal
|
||||
cy.uiSaveAndClose();
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ describe('Desktop notifications', () => {
|
||||
|
||||
// # Set channel notifications to show on mention only
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
cy.findByText('Desktop notifications').click();
|
||||
cy.findByRole('radio', {name: 'Only for mentions'}).click();
|
||||
cy.uiSaveAndClose();
|
||||
cy.findByText('Desktop Notifications').should('be.visible');
|
||||
cy.findByRole('radio', {name: 'Mentions, direct messages, and keywords only'}).click().should('be.checked');
|
||||
cy.uiSave();
|
||||
|
||||
// # Visit off-topic
|
||||
cy.uiClickSidebarItem('off-topic');
|
||||
|
@ -109,26 +109,15 @@ function addNumberOfUsersToChannel(num = 1) {
|
||||
}
|
||||
|
||||
function setIgnoreMentions(toSet) {
|
||||
let stringToSet = 'Off';
|
||||
if (toSet) {
|
||||
stringToSet = 'On';
|
||||
}
|
||||
|
||||
// # Open channel menu and click Notification Preferences
|
||||
cy.uiOpenChannelMenu('Notification Preferences');
|
||||
|
||||
// # Click on the edit button for ignore channel mentions
|
||||
cy.get('#ignoreChannelMentionsEdit').should('exist').click();
|
||||
// # find mute or ignore section
|
||||
cy.findByText('Mute or ignore').should('be.visible');
|
||||
|
||||
// # Click on selected option
|
||||
cy.get(`#ignoreChannelMentions${stringToSet}`).should('exist').click();
|
||||
// # find Ignore mentions checkbox, set value accordingly
|
||||
cy.findByRole('checkbox', {name: 'Ignore mentions for @channel, @here and @all'}).click().should(toSet ? 'be.checked' : 'not.be.checked');
|
||||
|
||||
// # Click on save to save the configuration
|
||||
cy.get('#saveSetting').should('exist').click();
|
||||
|
||||
// * Assert that the option selected is reflected
|
||||
cy.get('#ignoreChannelMentionsDesc').should('contain', stringToSet);
|
||||
|
||||
// # Click on the X button to close the modal
|
||||
cy.get('#channelNotificationModalLabel').siblings('.close').click();
|
||||
cy.uiSave();
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ export type AlertBannerProps = {
|
||||
id?: string;
|
||||
mode: ModeType;
|
||||
title?: React.ReactNode;
|
||||
customIcon?: React.ReactNode;
|
||||
message?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
@ -41,6 +42,7 @@ const AlertBanner = ({
|
||||
id,
|
||||
mode,
|
||||
title,
|
||||
customIcon,
|
||||
message,
|
||||
className,
|
||||
variant = 'sys',
|
||||
@ -57,6 +59,9 @@ const AlertBanner = ({
|
||||
const [tooltipId] = useState(`alert_banner_close_btn_tooltip_${Math.random()}`);
|
||||
|
||||
const bannerIcon = useCallback(() => {
|
||||
if (customIcon) {
|
||||
return customIcon;
|
||||
}
|
||||
if (mode === 'danger' || mode === 'warning') {
|
||||
return (
|
||||
<AlertOutlineIcon
|
||||
@ -72,7 +77,7 @@ const AlertBanner = ({
|
||||
<InformationOutlineIcon
|
||||
size={24}
|
||||
/>);
|
||||
}, [mode]);
|
||||
}, [mode, customIcon]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,139 @@
|
||||
.modal .channel-notifications-settings-modal {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin-top: -24px;
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
max-height: calc(100vh - 240px);
|
||||
flex-direction: column;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--elevation-6);
|
||||
}
|
||||
|
||||
&__ctr {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||
background: none;
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
min-height: 150px;
|
||||
flex-direction: column;
|
||||
padding: 32px;
|
||||
gap: 24px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 24px 32px;
|
||||
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__reset-btn {
|
||||
display: flex;
|
||||
padding: 5px 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
color: var(--button-bg);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
gap: 4px;
|
||||
line-height: 9.5px;
|
||||
place-items: center;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: rgba(var(--button-bg-rgb), 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&__server-error {
|
||||
flex-grow: 1;
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
&__save-btn {
|
||||
padding: 12px 20px 12px 20px;
|
||||
border: none;
|
||||
background: var(--button-bg);
|
||||
border-radius: 4px;
|
||||
color: var(--button-color);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 14px;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: rgba(var(--button-bg-rgb), 0.12);
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08)), var(--button-bg);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-sizing: border-box;
|
||||
padding: 10px 18px;
|
||||
border: 2px solid var(--sidebar-text-active-border);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel-btn {
|
||||
padding: 12px 20px 12px 20px;
|
||||
border: none;
|
||||
background: rgba(var(--button-bg-rgb), 0.08);
|
||||
border-radius: 4px;
|
||||
color: var(--button-bg);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 14px;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: rgba(var(--button-bg-rgb), 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&__divider {
|
||||
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
max-height: unset;
|
||||
flex-direction: column;
|
||||
border-radius: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 900px) and (min-width: 768px) {
|
||||
.modal-content {
|
||||
max-height: calc(100vh - 160px);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {screen, fireEvent, waitFor} from '@testing-library/react';
|
||||
import type {ComponentProps} from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type {ChannelMembership, ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
import type {ChannelMembership} from '@mattermost/types/channels';
|
||||
import type {UserNotifyProps} from '@mattermost/types/users';
|
||||
|
||||
import ChannelNotificationsModal from 'components/channel_notifications_modal/channel_notifications_modal';
|
||||
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
describe('components/channel_notifications_modal/ChannelNotificationsModal', () => {
|
||||
@ -23,12 +24,9 @@ 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,
|
||||
channel_auto_follow_threads: ChannelAutoFollowThreads.OFF,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
push_threads: NotificationLevels.DEFAULT,
|
||||
},
|
||||
@ -42,359 +40,209 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
}),
|
||||
sendPushNotifications: true,
|
||||
actions: {
|
||||
updateChannelNotifyProps: jest.fn(),
|
||||
updateChannelNotifyProps: jest.fn().mockImplementation(() => Promise.resolve({data: true})),
|
||||
},
|
||||
collapsedReplyThreads: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
it('should not show other settings if channel is mute', async () => {
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
const muteChannel = screen.getByTestId('muteChannel');
|
||||
|
||||
fireEvent.click(muteChannel);
|
||||
expect(muteChannel).toBeChecked();
|
||||
const AlertBanner = screen.queryByText('This channel is muted');
|
||||
expect(AlertBanner).toBeVisible();
|
||||
|
||||
expect(screen.queryByText('Desktop Notifications')).toBeNull();
|
||||
|
||||
expect(screen.queryByText('Mobile Notifications')).toBeNull();
|
||||
expect(screen.queryByText('Follow all threads in this channel')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: baseProps.channelMember?.notify_props.desktop,
|
||||
ignore_channel_mentions: 'off',
|
||||
mark_unread: 'mention',
|
||||
push: 'all',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for GMs', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal
|
||||
{...{
|
||||
...baseProps,
|
||||
channel: TestHelper.getChannelMock({
|
||||
id: 'channel_id',
|
||||
display_name: 'channel_display_name',
|
||||
type: 'G',
|
||||
}),
|
||||
channelMember: {
|
||||
notify_props: {
|
||||
...baseProps.channelMember!.notify_props,
|
||||
desktop: NotificationLevels.MENTION,
|
||||
push: NotificationLevels.MENTION,
|
||||
},
|
||||
} as unknown as ChannelMembership,
|
||||
currentUser: TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.MENTION,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
} as UserNotifyProps,
|
||||
}),
|
||||
}}
|
||||
/>,
|
||||
test('should Ignore mentions for @channel, @here and @all', async () => {
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
const ignoreChannel = screen.getByTestId('ignoreMentions');
|
||||
fireEvent.click(ignoreChannel);
|
||||
expect(ignoreChannel).toBeChecked();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: 'all',
|
||||
ignore_channel_mentions: 'on',
|
||||
mark_unread:
|
||||
baseProps.channelMember?.notify_props.mark_unread,
|
||||
push: 'all',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should provide default notify props when missing', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal
|
||||
{...baseProps}
|
||||
channelMember={{notify_props: {}} as ChannelMembership}
|
||||
/>,
|
||||
);
|
||||
|
||||
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.ALL);
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.OFF);
|
||||
expect(wrapper.state('channelAutoFollowThreads')).toEqual(ChannelAutoFollowThreads.OFF);
|
||||
});
|
||||
|
||||
test('should provide correct default when currentUser channel notify props is true', () => {
|
||||
const currentUser = TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
channel: 'true',
|
||||
} as UserNotifyProps,
|
||||
});
|
||||
const props = {...baseProps, currentUser};
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.OFF);
|
||||
});
|
||||
|
||||
test('should provide correct default when currentUser channel notify props is false', () => {
|
||||
const currentUser = TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
channel: 'false',
|
||||
} as UserNotifyProps,
|
||||
});
|
||||
const props = {...baseProps, currentUser};
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.ON);
|
||||
});
|
||||
|
||||
test('should provide correct value for ignoreChannelMentions when channelMember channel-wide mentions are off and false on the currentUser', () => {
|
||||
const currentUser = TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
channel: 'false',
|
||||
} as UserNotifyProps,
|
||||
});
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
ignore_channel_mentions: IgnoreChannelMentions.OFF,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember, currentUser};
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.OFF);
|
||||
});
|
||||
|
||||
test('should provide correct value for ignoreChannelMentions when channelMember channel-wide mentions are on but false on currentUser', () => {
|
||||
const currentUser = TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
channel: 'true',
|
||||
} as UserNotifyProps,
|
||||
});
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
ignore_channel_mentions: IgnoreChannelMentions.ON,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember, currentUser};
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.ON);
|
||||
});
|
||||
|
||||
test('should provide correct value for ignoreChannelMentions when channel is muted', () => {
|
||||
const currentUser = TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.ALL,
|
||||
channel: 'true',
|
||||
} as UserNotifyProps,
|
||||
});
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
mark_unread: NotificationLevels.MENTION,
|
||||
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember, currentUser};
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.ON);
|
||||
});
|
||||
|
||||
test('should call onExited and match state on handleOnHide', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
test('should check the options in the desktop notifications', async () => {
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({activeSection: NotificationSections.DESKTOP, desktopNotifyLevel: NotificationLevels.NONE});
|
||||
wrapper.instance().handleExit();
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.state('activeSection')).toEqual(NotificationSections.NONE);
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
expect(screen.queryByText('Desktop Notifications')).toBeVisible();
|
||||
|
||||
wrapper.setState({activeSection: NotificationSections.MARK_UNREAD, markUnreadNotifyLevel: NotificationLevels.MENTION});
|
||||
wrapper.instance().handleExit();
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(2);
|
||||
expect(wrapper.state('activeSection')).toEqual(NotificationSections.NONE);
|
||||
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
const AlllabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'desktopNotification-all',
|
||||
);
|
||||
fireEvent.click(AlllabelRadio);
|
||||
expect(AlllabelRadio.checked).toEqual(true);
|
||||
|
||||
wrapper.setState({activeSection: NotificationSections.PUSH, pushNotifyLevel: NotificationLevels.NONE});
|
||||
wrapper.instance().handleExit();
|
||||
expect(baseProps.onExited).toHaveBeenCalledTimes(3);
|
||||
expect(wrapper.state('activeSection')).toEqual(NotificationSections.NONE);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
const MentionslabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'desktopNotification-mention',
|
||||
);
|
||||
fireEvent.click(MentionslabelRadio);
|
||||
expect(MentionslabelRadio.checked).toEqual(true);
|
||||
|
||||
const NothinglabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'desktopNotification-none',
|
||||
);
|
||||
fireEvent.click(NothinglabelRadio);
|
||||
expect(NothinglabelRadio.checked).toEqual(true);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: 'none',
|
||||
ignore_channel_mentions: 'off',
|
||||
mark_unread: 'all',
|
||||
push: 'all',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state on updateSection', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
test('should save the options exactly same as Desktop for mobile if use same as desktop checkbox is checked', async () => {
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({activeSection: NotificationSections.NONE});
|
||||
wrapper.instance().updateSection(NotificationSections.DESKTOP);
|
||||
expect(wrapper.state('activeSection')).toEqual(NotificationSections.DESKTOP);
|
||||
expect(screen.queryByText('Desktop Notifications')).toBeVisible();
|
||||
|
||||
const sameAsDesktop: HTMLInputElement = screen.getByTestId(
|
||||
'sameMobileSettingsDesktop',
|
||||
);
|
||||
fireEvent.click(sameAsDesktop);
|
||||
expect(sameAsDesktop.checked).toEqual(true);
|
||||
|
||||
expect(screen.queryByText('All new messages')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: 'all',
|
||||
ignore_channel_mentions: 'off',
|
||||
mark_unread: 'all',
|
||||
push: 'all',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should reset state when collapsing a section', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
test('should check the options in the mobile notifications', async () => {
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().updateSection(NotificationSections.DESKTOP);
|
||||
wrapper.instance().handleUpdateDesktopNotifyLevel(NotificationLevels.NONE);
|
||||
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.NONE);
|
||||
|
||||
wrapper.instance().updateSection(NotificationSections.NONE);
|
||||
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(baseProps.channelMember?.notify_props.desktop);
|
||||
});
|
||||
|
||||
test('should match state on handleSubmitDesktopNotification', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
const AlllabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'MobileNotification-all',
|
||||
);
|
||||
fireEvent.click(AlllabelRadio);
|
||||
expect(AlllabelRadio.checked).toEqual(true);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.handleUpdateChannelNotifyProps = jest.fn();
|
||||
instance.updateSection = jest.fn();
|
||||
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.MENTION});
|
||||
instance.handleSubmitDesktopNotification();
|
||||
expect(instance.handleUpdateChannelNotifyProps).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.ALL});
|
||||
instance.handleSubmitDesktopNotification();
|
||||
expect(instance.updateSection).toHaveBeenCalledTimes(1);
|
||||
expect(instance.updateSection).toBeCalledWith('');
|
||||
});
|
||||
|
||||
test('should match state on handleUpdateDesktopNotifyLevel', () => {
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
const MentionslabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'MobileNotification-mention',
|
||||
);
|
||||
fireEvent.click(MentionslabelRadio);
|
||||
expect(MentionslabelRadio.checked).toEqual(true);
|
||||
|
||||
wrapper.setState({desktopNotifyLevel: NotificationLevels.ALL});
|
||||
wrapper.instance().handleUpdateDesktopNotifyLevel(NotificationLevels.MENTION);
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.MENTION);
|
||||
});
|
||||
|
||||
test('should match state on handleSubmitMarkUnreadLevel', () => {
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.NONE,
|
||||
mark_unread: NotificationLevels.ALL,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember};
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
const NothinglabelRadio: HTMLInputElement = screen.getByTestId(
|
||||
'MobileNotification-none',
|
||||
);
|
||||
fireEvent.click(NothinglabelRadio);
|
||||
expect(NothinglabelRadio.checked).toEqual(true);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.handleUpdateChannelNotifyProps = jest.fn();
|
||||
instance.updateSection = jest.fn();
|
||||
|
||||
wrapper.setState({markUnreadNotifyLevel: NotificationLevels.MENTION});
|
||||
instance.handleSubmitMarkUnreadLevel();
|
||||
expect(instance.handleUpdateChannelNotifyProps).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.setState({markUnreadNotifyLevel: NotificationLevels.ALL});
|
||||
instance.handleSubmitMarkUnreadLevel();
|
||||
expect(instance.updateSection).toHaveBeenCalledTimes(1);
|
||||
expect(instance.updateSection).toBeCalledWith('');
|
||||
});
|
||||
|
||||
test('should match state on handleUpdateMarkUnreadLevel', () => {
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.NONE,
|
||||
mark_unread: NotificationLevels.ALL,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember};
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: 'all',
|
||||
ignore_channel_mentions: 'off',
|
||||
mark_unread: 'all',
|
||||
push: 'none',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
wrapper.setState({markUnreadNotifyLevel: NotificationLevels.ALL});
|
||||
wrapper.instance().handleUpdateMarkUnreadLevel(NotificationLevels.MENTION);
|
||||
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.MENTION);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state on handleSubmitPushNotificationLevel', () => {
|
||||
const channelMember = {
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.NONE,
|
||||
mark_unread: NotificationLevels.MENTION,
|
||||
push: NotificationLevels.ALL,
|
||||
push_threads: NotificationLevels.ALL,
|
||||
},
|
||||
} as unknown as ChannelMembership;
|
||||
const props = {...baseProps, channelMember};
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.handleUpdateChannelNotifyProps = jest.fn();
|
||||
instance.updateSection = jest.fn();
|
||||
|
||||
wrapper.setState({pushNotifyLevel: NotificationLevels.DEFAULT});
|
||||
instance.handleSubmitPushNotificationLevel();
|
||||
expect(instance.handleUpdateChannelNotifyProps).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.setState({pushNotifyLevel: NotificationLevels.ALL});
|
||||
instance.handleSubmitPushNotificationLevel();
|
||||
expect(instance.updateSection).toHaveBeenCalledTimes(1);
|
||||
expect(instance.updateSection).toBeCalledWith('');
|
||||
});
|
||||
|
||||
test('should match state on handleUpdatePushNotificationLevel', () => {
|
||||
const channelMember = TestHelper.getChannelMembershipMock({
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.NONE,
|
||||
mark_unread: NotificationLevels.MENTION,
|
||||
push: NotificationLevels.ALL,
|
||||
},
|
||||
});
|
||||
const props = {...baseProps, channelMember};
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.setState({pushNotifyLevel: NotificationLevels.ALL});
|
||||
wrapper.instance().handleUpdatePushNotificationLevel(NotificationLevels.MENTION);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.MENTION);
|
||||
});
|
||||
|
||||
test('should match state on resetStateFromNotifyProps', () => {
|
||||
const channelMemberNotifyProps: Partial<ChannelNotifyProps> = {
|
||||
desktop: NotificationLevels.NONE,
|
||||
mark_unread: NotificationLevels.MENTION,
|
||||
push: NotificationLevels.ALL,
|
||||
it('should show auto follow, desktop threads and mobile threads settings if collapsed reply threads is enabled', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
collapsedReplyThreads: true,
|
||||
};
|
||||
const currentUserNotifyProps = {
|
||||
channel: 'false',
|
||||
} as UserNotifyProps;
|
||||
const wrapper = shallow<ChannelNotificationsModal>(
|
||||
<ChannelNotificationsModal {...baseProps}/>,
|
||||
const wrapper = renderWithContext(
|
||||
<ChannelNotificationsModal {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().resetStateFromNotifyProps(currentUserNotifyProps, channelMemberNotifyProps);
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.NONE);
|
||||
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.MENTION);
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.ON);
|
||||
expect(wrapper.state('channelAutoFollowThreads')).toEqual(ChannelAutoFollowThreads.OFF);
|
||||
expect(screen.queryByText('Follow all threads in this channel')).toBeVisible();
|
||||
|
||||
wrapper.instance().resetStateFromNotifyProps(currentUserNotifyProps, {...channelMemberNotifyProps, desktop: NotificationLevels.ALL});
|
||||
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
|
||||
wrapper.instance().resetStateFromNotifyProps(currentUserNotifyProps, {...channelMemberNotifyProps, mark_unread: NotificationLevels.ALL});
|
||||
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.ALL);
|
||||
|
||||
wrapper.instance().resetStateFromNotifyProps(currentUserNotifyProps, {...channelMemberNotifyProps, push: NotificationLevels.NONE});
|
||||
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.NONE);
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
'channel_id',
|
||||
{
|
||||
desktop: baseProps.channelMember?.notify_props.desktop,
|
||||
ignore_channel_mentions: 'off',
|
||||
mark_unread: 'all',
|
||||
channel_auto_follow_threads: 'off',
|
||||
push: 'all',
|
||||
push_threads: 'default',
|
||||
desktop_threads: 'all',
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -1,23 +1,29 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import React from 'react';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {BellOffOutlineIcon, RefreshIcon} from '@mattermost/compass-icons/components';
|
||||
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
import AlertBanner from 'components/alert_banner';
|
||||
import CheckboxSettingItem from 'components/widgets/modals/components/checkbox_setting_item';
|
||||
import ModalHeader from 'components/widgets/modals/components/modal_header';
|
||||
import ModalSection from 'components/widgets/modals/components/modal_section';
|
||||
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
|
||||
|
||||
import NotificationSection from 'components/channel_notifications_modal/components/notification_section.jsx';
|
||||
import {IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
|
||||
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import * as NotificationSounds from 'utils/notification_sounds';
|
||||
import type {ChannelMemberNotifyProps} from './utils';
|
||||
import utils from './utils';
|
||||
|
||||
import type {PropsFromRedux} from './index';
|
||||
|
||||
import './channel_notifications_modal.scss';
|
||||
|
||||
type Props = PropsFromRedux & {
|
||||
|
||||
/**
|
||||
@ -36,508 +42,296 @@ type Props = PropsFromRedux & {
|
||||
currentUser: UserProfile;
|
||||
};
|
||||
|
||||
export type ChannelMemberNotifyProps = Partial<ChannelNotifyProps> & Pick<UserNotifyProps, 'desktop_threads' | 'push_threads'>
|
||||
|
||||
type State = {
|
||||
show: boolean;
|
||||
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'];
|
||||
pushThreadsNotifyLevel: UserNotifyProps['push_threads'];
|
||||
ignoreChannelMentions: ChannelNotifyProps['ignore_channel_mentions'];
|
||||
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, isGM: boolean): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop) {
|
||||
if (currentUserNotifyProps.desktop === NotificationLevels.DEFAULT) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||
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, isGM: boolean): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
||||
if (currentUserNotifyProps?.push) {
|
||||
if (currentUserNotifyProps.push === NotificationLevels.DEFAULT) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
return currentUserNotifyProps.push;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
const getDefaultPushThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyProps, isGM: boolean): Exclude<ChannelMemberNotifyProps['push_threads'], undefined> => {
|
||||
if (currentUserNotifyProps?.push_threads) {
|
||||
if (currentUserNotifyProps.push_threads === 'default') {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (isGM && currentUserNotifyProps.push_threads === NotificationLevels.MENTION) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
return currentUserNotifyProps.push_threads;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
export default class ChannelNotificationsModal extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const channelNotifyProps = props.channelMember?.notify_props;
|
||||
|
||||
this.state = {
|
||||
show: true,
|
||||
activeSection: NotificationSections.NONE,
|
||||
serverError: null,
|
||||
...this.getStateFromNotifyProps(props.currentUser.notify_props, channelNotifyProps),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const prevChannelNotifyProps = prevProps.channelMember && prevProps.channelMember.notify_props;
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
|
||||
|
||||
if (!deepEqual(channelNotifyProps, prevChannelNotifyProps)) {
|
||||
this.resetStateFromNotifyProps(this.props.currentUser.notify_props, channelNotifyProps);
|
||||
}
|
||||
}
|
||||
|
||||
resetStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: Partial<ChannelNotifyProps>) {
|
||||
this.setState(this.getStateFromNotifyProps(currentUserNotifyProps, channelMemberNotifyProps));
|
||||
}
|
||||
|
||||
verifyNotificationsSettingSameAsGlobal({
|
||||
desktopNotifyLevel,
|
||||
desktopNotifySound,
|
||||
desktopSound,
|
||||
desktopThreadsNotifyLevel,
|
||||
}: DesktopNotificationProps) {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()) &&
|
||||
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
|
||||
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
|
||||
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isGM() {
|
||||
return this.props.channel.type === 'G';
|
||||
}
|
||||
|
||||
verifyPushNotificationsSettingSameAsGlobal({
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
}: PushNotificationProps) {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM()) &&
|
||||
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps, this.isGM())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
||||
|
||||
const desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM());
|
||||
const pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM());
|
||||
const pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps, this.isGM());
|
||||
|
||||
const channelDesktopNotifyProps = channelMemberNotifyProps?.desktop || NotificationLevels.DEFAULT;
|
||||
let desktopNotifyLevel = desktopNotifyLevelDefault;
|
||||
if (channelDesktopNotifyProps !== NotificationLevels.DEFAULT) {
|
||||
desktopNotifyLevel = channelDesktopNotifyProps;
|
||||
}
|
||||
|
||||
const channelPushNotifyProps = channelMemberNotifyProps?.push || NotificationLevels.DEFAULT;
|
||||
let pushNotifyLevel = pushNotifyLevelDefault;
|
||||
if (channelPushNotifyProps !== 'default') {
|
||||
pushNotifyLevel = channelPushNotifyProps;
|
||||
}
|
||||
|
||||
const channelPushThreadsNotifyProps = channelMemberNotifyProps?.push_threads || NotificationLevels.DEFAULT;
|
||||
let pushThreadsNotifyLevel = pushThreadsNotifyLevelDefault;
|
||||
if (channelPushThreadsNotifyProps !== 'default') {
|
||||
pushThreadsNotifyLevel = channelPushThreadsNotifyProps;
|
||||
}
|
||||
|
||||
if (channelMemberNotifyProps?.mark_unread === NotificationLevels.MENTION || (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false')) {
|
||||
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
|
||||
}
|
||||
|
||||
let ignoreChannelMentions = channelMemberNotifyProps?.ignore_channel_mentions;
|
||||
if (!ignoreChannelMentions || ignoreChannelMentions === IgnoreChannelMentions.DEFAULT) {
|
||||
ignoreChannelMentions = ignoreChannelMentionsDefault;
|
||||
}
|
||||
|
||||
return {
|
||||
desktopNotifyLevel,
|
||||
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,
|
||||
pushThreadsNotifyLevel,
|
||||
ignoreChannelMentions,
|
||||
channelAutoFollowThreads: channelMemberNotifyProps?.channel_auto_follow_threads || ChannelAutoFollowThreads.OFF,
|
||||
};
|
||||
}
|
||||
|
||||
handleHide = () => this.setState({show: false});
|
||||
|
||||
handleExit = () => {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
this.props.onExited();
|
||||
};
|
||||
|
||||
updateSection = (section = NotificationSections.NONE) => {
|
||||
this.setState({activeSection: section});
|
||||
|
||||
if (section === NotificationSections.NONE) {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
|
||||
this.resetStateFromNotifyProps(this.props.currentUser.notify_props, channelNotifyProps);
|
||||
}
|
||||
};
|
||||
|
||||
handleUpdateChannelNotifyProps = async (props: Partial<ChannelNotifyProps>) => {
|
||||
const {
|
||||
actions,
|
||||
channel,
|
||||
currentUser,
|
||||
} = this.props;
|
||||
|
||||
const {error} = await actions.updateChannelNotifyProps(currentUser.id, channel.id, props);
|
||||
if (error) {
|
||||
this.setState({serverError: error.message});
|
||||
} else {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
}
|
||||
};
|
||||
handleResetDesktopNotification = () => {
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
const userDesktopNotificationDefaults = {
|
||||
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()),
|
||||
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, this.isGM()),
|
||||
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps, this.isGM()),
|
||||
};
|
||||
|
||||
this.setState(userPushNotificationDefaults);
|
||||
};
|
||||
|
||||
handleSubmitDesktopNotification = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props as ChannelMemberNotifyProps;
|
||||
const {desktopNotifyLevel, desktopNotifySound, desktopSound, desktopThreadsNotifyLevel} = this.state;
|
||||
|
||||
if (
|
||||
channelNotifyProps?.desktop === desktopNotifyLevel &&
|
||||
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, desktop_sound: desktopSound, desktop_notification_sound: desktopNotifySound};
|
||||
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
|
||||
handleUpdateDesktopNotifyLevel = (desktopNotifyLevel: ChannelNotifyProps['desktop']) => this.setState({desktopNotifyLevel});
|
||||
|
||||
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;
|
||||
|
||||
if (channelNotifyProps?.mark_unread === markUnreadNotifyLevel) {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {mark_unread: markUnreadNotifyLevel};
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
|
||||
handleUpdateMarkUnreadLevel = (markUnreadNotifyLevel: ChannelNotifyProps['mark_unread']) => this.setState({markUnreadNotifyLevel});
|
||||
|
||||
handleSubmitPushNotificationLevel = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props as ChannelMemberNotifyProps;
|
||||
const {pushNotifyLevel, pushThreadsNotifyLevel} = this.state;
|
||||
|
||||
if (
|
||||
channelNotifyProps?.push === pushNotifyLevel &&
|
||||
channelNotifyProps?.push_threads === pushThreadsNotifyLevel
|
||||
) {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {push: pushNotifyLevel, push_threads: pushThreadsNotifyLevel};
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
|
||||
handleUpdatePushNotificationLevel = (pushNotifyLevel: ChannelNotifyProps['push']) => this.setState({pushNotifyLevel});
|
||||
handleUpdatePushThreadsNotificationLevel = (pushThreadsNotifyLevel: UserNotifyProps['push_threads']) => this.setState({pushThreadsNotifyLevel});
|
||||
handleUpdateIgnoreChannelMentions = (ignoreChannelMentions: ChannelNotifyProps['ignore_channel_mentions']) => this.setState({ignoreChannelMentions});
|
||||
|
||||
handleSubmitIgnoreChannelMentions = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
|
||||
const {ignoreChannelMentions} = this.state;
|
||||
|
||||
if (channelNotifyProps?.ignore_channel_mentions === ignoreChannelMentions) {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {ignore_channel_mentions: ignoreChannelMentions};
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
|
||||
handleUpdateChannelAutoFollowThreads = (channelAutoFollowThreads: ChannelNotifyProps['channel_auto_follow_threads']) => this.setState({channelAutoFollowThreads});
|
||||
|
||||
handleSubmitChannelAutoFollowThreads = () => {
|
||||
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
|
||||
const {channelAutoFollowThreads} = this.state;
|
||||
|
||||
if (channelNotifyProps?.channel_auto_follow_threads === channelAutoFollowThreads) {
|
||||
this.updateSection(NotificationSections.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {channel_auto_follow_threads: channelAutoFollowThreads};
|
||||
this.handleUpdateChannelNotifyProps(props);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
activeSection,
|
||||
desktopNotifyLevel,
|
||||
desktopThreadsNotifyLevel,
|
||||
desktopSound,
|
||||
desktopNotifySound,
|
||||
markUnreadNotifyLevel,
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
ignoreChannelMentions,
|
||||
channelAutoFollowThreads,
|
||||
serverError,
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
channel,
|
||||
channelMember,
|
||||
currentUser,
|
||||
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>;
|
||||
}
|
||||
|
||||
const isGM = this.isGM();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName='a11y__modal settings-modal settings-modal--tabless'
|
||||
show={this.state.show}
|
||||
onHide={this.handleHide}
|
||||
onExited={this.handleExit}
|
||||
role='dialog'
|
||||
aria-labelledby='channelNotificationModalLabel'
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title
|
||||
componentClass='h1'
|
||||
id='channelNotificationModalLabel'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.preferences'
|
||||
defaultMessage='Notification Preferences for '
|
||||
/>
|
||||
<span className='name'>{channel.display_name}</span>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className='settings-table'>
|
||||
<div className='settings-content'>
|
||||
<div className='user-settings'>
|
||||
<br/>
|
||||
<div className='divider-dark first'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.MARK_UNREAD}
|
||||
expand={activeSection === NotificationSections.MARK_UNREAD}
|
||||
memberNotificationLevel={markUnreadNotifyLevel}
|
||||
onChange={this.handleUpdateMarkUnreadLevel}
|
||||
onSubmit={this.handleSubmitMarkUnreadLevel}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.IGNORE_CHANNEL_MENTIONS}
|
||||
expand={activeSection === NotificationSections.IGNORE_CHANNEL_MENTIONS}
|
||||
memberNotificationLevel={markUnreadNotifyLevel}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
onChange={this.handleUpdateIgnoreChannelMentions}
|
||||
onSubmit={this.handleSubmitIgnoreChannelMentions}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
{!isChannelMuted(channelMember) &&
|
||||
<div>
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.DESKTOP}
|
||||
expand={activeSection === NotificationSections.DESKTOP}
|
||||
memberNotificationLevel={desktopNotifyLevel}
|
||||
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
|
||||
memberDesktopSound={desktopSound}
|
||||
memberDesktopNotificationSound={desktopNotifySound}
|
||||
globalNotificationLevel={getDefaultDesktopNotificationLevel(currentUser.notify_props, isGM)}
|
||||
globalNotificationSound={getDefaultDesktopNotificationSound(currentUser.notify_props)}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdateDesktopNotifyLevel}
|
||||
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
|
||||
onChangeDesktopSound={this.handleUpdateDesktopSound}
|
||||
onChangeNotificationSound={this.handleUpdateDesktopNotifySound}
|
||||
onReset={this.handleResetDesktopNotification}
|
||||
onSubmit={this.handleSubmitDesktopNotification}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
{sendPushNotifications &&
|
||||
<NotificationSection
|
||||
section={NotificationSections.PUSH}
|
||||
expand={activeSection === NotificationSections.PUSH}
|
||||
memberNotificationLevel={pushNotifyLevel}
|
||||
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
|
||||
globalNotificationLevel={getDefaultPushNotifyLevel(currentUser.notify_props, isGM)}
|
||||
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdatePushNotificationLevel}
|
||||
onReset={this.handleResetPushNotification}
|
||||
onChangeThreads={this.handleUpdatePushThreadsNotificationLevel}
|
||||
onSubmit={this.handleSubmitPushNotificationLevel}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{!isGM &&
|
||||
<>
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
expand={activeSection === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
memberNotificationLevel={markUnreadNotifyLevel}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
onChange={this.handleUpdateChannelAutoFollowThreads}
|
||||
onSubmit={this.handleSubmitChannelAutoFollowThreads}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{serverErrorTag}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
function getUseSameDesktopSetting(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||
const isSameAsDesktop = channelMemberNotifyProps ? channelMemberNotifyProps?.desktop === channelMemberNotifyProps?.push :
|
||||
currentUserNotifyProps.push === currentUserNotifyProps.desktop;
|
||||
const isSameAsDesktopThreads = channelMemberNotifyProps ? channelMemberNotifyProps?.desktop_threads === channelMemberNotifyProps?.push_threads :
|
||||
currentUserNotifyProps.push_threads === currentUserNotifyProps.desktop_threads;
|
||||
return isSameAsDesktop && isSameAsDesktopThreads;
|
||||
}
|
||||
|
||||
function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
||||
|
||||
if (channelMemberNotifyProps?.mark_unread === NotificationLevels.MENTION || (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false')) {
|
||||
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
|
||||
}
|
||||
|
||||
let ignoreChannelMentions = channelMemberNotifyProps?.ignore_channel_mentions;
|
||||
if (!ignoreChannelMentions || ignoreChannelMentions === IgnoreChannelMentions.DEFAULT) {
|
||||
ignoreChannelMentions = ignoreChannelMentionsDefault;
|
||||
}
|
||||
|
||||
const desktop = channelMemberNotifyProps?.desktop === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.desktop || currentUserNotifyProps.desktop);
|
||||
const push = channelMemberNotifyProps?.push === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.push || currentUserNotifyProps.push);
|
||||
|
||||
return {
|
||||
desktop,
|
||||
desktop_threads: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
|
||||
mark_unread: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
|
||||
push,
|
||||
push_threads: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
|
||||
ignore_channel_mentions: ignoreChannelMentions,
|
||||
channel_auto_follow_threads: channelMemberNotifyProps?.channel_auto_follow_threads || 'off',
|
||||
};
|
||||
}
|
||||
|
||||
type SettingsType = {
|
||||
desktop: ChannelNotifyProps['desktop'];
|
||||
desktop_threads: ChannelNotifyProps['desktop_threads'];
|
||||
mark_unread: ChannelNotifyProps['mark_unread'];
|
||||
push: ChannelNotifyProps['push'];
|
||||
push_threads: ChannelNotifyProps['push_threads'];
|
||||
ignore_channel_mentions: ChannelNotifyProps['ignore_channel_mentions'];
|
||||
channel_auto_follow_threads: ChannelNotifyProps['channel_auto_follow_threads'];
|
||||
};
|
||||
|
||||
export default function ChannelNotificationsModal(props: Props) {
|
||||
const {formatMessage} = useIntl();
|
||||
const [show, setShow] = useState(true);
|
||||
const [serverError, setServerError] = useState('');
|
||||
const [mobileSettingsSameAsDesktop, setMobileSettingsSameAsDesktop] = useState<boolean>(getUseSameDesktopSetting(props.currentUser.notify_props, props.channelMember?.notify_props));
|
||||
const [settings, setSettings] = useState<SettingsType>(getStateFromNotifyProps(props.currentUser.notify_props, props.channelMember?.notify_props));
|
||||
|
||||
function handleHide() {
|
||||
setShow(false);
|
||||
}
|
||||
|
||||
const handleChange = useCallback((values: Record<string, string>) => {
|
||||
setSettings((prevSettings) => ({...prevSettings, ...values}));
|
||||
}, []);
|
||||
|
||||
const handleMobileSettingsChange = useCallback(() => {
|
||||
setMobileSettingsSameAsDesktop((prevSettings) => !prevSettings);
|
||||
setSettings((prevSettings) => ({...prevSettings, push: prevSettings.desktop, push_threads: prevSettings.desktop_threads}));
|
||||
}, []);
|
||||
|
||||
const MuteIgnoreSectionContent = (
|
||||
<>
|
||||
<CheckboxSettingItem
|
||||
description={utils.MuteChannelDesc}
|
||||
inputFieldValue={settings.mark_unread === 'mention'}
|
||||
inputFieldData={utils.MuteChannelInputFieldData}
|
||||
handleChange={(e) => handleChange({mark_unread: e ? 'mention' : 'all'})}
|
||||
/>
|
||||
<CheckboxSettingItem
|
||||
description={utils.IgnoreMentionsDesc}
|
||||
inputFieldValue={settings.ignore_channel_mentions === 'on'}
|
||||
inputFieldData={utils.IgnoreMentionsInputFieldData}
|
||||
handleChange={(e) => handleChange({ignore_channel_mentions: e ? 'on' : 'off'})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const DesktopNotificationsSectionContent = (
|
||||
<>
|
||||
<RadioSettingItem
|
||||
title={utils.NotifyMeTitle}
|
||||
inputFieldValue={settings.desktop}
|
||||
inputFieldData={utils.desktopNotificationInputFieldData(props.currentUser.notify_props.desktop)}
|
||||
handleChange={(e) => handleChange({desktop: e.target.value})}
|
||||
/>
|
||||
{props.collapsedReplyThreads && settings.desktop === 'mention' &&
|
||||
<CheckboxSettingItem
|
||||
title={utils.ThreadsReplyTitle}
|
||||
inputFieldValue={settings.desktop_threads === 'all'}
|
||||
inputFieldData={utils.DesktopReplyThreadsInputFieldData}
|
||||
handleChange={(e) => handleChange({desktop_threads: e ? 'all' : 'mention'})}
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
|
||||
const MobileNotificationsSectionContent = (
|
||||
<>
|
||||
<CheckboxSettingItem
|
||||
inputFieldValue={mobileSettingsSameAsDesktop}
|
||||
inputFieldData={utils.sameMobileSettingsDesktopInputFieldData}
|
||||
handleChange={() => handleMobileSettingsChange()}
|
||||
/>
|
||||
{!mobileSettingsSameAsDesktop && (
|
||||
<>
|
||||
<RadioSettingItem
|
||||
title={utils.NotifyMeTitle}
|
||||
inputFieldValue={settings.push}
|
||||
inputFieldData={utils.mobileNotificationInputFieldData(props.currentUser.notify_props.push)}
|
||||
handleChange={(e) => handleChange({push: e.target.value})}
|
||||
/>
|
||||
{props.collapsedReplyThreads && settings.push === 'mention' &&
|
||||
<CheckboxSettingItem
|
||||
title={utils.ThreadsReplyTitle}
|
||||
inputFieldValue={settings.push_threads === 'all'}
|
||||
inputFieldData={utils.MobileReplyThreadsInputFieldData}
|
||||
handleChange={(e) => handleChange({push_threads: e ? 'all' : 'mention'})}
|
||||
/>}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const AutoFollowThreadsSectionContent = (
|
||||
<>
|
||||
<CheckboxSettingItem
|
||||
inputFieldValue={settings.channel_auto_follow_threads === 'on'}
|
||||
inputFieldData={utils.AutoFollowThreadsInputFieldData}
|
||||
handleChange={(e) => handleChange({channel_auto_follow_threads: e ? 'on' : 'off'})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
function handleSave() {
|
||||
const userSettings: Partial<SettingsType> = {...settings};
|
||||
if (!props.collapsedReplyThreads) {
|
||||
delete userSettings.push_threads;
|
||||
delete userSettings.desktop_threads;
|
||||
delete userSettings.channel_auto_follow_threads;
|
||||
}
|
||||
props.actions.updateChannelNotifyProps(props.currentUser.id, props.channel.id, userSettings).then((value) => {
|
||||
const {error} = value;
|
||||
if (error) {
|
||||
setServerError(error.message);
|
||||
} else {
|
||||
handleHide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const resetToDefaultBtn = useCallback((settingName: string) => {
|
||||
const defaultSettings = props.currentUser.notify_props;
|
||||
|
||||
const resetToDefault = (settingName: string) => {
|
||||
if (settingName === 'desktop') {
|
||||
setSettings({...settings, desktop: defaultSettings.desktop, desktop_threads: defaultSettings.desktop_threads || settings.desktop_threads});
|
||||
}
|
||||
if (settingName === 'push') {
|
||||
setSettings({...settings, push: defaultSettings.desktop, push_threads: defaultSettings.push_threads || settings.push_threads});
|
||||
}
|
||||
};
|
||||
|
||||
const isDesktopSameAsDefault = (defaultSettings.desktop === settings.desktop && defaultSettings.desktop_threads === settings.desktop_threads);
|
||||
const isPushSameAsDefault = (defaultSettings.push === settings.push && defaultSettings.push_threads === settings.push_threads);
|
||||
if ((settingName === 'desktop' && isDesktopSameAsDefault) || (settingName === 'push' && isPushSameAsDefault)) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className='channel-notifications-settings-modal__reset-btn'
|
||||
onClick={() => resetToDefault(settingName)}
|
||||
>
|
||||
<RefreshIcon
|
||||
size={14}
|
||||
color={'currentColor'}
|
||||
/>
|
||||
{formatMessage({
|
||||
id: 'channel_notifications.resetToDefault',
|
||||
defaultMessage: 'Reset to default',
|
||||
})}
|
||||
</button>
|
||||
);
|
||||
}, [props.currentUser, settings]);
|
||||
|
||||
const settingsAndAlertBanner = settings.mark_unread === 'all' ? (
|
||||
<>
|
||||
<div className='channel-notifications-settings-modal__divider'/>
|
||||
<ModalSection
|
||||
title={utils.DesktopNotificationsSectionTitle}
|
||||
description={utils.DesktopNotificationsSectionDesc}
|
||||
content={DesktopNotificationsSectionContent}
|
||||
titleSuffix={resetToDefaultBtn('desktop')}
|
||||
/>
|
||||
<div className='channel-notifications-settings-modal__divider'/>
|
||||
<ModalSection
|
||||
title={utils.MobileNotificationsSectionTitle}
|
||||
description={utils.MobileNotificationsSectionDesc}
|
||||
content={MobileNotificationsSectionContent}
|
||||
titleSuffix={resetToDefaultBtn('push')}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<AlertBanner
|
||||
mode='info'
|
||||
variant='app'
|
||||
customIcon={
|
||||
<BellOffOutlineIcon
|
||||
size={24}
|
||||
color={'currentColor'}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='channel_notifications.alertBanner.title'
|
||||
defaultMessage='This channel is muted'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='channel_notifications.alertBanner.description'
|
||||
defaultMessage='All other notification preferences for this channel are disabled'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName='a11y__modal channel-notifications-settings-modal'
|
||||
show={show}
|
||||
onHide={handleHide}
|
||||
onExited={props.onExited}
|
||||
role='dialog'
|
||||
aria-labelledby='channelNotificationModalLabel'
|
||||
style={{display: 'flex', placeItems: 'center'}}
|
||||
>
|
||||
<ModalHeader
|
||||
id={'channelNotificationModalLabel'}
|
||||
title={formatMessage({
|
||||
id: 'channel_notifications.preferences',
|
||||
defaultMessage: 'Notification Preferences',
|
||||
})}
|
||||
subtitle={props.channel.display_name}
|
||||
handleClose={handleHide}
|
||||
/>
|
||||
<main className='channel-notifications-settings-modal__body'>
|
||||
<ModalSection
|
||||
title={utils.MuteAndIgnoreSectionTitle}
|
||||
content={MuteIgnoreSectionContent}
|
||||
/>
|
||||
{settingsAndAlertBanner}
|
||||
{props.collapsedReplyThreads &&
|
||||
<>
|
||||
<div className='channel-notifications-settings-modal__divider'/>
|
||||
<ModalSection
|
||||
title={utils.AutoFollowThreadsTitle}
|
||||
description={utils.AutoFollowThreadsDesc}
|
||||
content={AutoFollowThreadsSectionContent}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</main>
|
||||
<footer className='channel-notifications-settings-modal__footer'>
|
||||
{serverError &&
|
||||
<span className='channel-notifications-settings-modal__server-error'>
|
||||
{serverError}
|
||||
</span>
|
||||
}
|
||||
<button
|
||||
onClick={handleHide}
|
||||
className='channel-notifications-settings-modal__cancel-btn'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='generic_btn.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={'channel-notifications-settings-modal__save-btn'}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='generic_btn.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, DESKTOP on collapsed view 1`] = `
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
isCollapsed={true}
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
section="desktop"
|
||||
title={
|
||||
<SectionTitle
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, MARK_UNREAD on collapsed view 1`] = `
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
isCollapsed={true}
|
||||
memberNotifyLevel="all"
|
||||
section="markUnread"
|
||||
/>
|
||||
}
|
||||
section="markUnread"
|
||||
title={
|
||||
<SectionTitle
|
||||
section="markUnread"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, PUSH on collapsed view 1`] = `
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
isCollapsed={true}
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
section="push"
|
||||
title={
|
||||
<SectionTitle
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
@ -1,59 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="For all activity {isDefault}"
|
||||
id="channel_notifications.allActivity"
|
||||
values={
|
||||
Object {
|
||||
"isDefault": <React.Fragment />,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MARK_UNREAD & ALL 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Off"
|
||||
id="channel_notifications.muteChannel.off.title"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
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 {isDefault}"
|
||||
id="channel_notifications.never"
|
||||
values={
|
||||
Object {
|
||||
"isDefault": <React.Fragment />,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on global DEFAULT 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Global default ({notifyLevel})"
|
||||
id="channel_notifications.globalDefault"
|
||||
values={
|
||||
Object {
|
||||
"notifyLevel": <Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="default"
|
||||
id="channel_notifications.levels.default"
|
||||
/>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
@ -1,942 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
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>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="push"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
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>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="desktop"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Thread reply notifications"
|
||||
id="user.settings.notifications.threads.desktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={true}
|
||||
id="desktopThreadsNotificationAllActivity"
|
||||
name="desktopThreadsNotificationLevel"
|
||||
onChange={[MockFunction]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notify me about threads I'm following"
|
||||
id="user.settings.notifications.threads.allActivity"
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="When enabled, any reply to a thread you're following will send a desktop notification."
|
||||
id="user.settings.notifications.threads"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
<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>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationUnmute"
|
||||
name="channelNotificationMute"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
memberNotifyLevel="mention"
|
||||
section="markUnread"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMute"
|
||||
name="channelNotificationMute"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
memberNotifyLevel="all"
|
||||
section="markUnread"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="markUnread"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="markUnread"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="push"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="push"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Thread reply notifications"
|
||||
id="user.settings.notifications.threads.push"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={true}
|
||||
id="pushThreadsNotificationAllActivity"
|
||||
name="pushThreadsNotificationLevel"
|
||||
onChange={[MockFunction]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notify me about threads I'm following"
|
||||
id="user.settings.notifications.push_threads.allActivity"
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="When enabled, any reply to a thread you're following will send a mobile push notification."
|
||||
id="user.settings.notifications.push_threads"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
@ -1,28 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
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>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on MARK_UNREAD 1`] = `
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Muting turns off desktop, email and push notifications for this channel. The channel will not be marked as unread unless you are mentioned."
|
||||
id="channel_notifications.muteChannel.help"
|
||||
/>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Selecting an option other than the \\"default\\" will override the global notification settings for mobile push notifications."
|
||||
id="channel_notifications.overridePush"
|
||||
/>
|
||||
</span>
|
||||
`;
|
@ -1,91 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot on server error 1`] = `
|
||||
<CollapseView
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
onExpandSection={[Function]}
|
||||
section="desktop"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, DESKTOP on collapsed view 1`] = `
|
||||
<CollapseView
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
onExpandSection={[Function]}
|
||||
section="desktop"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, DESKTOP on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel="default"
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="desktop"
|
||||
serverError=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, MARK_UNREAD on collapsed view 1`] = `
|
||||
<CollapseView
|
||||
globalNotifyLevel={null}
|
||||
memberNotifyLevel="all"
|
||||
onExpandSection={[Function]}
|
||||
section="markUnread"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel={null}
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="markUnread"
|
||||
serverError=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, PUSH on collapsed view 1`] = `
|
||||
<CollapseView
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
onExpandSection={[Function]}
|
||||
section="push"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, PUSH on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel="default"
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onCollapseSection={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
section="push"
|
||||
serverError=""
|
||||
/>
|
||||
`;
|
@ -1,30 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
|
||||
<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`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Mute Channel"
|
||||
id="channel_notifications.muteChannel.settings"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
|
||||
<div
|
||||
className="SectionTitle__wrapper"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Mobile push notifications"
|
||||
id="channel_notifications.push"
|
||||
/>
|
||||
</div>
|
||||
`;
|
@ -1,44 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import CollapseView from 'components/channel_notifications_modal/components/collapse_view';
|
||||
|
||||
import {NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
describe('components/channel_notifications_modal/CollapseView', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
memberNotifyLevel: NotificationLevels.ALL,
|
||||
globalNotifyLevel: NotificationLevels.DEFAULT,
|
||||
onExpandSection: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot, DESKTOP on collapsed view', () => {
|
||||
const wrapper = shallow(
|
||||
<CollapseView {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on collapsed view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<CollapseView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on collapsed view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<CollapseView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
|
||||
import Describe from './describe';
|
||||
import SectionTitle from './section_title';
|
||||
|
||||
type Props = {
|
||||
ignoreChannelMentions?: string;
|
||||
channelAutoFollowThreads?: string;
|
||||
onExpandSection: (section: string) => void;
|
||||
globalNotifyLevel?: string;
|
||||
memberNotifyLevel: string;
|
||||
section: string;
|
||||
}
|
||||
|
||||
export default function CollapseView({onExpandSection, globalNotifyLevel, memberNotifyLevel, section, ignoreChannelMentions, channelAutoFollowThreads}: Props) {
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={<SectionTitle section={section}/>}
|
||||
describe={
|
||||
<Describe
|
||||
section={section}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
memberNotifyLevel={memberNotifyLevel}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
isCollapsed={true}
|
||||
/>
|
||||
}
|
||||
updateSection={onExpandSection}
|
||||
section={section}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Describe from 'components/channel_notifications_modal/components/describe';
|
||||
|
||||
import {NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
describe('components/channel_notifications_modal/NotificationSection', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
memberNotifyLevel: NotificationLevels.DEFAULT,
|
||||
globalNotifyLevel: NotificationLevels.DEFAULT,
|
||||
};
|
||||
|
||||
test('should match snapshot, on global DEFAULT', () => {
|
||||
const wrapper = shallow(
|
||||
<Describe {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on MENTION', () => {
|
||||
const props = {...baseProps, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<Describe {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on DESKTOP/PUSH & ALL', () => {
|
||||
const props = {...baseProps, memberNotifyLevel: NotificationLevels.ALL};
|
||||
const wrapper = shallow(
|
||||
<Describe {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on MARK_UNREAD & ALL', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD, memberNotifyLevel: NotificationLevels.ALL};
|
||||
const wrapper = shallow(
|
||||
<Describe {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on NONE', () => {
|
||||
const props = {...baseProps, memberNotifyLevel: NotificationLevels.NONE};
|
||||
const wrapper = shallow(
|
||||
<Describe {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,139 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
|
||||
type Props = {
|
||||
globalNotifyLevel?: string;
|
||||
ignoreChannelMentions?: string;
|
||||
channelAutoFollowThreads?: string;
|
||||
memberNotifyLevel: string;
|
||||
section: string;
|
||||
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');
|
||||
t('channel_notifications.levels.all');
|
||||
t('channel_notifications.levels.mention');
|
||||
t('channel_notifications.levels.none');
|
||||
const levelsFormattedMessageId = 'channel_notifications.levels.' + globalNotifyLevel;
|
||||
const notifyLevel = (
|
||||
<FormattedMessage
|
||||
id={levelsFormattedMessageId}
|
||||
defaultMessage={globalNotifyLevel}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.globalDefault'
|
||||
defaultMessage='Global default ({notifyLevel})'
|
||||
values={{notifyLevel}}
|
||||
/>
|
||||
);
|
||||
} else if (memberNotifyLevel === NotificationLevels.MENTION && section === NotificationSections.MARK_UNREAD) {
|
||||
if (isCollapsed) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.muteChannel.on.title.collapse'
|
||||
defaultMessage='Mute is enabled. Desktop, email and push notifications will not be sent for this channel.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.muteChannel.on.title'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
section === NotificationSections.IGNORE_CHANNEL_MENTIONS &&
|
||||
ignoreChannelMentions === IgnoreChannelMentions.ON
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.ignoreChannelMentions.on.title'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
section === NotificationSections.IGNORE_CHANNEL_MENTIONS &&
|
||||
ignoreChannelMentions === IgnoreChannelMentions.OFF
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.ignoreChannelMentions.off.title'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
section === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS &&
|
||||
channelAutoFollowThreads === ChannelAutoFollowThreads.ON
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.channelAutoFollowThreads.on.title'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
section === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS &&
|
||||
channelAutoFollowThreads === ChannelAutoFollowThreads.OFF
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.channelAutoFollowThreads.off.title'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else if (memberNotifyLevel === NotificationLevels.MENTION) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
(section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) &&
|
||||
memberNotifyLevel === NotificationLevels.ALL
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.allActivity'
|
||||
defaultMessage='For all activity {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
section === NotificationSections.MARK_UNREAD &&
|
||||
memberNotifyLevel === NotificationLevels.ALL
|
||||
) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.muteChannel.off.title'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.never'
|
||||
defaultMessage='Never {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import ExpandView from 'components/channel_notifications_modal/components/expand_view';
|
||||
|
||||
import {NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useSelector: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
describe('components/channel_notifications_modal/ExpandView', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
memberNotifyLevel: NotificationLevels.ALL,
|
||||
memberThreadsNotifyLevel: NotificationLevels.ALL,
|
||||
globalNotifyLevel: NotificationLevels.DEFAULT,
|
||||
serverError: '',
|
||||
onChange: jest.fn(),
|
||||
onChangeThreads: jest.fn(),
|
||||
onCollapseSection: jest.fn(),
|
||||
onSubmit: jest.fn(),
|
||||
onReset: jest.fn(),
|
||||
isGM: false,
|
||||
};
|
||||
|
||||
describe('normal channels', () => {
|
||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gms', () => {
|
||||
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,437 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo, useRef} from 'react';
|
||||
import type {ChangeEvent} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
import ReactSelect from 'react-select';
|
||||
import type {ValueType} from 'react-select';
|
||||
|
||||
import type {ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
|
||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
|
||||
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
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;
|
||||
isGM: boolean;
|
||||
}
|
||||
|
||||
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,
|
||||
ignoreChannelMentions,
|
||||
channelAutoFollowThreads,
|
||||
isGM,
|
||||
}: 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>
|
||||
{ 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='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.ALL}
|
||||
checked={memberNotifyLevel === NotificationLevels.ALL}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.ALL}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationMentions'
|
||||
name='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.MENTION}
|
||||
checked={memberNotifyLevel === NotificationLevels.MENTION}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.MENTION}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelNotificationNever'
|
||||
name='channelNotifications'
|
||||
type='radio'
|
||||
value={NotificationLevels.NONE}
|
||||
checked={memberNotifyLevel === NotificationLevels.NONE}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.NONE}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
{section === NotificationSections.IGNORE_CHANNEL_MENTIONS &&
|
||||
<fieldset>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='ignoreChannelMentionsOn'
|
||||
name='ignoreChannelMentions'
|
||||
type='radio'
|
||||
value={IgnoreChannelMentions.ON}
|
||||
checked={ignoreChannelMentions === IgnoreChannelMentions.ON}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
ignoreChannelMentions={IgnoreChannelMentions.ON}
|
||||
memberNotifyLevel={memberNotifyLevel}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='ignoreChannelMentionsOff'
|
||||
name='ignoreChannelMentions'
|
||||
type='radio'
|
||||
value={IgnoreChannelMentions.OFF}
|
||||
checked={ignoreChannelMentions === IgnoreChannelMentions.OFF}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
ignoreChannelMentions={IgnoreChannelMentions.OFF}
|
||||
memberNotifyLevel={memberNotifyLevel}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
{section === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS &&
|
||||
<fieldset>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelAutoFollowThreadsOn'
|
||||
name='channelAutoFollowThreads'
|
||||
type='radio'
|
||||
value={ChannelAutoFollowThreads.ON}
|
||||
checked={channelAutoFollowThreads === ChannelAutoFollowThreads.ON}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
channelAutoFollowThreads={ChannelAutoFollowThreads.ON}
|
||||
memberNotifyLevel={memberNotifyLevel}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelAutoFollowThreadsOff'
|
||||
name='channelAutoFollowThreads'
|
||||
type='radio'
|
||||
value={ChannelAutoFollowThreads.OFF}
|
||||
checked={channelAutoFollowThreads === ChannelAutoFollowThreads.OFF}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
channelAutoFollowThreads={ChannelAutoFollowThreads.OFF}
|
||||
memberNotifyLevel={memberNotifyLevel}
|
||||
globalNotifyLevel={globalNotifyLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
{section === NotificationSections.MARK_UNREAD &&
|
||||
<fieldset>
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationUnmute'
|
||||
name='channelNotificationMute'
|
||||
type='radio'
|
||||
value={NotificationLevels.MENTION}
|
||||
checked={memberNotifyLevel === NotificationLevels.MENTION}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.MENTION}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label className=''>
|
||||
<input
|
||||
id='channelNotificationMute'
|
||||
name='channelNotificationMute'
|
||||
type='radio'
|
||||
value={NotificationLevels.ALL}
|
||||
checked={memberNotifyLevel === NotificationLevels.ALL}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Describe
|
||||
section={section}
|
||||
memberNotifyLevel={NotificationLevels.ALL}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
<div className='mt-5'>
|
||||
<ExtraInfo section={section}/>
|
||||
</div>
|
||||
|
||||
{isCRTEnabled &&
|
||||
section === NotificationSections.DESKTOP &&
|
||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||
!isGM &&
|
||||
<>
|
||||
<hr/>
|
||||
<fieldset>
|
||||
<legend className='form-legend'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.threads.desktop'
|
||||
defaultMessage='Thread reply notifications'
|
||||
/>
|
||||
</legend>
|
||||
<div className='checkbox'>
|
||||
<label>
|
||||
<input
|
||||
id='desktopThreadsNotificationAllActivity'
|
||||
type='checkbox'
|
||||
name='desktopThreadsNotificationLevel'
|
||||
checked={memberThreadsNotifyLevel === NotificationLevels.ALL}
|
||||
onChange={onChangeThreads}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.threads.allActivity'
|
||||
defaultMessage={'Notify me about threads I\'m following'}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='mt-5'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.threads'
|
||||
defaultMessage={'When enabled, any reply to a thread you\'re following will send a desktop notification.'}
|
||||
/>
|
||||
</div>
|
||||
</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 &&
|
||||
!isGM &&
|
||||
<>
|
||||
<hr/>
|
||||
<fieldset>
|
||||
<legend className='form-legend'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.threads.push'
|
||||
defaultMessage='Thread reply notifications'
|
||||
/>
|
||||
</legend>
|
||||
<div className='checkbox'>
|
||||
<label>
|
||||
<input
|
||||
id='pushThreadsNotificationAllActivity'
|
||||
type='checkbox'
|
||||
name='pushThreadsNotificationLevel'
|
||||
checked={memberThreadsNotifyLevel === NotificationLevels.ALL}
|
||||
onChange={onChangeThreads}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.push_threads.allActivity'
|
||||
defaultMessage={'Notify me about threads I\'m following'}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='mt-5'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.push_threads'
|
||||
defaultMessage={'When enabled, any reply to a thread you\'re following will send a mobile push notification.'}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)];
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={
|
||||
<SectionTitle
|
||||
section={section}
|
||||
isExpanded={true}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
onClickResetButton={onReset}
|
||||
/>}
|
||||
inputs={inputs}
|
||||
submit={onSubmit}
|
||||
serverError={serverError}
|
||||
updateSection={onCollapseSection}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import ExtraInfo from 'components/channel_notifications_modal/components/extra_info';
|
||||
|
||||
import {NotificationSections} from 'utils/constants';
|
||||
|
||||
describe('components/channel_notifications_modal/ExtraInfo', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
};
|
||||
|
||||
test('should match snapshot, on DESKTOP', () => {
|
||||
const wrapper = shallow(
|
||||
<ExtraInfo {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on PUSH', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<ExtraInfo {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on MARK_UNREAD', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<ExtraInfo {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {NotificationSections} from 'utils/constants';
|
||||
|
||||
type Props = {
|
||||
section: string;
|
||||
}
|
||||
|
||||
export default function ExtraInfo({section}: Props) {
|
||||
switch (section) {
|
||||
case NotificationSections.DESKTOP:
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.override'
|
||||
defaultMessage='Selecting an option other than the "default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
case NotificationSections.PUSH:
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.overridePush'
|
||||
defaultMessage='Selecting an option other than the "default" will override the global notification settings for mobile push notifications.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
case NotificationSections.MARK_UNREAD:
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.muteChannel.help'
|
||||
defaultMessage='Muting turns off desktop, email and push notifications for this channel. The channel will not be marked as unread unless you are mentioned.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
case NotificationSections.IGNORE_CHANNEL_MENTIONS:
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.ignoreChannelMentions.help'
|
||||
defaultMessage='When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
case NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS:
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='channel_notifications.channelAutoFollowThreads.help'
|
||||
defaultMessage='When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,182 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import {NotificationSections, NotificationLevels} from 'utils/constants';
|
||||
|
||||
import CollapseView from './collapse_view';
|
||||
import ExpandView from './expand_view';
|
||||
|
||||
export default class NotificationSection extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Notification section
|
||||
*/
|
||||
section: PropTypes.string.isRequired,
|
||||
|
||||
/**
|
||||
* Expand if true, else collapse the section
|
||||
*/
|
||||
expand: PropTypes.bool.isRequired,
|
||||
|
||||
/**
|
||||
* Member's desktop notification level
|
||||
*/
|
||||
memberNotificationLevel: PropTypes.string.isRequired,
|
||||
|
||||
memberDesktopSound: PropTypes.string,
|
||||
|
||||
memberDesktopNotificationSound: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Member's desktop_threads notification level
|
||||
*/
|
||||
memberThreadsNotificationLevel: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Ignore channel-wide mentions @channel, @here and @all
|
||||
*/
|
||||
ignoreChannelMentions: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Auto-follow all new threads in this channel
|
||||
*/
|
||||
channelAutoFollowThreads: PropTypes.string,
|
||||
|
||||
/**
|
||||
* User's global notification level
|
||||
*/
|
||||
globalNotificationLevel: PropTypes.string,
|
||||
|
||||
/**
|
||||
* User's global notification sound
|
||||
*/
|
||||
globalNotificationSound: PropTypes.string,
|
||||
|
||||
/**
|
||||
* onChange handles update of desktop notification level
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
/**
|
||||
* onChangeThreads handles update of desktop_threads notification level
|
||||
*/
|
||||
onChangeThreads: PropTypes.func,
|
||||
|
||||
onChangeDesktopSound: PropTypes.func,
|
||||
|
||||
onChangeNotificationSound: PropTypes.func,
|
||||
|
||||
onReset: PropTypes.func,
|
||||
|
||||
isNotificationsSettingSameAsGlobal: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Submit function to save notification level
|
||||
*/
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
|
||||
/**
|
||||
* Update function to to expand or collapse a section
|
||||
*/
|
||||
onUpdateSection: PropTypes.func.isRequired,
|
||||
|
||||
/**
|
||||
* Error string from the server
|
||||
*/
|
||||
serverError: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether the preferences are those of a GM
|
||||
*/
|
||||
isGM: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleOnChange = (e) => {
|
||||
this.props.onChange(e.target.value);
|
||||
};
|
||||
|
||||
handleOnChangeThreads = (e) => {
|
||||
const value = e.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
handleCollapseSection = () => {
|
||||
this.props.onUpdateSection(NotificationSections.NONE);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
expand,
|
||||
globalNotificationLevel,
|
||||
globalNotificationSound,
|
||||
memberNotificationLevel,
|
||||
memberThreadsNotificationLevel,
|
||||
memberDesktopSound,
|
||||
memberDesktopNotificationSound,
|
||||
ignoreChannelMentions,
|
||||
isNotificationsSettingSameAsGlobal,
|
||||
channelAutoFollowThreads,
|
||||
onSubmit,
|
||||
onReset,
|
||||
section,
|
||||
serverError,
|
||||
isGM,
|
||||
} = this.props;
|
||||
|
||||
if (expand) {
|
||||
return (
|
||||
<ExpandView
|
||||
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}
|
||||
isGM={isGM}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CollapseView
|
||||
section={section}
|
||||
onExpandSection={this.handleExpandSection}
|
||||
memberNotifyLevel={memberNotificationLevel}
|
||||
globalNotifyLevel={globalNotificationLevel}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import NotificationSection from 'components/channel_notifications_modal/components/notification_section.jsx';
|
||||
|
||||
import {NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
describe('components/channel_notifications_modal/NotificationSection', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
expand: false,
|
||||
memberNotificationLevel: NotificationLevels.ALL,
|
||||
memberThreadsNotificationLevel: NotificationLevels.ALL,
|
||||
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: '',
|
||||
isGM: false,
|
||||
};
|
||||
|
||||
test('should match snapshot, DESKTOP on collapsed view', () => {
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||
const props = {...baseProps, expand: true};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on collapsed view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH, expand: true};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on collapsed view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD, globalNotificationLevel: null};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD, expand: true, globalNotificationLevel: null};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have called onChange when handleOnChange is called', () => {
|
||||
const onChange = jest.fn();
|
||||
const props = {...baseProps, expand: true, onChange};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
wrapper.instance().handleOnChange({target: {value: NotificationLevels.ALL}});
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(NotificationLevels.ALL);
|
||||
});
|
||||
|
||||
test('should have called onUpdateSection when handleExpandSection is called', () => {
|
||||
const onUpdateSection = jest.fn();
|
||||
const props = {...baseProps, expand: true, onUpdateSection};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
wrapper.instance().handleExpandSection({preventDefault: jest.fn()});
|
||||
expect(onUpdateSection).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdateSection).toHaveBeenCalledWith(NotificationSections.DESKTOP);
|
||||
});
|
||||
|
||||
test('should have called onUpdateSection when handleCollapseSection is called', () => {
|
||||
const onUpdateSection = jest.fn();
|
||||
const props = {...baseProps, expand: true, onUpdateSection};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
wrapper.instance().handleCollapseSection({preventDefault: jest.fn()});
|
||||
expect(onUpdateSection).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdateSection).toHaveBeenCalledWith(NotificationSections.NONE);
|
||||
});
|
||||
|
||||
test('should match snapshot on server error', () => {
|
||||
const props = {...baseProps, serverError: 'server error occurred'};
|
||||
const wrapper = shallow(
|
||||
<NotificationSection {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
@import 'sass/utils/mixins';
|
||||
|
||||
.SectionTitle {
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__resetButton {
|
||||
@include button-style--none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import SectionTitle from 'components/channel_notifications_modal/components/section_title';
|
||||
|
||||
import {NotificationSections} from 'utils/constants';
|
||||
|
||||
describe('components/channel_notifications_modal/ExtraInfo', () => {
|
||||
const baseProps = {
|
||||
section: NotificationSections.DESKTOP,
|
||||
};
|
||||
|
||||
test('should match snapshot, on DESKTOP', () => {
|
||||
const wrapper = shallow(
|
||||
<SectionTitle {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on PUSH', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<SectionTitle {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on MARK_UNREAD', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<SectionTitle {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,71 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
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, isExpanded, isNotificationsSettingSameAsGlobal, onClickResetButton}: Props) {
|
||||
if (section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) {
|
||||
return (
|
||||
<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 (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.muteChannel.settings'
|
||||
defaultMessage='Mute Channel'
|
||||
/>
|
||||
);
|
||||
} else if (section === NotificationSections.IGNORE_CHANNEL_MENTIONS) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.ignoreChannelMentions'
|
||||
defaultMessage='Ignore mentions for @channel, @here and @all'
|
||||
/>
|
||||
);
|
||||
} else if (section === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.channelAutoFollowThreads'
|
||||
defaultMessage='Auto-follow all new threads in this channel'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -11,6 +11,9 @@ import type {ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
import {updateChannelNotifyProps} from 'mattermost-redux/actions/channels';
|
||||
import {getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {
|
||||
isCollapsedThreadsEnabled,
|
||||
} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
|
||||
import type {GlobalState} from 'types/store/index';
|
||||
@ -18,6 +21,7 @@ import type {GlobalState} from 'types/store/index';
|
||||
import ChannelNotificationsModal from './channel_notifications_modal';
|
||||
|
||||
const mapStateToProps = (state: GlobalState) => ({
|
||||
collapsedReplyThreads: isCollapsedThreadsEnabled(state),
|
||||
channelMember: getMyCurrentChannelMembership(state),
|
||||
sendPushNotifications: getConfig(state).SendPushNotifications === 'true',
|
||||
});
|
||||
|
@ -0,0 +1,276 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage, defineMessages} from 'react-intl';
|
||||
|
||||
import type {ChannelNotifyProps} from '@mattermost/types/channels';
|
||||
import type {UserNotifyProps} from '@mattermost/types/users';
|
||||
|
||||
import type {FieldsetCheckbox} from 'components/widgets/modals/components/checkbox_setting_item';
|
||||
import type {FieldsetRadio} from 'components/widgets/modals/components/radio_setting_item';
|
||||
|
||||
import {NotificationLevels} from 'utils/constants';
|
||||
|
||||
export type ChannelMemberNotifyProps = Partial<ChannelNotifyProps> & Pick<UserNotifyProps, 'desktop_threads' | 'push_threads'>
|
||||
|
||||
const translations = defineMessages({
|
||||
MuteAndIgnoreSectionTitle: {
|
||||
id: 'channel_notifications.muteAndIgnore',
|
||||
defaultMessage: 'Mute or ignore',
|
||||
},
|
||||
NotifyMeTitle: {
|
||||
id: 'channel_notifications.NotifyMeTitle',
|
||||
defaultMessage: 'Notify me about…',
|
||||
},
|
||||
ThreadsReplyTitle: {
|
||||
id: 'channel_notifications.ThreadsReplyTitle',
|
||||
defaultMessage: 'Thread reply notifications',
|
||||
},
|
||||
|
||||
DesktopNotificationsSectionTitle: {
|
||||
id: 'channel_notifications.desktopNotificationsTitle',
|
||||
defaultMessage: 'Desktop Notifications',
|
||||
},
|
||||
|
||||
DesktopNotificationsSectionDesc: {
|
||||
id: 'channel_notifications.desktopNotificationsDesc',
|
||||
defaultMessage: 'Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.',
|
||||
},
|
||||
|
||||
MobileNotificationsSectionTitle: {
|
||||
id: 'channel_notifications.mobileNotificationsTitle',
|
||||
defaultMessage: 'Mobile Notifications',
|
||||
},
|
||||
|
||||
MobileNotificationsSectionDesc: {
|
||||
id: 'channel_notifications.mobileNotificationsDesc',
|
||||
defaultMessage: 'Notification alerts are pushed to your mobile device when there is activity in Mattermost.',
|
||||
},
|
||||
|
||||
MuteChannelDesc: {
|
||||
id: 'channel_notifications.muteChannelDesc',
|
||||
defaultMessage: 'Turns off notifications for this channel. You’ll still see badges if you’re mentioned.',
|
||||
},
|
||||
|
||||
IgnoreMentionsDesc: {
|
||||
id: 'channel_notifications.ignoreMentionsDesc',
|
||||
defaultMessage: 'When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel',
|
||||
},
|
||||
|
||||
MuteChannelInputFieldTitle: {
|
||||
id: 'channel_notifications.muteChannelTitle',
|
||||
defaultMessage: 'Mute channel',
|
||||
},
|
||||
|
||||
DesktopReplyThreadsInputFieldTitle: {
|
||||
id: 'channel_notifications.checkbox.threadsReplyTitle',
|
||||
defaultMessage: 'Notify me about replies to threads I\'m following',
|
||||
},
|
||||
|
||||
MobileReplyThreadsInputFieldTitle: {
|
||||
id: 'channel_notifications.checkbox.threadsReplyTitle',
|
||||
defaultMessage: 'Notify me about replies to threads I\'m following',
|
||||
},
|
||||
|
||||
sameMobileSettingsDesktopInputFieldTitle: {
|
||||
id: 'channel_notifications.checkbox.sameMobileSettingsDesktop',
|
||||
defaultMessage: 'Use the same notification settings as desktop',
|
||||
},
|
||||
|
||||
IgnoreMentionsInputFieldTitle: {
|
||||
id: 'channel_notifications.ignoreMentionsTitle',
|
||||
defaultMessage: 'Ignore mentions for @channel, @here and @all',
|
||||
},
|
||||
|
||||
AutoFollowThreadsTitle: {
|
||||
id: 'channel_notifications.autoFollowThreadsTitle',
|
||||
defaultMessage: 'Follow all threads in this channel',
|
||||
},
|
||||
|
||||
AutoFollowThreadsDesc: {
|
||||
id: 'channel_notifications.autoFollowThreadsDesc',
|
||||
defaultMessage: 'When enabled, all new replies in this channel will be automatically followed and will appear in your Threads view.',
|
||||
},
|
||||
|
||||
AutoFollowThreadsInputFieldTitle: {
|
||||
id: 'channel_notifications.checkbox.autoFollowThreadsTitle',
|
||||
defaultMessage: 'Automatically follow threads in this channel',
|
||||
},
|
||||
});
|
||||
|
||||
const desktopNotificationInputFieldOptions = defineMessages({
|
||||
allNewMessages: {
|
||||
id: 'channel_notifications.desktopNotificationAllLabel',
|
||||
defaultMessage: 'All new messages',
|
||||
},
|
||||
mentions: {
|
||||
id: 'channel_notifications.desktopNotificationMentionLabel',
|
||||
defaultMessage: 'Mentions, direct messages, and keywords only',
|
||||
},
|
||||
nothing: {
|
||||
id: 'channel_notifications.desktopNotificationNothingLabel',
|
||||
defaultMessage: 'Nothing',
|
||||
},
|
||||
});
|
||||
|
||||
const mobileNotificationInputFieldOptions = defineMessages({
|
||||
allNewMessages: {
|
||||
id: 'channel_notifications.MobileNotificationAllLabel',
|
||||
defaultMessage: 'All new messages',
|
||||
},
|
||||
mentions: {
|
||||
id: 'channel_notifications.MobileNotificationMentionLabel',
|
||||
defaultMessage: 'Mentions, direct messages, and keywords only',
|
||||
},
|
||||
nothing: {
|
||||
id: 'channel_notifications.MobileNotificationNothingLabel',
|
||||
defaultMessage: 'Nothing',
|
||||
},
|
||||
});
|
||||
|
||||
const MuteChannelInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.MuteChannelInputFieldTitle,
|
||||
name: 'mute channel',
|
||||
dataTestId: 'muteChannel',
|
||||
};
|
||||
|
||||
const DesktopReplyThreadsInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.DesktopReplyThreadsInputFieldTitle,
|
||||
name: 'desktop reply threads',
|
||||
dataTestId: 'desktopReplyThreads',
|
||||
};
|
||||
|
||||
const MobileReplyThreadsInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.MobileReplyThreadsInputFieldTitle,
|
||||
name: 'mobile reply threads',
|
||||
dataTestId: 'mobileReplyThreads',
|
||||
};
|
||||
|
||||
const sameMobileSettingsDesktopInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.sameMobileSettingsDesktopInputFieldTitle,
|
||||
name: 'same mobile settings as Desktop',
|
||||
dataTestId: 'sameMobileSettingsDesktop',
|
||||
};
|
||||
|
||||
const IgnoreMentionsInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.IgnoreMentionsInputFieldTitle,
|
||||
name: 'ignore mentions',
|
||||
dataTestId: 'ignoreMentions',
|
||||
};
|
||||
|
||||
const AutoFollowThreadsInputFieldData: FieldsetCheckbox = {
|
||||
title: translations.AutoFollowThreadsInputFieldTitle,
|
||||
name: 'auto follow threads',
|
||||
dataTestId: 'autoFollowThreads',
|
||||
};
|
||||
|
||||
const desktopNotificationInputFieldData = (defaultOption: string): FieldsetRadio => {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
dataTestId: `desktopNotification-${NotificationLevels.ALL}`,
|
||||
title: desktopNotificationInputFieldOptions.allNewMessages,
|
||||
name: `desktopNotification-${NotificationLevels.ALL}`,
|
||||
key: `desktopNotification-${NotificationLevels.ALL}`,
|
||||
value: NotificationLevels.ALL,
|
||||
suffix: defaultOption === NotificationLevels.ALL ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
{
|
||||
dataTestId: `desktopNotification-${NotificationLevels.MENTION}`,
|
||||
title: desktopNotificationInputFieldOptions.mentions,
|
||||
name: `desktopNotification-${NotificationLevels.MENTION}`,
|
||||
key: `desktopNotification-${NotificationLevels.MENTION}`,
|
||||
value: NotificationLevels.MENTION,
|
||||
suffix: defaultOption === NotificationLevels.MENTION ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
{
|
||||
dataTestId: `desktopNotification-${NotificationLevels.NONE}`,
|
||||
title: desktopNotificationInputFieldOptions.nothing,
|
||||
name: `desktopNotification-${NotificationLevels.NONE}`,
|
||||
key: `desktopNotification-${NotificationLevels.NONE}`,
|
||||
value: NotificationLevels.NONE,
|
||||
suffix: defaultOption === NotificationLevels.NONE ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const mobileNotificationInputFieldData = (defaultOption: string): FieldsetRadio => {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
dataTestId: `MobileNotification-${NotificationLevels.ALL}`,
|
||||
title: mobileNotificationInputFieldOptions.allNewMessages,
|
||||
name: `MobileNotification-${NotificationLevels.ALL}`,
|
||||
key: `MobileNotification-${NotificationLevels.ALL}`,
|
||||
value: NotificationLevels.ALL,
|
||||
suffix: defaultOption === NotificationLevels.ALL ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
{
|
||||
dataTestId: `MobileNotification-${NotificationLevels.MENTION}`,
|
||||
title: mobileNotificationInputFieldOptions.mentions,
|
||||
name: `MobileNotification-${NotificationLevels.MENTION}`,
|
||||
key: `MobileNotification-${NotificationLevels.MENTION}`,
|
||||
value: NotificationLevels.MENTION,
|
||||
suffix: defaultOption === NotificationLevels.MENTION ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
{
|
||||
dataTestId: `MobileNotification-${NotificationLevels.NONE}`,
|
||||
title: mobileNotificationInputFieldOptions.nothing,
|
||||
name: `MobileNotification-${NotificationLevels.NONE}`,
|
||||
key: `MobileNotification-${NotificationLevels.NONE}`,
|
||||
value: NotificationLevels.NONE,
|
||||
suffix: defaultOption === NotificationLevels.NONE ? (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.default'
|
||||
defaultMessage='(default)'
|
||||
/>) : undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const utils = {
|
||||
desktopNotificationInputFieldData,
|
||||
DesktopNotificationsSectionDesc: translations.DesktopNotificationsSectionDesc,
|
||||
DesktopNotificationsSectionTitle: translations.DesktopNotificationsSectionTitle,
|
||||
IgnoreMentionsDesc: translations.IgnoreMentionsDesc,
|
||||
IgnoreMentionsInputFieldData,
|
||||
mobileNotificationInputFieldData,
|
||||
MobileNotificationsSectionDesc: translations.MobileNotificationsSectionDesc,
|
||||
MobileNotificationsSectionTitle: translations.MobileNotificationsSectionTitle,
|
||||
MuteAndIgnoreSectionTitle: translations.MuteAndIgnoreSectionTitle,
|
||||
MuteChannelDesc: translations.MuteChannelDesc,
|
||||
MuteChannelInputFieldData,
|
||||
NotifyMeTitle: translations.NotifyMeTitle,
|
||||
DesktopReplyThreadsInputFieldData,
|
||||
ThreadsReplyTitle: translations.ThreadsReplyTitle,
|
||||
MobileReplyThreadsInputFieldData,
|
||||
AutoFollowThreadsTitle: translations.AutoFollowThreadsTitle,
|
||||
AutoFollowThreadsDesc: translations.AutoFollowThreadsDesc,
|
||||
AutoFollowThreadsInputFieldData,
|
||||
sameMobileSettingsDesktopInputFieldData,
|
||||
};
|
||||
|
||||
export default utils;
|
@ -0,0 +1,152 @@
|
||||
.mm-modal-generic-section-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0 0 8px;
|
||||
color: var(--center-channel-color);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__fieldset-checkbox-ctr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&__fieldset-checkbox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
gap: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__fieldset-radio {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__label-radio {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
gap: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
// Todo: add styling for react select
|
||||
//&__fieldset-react-select{
|
||||
//}
|
||||
|
||||
&__input-checkbox {
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
display: grid;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.24);
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 3px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.24);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
place-content: center;
|
||||
-webkit-transition: background-color 200ms ease-out;
|
||||
-moz-transition: background-color 200ms ease-out;
|
||||
-o-transition: background-color 200ms ease-out;
|
||||
transition: background-color 200ms ease-out;
|
||||
}
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--button-color);
|
||||
clip-path: polygon(0% 57%, 32% 88%, 100% 20%, 88% 8%, 32% 64%, 12% 45%);
|
||||
content: '';
|
||||
transform: scale(0);
|
||||
transform-origin: center center;
|
||||
transition: 200ms transform ease-in-out;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
border-color: var(--denim-button-bg);
|
||||
background: var(--denim-button-bg);
|
||||
}
|
||||
|
||||
input[type=radio] {
|
||||
display: grid;
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.24);
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 50%;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.24);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
input[type="radio"]::before {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--denim-button-bg);
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
transform: scale(0);
|
||||
transform-origin: center center;
|
||||
transition: 200ms transform ease-in-out;
|
||||
}
|
||||
|
||||
input[type="radio"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked {
|
||||
border-color: var(--denim-button-bg);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import './base_setting_item.scss';
|
||||
|
||||
export type BaseSettingItemProps = {
|
||||
title?: MessageDescriptor;
|
||||
description?: MessageDescriptor;
|
||||
};
|
||||
|
||||
type Props = BaseSettingItemProps & {
|
||||
content: JSX.Element;
|
||||
}
|
||||
|
||||
function BaseSettingItem({title, description, content}: Props): JSX.Element {
|
||||
const {formatMessage} = useIntl();
|
||||
const Title = title && (
|
||||
<h4 className='mm-modal-generic-section-item__title'>
|
||||
{formatMessage({id: title.id, defaultMessage: title.defaultMessage})}
|
||||
</h4>
|
||||
);
|
||||
|
||||
const Description = description && (
|
||||
<p className='mm-modal-generic-section-item__description'>
|
||||
{formatMessage({id: description.id, defaultMessage: description.defaultMessage})}
|
||||
</p>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='mm-modal-generic-section-item'>
|
||||
{Title}
|
||||
<div className='mm-modal-generic-section-item__content'>
|
||||
{content}
|
||||
</div>
|
||||
{Description}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BaseSettingItem;
|
||||
|
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
|
||||
import type {BaseSettingItemProps} from './base_setting_item';
|
||||
import BaseSettingItem from './base_setting_item';
|
||||
|
||||
export type FieldsetCheckbox = {
|
||||
dataTestId?: string;
|
||||
title: MessageDescriptor;
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Props = BaseSettingItemProps & {
|
||||
inputFieldData: FieldsetCheckbox;
|
||||
inputFieldValue: boolean;
|
||||
handleChange: (e: boolean) => void;
|
||||
}
|
||||
function CheckboxSettingItem({
|
||||
title,
|
||||
description,
|
||||
inputFieldData,
|
||||
inputFieldValue,
|
||||
handleChange,
|
||||
}: Props): JSX.Element {
|
||||
const content = (
|
||||
<fieldset
|
||||
key={inputFieldData.name}
|
||||
className='mm-modal-generic-section-item__fieldset-checkbox-ctr'
|
||||
>
|
||||
<label className='mm-modal-generic-section-item__fieldset-checkbox'>
|
||||
<input
|
||||
className='mm-modal-generic-section-item__input-checkbox'
|
||||
data-testid={inputFieldData.dataTestId}
|
||||
type='checkbox'
|
||||
name={inputFieldData.name}
|
||||
checked={inputFieldValue}
|
||||
onChange={(e) => handleChange(e.target.checked)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id={inputFieldData.title.id}
|
||||
defaultMessage={inputFieldData.title.defaultMessage}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</fieldset>
|
||||
);
|
||||
return (
|
||||
<BaseSettingItem
|
||||
content={content}
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CheckboxSettingItem;
|
@ -0,0 +1,84 @@
|
||||
.mm-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 20px 20px 32px;
|
||||
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||
background: none;
|
||||
gap: 8px;
|
||||
|
||||
&__title {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: var(--center-channel-color);
|
||||
font-family: 'Metropolis', serif;
|
||||
font-size: 22px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
&__vertical-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(var(--center-channel-color-rgb), 0.16);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__ctr {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__close-btn {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: unset;
|
||||
margin-left: 8px;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
padding: 18px 24px;
|
||||
|
||||
&__title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__vertical-divider {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {CloseIcon} from '@mattermost/compass-icons/components';
|
||||
import './modal_header.scss';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
handleClose?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
function ModalHeader({id, title, subtitle, handleClose}: Props) {
|
||||
return (
|
||||
<header className='mm-modal-header'>
|
||||
<h1
|
||||
id={`mm-modal-header-${id}`}
|
||||
className='mm-modal-header__title'
|
||||
tabIndex={0}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
<div className='mm-modal-header__vertical-divider'/>
|
||||
<p className='mm-modal-header__subtitle'>{subtitle}</p>
|
||||
<div
|
||||
className='mm-modal-header__ctr'
|
||||
onClick={handleClose}
|
||||
>
|
||||
<button className='style--none mm-modal-header__close-btn'>
|
||||
<CloseIcon
|
||||
size={24}
|
||||
color={'currentcolor'}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
export default ModalHeader;
|
@ -0,0 +1,50 @@
|
||||
.mm-modal-generic-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
&__info-ctr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: var(--center-channel-color);
|
||||
font-family: 'Metropolis', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import './modal_section.scss';
|
||||
|
||||
type Props = {
|
||||
title: MessageDescriptor;
|
||||
description?: MessageDescriptor;
|
||||
content: JSX.Element;
|
||||
titleSuffix?: JSX.Element;
|
||||
};
|
||||
|
||||
function ModalSection({
|
||||
title,
|
||||
description,
|
||||
content,
|
||||
titleSuffix,
|
||||
}: Props): JSX.Element {
|
||||
const {formatMessage} = useIntl();
|
||||
const titleContent = (
|
||||
<h4 className='mm-modal-generic-section__title'>
|
||||
{formatMessage({id: title.id, defaultMessage: title.defaultMessage})}
|
||||
</h4>
|
||||
);
|
||||
|
||||
const descriptionContent = description && (
|
||||
<p className='mm-modal-generic-section__description'>
|
||||
{formatMessage({id: description.id, defaultMessage: description.defaultMessage})}
|
||||
</p>
|
||||
);
|
||||
|
||||
function titleRow() {
|
||||
if (titleSuffix) {
|
||||
return (<div className='mm-modal-generic-section__row'>
|
||||
{titleContent}
|
||||
{titleSuffix}
|
||||
</div>);
|
||||
}
|
||||
return titleContent;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className='mm-modal-generic-section'>
|
||||
<div className='mm-modal-generic-section__info-ctr'>
|
||||
{titleRow()}
|
||||
{descriptionContent}
|
||||
</div>
|
||||
<div className='mm-modal-generic-section__content'>
|
||||
{content}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalSection;
|
@ -0,0 +1,35 @@
|
||||
.mm-modal-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: 8px;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
width: 184px;
|
||||
height: 32px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--denim-button-bg-rgb), 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
&__item--active {
|
||||
background: rgba(var(--denim-button-bg-rgb), 0.08);
|
||||
color: var(--denim-button-bg);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import './modal_sidebar.scss';
|
||||
|
||||
export type Tab = {
|
||||
icon: JSX.Element;
|
||||
name: string;
|
||||
uiName: string;
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
activeTab?: string;
|
||||
tabs: Tab[];
|
||||
updateTab: (name: string) => void;
|
||||
}
|
||||
|
||||
function ModalSidebar({tabs, activeTab, updateTab}: Props) {
|
||||
const handleClick = (tab: Tab, e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
updateTab(tab.name);
|
||||
(e.target as Element).closest('.settings-modal')?.classList.add('display--content');
|
||||
};
|
||||
|
||||
const tabList = tabs.map((tab) => {
|
||||
const key = `${tab.name}_li`;
|
||||
let className = 'mm-modal-sidebar__item';
|
||||
if (activeTab === tab.name) {
|
||||
className += ' mm-modal-sidebar__item--active';
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
id={`${tab.name}Li`}
|
||||
key={key}
|
||||
>
|
||||
<button
|
||||
id={`${tab.name}Button`}
|
||||
className={className}
|
||||
onClick={handleClick.bind(null, tab)}
|
||||
aria-label={tab.uiName.toLowerCase()}
|
||||
>
|
||||
{tab.icon}
|
||||
{tab.uiName}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<ul
|
||||
id='tabList'
|
||||
className='mm-modal-sidebar'
|
||||
>
|
||||
{tabList}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
export default ModalSidebar;
|
@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {BaseSettingItemProps} from './base_setting_item';
|
||||
import BaseSettingItem from './base_setting_item';
|
||||
|
||||
export type FieldsetRadio = {
|
||||
options: Array<{
|
||||
dataTestId?: string;
|
||||
title: MessageDescriptor;
|
||||
name: string;
|
||||
key: string;
|
||||
value: string;
|
||||
suffix?: JSX.Element;
|
||||
}>;
|
||||
}
|
||||
|
||||
type Props = BaseSettingItemProps & {
|
||||
inputFieldData: FieldsetRadio;
|
||||
inputFieldValue: string;
|
||||
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
function RadioSettingItem({
|
||||
title,
|
||||
description,
|
||||
inputFieldData,
|
||||
inputFieldValue,
|
||||
handleChange,
|
||||
}: Props): JSX.Element {
|
||||
const fields = inputFieldData.options.map((option) => {
|
||||
return (
|
||||
<label
|
||||
key={option.key}
|
||||
className='mm-modal-generic-section-item__label-radio'
|
||||
>
|
||||
<input
|
||||
id={option.key}
|
||||
data-testid={option.dataTestId}
|
||||
type='radio'
|
||||
name={option.name}
|
||||
checked={option.value === inputFieldValue}
|
||||
value={option.value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id={option.title.id}
|
||||
defaultMessage={option.title.defaultMessage}
|
||||
/>
|
||||
{option.suffix}
|
||||
</label>
|
||||
);
|
||||
});
|
||||
|
||||
const content = (
|
||||
<fieldset className='mm-modal-generic-section-item__fieldset-radio'>
|
||||
{[...fields]}
|
||||
</fieldset>
|
||||
);
|
||||
return (
|
||||
<BaseSettingItem
|
||||
content={content}
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default RadioSettingItem;
|
@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {ValueType} from 'react-select';
|
||||
import ReactSelect from 'react-select';
|
||||
|
||||
import type {BaseSettingItemProps} from './base_setting_item';
|
||||
import BaseSettingItem from './base_setting_item';
|
||||
|
||||
export type Option = {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type FieldsetReactSelect = {
|
||||
dataTestId?: string;
|
||||
options: Option[];
|
||||
}
|
||||
|
||||
type Props = BaseSettingItemProps & {
|
||||
inputFieldData: FieldsetReactSelect;
|
||||
inputFieldValue: Option;
|
||||
handleChange: (selected: ValueType<Option>) => void;
|
||||
}
|
||||
function ReactSelectItemCreator({
|
||||
title,
|
||||
description,
|
||||
inputFieldData,
|
||||
inputFieldValue,
|
||||
handleChange,
|
||||
}: Props): JSX.Element {
|
||||
const content = (
|
||||
<fieldset className='mm-modal-generic-section-item__fieldset-react-select'>
|
||||
<ReactSelect
|
||||
className='react-select'
|
||||
classNamePrefix='react-select'
|
||||
id='limitVisibleGMsDMs'
|
||||
options={inputFieldData.options}
|
||||
clearable={false}
|
||||
onChange={handleChange}
|
||||
value={inputFieldValue}
|
||||
isSearchable={false}
|
||||
menuPortalTarget={document.body}
|
||||
styles={reactStyles}
|
||||
/>
|
||||
</fieldset>
|
||||
);
|
||||
return (
|
||||
<BaseSettingItem
|
||||
content={content}
|
||||
title={title}
|
||||
description={description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReactSelectItemCreator;
|
||||
|
||||
const reactStyles = {
|
||||
menuPortal: (provided: React.CSSProperties) => ({
|
||||
...provided,
|
||||
zIndex: 9999,
|
||||
}),
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
.mm-save-changes-panel {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
display: flex;
|
||||
width: calc(100% - 232px - 40px);
|
||||
align-items: center;
|
||||
padding: 18px;
|
||||
background-color: var(--dnd-indicator);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--elevation-3);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
|
||||
&__message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
color: var(--sidebar-text);
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__btn-ctr {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__cancel-btn {
|
||||
display: flex;
|
||||
width: 64px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-radius: 4px;
|
||||
color: var(--sidebar-text);
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
gap: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
&__save-btn {
|
||||
display: flex;
|
||||
width: 59px;
|
||||
height: 32px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
background: var(--sidebar-text);
|
||||
border-radius: 4px;
|
||||
color: var(--denim-button-bg);
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
gap: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import './save_changes_panel.scss';
|
||||
import {AlertCircleOutlineIcon} from '@mattermost/compass-icons/components';
|
||||
|
||||
type Props = {
|
||||
handleSubmit: () => void;
|
||||
handleCancel: () => void;
|
||||
}
|
||||
function SaveChangesPanel({handleSubmit, handleCancel}: Props) {
|
||||
return (
|
||||
<div className='mm-save-changes-panel'>
|
||||
<p className='mm-save-changes-panel__message'>
|
||||
<AlertCircleOutlineIcon
|
||||
size={18}
|
||||
color={'currentcolor'}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='saveChangesPanel.message'
|
||||
defaultMessage='You have unsaved changes'
|
||||
/>
|
||||
</p>
|
||||
<div className='mm-save-changes-panel__btn-ctr'>
|
||||
<button
|
||||
className='mm-save-changes-panel__cancel-btn'
|
||||
onClick={handleCancel}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='saveChangesPanel.cancel'
|
||||
defaultMessage='Undo'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className='mm-save-changes-panel__save-btn'
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='saveChangesPanel.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default SaveChangesPanel;
|
@ -2998,40 +2998,34 @@
|
||||
"channel_modal.type.private.title": "Private Channel",
|
||||
"channel_modal.type.public.description": "Anyone can join",
|
||||
"channel_modal.type.public.title": "Public Channel",
|
||||
"channel_notifications.allActivity": "For all activity {isDefault}",
|
||||
"channel_notifications.channelAutoFollowThreads": "Auto-follow all new threads in this channel",
|
||||
"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.",
|
||||
"channel_notifications.ignoreChannelMentions.off.title": "Off",
|
||||
"channel_notifications.ignoreChannelMentions.on.title": "On",
|
||||
"channel_notifications.alertBanner.description": "All other notification preferences for this channel are disabled",
|
||||
"channel_notifications.alertBanner.title": "This channel is muted",
|
||||
"channel_notifications.autoFollowThreadsDesc": "When enabled, all new replies in this channel will be automatically followed and will appear in your Threads view.",
|
||||
"channel_notifications.autoFollowThreadsTitle": "Follow all threads in this channel",
|
||||
"channel_notifications.checkbox.autoFollowThreadsTitle": "Automatically follow threads in this channel",
|
||||
"channel_notifications.checkbox.sameMobileSettingsDesktop": "Use the same notification settings as desktop",
|
||||
"channel_notifications.checkbox.threadsReplyTitle": "Notify me about replies to threads I’m following",
|
||||
"channel_notifications.default": "(default)",
|
||||
"channel_notifications.desktopNotificationAllLabel": "All new messages",
|
||||
"channel_notifications.desktopNotificationMentionLabel": "Mentions, direct messages, and keywords only",
|
||||
"channel_notifications.desktopNotificationNothingLabel": "Nothing",
|
||||
"channel_notifications.desktopNotificationsDesc": "Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.",
|
||||
"channel_notifications.desktopNotificationsTitle": "Desktop Notifications",
|
||||
"channel_notifications.ignoreMentionsDesc": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel",
|
||||
"channel_notifications.ignoreMentionsTitle": "Ignore mentions for @channel, @here and @all",
|
||||
"channel_notifications.levels.all": "All",
|
||||
"channel_notifications.levels.default": "Default",
|
||||
"channel_notifications.levels.mention": "Mention",
|
||||
"channel_notifications.levels.none": "None",
|
||||
"channel_notifications.muteChannel.help": "Muting turns off desktop, email and push notifications for this channel. The channel will not be marked as unread unless you're mentioned.",
|
||||
"channel_notifications.muteChannel.off.title": "Off",
|
||||
"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 {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": "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_notifications.MobileNotificationAllLabel": "All new messages",
|
||||
"channel_notifications.MobileNotificationMentionLabel": "Mentions, direct messages, and keywords only",
|
||||
"channel_notifications.MobileNotificationNothingLabel": "Nothing",
|
||||
"channel_notifications.mobileNotificationsDesc": "Notification alerts are pushed to your mobile device when there is activity in Mattermost.",
|
||||
"channel_notifications.mobileNotificationsTitle": "Mobile Notifications",
|
||||
"channel_notifications.muteAndIgnore": "Mute or ignore",
|
||||
"channel_notifications.muteChannelDesc": "Turns off notifications for this channel. You’ll still see badges if you’re mentioned.",
|
||||
"channel_notifications.muteChannelTitle": "Mute channel",
|
||||
"channel_notifications.NotifyMeTitle": "Notify me about…",
|
||||
"channel_notifications.preferences": "Notification Preferences",
|
||||
"channel_notifications.resetToDefault": "Reset to default",
|
||||
"channel_notifications.ThreadsReplyTitle": "Thread reply notifications",
|
||||
"channel_select.placeholder": "--- Select a channel ---",
|
||||
"channel_switch_modal.deactivated": "Deactivated",
|
||||
"channel_toggle_button.private": "Private",
|
||||
@ -3564,6 +3558,8 @@
|
||||
"general_tab.teamNameRestrictions": "Team Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description.",
|
||||
"general_tab.title": "General Settings",
|
||||
"general_tab.yes": "Yes",
|
||||
"generic_btn.cancel": "Cancel",
|
||||
"generic_btn.save": "Save",
|
||||
"generic_icons.add": "Add Icon",
|
||||
"generic_icons.add-mail": "Add Mail Icon",
|
||||
"generic_icons.add-reaction": "Add Reaction Icon",
|
||||
@ -4587,6 +4583,9 @@
|
||||
"rhs_thread.toast.newReplies": "New Replies",
|
||||
"save_button.save": "Save",
|
||||
"save_button.saving": "Saving",
|
||||
"saveChangesPanel.cancel": "Undo",
|
||||
"saveChangesPanel.message": "You have unsaved changes",
|
||||
"saveChangesPanel.save": "Save",
|
||||
"search_bar.files_tab": "Files",
|
||||
"search_bar.messages_tab": "Messages",
|
||||
"search_bar.search": "Search",
|
||||
|
@ -17,12 +17,12 @@ import type {Scheme} from '@mattermost/types/schemes';
|
||||
import type {Team, TeamMembership} from '@mattermost/types/teams';
|
||||
import type {UserProfile, UserNotifyProps} from '@mattermost/types/users';
|
||||
|
||||
import General from 'mattermost-redux/constants/general';
|
||||
import {generateId} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
export const DEFAULT_SERVER = 'http://localhost:8065';
|
||||
const PASSWORD = 'password1';
|
||||
|
||||
import General from 'mattermost-redux/constants/general';
|
||||
import {generateId} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
const {DEFAULT_LOCALE} = General;
|
||||
|
||||
class TestHelper {
|
||||
@ -452,6 +452,8 @@ class TestHelper {
|
||||
return {
|
||||
desktop: 'default',
|
||||
desktop_sound: 'off',
|
||||
desktop_threads: 'default',
|
||||
push_threads: 'default',
|
||||
email: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
|
@ -20,12 +20,14 @@ export type ChannelStats = {
|
||||
};
|
||||
|
||||
export type ChannelNotifyProps = {
|
||||
desktop_threads: 'default' | 'all' | 'mention' | 'none';
|
||||
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';
|
||||
push_threads: 'default' | 'all' | 'mention' | 'none';
|
||||
ignore_channel_mentions: 'default' | 'off' | 'on';
|
||||
channel_auto_follow_threads: 'off' | 'on';
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user