diff --git a/app/javascript/mastodon/api_types/relationships.ts b/app/javascript/mastodon/api_types/relationships.ts index 9f26a0ce9b..aa871d6f79 100644 --- a/app/javascript/mastodon/api_types/relationships.ts +++ b/app/javascript/mastodon/api_types/relationships.ts @@ -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; diff --git a/app/javascript/mastodon/components/badge.stories.tsx b/app/javascript/mastodon/components/badge.stories.tsx index aaddcaa91a..6c4921809c 100644 --- a/app/javascript/mastodon/components/badge.stories.tsx +++ b/app/javascript/mastodon/components/badge.stories.tsx @@ -8,7 +8,7 @@ const meta = { component: badges.Badge, title: 'Components/Badge', args: { - label: 'Example', + label: undefined, }, } satisfies Meta; @@ -16,16 +16,22 @@ export default meta; type Story = StoryObj; -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: , }, }; @@ -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 ; + }, +}; + export const Blocked: Story = { render(args) { return ; diff --git a/app/javascript/mastodon/components/badge.tsx b/app/javascript/mastodon/components/badge.tsx index 0ffb7baa8a..07ecdfa46c 100644 --- a/app/javascript/mastodon/components/badge.tsx +++ b/app/javascript/mastodon/components/badge.tsx @@ -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 = ({ ); -export const AdminBadge: FC> = (props) => ( +export const AdminBadge: FC> = ({ label, ...props }) => ( } label={ - + label ?? ( + + ) } {...props} /> ); -export const GroupBadge: FC> = (props) => ( +export const GroupBadge: FC> = ({ label, ...props }) => ( } label={ - + label ?? ( + + ) } {...props} /> @@ -66,21 +70,54 @@ export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => ( /> ); -export const MutedBadge: FC> = (props) => ( - } - label={ - - } - {...props} - /> -); +export const MutedBadge: FC< + Partial & { 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 ( + } + label={ + label ?? + (formattedDate ? ( + + ) : ( + + )) + } + {...props} + /> + ); +}; -export const BlockedBadge: FC> = (props) => ( +export const BlockedBadge: FC> = ({ label, ...props }) => ( } label={ - + label ?? ( + + ) } {...props} /> diff --git a/app/javascript/mastodon/features/account_timeline/components/badges.tsx b/app/javascript/mastodon/features/account_timeline/components/badges.tsx index d48dc669f5..9e6d020c9a 100644 --- a/app/javascript/mastodon/features/account_timeline/components/badges.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/badges.tsx @@ -106,6 +106,7 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { , ); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index e89efa84d5..bdc08eecd0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -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", diff --git a/app/javascript/mastodon/models/relationship.ts b/app/javascript/mastodon/models/relationship.ts index 115b278738..450f408b33 100644 --- a/app/javascript/mastodon/models/relationship.ts +++ b/app/javascript/mastodon/models/relationship.ts @@ -15,8 +15,9 @@ const RelationshipFactory = Record({ following: false, id: '', languages: null, - muting_notifications: false, muting: false, + muting_notifications: false, + muting_expires_at: null, note: '', notifying: false, requested_by: false, diff --git a/app/javascript/testing/factories.ts b/app/javascript/testing/factories.ts index 6f2a45e58f..7855157f6a 100644 --- a/app/javascript/testing/factories.ts +++ b/app/javascript/testing/factories.ts @@ -99,10 +99,11 @@ export const relationshipsFactory: FactoryFunction = ({ 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, diff --git a/app/models/concerns/account/mappings.rb b/app/models/concerns/account/mappings.rb index b8b43cad7c..b44ff9c844 100644 --- a/app/models/concerns/account/mappings.rb +++ b/app/models/concerns/account/mappings.rb @@ -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 diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb index 4d7ed75935..221da77916 100644 --- a/app/serializers/rest/relationship_serializer.rb +++ b/app/serializers/rest/relationship_serializer.rb @@ -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 diff --git a/spec/models/concerns/account/mappings_spec.rb b/spec/models/concerns/account/mappings_spec.rb index 18c936b892..80c66aefde 100644 --- a/spec/models/concerns/account/mappings_spec.rb +++ b/spec/models/concerns/account/mappings_spec.rb @@ -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