Fix error alerts for deleted quotes (#35918)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions

This commit is contained in:
diondiondion 2025-08-26 18:34:14 +02:00 committed by GitHub
parent 6ad0ddebe4
commit 4180f754d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 56 additions and 18 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

@ -32,9 +32,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,
@ -66,6 +64,7 @@ type GetStatusSelector = (
interface QuotedStatusProps { interface QuotedStatusProps {
quote: QuoteMap; quote: QuoteMap;
contextType?: string; contextType?: string;
parentQuotePostId?: string | null;
variant?: 'full' | 'link'; variant?: 'full' | 'link';
nestingLevel?: number; nestingLevel?: number;
onQuoteCancel?: () => void; // Used for composer. onQuoteCancel?: () => void; // Used for composer.
@ -74,23 +73,35 @@ interface QuotedStatusProps {
export const QuotedStatus: React.FC<QuotedStatusProps> = ({ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
quote, quote,
contextType, contextType,
parentQuotePostId,
nestingLevel = 1, nestingLevel = 1,
variant = 'full', variant = 'full',
onQuoteCancel, onQuoteCancel,
}) => { }) => {
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`.
@ -175,6 +186,7 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
{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'
@ -210,7 +222,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

@ -399,7 +399,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

@ -90,8 +90,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: {
if (action.parentQuotePostId && action.error.status === 404) {
return state
.delete(action.id)
.setIn([action.parentQuotePostId, 'quote', 'state'], 'deleted')
} else {
return state.delete(action.id); 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: