mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-27 14:48:13 -05:00
Redesign: Re-add "mark all as read" button to notifications page (#40667)
This commit is contained in:
@@ -13,7 +13,7 @@ import GrantPermissionButton from './grant_permission_button';
|
||||
import { PolicyControls } from './policy_controls';
|
||||
import SettingToggle from './setting_toggle';
|
||||
import { ColumnSettingsGroup } from './column_settings_group';
|
||||
import { ShowAnnouncementsButton } from './show_announcements_button';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
class ColumnSettings extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -47,10 +47,11 @@ class ColumnSettings extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className='column-settings'>
|
||||
<section>
|
||||
<ClearColumnButton onClick={onClear} />
|
||||
<ShowAnnouncementsButton />
|
||||
</section>
|
||||
{!isRedesignEnabled() &&
|
||||
<section>
|
||||
<ClearColumnButton onClick={onClear} />
|
||||
</section>
|
||||
}
|
||||
|
||||
{alertsEnabled && browserSupport && browserPermission === 'denied' && (
|
||||
<section>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.lockup {
|
||||
--lockup-padding-block: var(--space-3);
|
||||
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-3);
|
||||
}
|
||||
@@ -4,12 +4,19 @@ import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { TrayIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { LockupLink, LockupWrapper } from '@/mastodon/components/lockup';
|
||||
import type { MastodonLocationDescriptor } from '@/mastodon/components/router';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
|
||||
import { fetchNotificationPolicy } from 'mastodon/actions/notification_policies';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { selectSettingsNotificationsMinimizeFilteredBanner } from 'mastodon/selectors/settings';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import classes from './filtered_notifications_banner.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
filteredNotifications: {
|
||||
id: 'notification_requests.title',
|
||||
@@ -80,28 +87,61 @@ export const FilteredNotificationsBanner: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className='filtered-notifications-banner'
|
||||
<LinkBanner
|
||||
to='/notifications/requests'
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon icon={InventoryIcon} id='filtered-notifications' />
|
||||
</div>
|
||||
icon={
|
||||
isRedesignEnabled() ? (
|
||||
<TrayIcon size={24} />
|
||||
) : (
|
||||
<Icon icon={InventoryIcon} id='filtered-notifications' />
|
||||
)
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='filtered_notifications_banner.title'
|
||||
defaultMessage='Filtered notifications'
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id='filtered_notifications_banner.pending_requests'
|
||||
defaultMessage='From {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
||||
values={{ count: policy.summary.pending_requests_count }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface LinkBannerProps {
|
||||
to: MastodonLocationDescriptor;
|
||||
icon: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
subtitle: React.ReactNode;
|
||||
}
|
||||
|
||||
export const LinkBanner: React.FC<LinkBannerProps> = ({
|
||||
to,
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
}) => {
|
||||
if (isRedesignEnabled()) {
|
||||
return (
|
||||
<LockupWrapper icon={icon} className={classes.lockup}>
|
||||
<LockupLink to={to} subtitle={subtitle}>
|
||||
{title}
|
||||
</LockupLink>
|
||||
</LockupWrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link className='filtered-notifications-banner' to={to}>
|
||||
<div className='notification-group__icon'>{icon}</div>
|
||||
|
||||
<div className='filtered-notifications-banner__text'>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='filtered_notifications_banner.title'
|
||||
defaultMessage='Filtered notifications'
|
||||
/>
|
||||
</strong>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='filtered_notifications_banner.pending_requests'
|
||||
defaultMessage='From {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
||||
values={{ count: policy.summary.pending_requests_count }}
|
||||
/>
|
||||
</span>
|
||||
<strong>{title}</strong>
|
||||
<span>{subtitle}</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -2,13 +2,16 @@ import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { messages as columnHeaderMessages } from '@/mastodon/components/column/header';
|
||||
import { GearIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { Button } from '@/mastodon/components/button/redesign';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import UnfoldMoreIcon from '@/material-icons/400-24px/unfold_more.svg?react';
|
||||
import { requestBrowserPermission } from 'mastodon/actions/notifications';
|
||||
import { changeSetting } from 'mastodon/actions/settings';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Button as LegacyButton } from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
|
||||
@@ -53,19 +56,31 @@ const NotificationsPermissionBanner: React.FC = () => {
|
||||
icon: (
|
||||
<Icon
|
||||
id='sliders'
|
||||
icon={UnfoldMoreIcon}
|
||||
aria-label={intl.formatMessage(columnHeaderMessages.show)}
|
||||
icon={isRedesignEnabled() ? GearIcon : UnfoldMoreIcon}
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'notifications.settings',
|
||||
defaultMessage: 'Notification Settings',
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<Button onClick={handleClick}>
|
||||
<FormattedMessage
|
||||
id='notifications_permission_banner.enable'
|
||||
defaultMessage='Enable desktop notifications'
|
||||
/>
|
||||
</Button>
|
||||
{isRedesignEnabled() ? (
|
||||
<Button onClick={handleClick} variant='solid' size='sm'>
|
||||
<FormattedMessage
|
||||
id='notifications_permission_banner.enable'
|
||||
defaultMessage='Enable desktop notifications'
|
||||
/>
|
||||
</Button>
|
||||
) : (
|
||||
<LegacyButton onClick={handleClick}>
|
||||
<FormattedMessage
|
||||
id='notifications_permission_banner.enable'
|
||||
defaultMessage='Enable desktop notifications'
|
||||
/>
|
||||
</LegacyButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { NewspaperIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { showAnnouncements } from '@/mastodon/actions/announcements';
|
||||
import { closeModal } from '@/mastodon/actions/modal';
|
||||
import { Button } from '@/mastodon/components/button/redesign';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import { useHasAnnouncements } from '../../announcements/hooks';
|
||||
|
||||
export const ShowAnnouncementsButton: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { hasAnnouncements, shouldShowAnnouncements } = useHasAnnouncements({
|
||||
fetch: false,
|
||||
});
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
dispatch(showAnnouncements());
|
||||
dispatch(
|
||||
closeModal({ modalType: 'NOTIFICATION_SETTINGS', ignoreFocus: false }),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
if (!isRedesignEnabled() || !hasAnnouncements || shouldShowAnnouncements) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
size='sm'
|
||||
leadingIcon={NewspaperIcon}
|
||||
variant='ghost'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='notifications.show_server_announcements'
|
||||
defaultMessage='Show server announcements'
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,9 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { UserPlusIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import { useFollowRequestsCount } from '../../navigation_panel/redesign';
|
||||
import { LinkBanner } from '../../notifications/components/filtered_notifications_banner';
|
||||
|
||||
/**
|
||||
* This banner is only shown to users whose notification policy filters
|
||||
@@ -21,26 +18,22 @@ export const FollowRequestsBanner: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className='filtered-notifications-banner' to='/follow_requests'>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon icon={UserPlusIcon} id='filtered-notifications' />
|
||||
</div>
|
||||
|
||||
<div className='filtered-notifications-banner__text'>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='column.follow_requests'
|
||||
defaultMessage='Follow requests'
|
||||
/>
|
||||
</strong>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='follow_requests.pending_requests'
|
||||
defaultMessage='From {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
||||
values={{ count: followRequestsCount }}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<LinkBanner
|
||||
to='/follow_requests'
|
||||
icon={<UserPlusIcon size={24} />}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='column.follow_requests'
|
||||
defaultMessage='Follow requests'
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id='follow_requests.pending_requests'
|
||||
defaultMessage='From {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
||||
values={{ count: followRequestsCount }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,17 @@ import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { GearIcon } from '@phosphor-icons/react';
|
||||
import {
|
||||
ChecksIcon,
|
||||
GearIcon,
|
||||
NewspaperIcon,
|
||||
TrashIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { toggleShowAnnouncements } from '@/mastodon/actions/announcements';
|
||||
import {
|
||||
addColumn,
|
||||
removeColumn,
|
||||
@@ -23,6 +29,7 @@ import {
|
||||
} from '@/mastodon/components/column_header';
|
||||
import { MultiColumnMenuItems } from '@/mastodon/components/column_header/multicolumn_settings';
|
||||
import { LoadGap } from '@/mastodon/components/load_gap';
|
||||
import { MenuItem, MenuItemDivider } from '@/mastodon/components/menu';
|
||||
import ScrollableList from '@/mastodon/components/scrollable_list';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react';
|
||||
@@ -64,6 +71,7 @@ import ColumnSettingsContainer from '../notifications/containers/column_settings
|
||||
import { FollowRequestsBanner } from './components/follow_requests_banner';
|
||||
import { NotificationGroup } from './components/notification_group';
|
||||
import { FilterBar } from './filter_bar';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||
@@ -71,6 +79,10 @@ const messages = defineMessages({
|
||||
id: 'notifications.mark_as_read',
|
||||
defaultMessage: 'Mark every notification as read',
|
||||
},
|
||||
markAsReadRedesign: {
|
||||
id: 'notifications.mark_all_as_read',
|
||||
defaultMessage: 'Mark all as read',
|
||||
},
|
||||
});
|
||||
|
||||
export const Notifications: React.FC<{
|
||||
@@ -172,11 +184,6 @@ export const Notifications: React.FC<{
|
||||
[dispatch, columnId],
|
||||
);
|
||||
|
||||
const handleMarkAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead());
|
||||
void dispatch(submitMarkers({ immediate: true }));
|
||||
}, [dispatch]);
|
||||
|
||||
const openSettingsModal = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
@@ -186,6 +193,21 @@ export const Notifications: React.FC<{
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
const handleMarkAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead());
|
||||
void dispatch(submitMarkers({ immediate: true }));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleToggleAnnouncements = useCallback(() => {
|
||||
dispatch(toggleShowAnnouncements());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClearNotifications = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({ modalType: 'CONFIRM_CLEAR_NOTIFICATIONS', modalProps: {} }),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
const pinned = !!columnId;
|
||||
const emptyMessage = (
|
||||
<FormattedMessage
|
||||
@@ -223,14 +245,16 @@ export const Notifications: React.FC<{
|
||||
);
|
||||
}, [notifications, isLoading, hasMore, lastReadId, handleLoadGap]);
|
||||
|
||||
const { shouldShowAnnouncements } = useHasAnnouncements();
|
||||
const { hasAnnouncements, shouldShowAnnouncements } = useHasAnnouncements();
|
||||
|
||||
const prepend = (
|
||||
<>
|
||||
{needsNotificationPermission && <NotificationsPermissionBanner />}
|
||||
{shouldShowAnnouncements && <Announcements />}
|
||||
<FilteredNotificationsBanner />
|
||||
<FollowRequestsBanner />
|
||||
<div className={isRedesignEnabled() ? classes.topLinks : undefined}>
|
||||
{isRedesignEnabled() && <FollowRequestsBanner />}
|
||||
<FilteredNotificationsBanner />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -292,17 +316,54 @@ export const Notifications: React.FC<{
|
||||
defaultMessage='Notification Settings'
|
||||
/>
|
||||
</ColumnHeaderButton>
|
||||
{multiColumn && (
|
||||
<ColumnSettingsMenu
|
||||
labelPrefix={intl.formatMessage(messages.title)}
|
||||
<ColumnSettingsMenu
|
||||
labelPrefix={intl.formatMessage(messages.title)}
|
||||
>
|
||||
<MenuItem
|
||||
disabled={!canMarkAsRead}
|
||||
onClick={handleMarkAsRead}
|
||||
icon={ChecksIcon}
|
||||
>
|
||||
{intl.formatMessage(messages.markAsReadRedesign)}
|
||||
</MenuItem>
|
||||
{hasAnnouncements && (
|
||||
<MenuItem
|
||||
onClick={handleToggleAnnouncements}
|
||||
icon={NewspaperIcon}
|
||||
>
|
||||
{shouldShowAnnouncements ? (
|
||||
<FormattedMessage
|
||||
id='notifications.hide_server_announcements'
|
||||
defaultMessage='Hide server announcements'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='notifications.show_server_announcements'
|
||||
defaultMessage='Show server announcements'
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItemDivider />
|
||||
<MenuItem
|
||||
destructive
|
||||
icon={TrashIcon}
|
||||
onClick={handleClearNotifications}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='notifications.clear'
|
||||
defaultMessage='Clear notifications'
|
||||
/>
|
||||
</MenuItem>
|
||||
{multiColumn && (
|
||||
<MultiColumnMenuItems
|
||||
withDivider
|
||||
pinned={pinned}
|
||||
onPin={handlePin}
|
||||
onMove={handleMove}
|
||||
/>
|
||||
</ColumnSettingsMenu>
|
||||
)}
|
||||
)}
|
||||
</ColumnSettingsMenu>
|
||||
</>
|
||||
}
|
||||
extraStickyContent={filterBar}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.topLinks {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
margin: var(--space-4) var(--space-4) var(--space-2);
|
||||
|
||||
> * {
|
||||
min-width: 250px;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
@@ -1183,6 +1183,8 @@
|
||||
"notifications.filter.statuses": "Updates from people you follow",
|
||||
"notifications.grant_permission": "Grant permission.",
|
||||
"notifications.group": "{count} notifications",
|
||||
"notifications.hide_server_announcements": "Hide server announcements",
|
||||
"notifications.mark_all_as_read": "Mark all as read",
|
||||
"notifications.mark_as_read": "Mark every notification as read",
|
||||
"notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
|
||||
"notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
|
||||
|
||||
Reference in New Issue
Block a user