2
0

Fix posts coming from public/hashtag streaming being marked as unquotable (#36860)

This commit is contained in:
Claire
2025-11-13 13:54:28 +01:00
parent 59f0134578
commit 55b9d21537
10 changed files with 49 additions and 16 deletions

View File

@@ -46,11 +46,11 @@ export function importFetchedAccounts(accounts) {
return importAccounts({ accounts: normalAccounts }); return importAccounts({ accounts: normalAccounts });
} }
export function importFetchedStatus(status) { export function importFetchedStatus(status, options = {}) {
return importFetchedStatuses([status]); return importFetchedStatuses([status], options);
} }
export function importFetchedStatuses(statuses) { export function importFetchedStatuses(statuses, options = {}) {
return (dispatch, getState) => { return (dispatch, getState) => {
const accounts = []; const accounts = [];
const normalStatuses = []; const normalStatuses = [];
@@ -58,7 +58,7 @@ export function importFetchedStatuses(statuses) {
const filters = []; const filters = [];
function processStatus(status) { function processStatus(status) {
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]))); pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), options));
pushUnique(accounts, status.account); pushUnique(accounts, status.account);
if (status.filtered) { if (status.filtered) {

View File

@@ -27,9 +27,12 @@ function stripQuoteFallback(text) {
return wrapper.innerHTML; return wrapper.innerHTML;
} }
export function normalizeStatus(status, normalOldStatus) { export function normalizeStatus(status, normalOldStatus, { bogusQuotePolicy = false }) {
const normalStatus = { ...status }; const normalStatus = { ...status };
if (bogusQuotePolicy)
normalStatus.quote_approval = null;
normalStatus.account = status.account.id; normalStatus.account = status.account.id;
if (status.reblog && status.reblog.id) { if (status.reblog && status.reblog.id) {
@@ -109,6 +112,8 @@ export function normalizeStatus(status, normalOldStatus) {
} }
if (normalOldStatus) { if (normalOldStatus) {
normalStatus.quote_approval ||= normalOldStatus.quote_approval;
const list = normalOldStatus.get('media_attachments'); const list = normalOldStatus.get('media_attachments');
if (normalStatus.media_attachments && list) { if (normalStatus.media_attachments && list) {
normalStatus.media_attachments.forEach(item => { normalStatus.media_attachments.forEach(item => {

View File

@@ -203,8 +203,8 @@ export function deleteStatusFail(id, error) {
}; };
} }
export const updateStatus = status => dispatch => export const updateStatus = (status, { bogusQuotePolicy }) => dispatch =>
dispatch(importFetchedStatus(status)); dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
export function muteStatus(id) { export function muteStatus(id) {
return (dispatch) => { return (dispatch) => {

View File

@@ -52,6 +52,9 @@ const randomUpTo = max =>
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => { export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
const { messages } = getLocale(); const { messages } = getLocale();
// Public streams are currently not returning personalized quote policies
const bogusQuotePolicy = channelName.startsWith('public') || channelName.startsWith('hashtag');
return connectStream(channelName, params, (dispatch, getState) => { return connectStream(channelName, params, (dispatch, getState) => {
// @ts-ignore // @ts-ignore
const locale = getState().getIn(['meta', 'locale']); const locale = getState().getIn(['meta', 'locale']);
@@ -97,11 +100,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
switch (data.event) { switch (data.event) {
case 'update': case 'update':
// @ts-expect-error // @ts-expect-error
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept)); dispatch(updateTimeline(timelineId, JSON.parse(data.payload), { accept: options.accept, bogusQuotePolicy }));
break; break;
case 'status.update': case 'status.update':
// @ts-expect-error // @ts-expect-error
dispatch(updateStatus(JSON.parse(data.payload))); dispatch(updateStatus(JSON.parse(data.payload), { bogusQuotePolicy }));
break; break;
case 'delete': case 'delete':
dispatch(deleteFromTimelines(data.payload)); dispatch(deleteFromTimelines(data.payload));

View File

@@ -32,7 +32,7 @@ export const loadPending = timeline => ({
timeline, timeline,
}); });
export function updateTimeline(timeline, status, accept) { export function updateTimeline(timeline, status, { accept = undefined, bogusQuotePolicy = false }) {
return (dispatch, getState) => { return (dispatch, getState) => {
if (typeof accept === 'function' && !accept(status)) { if (typeof accept === 'function' && !accept(status)) {
return; return;
@@ -45,7 +45,7 @@ export function updateTimeline(timeline, status, accept) {
return; return;
} }
dispatch(importFetchedStatus(status)); dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
dispatch({ dispatch({
type: TIMELINE_UPDATE, type: TIMELINE_UPDATE,

View File

@@ -26,6 +26,7 @@ import {
closeDropdownMenu, closeDropdownMenu,
} from 'mastodon/actions/dropdown_menu'; } from 'mastodon/actions/dropdown_menu';
import { openModal, closeModal } from 'mastodon/actions/modal'; import { openModal, closeModal } from 'mastodon/actions/modal';
import { fetchStatus } from 'mastodon/actions/statuses';
import { CircularProgress } from 'mastodon/components/circular_progress'; import { CircularProgress } from 'mastodon/components/circular_progress';
import { isUserTouching } from 'mastodon/is_mobile'; import { isUserTouching } from 'mastodon/is_mobile';
import { import {
@@ -302,6 +303,7 @@ interface DropdownProps<Item extends object | null = MenuItem> {
*/ */
scrollKey?: string; scrollKey?: string;
status?: ImmutableMap<string, unknown>; status?: ImmutableMap<string, unknown>;
needsStatusRefresh?: boolean;
forceDropdown?: boolean; forceDropdown?: boolean;
renderItem?: RenderItemFn<Item>; renderItem?: RenderItemFn<Item>;
renderHeader?: RenderHeaderFn<Item>; renderHeader?: RenderHeaderFn<Item>;
@@ -325,6 +327,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
placement = 'bottom', placement = 'bottom',
offset = [5, 5], offset = [5, 5],
status, status,
needsStatusRefresh,
forceDropdown = false, forceDropdown = false,
renderItem, renderItem,
renderHeader, renderHeader,
@@ -344,6 +347,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
const prefetchAccountId = status const prefetchAccountId = status
? status.getIn(['account', 'id']) ? status.getIn(['account', 'id'])
: undefined; : undefined;
const statusId = status?.get('id') as string | undefined;
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (buttonRef.current) { if (buttonRef.current) {
@@ -408,6 +412,15 @@ export const Dropdown = <Item extends object | null = MenuItem>({
dispatch(fetchRelationships([prefetchAccountId])); dispatch(fetchRelationships([prefetchAccountId]));
} }
if (needsStatusRefresh && statusId) {
dispatch(
fetchStatus(statusId, {
forceFetch: true,
alsoFetchContext: false,
}),
);
}
if (isUserTouching() && !forceDropdown) { if (isUserTouching() && !forceDropdown) {
dispatch( dispatch(
openModal({ openModal({
@@ -441,6 +454,8 @@ export const Dropdown = <Item extends object | null = MenuItem>({
items, items,
forceDropdown, forceDropdown,
handleClose, handleClose,
statusId,
needsStatusRefresh,
], ],
); );

View File

@@ -8,6 +8,7 @@ import classNames from 'classnames';
import { quoteComposeById } from '@/mastodon/actions/compose_typed'; import { quoteComposeById } from '@/mastodon/actions/compose_typed';
import { toggleReblog } from '@/mastodon/actions/interactions'; import { toggleReblog } from '@/mastodon/actions/interactions';
import { openModal } from '@/mastodon/actions/modal'; import { openModal } from '@/mastodon/actions/modal';
import { fetchStatus } from '@/mastodon/actions/statuses';
import { quickBoosting } from '@/mastodon/initial_state'; import { quickBoosting } from '@/mastodon/initial_state';
import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu'; import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu';
import type { Status } from '@/mastodon/models/status'; import type { Status } from '@/mastodon/models/status';
@@ -111,6 +112,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
const statusId = status.get('id') as string; const statusId = status.get('id') as string;
const wasBoosted = !!status.get('reblogged'); const wasBoosted = !!status.get('reblogged');
const quoteApproval = status.get('quote_approval');
const showLoginPrompt = useCallback(() => { const showLoginPrompt = useCallback(() => {
dispatch( dispatch(
@@ -167,9 +169,16 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
dispatch(toggleReblog(status.get('id'), true)); dispatch(toggleReblog(status.get('id'), true));
return false; return false;
} }
if (quoteApproval === null) {
dispatch(
fetchStatus(statusId, { forceFetch: true, alsoFetchContext: false }),
);
}
return true; return true;
}, },
[dispatch, isLoggedIn, showLoginPrompt, status], [dispatch, isLoggedIn, showLoginPrompt, status, quoteApproval, statusId],
); );
return ( return (

View File

@@ -404,6 +404,7 @@ class StatusActionBar extends ImmutablePureComponent {
<Dropdown <Dropdown
scrollKey={scrollKey} scrollKey={scrollKey}
status={status} status={status}
needsStatusRefresh={quickBoosting && status.get('quote_approval') === null}
items={menu} items={menu}
icon='ellipsis-h' icon='ellipsis-h'
iconComponent={MoreHorizIcon} iconComponent={MoreHorizIcon}

View File

@@ -159,7 +159,7 @@ class Status extends ImmutablePureComponent {
}; };
UNSAFE_componentWillMount () { UNSAFE_componentWillMount () {
this.props.dispatch(fetchStatus(this.props.params.statusId)); this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true }));
} }
componentDidMount () { componentDidMount () {
@@ -170,7 +170,7 @@ class Status extends ImmutablePureComponent {
UNSAFE_componentWillReceiveProps (nextProps) { UNSAFE_componentWillReceiveProps (nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchStatus(nextProps.params.statusId)); this.props.dispatch(fetchStatus(nextProps.params.statusId, { forceFetch: true }));
} }
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) { if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {

View File

@@ -32,7 +32,7 @@ function getStatusResultFunction(
}; };
} }
if (statusBase.get('isLoading')) { if (statusBase.get('isLoading') && !statusBase.get('content')) {
return { return {
status: null, status: null,
loadingState: 'loading', loadingState: 'loading',
@@ -74,7 +74,7 @@ function getStatusResultFunction(
map.set('matched_filters', filtered); map.set('matched_filters', filtered);
map.set('matched_media_filters', mediaFiltered); map.set('matched_media_filters', mediaFiltered);
}), }),
loadingState: 'complete' loadingState: statusBase.get('isLoading') ? 'loading' : 'complete'
}; };
} }