diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml
index c48668d3bc..d6c542eb36 100644
--- a/.github/workflows/crowdin-upload.yml
+++ b/.github/workflows/crowdin-upload.yml
@@ -14,6 +14,7 @@ on:
- config/locales-glitch/devise.en.yml
- config/locales-glitch/doorkeeper.en.yml
- .github/workflows/crowdin-upload.yml
+ workflow_dispatch:
jobs:
upload-translations:
diff --git a/Dockerfile b/Dockerfile
index 6cb4a2a1c0..b7d1159dfc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -186,7 +186,7 @@ FROM build AS libvips
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
-ARG VIPS_VERSION=8.17.0
+ARG VIPS_VERSION=8.17.1
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
diff --git a/Gemfile.lock b/Gemfile.lock
index 299507cac9..c59f26c44d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -231,7 +231,7 @@ GEM
excon (1.2.5)
logger
fabrication (3.0.0)
- faker (3.5.1)
+ faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.13.1)
faraday-net_http (>= 2.0, < 3.5)
@@ -553,7 +553,7 @@ GEM
opentelemetry-instrumentation-faraday (0.27.0)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0)
- opentelemetry-instrumentation-http (0.25.0)
+ opentelemetry-instrumentation-http (0.25.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-http_client (0.23.0)
@@ -682,7 +682,7 @@ GEM
activesupport (= 8.0.2)
bundler (>= 1.15.0)
railties (= 8.0.2)
- rails-dom-testing (2.2.0)
+ rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -815,7 +815,7 @@ GEM
sanitize (7.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.16.8)
- scenic (1.8.0)
+ scenic (1.9.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securerandom (0.4.1)
diff --git a/README.md b/README.md
index ad264822e2..10de9c4615 100644
--- a/README.md
+++ b/README.md
@@ -33,71 +33,71 @@ Mastodon Glitch Edition is a fork of [Mastodon](https://github.com/mastodon/mast
-Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
+Mastodon is a **free, open-source social network server** based on [ActivityPub](https://www.w3.org/TR/activitypub/) where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
## Navigation
- [Project homepage 🐘](https://joinmastodon.org)
-- [Support the development via Patreon][patreon]
-- [View sponsors](https://joinmastodon.org/sponsors)
-- [Blog](https://blog.joinmastodon.org)
-- [Documentation](https://docs.joinmastodon.org)
-- [Roadmap](https://joinmastodon.org/roadmap)
-- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
-- [Browse Mastodon servers](https://joinmastodon.org/communities)
-- [Browse Mastodon apps](https://joinmastodon.org/apps)
-
-[patreon]: https://www.patreon.com/mastodon
+- [Donate to support development 🎁](https://joinmastodon.org/sponsors#donate)
+ - [View sponsors](https://joinmastodon.org/sponsors)
+- [Blog 📰](https://blog.joinmastodon.org)
+- [Documentation 📚](https://docs.joinmastodon.org)
+- [Official container image 🚢](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
## Features
-
+
-**No vendor lock-in: Fully interoperable with any conforming platform** - It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
+**Part of the Fediverse. Based on open standards, with no vendor lock-in.** - the network goes beyond just Mastodon; anything that implements ActivityPub is part of a broader social network known as [the Fediverse](https://jointhefediverse.net/). You can follow and interact with users on other servers (including those running different software), and they can follow you back.
-**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
+**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI.
-**Media attachments like images and short videos** - upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
+**Media attachments** - upload and view images and videos attached to the updates. Videos with no audio track are treated like animated GIFs; normal videos loop continuously.
-**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
+**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and many other features, along with a reporting and moderation system.
-**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
+**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, and third party apps can use the REST and Streaming APIs. This results in a [rich app ecosystem](https://joinmastodon.org/apps) with a variety of choices!
## Deployment
### Tech stack
-- **Ruby on Rails** powers the REST API and other web pages
-- **React.js** and **Redux** are used for the dynamic parts of the interface
-- **Node.js** powers the streaming API
+- [Ruby on Rails](https://github.com/rails/rails) powers the REST API and other web pages.
+- [PostgreSQL](https://www.postgresql.org/) is the main database.
+- [Redis](https://redis.io/) and [Sidekiq](https://sidekiq.org/) are used for caching and queueing.
+- [Node.js](https://nodejs.org/) powers the streaming API.
+- [React.js](https://reactjs.org/) and [Redux](https://redux.js.org/) are used for the dynamic parts of the interface.
+- [BrowserStack](https://www.browserstack.com/) supports testing on real devices and browsers. (This project is tested with BrowserStack)
+- [Chromatic](https://www.chromatic.com/) provides visual regression testing. (This project is tested with Chromatic)
### Requirements
+- **Ruby** 3.2+
- **PostgreSQL** 13+
- **Redis** 6.2+
-- **Ruby** 3.2+
- **Node.js** 20+
-The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
+This repository includes deployment configurations for **Docker and docker-compose**, as well as for other environments like Heroku and Scalingo. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). A [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the main documentation.
## Contributing
-Mastodon is **free, open-source software** licensed under **AGPLv3**.
+Mastodon is **free, open-source software** licensed under **AGPLv3**. We welcome contributions and help from anyone who wants to improve the project.
-You can open issues for bugs you've found or features you think are missing. You
-can also submit pull requests to this repository or translations via Crowdin. To
-get started, look at the [CONTRIBUTING] and [DEVELOPMENT] guides. For changes
-accepted into Mastodon, you can request to be paid through our [OpenCollective].
+You should read the overall [CONTRIBUTING](https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md) guide, which covers our development processes.
-**IRC channel**: #mastodon on [`irc.libera.chat`](https://libera.chat)
+You should also read and understand the [CODE OF CONDUCT](https://github.com/mastodon/.github/blob/main/CODE_OF_CONDUCT.md) that enables us to maintain a welcoming and inclusive community. Collaboration begins with mutual respect and understanding.
-## License
+You can learn about setting up a development environment in the [DEVELOPMENT](docs/DEVELOPMENT.md) documentation.
+
+If you would like to help with translations 🌐 you can do so on [Crowdin](https://crowdin.com/project/mastodon).
+
+## LICENSE
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
-```
+```text
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
This program is free software: you can redistribute it and/or modify it under
@@ -113,7 +113,3 @@ details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see https://www.gnu.org/licenses/
```
-
-[CONTRIBUTING]: CONTRIBUTING.md
-[DEVELOPMENT]: docs/DEVELOPMENT.md
-[OpenCollective]: https://opencollective.com/mastodon
diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb
index 91849811e3..3cfd1e1761 100644
--- a/app/controllers/admin/account_actions_controller.rb
+++ b/app/controllers/admin/account_actions_controller.rb
@@ -14,16 +14,20 @@ module Admin
def create
authorize @account, :show?
- account_action = Admin::AccountAction.new(resource_params)
- account_action.target_account = @account
- account_action.current_account = current_account
+ @account_action = Admin::AccountAction.new(resource_params)
+ @account_action.target_account = @account
+ @account_action.current_account = current_account
- account_action.save!
-
- if account_action.with_report?
- redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
+ if @account_action.save
+ if @account_action.with_report?
+ redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
+ else
+ redirect_to admin_account_path(@account.id)
+ end
else
- redirect_to admin_account_path(@account.id)
+ @warning_presets = AccountWarningPreset.all
+
+ render :new
end
end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index 902feef683..b61a569860 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -64,6 +64,9 @@ module SignatureVerification
return (@signed_request_actor = actor) if signed_request.verified?(actor)
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}"
+ rescue Mastodon::MalformedHeaderError => e
+ @signature_verification_failure_code = 400
+ fail_with! e.message
rescue Mastodon::SignatureVerificationError => e
fail_with! e.message
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb
index 9e303fba2d..9162620534 100644
--- a/app/controllers/concerns/web_app_controller_concern.rb
+++ b/app/controllers/concerns/web_app_controller_concern.rb
@@ -50,6 +50,13 @@ module WebAppControllerConcern
return unless current_user&.require_tos_interstitial?
@terms_of_service = TermsOfService.published.first
+
+ # Handle case where terms of service have been removed from the database
+ if @terms_of_service.nil?
+ current_user.update(require_tos_interstitial: false)
+ return
+ end
+
render 'terms_of_service_interstitial/show', layout: 'auth'
end
diff --git a/app/javascript/mastodon/features/compose/components/search.tsx b/app/javascript/mastodon/features/compose/components/search.tsx
index ae242190e8..2d44772ba2 100644
--- a/app/javascript/mastodon/features/compose/components/search.tsx
+++ b/app/javascript/mastodon/features/compose/components/search.tsx
@@ -47,10 +47,6 @@ const labelForRecentSearch = (search: RecentSearch) => {
}
};
-const unfocus = () => {
- document.querySelector('.ui')?.parentElement?.focus();
-};
-
const ClearButton: React.FC<{
onClick: () => void;
hasValue: boolean;
@@ -107,6 +103,11 @@ export const Search: React.FC<{
}, [initialValue]);
const searchOptions: SearchOption[] = [];
+ const unfocus = useCallback(() => {
+ document.querySelector('.ui')?.parentElement?.focus();
+ setExpanded(false);
+ }, []);
+
if (searchEnabled) {
searchOptions.push(
{
@@ -282,7 +283,7 @@ export const Search: React.FC<{
history.push({ pathname: '/search', search: queryParams.toString() });
unfocus();
},
- [dispatch, history],
+ [dispatch, history, unfocus],
);
const handleChange = useCallback(
@@ -402,7 +403,7 @@ export const Search: React.FC<{
setQuickActions(newQuickActions);
},
- [dispatch, history, signedIn, setValue, setQuickActions, submit],
+ [signedIn, dispatch, unfocus, history, submit],
);
const handleClear = useCallback(() => {
@@ -410,7 +411,7 @@ export const Search: React.FC<{
setQuickActions([]);
setSelectedOption(-1);
unfocus();
- }, [setValue, setQuickActions, setSelectedOption]);
+ }, [unfocus]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
@@ -461,7 +462,7 @@ export const Search: React.FC<{
break;
}
},
- [navigableOptions, value, selectedOption, setSelectedOption, submit],
+ [unfocus, navigableOptions, selectedOption, submit, value],
);
const handleFocus = useCallback(() => {
@@ -481,12 +482,38 @@ export const Search: React.FC<{
}, [setExpanded, setSelectedOption, singleColumn]);
const handleBlur = useCallback(() => {
- setExpanded(false);
setSelectedOption(-1);
- }, [setExpanded, setSelectedOption]);
+ }, [setSelectedOption]);
+
+ const formRef = useRef(null);
+
+ useEffect(() => {
+ // If the search popover is expanded, close it when tabbing or
+ // clicking outside of it or the search form, while allowing
+ // tabbing or clicking inside of the popover
+ if (expanded) {
+ function closeOnLeave(event: FocusEvent | MouseEvent) {
+ const form = formRef.current;
+ const isClickInsideForm =
+ form &&
+ (form === event.target || form.contains(event.target as Node));
+ if (!isClickInsideForm) {
+ setExpanded(false);
+ }
+ }
+ document.addEventListener('focusin', closeOnLeave);
+ document.addEventListener('click', closeOnLeave);
+
+ return () => {
+ document.removeEventListener('focusin', closeOnLeave);
+ document.removeEventListener('click', closeOnLeave);
+ };
+ }
+ return () => null;
+ }, [expanded]);
return (
-