Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a04a210
Suggest ES image version 7.17.29 in docker compose (#36972)
mjankowski Nov 21, 2025
86e463c
Fix compose autosuggest always lowercasing token (#36995)
ClearlyClaire Nov 25, 2025
770d121
Increase HTTP read timeout for expensive S3 batch delete operation (#…
ClearlyClaire Nov 25, 2025
240d38b
Fix `tootctl status remove` removing quoted posts and remote quotes o…
ClearlyClaire Nov 26, 2025
1ef4bbd
Fix post navigation in single-column mode when Advanced UI is enabled…
diondiondion Nov 28, 2025
b0c141e
Fix error handling when re-fetching already-known statuses (#37077)
ClearlyClaire Dec 1, 2025
782e410
Make settings-related database migrations more robust (#37079)
ClearlyClaire Dec 1, 2025
c42c71c
Remove noreferrer from external links (#37107)
ChaosExAnima Dec 3, 2025
1e2cf6c
Fix creation of duplicate conversations (#37108)
oneiros Dec 3, 2025
a6d31c0
Fix streamed quoted polls not being hydrated correctly (#37118)
ClearlyClaire Dec 4, 2025
9bc9ebc
Fixes YouTube embeds (#37126)
ChaosExAnima Dec 4, 2025
dfe2694
Fix “Delete and Redraft” on a non-quote being treated as a quote post…
ClearlyClaire Dec 5, 2025
a9f8268
New Crowdin Translations for stable-4.5 (automated) (#37158)
github-actions[bot] Dec 8, 2025
e6d2fc8
Merge commit from fork
ClearlyClaire Dec 8, 2025
86cff1a
Bump version to v4.5.3 (#37142)
ClearlyClaire Dec 8, 2025
6c1e892
Merge commit '86cff1abca6af7ffb1a6ca004ae308c0df6d45ba' into glitch-s…
ClearlyClaire Dec 8, 2025
e016e2a
[Glitch] Fix compose autosuggest always lowercasing token
ClearlyClaire Nov 25, 2025
e4e4ffb
[Glitch] Fix post navigation in single-column mode when Advanced UI i…
diondiondion Nov 28, 2025
66f25c6
[Glitch] Fix error handling when re-fetching already-known statuses
ClearlyClaire Dec 1, 2025
e1f1459
[Glitch] Remove noreferrer from external links
ChaosExAnima Dec 3, 2025
fe9a719
[Glitch] Fixes YouTube embeds
ChaosExAnima Dec 4, 2025
12ec21a
[Glitch] Fix “Delete and Redraft” on a non-quote being treated as a q…
ClearlyClaire Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

All notable changes to this project will be documented in this file.

## [4.5.3] - 2025-12-08

### Security

- Fix inconsistent error handling leaking information on existence of private posts ([GHSA-gwhw-gcjx-72v8](https://github.com/mastodon/mastodon/security/advisories/GHSA-gwhw-gcjx-72v8))

### Fixed

- Fix “Delete and Redraft” on a non-quote being treated as a quote post in some cases (#37140 by @ClearlyClaire)
- Fix YouTube embeds by sending referer (#37126 by @ChaosExAnima)
- Fix streamed quoted polls not being hydrated correctly (#37118 by @ClearlyClaire)
- Fix creation of duplicate conversations (#37108 by @oneiros)
- Fix extraneous `noreferrer` in external links (#37107 by @ChaosExAnima)
- Fix edge case error handling in some database migrations (#37079 by @ClearlyClaire)
- Fix error handling when re-fetching already-known statuses (#37077 by @ClearlyClaire)
- Fix post navigation in single-column mode when Advanced UI is enabled (#37044 by @diondiondion)
- Fix `tootctl status remove` removing quoted posts and remote quotes of local posts (#37009 by @ClearlyClaire)
- Fix known expensive S3 batch delete operation failing because of short timeouts (#37004 by @ClearlyClaire)
- Fix compose autosuggest always lowercasing input token (#36995 by @ClearlyClaire)

## [4.5.2] - 2025-11-20

### Changed
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/activitypub/likes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def pundit_user
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def set_quote_authorization
return not_found unless @quote.status.present? && @quote.quoted_status.present?

authorize @quote.quoted_status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end
2 changes: 1 addition & 1 deletion app/controllers/activitypub/replies_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def pundit_user
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/activitypub/shares_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def pundit_user
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/polls/votes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def create
def set_poll
@poll = Poll.find(params[:poll_id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/polls_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def show
def set_poll
@poll = Poll.find(params[:id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/statuses/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Api::V1::Statuses::BaseController < Api::BaseController
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end
2 changes: 1 addition & 1 deletion app/controllers/api/v1/statuses/bookmarks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def destroy
bookmark&.destroy!

render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end
2 changes: 1 addition & 1 deletion app/controllers/api/v1/statuses/favourites_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def destroy

relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } })
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end
4 changes: 2 additions & 2 deletions app/controllers/api/v1/statuses/reblogs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def destroy

relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } })
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand All @@ -45,7 +45,7 @@ def destroy
def set_reblog
@reblog = Status.find(params[:status_id])
authorize @reblog, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def set_statuses
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/web/embeds_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def show
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end
end
2 changes: 1 addition & 1 deletion app/controllers/authorize_interactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def show
def set_resource
@resource = located_resource
authorize(@resource, :show?) if @resource.is_a?(Status)
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/media_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def set_media_attachment

def verify_permitted_status!
authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def set_link_headers
def set_status
@status = @account.statuses.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
not_found
end

Expand Down
2 changes: 2 additions & 0 deletions app/javascript/flavours/glitch/actions/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export function fetchStatus(id, {
dispatch(fetchStatusSuccess(skipLoading));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId));
if (error.status === 404)
dispatch(deleteFromTimelines(id));
});
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
return [null, null];
}

word = word.trim().toLowerCase();
word = word.trim();

if (word.length > 0) {
return [left + 1, word];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
return [null, null];
}

word = word.trim().toLowerCase();
word = word.trim();

if (word.length > 0) {
return [left + 1, word];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
title={href}
className={classNames('unhandled-link', className)}
target='_blank'
rel='noreferrer noopener'
rel='noopener'
translate='no'
ref={linkRef}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ const handleIframeUrl = (html, url, providerName) => {
iframeUrl.searchParams.set('autoplay', 1)
iframeUrl.searchParams.set('auto_play', 1)

if (startTime && providerName === "YouTube") iframeUrl.searchParams.set('start', startTime)
if (providerName === 'YouTube') {
iframeUrl.searchParams.set('start', startTime || '');
iframe.referrerPolicy = 'strict-origin-when-cross-origin';
}

iframe.src = iframeUrl.href

Expand Down
7 changes: 4 additions & 3 deletions app/javascript/flavours/glitch/features/ui/util/focusUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { initialState } from '@/flavours/glitch/initial_state';

interface FocusColumnOptions {
index?: number;
focusItem?: 'first' | 'first-visible';
Expand All @@ -14,7 +12,10 @@ export function focusColumn({
focusItem = 'first',
}: FocusColumnOptions = {}) {
// Skip the leftmost drawer in multi-column mode
const indexOffset = initialState?.meta.advanced_layout ? 1 : 0;
const isMultiColumnLayout = !!document.querySelector(
'body.layout-multiple-columns',
);
const indexOffset = isMultiColumnLayout ? 1 : 0;

const column = document.querySelector(
`.column:nth-child(${index + indexOffset})`,
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/flavours/glitch/reducers/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ export const composeReducer = (state = initialState, action) => {
map => map.merge(new ImmutableMap({ do_not_federate })),
);
map.set('id', null);
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status']));
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'], null));
// Mastodon-authored posts can be expected to have at most one automatic approval policy
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');

Expand Down Expand Up @@ -690,7 +690,7 @@ export const composeReducer = (state = initialState, action) => {
map.set('idempotencyKey', uuid());
map.set('sensitive', action.status.get('sensitive'));
map.set('language', action.status.get('language'));
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status']));
map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'], null));
// Mastodon-authored posts can be expected to have at most one automatic approval policy
map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody');

Expand Down
9 changes: 6 additions & 3 deletions app/javascript/flavours/glitch/reducers/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ const statusTranslateUndo = (state, id) => {
});
};

const removeStatusStub = (state, id) => {
return state.getIn([id, 'id']) ? state.deleteIn([id, 'isLoading']) : state.delete(id);
}


/** @type {ImmutableMap<string, import('flavours/glitch/models/status').Status>} */
const initialState = ImmutableMap();
Expand Down Expand Up @@ -92,11 +96,10 @@ export default function statuses(state = initialState, action) {
return state.setIn([action.id, 'isLoading'], true);
case STATUS_FETCH_FAIL: {
if (action.parentQuotePostId && action.error.status === 404) {
return state
.delete(action.id)
return removeStatusStub(state, action.id)
.setIn([action.parentQuotePostId, 'quote', 'state'], 'deleted')
} else {
return state.delete(action.id);
return removeStatusStub(state, action.id);
}
}
case STATUS_IMPORT:
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/mastodon/actions/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export function fetchStatus(id, {
dispatch(fetchStatusSuccess(skipLoading));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId));
if (error.status === 404)
dispatch(deleteFromTimelines(id));
});
};
}
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/autosuggest_input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
return [null, null];
}

word = word.trim().toLowerCase();
word = word.trim();

if (word.length > 0) {
return [left + 1, word];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
return [null, null];
}

word = word.trim().toLowerCase();
word = word.trim();

if (word.length > 0) {
return [left + 1, word];
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/status/handled_link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
title={href}
className={classNames('unhandled-link', className)}
target='_blank'
rel='noreferrer noopener'
rel='noopener'
translate='no'
>
{children}
Expand Down
5 changes: 4 additions & 1 deletion app/javascript/mastodon/features/status/components/card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ const handleIframeUrl = (html, url, providerName) => {
iframeUrl.searchParams.set('autoplay', 1)
iframeUrl.searchParams.set('auto_play', 1)

if (startTime && providerName === "YouTube") iframeUrl.searchParams.set('start', startTime)
if (providerName === 'YouTube') {
iframeUrl.searchParams.set('start', startTime || '');
iframe.referrerPolicy = 'strict-origin-when-cross-origin';
}

iframe.src = iframeUrl.href

Expand Down
7 changes: 4 additions & 3 deletions app/javascript/mastodon/features/ui/util/focusUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { initialState } from '@/mastodon/initial_state';

interface FocusColumnOptions {
index?: number;
focusItem?: 'first' | 'first-visible';
Expand All @@ -14,7 +12,10 @@ export function focusColumn({
focusItem = 'first',
}: FocusColumnOptions = {}) {
// Skip the leftmost drawer in multi-column mode
const indexOffset = initialState?.meta.advanced_layout ? 1 : 0;
const isMultiColumnLayout = !!document.querySelector(
'body.layout-multiple-columns',
);
const indexOffset = isMultiColumnLayout ? 1 : 0;

const column = document.querySelector(
`.column:nth-child(${index + indexOffset})`,
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/mastodon/locales/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@
"confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?",
"confirmations.mute.confirm": "Silencia",
"confirmations.private_quote_notify.cancel": "Torna a l'edició",
"confirmations.private_quote_notify.confirm": "Publica la publicació",
"confirmations.private_quote_notify.do_not_show_again": "No tornis a mostrar-me aquest missatge",
"confirmations.private_quote_notify.message": "La persona que citeu i altres mencionades rebran una notificació i podran veure la vostra publicació, encara que no us segueixen.",
"confirmations.private_quote_notify.title": "Voleu compartir amb seguidors i usuaris mencionats?",
"confirmations.quiet_post_quote_info.dismiss": "No m'ho tornis a recordar",
Expand Down Expand Up @@ -339,7 +341,7 @@
"empty_column.bookmarked_statuses": "Encara no has marcat cap tut. Quan en marquis un, apareixerà aquí.",
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per posar-ho tot en marxa!",
"empty_column.direct": "Encara no tens mencions privades. Quan n'enviïs o en rebis una, et sortirà aquí.",
"empty_column.disabled_feed": "Aquest canal ha estat desactivat per l'administració del vostre servidor.",
"empty_column.disabled_feed": "L'administració del vostre servidor ha desactivat aquest canal.",
"empty_column.domain_blocks": "Encara no hi ha dominis blocats.",
"empty_column.explore_statuses": "No hi ha res en tendència ara mateix. Revisa-ho més tard!",
"empty_column.favourited_statuses": "Encara no has afavorit cap tut. Quan ho facis, apareixerà aquí.",
Expand Down
Loading
Loading