Show mute end date in badge (#37747)

This commit is contained in:
Echo
2026-02-05 21:01:28 +01:00
committed by GitHub
parent acdd0b33a3
commit 7a4945c0d3
10 changed files with 84 additions and 24 deletions

View File

@@ -8,8 +8,9 @@ export interface ApiRelationshipJSON {
following: boolean;
id: string;
languages: string[] | null;
muting_notifications: boolean;
muting: boolean;
muting_notifications: boolean;
muting_expires_at: string | null;
note: string;
notifying: boolean;
requested_by: boolean;

View File

@@ -8,7 +8,7 @@ const meta = {
component: badges.Badge,
title: 'Components/Badge',
args: {
label: 'Example',
label: undefined,
},
} satisfies Meta<typeof badges.Badge>;
@@ -16,16 +16,22 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Default: Story = {
args: {
label: 'Example',
},
};
export const Domain: Story = {
args: {
...Default.args,
domain: 'example.com',
},
};
export const CustomIcon: Story = {
args: {
...Default.args,
icon: <CelebrationIcon />,
},
};
@@ -57,6 +63,13 @@ export const Muted: Story = {
},
};
export const MutedWithDate: Story = {
render(args) {
const futureDate = new Date(new Date().getFullYear(), 11, 31).toISOString();
return <badges.MutedBadge {...args} expiresAt={futureDate} />;
},
};
export const Blocked: Story = {
render(args) {
return <badges.BlockedBadge {...args} />;

View File

@@ -1,6 +1,6 @@
import type { FC, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
@@ -36,21 +36,25 @@ export const Badge: FC<BadgeProps> = ({
</div>
);
export const AdminBadge: FC<Partial<BadgeProps>> = (props) => (
export const AdminBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
<Badge
icon={<AdminIcon />}
label={
<FormattedMessage id='account.badges.admin' defaultMessage='Admin' />
label ?? (
<FormattedMessage id='account.badges.admin' defaultMessage='Admin' />
)
}
{...props}
/>
);
export const GroupBadge: FC<Partial<BadgeProps>> = (props) => (
export const GroupBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
<Badge
icon={<GroupsIcon />}
label={
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
label ?? (
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
)
}
{...props}
/>
@@ -66,21 +70,54 @@ export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
/>
);
export const MutedBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<VolumeOffIcon />}
label={
<FormattedMessage id='account.badges.muted' defaultMessage='Muted' />
}
{...props}
/>
);
export const MutedBadge: FC<
Partial<BadgeProps> & { expiresAt?: string | null }
> = ({ expiresAt, label, ...props }) => {
// Format the date, only showing the year if it's different from the current year.
const intl = useIntl();
let formattedDate: string | null = null;
if (expiresAt) {
const expiresDate = new Date(expiresAt);
const isCurrentYear =
expiresDate.getFullYear() === new Date().getFullYear();
formattedDate = intl.formatDate(expiresDate, {
month: 'short',
day: 'numeric',
...(isCurrentYear ? {} : { year: 'numeric' }),
});
}
return (
<Badge
icon={<VolumeOffIcon />}
label={
label ??
(formattedDate ? (
<FormattedMessage
id='account.badges.muted_until'
defaultMessage='Muted until {until}'
values={{
until: formattedDate,
}}
/>
) : (
<FormattedMessage id='account.badges.muted' defaultMessage='Muted' />
))
}
{...props}
/>
);
};
export const BlockedBadge: FC<Partial<BadgeProps>> = (props) => (
export const BlockedBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
<Badge
icon={<BlockIcon />}
label={
<FormattedMessage id='account.badges.blocked' defaultMessage='Blocked' />
label ?? (
<FormattedMessage
id='account.badges.blocked'
defaultMessage='Blocked'
/>
)
}
{...props}
/>

View File

@@ -106,6 +106,7 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
<MutedBadge
key='muted-badge'
className={classNames(className, classes.badgeMuted)}
expiresAt={relationship.muting_expires_at}
/>,
);
}

View File

@@ -23,6 +23,7 @@
"account.badges.domain_blocked": "Blocked domain",
"account.badges.group": "Group",
"account.badges.muted": "Muted",
"account.badges.muted_until": "Muted until {until}",
"account.block": "Block @{name}",
"account.block_domain": "Block domain {domain}",
"account.block_short": "Block",

View File

@@ -15,8 +15,9 @@ const RelationshipFactory = Record<RelationshipShape>({
following: false,
id: '',
languages: null,
muting_notifications: false,
muting: false,
muting_notifications: false,
muting_expires_at: null,
note: '',
notifying: false,
requested_by: false,

View File

@@ -99,10 +99,11 @@ export const relationshipsFactory: FactoryFunction<ApiRelationshipJSON> = ({
blocking: false,
blocked_by: false,
languages: null,
muting: false,
muting_notifications: false,
muting_expires_at: null,
note: '',
requested_by: false,
muting: false,
requested: false,
domain_blocking: false,
endorsed: false,

View File

@@ -39,6 +39,7 @@ module Account::Mappings
Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
mapping[mute.target_account_id] = {
notifications: mute.hide_notifications?,
expires_at: mute.expires_at,
}
end
end

View File

@@ -4,7 +4,7 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
# Please update `app/javascript/mastodon/api_types/relationships.ts` when making changes to the attributes
attributes :id, :following, :showing_reblogs, :notifying, :languages, :followed_by,
:blocking, :blocked_by, :muting, :muting_notifications,
:blocking, :blocked_by, :muting, :muting_notifications, :muting_expires_at,
:requested, :requested_by, :domain_blocking, :endorsed, :note
def id
@@ -52,6 +52,10 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
(instance_options[:relationships].muting[object.id] || {})[:notifications] || false
end
def muting_expires_at
(instance_options[:relationships].muting[object.id] || {})[:expires_at]&.iso8601
end
def requested
instance_options[:relationships].requested[object.id] ? true : false
end

View File

@@ -93,13 +93,13 @@ RSpec.describe Account::Mappings do
context 'when Mute#hide_notifications?' do
let(:hide) { true }
it { is_expected.to eq(target_account_id => { notifications: true }) }
it { is_expected.to eq(target_account_id => { expires_at: nil, notifications: true }) }
end
context 'when not Mute#hide_notifications?' do
let(:hide) { false }
it { is_expected.to eq(target_account_id => { notifications: false }) }
it { is_expected.to eq(target_account_id => { expires_at: nil, notifications: false }) }
end
end