diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 58728936b71..3fddd1bcc56 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -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, }; diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 1b6ae2aaf36..3ac720bbc08 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -32,9 +32,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, @@ -66,6 +64,7 @@ type GetStatusSelector = ( interface QuotedStatusProps { quote: QuoteMap; contextType?: string; + parentQuotePostId?: string | null; variant?: 'full' | 'link'; nestingLevel?: number; onQuoteCancel?: () => void; // Used for composer. @@ -74,23 +73,35 @@ interface QuotedStatusProps { export const QuotedStatus: React.FC = ({ quote, contextType, + parentQuotePostId, nestingLevel = 1, variant = 'full', onQuoteCancel, }) => { 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`. @@ -175,6 +186,7 @@ export const QuotedStatus: React.FC = ({ {canRenderChildQuote && ( { if (quote) { return ( - + ); } diff --git a/app/javascript/mastodon/features/standalone/status/index.tsx b/app/javascript/mastodon/features/standalone/status/index.tsx index 8d1b8314673..a7850eae1cc 100644 --- a/app/javascript/mastodon/features/standalone/status/index.tsx +++ b/app/javascript/mastodon/features/standalone/status/index.tsx @@ -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(() => { diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 16e070728be..652eab73846 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -399,7 +399,10 @@ export const DetailedStatus: React.FC<{ {hashtagBar} {status.get('quote') && ( - + )} )} diff --git a/app/javascript/mastodon/features/ui/components/filter_modal.jsx b/app/javascript/mastodon/features/ui/components/filter_modal.jsx index 477575bd7b3..a1a39ba0abf 100644 --- a/app/javascript/mastodon/features/ui/components/filter_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/filter_modal.jsx @@ -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' }); }; diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 13ff5e016e5..a334709db0a 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -90,8 +90,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: