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