diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index a0024bbf6..647672e05 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -83,6 +83,62 @@ const LimitedAccountHint: React.FC<{ accountId: string }> = ({ accountId }) => { ); }; +const FilteredQuote: React.FC<{ + reveal: VoidFunction; + quotedAccountId: string; + quoteState: string; +}> = ({ reveal, quotedAccountId, quoteState }) => { + const account = useAppSelector((state) => + quotedAccountId ? state.accounts.get(quotedAccountId) : undefined, + ); + + const quoteAuthorName = account?.acct; + const domain = quoteAuthorName?.split('@')[1]; + + let message; + + switch (quoteState) { + case 'blocked_account': + message = ( + + ); + break; + case 'blocked_domain': + message = ( + + ); + break; + case 'muted_account': + message = ( + + ); + } + + return ( + <> + {message} + + + ); +}; + interface QuotedStatusProps { quote: QuoteMap; contextType?: string; @@ -130,6 +186,11 @@ export const QuotedStatus: React.FC = ({ const isLoaded = loadingState === 'complete'; const isFetchingQuoteRef = useRef(false); + const [revealed, setRevealed] = useState(false); + + const reveal = useCallback(() => { + setRevealed(true); + }, [setRevealed]); useEffect(() => { if (isLoaded) { @@ -189,6 +250,20 @@ export const QuotedStatus: React.FC = ({ defaultMessage='Post removed by author' /> ); + } else if ( + (quoteState === 'blocked_account' || + quoteState === 'blocked_domain' || + quoteState === 'muted_account') && + !revealed && + accountId + ) { + quoteError = ( + + ); } else if ( !status || !quotedStatusId || diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f20bae291..0481eb7c0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -911,9 +911,12 @@ "status.pin": "Pin on profile", "status.quote": "Quote", "status.quote.cancel": "Cancel quote", + "status.quote_error.blocked_account_hint.title": "This post is hidden because you've blocked @{name}.", + "status.quote_error.blocked_domain_hint.title": "This post is hidden because you've blocked {domain}.", "status.quote_error.filtered": "Hidden due to one of your filters", "status.quote_error.limited_account_hint.action": "Show anyway", "status.quote_error.limited_account_hint.title": "This account has been hidden by the moderators of {domain}.", + "status.quote_error.muted_account_hint.title": "This post is hidden because you've muted @{name}.", "status.quote_error.not_available": "Post unavailable", "status.quote_error.pending_approval": "Post pending", "status.quote_error.pending_approval_popout.body": "On Mastodon, you can control whether someone can quote you. This post is pending while we're getting the original author's approval.", diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index b6a5e3705..be425e129 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -98,12 +98,12 @@ class StatusCacheHydrator if quote.quoted_status.nil? payload[nested ? :quoted_status_id : :quoted_status] = nil payload[:state] = 'deleted' - elsif StatusFilter.new(quote.quoted_status, Account.find_by(id: account_id)).filtered_for_quote? - payload[nested ? :quoted_status_id : :quoted_status] = nil - payload[:state] = 'unauthorized' else - payload[:state] = 'accepted' - if nested + filter_state = StatusFilter.new(quote.quoted_status, Account.find_by(id: account_id)).filter_state_for_quote + payload[:state] = filter_state || 'accepted' + if filter_state == 'unauthorized' + payload[nested ? :quoted_status_id : :quoted_status] = nil + elsif nested payload[:quoted_status_id] = quote.quoted_status_id&.to_s else payload[:quoted_status] = StatusCacheHydrator.new(quote.quoted_status).hydrate(account_id, nested: true) diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index dbf7d28b6..2313e1407 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -15,10 +15,18 @@ class StatusFilter blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? end - def filtered_for_quote? - return false if !account.nil? && account.id == status.account_id - - blocked_by_policy? || (account_present? && filtered_status?) + def filter_state_for_quote + if !account.nil? && account.id == status.account_id + nil + elsif blocked_by_policy? + 'unauthorized' + elsif account_present? && blocking_domain? + 'blocked_domain' + elsif account_present? && blocking_account? + 'blocked_account' + elsif account_present? && muting_account? + 'muted_account' + end end private diff --git a/app/serializers/rest/base_quote_serializer.rb b/app/serializers/rest/base_quote_serializer.rb index 2637014b6..ac3b545d5 100644 --- a/app/serializers/rest/base_quote_serializer.rb +++ b/app/serializers/rest/base_quote_serializer.rb @@ -8,13 +8,12 @@ class REST::BaseQuoteSerializer < ActiveModel::Serializer # Extra states when a status is unavailable return 'deleted' if object.quoted_status.nil? - return 'unauthorized' if status_filter.filtered_for_quote? - object.state + status_filter.filter_state_for_quote || object.state end def quoted_status - object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && !status_filter.filtered_for_quote? + object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && status_filter.filter_state_for_quote != 'unauthorized' end private