Fix error alerts for deleted quotes (#35918)

This commit is contained in:
diondiondion 2025-08-26 18:34:14 +02:00 committed by Claire
commit 1faf520ce4
6 changed files with 62 additions and 19 deletions

View file

@ -3,7 +3,7 @@ import { browserHistory } from 'mastodon/components/router';
import api from '../api';
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
import { importFetchedStatus, importFetchedAccount } from './importer';
import { fetchContext } from './statuses_typed';
import { deleteFromTimelines } from './timelines';
@ -48,7 +48,18 @@ export function fetchStatusRequest(id, skipLoading) {
};
}
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
/**
* @param {string} id
* @param {Object} [options]
* @param {boolean} [options.forceFetch]
* @param {boolean} [options.alsoFetchContext]
* @param {string | null | undefined} [options.parentQuotePostId]
*/
export function fetchStatus(id, {
forceFetch = false,
alsoFetchContext = true,
parentQuotePostId,
} = {}) {
return (dispatch, getState) => {
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
@ -66,7 +77,7 @@ export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
dispatch(importFetchedStatus(response.data));
dispatch(fetchStatusSuccess(skipLoading));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading));
dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId));
});
};
}
@ -78,11 +89,12 @@ export function fetchStatusSuccess(skipLoading) {
};
}
export function fetchStatusFail(id, error, skipLoading) {
export function fetchStatusFail(id, error, skipLoading, parentQuotePostId) {
return {
type: STATUS_FETCH_FAIL,
id,
error,
parentQuotePostId,
skipLoading,
skipAlert: true,
};

View file

@ -37,9 +37,7 @@ const QuoteWrapper: React.FC<{
);
};
const NestedQuoteLink: React.FC<{
status: Status;
}> = ({ status }) => {
const NestedQuoteLink: React.FC<{ status: Status }> = ({ status }) => {
const accountId = status.get('account') as string;
const account = useAppSelector((state) =>
accountId ? state.accounts.get(accountId) : undefined,
@ -78,22 +76,40 @@ type GetStatusSelector = (
export const QuotedStatus: React.FC<{
quote: QuoteMap;
contextType?: string;
parentQuotePostId?: string | null;
variant?: 'full' | 'link';
nestingLevel?: number;
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
}> = ({
quote,
contextType,
parentQuotePostId,
nestingLevel = 1,
variant = 'full',
}) => {
const dispatch = useAppDispatch();
const quoteState = useAppSelector((state) =>
parentQuotePostId
? state.statuses.getIn([parentQuotePostId, 'quote', 'state'])
: quote.get('state'),
);
const quotedStatusId = quote.get('quoted_status');
const quoteState = quote.get('state');
const status = useAppSelector((state) =>
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
);
const isQuoteLoaded = !!status && !status.get('isLoading');
const shouldLoadQuote = !status?.get('isLoading') && quoteState !== 'deleted';
useEffect(() => {
if (!isQuoteLoaded && quotedStatusId) {
dispatch(fetchStatus(quotedStatusId));
if (shouldLoadQuote && quotedStatusId) {
dispatch(
fetchStatus(quotedStatusId, {
parentQuotePostId,
alsoFetchContext: false,
}),
);
}
}, [isQuoteLoaded, quotedStatusId, dispatch]);
}, [shouldLoadQuote, quotedStatusId, parentQuotePostId, dispatch]);
// In order to find out whether the quoted post should be completely hidden
// due to a matching filter, we run it through the selector used by `status_container`.
@ -174,6 +190,7 @@ export const QuotedStatus: React.FC<{
{canRenderChildQuote && (
<QuotedStatus
quote={childQuote}
parentQuotePostId={quotedStatusId}
contextType={contextType}
variant={
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
@ -209,7 +226,11 @@ export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
if (quote) {
return (
<StatusContainer {...props}>
<QuotedStatus quote={quote} contextType={props.contextType} />
<QuotedStatus
quote={quote}
parentQuotePostId={status?.get('id') as string}
contextType={props.contextType}
/>
</StatusContainer>
);
}

View file

@ -32,7 +32,7 @@ const Embed: React.FC<{ id: string }> = ({ id }) => {
const dispatchRenderSignal = useRenderSignal();
useEffect(() => {
dispatch(fetchStatus(id, false, false));
dispatch(fetchStatus(id, { alsoFetchContext: false }));
}, [dispatch, id]);
const handleToggleHidden = useCallback(() => {

View file

@ -381,7 +381,10 @@ export const DetailedStatus: React.FC<{
{hashtagBar}
{status.get('quote') && (
<QuotedStatus quote={status.get('quote')} />
<QuotedStatus
quote={status.get('quote')}
parentQuotePostId={status.get('id')}
/>
)}
</>
)}

View file

@ -38,7 +38,7 @@ class FilterModal extends ImmutablePureComponent {
handleSuccess = () => {
const { dispatch, statusId } = this.props;
dispatch(fetchStatus(statusId, true));
dispatch(fetchStatus(statusId, {forceFetch: true}));
this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' });
};

View file

@ -73,8 +73,15 @@ export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_FETCH_REQUEST:
return state.setIn([action.id, 'isLoading'], true);
case STATUS_FETCH_FAIL:
return state.delete(action.id);
case STATUS_FETCH_FAIL: {
if (action.parentQuotePostId && action.error.status === 404) {
return state
.delete(action.id)
.setIn([action.parentQuotePostId, 'quote', 'state'], 'deleted')
} else {
return state.delete(action.id);
}
}
case STATUS_IMPORT:
return importStatus(state, action.status);
case STATUSES_IMPORT: