[MM-56695] Consolidate desktop and mobile Notifications to one and a new desktop notification sound settings (#26198)

Этот коммит содержится в:
M-ZubairAhmed
2024-04-08 18:59:05 +00:00
коммит произвёл GitHub
родитель a81ee87832
Коммит 9698cfcc19
42 изменённых файлов: 2395 добавлений и 2780 удалений

Просмотреть файл

@@ -11,7 +11,7 @@ exports[`components/SettingItemMax should match snapshot 1`] = `
title
</h4>
<div
className="col-sm-12"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -72,7 +72,7 @@ exports[`components/SettingItemMax should match snapshot, on clientError 1`] = `
title
</h4>
<div
className="col-sm-12"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -143,7 +143,7 @@ exports[`components/SettingItemMax should match snapshot, on serverError 1`] = `
title
</h4>
<div
className="col-sm-12"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -214,7 +214,7 @@ exports[`components/SettingItemMax should match snapshot, with new saveTextButto
title
</h4>
<div
className="col-sm-12"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -270,7 +270,7 @@ exports[`components/SettingItemMax should match snapshot, without submit 1`] = `
title
</h4>
<div
className="col-sm-12"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"

Просмотреть файл

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React from 'react';
import type {ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
@@ -44,7 +45,7 @@ type Props = {
submitExtra?: ReactNode;
saving?: boolean;
title?: ReactNode;
width?: string;
isFullWidth?: boolean;
cancelButtonText?: ReactNode;
shiftEnter?: boolean;
saveButtonText?: string;
@@ -177,14 +178,6 @@ export default class SettingItemMax extends React.PureComponent<Props> {
}
const inputs = this.props.inputs;
let widthClass;
if (this.props.width === 'full') {
widthClass = 'col-sm-12';
} else if (this.props.width === 'medium') {
widthClass = 'col-sm-10 col-sm-offset-2';
} else {
widthClass = 'col-sm-9 col-sm-offset-3';
}
let title;
if (this.props.title) {
@@ -231,7 +224,12 @@ export default class SettingItemMax extends React.PureComponent<Props> {
className={`section-max form-horizontal ${this.props.containerStyle}`}
>
{title}
<div className={widthClass}>
<div
className={classNames('sectionContent', {
'col-sm-12': this.props.isFullWidth,
'col-sm-10 col-sm-offset-2': !this.props.isFullWidth,
})}
>
<div
tabIndex={-1}
ref={this.settingList}

Просмотреть файл

@@ -256,7 +256,6 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
defaultMessage='Language'
/>
}
width='medium'
submit={this.changeLanguage}
saving={this.state.isSaving}
inputs={[input]}

Просмотреть файл

@@ -245,7 +245,6 @@ export default class ManageTimezones extends React.PureComponent<Props, State> {
/>
}
containerStyle='timezone-container'
width='medium'
submit={this.changeTimezone}
saving={this.state.isSaving}
inputs={inputs}

Просмотреть файл

@@ -309,7 +309,7 @@ export default class ThemeSetting extends React.PureComponent<Props, State> {
disableEnterSubmit={true}
saving={this.state.isSaving}
serverError={serverError}
width='full'
isFullWidth={true}
updateSection={this.handleUpdateSection}
/>
);

Просмотреть файл

@@ -34,7 +34,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -88,15 +88,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -107,9 +107,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -125,7 +160,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -158,15 +193,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -177,42 +212,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>
@@ -228,15 +228,15 @@ Object {
>
<h4
class="section-min__title"
id="keysWithHighlightTitle"
id="keywordsAndHighlightTitle"
>
Keywords That Get Highlighted (Without Notifications)
Keywords that get highlighted (without notifications)
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithHighlightTitle keysWithHighlightEdit"
aria-labelledby="keywordsAndHighlightTitle keywordsAndHighlightEdit"
class="color--link style--none section-min__edit"
id="keysWithHighlightEdit"
id="keywordsAndHighlightEdit"
>
<i
class="icon-pencil-outline"
@@ -247,7 +247,7 @@ Object {
</div>
<div
class="section-min__describe"
id="keysWithHighlightDesc"
id="keywordsAndHighlightDesc"
>
None
</div>
@@ -292,7 +292,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -346,15 +346,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -365,9 +365,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -383,7 +418,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -416,15 +451,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -435,42 +470,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>
@@ -486,15 +486,15 @@ Object {
>
<h4
class="section-min__title"
id="keysWithHighlightTitle"
id="keywordsAndHighlightTitle"
>
Keywords That Get Highlighted (Without Notifications)
Keywords that get highlighted (without notifications)
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithHighlightTitle keysWithHighlightEdit"
aria-labelledby="keywordsAndHighlightTitle keywordsAndHighlightEdit"
class="color--link style--none section-min__edit"
id="keysWithHighlightEdit"
id="keywordsAndHighlightEdit"
>
<i
class="icon-pencil-outline"
@@ -505,7 +505,7 @@ Object {
</div>
<div
class="section-min__describe"
id="keysWithHighlightDesc"
id="keywordsAndHighlightDesc"
>
None
</div>
@@ -609,7 +609,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -663,15 +663,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -682,9 +682,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -700,7 +735,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -733,15 +768,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -752,42 +787,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>
@@ -806,9 +806,9 @@ Object {
>
<h4
class="section-min__title isDisabled"
id="keysWithHighlightTitle"
id="keywordsAndHighlightTitle"
>
Keywords That Get Highlighted (Without Notifications)
Keywords that get highlighted (without notifications)
</h4>
<span
class="RestrictedIndicator__icon-tooltip-container"
@@ -828,7 +828,7 @@ Object {
</div>
<div
class="section-min__describe isDisabled"
id="keysWithHighlightDesc"
id="keywordsAndHighlightDesc"
>
None
</div>
@@ -870,7 +870,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -924,15 +924,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -943,9 +943,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -961,7 +996,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -994,15 +1029,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -1013,42 +1048,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>
@@ -1067,9 +1067,9 @@ Object {
>
<h4
class="section-min__title isDisabled"
id="keysWithHighlightTitle"
id="keywordsAndHighlightTitle"
>
Keywords That Get Highlighted (Without Notifications)
Keywords that get highlighted (without notifications)
</h4>
<span
class="RestrictedIndicator__icon-tooltip-container"
@@ -1089,7 +1089,7 @@ Object {
</div>
<div
class="section-min__describe isDisabled"
id="keysWithHighlightDesc"
id="keywordsAndHighlightDesc"
>
None
</div>
@@ -1190,7 +1190,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -1244,15 +1244,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -1263,9 +1263,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -1281,7 +1316,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -1314,15 +1349,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -1333,42 +1368,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>
@@ -1413,7 +1413,7 @@ Object {
class="fa fa-angle-left"
/>
</div>
Notification Settings
Notification settings
</h4>
</div>
<div
@@ -1467,15 +1467,15 @@ Object {
>
<h4
class="section-min__title"
id="desktopTitle"
id="desktopAndMobileTitle"
>
Desktop Notifications
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopTitle desktopEdit"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopEdit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
@@ -1486,9 +1486,44 @@ Object {
</div>
<div
class="section-min__describe"
id="desktopDesc"
id="desktopAndMobileDesc"
>
For all activity, without sound
Configure desktop and mobile settings
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopNotificationSoundTitle"
>
Desktop notification sounds
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopNotificationSoundTitle desktopNotificationSoundEdit"
class="color--link style--none section-min__edit"
id="desktopNotificationSoundEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopNotificationSoundDesc"
>
No sound
</div>
</div>
<div
@@ -1504,7 +1539,7 @@ Object {
class="section-min__title"
id="emailTitle"
>
Email Notifications
Email notifications
</h4>
<button
aria-expanded="false"
@@ -1537,15 +1572,15 @@ Object {
>
<h4
class="section-min__title"
id="pushTitle"
id="keywordsAndMentionsTitle"
>
Mobile Push Notifications
Keywords that trigger notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="pushTitle pushEdit"
aria-labelledby="keywordsAndMentionsTitle keywordsAndMentionsEdit"
class="color--link style--none section-min__edit"
id="pushEdit"
id="keywordsAndMentionsEdit"
>
<i
class="icon-pencil-outline"
@@ -1556,42 +1591,7 @@ Object {
</div>
<div
class="section-min__describe"
id="pushDesc"
>
Never
</div>
</div>
<div
class="divider-light"
/>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="keysWithNotificationTitle"
>
Keywords That Trigger Notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="keysWithNotificationTitle keysWithNotificationEdit"
class="color--link style--none section-min__edit"
id="keysWithNotificationEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="keysWithNotificationDesc"
id="keywordsAndMentionsDesc"
>
"@some-user"
</div>

Просмотреть файл

@@ -0,0 +1,359 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DesktopNotificationSettings should match snapshot, on max setting 1`] = `
<div>
<section
class="section-max form-horizontal "
>
<h4
class="col-sm-12 section-title"
id="settingTitle"
>
Desktop and mobile notifications
</h4>
<div
class="sectionContent col-sm-10 col-sm-offset-2"
>
<div
class="setting-list"
tabindex="-1"
>
<div
class="setting-list-item"
>
<fieldset
id="sendDesktopNotificationsSection"
>
<legend
class="form-legend"
>
Send notifications for:
</legend>
<div
class="radio"
>
<label>
<input
type="radio"
value="all"
/>
All new messages
</label>
</div>
<div
class="radio"
>
<label>
<input
type="radio"
value="mention"
/>
Mentions, direct messages, and group messages
</label>
</div>
<div
class="radio"
>
<label>
<input
type="radio"
value="none"
/>
Nothing
</label>
</div>
</fieldset>
<br />
<div
class="checkbox single-checkbox"
>
<label>
<input
type="checkbox"
/>
Notify me about replies to threads I'm following
</label>
</div>
<hr />
<div
class="checkbox single-checkbox"
>
<label>
<input
type="checkbox"
/>
Use different settings for my mobile devices
</label>
</div>
<br />
<label
class="singleSelectLabel"
for="pushMobileNotificationSelectInput"
id="pushMobileNotificationsLabel"
>
Trigger mobile notifications when I am:
</label>
<div
class="react-select singleSelect css-2b097c-container"
>
<div
class="react-select__control css-yk16xz-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
>
Offline
</div>
<input
class="css-62g3xt-dummyInput"
id="pushMobileNotificationSelectInput"
readonly=""
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
<div
class="setting-list-item"
>
<hr />
<button
class="btn btn-primary "
data-testid="saveSetting"
id="saveSetting"
type="submit"
>
<span>
Save
</span>
</button>
<button
class="btn btn-tertiary"
id="cancelSetting"
>
Cancel
</button>
</div>
</div>
</div>
</section>
</div>
`;
exports[`DesktopNotificationSettings should match snapshot, on min setting 1`] = `
<div>
<div
class="section-min"
>
<div
class="secion-min__header"
>
<h4
class="section-min__title"
id="desktopAndMobileTitle"
>
Desktop and mobile notifications
</h4>
<button
aria-expanded="false"
aria-labelledby="desktopAndMobileTitle desktopAndMobileEdit"
class="color--link style--none section-min__edit"
id="desktopAndMobileEdit"
>
<i
class="icon-pencil-outline"
title="Edit Icon"
/>
Edit
</button>
</div>
<div
class="section-min__describe"
id="desktopAndMobileDesc"
>
Configure desktop and mobile settings
</div>
</div>
</div>
`;
exports[`DesktopNotificationSettings should not show desktop thread notification checkbox when collapsed threads are not enabled 1`] = `
<div>
<section
class="section-max form-horizontal "
>
<h4
class="col-sm-12 section-title"
id="settingTitle"
>
Desktop and mobile notifications
</h4>
<div
class="sectionContent col-sm-10 col-sm-offset-2"
>
<div
class="setting-list"
tabindex="-1"
>
<div
class="setting-list-item"
>
<fieldset
id="sendDesktopNotificationsSection"
>
<legend
class="form-legend"
>
Send notifications for:
</legend>
<div
class="radio"
>
<label>
<input
type="radio"
value="all"
/>
All new messages
</label>
</div>
<div
class="radio"
>
<label>
<input
type="radio"
value="mention"
/>
Mentions, direct messages, and group messages
</label>
</div>
<div
class="radio"
>
<label>
<input
type="radio"
value="none"
/>
Nothing
</label>
</div>
</fieldset>
<hr />
<div
class="checkbox single-checkbox"
>
<label>
<input
type="checkbox"
/>
Use different settings for my mobile devices
</label>
</div>
<br />
<label
class="singleSelectLabel"
for="pushMobileNotificationSelectInput"
id="pushMobileNotificationsLabel"
>
Trigger mobile notifications when I am:
</label>
<div
class="react-select singleSelect css-2b097c-container"
>
<div
class="react-select__control css-yk16xz-control"
>
<div
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
>
<div
class="react-select__single-value css-1uccc91-singleValue"
>
Offline
</div>
<input
class="css-62g3xt-dummyInput"
id="pushMobileNotificationSelectInput"
readonly=""
tabindex="0"
value=""
/>
</div>
<div
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
>
<div
aria-hidden="true"
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-6q0nyr-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
<div
class="setting-list-item"
>
<hr />
<button
class="btn btn-primary "
data-testid="saveSetting"
id="saveSetting"
type="submit"
>
<span>
Save
</span>
</button>
<button
class="btn btn-tertiary"
id="cancelSetting"
>
Cancel
</button>
</div>
</div>
</div>
</section>
</div>
`;

Просмотреть файл

@@ -0,0 +1,312 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import Constants, {NotificationLevels} from 'utils/constants';
import type {SelectOption, Props} from './index';
import DesktopNotificationSettings, {
shouldShowDesktopThreadsSection,
shouldShowMobileThreadsSection,
getValueOfSendMobileNotificationForSelect,
getValueOfSendMobileNotificationWhenSelect,
shouldShowTriggerMobileNotificationsSection,
} from './index';
const validNotificationLevels = Object.values(NotificationLevels);
describe('DesktopNotificationSettings', () => {
const baseProps: Props = {
active: true,
updateSection: jest.fn(),
onSubmit: jest.fn(),
onCancel: jest.fn(),
saving: false,
error: '',
setParentState: jest.fn(),
areAllSectionsInactive: false,
isCollapsedThreadsEnabled: true,
desktopActivity: NotificationLevels.DEFAULT,
pushActivity: NotificationLevels.DEFAULT,
pushStatus: Constants.UserStatuses.OFFLINE,
desktopThreads: NotificationLevels.DEFAULT,
pushThreads: NotificationLevels.DEFAULT,
sendPushNotifications: true,
desktopAndMobileSettingsDifferent: false,
};
test('should match snapshot, on max setting', () => {
const {container} = renderWithContext(
<DesktopNotificationSettings {...baseProps}/>,
);
expect(container).toMatchSnapshot();
});
test('should match snapshot, on min setting', () => {
const props = {...baseProps, active: false};
const {container} = renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(container).toMatchSnapshot();
});
test('should not show desktop thread notification checkbox when collapsed threads are not enabled', () => {
const props = {...baseProps, isCollapsedThreadsEnabled: false};
const {container} = renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull();
expect(container).toMatchSnapshot();
});
test('should not show desktop thread notification checkbox when desktop is all', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.ALL};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull();
});
test('should not show desktop thread notification checkbox when desktop is none', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.NONE};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull();
});
test('should show desktop thread notification checkbox when desktop is mention', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.MENTION};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.getByText('Notify me about replies to threads I\'m following')).toBeInTheDocument();
});
test('should show mobile notification when checkbox for use different mobile settings is checked', () => {
const props = {...baseProps, desktopAndMobileSettingsDifferent: true};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.getByText('Send mobile notifications for:')).toBeInTheDocument();
});
test('should not show mobile notification when checkbox for use different mobile settings is not checked', () => {
const props = {...baseProps, desktopAndMobileSettingsDifferent: false};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.queryByText('Send mobile notifications for:')).toBeNull();
});
test('should shown notify me about mobile threads when mobile setting is mention and desktop setting is anything', () => {
const {rerender} = renderWithContext(
<DesktopNotificationSettings
{...baseProps}
desktopAndMobileSettingsDifferent={true}
pushActivity={NotificationLevels.MENTION}
desktopActivity={NotificationLevels.NONE}
/>,
);
expect(screen.getByText('Notify me on mobile about replies to threads I\'m following')).toBeInTheDocument();
rerender(
<DesktopNotificationSettings
{...baseProps}
desktopAndMobileSettingsDifferent={true}
pushActivity={NotificationLevels.MENTION}
desktopActivity={NotificationLevels.ALL}
/>,
);
expect(screen.getByText('Notify me on mobile about replies to threads I\'m following')).toBeInTheDocument();
});
test('should not show notify me about mobile threads when mobile setting is anything other than mention', () => {
const {rerender} = renderWithContext(
<DesktopNotificationSettings
{...baseProps}
desktopAndMobileSettingsDifferent={true}
pushActivity={NotificationLevels.NONE}
desktopActivity={NotificationLevels.NONE}
/>,
);
expect(screen.queryByText('Notify me on mobile about replies to threads I\'m following')).toBeNull();
rerender(
<DesktopNotificationSettings
{...baseProps}
desktopAndMobileSettingsDifferent={true}
pushActivity={NotificationLevels.ALL}
desktopActivity={NotificationLevels.ALL}
/>,
);
expect(screen.queryByText('Notify me on mobile about replies to threads I\'m following')).toBeNull();
});
test('should show trigger mobile notifications section when desktop setting is mention', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.MENTION, desktopAndMobileSettingsDifferent: false};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.getByText('Trigger mobile notifications when I am:')).toBeInTheDocument();
});
test('should not show trigger mobile notifications section when desktop setting is none', () => {
const props = {...baseProps, desktopActivity: NotificationLevels.NONE, desktopAndMobileSettingsDifferent: false};
renderWithContext(
<DesktopNotificationSettings {...props}/>,
);
expect(screen.queryByText('Trigger mobile notifications when I am:')).toBeNull();
});
});
describe('shouldShowDesktopThreadsSection', () => {
test('should not show when collapsed threads are not enabled', () => {
expect(shouldShowDesktopThreadsSection(false, 'hello' as any)).toBe(false);
});
test('should not show when desktop setting is either none or all', () => {
expect(shouldShowDesktopThreadsSection(true, NotificationLevels.NONE)).toBe(false);
expect(shouldShowDesktopThreadsSection(true, NotificationLevels.ALL)).toBe(false);
});
test('should show when desktop setting is mention', () => {
expect(shouldShowDesktopThreadsSection(true, NotificationLevels.MENTION)).toBe(true);
expect(shouldShowDesktopThreadsSection(true, NotificationLevels.DEFAULT)).toBe(true);
});
test('should show by default when desktop setting is undefined', () => {
expect(shouldShowDesktopThreadsSection(true, undefined as any)).toBe(true);
});
});
describe('shouldShowMobileThreadsSection', () => {
test('should return false if sendPushNotifications is false', () => {
const result = shouldShowMobileThreadsSection(false, true, true, 'any' as any);
expect(result).toBe(false);
});
test('should return false if isCollapsedThreadsEnabled is false', () => {
const result = shouldShowMobileThreadsSection(true, false, true, 'any' as any);
expect(result).toBe(false);
});
test('should return false if desktop and mobile settings are same', () => {
const result = shouldShowMobileThreadsSection(true, true, false, 'any' as any);
expect(result).toBe(false);
});
test('should return false if pushActivity is all', () => {
const result = shouldShowMobileThreadsSection(true, true, true, NotificationLevels.ALL);
expect(result).toBe(false);
});
test('should return false if pushActivity is none', () => {
const result = shouldShowMobileThreadsSection(true, true, true, NotificationLevels.NONE);
expect(result).toBe(false);
});
});
describe('getValueOfSendMobileNotificationForSelect', () => {
test('should return the middle option when input is undefined', () => {
expect(getValueOfSendMobileNotificationForSelect(undefined as any)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationForSelect(undefined as any) as SelectOption;
expect(result.value).toBe(NotificationLevels.MENTION);
});
test('should return the middle option when input is not a valid option', () => {
expect(getValueOfSendMobileNotificationForSelect('invalid' as any)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationForSelect('invalid' as any) as SelectOption;
expect(result.value).toBe(NotificationLevels.MENTION);
});
test('should return the same option when input is a valid option', () => {
validNotificationLevels.
filter((level) => level !== NotificationLevels.DEFAULT).
forEach((level) => {
expect(getValueOfSendMobileNotificationForSelect(level)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationForSelect(level) as SelectOption;
expect(result.value).toBe(level);
});
});
});
describe('shouldShowTriggerMobileNotificationsSection', () => {
test('should not show when push notifications are off', () => {
expect(shouldShowTriggerMobileNotificationsSection(false, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(false);
});
test('should show if either of desktop or mobile settings are not defined', () => {
expect(shouldShowTriggerMobileNotificationsSection(true, undefined as any, NotificationLevels.ALL, true)).toBe(true);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, undefined as any, true)).toBe(true);
});
test('should show if desktop is either mention or all for same mobile setting', () => {
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.MENTION, false)).toBe(true);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.ALL, false)).toBe(true);
});
test('should not show if desktop is none for same mobile setting', () => {
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.NONE, false)).toBe(false);
});
test('should show for any desktop setting if mobile setting is mention', () => {
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.MENTION, true)).toBe(true);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.MENTION, true)).toBe(true);
});
test('should not show for any desktop setting if mobile setting is none', () => {
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(false);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.NONE, true)).toBe(false);
expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(false);
});
});
describe('getValueOfSendMobileNotificationWhenSelect', () => {
test('When input is undefined it should return the last option', () => {
expect(getValueOfSendMobileNotificationWhenSelect(undefined)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationWhenSelect(undefined) as SelectOption;
expect(result.value).toBe(Constants.UserStatuses.OFFLINE);
});
test('when input is defined but is not a valid option it should return the last option', () => {
// We are purposely testing with an invalid value hence the 'any'
expect(getValueOfSendMobileNotificationWhenSelect('invalid' as any)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationWhenSelect('invalid' as any) as SelectOption;
expect(result.value).toBe(Constants.UserStatuses.OFFLINE);
});
test('When input is a valid option it should return the same option', () => {
expect(getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.ONLINE)).not.toBeUndefined();
const result = getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.ONLINE) as SelectOption;
expect(result.value).toBe(Constants.UserStatuses.ONLINE);
expect(getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.AWAY)).not.toBeUndefined();
const result2 = getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.AWAY) as SelectOption;
expect(result2.value).toBe(Constants.UserStatuses.AWAY);
});
});

Просмотреть файл

@@ -0,0 +1,582 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {Fragment, useCallback, useEffect, useMemo, useRef, memo} from 'react';
import type {ChangeEvent, ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
import ReactSelect from 'react-select';
import type {ValueType, OptionsType} from 'react-select';
import type {UserNotifyProps} from '@mattermost/types/users';
import SettingItemMax from 'components/setting_item_max';
import SettingItemMin from 'components/setting_item_min';
import type SettingItemMinComponent from 'components/setting_item_min';
import Constants, {NotificationLevels, UserSettingsNotificationSections} from 'utils/constants';
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
export type SelectOption = {
label: ReactNode;
value: string;
};
export type Props = {
active: boolean;
updateSection: (section: string) => void;
onSubmit: () => void;
onCancel: () => void;
saving: boolean;
error: string;
setParentState: (key: string, value: string | boolean) => void;
areAllSectionsInactive: boolean;
isCollapsedThreadsEnabled: boolean;
desktopActivity: UserNotifyProps['desktop'];
sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'];
pushActivity: UserNotifyProps['push'];
pushStatus: UserNotifyProps['push_status'];
desktopThreads: UserNotifyProps['desktop_threads'];
pushThreads: UserNotifyProps['push_threads'];
desktopAndMobileSettingsDifferent: boolean;
};
function DesktopAndMobileNotificationSettings({
active,
updateSection,
onSubmit,
onCancel,
saving,
error,
setParentState,
areAllSectionsInactive,
isCollapsedThreadsEnabled,
desktopActivity,
sendPushNotifications,
pushActivity,
pushStatus,
desktopThreads,
pushThreads,
desktopAndMobileSettingsDifferent,
}: Props) {
const editButtonRef = useRef<SettingItemMinComponent>(null);
const previousActiveRef = useRef(active);
// Focus back on the edit button, after this section was closed after it was opened
useEffect(() => {
if (previousActiveRef.current && !active && areAllSectionsInactive) {
editButtonRef.current?.focus();
}
previousActiveRef.current = active;
}, [active, areAllSectionsInactive]);
const handleChangeForSendDesktopNotificationsRadio = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setParentState('desktopActivity', value);
}, [setParentState]);
const handleChangeForDesktopThreadsCheckbox = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION;
setParentState('desktopThreads', value);
}, [setParentState]);
const handleChangeForDifferentMobileNotificationsCheckbox = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked;
setParentState('desktopAndMobileSettingsDifferent', value);
}, [setParentState]);
const handleChangeForSendMobileNotificationsSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
if (selectedOption && 'value' in selectedOption) {
setParentState('pushActivity', selectedOption.value);
}
}, [setParentState]);
const handleChangeForMobileThreadsCheckbox = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION;
setParentState('pushThreads', value);
}, [setParentState]);
const handleChangeForTriggerMobileNotificationsSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
if (selectedOption && 'value' in selectedOption) {
setParentState('pushStatus', selectedOption.value);
}
}, [setParentState]);
const maximizedSettingsInputs = useMemo(() => {
const maximizedSettingInputs = [];
const sendDesktopNotificationsSection = (
<fieldset
id='sendDesktopNotificationsSection'
key='sendDesktopNotificationsSection'
>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.sendDesktopNotificationFor'
defaultMessage='Send notifications for:'
/>
</legend>
{optionsOfSendNotifications.map((optionOfSendNotifications) => (
<div
key={optionOfSendNotifications.value}
className='radio'
>
<label>
<input
type='radio'
checked={desktopActivity === optionOfSendNotifications.value}
value={optionOfSendNotifications.value}
onChange={handleChangeForSendDesktopNotificationsRadio}
/>
{optionOfSendNotifications.label}
</label>
</div>
))}
</fieldset>
);
maximizedSettingInputs.push(sendDesktopNotificationsSection);
if (shouldShowDesktopThreadsSection(isCollapsedThreadsEnabled, desktopActivity)) {
const desktopThreadNotificationSection = (
<Fragment key='desktopThreadNotificationSection'>
<br/>
<div className='checkbox single-checkbox'>
<label>
<input
type='checkbox'
checked={desktopThreads === NotificationLevels.ALL}
onChange={handleChangeForDesktopThreadsCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.notifyForDesktopthreads'
defaultMessage={'Notify me about replies to threads I\'m following'}
/>
</label>
</div>
</Fragment>
);
maximizedSettingInputs.push(desktopThreadNotificationSection);
}
if (sendPushNotifications) {
const differentMobileNotificationsSection = (
<Fragment key='differentMobileNotificationsSection'>
<hr/>
<div className='checkbox single-checkbox'>
<label>
<input
type='checkbox'
checked={desktopAndMobileSettingsDifferent}
onChange={handleChangeForDifferentMobileNotificationsCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.differentMobileNotificationsTitle'
defaultMessage='Use different settings for my mobile devices'
/>
</label>
</div>
</Fragment>
);
maximizedSettingInputs.push(differentMobileNotificationsSection);
}
if (shouldShowSendMobileNotificationsSection(sendPushNotifications, desktopAndMobileSettingsDifferent)) {
const sendMobileNotificationsSection = (
<React.Fragment key='sendMobileNotificationsSection'>
<br/>
<label
id='sendMobileNotificationsLabel'
htmlFor='sendMobileNotificationsSelectInput'
className='singleSelectLabel'
>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.sendMobileNotificationsFor'
defaultMessage='Send mobile notifications for:'
/>
</label>
<ReactSelect
inputId='sendMobileNotificationsSelectInput'
aria-labelledby='sendMobileNotificationsLabel'
className='react-select singleSelect'
classNamePrefix='react-select'
options={optionsOfSendNotifications}
clearable={false}
isClearable={false}
isSearchable={false}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
value={getValueOfSendMobileNotificationForSelect(pushActivity)}
onChange={handleChangeForSendMobileNotificationsSelect}
/>
</React.Fragment>
);
maximizedSettingInputs.push(sendMobileNotificationsSection);
}
if (shouldShowMobileThreadsSection(sendPushNotifications, isCollapsedThreadsEnabled, desktopAndMobileSettingsDifferent, pushActivity)) {
const threadNotificationSection = (
<Fragment key='threadNotificationSection'>
<br/>
<div className='checkbox single-checkbox'>
<label>
<input
type='checkbox'
checked={pushThreads === NotificationLevels.ALL}
onChange={handleChangeForMobileThreadsCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.notifyForMobilethreads'
defaultMessage={'Notify me on mobile about replies to threads I\'m following'}
/>
</label>
</div>
</Fragment>
);
maximizedSettingInputs.push(threadNotificationSection);
}
if (shouldShowTriggerMobileNotificationsSection(sendPushNotifications, desktopActivity, pushActivity, desktopAndMobileSettingsDifferent)) {
const triggerMobileNotificationsSection = (
<React.Fragment key='triggerMobileNotificationsSection'>
<br/>
<label
id='pushMobileNotificationsLabel'
htmlFor='pushMobileNotificationSelectInput'
className='singleSelectLabel'
>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.pushNotification'
defaultMessage='Trigger mobile notifications when I am:'
/>
</label>
<ReactSelect
inputId='pushMobileNotificationSelectInput'
aria-labelledby='pushMobileNotificationsLabel'
className='react-select singleSelect'
classNamePrefix='react-select'
options={optionsOfSendMobileNotificationsWhenSelect}
clearable={false}
isClearable={false}
isSearchable={false}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
value={getValueOfSendMobileNotificationWhenSelect(pushStatus)}
onChange={handleChangeForTriggerMobileNotificationsSelect}
/>
</React.Fragment>
);
maximizedSettingInputs.push(triggerMobileNotificationsSection);
}
if (!sendPushNotifications) {
const disabledPushNotificationsSection = (
<>
<br/>
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.pushNotificationsDisabled'
defaultMessage={'Mobile push notifications haven\'t been enabled by your system administrator.'}
/>
</>
);
maximizedSettingInputs.push(disabledPushNotificationsSection);
}
return maximizedSettingInputs;
},
[
desktopActivity,
handleChangeForSendDesktopNotificationsRadio,
isCollapsedThreadsEnabled,
desktopThreads,
handleChangeForDesktopThreadsCheckbox,
sendPushNotifications,
desktopAndMobileSettingsDifferent,
handleChangeForDifferentMobileNotificationsCheckbox,
pushActivity,
handleChangeForSendMobileNotificationsSelect,
pushThreads,
handleChangeForMobileThreadsCheckbox,
pushStatus,
handleChangeForTriggerMobileNotificationsSelect,
]);
function handleChangeForMaxSection(section: string) {
updateSection(section);
}
function handleChangeForMinSection(section: string) {
updateSection(section);
onCancel();
}
if (active) {
return (
<SettingItemMax
title={
<FormattedMessage
id={'user.settings.notifications.desktopAndMobile.title'}
defaultMessage='Desktop and mobile notifications'
/>
}
inputs={maximizedSettingsInputs}
submit={onSubmit}
saving={saving}
serverError={error}
updateSection={handleChangeForMaxSection}
/>
);
}
return (
<SettingItemMin
ref={editButtonRef}
title={
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.title'
defaultMessage='Desktop and mobile notifications'
/>
}
describe={getCollapsedText(desktopActivity, pushActivity)}
section={UserSettingsNotificationSections.DESKTOP_AND_MOBILE}
updateSection={handleChangeForMinSection}
/>
);
}
function NoIndicatorSeparatorComponent() {
return null;
}
const optionsOfSendNotifications = [
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.allNewMessages'
defaultMessage='All new messages'
/>
),
value: NotificationLevels.ALL,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.onlyMentions'
defaultMessage='Mentions, direct messages, and group messages'
/>
),
value: NotificationLevels.MENTION,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.nothing'
defaultMessage='Nothing'
/>
),
value: NotificationLevels.NONE,
},
];
export function shouldShowDesktopThreadsSection(isCollapsedThreadsEnabled: boolean, desktopActivity: UserNotifyProps['desktop']) {
if (!isCollapsedThreadsEnabled) {
return false;
}
if (desktopActivity === NotificationLevels.ALL || desktopActivity === NotificationLevels.NONE) {
return false;
}
return true;
}
export function shouldShowMobileThreadsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], isCollapsedThreadsEnabled: boolean, desktopAndMobileSettingsDifferent: boolean, pushActivity: UserNotifyProps['push']) {
if (!sendPushNotifications) {
return false;
}
if (!isCollapsedThreadsEnabled) {
return false;
}
if (!desktopAndMobileSettingsDifferent) {
return false;
}
if (pushActivity === NotificationLevels.ALL || pushActivity === NotificationLevels.NONE) {
return false;
}
return true;
}
function shouldShowSendMobileNotificationsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], desktopAndMobileSettingsDifferent: boolean) {
if (!sendPushNotifications) {
return false;
}
if (desktopAndMobileSettingsDifferent) {
return true;
}
return false;
}
export function getValueOfSendMobileNotificationForSelect(pushActivity: UserNotifyProps['push']): ValueType<SelectOption> {
if (!pushActivity) {
return optionsOfSendNotifications[1];
}
const option = optionsOfSendNotifications.find((option) => option.value === pushActivity);
if (!option) {
return optionsOfSendNotifications[1];
}
return option;
}
export function shouldShowTriggerMobileNotificationsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], desktopActivity: UserNotifyProps['desktop'], pushActivity: UserNotifyProps['push'], desktopAndMobileSettingsDifferent: boolean): boolean {
if (!sendPushNotifications) {
return false;
}
if (!desktopActivity || !pushActivity) {
return true;
}
if (!desktopAndMobileSettingsDifferent) {
if (desktopActivity === NotificationLevels.NONE) {
return false;
}
return true;
}
if (pushActivity === NotificationLevels.NONE) {
return false;
}
return true;
}
const optionsOfSendMobileNotificationsWhenSelect: OptionsType<SelectOption> = [
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.online'
defaultMessage='Online, away, or offline'
/>
),
value: Constants.UserStatuses.ONLINE,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.away'
defaultMessage='Away or offline'
/>
),
value: Constants.UserStatuses.AWAY,
},
{
label: (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.offline'
defaultMessage='Offline'
/>
),
value: Constants.UserStatuses.OFFLINE,
},
];
export function getValueOfSendMobileNotificationWhenSelect(pushStatus?: UserNotifyProps['push_status']): ValueType<SelectOption> {
if (!pushStatus) {
return optionsOfSendMobileNotificationsWhenSelect[2];
}
const option = optionsOfSendMobileNotificationsWhenSelect.find((option) => option.value === pushStatus);
if (!option) {
return optionsOfSendMobileNotificationsWhenSelect[2];
}
return option;
}
function getCollapsedText(desktopActivity: UserNotifyProps['desktop'], pushActivity: UserNotifyProps['push']): ReactNode {
if (desktopActivity === NotificationLevels.ALL) {
if (pushActivity === NotificationLevels.ALL) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.allForDesktopAndMobile'
defaultMessage='All new messages'
/>
);
} else if (pushActivity === NotificationLevels.MENTION) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.allDesktopButMobileMentions'
defaultMessage='All new messages on desktop; mentions, direct messages, and group messages on mobile'
/>
);
} else if (pushActivity === NotificationLevels.NONE) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.allDesktopButMobileNone'
defaultMessage='All new messages on desktop; never on mobile'
/>
);
}
} else if (desktopActivity === NotificationLevels.MENTION) {
if (pushActivity === NotificationLevels.ALL) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.mentionsDesktopButMobileAll'
defaultMessage='Mentions, direct messages, and group messages on desktop; all new messages on mobile'
/>
);
} else if (pushActivity === NotificationLevels.MENTION) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.mentionsForDesktopAndMobile'
defaultMessage='Mentions, direct messages, and group messages'
/>
);
} else if (pushActivity === NotificationLevels.NONE) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.mentionsForDesktopButMobileNone'
defaultMessage='Mentions, direct messages, and group messages on desktop; never on mobile'
/>
);
}
} else if (desktopActivity === NotificationLevels.NONE) {
if (pushActivity === NotificationLevels.ALL) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.noneDesktopButMobileAll'
defaultMessage='Never on desktop; all new messages on mobile'
/>
);
} else if (pushActivity === NotificationLevels.MENTION) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.noneDesktopButMobileMentions'
defaultMessage='Never on desktop; mentions, direct messages, and group messages on mobile'
/>
);
} else if (pushActivity === NotificationLevels.NONE) {
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.noneForDesktopAndMobile'
defaultMessage='Never'
/>
);
}
}
return (
<FormattedMessage
id='user.settings.notifications.desktopAndMobile.noValidSettings'
defaultMessage='Configure desktop and mobile settings'
/>
);
}
export default memo(DesktopAndMobileNotificationSettings);

Просмотреть файл

@@ -1,155 +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 type {ComponentProps} from 'react';
import {NotificationLevels} from 'utils/constants';
import DesktopNotificationSettings from './desktop_notification_settings';
jest.mock('utils/notification_sounds', () => {
const original = jest.requireActual('utils/notification_sounds');
return {
...original,
hasSoundOptions: jest.fn(() => true),
};
});
describe('components/user_settings/notifications/DesktopNotificationSettings', () => {
const baseProps: ComponentProps<typeof DesktopNotificationSettings> = {
active: true,
updateSection: jest.fn(),
onSubmit: jest.fn(),
onCancel: jest.fn(),
saving: false,
error: '',
setParentState: jest.fn(),
areAllSectionsInactive: false,
isCollapsedThreadsEnabled: false,
activity: NotificationLevels.MENTION,
threads: NotificationLevels.ALL,
sound: 'false',
callsSound: 'false',
selectedSound: 'Bing',
callsSelectedSound: 'Dynamic',
isCallsRingingEnabled: false,
};
test('should match snapshot, on max setting', () => {
const wrapper = shallow(
<DesktopNotificationSettings {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, on max setting with sound enabled', () => {
const props = {...baseProps, sound: 'true'};
const wrapper = shallow(
<DesktopNotificationSettings {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, on max setting with Calls enabled', () => {
const props = {...baseProps, isCallsRingingEnabled: true};
const wrapper = shallow(
<DesktopNotificationSettings {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, on max setting with Calls enabled, calls sound true', () => {
const props = {...baseProps, isCallsRingingEnabled: true, callsSound: 'true'};
const wrapper = shallow(
<DesktopNotificationSettings {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, on min setting', () => {
const props = {...baseProps, active: false};
const wrapper = shallow(
<DesktopNotificationSettings {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should call props.updateSection and props.onCancel on handleMinUpdateSection', () => {
const props = {...baseProps, updateSection: jest.fn(), onCancel: jest.fn()};
const wrapper = shallow<DesktopNotificationSettings>(
<DesktopNotificationSettings {...props}/>,
);
wrapper.instance().handleMinUpdateSection('');
expect(props.updateSection).toHaveBeenCalledTimes(1);
expect(props.updateSection).toHaveBeenCalledWith('');
expect(props.onCancel).toHaveBeenCalledTimes(1);
expect(props.onCancel).toHaveBeenCalledWith();
wrapper.instance().handleMinUpdateSection('desktop');
expect(props.updateSection).toHaveBeenCalledTimes(2);
expect(props.updateSection).toHaveBeenCalledWith('desktop');
expect(props.onCancel).toHaveBeenCalledTimes(2);
expect(props.onCancel).toHaveBeenCalledWith();
});
test('should call props.updateSection on handleMaxUpdateSection', () => {
const props = {...baseProps, updateSection: jest.fn()};
const wrapper = shallow<DesktopNotificationSettings>(
<DesktopNotificationSettings {...props}/>,
);
wrapper.instance().handleMaxUpdateSection('');
expect(props.updateSection).toHaveBeenCalledTimes(1);
expect(props.updateSection).toHaveBeenCalledWith('');
wrapper.instance().handleMaxUpdateSection('desktop');
expect(props.updateSection).toHaveBeenCalledTimes(2);
expect(props.updateSection).toHaveBeenCalledWith('desktop');
});
test('should call props.setParentState on handleOnChange', () => {
const props = {...baseProps, setParentState: jest.fn()};
const wrapper = shallow<DesktopNotificationSettings>(
<DesktopNotificationSettings {...props}/>,
);
wrapper.instance().handleOnChange({
currentTarget: {getAttribute: (key: string) => {
return {'data-key': 'dataKey', 'data-value': 'dataValue'}[key];
}},
} as unknown as React.ChangeEvent<HTMLInputElement>);
expect(props.setParentState).toHaveBeenCalledTimes(1);
expect(props.setParentState).toHaveBeenCalledWith('dataKey', 'dataValue');
});
test('should match snapshot, on buildMaximizedSetting', () => {
const wrapper = shallow<DesktopNotificationSettings>(
<DesktopNotificationSettings {...baseProps}/>,
);
expect(wrapper.instance().buildMaximizedSetting()).toMatchSnapshot();
wrapper.setProps({activity: NotificationLevels.NONE});
expect(wrapper.instance().buildMaximizedSetting()).toMatchSnapshot();
});
test('should match snapshot, on buildMinimizedSetting', () => {
const wrapper = shallow<DesktopNotificationSettings>(
<DesktopNotificationSettings {...baseProps}/>,
);
expect(wrapper.instance().buildMinimizedSetting()).toMatchSnapshot();
wrapper.setProps({activity: NotificationLevels.NONE});
expect(wrapper.instance().buildMinimizedSetting()).toMatchSnapshot();
});
});

Просмотреть файл

@@ -1,547 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {type ChangeEvent, type RefObject, type ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
import ReactSelect, {type ValueType} from 'react-select';
import SettingItemMax from 'components/setting_item_max';
import SettingItemMin from 'components/setting_item_min';
import type SettingItemMinComponent from 'components/setting_item_min';
import {NotificationLevels} from 'utils/constants';
import * as NotificationSounds from 'utils/notification_sounds';
import {a11yFocus} from 'utils/utils';
type SelectedOption = {
label: string;
value: string;
};
type Props = {
active: boolean;
updateSection: (section: string) => void;
onSubmit: () => void;
onCancel: () => void;
saving: boolean;
error: string;
setParentState: (key: string, value: string | boolean) => void;
areAllSectionsInactive: boolean;
isCollapsedThreadsEnabled: boolean;
activity: string;
threads?: string;
sound: string;
callsSound: string;
selectedSound: string;
callsSelectedSound: string;
isCallsRingingEnabled: boolean;
};
type State = {
selectedOption: SelectedOption;
callsSelectedOption: SelectedOption;
blurDropdown: boolean;
};
export default class DesktopNotificationSettings extends React.PureComponent<Props, State> {
dropdownSoundRef: RefObject<ReactSelect>;
callsDropdownRef: RefObject<ReactSelect>;
editButtonRef: RefObject<SettingItemMinComponent>;
constructor(props: Props) {
super(props);
this.state = {
selectedOption: {value: props.selectedSound, label: props.selectedSound},
callsSelectedOption: {value: props.callsSelectedSound, label: props.callsSelectedSound},
blurDropdown: false,
};
this.dropdownSoundRef = React.createRef();
this.callsDropdownRef = React.createRef();
this.editButtonRef = React.createRef();
}
focusEditButton(): void {
this.editButtonRef.current?.focus();
}
handleMinUpdateSection = (section: string): void => {
this.props.updateSection(section);
this.props.onCancel();
};
handleMaxUpdateSection = (section: string): void => this.props.updateSection(section);
handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
const key = e.currentTarget.getAttribute('data-key');
const value = e.currentTarget.getAttribute('data-value');
if (key && value) {
this.props.setParentState(key, value);
a11yFocus(e.currentTarget);
}
if (key === 'callsDesktopSound' && value === 'false') {
NotificationSounds.stopTryNotificationRing();
}
};
handleThreadsOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
const value = e.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION;
this.props.setParentState('desktopThreads', value);
};
setDesktopNotificationSound: ReactSelect['onChange'] = (selectedOption: ValueType<SelectedOption>): void => {
if (selectedOption && 'value' in selectedOption) {
this.props.setParentState('desktopNotificationSound', selectedOption.value);
this.setState({selectedOption});
NotificationSounds.tryNotificationSound(selectedOption.value);
}
};
setCallsNotificationRing: ReactSelect['onChange'] = (selectedOption: ValueType<SelectedOption>): void => {
if (selectedOption && 'value' in selectedOption) {
this.props.setParentState('callsNotificationSound', selectedOption.value);
this.setState({callsSelectedOption: selectedOption});
NotificationSounds.tryNotificationRing(selectedOption.value);
}
};
blurDropdown(): void {
if (!this.state.blurDropdown) {
this.setState({blurDropdown: true});
if (this.dropdownSoundRef.current) {
this.dropdownSoundRef.current.blur();
}
if (this.callsDropdownRef.current) {
this.callsDropdownRef.current.blur();
}
}
}
buildMaximizedSetting = (): JSX.Element => {
const inputs = [];
const activityRadio = [false, false, false];
if (this.props.activity === NotificationLevels.MENTION) {
activityRadio[1] = true;
} else if (this.props.activity === NotificationLevels.NONE) {
activityRadio[2] = true;
} else {
activityRadio[0] = true;
}
let soundSection;
let notificationSelection;
let threadsNotificationSelection;
let callsSection;
let callsNotificationSelection;
if (this.props.activity !== NotificationLevels.NONE) {
const soundRadio = [false, false];
if (this.props.sound === 'false') {
soundRadio[1] = true;
} else {
soundRadio[0] = true;
}
if (this.props.sound === 'true') {
const sounds = Array.from(NotificationSounds.notificationSounds.keys());
const options = sounds.map((sound) => {
return {value: sound, label: sound};
});
notificationSelection = (<div className='pt-2'>
<ReactSelect
className='react-select notification-sound-dropdown'
classNamePrefix='react-select'
id='displaySoundNotification'
options={options}
clearable={false}
onChange={this.setDesktopNotificationSound}
value={this.state.selectedOption}
isSearchable={false}
ref={this.dropdownSoundRef}
components={{SingleValue: (props) => <div data-testid='displaySoundNotificationValue'>{props.children}</div>}}
/></div>);
}
if (this.props.isCallsRingingEnabled) {
const callsSoundRadio = [false, false];
if (this.props.callsSound === 'false') {
callsSoundRadio[1] = true;
} else {
callsSoundRadio[0] = true;
}
if (this.props.callsSound === 'true') {
const callsSounds = Array.from(NotificationSounds.callsNotificationSounds.keys());
const callsOptions = callsSounds.map((sound) => {
return {value: sound, label: sound};
});
callsNotificationSelection = (<div className='pt-2'>
<ReactSelect
className='react-select notification-sound-dropdown'
classNamePrefix='react-select'
id='displayCallsSoundNotification'
options={callsOptions}
clearable={false}
onChange={this.setCallsNotificationRing}
value={this.state.callsSelectedOption}
isSearchable={false}
ref={this.callsDropdownRef}
components={{SingleValue: (props) => <div data-testid='displayCallsSoundNotificationValue'>{props.children}</div>}}
/></div>);
}
callsSection = (
<>
<hr/>
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.desktop.calls_sound'
defaultMessage='Notification sound for incoming calls'
/>
</legend>
<div className='radio'>
<label>
<input
id='callsSoundOn'
type='radio'
name='callsNotificationSounds'
checked={callsSoundRadio[0]}
data-key={'callsDesktopSound'}
data-value={'true'}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.on'
defaultMessage='On'
/>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
id='soundOff'
type='radio'
name='callsNotificationSounds'
checked={callsSoundRadio[1]}
data-key={'callsDesktopSound'}
data-value={'false'}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.off'
defaultMessage='Off'
/>
</label>
<br/>
</div>
{callsNotificationSelection}
</fieldset>
</>
);
}
if (NotificationSounds.hasSoundOptions()) {
soundSection = (
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.desktop.sound'
defaultMessage='Notification sound'
/>
</legend>
<div className='radio'>
<label>
<input
id='soundOn'
type='radio'
name='notificationSounds'
checked={soundRadio[0]}
data-key={'desktopSound'}
data-value={'true'}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.on'
defaultMessage='On'
/>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
id='soundOff'
type='radio'
name='notificationSounds'
checked={soundRadio[1]}
data-key={'desktopSound'}
data-value={'false'}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.off'
defaultMessage='Off'
/>
</label>
<br/>
</div>
{notificationSelection}
<div className='mt-5'>
<FormattedMessage
id='user.settings.notifications.sounds_info'
defaultMessage='Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.'
/>
</div>
</fieldset>
);
} else {
soundSection = (
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.desktop.sound'
defaultMessage='Notification sound'
/>
</legend>
<br/>
<FormattedMessage
id='user.settings.notifications.soundConfig'
defaultMessage='Please configure notification sounds in your browser settings'
/>
</fieldset>
);
}
}
if (this.props.isCollapsedThreadsEnabled && NotificationLevels.MENTION === this.props.activity) {
threadsNotificationSelection = (
<>
<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={this.props.threads === NotificationLevels.ALL}
onChange={this.handleThreadsOnChange}
/>
<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>
<hr/>
</>
);
}
inputs.push(
<div key='userNotificationLevelOption'>
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.desktop'
defaultMessage='Send desktop notifications'
/>
</legend>
<div className='radio'>
<label>
<input
id='desktopNotificationAllActivity'
type='radio'
name='desktopNotificationLevel'
checked={activityRadio[0]}
data-key={'desktopActivity'}
data-value={NotificationLevels.ALL}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.allActivity'
defaultMessage='For all activity'
/>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
id='desktopNotificationMentions'
type='radio'
name='desktopNotificationLevel'
checked={activityRadio[1]}
data-key={'desktopActivity'}
data-value={NotificationLevels.MENTION}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.onlyMentions'
defaultMessage='Only for mentions, direct messages, and group messages'
/>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
id='desktopNotificationNever'
type='radio'
name='desktopNotificationLevel'
checked={activityRadio[2]}
data-key={'desktopActivity'}
data-value={NotificationLevels.NONE}
onChange={this.handleOnChange}
/>
<FormattedMessage
id='user.settings.notifications.never'
defaultMessage='Never'
/>
</label>
</div>
<div className='mt-5'>
<FormattedMessage
id='user.settings.notifications.info'
defaultMessage='Desktop notifications are available on Edge, Firefox, Safari, Chrome and Mattermost Desktop Apps.'
/>
</div>
</fieldset>
<hr/>
{threadsNotificationSelection}
{soundSection}
{callsSection}
</div>,
);
return (
<SettingItemMax
title={
<FormattedMessage
id={'user.settings.notifications.desktop.title'}
defaultMessage={'Desktop Notifications'}
/>
}
inputs={inputs}
submit={this.props.onSubmit}
saving={this.props.saving}
serverError={this.props.error}
updateSection={this.handleMaxUpdateSection}
/>
);
};
buildMinimizedSetting = () => {
const hasSoundOption = NotificationSounds.hasSoundOptions();
let collapsedDescription: ReactNode = null;
if (this.props.activity === NotificationLevels.MENTION) {
if (hasSoundOption && this.props.sound !== 'false') {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.mentionsSound'
defaultMessage='For mentions and direct messages, with sound'
/>
);
} else if (hasSoundOption && this.props.sound === 'false') {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.mentionsNoSound'
defaultMessage='For mentions and direct messages, without sound'
/>
);
} else {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.mentionsSoundHidden'
defaultMessage='For mentions and direct messages'
/>
);
}
} else if (this.props.activity === NotificationLevels.NONE) {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.off'
defaultMessage='Off'
/>
);
} else {
if (hasSoundOption && this.props.sound !== 'false') { //eslint-disable-line no-lonely-if
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.allSound'
defaultMessage='For all activity, with sound'
/>
);
} else if (hasSoundOption && this.props.sound === 'false') {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.allNoSound'
defaultMessage='For all activity, without sound'
/>
);
} else {
collapsedDescription = (
<FormattedMessage
id='user.settings.notifications.desktop.allSoundHidden'
defaultMessage='For all activity'
/>
);
}
}
return (
<SettingItemMin
ref={this.editButtonRef}
title={
<FormattedMessage
id={'user.settings.notifications.desktop.title'}
defaultMessage={'Desktop Notifications'}
/>
}
describe={collapsedDescription}
section={'desktop'}
updateSection={this.handleMinUpdateSection}
/>
);
};
componentDidUpdate(prevProps: Props) {
this.blurDropdown();
if (prevProps.active && !this.props.active && this.props.areAllSectionsInactive) {
this.focusEditButton();
}
if (this.props.selectedSound !== prevProps.selectedSound) {
this.setState({selectedOption: {value: this.props.selectedSound, label: this.props.selectedSound}});
}
if (this.props.callsSelectedSound !== prevProps.callsSelectedSound) {
this.setState({callsSelectedOption: {value: this.props.callsSelectedSound, label: this.props.callsSelectedSound}});
}
}
render() {
if (this.props.active) {
return this.buildMaximizedSetting();
}
return this.buildMinimizedSetting();
}
}

Просмотреть файл

@@ -0,0 +1,493 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ChangeEvent, ReactNode} from 'react';
import React, {memo, useEffect, useRef, Fragment, useMemo, useCallback} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import type {ValueType} from 'react-select';
import ReactSelect from 'react-select';
import type {UserNotifyProps} from '@mattermost/types/users';
import SettingItemMax from 'components/setting_item_max';
import SettingItemMin from 'components/setting_item_min';
import type SettingItemMinComponent from 'components/setting_item_min';
import {UserSettingsNotificationSections} from 'utils/constants';
import {
callsNotificationSounds,
notificationSounds,
stopTryNotificationRing,
tryNotificationSound,
tryNotificationRing,
} from 'utils/notification_sounds';
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
export type SelectOption = {
value: string;
label: ReactNode;
};
export type Props = {
active: boolean;
updateSection: (section: string) => void;
onSubmit: () => void;
onCancel: () => void;
saving: boolean;
error: string;
setParentState: (key: string, value: string | boolean) => void;
areAllSectionsInactive: boolean;
desktopSound: UserNotifyProps['desktop_sound'];
desktopNotificationSound: UserNotifyProps['desktop_notification_sound'];
isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled'];
callsDesktopSound: UserNotifyProps['calls_desktop_sound'];
callsNotificationSound: UserNotifyProps['calls_notification_sound'];
};
function DesktopNotificationSoundsSettings({
active,
updateSection,
onSubmit,
onCancel,
saving,
error,
setParentState,
areAllSectionsInactive,
desktopSound,
desktopNotificationSound,
isCallsRingingEnabled,
callsDesktopSound,
callsNotificationSound,
}: Props) {
const intl = useIntl();
const editButtonRef = useRef<SettingItemMinComponent>(null);
const previousActiveRef = useRef(active);
// Focus back on the edit button, after this section was closed after it was opened
useEffect(() => {
if (previousActiveRef.current && !active && areAllSectionsInactive) {
editButtonRef.current?.focus();
}
previousActiveRef.current = active;
}, [active, areAllSectionsInactive]);
const handleChangeForMessageNotificationSoundCheckbox = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked ? 'true' : 'false';
setParentState('desktopSound', value);
if (value === 'false') {
stopTryNotificationRing();
}
}, [setParentState]);
const handleChangeForIncomginCallSoundCheckbox = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked ? 'true' : 'false';
setParentState('callsDesktopSound', value);
if (value === 'false') {
stopTryNotificationRing();
}
}, [setParentState]);
const handleChangeForMessageNotificationSoundSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {
setParentState('desktopNotificationSound', selectedOption.value);
tryNotificationSound(selectedOption.value);
}
}, [setParentState]);
const handleChangeForIncomingCallSoundSelect = useCallback((selectedOption: ValueType<SelectOption>) => {
stopTryNotificationRing();
if (selectedOption && 'value' in selectedOption) {
setParentState('callsNotificationSound', selectedOption.value);
tryNotificationRing(selectedOption.value);
}
}, [setParentState]);
const maximizedSettingInputs = useMemo(() => {
const maximizedSettingInputs = [];
const isMessageNotificationSoundChecked = desktopSound === 'true';
const messageSoundSection = (
<Fragment key='messageSoundSection'>
<div className='checkbox inlineCheckboxSelect'>
<label>
<input
type='checkbox'
checked={desktopSound === 'true'}
onChange={handleChangeForMessageNotificationSoundCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.messageNotificationSound'
defaultMessage='Message notification sound'
/>
</label>
<ReactSelect
id='messageNotificationSoundSelect'
inputId='messageNotificationSoundSelectInput'
className='react-select inlineSelect'
classNamePrefix='react-select'
options={optionsOfMessageNotificationSoundsSelect}
clearable={false}
isClearable={false}
isSearchable={false}
isDisabled={!isMessageNotificationSoundChecked}
placeholder={intl.formatMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundSelectPlaceholder',
defaultMessage: 'Select a sound',
})}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
value={getValueOfMessageNotificationSoundsSelect(desktopNotificationSound)}
onChange={handleChangeForMessageNotificationSoundSelect}
/>
</div>
</Fragment>
);
maximizedSettingInputs.push(messageSoundSection);
if (isCallsRingingEnabled) {
const isIncomingCallSoundChecked = callsDesktopSound === 'true';
const callSoundSection = (
<Fragment key='callSoundSection'>
<br/>
<div className='checkbox inlineCheckboxSelect'>
<label>
<input
type='checkbox'
checked={isIncomingCallSoundChecked}
onChange={handleChangeForIncomginCallSoundCheckbox}
/>
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.incomingCallSound'
defaultMessage='Incoming call sound'
/>
</label>
<ReactSelect
id='incomingCallSoundNotificationSelect'
inputId='incomingCallSoundNotificationSelectInput'
className='react-select inlineSelect'
classNamePrefix='react-select'
options={optionsOfIncomingCallSoundsSelect}
clearable={false}
isClearable={false}
isSearchable={false}
isDisabled={!isIncomingCallSoundChecked}
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
placeholder={intl.formatMessage({
id: 'user.settings.notifications.desktopNotificationSound.soundSelectPlaceholder',
defaultMessage: 'Select a sound',
})}
value={getValueOfIncomingCallSoundsSelect(callsNotificationSound)}
onChange={handleChangeForIncomingCallSoundSelect}
/>
</div>
</Fragment>
);
maximizedSettingInputs.push(callSoundSection);
}
return maximizedSettingInputs;
},
[
desktopSound,
handleChangeForMessageNotificationSoundCheckbox,
handleChangeForMessageNotificationSoundSelect,
desktopNotificationSound,
isCallsRingingEnabled,
callsDesktopSound,
handleChangeForIncomginCallSoundCheckbox,
callsNotificationSound,
handleChangeForIncomingCallSoundSelect,
]);
function handleChangeForMaxSection(section: string) {
stopTryNotificationRing();
updateSection(section);
}
function handleChangeForMinSection(section: string) {
stopTryNotificationRing();
updateSection(section);
onCancel();
}
function handleSubmit() {
stopTryNotificationRing();
onSubmit();
}
if (active) {
return (
<SettingItemMax
title={
<FormattedMessage
id='user.settings.notifications.desktopNotificationSounds.title'
defaultMessage='Desktop notification sounds'
/>
}
inputs={maximizedSettingInputs}
submit={handleSubmit}
saving={saving}
serverError={error}
updateSection={handleChangeForMaxSection}
/>
);
}
return (
<SettingItemMin
ref={editButtonRef}
title={
<FormattedMessage
id='user.settings.notifications.desktopNotificationSounds.title'
defaultMessage='Desktop notification sounds'
/>
}
describe={getCollapsedText(isCallsRingingEnabled, desktopSound, desktopNotificationSound, callsDesktopSound, callsNotificationSound)}
section={UserSettingsNotificationSections.DESKTOP_NOTIFICATION_SOUND}
updateSection={handleChangeForMinSection}
/>
);
}
function NoIndicatorSeparatorComponent() {
return null;
}
const notificationSoundKeys = Array.from(notificationSounds.keys());
const optionsOfMessageNotificationSoundsSelect: SelectOption[] = notificationSoundKeys.map((soundName) => {
if (soundName === 'Bing') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundBing'
defaultMessage='Bing'
/>
),
};
} else if (soundName === 'Crackle') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCrackle'
defaultMessage='Crackle'
/>
),
};
} else if (soundName === 'Down') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundDown'
defaultMessage='Down'
/>
),
};
} else if (soundName === 'Hello') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundHello'
defaultMessage='Hello'
/>
),
};
} else if (soundName === 'Ripple') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundRipple'
defaultMessage='Ripple'
/>
),
};
} else if (soundName === 'Upstairs') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundUpstairs'
defaultMessage='Upstairs'
/>
),
};
}
return {
value: '',
label: '',
};
});
function getValueOfMessageNotificationSoundsSelect(soundName?: string) {
const soundOption = optionsOfMessageNotificationSoundsSelect.find((option) => option.value === soundName);
if (!soundOption) {
return undefined;
}
return soundOption;
}
const callNotificationSoundKeys = Array.from(callsNotificationSounds.keys());
const optionsOfIncomingCallSoundsSelect: SelectOption[] = callNotificationSoundKeys.map((soundName) => {
if (soundName === 'Dynamic') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundDynamic'
defaultMessage='Dynamic'
/>
),
};
} else if (soundName === 'Calm') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCalm'
defaultMessage='Calm'
/>
),
};
} else if (soundName === 'Urgent') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundUrgent'
defaultMessage='Urgent'
/>
),
};
} else if (soundName === 'Cheerful') {
return {
value: soundName,
label: (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.soundCheerful'
defaultMessage='Cheerful'
/>
),
};
}
return {
value: '',
label: '',
};
});
function getValueOfIncomingCallSoundsSelect(soundName?: string) {
const soundOption = optionsOfIncomingCallSoundsSelect.find((option) => option.value === soundName);
if (!soundOption) {
return undefined;
}
return soundOption;
}
function getCollapsedText(
isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled'],
desktopSound: UserNotifyProps['desktop_sound'],
desktopNotificationSound: UserNotifyProps['desktop_notification_sound'],
callsDesktopSound: UserNotifyProps['calls_desktop_sound'],
callsNotificationSound: UserNotifyProps['calls_notification_sound'],
) {
const desktopNotificationSoundIsSelected = notificationSoundKeys.includes(desktopNotificationSound as string);
const callNotificationSoundIsSelected = callNotificationSoundKeys.includes(callsNotificationSound as string);
let hasCallsSound: boolean | null = null;
if (isCallsRingingEnabled && callNotificationSoundIsSelected) {
if (callsDesktopSound === 'true') {
hasCallsSound = true;
} else {
hasCallsSound = false;
}
}
let hasDesktopSound: boolean | null = null;
if (desktopNotificationSoundIsSelected) {
if (desktopSound === 'true') {
hasDesktopSound = true;
} else {
hasDesktopSound = false;
}
}
if (hasDesktopSound !== null && hasCallsSound !== null) {
if (hasDesktopSound && hasCallsSound) {
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.hasDesktopAndCallsSound'
defaultMessage='"{desktopSound}" for messages, "{callsSound}" for calls'
values={{
desktopSound: desktopNotificationSound,
callsSound: callsNotificationSound,
}}
/>
);
} else if (!hasDesktopSound && hasCallsSound) {
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.noDesktopAndhasCallsSound'
defaultMessage='No sound for messages, "{callsSound}" for calls'
values={{callsSound: callsNotificationSound}}
/>
);
} else if (hasDesktopSound && !hasCallsSound) {
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.hasDesktopAndNoCallsSound'
defaultMessage='"{desktopSound}" for messages, no sound for calls'
values={{desktopSound: desktopNotificationSound}}
/>
);
}
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.noDesktopAndNoCallsSound'
defaultMessage='No sound'
/>
);
} else if (hasDesktopSound !== null && hasCallsSound === null) {
if (hasDesktopSound) {
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.hasDesktopSound'
defaultMessage='"{desktopSound}" for messages'
values={{desktopSound: desktopNotificationSound}}
/>
);
}
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.noDesktopSound'
defaultMessage='No sound'
/>
);
}
return (
<FormattedMessage
id='user.settings.notifications.desktopNotificationSound.noValidSound'
defaultMessage='Configure desktop notification sounds'
/>
);
}
export default memo(DesktopNotificationSoundsSettings);

Просмотреть файл

@@ -95,7 +95,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
submit={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -109,16 +109,16 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
id="settingTitle"
>
<FormattedMessage
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
>
<span>
Email Notifications
Email notifications
</span>
</FormattedMessage>
</h4>
<div
className="col-sm-9 col-sm-offset-3"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -289,7 +289,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
section="email"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -308,7 +308,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
section="email"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -327,7 +327,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
section="email"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -479,7 +479,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
submit={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -493,16 +493,16 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
id="settingTitle"
>
<FormattedMessage
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
>
<span>
Email Notifications
Email notifications
</span>
</FormattedMessage>
</h4>
<div
className="col-sm-9 col-sm-offset-3"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -744,7 +744,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
serverError=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -824,7 +824,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
submit={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -898,16 +898,8 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
<React.Fragment>
<hr />
<fieldset>
<legend
className="form-legend"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Thread reply notifications"
id="user.settings.notifications.threads.desktop"
/>
</legend>
<div
className="checkbox"
className="checkbox single-checkbox"
>
<label>
<input
@@ -918,19 +910,10 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
type="checkbox"
/>
<Memo(MemoizedFormattedMessage)
defaultMessage="Notify me about threads I'm following"
id="user.settings.notifications.threads.allActivity"
defaultMessage="Notify me about replies to threads Im following"
id="user.settings.notifications.email.notifyForthreads"
/>
</label>
<br />
</div>
<div
className="mt-5"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="When enabled, any reply to a thread you're following will send an email notification."
id="user.settings.notifications.email_threads"
/>
</div>
</fieldset>
</React.Fragment>,
@@ -942,7 +925,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
submit={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}
@@ -1022,7 +1005,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
submit={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Email Notifications"
defaultMessage="Email notifications"
id="user.settings.notifications.emailNotifications"
/>
}

Просмотреть файл

@@ -237,8 +237,8 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
ref={this.editButtonRef}
title={
<FormattedMessage
id={'user.settings.notifications.emailNotifications'}
defaultMessage={'Email Notifications'}
id='user.settings.notifications.emailNotifications'
defaultMessage='Email notifications'
/>
}
describe={description}
@@ -254,8 +254,8 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
<SettingItemMax
title={
<FormattedMessage
id={'user.settings.notifications.emailNotifications'}
defaultMessage={'Email Notifications'}
id='user.settings.notifications.emailNotifications'
defaultMessage='Email notifications'
/>
}
inputs={[
@@ -334,13 +334,7 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
<React.Fragment key='userNotificationEmailThreadsOptions'>
<hr/>
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.threads.desktop'
defaultMessage='Thread reply notifications'
/>
</legend>
<div className='checkbox'>
<div className='checkbox single-checkbox'>
<label>
<input
id='desktopThreadsNotificationAllActivity'
@@ -350,17 +344,10 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
onChange={this.handleThreadsOnChange}
/>
<FormattedMessage
id='user.settings.notifications.threads.allActivity'
defaultMessage={'Notify me about threads I\'m following'}
id='user.settings.notifications.email.notifyForthreads'
defaultMessage={'Notify me about replies to threads Im following'}
/>
</label>
<br/>
</div>
<div className='mt-5'>
<FormattedMessage
id='user.settings.notifications.email_threads'
defaultMessage={'When enabled, any reply to a thread you\'re following will send an email notification.'}
/>
</div>
</fieldset>
</React.Fragment>
@@ -371,8 +358,8 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
<SettingItemMax
title={
<FormattedMessage
id={'user.settings.notifications.emailNotifications'}
defaultMessage={'Email Notifications'}
id='user.settings.notifications.emailNotifications'
defaultMessage='Email notifications'
/>
}
inputs={[

Просмотреть файл

@@ -48,12 +48,11 @@ exports[`components/user_settings/notifications/ManageAutoResponder should match
submit={[MockFunction]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Automatic Direct Message Replies"
defaultMessage="Automatic direct message replies"
id="user.settings.notifications.autoResponder"
/>
}
updateSection={[MockFunction]}
width="medium"
>
<section
className="section-max form-horizontal "
@@ -63,16 +62,16 @@ exports[`components/user_settings/notifications/ManageAutoResponder should match
id="settingTitle"
>
<FormattedMessage
defaultMessage="Automatic Direct Message Replies"
defaultMessage="Automatic direct message replies"
id="user.settings.notifications.autoResponder"
>
<span>
Automatic Direct Message Replies
Automatic direct message replies
</span>
</FormattedMessage>
</h4>
<div
className="col-sm-10 col-sm-offset-2"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"
@@ -262,12 +261,11 @@ exports[`components/user_settings/notifications/ManageAutoResponder should match
submit={[MockFunction]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Automatic Direct Message Replies"
defaultMessage="Automatic direct message replies"
id="user.settings.notifications.autoResponder"
/>
}
updateSection={[MockFunction]}
width="medium"
>
<section
className="section-max form-horizontal "
@@ -277,16 +275,16 @@ exports[`components/user_settings/notifications/ManageAutoResponder should match
id="settingTitle"
>
<FormattedMessage
defaultMessage="Automatic Direct Message Replies"
defaultMessage="Automatic direct message replies"
id="user.settings.notifications.autoResponder"
>
<span>
Automatic Direct Message Replies
Automatic direct message replies
</span>
</FormattedMessage>
</h4>
<div
className="col-sm-10 col-sm-offset-2"
className="sectionContent col-sm-10 col-sm-offset-2"
>
<div
className="setting-list"

Просмотреть файл

@@ -106,10 +106,9 @@ export default class ManageAutoResponder extends React.PureComponent<Props> {
title={
<FormattedMessage
id='user.settings.notifications.autoResponder'
defaultMessage='Automatic Direct Message Replies'
defaultMessage='Automatic direct message replies'
/>
}
width='medium'
shiftEnter={true}
submit={this.props.submit}
saving={this.props.saving}

Просмотреть файл

@@ -5,9 +5,10 @@ import React from 'react';
import {type IntlShape} from 'react-intl';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {NotificationLevels} from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import UserSettingsNotifications from './user_settings_notifications';
import UserSettingsNotifications, {areDesktopAndMobileSettingsDifferent} from './user_settings_notifications';
describe('components/user_settings/display/UserSettingsDisplay', () => {
const defaultProps = {
@@ -20,7 +21,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
isCollapsedThreadsEnabled: true,
sendPushNotifications: false,
enableAutoResponder: false,
isCallsRingingEnabled: true,
isCallsRingingEnabled: false,
intl: {} as IntlShape,
isEnterpriseOrCloudOrSKUStarterFree: false,
isEnterpriseReady: true,
@@ -62,3 +63,27 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
expect(screen.getByText('Reply notifications')).toBeInTheDocument();
});
});
describe('areDesktopAndMobileSettingsDifferent', () => {
test('should return true when desktop and push notification levels are different', () => {
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.MENTION, NotificationLevels.NONE, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(true);
});
test('should return false when desktop and push notification levels are the same', () => {
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(false);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.MENTION, NotificationLevels.MENTION, NotificationLevels.MENTION, NotificationLevels.MENTION, false)).toBe(false);
});
test('should return true any of desktop or push settings are undefined', () => {
expect(areDesktopAndMobileSettingsDifferent(undefined as any, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, undefined as any, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, undefined as any, NotificationLevels.ALL, true)).toBe(true);
expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, undefined as any, true)).toBe(true);
});
});

Просмотреть файл

@@ -11,21 +11,19 @@ import type {Styles as ReactSelectStyles, ValueType} from 'react-select';
import CreatableReactSelect from 'react-select/creatable';
import {LightbulbOutlineIcon} from '@mattermost/compass-icons/components';
import type {ServerError} from '@mattermost/types/errors';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
import ExternalLink from 'components/external_link';
import SettingItem from 'components/setting_item';
import SettingItemMax from 'components/setting_item_max';
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus} from 'utils/constants';
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus, UserSettingsNotificationSections} from 'utils/constants';
import {stopTryNotificationRing} from 'utils/notification_sounds';
import {a11yFocus} from 'utils/utils';
import DesktopNotificationSettings from './desktop_notification_setting/desktop_notification_settings';
import DesktopAndMobileNotificationSettings from './desktop_and_mobile_notification_setting';
import DesktopNotificationSoundsSettings from './desktop_notification_sounds_setting';
import EmailNotificationSetting from './email_notification_setting';
import ManageAutoResponder from './manage_auto_responder/manage_auto_responder';
@@ -50,7 +48,7 @@ type OwnProps = {
collapseModal: () => void;
}
type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
export type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
type State = {
enableEmail: UserNotifyProps['email'];
@@ -77,6 +75,7 @@ type State = {
notifyCommentsLevel: UserNotifyProps['comments'];
isSaving: boolean;
serverError: string;
desktopAndMobileSettingsDifferent: boolean;
};
function getDefaultStateFromProps(props: Props): State {
@@ -97,6 +96,7 @@ function getDefaultStateFromProps(props: Props): State {
id: 'user.settings.notifications.autoResponderDefault',
defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
});
let desktopAndMobileSettingsDifferent = true;
if (props.user.notify_props) {
if (props.user.notify_props.desktop) {
@@ -143,6 +143,10 @@ function getDefaultStateFromProps(props: Props): State {
if (props.user.notify_props.auto_responder_message) {
autoResponderMessage = props.user.notify_props.auto_responder_message;
}
if (props.user.notify_props.desktop && props.user.notify_props.push) {
desktopAndMobileSettingsDifferent = areDesktopAndMobileSettingsDifferent(props.user.notify_props.desktop, props.user.notify_props.push, props.user.notify_props?.desktop_threads, props.user.notify_props?.push_threads, props.isCollapsedThreadsEnabled);
}
}
let usernameKey = false;
@@ -211,6 +215,7 @@ function getDefaultStateFromProps(props: Props): State {
notifyCommentsLevel: comments,
isSaving: false,
serverError: '',
desktopAndMobileSettingsDifferent,
};
}
@@ -235,8 +240,6 @@ class NotificationsTab extends React.PureComponent<Props, State> {
data.desktop = this.state.desktopActivity;
data.desktop_threads = this.state.desktopThreads;
data.email_threads = this.state.emailThreads;
data.push_threads = this.state.pushThreads;
data.push = this.state.pushActivity;
data.push_status = this.state.pushStatus;
data.comments = this.state.notifyCommentsLevel;
data.auto_responder_active = this.state.autoResponderActive ? 'true' : 'false';
@@ -244,6 +247,14 @@ class NotificationsTab extends React.PureComponent<Props, State> {
data.first_name = this.state.firstNameKey ? 'true' : 'false';
data.channel = this.state.channelKey ? 'true' : 'false';
if (this.state.desktopAndMobileSettingsDifferent) {
data.push = this.state.pushActivity;
data.push_threads = this.state.pushThreads;
} else {
data.push = this.state.desktopActivity;
data.push_threads = this.state.desktopThreads;
}
if (!data.auto_responder_message || data.auto_responder_message === '') {
data.auto_responder_message = this.props.intl.formatMessage({
id: 'user.settings.notifications.autoResponderDefault',
@@ -273,7 +284,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
this.setState({isSaving: true});
stopTryNotificationRing();
const {data: updatedUser, error} = await this.props.updateMe({notify_props: data}) as ActionResult<Partial<UserProfile>, ServerError>; // Fix in MM-46907
const {data: updatedUser, error} = await this.props.updateMe({notify_props: data});
if (updatedUser) {
this.handleUpdateSection('');
this.setState(getDefaultStateFromProps(this.props));
@@ -305,26 +316,11 @@ class NotificationsTab extends React.PureComponent<Props, State> {
this.setState((prevState) => ({...prevState, ...data}));
};
handleNotifyPushThread = (e: ChangeEvent<HTMLInputElement>): void => {
const pushThreads = e.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION;
this.setState({pushThreads});
};
handleNotifyCommentsRadio = (notifyCommentsLevel: UserNotifyProps['comments'], e?: React.ChangeEvent): void => {
this.setState({notifyCommentsLevel});
a11yFocus(e?.currentTarget as HTMLElement);
};
handlePushRadio = (pushActivity: UserNotifyProps['push'], e?: React.ChangeEvent): void => {
this.setState({pushActivity});
a11yFocus(e?.currentTarget as HTMLElement);
};
handlePushStatusRadio = (pushStatus: UserNotifyProps['push_status'], e?: React.ChangeEvent): void => {
this.setState({pushStatus});
a11yFocus(e?.currentTarget as HTMLElement);
};
handleEmailRadio = (enableEmail: UserNotifyProps['email']): void => {
this.setState({enableEmail});
};
@@ -472,311 +468,10 @@ class NotificationsTab extends React.PureComponent<Props, State> {
this.props.closeModal();
};
createPushNotificationSection = () => {
const active = this.props.activeSection === 'push';
const inputs = [];
let submit = null;
let max = null;
if (active) {
if (this.props.sendPushNotifications) {
const pushActivityRadio = [false, false, false];
if (this.state.pushActivity === NotificationLevels.ALL) {
pushActivityRadio[0] = true;
} else if (this.state.pushActivity === NotificationLevels.NONE) {
pushActivityRadio[2] = true;
} else {
pushActivityRadio[1] = true;
}
const pushStatusRadio = [false, false, false];
if (this.state.pushStatus === Constants.UserStatuses.ONLINE) {
pushStatusRadio[0] = true;
} else if (this.state.pushStatus === Constants.UserStatuses.AWAY) {
pushStatusRadio[1] = true;
} else {
pushStatusRadio[2] = true;
}
let pushThreadsNotificationSelection = null;
if (this.props.isCollapsedThreadsEnabled && this.state.pushActivity === NotificationLevels.MENTION) {
pushThreadsNotificationSelection = (
<React.Fragment key='userNotificationPushThreadsOptions'>
<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={this.state.pushThreads === NotificationLevels.ALL}
onChange={this.handleNotifyPushThread}
/>
<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>
</React.Fragment>
);
}
let pushStatusSettings;
if (this.state.pushActivity !== NotificationLevels.NONE) {
pushStatusSettings = (
<React.Fragment key='userNotificationPushStatusOptions'>
<hr/>
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.notifications.push_notification.status'
defaultMessage='Trigger push notifications when'
/>
</legend>
<div className='radio'>
<label>
<input
id='pushNotificationOnline'
type='radio'
name='pushNotificationStatus'
checked={pushStatusRadio[0]}
onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.ONLINE)}
/>
<FormattedMessage
id='user.settings.push_notification.online'
defaultMessage='Online, away or offline'
/>
</label>
</div>
<div className='radio'>
<label>
<input
id='pushNotificationAway'
type='radio'
name='pushNotificationStatus'
checked={pushStatusRadio[1]}
onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.AWAY)}
/>
<FormattedMessage
id='user.settings.push_notification.away'
defaultMessage='Away or offline'
/>
</label>
</div>
<div className='radio'>
<label>
<input
id='pushNotificationOffline'
type='radio'
name='pushNotificationStatus'
checked={pushStatusRadio[2]}
onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.OFFLINE)}
/>
<FormattedMessage
id='user.settings.push_notification.offline'
defaultMessage='Offline'
/>
</label>
</div>
<div className='mt-5'>
<span>
<FormattedMessage
id='user.settings.push_notification.status_info'
defaultMessage='Notification alerts are only pushed to your mobile device when your availability matches the selection above.'
/>
</span>
</div>
</fieldset>
</React.Fragment>
);
}
inputs.push(
<div>
<fieldset key='userNotificationLevelOption'>
<legend className='form-legend'>
<FormattedMessage
id='user.settings.push_notification.send'
defaultMessage='Send mobile push notifications'
/>
</legend>
<div className='radio'>
<label>
<input
id='pushNotificationAllActivity'
type='radio'
name='pushNotificationLevel'
checked={pushActivityRadio[0]}
onChange={this.handlePushRadio.bind(this, NotificationLevels.ALL)}
/>
<FormattedMessage
id='user.settings.push_notification.allActivity'
defaultMessage='For all activity'
/>
</label>
</div>
<div className='radio'>
<label>
<input
id='pushNotificationMentions'
type='radio'
name='pushNotificationLevel'
checked={pushActivityRadio[1]}
onChange={this.handlePushRadio.bind(this, NotificationLevels.MENTION)}
/>
<FormattedMessage
id='user.settings.push_notification.onlyMentions'
defaultMessage='For mentions and direct messages'
/>
</label>
</div>
<div className='radio'>
<label>
<input
id='pushNotificationNever'
type='radio'
name='pushNotificationLevel'
checked={pushActivityRadio[2]}
onChange={this.handlePushRadio.bind(this, NotificationLevels.NONE)}
/>
<FormattedMessage
id='user.settings.notifications.never'
defaultMessage='Never'
/>
</label>
</div>
<div className='mt-5'>
<FormattedMessage
id='user.settings.push_notification.info'
defaultMessage='Notification alerts are pushed to your mobile device when there is activity in Mattermost.'
/>
</div>
</fieldset>
</div>,
pushStatusSettings,
pushThreadsNotificationSelection,
);
submit = this.handleSubmit;
} else {
inputs.push(
<div
key='oauthEmailInfo'
className='pt-2'
>
<FormattedMessage
id='user.settings.push_notification.disabled_long'
defaultMessage='Push notifications have not been enabled by your System Administrator.'
/>
</div>,
);
}
max = (
<SettingItemMax
title={this.props.intl.formatMessage({id: 'user.settings.notifications.push', defaultMessage: 'Mobile Push Notifications'})}
inputs={inputs}
submit={submit}
serverError={this.state.serverError}
updateSection={this.handleUpdateSection}
/>
);
}
let describe: JSX.Element;
if (this.state.pushActivity === NotificationLevels.ALL) {
if (this.state.pushStatus === Constants.UserStatuses.AWAY) {
describe = (
<FormattedMessage
id='user.settings.push_notification.allActivityAway'
defaultMessage='For all activity when away or offline'
/>
);
} else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) {
describe = (
<FormattedMessage
id='user.settings.push_notification.allActivityOffline'
defaultMessage='For all activity when offline'
/>
);
} else {
describe = (
<FormattedMessage
id='user.settings.push_notification.allActivityOnline'
defaultMessage='For all activity when online, away or offline'
/>
);
}
} else if (this.state.pushActivity === NotificationLevels.NONE) {
describe = (
<FormattedMessage
id='user.settings.notifications.never'
defaultMessage='Never'
/>
);
} else if (this.props.sendPushNotifications) {
if (this.state.pushStatus === Constants.UserStatuses.AWAY) { //eslint-disable-line no-lonely-if
describe = (
<FormattedMessage
id='user.settings.push_notification.onlyMentionsAway'
defaultMessage='For mentions and direct messages when away or offline'
/>
);
} else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) {
describe = (
<FormattedMessage
id='user.settings.push_notification.onlyMentionsOffline'
defaultMessage='For mentions and direct messages when offline'
/>
);
} else {
describe = (
<FormattedMessage
id='user.settings.push_notification.onlyMentionsOnline'
defaultMessage='For mentions and direct messages when online, away or offline'
/>
);
}
} else {
describe = (
<FormattedMessage
id='user.settings.push_notification.disabled'
defaultMessage='Push notifications are not enabled'
/>
);
}
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.push', defaultMessage: 'Mobile Push Notifications'})}
active={active}
areAllSectionsInactive={this.props.activeSection === ''}
describe={describe}
section={'push'}
updateSection={this.handleUpdateSection}
max={max}
/>
);
};
createKeywordsWithNotificationSection = () => {
const serverError = this.state.serverError;
const user = this.props.user;
const isSectionExpanded = this.props.activeSection === 'keysWithNotification';
const isSectionExpanded = this.props.activeSection === UserSettingsNotificationSections.KEYWORDS_MENTIONS;
let expandedSection = null;
if (isSectionExpanded) {
@@ -901,7 +596,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
expandedSection = (
<SettingItemMax
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords That Trigger Notifications'})}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger notifications'})}
inputs={inputs}
submit={this.handleSubmit}
saving={this.state.isSaving}
@@ -932,8 +627,8 @@ class NotificationsTab extends React.PureComponent<Props, State> {
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords That Trigger Notifications'})}
section='keysWithNotification'
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger notifications'})}
section={UserSettingsNotificationSections.KEYWORDS_MENTIONS}
active={isSectionExpanded}
areAllSectionsInactive={this.props.activeSection === ''}
describe={collapsedDescription}
@@ -943,7 +638,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
};
createKeywordsWithHighlightSection = () => {
const isSectionExpanded = this.props.activeSection === 'keysWithHighlight';
const isSectionExpanded = this.props.activeSection === UserSettingsNotificationSections.KEYWORDS_HIGHLIGHT;
let expandedSection = null;
if (isSectionExpanded) {
@@ -990,7 +685,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
expandedSection = (
<SettingItemMax
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords That Get Highlighted (Without Notifications)'})}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords that get highlighted (without notifications)'})}
inputs={inputs}
submit={this.handleSubmit}
saving={this.state.isSaving}
@@ -1063,8 +758,8 @@ class NotificationsTab extends React.PureComponent<Props, State> {
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords That Get Highlighted (Without Notifications)'})}
section='keysWithHighlight'
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords that get highlighted (without notifications)'})}
section={UserSettingsNotificationSections.KEYWORDS_HIGHLIGHT}
active={isSectionExpanded}
areAllSectionsInactive={this.props.activeSection === ''}
describe={collapsedDescription}
@@ -1078,9 +773,8 @@ class NotificationsTab extends React.PureComponent<Props, State> {
createCommentsSection = () => {
const serverError = this.state.serverError;
const active = this.props.activeSection === 'comments';
let max = null;
if (active) {
if (this.props.activeSection === UserSettingsNotificationSections.REPLY_NOTIFCATIONS) {
const commentsActive = [false, false, false];
if (this.state.notifyCommentsLevel === 'never') {
commentsActive[2] = true;
@@ -1097,7 +791,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
<legend className='form-legend hidden-label'>
<FormattedMessage
id='user.settings.notifications.comments'
defaultMessage='Reply Notifications'
defaultMessage='Reply notifications'
/>
</legend>
<div className='radio'>
@@ -1161,7 +855,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
max = (
<SettingItemMax
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply Notifications'})}
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply notifications'})}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -1199,9 +893,9 @@ class NotificationsTab extends React.PureComponent<Props, State> {
return (
<SettingItem
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply notifications'})}
active={active}
active={this.props.activeSection === UserSettingsNotificationSections.REPLY_NOTIFCATIONS}
describe={describe}
section={'comments'}
section={UserSettingsNotificationSections.REPLY_NOTIFCATIONS}
updateSection={this.handleUpdateSection}
max={max}
areAllSectionsInactive={this.props.activeSection === ''}
@@ -1224,16 +918,16 @@ class NotificationsTab extends React.PureComponent<Props, State> {
return (
<SettingItem
active={this.props.activeSection === 'auto-responder'}
active={this.props.activeSection === UserSettingsNotificationSections.AUTO_RESPONDER}
areAllSectionsInactive={this.props.activeSection === ''}
title={
<FormattedMessage
id='user.settings.notifications.autoResponder'
defaultMessage='Automatic Direct Message Replies'
defaultMessage='Automatic direct message replies'
/>
}
describe={describe}
section={'auto-responder'}
section={UserSettingsNotificationSections.AUTO_RESPONDER}
updateSection={this.handleUpdateSection}
max={(
<div>
@@ -1254,12 +948,13 @@ class NotificationsTab extends React.PureComponent<Props, State> {
};
render() {
const pushNotificationSection = this.createPushNotificationSection();
const keywordsWithNotificationSection = this.createKeywordsWithNotificationSection();
const keywordsWithHighlightSection = this.createKeywordsWithHighlightSection();
const commentsSection = this.createCommentsSection();
const autoResponderSection = this.createAutoResponderSection();
const areAllSectionsInactive = this.props.activeSection === '';
return (
<div id='notificationSettings'>
<SettingMobileHeader
@@ -1268,7 +963,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
text={
<FormattedMessage
id='user.settings.notifications.title'
defaultMessage='Notification Settings'
defaultMessage='Notification settings'
/>
}
/>
@@ -1302,42 +997,56 @@ class NotificationsTab extends React.PureComponent<Props, State> {
}
/>
<div className='divider-dark first'/>
<DesktopNotificationSettings
active={this.props.activeSection === 'desktop'}
<DesktopAndMobileNotificationSettings
active={this.props.activeSection === UserSettingsNotificationSections.DESKTOP_AND_MOBILE}
updateSection={this.handleUpdateSection}
onSubmit={this.handleSubmit}
onCancel={this.handleCancel}
saving={this.state.isSaving}
error={this.state.serverError}
setParentState={this.setStateValue}
areAllSectionsInactive={this.props.activeSection === ''}
areAllSectionsInactive={areAllSectionsInactive}
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
activity={this.state.desktopActivity}
threads={this.state.desktopThreads}
sound={this.state.desktopSound}
callsSound={this.state.callsDesktopSound}
selectedSound={this.state.desktopNotificationSound || 'default'}
callsSelectedSound={this.state.callsNotificationSound || 'default'}
desktopActivity={this.state.desktopActivity}
pushActivity={this.state.pushActivity}
sendPushNotifications={this.props.sendPushNotifications}
pushStatus={this.state.pushStatus}
desktopThreads={this.state.desktopThreads}
pushThreads={this.state.pushThreads}
desktopAndMobileSettingsDifferent={this.state.desktopAndMobileSettingsDifferent}
/>
<div className='divider-light'/>
<DesktopNotificationSoundsSettings
active={this.props.activeSection === UserSettingsNotificationSections.DESKTOP_NOTIFICATION_SOUND}
updateSection={this.handleUpdateSection}
onSubmit={this.handleSubmit}
onCancel={this.handleCancel}
saving={this.state.isSaving}
error={this.state.serverError}
setParentState={this.setStateValue}
areAllSectionsInactive={areAllSectionsInactive}
desktopSound={this.state.desktopSound}
desktopNotificationSound={this.state.desktopNotificationSound}
isCallsRingingEnabled={this.props.isCallsRingingEnabled}
callsDesktopSound={this.state.callsDesktopSound}
callsNotificationSound={this.state.callsNotificationSound}
/>
<div className='divider-light'/>
<EmailNotificationSetting
active={this.props.activeSection === 'email'}
active={this.props.activeSection === UserSettingsNotificationSections.EMAIL}
updateSection={this.handleUpdateSection}
onSubmit={this.handleSubmit}
onCancel={this.handleCancel}
saving={this.state.isSaving}
error={this.state.serverError}
setParentState={this.setStateValue}
areAllSectionsInactive={this.props.activeSection === ''}
areAllSectionsInactive={areAllSectionsInactive}
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
enableEmail={this.state.enableEmail === 'true'}
onChange={this.handleEmailRadio}
threads={this.state.emailThreads || ''}
/>
<div className='divider-light'/>
{pushNotificationSection}
<div className='divider-light'/>
{keywordsWithNotificationSection}
{(!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) && (
<>
@@ -1414,4 +1123,39 @@ const customKeywordsSelectorStyles: ReactSelectStyles = {
})),
};
const validNotificationLevels = Object.values(NotificationLevels);
export function areDesktopAndMobileSettingsDifferent(
desktopActivity: UserNotifyProps['desktop'],
pushActivity: UserNotifyProps['push'],
desktopThreads?: UserNotifyProps['desktop_threads'],
pushThreads?: UserNotifyProps['push_threads'],
isCollapsedThreadsEnabled?: boolean,
): boolean {
if (!desktopActivity || !pushActivity || !desktopThreads || !pushThreads) {
return true;
}
if (
!validNotificationLevels.includes(desktopActivity) ||
!validNotificationLevels.includes(pushActivity) ||
!validNotificationLevels.includes(desktopThreads) ||
!validNotificationLevels.includes(pushThreads)
) {
return true;
}
if (desktopActivity === pushActivity) {
if (isCollapsedThreadsEnabled) {
if (desktopThreads === pushThreads) {
return false;
}
return true;
}
return false;
}
return true;
}
export default injectIntl(NotificationsTab);

Просмотреть файл

@@ -77,7 +77,6 @@ exports[`MfaSection rendering when section is expanded and MFA is active and enf
/>
}
updateSection={[MockFunction]}
width="medium"
/>
`;
@@ -118,7 +117,6 @@ exports[`MfaSection rendering when section is expanded and MFA is active but not
/>
}
updateSection={[MockFunction]}
width="medium"
/>
`;
@@ -159,7 +157,6 @@ exports[`MfaSection rendering when section is expanded and MFA is not active 1`]
/>
}
updateSection={[MockFunction]}
width="medium"
/>
`;
@@ -200,6 +197,5 @@ exports[`MfaSection rendering when section is expanded with a server error 1`] =
/>
}
updateSection={[MockFunction]}
width="medium"
/>
`;

Просмотреть файл

@@ -219,7 +219,6 @@ export default class MfaSection extends React.PureComponent<Props, State> {
extraInfo={this.renderHelpText()}
serverError={this.state.serverError}
updateSection={this.props.updateSection}
width='medium'
/>
);
}

Просмотреть файл

@@ -627,7 +627,7 @@ export default class UserAccessTokenSection extends React.PureComponent<Props, S
infoPosition='top'
serverError={this.state.serverError}
updateSection={this.props.updateSection}
width='full'
isFullWidth={true}
saving={this.state.saving}
cancelButtonText={
<FormattedMessage

Просмотреть файл

@@ -918,7 +918,7 @@ export class SecurityTab extends React.PureComponent<Props, State> {
inputs={inputs}
serverError={this.state.serverError}
updateSection={this.handleUpdateSection}
width='full'
isFullWidth={true}
cancelButtonText={
<FormattedMessage
id='user.settings.security.close'