mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-60484 & MM-60485] Add disabled notification banners and section notices for web (#28372)
This commit is contained in:
@@ -45,7 +45,6 @@ function mapStateToProps(state: GlobalState) {
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
const dismissFirstError = dismissError.bind(null, 0);
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NotificationPermissionBar should render the NotificationPermissionNeverGrantedBar when permission is never granted yet 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_StyledDiv-BRlth ksRYxX announcement-bar"
|
||||
>
|
||||
<div
|
||||
class="announcement-bar__text"
|
||||
>
|
||||
<i
|
||||
class="icon icon-alert-circle-outline"
|
||||
/>
|
||||
<span>
|
||||
We need your permission to show notifications in the browser.
|
||||
</span>
|
||||
<button>
|
||||
Enable notifications
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
class="announcement-bar__close"
|
||||
href="#"
|
||||
>
|
||||
×
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotificationPermissionBar should render the NotificationUnsupportedBar if notifications are not supported 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_StyledDiv-BRlth ksRYxX announcement-bar"
|
||||
>
|
||||
<div
|
||||
class="announcement-bar__text"
|
||||
>
|
||||
<i
|
||||
class="icon icon-alert-circle-outline"
|
||||
/>
|
||||
<span>
|
||||
Your browser does not support browser notifications.
|
||||
</span>
|
||||
<button>
|
||||
Update your browser
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
class="announcement-bar__close"
|
||||
href="#"
|
||||
>
|
||||
×
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,19 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {screen, waitFor} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
|
||||
import {requestNotificationPermission, isNotificationAPISupported} from 'utils/notifications';
|
||||
import {renderWithContext, userEvent, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import * as utilsNotifications from 'utils/notifications';
|
||||
|
||||
import NotificationPermissionBar from './index';
|
||||
|
||||
jest.mock('utils/notifications', () => ({
|
||||
requestNotificationPermission: jest.fn(),
|
||||
isNotificationAPISupported: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('NotificationPermissionBar', () => {
|
||||
const initialState = {
|
||||
entities: {
|
||||
@@ -27,52 +21,71 @@ describe('NotificationPermissionBar', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(isNotificationAPISupported as jest.Mock).mockReturnValue(true);
|
||||
(window as any).Notification = {permission: 'default'};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete (window as any).Notification;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('should render the notification bar when conditions are met', () => {
|
||||
renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
test('should not render anything if user is not logged in', () => {
|
||||
const {container} = renderWithContext(<NotificationPermissionBar/>);
|
||||
|
||||
expect(screen.getByText('We need your permission to show desktop notifications.')).toBeInTheDocument();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
test('should render the NotificationUnsupportedBar if notifications are not supported', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(false);
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
expect(screen.queryByText('Your browser does not support browser notifications.')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Update your browser')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the NotificationPermissionNeverGrantedBar when permission is never granted yet', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionNeverGranted);
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
expect(screen.getByText('We need your permission to show notifications in the browser.')).toBeInTheDocument();
|
||||
expect(screen.getByText('Enable notifications')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render the notification bar if user is not logged in', () => {
|
||||
renderWithContext(<NotificationPermissionBar/>);
|
||||
|
||||
expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Enable notifications')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render the notification bar if Notifications are not supported', () => {
|
||||
delete (window as any).Notification;
|
||||
(isNotificationAPISupported as jest.Mock).mockReturnValue(false);
|
||||
test('should call requestNotificationPermission and hide the bar when the button is clicked in NotificationPermissionNeverGrantedBar', async () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionNeverGranted);
|
||||
jest.spyOn(utilsNotifications, 'requestNotificationPermission').mockResolvedValue(utilsNotifications.NotificationPermissionGranted);
|
||||
|
||||
renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Enable notifications')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call requestNotificationPermission and hide the bar when the button is clicked', async () => {
|
||||
(requestNotificationPermission as jest.Mock).mockResolvedValue('granted');
|
||||
|
||||
renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(screen.getByText('We need your permission to show desktop notifications.')).toBeInTheDocument();
|
||||
expect(screen.getByText('We need your permission to show notifications in the browser.')).toBeInTheDocument();
|
||||
|
||||
await waitFor(async () => {
|
||||
userEvent.click(screen.getByText('Enable notifications'));
|
||||
});
|
||||
|
||||
expect(requestNotificationPermission).toHaveBeenCalled();
|
||||
expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
|
||||
expect(utilsNotifications.requestNotificationPermission).toHaveBeenCalled();
|
||||
expect(screen.queryByText('We need your permission to show browser notifications.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render anything if permission is denied', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('denied');
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
test('should not render anything if permission is granted', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('granted');
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,57 +1,42 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import React from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
|
||||
import NotificationPermissionNeverGrantedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_never_granted_bar';
|
||||
import NotificationPermissionUnsupportedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_unsupported_bar';
|
||||
|
||||
import {AnnouncementBarTypes} from 'utils/constants';
|
||||
import {requestNotificationPermission, isNotificationAPISupported} from 'utils/notifications';
|
||||
import {
|
||||
isNotificationAPISupported,
|
||||
NotificationPermissionDenied,
|
||||
NotificationPermissionNeverGranted,
|
||||
getNotificationPermission,
|
||||
} from 'utils/notifications';
|
||||
|
||||
export default function NotificationPermissionBar() {
|
||||
const isLoggedIn = Boolean(useSelector(getCurrentUserId));
|
||||
|
||||
const [show, setShow] = useState(isNotificationAPISupported() ? Notification.permission === 'default' : false);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
await requestNotificationPermission();
|
||||
setShow(false);
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
|
||||
// show it again on app refresh.
|
||||
setShow(false);
|
||||
}, []);
|
||||
|
||||
if (!show || !isLoggedIn || !isNotificationAPISupported()) {
|
||||
if (!isLoggedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBar
|
||||
showCloseButton={true}
|
||||
handleClose={handleClose}
|
||||
type={AnnouncementBarTypes.ANNOUNCEMENT}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='announcement_bar.notification.needs_permission'
|
||||
defaultMessage='We need your permission to show desktop notifications.'
|
||||
/>
|
||||
}
|
||||
ctaText={
|
||||
<FormattedMessage
|
||||
id='announcement_bar.notification.enable_notifications'
|
||||
defaultMessage='Enable notifications'
|
||||
/>
|
||||
}
|
||||
showCTA={true}
|
||||
showLinkAsButton={true}
|
||||
onButtonClick={handleClick}
|
||||
/>
|
||||
);
|
||||
// When browser does not support notification API, we show the notification bar to update browser
|
||||
if (!isNotificationAPISupported()) {
|
||||
return <NotificationPermissionUnsupportedBar/>;
|
||||
}
|
||||
|
||||
// When user has not granted permission, we show the notification bar to request permission
|
||||
if (getNotificationPermission() === NotificationPermissionNeverGranted) {
|
||||
return <NotificationPermissionNeverGrantedBar/>;
|
||||
}
|
||||
|
||||
// When user has denied permission, we don't show since user explicitly denied permission
|
||||
if (getNotificationPermission() === NotificationPermissionDenied) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
|
||||
|
||||
import {AnnouncementBarTypes} from 'utils/constants';
|
||||
import {requestNotificationPermission} from 'utils/notifications';
|
||||
|
||||
export default function NotificationPermissionNeverGrantedBar() {
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
try {
|
||||
await requestNotificationPermission();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error requesting notification permission', error);
|
||||
} finally {
|
||||
// Dismiss the bar after user makes a choice
|
||||
setShow(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
|
||||
// show it again on app refresh.
|
||||
setShow(false);
|
||||
}, []);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBar
|
||||
showCloseButton={true}
|
||||
handleClose={handleClose}
|
||||
type={AnnouncementBarTypes.ANNOUNCEMENT}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='announcementBar.notification.permissionNeverGrantedBar.message'
|
||||
defaultMessage='We need your permission to show notifications in the browser.'
|
||||
/>
|
||||
}
|
||||
ctaText={
|
||||
<FormattedMessage
|
||||
id='announcementBar.notification.permissionNeverGrantedBar.cta'
|
||||
defaultMessage='Enable notifications'
|
||||
/>
|
||||
}
|
||||
showCTA={true}
|
||||
showLinkAsButton={true}
|
||||
onButtonClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
|
||||
|
||||
import {AnnouncementBarTypes} from 'utils/constants';
|
||||
|
||||
export default function UnsupportedNotificationAnnouncementBar() {
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
window.open('https://mattermost.com/pl/pc-web-requirements', '_blank', 'noopener,noreferrer');
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
|
||||
// show it again on app refresh.
|
||||
setShow(false);
|
||||
}, []);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBar
|
||||
showCloseButton={true}
|
||||
type={AnnouncementBarTypes.ANNOUNCEMENT}
|
||||
handleClose={handleClose}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='announcementBar.notification.unsupportedBar.message'
|
||||
defaultMessage='Your browser does not support browser notifications.'
|
||||
/>
|
||||
}
|
||||
ctaText={
|
||||
<FormattedMessage
|
||||
id='announcementBar.notification.unsupportedBar.cta'
|
||||
defaultMessage='Update your browser'
|
||||
/>
|
||||
}
|
||||
showCTA={true}
|
||||
showLinkAsButton={true}
|
||||
onButtonClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -559,7 +559,7 @@ exports[`components/drafts/panel/panel_body should match snapshot for priority 1
|
||||
uppercase={true}
|
||||
>
|
||||
<div
|
||||
className="TagWrapper-keYggn hpsCJu Tag Tag--info Tag--xs"
|
||||
className="TagWrapper-keYggn gnIgwl Tag Tag--info Tag--xs"
|
||||
>
|
||||
<AlertCircleOutlineIcon
|
||||
size={10}
|
||||
|
||||
@@ -23,6 +23,10 @@ function getBaseProps(): Props {
|
||||
onClick: jest.fn(),
|
||||
text: 'secondary button title',
|
||||
},
|
||||
tertiaryButton: {
|
||||
onClick: jest.fn(),
|
||||
text: 'tertiary button title',
|
||||
},
|
||||
linkButton: {
|
||||
onClick: jest.fn(),
|
||||
text: 'link button title',
|
||||
@@ -39,11 +43,13 @@ describe('PluginAction', () => {
|
||||
renderWithContext(<SectionNotice {...props}/>);
|
||||
const primaryButton = screen.getByText(props.primaryButton!.text);
|
||||
const secondaryButton = screen.getByText(props.secondaryButton!.text);
|
||||
const tertiaryButton = screen.getByText(props.tertiaryButton!.text);
|
||||
const linkButton = screen.getByText(props.linkButton!.text);
|
||||
const closeButton = screen.getByLabelText('Dismiss notice');
|
||||
|
||||
expect(primaryButton).toBeInTheDocument();
|
||||
expect(secondaryButton).toBeInTheDocument();
|
||||
expect(tertiaryButton).toBeInTheDocument();
|
||||
expect(linkButton).toBeInTheDocument();
|
||||
expect(closeButton).toBeInTheDocument();
|
||||
expect(screen.queryByText(props.text as string)).toBeInTheDocument();
|
||||
@@ -52,6 +58,8 @@ describe('PluginAction', () => {
|
||||
expect(props.primaryButton?.onClick).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(secondaryButton);
|
||||
expect(props.secondaryButton?.onClick).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(tertiaryButton);
|
||||
expect(props.tertiaryButton?.onClick).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(linkButton);
|
||||
expect(props.linkButton?.onClick).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(closeButton);
|
||||
@@ -62,6 +70,7 @@ describe('PluginAction', () => {
|
||||
const props = getBaseProps();
|
||||
props.primaryButton = undefined;
|
||||
props.secondaryButton = undefined;
|
||||
props.tertiaryButton = undefined;
|
||||
props.linkButton = undefined;
|
||||
props.isDismissable = false;
|
||||
renderWithContext(<SectionNotice {...props}/>);
|
||||
|
||||
@@ -17,6 +17,7 @@ type Props = {
|
||||
text?: string;
|
||||
primaryButton?: SectionNoticeButtonProp;
|
||||
secondaryButton?: SectionNoticeButtonProp;
|
||||
tertiaryButton?: SectionNoticeButtonProp;
|
||||
linkButton?: SectionNoticeButtonProp;
|
||||
type?: 'info' | 'success' | 'danger' | 'welcome' | 'warning' | 'hint';
|
||||
isDismissable?: boolean;
|
||||
@@ -37,6 +38,7 @@ const SectionNotice = ({
|
||||
text,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
tertiaryButton,
|
||||
linkButton,
|
||||
type = 'info',
|
||||
isDismissable,
|
||||
@@ -45,7 +47,7 @@ const SectionNotice = ({
|
||||
const intl = useIntl();
|
||||
const icon = iconByType[type];
|
||||
const showDismiss = Boolean(isDismissable && onDismissClick);
|
||||
const hasButtons = Boolean(primaryButton || secondaryButton || linkButton);
|
||||
const hasButtons = Boolean(primaryButton || secondaryButton || tertiaryButton || linkButton);
|
||||
return (
|
||||
<div className={classNames('sectionNoticeContainer', type)}>
|
||||
<div className={'sectionNoticeContent'}>
|
||||
@@ -64,9 +66,15 @@ const SectionNotice = ({
|
||||
{secondaryButton &&
|
||||
<SectionNoticeButton
|
||||
button={secondaryButton}
|
||||
buttonClass='btn-tertiary'
|
||||
buttonClass='btn-secondary'
|
||||
/>
|
||||
}
|
||||
{tertiaryButton && (
|
||||
<SectionNoticeButton
|
||||
button={tertiaryButton}
|
||||
buttonClass='btn-tertiary'
|
||||
/>
|
||||
)}
|
||||
{linkButton &&
|
||||
<SectionNoticeButton
|
||||
button={linkButton}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
}
|
||||
|
||||
.sectionNoticeIcon {
|
||||
font-size: 20px;
|
||||
font-size: 24px;
|
||||
|
||||
&.info, &.hint {
|
||||
color: var(--sidebar-text-active-border);
|
||||
@@ -99,7 +99,7 @@
|
||||
font-family: 'Open Sans';
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
line-height: 24px;
|
||||
|
||||
&.welcome {
|
||||
font-family: 'Metropolis';
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {SectionNoticeButtonProp} from './types';
|
||||
|
||||
type Props = {
|
||||
button: SectionNoticeButtonProp;
|
||||
buttonClass: 'btn-primary' | 'btn-tertiary' | 'btn-link';
|
||||
buttonClass: 'btn-primary' | 'btn-secondary' | 'btn-tertiary' | 'btn-link';
|
||||
}
|
||||
|
||||
const SectionNoticeButton = ({
|
||||
|
||||
@@ -45,6 +45,7 @@ type Props = {
|
||||
submitExtra?: ReactNode;
|
||||
saving?: boolean;
|
||||
title?: ReactNode;
|
||||
extraContentBeforeSettingList?: ReactNode;
|
||||
isFullWidth?: boolean;
|
||||
cancelButtonText?: ReactNode;
|
||||
shiftEnter?: boolean;
|
||||
@@ -224,6 +225,7 @@ export default class SettingItemMax extends React.PureComponent<Props> {
|
||||
className={`section-max form-horizontal ${this.props.containerStyle}`}
|
||||
>
|
||||
{title}
|
||||
{this.props.extraContentBeforeSettingList}
|
||||
<div
|
||||
className={classNames('sectionContent', {
|
||||
'col-sm-12': this.props.isFullWidth,
|
||||
|
||||
@@ -92,6 +92,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -351,6 +352,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -669,6 +671,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -931,6 +934,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -1252,6 +1256,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -1476,6 +1481,7 @@ Object {
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
|
||||
@@ -11,6 +11,7 @@ exports[`DesktopNotificationSettings should match snapshot, on max setting 1`] =
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
</h4>
|
||||
<div />
|
||||
<div
|
||||
class="sectionContent col-sm-10 col-sm-offset-2"
|
||||
>
|
||||
@@ -179,6 +180,7 @@ exports[`DesktopNotificationSettings should match snapshot, on min setting 1`] =
|
||||
id="desktopAndMobileTitle"
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
<div />
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
@@ -214,6 +216,7 @@ exports[`DesktopNotificationSettings should not show desktop thread notification
|
||||
>
|
||||
Desktop and mobile notifications
|
||||
</h4>
|
||||
<div />
|
||||
<div
|
||||
class="sectionContent col-sm-10 col-sm-offset-2"
|
||||
>
|
||||
|
||||
@@ -17,6 +17,9 @@ import DesktopNotificationSettings, {
|
||||
|
||||
const validNotificationLevels = Object.values(NotificationLevels);
|
||||
|
||||
jest.mock('components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice', () => () => <div/>);
|
||||
jest.mock('components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_title_tag', () => () => <div/>);
|
||||
|
||||
describe('DesktopNotificationSettings', () => {
|
||||
const baseProps: Props = {
|
||||
active: true,
|
||||
|
||||
@@ -12,6 +12,8 @@ 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 NotificationPermissionSectionNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice';
|
||||
import NotificationPermissionTitleTag from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_title_tag';
|
||||
|
||||
import Constants, {NotificationLevels, UserSettingsNotificationSections} from 'utils/constants';
|
||||
|
||||
@@ -322,6 +324,7 @@ function DesktopAndMobileNotificationSettings({
|
||||
saving={saving}
|
||||
serverError={error}
|
||||
updateSection={handleChangeForMaxSection}
|
||||
extraContentBeforeSettingList={<NotificationPermissionSectionNotice/>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -330,10 +333,13 @@ function DesktopAndMobileNotificationSettings({
|
||||
<SettingItemMin
|
||||
ref={editButtonRef}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktopAndMobile.title'
|
||||
defaultMessage='Desktop and mobile notifications'
|
||||
/>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktopAndMobile.title'
|
||||
defaultMessage='Desktop and mobile notifications'
|
||||
/>
|
||||
<NotificationPermissionTitleTag/>
|
||||
</>
|
||||
}
|
||||
describe={getCollapsedText(desktopActivity, pushActivity)}
|
||||
section={UserSettingsNotificationSections.DESKTOP_AND_MOBILE}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 * as utilsNotifications from 'utils/notifications';
|
||||
|
||||
import NotificationPermissionSectionNotice from './index';
|
||||
|
||||
describe('NotificationPermissionSectionNotice', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('should render "Unsupported" notice when notifications are not supported', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(false);
|
||||
|
||||
renderWithContext(<NotificationPermissionSectionNotice/>);
|
||||
|
||||
expect(screen.getByText('Browser notifications unsupported')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render "Never granted" notice when notifications are never granted', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('default');
|
||||
|
||||
renderWithContext(<NotificationPermissionSectionNotice/>);
|
||||
|
||||
expect(screen.getByText('Browser notifications are disabled')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render "Denied" notice when notifications are denied', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('denied');
|
||||
|
||||
renderWithContext(<NotificationPermissionSectionNotice/>);
|
||||
|
||||
expect(screen.getByText('Browser notification permission was denied')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render nothing when notifications are granted', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('granted');
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionSectionNotice/>);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState} from 'react';
|
||||
|
||||
import NotificationPermissionDeniedNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_denied_section_notice';
|
||||
import NotificationPermissionNeverGrantedNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_never_granted_section_notice';
|
||||
import NotificationPermissionUnsupportedSectionNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_unsupported_section_notice';
|
||||
|
||||
import {getNotificationPermission, isNotificationAPISupported, NotificationPermissionDenied, NotificationPermissionNeverGranted} from 'utils/notifications';
|
||||
|
||||
export default function NotificationPermissionSectionNotice() {
|
||||
const isNotificationSupported = isNotificationAPISupported();
|
||||
|
||||
const [notificationPermission, setNotificationPermission] = useState(getNotificationPermission());
|
||||
|
||||
function handleRequestNotificationClicked(permission: NotificationPermission) {
|
||||
setNotificationPermission(permission);
|
||||
}
|
||||
|
||||
if (!isNotificationSupported) {
|
||||
return <NotificationPermissionUnsupportedSectionNotice/>;
|
||||
}
|
||||
|
||||
if (isNotificationSupported && notificationPermission === NotificationPermissionNeverGranted) {
|
||||
return <NotificationPermissionNeverGrantedNotice onCtaButtonClick={handleRequestNotificationClicked}/>;
|
||||
}
|
||||
|
||||
if (isNotificationSupported && notificationPermission === NotificationPermissionDenied) {
|
||||
return <NotificationPermissionDeniedNotice/>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SectionNotice from 'components/section_notice';
|
||||
|
||||
export default function NotificationPermissionDeniedSectionNotice() {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
window.open('https://mattermost.com/pl/manage-notifications', '_blank', 'noopener,noreferrer');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='extraContentBeforeSettingList'>
|
||||
<SectionNotice
|
||||
type='danger'
|
||||
title={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.title',
|
||||
defaultMessage: 'Browser notification permission was denied',
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.message',
|
||||
defaultMessage: 'You\'re missing important message and call notifications from Mattermost. To start receiving notifications, please enable notifications for Mattermost in your browser settings.',
|
||||
})}
|
||||
tertiaryButton={{
|
||||
text: intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.button',
|
||||
defaultMessage: 'How to enable notifications',
|
||||
}),
|
||||
onClick: handleClick,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SectionNotice from 'components/section_notice';
|
||||
|
||||
import {requestNotificationPermission} from 'utils/notifications';
|
||||
|
||||
type Props = {
|
||||
onCtaButtonClick: (permission: NotificationPermission) => void;
|
||||
}
|
||||
|
||||
export default function NotificationPermissionNeverGrantedSectionNotice(props: Props) {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const permission = await requestNotificationPermission();
|
||||
if (permission) {
|
||||
props.onCtaButtonClick(permission);
|
||||
}
|
||||
}, [props.onCtaButtonClick]);
|
||||
|
||||
return (
|
||||
<div className='extraContentBeforeSettingList'>
|
||||
<SectionNotice
|
||||
type='danger'
|
||||
title={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.title',
|
||||
defaultMessage: 'Browser notifications are disabled',
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.message',
|
||||
defaultMessage: 'You\'re missing important message and call notifications from Mattermost. Mattermost notifications are disabled by this browser.',
|
||||
})}
|
||||
primaryButton={{
|
||||
text: intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.button',
|
||||
defaultMessage: 'Enable notifications',
|
||||
}),
|
||||
onClick: handleClick,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SectionNotice from 'components/section_notice';
|
||||
|
||||
export default function NotificationPermissionUnsupportedSectionNotice() {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
window.open('https://mattermost.com/pl/pc-web-requirements', '_blank', 'noopener,noreferrer');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='extraContentBeforeSettingList'>
|
||||
<SectionNotice
|
||||
type='danger'
|
||||
title={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.title',
|
||||
defaultMessage: 'Browser notifications unsupported',
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.message',
|
||||
defaultMessage: 'You\'re missing important message and call notifications from Mattermost. To start receiving notifications, please update to a supported browser.',
|
||||
})}
|
||||
tertiaryButton={{
|
||||
text: intl.formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.button',
|
||||
defaultMessage: 'Update your browser',
|
||||
}),
|
||||
onClick: handleClick,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 * as utilsNotifications from 'utils/notifications';
|
||||
|
||||
import NotificationPermissionTitleTag from './index';
|
||||
|
||||
describe('NotificationPermissionTitleTag', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('should render "Not supported" tag when notifications are not supported', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(false);
|
||||
|
||||
renderWithContext(<NotificationPermissionTitleTag/>);
|
||||
|
||||
expect(screen.queryByText('Not supported')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render "Permission required" tag when permission is never granted yet', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionNeverGranted);
|
||||
|
||||
renderWithContext(<NotificationPermissionTitleTag/>);
|
||||
|
||||
expect(screen.queryByText('Permission required')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render "Permission required" tag when permission is denied', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionDenied);
|
||||
|
||||
renderWithContext(<NotificationPermissionTitleTag/>);
|
||||
|
||||
expect(screen.queryByText('Permission required')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render nothing when permission is granted', () => {
|
||||
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
|
||||
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionGranted);
|
||||
|
||||
const {container} = renderWithContext(<NotificationPermissionTitleTag/>);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import Tag from 'components/widgets/tag/tag';
|
||||
|
||||
import {
|
||||
getNotificationPermission,
|
||||
isNotificationAPISupported,
|
||||
NotificationPermissionDenied,
|
||||
NotificationPermissionNeverGranted,
|
||||
} from 'utils/notifications';
|
||||
|
||||
export default function NotificationPermissionTitleTag() {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
if (!isNotificationAPISupported()) {
|
||||
return (
|
||||
<Tag
|
||||
size='sm'
|
||||
variant='danger'
|
||||
icon='alert-outline'
|
||||
text={formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.noPermissionIssueTag',
|
||||
defaultMessage: 'Not supported',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
getNotificationPermission() === NotificationPermissionNeverGranted ||
|
||||
getNotificationPermission() === NotificationPermissionDenied
|
||||
) {
|
||||
return (
|
||||
<Tag
|
||||
size='sm'
|
||||
variant='dangerDim'
|
||||
icon='alert-outline'
|
||||
text={formatMessage({
|
||||
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionIssueTag',
|
||||
defaultMessage: 'Permission required',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import UserSettingsNotifications, {areDesktopAndMobileSettingsDifferent} from './user_settings_notifications';
|
||||
|
||||
jest.mock('components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice', () => () => <div/>);
|
||||
jest.mock('components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_title_tag', () => () => <div/>);
|
||||
|
||||
describe('components/user_settings/display/UserSettingsDisplay', () => {
|
||||
const defaultProps = {
|
||||
user: TestHelper.getUserMock({id: 'user_id'}),
|
||||
|
||||
@@ -9,7 +9,7 @@ import styled, {css} from 'styled-components';
|
||||
import glyphMap from '@mattermost/compass-icons/components';
|
||||
import type {IconGlyphTypes} from '@mattermost/compass-icons/IconGlyphs';
|
||||
|
||||
export type TagVariant = 'info' | 'success' | 'warning' | 'danger';
|
||||
export type TagVariant = 'info' | 'success' | 'warning' | 'danger' | 'dangerDim';
|
||||
|
||||
export type TagSize = 'xs' | 'sm' | 'md' | 'lg'
|
||||
|
||||
@@ -26,10 +26,6 @@ type Props = {
|
||||
type TagWrapperProps = Required<Pick<Props, 'uppercase'>>;
|
||||
|
||||
const TagWrapper = styled.div<TagWrapperProps>`
|
||||
--tag-bg: var(--semantic-color-general);
|
||||
--tag-bg-opacity: 0.08;
|
||||
--tag-color: var(--semantic-color-general);
|
||||
|
||||
appearance: none;
|
||||
|
||||
display: inline-flex;
|
||||
@@ -84,38 +80,39 @@ const TagWrapper = styled.div<TagWrapperProps>`
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
&.Tag--info,
|
||||
&.Tag--success,
|
||||
&.Tag--warning,
|
||||
&.Tag--danger {
|
||||
--tag-bg-opacity: 1;
|
||||
--tag-color: 255, 255, 255;
|
||||
}
|
||||
background: rgba(var(--semantic-color-general), 0.08);
|
||||
color: rgb(var(--semantic-color-general));
|
||||
|
||||
&.Tag--info {
|
||||
--tag-bg: var(--semantic-color-info);
|
||||
background: rgba(var(--semantic-color-info), 1);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
&.Tag--success {
|
||||
--tag-bg: var(--semantic-color-success);
|
||||
background: rgba(var(--semantic-color-success), 1);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
&.Tag--warning {
|
||||
--tag-bg: var(--semantic-color-warning);
|
||||
background: rgba(var(--semantic-color-warning), 1);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
&.Tag--danger {
|
||||
--tag-bg: var(--semantic-color-danger);
|
||||
background: rgba(var(--semantic-color-danger), 1);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
background: rgba(var(--tag-bg), var(--tag-bg-opacity));
|
||||
color: rgb(var(--tag-color));
|
||||
&.Tag--dangerDim {
|
||||
background: rgba(var(--semantic-color-danger), 0.08);
|
||||
color: rgb(var(--semantic-color-danger));
|
||||
}
|
||||
|
||||
${({onClick}) => typeof onClick === 'function' && (
|
||||
css`
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: rgba(var(--tag-bg), 0.08);
|
||||
background: rgba(var(--semantic-color-general), 0.08);
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -2892,12 +2892,14 @@
|
||||
"announcement_bar.error.trial_license_expiring_last_day": "This is the last day of your free trial. Purchase a license now to continue using Mattermost Professional and Enterprise features.",
|
||||
"announcement_bar.error.trial_license_expiring_last_day.short": "This is the last day of your free trial.",
|
||||
"announcement_bar.notification.email_verified": "Email verified",
|
||||
"announcement_bar.notification.enable_notifications": "Enable notifications",
|
||||
"announcement_bar.notification.needs_permission": "We need your permission to show desktop notifications.",
|
||||
"announcement_bar.warn.contact_support_email": "<a>Contact support</a>.",
|
||||
"announcement_bar.warn.contact_support_text": "To renew your license, contact support at support@mattermost.com.",
|
||||
"announcement_bar.warn.no_internet_connection": "Looks like you do not have access to the internet.",
|
||||
"announcement_bar.warn.renew_license_contact_sales": "Contact sales",
|
||||
"announcementBar.notification.permissionNeverGrantedBar.cta": "Enable notifications",
|
||||
"announcementBar.notification.permissionNeverGrantedBar.message": "We need your permission to show notifications in the browser.",
|
||||
"announcementBar.notification.unsupportedBar.cta": "Update your browser",
|
||||
"announcementBar.notification.unsupportedBar.message": "Your browser does not support browser notifications.",
|
||||
"api.channel.add_guest.added": "{addedUsername} added to the channel as a guest by {username}.",
|
||||
"api.channel.add_member.added": "{addedUsername} added to the channel by {username}.",
|
||||
"api.channel.delete_channel.archived": "{username} archived the channel.",
|
||||
@@ -5661,6 +5663,17 @@
|
||||
"user.settings.notifications.desktopAndMobile.noneDesktopButMobileMentions": "Never on desktop; mentions, direct messages, and group messages on mobile",
|
||||
"user.settings.notifications.desktopAndMobile.noneForDesktopAndMobile": "Never",
|
||||
"user.settings.notifications.desktopAndMobile.nothing": "Nothing",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.noPermissionIssueTag": "Not supported",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.button": "How to enable notifications",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.message": "You're missing important message and call notifications from Mattermost. To start receiving notifications, please enable notifications for Mattermost in your browser settings.",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.title": "Browser notification permission was denied",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionIssueTag": "Permission required",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.button": "Enable notifications",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.message": "You're missing important message and call notifications from Mattermost. Mattermost notifications are disabled by this browser.",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.title": "Browser notifications are disabled",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.button": "Update your browser",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.message": "You're missing important message and call notifications from Mattermost. To start receiving notifications, please update to a supported browser.",
|
||||
"user.settings.notifications.desktopAndMobile.notificationSection.permissionUnsupported.title": "Browser notifications unsupported",
|
||||
"user.settings.notifications.desktopAndMobile.notifyForDesktopthreads": "Notify me about replies to threads I'm following",
|
||||
"user.settings.notifications.desktopAndMobile.notifyForMobilethreads": "Notify me on mobile about replies to threads I'm following",
|
||||
"user.settings.notifications.desktopAndMobile.noValidSettings": "Configure desktop and mobile settings",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,6 +255,8 @@
|
||||
.section-max {
|
||||
@include pie-clearfix;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
margin-bottom: 0;
|
||||
background: rgba(var(--center-channel-color-rgb), 0.04);
|
||||
@@ -267,6 +269,11 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.extraContentBeforeSettingList {
|
||||
width: 100%;
|
||||
padding-block-start: 20px;
|
||||
}
|
||||
|
||||
.sectionContent {
|
||||
padding: 20px 0 0 0;
|
||||
|
||||
@@ -774,10 +781,13 @@
|
||||
}
|
||||
|
||||
.section-min__title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-right: 50px;
|
||||
margin: 0 0 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
gap: 8px;
|
||||
line-height: 20px;
|
||||
|
||||
&.isDisabled {
|
||||
|
||||
@@ -105,7 +105,15 @@ export function isNotificationAPISupported() {
|
||||
return ('Notification' in window) && (typeof Notification.requestPermission === 'function');
|
||||
}
|
||||
|
||||
export async function requestNotificationPermission() {
|
||||
export function getNotificationPermission(): NotificationPermission | null {
|
||||
if (!isNotificationAPISupported()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Notification.permission;
|
||||
}
|
||||
|
||||
export async function requestNotificationPermission(): Promise<NotificationPermission | null> {
|
||||
if (!isNotificationAPISupported()) {
|
||||
return null;
|
||||
}
|
||||
@@ -117,3 +125,7 @@ export async function requestNotificationPermission() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const NotificationPermissionNeverGranted = 'default';
|
||||
export const NotificationPermissionGranted = 'granted';
|
||||
export const NotificationPermissionDenied = 'denied';
|
||||
|
||||
Reference in New Issue
Block a user