2
0

Fix error alerts for deleted quotes (#35918)

This commit is contained in:
diondiondion
2025-08-26 18:34:14 +02:00
committed by Claire
parent 8777443c9b
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 api from '../api';
import { ensureComposeIsVisible, setComposeToStatus } from './compose'; import { ensureComposeIsVisible, setComposeToStatus } from './compose';
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer'; import { importFetchedStatus, importFetchedAccount } from './importer';
import { fetchContext } from './statuses_typed'; import { fetchContext } from './statuses_typed';
import { deleteFromTimelines } from './timelines'; 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) => { return (dispatch, getState) => {
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null; 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(importFetchedStatus(response.data));
dispatch(fetchStatusSuccess(skipLoading)); dispatch(fetchStatusSuccess(skipLoading));
}).catch(error => { }).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 { return {
type: STATUS_FETCH_FAIL, type: STATUS_FETCH_FAIL,
id, id,
error, error,
parentQuotePostId,
skipLoading, skipLoading,
skipAlert: true, skipAlert: true,
}; };

View File

@@ -37,9 +37,7 @@ const QuoteWrapper: React.FC<{
); );
}; };
const NestedQuoteLink: React.FC<{ const NestedQuoteLink: React.FC<{ status: Status }> = ({ status }) => {
status: Status;
}> = ({ status }) => {
const accountId = status.get('account') as string; const accountId = status.get('account') as string;
const account = useAppSelector((state) => const account = useAppSelector((state) =>
accountId ? state.accounts.get(accountId) : undefined, accountId ? state.accounts.get(accountId) : undefined,
@@ -78,22 +76,40 @@ type GetStatusSelector = (
export const QuotedStatus: React.FC<{ export const QuotedStatus: React.FC<{
quote: QuoteMap; quote: QuoteMap;
contextType?: string; contextType?: string;
parentQuotePostId?: string | null;
variant?: 'full' | 'link'; variant?: 'full' | 'link';
nestingLevel?: number; nestingLevel?: number;
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => { }> = ({
quote,
contextType,
parentQuotePostId,
nestingLevel = 1,
variant = 'full',
}) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const quoteState = useAppSelector((state) =>
parentQuotePostId
? state.statuses.getIn([parentQuotePostId, 'quote', 'state'])
: quote.get('state'),
);
const quotedStatusId = quote.get('quoted_status'); const quotedStatusId = quote.get('quoted_status');
const quoteState = quote.get('state');
const status = useAppSelector((state) => const status = useAppSelector((state) =>
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined, quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
); );
const isQuoteLoaded = !!status && !status.get('isLoading');
const shouldLoadQuote = !status?.get('isLoading') && quoteState !== 'deleted';
useEffect(() => { useEffect(() => {
if (!isQuoteLoaded && quotedStatusId) { if (shouldLoadQuote && quotedStatusId) {
dispatch(fetchStatus(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 // 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`. // 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 && ( {canRenderChildQuote && (
<QuotedStatus <QuotedStatus
quote={childQuote} quote={childQuote}
parentQuotePostId={quotedStatusId}
contextType={contextType} contextType={contextType}
variant={ variant={
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full' nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
@@ -209,7 +226,11 @@ export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
if (quote) { if (quote) {
return ( return (
<StatusContainer {...props}> <StatusContainer {...props}>
<QuotedStatus quote={quote} contextType={props.contextType} /> <QuotedStatus
quote={quote}
parentQuotePostId={status?.get('id') as string}
contextType={props.contextType}
/>
</StatusContainer> </StatusContainer>
); );
} }

View File

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

View File

@@ -381,7 +381,10 @@ export const DetailedStatus: React.FC<{
{hashtagBar} {hashtagBar}
{status.get('quote') && ( {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 = () => { handleSuccess = () => {
const { dispatch, statusId } = this.props; const { dispatch, statusId } = this.props;
dispatch(fetchStatus(statusId, true)); dispatch(fetchStatus(statusId, {forceFetch: true}));
this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' }); this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' });
}; };

View File

@@ -73,8 +73,15 @@ export default function statuses(state = initialState, action) {
switch(action.type) { switch(action.type) {
case STATUS_FETCH_REQUEST: case STATUS_FETCH_REQUEST:
return state.setIn([action.id, 'isLoading'], true); return state.setIn([action.id, 'isLoading'], true);
case STATUS_FETCH_FAIL: case STATUS_FETCH_FAIL: {
return state.delete(action.id); 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: case STATUS_IMPORT:
return importStatus(state, action.status); return importStatus(state, action.status);
case STATUSES_IMPORT: case STATUSES_IMPORT: