Redesign: Move server announcements to notifications page (#40674)

This commit is contained in:
diondiondion
2026-09-24 09:31:18 +00:00
committed by GitHub
parent e7de0c1c92
commit 4b160e3811
13 changed files with 189 additions and 31 deletions

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from 'react';
import type {
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
import { fetchAnnouncements } from '@/mastodon/actions/announcements';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
type AnnouncementItem = ImmutableRecord<{
id: string;
starts_at: string;
published_at: string;
read: boolean;
}>;
type AnnouncementsState = ImmutableRecord<{
items: ImmutableList<AnnouncementItem>;
isLoading: boolean;
show: boolean;
}>;
export function useHasAnnouncements({
fetch = true,
}: { fetch?: boolean } = {}) {
const dispatch = useAppDispatch();
const hasAnnouncements = useAppSelector(
(state) =>
!(state.announcements as AnnouncementsState).get('items').isEmpty(),
);
const shouldShowAnnouncements = useAppSelector((state) =>
(state.announcements as AnnouncementsState).get('show'),
);
const unreadAnnouncementCount = useAppSelector((state) =>
(state.announcements as AnnouncementsState)
.get('items')
.count((item) => !item.get('read')),
);
const hasUnreadAnnouncements = !!unreadAnnouncementCount;
// Announcements display is transient – it's toggled on when
// there are unread announcements or the `show` Redux state is
// enabled, but resets to hidden when the page is left (by virtue
// of React simply losing this state).
const [showAnnouncements, setShowAnnouncements] = useState(false);
if (
(hasUnreadAnnouncements || shouldShowAnnouncements) &&
!showAnnouncements
) {
setShowAnnouncements(true);
}
useEffect(() => {
const timeout = setTimeout(() => {
if (fetch) {
dispatch(fetchAnnouncements());
}
}, 700);
return () => {
clearTimeout(timeout);
};
}, [fetch, dispatch]);
return {
hasAnnouncements,
hasUnreadAnnouncements,
showAnnouncements,
unreadAnnouncementCount,
};
}

View File

@@ -19,6 +19,7 @@ import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { withBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint';
import { Announcements } from '@/mastodon/features/announcements';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { expandHomeTimeline } from '../../actions/timelines';
@@ -26,7 +27,6 @@ import StatusListContainer from '../ui/containers/status_list_container';
import { ColumnSettings } from './components/column_settings';
import { CriticalUpdateBanner } from './components/critical_update_banner';
import { Announcements } from './components/announcements';
import { AnnualReportTimeline } from '../annual_report/timeline';
import { isRedesignEnabled } from '@/mastodon/utils/environment';
import { ColumnHeader } from '@/mastodon/components/column_header';
@@ -83,7 +83,9 @@ class HomeTimeline extends PureComponent {
};
componentDidMount () {
setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700);
if (!isRedesignEnabled()) {
setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700);
}
this._checkIfReloadNeeded(false, this.props.isPartial);
}

View File

@@ -29,6 +29,8 @@ import { selectUnreadNotificationGroupsCount } from '@/mastodon/selectors/notifi
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { invokeVirtualIosKeyboard } from '@/mastodon/utils/invoke_virtual_ios_keyboard';
import { useHasAnnouncements } from '../../announcements/hooks';
import { NavigationAccountCardAndMenu } from './account_card_and_menu';
import { NavigationFooterLinks } from './footer_links';
import { NavigationHeader } from './header';
@@ -76,6 +78,15 @@ function useFollowedHashtags() {
return { followedHashtags: tags };
}
export function useNotificationsCount() {
const unreadNotificationsCount = useAppSelector(
selectUnreadNotificationGroupsCount,
);
const { unreadAnnouncementCount } = useHasAnnouncements();
return unreadNotificationsCount + unreadAnnouncementCount;
}
const isFediverseFeedsLinkActive = (
match: unknown,
{ pathname }: { pathname: string },
@@ -97,9 +108,7 @@ export const RedesignNavigationPanel: React.FC<{
const intl = useIntl();
const dispatch = useAppDispatch();
const { signedIn } = useIdentity();
const notificationsCount = useAppSelector(
selectUnreadNotificationGroupsCount,
);
const notificationsCount = useNotificationsCount();
const openComposer = useCallback(() => {
dispatch(closeNavigation());

View File

@@ -23,11 +23,10 @@ import { FOCUS_TARGET } from '@/mastodon/components/navigation_focus_target';
import { ComposeRedesignButton } from '@/mastodon/features/compose/redesign/trigger';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useIdentity } from '@/mastodon/identity_context';
import { selectUnreadNotificationGroupsCount } from '@/mastodon/selectors/notifications';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { invokeVirtualIosKeyboard } from '@/mastodon/utils/invoke_virtual_ios_keyboard';
import { RedesignNavigationPanel } from '.';
import { RedesignNavigationPanel, useNotificationsCount } from '.';
import { AccountMenuItems } from './account_card_and_menu';
import { LogoLockup } from './header';
import classes from './mobile_nav.module.scss';
@@ -38,9 +37,7 @@ export const RedesignMobileNavigation: React.FC = () => {
const { accountId, signedIn } = useIdentity();
const account = useAccount(accountId);
const notificationsCount = useAppSelector(
selectUnreadNotificationGroupsCount,
);
const notificationsCount = useNotificationsCount();
const openMobileNav = useCallback(() => {
dispatch(openNavigation());

View File

@@ -1,21 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react';
import { Icon } from 'mastodon/components/icon';
export default class ClearColumnButton extends PureComponent {
static propTypes = {
onClick: PropTypes.func.isRequired,
};
render () {
return (
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' icon={DeleteForeverIcon} /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
);
}
}

View File

@@ -0,0 +1,46 @@
import { FormattedMessage } from 'react-intl';
import { TrashIcon } from '@phosphor-icons/react';
import { Button } from '@/mastodon/components/button/redesign';
import { isRedesignEnabled } from '@/mastodon/utils/environment';
import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react';
import { Icon } from 'mastodon/components/icon';
const ClearColumnButton: React.FC<{
onClick: React.MouseEventHandler<HTMLButtonElement>;
}> = ({ onClick }) => {
if (isRedesignEnabled()) {
return (
<Button
size='sm'
onClick={onClick}
leadingIcon={TrashIcon}
color='destructive'
variant='ghost'
clipPadding
>
<FormattedMessage
id='notifications.clear'
defaultMessage='Clear notifications'
/>
</Button>
);
}
return (
<button
type='button'
className='text-btn column-header__setting-btn'
onClick={onClick}
>
<Icon id='eraser' icon={DeleteForeverIcon} />{' '}
<FormattedMessage
id='notifications.clear'
defaultMessage='Clear notifications'
/>
</button>
);
};
// eslint-disable-next-line import/no-default-export
export default ClearColumnButton;

View File

@@ -13,6 +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';
class ColumnSettings extends PureComponent {
static propTypes = {
@@ -48,6 +49,7 @@ class ColumnSettings extends PureComponent {
<div className='column-settings'>
<section>
<ClearColumnButton onClick={onClear} />
<ShowAnnouncementsButton />
</section>
{alertsEnabled && browserSupport && browserPermission === 'denied' && (

View File

@@ -0,0 +1,45 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { NewspaperIcon } from '@phosphor-icons/react';
import { toggleShowAnnouncements } 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, showAnnouncements } = useHasAnnouncements({
fetch: false,
});
const handleClick = useCallback(() => {
dispatch(toggleShowAnnouncements());
dispatch(
closeModal({ modalType: 'NOTIFICATION_SETTINGS', ignoreFocus: false }),
);
}, [dispatch]);
if (!isRedesignEnabled() || !hasAnnouncements || showAnnouncements) {
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

@@ -52,6 +52,8 @@ import {
} from 'mastodon/selectors/settings';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { Announcements } from '../announcements';
import { useHasAnnouncements } from '../announcements/hooks';
import {
FilteredNotificationsBanner,
FilteredNotificationsIconButton,
@@ -220,9 +222,12 @@ export const Notifications: React.FC<{
);
}, [notifications, isLoading, hasMore, lastReadId, handleLoadGap]);
const { showAnnouncements } = useHasAnnouncements();
const prepend = (
<>
{needsNotificationPermission && <NotificationsPermissionBanner />}
{showAnnouncements && <Announcements />}
<FilteredNotificationsBanner />
</>
);

View File

@@ -1204,6 +1204,7 @@
"notifications.policy.filter_private_mentions_title": "Unsolicited private mentions",
"notifications.policy.title": "Manage notifications from…",
"notifications.settings": "Notification Settings",
"notifications.show_server_announcements": "Show server announcements",
"notifications_permission_banner.enable": "Enable desktop notifications",
"notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
"notifications_permission_banner.title": "Never miss a thing",