Redesign: Re-add "mark all as read" button to notifications page (#40667)

This commit is contained in:
diondiondion
2026-09-25 14:23:28 +00:00
committed by GitHub
parent a869429421
commit 0f5aa7194f
9 changed files with 202 additions and 118 deletions

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
.lockup {
--lockup-padding-block: var(--space-3);
background: var(--color-bg-secondary);
border-radius: var(--radius-3);
}

View File

@@ -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>
);

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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 }}
/>
}
/>
);
};

View File

@@ -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}

View File

@@ -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;
}
}

View File

@@ -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",