Fix keyboard shortcuts and navigation in grouped notifications (#31076)
This commit is contained in:
parent
55705d8191
commit
af06d74574
7 changed files with 188 additions and 66 deletions
|
@ -2,8 +2,10 @@ import { useMemo } from 'react';
|
|||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { navigateToProfile } from 'mastodon/actions/accounts';
|
||||
import { mentionComposeById } from 'mastodon/actions/compose';
|
||||
import type { NotificationGroup as NotificationGroupModel } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
|
@ -30,6 +32,13 @@ export const NotificationGroup: React.FC<{
|
|||
),
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accountId =
|
||||
notificationGroup?.type === 'gap'
|
||||
? undefined
|
||||
: notificationGroup?.sampleAccountIds[0];
|
||||
|
||||
const handlers = useMemo(
|
||||
() => ({
|
||||
moveUp: () => {
|
||||
|
@ -39,8 +48,16 @@ export const NotificationGroup: React.FC<{
|
|||
moveDown: () => {
|
||||
onMoveDown(notificationGroupId);
|
||||
},
|
||||
|
||||
openProfile: () => {
|
||||
if (accountId) dispatch(navigateToProfile(accountId));
|
||||
},
|
||||
|
||||
mention: () => {
|
||||
if (accountId) dispatch(mentionComposeById(accountId));
|
||||
},
|
||||
}),
|
||||
[notificationGroupId, onMoveUp, onMoveDown],
|
||||
[dispatch, notificationGroupId, accountId, onMoveUp, onMoveDown],
|
||||
);
|
||||
|
||||
if (!notificationGroup || notificationGroup.type === 'gap') return null;
|
||||
|
|
|
@ -2,9 +2,14 @@ import { useMemo } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { replyComposeById } from 'mastodon/actions/compose';
|
||||
import { navigateToStatus } from 'mastodon/actions/statuses';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { AvatarGroup } from './avatar_group';
|
||||
import { EmbeddedStatus } from './embedded_status';
|
||||
|
@ -39,6 +44,8 @@ export const NotificationGroupWithStatus: React.FC<{
|
|||
type,
|
||||
unread,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const label = useMemo(
|
||||
() =>
|
||||
labelRenderer({
|
||||
|
@ -53,39 +60,54 @@ export const NotificationGroupWithStatus: React.FC<{
|
|||
[labelRenderer, accountIds, count, labelSeeMoreHref],
|
||||
);
|
||||
|
||||
const handlers = useMemo(
|
||||
() => ({
|
||||
open: () => {
|
||||
dispatch(navigateToStatus(statusId));
|
||||
},
|
||||
|
||||
reply: () => {
|
||||
dispatch(replyComposeById(statusId));
|
||||
},
|
||||
}),
|
||||
[dispatch, statusId],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
`notification-group focusable notification-group--${type}`,
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon icon={icon} id={iconId} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__wrapper'>
|
||||
<AvatarGroup accountIds={accountIds} />
|
||||
|
||||
{actions}
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main__header__label'>
|
||||
{label}
|
||||
{timestamp && <RelativeTimestamp timestamp={timestamp} />}
|
||||
</div>
|
||||
<HotKeys handlers={handlers}>
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
`notification-group focusable notification-group--${type}`,
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon icon={icon} id={iconId} />
|
||||
</div>
|
||||
|
||||
{statusId && (
|
||||
<div className='notification-group__main__status'>
|
||||
<EmbeddedStatus statusId={statusId} />
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__wrapper'>
|
||||
<AvatarGroup accountIds={accountIds} />
|
||||
|
||||
{actions}
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main__header__label'>
|
||||
{label}
|
||||
{timestamp && <RelativeTimestamp timestamp={timestamp} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{statusId && (
|
||||
<div className='notification-group__main__status'>
|
||||
<EmbeddedStatus statusId={statusId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,10 +2,18 @@ import { useMemo } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { replyComposeById } from 'mastodon/actions/compose';
|
||||
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
||||
import {
|
||||
navigateToStatus,
|
||||
toggleStatusSpoilers,
|
||||
} from 'mastodon/actions/statuses';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import Status from 'mastodon/containers/status_container';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { NamesList } from './names_list';
|
||||
import type { LabelRenderer } from './notification_group_with_status';
|
||||
|
@ -29,6 +37,8 @@ export const NotificationWithStatus: React.FC<{
|
|||
type,
|
||||
unread,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const label = useMemo(
|
||||
() =>
|
||||
labelRenderer({
|
||||
|
@ -41,33 +51,61 @@ export const NotificationWithStatus: React.FC<{
|
|||
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
`notification-ungrouped focusable notification-ungrouped--${type}`,
|
||||
{
|
||||
'notification-ungrouped--unread': unread,
|
||||
'notification-ungrouped--direct': isPrivateMention,
|
||||
},
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-ungrouped__header'>
|
||||
<div className='notification-ungrouped__header__icon'>
|
||||
<Icon icon={icon} id={iconId} />
|
||||
</div>
|
||||
{label}
|
||||
</div>
|
||||
const handlers = useMemo(
|
||||
() => ({
|
||||
open: () => {
|
||||
dispatch(navigateToStatus(statusId));
|
||||
},
|
||||
|
||||
<Status
|
||||
// @ts-expect-error -- <Status> is not yet typed
|
||||
id={statusId}
|
||||
contextType='notifications'
|
||||
withDismiss
|
||||
skipPrepend
|
||||
avatarSize={40}
|
||||
/>
|
||||
</div>
|
||||
reply: () => {
|
||||
dispatch(replyComposeById(statusId));
|
||||
},
|
||||
|
||||
boost: () => {
|
||||
dispatch(toggleReblog(statusId));
|
||||
},
|
||||
|
||||
favourite: () => {
|
||||
dispatch(toggleFavourite(statusId));
|
||||
},
|
||||
|
||||
toggleHidden: () => {
|
||||
dispatch(toggleStatusSpoilers(statusId));
|
||||
},
|
||||
}),
|
||||
[dispatch, statusId],
|
||||
);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
`notification-ungrouped focusable notification-ungrouped--${type}`,
|
||||
{
|
||||
'notification-ungrouped--unread': unread,
|
||||
'notification-ungrouped--direct': isPrivateMention,
|
||||
},
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-ungrouped__header'>
|
||||
<div className='notification-ungrouped__header__icon'>
|
||||
<Icon icon={icon} id={iconId} />
|
||||
</div>
|
||||
{label}
|
||||
</div>
|
||||
|
||||
<Status
|
||||
// @ts-expect-error -- <Status> is not yet typed
|
||||
id={statusId}
|
||||
contextType='notifications'
|
||||
withDismiss
|
||||
skipPrepend
|
||||
avatarSize={40}
|
||||
unfocusable
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue