diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c1a1c99eb7..2bffaaf27b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -23,8 +23,6 @@ // Require Dependency Dashboard Approval for major version bumps of these node packages matchManagers: ['npm'], matchPackageNames: [ - 'tesseract.js', // Requires code changes - // react-router: Requires manual upgrade 'history', 'react-router-dom', diff --git a/CHANGELOG.md b/CHANGELOG.md index b706b8ac47..15b458e0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ All notable changes to this project will be documented in this file. ### Added -- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516, #36528, #36549, #36550 and #36559 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\ +- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516, #36528, #36549, #36550, #36559, #36693, #36704, #36690, #36689, #36696, #36721 and #36695 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\ This includes a revamp of the composer interface.\ See https://blog.joinmastodon.org/2025/09/introducing-quote-posts/ for a user-centric overview of the feature, and https://docs.joinmastodon.org/client/quotes/ for API documentation. - **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, #36239, #36484, #36481, #36583, #36627 and #36547 by @ClearlyClaire, @diondiondion, @Gargron and @renchap) - **Add ability to block words in usernames** (#35407, #35655, and #35806 by @ClearlyClaire and @Gargron) -- Add ability to individually disable local or remote feeds for visitors or logged-in users `disabled` value to server setting for live and topic feeds, as well as user permission to bypass that (#36338, #36467, #36497, #36563, #36577, #36585, and #36607 by @ClearlyClaire)\ - This splits the `timeline_preview` setting into four more granular settings controlling live feeds and topic (hashtag, trending link) feeds, with 3 values each: `public`, `authenticated`, `disabled`.\ +- Add ability to individually disable local or remote feeds for visitors or logged-in users `disabled` value to server setting for live and topic feeds, as well as user permission to bypass that (#36338, #36467, #36497, #36563, #36577, #36585, #36607 and #36703 by @ClearlyClaire)\ + This splits the `timeline_preview` setting into four more granular settings controlling live feeds and topic (hashtag, trending link) feeds.\ + The setting for local topic feeds has 2 values: `public` and `authenticated`. Every other setting has 3 values: `public`, `authenticated`, `disabled`.\ When `disabled`, users with the “View live and topic feeds” will still be able to view them. - Add support for displaying of quote posts in Moderator UI (#35964 by @ThisIsMissEm) - Add support for displaying link previews for Admin UI (#35958 by @ThisIsMissEm) @@ -43,6 +44,9 @@ All notable changes to this project will be documented in this file. - Change appearance settings to introduce new Advanced settings section (#36496 and #36506 by @diondiondion) - Change display of blocked and muted quoted users (#36619 by @ClearlyClaire)\ This adds `blocked_account`, `blocked_domain` and `muted_account` values to the `state` attribute of `Quote` and `ShallowQuote` REST API entities. +- Change submitting an empty post to show an error rather than failing silently (#36650 by @diondiondion) +- Change "Privacy and reach" settings from "Public profile" to their own top-level category (#27294 by @ChaelCodes) +- Change number of times quote verification is retried to better deal with temporary failures (#36698 by @ClearlyClaire) - Change display of content warnings in Admin UI (#35935 by @ThisIsMissEm) - Change styling of column banners (#36531 by @ClearlyClaire) - Change recommended Node version to 24 (LTS) (#36539 by @renchap) @@ -70,9 +74,11 @@ All notable changes to this project will be documented in this file. - Fix relationship not being fetched to evaluate whether to show a quote post (#36517 by @ClearlyClaire) - Fix rendering of poll options in status history modal (#35633 by @ThisIsMissEm) - Fix “mute” button being displayed to unauthenticated visitors in hashtag dropdown (#36353 by @mkljczk) +- Fix initially selected language in Rules panel, hide selector when no alternative translations exist (#36672 by @diondiondion) - Fix URL comparison for mentions in case of empty path (#36613 and #36626 by @ClearlyClaire) - Fix hashtags not being picked up when full-width hash sign is used (#36103 and #36625 by @ClearlyClaire and @Gargron) - Fix layout of severed relationships when purged events are listed (#36593 by @mejofi) +- Fix Skeleton placeholders being animated when setting to reduce animations is enabled (#36716 by @ClearlyClaire) - Fix vacuum tasks being interrupted by a single batch failure (#36606 by @Gargron) - Fix handling of unreachable network error for search services (#36587 by @mjankowski) - Fix bookmarks export when a bookmarked status is soft-deleted (#36576 by @ClearlyClaire) diff --git a/Gemfile b/Gemfile index 91e2655886..e54842fcda 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ gem 'haml-rails', '~>3.0' gem 'pg', '~> 1.5' gem 'pghero' -gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873 +gem 'aws-sdk-core', require: false gem 'aws-sdk-s3', '~> 1.123', require: false gem 'blurhash', '~> 0.1' gem 'fog-core', '<= 2.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index a798595fa7..95bf955c28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,17 +96,20 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1168.0) - aws-sdk-core (3.215.1) + aws-partitions (1.1180.0) + aws-sdk-core (3.236.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.96.0) - aws-sdk-core (~> 3, >= 3.210.0) + logger + aws-sdk-kms (1.116.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.177.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.202.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) @@ -933,7 +936,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotaterb (~> 4.13) - aws-sdk-core (< 3.216.0) + aws-sdk-core aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) diff --git a/app/javascript/mastodon/components/alert/index.tsx b/app/javascript/mastodon/components/alert/index.tsx index 72fee0a4a3..8bee99130f 100644 --- a/app/javascript/mastodon/components/alert/index.tsx +++ b/app/javascript/mastodon/components/alert/index.tsx @@ -49,7 +49,11 @@ export const Alert: React.FC<{ {hasAction && ( - )} diff --git a/app/javascript/mastodon/components/button/index.tsx b/app/javascript/mastodon/components/button/index.tsx index f940c07d05..ca2da05b70 100644 --- a/app/javascript/mastodon/components/button/index.tsx +++ b/app/javascript/mastodon/components/button/index.tsx @@ -78,6 +78,7 @@ export const Button: React.FC = ({ aria-live={loading !== undefined ? 'polite' : undefined} onClick={handleClick} title={title} + // eslint-disable-next-line react/button-has-type -- set correctly via TS type={type} {...props} > diff --git a/app/javascript/mastodon/components/column_back_button.tsx b/app/javascript/mastodon/components/column_back_button.tsx index aad355d710..8012ba7df6 100644 --- a/app/javascript/mastodon/components/column_back_button.tsx +++ b/app/javascript/mastodon/components/column_back_button.tsx @@ -30,7 +30,7 @@ export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({ const handleClick = useHandleClick(onClick); const component = ( - @@ -193,6 +196,7 @@ export const ColumnHeader: React.FC = ({ aria-label={intl.formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={handleMoveRight} + type='button' > @@ -203,6 +207,7 @@ export const ColumnHeader: React.FC = ({ diff --git a/app/javascript/mastodon/components/edited_timestamp/index.tsx b/app/javascript/mastodon/components/edited_timestamp/index.tsx index 63b21cf5bd..2253434667 100644 --- a/app/javascript/mastodon/components/edited_timestamp/index.tsx +++ b/app/javascript/mastodon/components/edited_timestamp/index.tsx @@ -98,7 +98,12 @@ export const EditedTimestamp: React.FC<{ className='dropdown-menu__item edited-timestamp__history__item' key={item.get('created_at') as string} > - @@ -118,7 +123,7 @@ export const EditedTimestamp: React.FC<{ onItemClick={handleItemClick} forceDropdown > - is focused, + When a is focused, Enter should not trigger open, but o should. diff --git a/app/javascript/mastodon/components/learn_more_link.tsx b/app/javascript/mastodon/components/learn_more_link.tsx index b5337794c9..8d22bb7a3b 100644 --- a/app/javascript/mastodon/components/learn_more_link.tsx +++ b/app/javascript/mastodon/components/learn_more_link.tsx @@ -23,6 +23,7 @@ export const LearnMoreLink: React.FC<{ children: React.ReactNode }> = ({ onClick={handleClick} aria-expanded={open} aria-controls={accessibilityId} + type='button' > = ({
{children}
- )} {!showResults && ( <> - {' '} ·{' '} @@ -185,7 +186,11 @@ export const Poll: React.FC = ({ pollId, disabled, status }) => { )} {showResults && !disabled && ( <> - {' '} ·{' '} diff --git a/app/javascript/mastodon/components/status/boost_button.tsx b/app/javascript/mastodon/components/status/boost_button.tsx index cdbe242289..bbb7c031d9 100644 --- a/app/javascript/mastodon/components/status/boost_button.tsx +++ b/app/javascript/mastodon/components/status/boost_button.tsx @@ -232,6 +232,7 @@ const ReblogMenuItem: FC = ({ ref={focusRefCallback} aria-disabled={disabled} data-index={index} + type='button' > diff --git a/app/javascript/mastodon/components/status_banner.tsx b/app/javascript/mastodon/components/status_banner.tsx index a1d200133f..b7f7289652 100644 --- a/app/javascript/mastodon/components/status_banner.tsx +++ b/app/javascript/mastodon/components/status_banner.tsx @@ -49,6 +49,7 @@ export const StatusBanner: React.FC<{ @@ -154,6 +155,7 @@ export const DomainPill: React.FC<{ @@ -169,6 +171,7 @@ export const DomainPill: React.FC<{ diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index f285c35929..c118c1812b 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -489,6 +489,7 @@ export const AltTextModal = forwardRef>( className='link-button' onClick={handleDetectClick} disabled={type !== 'image' || isDetecting} + type='button' > (null); const volumeRef = useRef(null); const hoverTimeoutRef = useRef | null>(); - const accessibilityId = useId(); const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } = useAudioContext({ audioElementRef: audioRef }); @@ -538,19 +539,6 @@ export const Audio: React.FC<{ [togglePlay, toggleMute], ); - const springForBand0 = useSpring({ - to: { r: 50 + (frequencyBands[0] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand1 = useSpring({ - to: { r: 50 + (frequencyBands[1] ?? 0) * 10 }, - config: config.wobbly, - }); - const springForBand2 = useSpring({ - to: { r: 50 + (frequencyBands[2] ?? 0) * 10 }, - config: config.wobbly, - }); - const progress = Math.min((currentTime / loadedDuration) * 100, 100); const effectivelyMuted = muted || volume === 0; @@ -641,81 +629,7 @@ export const Audio: React.FC<{
- - - - - - - - - - - - - - - - - - - - - - +
@@ -591,6 +595,7 @@ export const Search: React.FC<{ className={classNames('search__popout__menu__item', { selected: selectedOption === i, })} + type='button' > {label} @@ -617,6 +622,7 @@ export const Search: React.FC<{ selectedOption === (quickActions.length || recent.length) + i, })} + type='button' > {label} diff --git a/app/javascript/mastodon/features/compose/components/upload.tsx b/app/javascript/mastodon/features/compose/components/upload.tsx index adc51733d9..85fed0cbd3 100644 --- a/app/javascript/mastodon/features/compose/components/upload.tsx +++ b/app/javascript/mastodon/features/compose/components/upload.tsx @@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import CloseIcon from '@/material-icons/400-20px/close.svg?react'; +import SoundIcon from '@/material-icons/400-24px/audio.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { undoUploadCompose } from 'mastodon/actions/compose'; @@ -17,7 +18,18 @@ import { openModal } from 'mastodon/actions/modal'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; -import { useAppDispatch, useAppSelector } from 'mastodon/store'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from 'mastodon/store'; + +import { AudioVisualizer } from '../../audio/visualizer'; + +const selectUserAvatar = createAppSelector( + [(state) => state.accounts, (state) => state.meta.get('me') as string], + (accounts, myId) => accounts.get(myId)?.avatar_static, +); export const Upload: React.FC<{ id: string; @@ -38,6 +50,7 @@ export const Upload: React.FC<{ const sensitive = useAppSelector( (state) => state.compose.get('spoiler') as boolean, ); + const userAvatar = useAppSelector(selectUserAvatar); const handleUndoClick = useCallback(() => { dispatch(undoUploadCompose(id)); @@ -67,6 +80,8 @@ export const Upload: React.FC<{ transform: CSS.Transform.toString(transform), transition, }; + const preview_url = media.get('preview_url') as string | null; + const blurhash = media.get('blurhash') as string | null; return (
- {sensitive && ( - + {sensitive && blurhash && ( + + )} + {!sensitive && !preview_url && ( +
+ + +
)}
diff --git a/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx b/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx index 39d3c881b0..da0818c4fe 100644 --- a/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/announcements/announcement.tsx @@ -1,11 +1,13 @@ -import { useEffect, useState } from 'react'; import type { FC } from 'react'; +import { useEffect, useState } from 'react'; import { FormattedDate, FormattedMessage } from 'react-intl'; +import { dismissAnnouncement } from '@/mastodon/actions/announcements'; import type { ApiAnnouncementJSON } from '@/mastodon/api_types/announcements'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { useAppDispatch } from '@/mastodon/store'; import { ReactionsBar } from './reactions'; @@ -22,13 +24,23 @@ export const Announcement: FC = ({ announcement, active, }) => { - const [unread, setUnread] = useState(!announcement.read); + const { read, id } = announcement; + + // Dismiss announcement when it becomes active. + const dispatch = useAppDispatch(); useEffect(() => { - // Only update `unread` marker once the announcement is out of view - if (!active && unread !== !announcement.read) { - setUnread(!announcement.read); + if (active && !read) { + dispatch(dismissAnnouncement(id)); } - }, [announcement.read, active, unread]); + }, [active, id, dispatch, read]); + + // But visually show the announcement as read only when it goes out of view. + const [unread, setUnread] = useState(!read); + useEffect(() => { + if (!active && unread !== !read) { + setUnread(!read); + } + }, [active, unread, read]); return ( diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx index 05799ccb82..d297d7cee5 100644 --- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx @@ -270,7 +270,7 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
- +
+ + ); +}; + +const NewListWrapper: React.FC<{ + multiColumn?: boolean; +}> = ({ multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { id } = useParams<{ id?: string }>(); + const list = useAppSelector((state) => + id ? state.lists.get(id) : undefined, + ); + + useEffect(() => { + if (id) { + dispatch(fetchList(id)); + } + }, [dispatch, id]); + + const isLoading = id && !list; + return (
-
-
-
-
- - -
- -
-
-
-
- -
-
-
- - -
- -
-
-
-
- - {id && ( -
- -
- )} - -
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} - -
- -
- -
-
+ {isLoading ? : }
@@ -298,4 +306,4 @@ const NewList: React.FC<{ }; // eslint-disable-next-line import/no-default-export -export default NewList; +export default NewListWrapper; diff --git a/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx b/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx index a3477ec4e5..4d4f8e8ff0 100644 --- a/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx +++ b/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx @@ -113,7 +113,7 @@ export const MoreLink: React.FC = () => { return ( - diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx index 8b92f31add..4541592fbd 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_annual_report.tsx @@ -47,7 +47,7 @@ export const NotificationAnnualReport: React.FC<{ values={{ year }} />

- diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 7c73d602fc..3fca9eb44e 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -245,6 +245,7 @@ export const Notifications: React.FC<{ title={intl.formatMessage(messages.markAsRead)} onClick={handleMarkAsRead} className='column-header__button' + type='button' > diff --git a/app/javascript/mastodon/features/search/components/search_section.tsx b/app/javascript/mastodon/features/search/components/search_section.tsx index ae0c129676..c59d0c2fe4 100644 --- a/app/javascript/mastodon/features/search/components/search_section.tsx +++ b/app/javascript/mastodon/features/search/components/search_section.tsx @@ -9,7 +9,7 @@ export const SearchSection: React.FC<{

{title}

{onClickMore && ( - ); diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.tsx b/app/javascript/mastodon/features/ui/components/boost_modal.tsx index 1963189194..a55141167b 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.tsx @@ -113,7 +113,7 @@ export const BoostModal: React.FC<{
- diff --git a/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx b/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx index ec08991144..b6c17f9062 100644 --- a/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/domain_block_modal.tsx @@ -196,7 +196,7 @@ export const DomainBlockModal: React.FC<{
- @@ -257,6 +258,7 @@ export const MediaModal: FC = forwardRef< className='media-modal__nav media-modal__nav--next' onClick={handleNextClick} aria-label={intl.formatMessage(messages.next)} + type='button' > @@ -354,6 +356,7 @@ const MediaPagination: FC = ({ active: i === index, })} onClick={handleChangeIndex(i)} + type='button' > {i + 1} diff --git a/app/javascript/mastodon/features/ui/components/navigation_bar.tsx b/app/javascript/mastodon/features/ui/components/navigation_bar.tsx index 467e20a572..a542bd1f68 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_bar.tsx +++ b/app/javascript/mastodon/features/ui/components/navigation_bar.tsx @@ -127,7 +127,11 @@ const LoginOrSignUp: React.FC = () => { ); } else { signupButton = ( - diff --git a/app/javascript/mastodon/features/video/index.tsx b/app/javascript/mastodon/features/video/index.tsx index 65f26cedad..aa03e3d2e9 100644 --- a/app/javascript/mastodon/features/video/index.tsx +++ b/app/javascript/mastodon/features/video/index.tsx @@ -875,6 +875,7 @@ export const Video: React.FC<{