diff --git a/CHANGELOG.md b/CHANGELOG.md index 49da341ec1c4a2..ad084c8a562e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ All notable changes to this project will be documented in this file. +## [4.4.5] - 2025-09-23 + +### Security + +- Update dependencies + +### Added + +- Add support for `has:quote` in search (#36217 by @ClearlyClaire) + +### Changed + +- Change quoted posts from silenced accounts to use a click-through rather than being hidden (#36166 and #36167 by @ClearlyClaire) + +### Fixed + +- Fix processing of out-of-order `Update` as implicit updates (#36190 by @ClearlyClaire) +- Fix getting `Create` and `Update` out of order (#36176 by @ClearlyClaire) +- Fix quotes with Content Warnings but no text being shown without Content Warnings (#36150 by @ClearlyClaire) + ## [4.4.4] - 2025-09-16 ### Security @@ -18,7 +38,7 @@ All notable changes to this project will be documented in this file. - Fix WebUI handling of deleted quoted posts (#35909 and #35918 by @ClearlyClaire and @diondiondion) - Fix “Edit” and “Delete & Redraft” on a poll not inserting empty option (#35892 by @ClearlyClaire) - Fix loading of some compatibility CSS on some configurations (#35876 by @shleeable) -- Fix HttpLog not being enabled with `RAILS_LOGÈ_LEVEL=debug` (#35833 by @mjankowski) +- Fix HttpLog not being enabled with `RAILS_LOG_LEVEL=debug` (#35833 by @mjankowski) - Fix self-destruct scheduler behavior on some Redis setups (#35823 by @ClearlyClaire) - Fix `tootctl admin create` not bypassing reserved username checks (#35779 by @ClearlyClaire) - Fix interaction policy changes in implicit updates not being saved (#35751 by @ClearlyClaire) diff --git a/Gemfile.lock b/Gemfile.lock index 0718b102a8d7e3..2d144104b2f602 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -725,7 +725,7 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.4.1) + rexml (3.4.4) rotp (6.3.0) rouge (4.5.2) rpam2 (4.0.2) diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 5bb221df305406..cdb91dc8a8e6a6 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -93,7 +93,7 @@ export function normalizeStatus(status, normalOldStatus, options = undefined) { } else { // If the status has a CW but no contents, treat the CW as if it were the // status' contents, to avoid having a CW toggle with seemingly no effect. - if (normalStatus.spoiler_text && !normalStatus.content) { + if (normalStatus.spoiler_text && !normalStatus.content && !normalStatus.quote) { normalStatus.content = normalStatus.spoiler_text; normalStatus.spoiler_text = ''; } diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx index 87fbec0e25a85c..a0d2ad7cfb4745 100644 --- a/app/javascript/mastodon/components/hover_card_account.tsx +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -19,7 +19,7 @@ import { FollowButton } from 'mastodon/components/follow_button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ShortNumber } from 'mastodon/components/short_number'; import { useFetchFamiliarFollowers } from 'mastodon/features/account_timeline/hooks/familiar_followers'; -import { domain } from 'mastodon/initial_state'; +import { domain, isHideItem } from 'mastodon/initial_state'; import { getAccountHidden } from 'mastodon/selectors/accounts'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -54,8 +54,10 @@ export const HoverCardAccount = forwardRef< const relationship = useAppSelector((state) => accountId ? state.relationships.get(accountId) : undefined, ); - const isMutual = relationship?.followed_by && relationship.following; - const isFollower = relationship?.followed_by; + const isHideRelationships = isHideItem('relationships'); + const isMutual = + !isHideRelationships && relationship?.followed_by && relationship.following; + const isFollower = !isHideRelationships && relationship?.followed_by; const hasRelationshipLoaded = !!relationship; const shouldDisplayFamiliarFollowers = diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 3f7f51cf0643a3..d58760ac6e37c4 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -11,13 +11,16 @@ import ArticleIcon from '@/material-icons/400-24px/article.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { Icon } from 'mastodon/components/icon'; import StatusContainer from 'mastodon/containers/status_container'; +import { domain } from 'mastodon/initial_state'; import type { Status } from 'mastodon/models/status'; import type { RootState } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import QuoteIcon from '../../images/quote.svg?react'; +import { revealAccount } from '../actions/accounts_typed'; import { fetchStatus } from '../actions/statuses'; import { makeGetStatus } from '../selectors'; +import { getAccountHidden } from '../selectors/accounts'; const MAX_QUOTE_POSTS_NESTING_LEVEL = 1; @@ -73,6 +76,29 @@ type GetStatusSelector = ( props: { id?: string | null; contextType?: string }, ) => Status | null; +const LimitedAccountHint: React.FC<{ accountId: string }> = ({ accountId }) => { + const dispatch = useAppDispatch(); + const reveal = useCallback(() => { + dispatch(revealAccount({ id: accountId })); + }, [dispatch, accountId]); + + return ( + <> + + + + ); +}; + export const QuotedStatus: React.FC<{ quote: QuoteMap; contextType?: string; @@ -100,6 +126,14 @@ export const QuotedStatus: React.FC<{ const shouldLoadQuote = !status?.get('isLoading') && quoteState !== 'deleted'; + const accountId: string | null = status?.get('account', null) as + | string + | null; + + const hiddenAccount = useAppSelector( + (state) => accountId && getAccountHidden(state, accountId), + ); + useEffect(() => { if (shouldLoadQuote && quotedStatusId) { dispatch( @@ -164,6 +198,8 @@ export const QuotedStatus: React.FC<{ defaultMessage='This post cannot be displayed.' /> ); + } else if (hiddenAccount && accountId) { + quoteError = ; } if (quoteError) { diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index d7f228129af6b7..5887ce114f90b2 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -50,7 +50,12 @@ import { DomainPill } from 'mastodon/features/account/components/domain_pill'; import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container'; import { useLinks } from 'mastodon/hooks/useLinks'; import { useIdentity } from 'mastodon/identity_context'; -import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; +import { + autoPlayGif, + me, + domain as localDomain, + isHideItem, +} from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { @@ -220,6 +225,8 @@ export const AccountHeader: React.FC<{ const hidden = useAppSelector((state) => getAccountHidden(state, accountId)); const handleLinkClick = useLinks(); + const isHideRelationships = isHideItem('relationships'); + const handleBlock = useCallback(() => { if (!account) { return; @@ -548,7 +555,7 @@ export const AccountHeader: React.FC<{ text: intl.formatMessage(messages.add_or_remove_from_exclude_antenna), action: handleAddToExcludeAntenna, }); - if (relationship?.followed_by) { + if (!isHideRelationships && relationship?.followed_by) { arr.push({ text: intl.formatMessage(messages.add_or_remove_from_circle), action: handleAddToCircle, @@ -556,7 +563,7 @@ export const AccountHeader: React.FC<{ } arr.push(null); - if (relationship?.followed_by) { + if (!isHideRelationships && relationship?.followed_by) { const handleRemoveFromFollowers = () => { dispatch( openModal({ @@ -709,6 +716,7 @@ export const AccountHeader: React.FC<{ handleReblogToggle, handleReport, handleUnblockDomain, + isHideRelationships, ]); if (!account) { @@ -724,6 +732,7 @@ export const AccountHeader: React.FC<{ if (me !== account.id && relationship) { if ( + !isHideRelationships && relationship.followed_by && (relationship.following || relationship.requested) ) { @@ -735,7 +744,7 @@ export const AccountHeader: React.FC<{ /> , ); - } else if (relationship.followed_by) { + } else if (!isHideRelationships && relationship.followed_by) { info.push( , ); - } else if (relationship.requested_by) { + } else if (!relationship.followed_by && relationship.requested_by) { info.push(