This PR takes the localization features out of "experimental" to prep
for the announcement
- rename settings and gives them its own area
- `experimental_content_localization` to `content_localization_enabled`
- `experimental_content_localization_allowed_groups` to
`content_localization_allowed_groups`
- `experimental_content_localization_supported_locales` to
`content_localization_supported_locales`
- `experimental_anon_language_switcher` to
`content_localization_anon_language_switcher`
- migration
- related to https://github.com/discourse/discourse-ai/pull/1439
| screenshot 📸 |
|---|
| <img width="964" alt="Screenshot 2025-06-17 at 5 06 32 PM"
src="https://github.com/user-attachments/assets/9a8b2c38-c846-4fc9-8ddd-815c45cc3d0e"
/> |
The `on_update` callback of triggers is called with previous fields and
new fields, values. However since a recent change:
https://github.com/discourse/discourse/pull/32810 this relationship is
cached and would cause previous fields to equal new fields. This commit
ensures we are refreshing the relationship before calling the
`on_update` callback.
This is a follow up to b02bc707dec12c607511d4a95c7d791f63131b49 where
the `S3Helper#upsert_tag` method was introduced. Using the method
resulted in an error being thrown because the key was incorrect.
Currently when a model is not found, we raise an `ArgumentError`
exception and that exception is stored in the resulting context object.
However, since we’re also storing unexpected exceptions, this default
exception can pollute the context object when we need to inspect it or
act on it.
This patch addresses that issue by raising a custom exception instead,
and we then discard it from the context object.
Adds a Discourse ID authenticator. Not available for use in production
just yet, but soon communities will be able to use this service to let
users authenticate using a central Discourse ID account.
Includes a support for a `/revoke` action, allowing users to log out of
multiple client instances from a central auth service.
Internal ticket: t/155397
---------
Co-authored-by: Loïc Guitaut <loic@discourse.org>
Strips the site's base URL and `https`/`mailto` protocols from the link
toolbar display

Refactors the toolbar to use getters instead of function calls, re-uses
`DButton` for the `href` toolbar item instead of a custom `a` tag, fixes
a bug where we passed a translated string to DButton's `label` that
expects a i18n key, and adds support to “disabled” buttons.
Slightly simplifies the toolbar implementation by extracting one step of
indirection removing `toolbarButton` from d-editor.
This commit is a follow-up to b02bc707dec12c607511d4a95c7d791f63131b49
where `Upload#update_secure_status` does not call
`FileStore::S3Store#update_upload_access_control` if
`s3_use_acls` is disabled. This is no longer correct as an upload's
access control on S3 can now be based on tags if the
`s3_enable_access_control_tags` site setting is
enabled.
To fix this, this commit removes the `s3_use_acls` check in
`Upload#update_secure_status` and updates
`FileStore::S3Store#default_s3_options`
to not set the `acl` option if the `s3_use_acls` site setting is
disabled.
This commit is a follow up to b02bc707dec12c607511d4a95c7d791f63131b49.
When the `s3_enable_access_control_tags` site setting is enabled,
calling `FileStore::S3Store#update_access_control` will result in a
argument error.
This commit introduces a new property `hasNoPreferredMode` to the
chat state manager, which represents a user who has not purposely
set chat mode to drawer or full page, meaning they have no LocalStorage
value set.
This can be useful for themes to change the chat mode but only
if the user has no preference already.
c.f.
https://meta.discourse.org/t/full-screen-chat-as-default-for-collaboration-setup/369849
Before this change, we were relying on the `lograge`'s gem default
`log_level` of `info`. However, `lograge` should be following
`Rails.application.config.log_level` instead and this commits seeks to
correct that behaviour.
### What I changed
- Wrapped the theme title and the pencil icon in a single `DButton` so
the whole thing is clickable
- Added ARIA stuff: heading role, level, and label to help screen
readers know it’s both a title and editable
### Why
Changed it so the entire title acts as the edit trigger, instead of just
the icon.
[Kapture 2025-06-13 at
16.34.12.webm](https://github.com/user-attachments/assets/c44049a0-bfd0-455a-94a3-fe9ec4d387ff)
A new `<InterpolatedTranslation>` component for interpolating components into translatable strings.
This change also includes a little bit of refactoring in the `i18n` library to make it easier to hook into.
The current way mentions are prioritised when trying to autocomplete
(with the `@` character in composer and some other search interfaces),
it's fairly easy for the list of suggestions to get crowded out by
seemingly unrelated suggestions as users that match on metadata are
prioritised in front of all groups.
The existing order of prioritisation:
1. All users that match (even partially) on usernames, names, metadata
2. Emails
3. Groups
This PR changes the order of prioritisation to favour the following in
order:
1. Exact match on User usernames
5. Exact match on Group names
6. Emails (this is kind of a special case, we don’t allow the ‘@’
character in usernames or group names anyway, but we do want to include
it here to easily adhere to the suggestion list limit)
7. Partial match on User usernames
8. Partial match on Group names
9. Exact match on User names
10. Partial match on User names
11. Users with matching metadata
Previously, a mention autocomplete with no matching entities would at
minimum return groups viewable by the user, this change also removes
that so it's a stricter autocomplete.
We recently added validation for mentions in rich text editor mode of
composer in #32879.
In an effort to eliminate potential edge cases for non-text nodes, I
have restored the input rules and simplified the approach used in the
plugin code.
This change means that Instead of finding and inserting mention nodes
within text while the user is typing, we let the input rules handle the
mention node creation part. This allows the plugin code to focus
entirely on validating nodes within the document.
As part of this change we introduced a new data attribute ("data-valid")
which will assume by default that mentions are valid and therefore be
"true", but will proceed to invalidate them by changing to false for
mentions that were not found. It also means that invalid mentions are
retained as mention nodes, whereas previously we would convert them to
text.
When uploading > 1 video in the composer, any upload after
the first one would fail silently, and not show anything in
the composer.
This was happening because we were removing uploads before the video
thumbnail callback was fired, but that had code like this `if
(this.#inProgressUploads.length === 0) { this.#reset() }`, which meant
that before the second upload was completely done we were resetting the
state.
Instead, we can remove the in progress upload file inside the video
thumbnail callback, which will ensure that the state is not reset
until all uploads are done.
This commit introduces a `s3_enable_access_control_tags` site setting
which,
when enabled, adds a `discourse:acl` tag with values `public` or
`private` to
S3 objects created by the application. The presence of the tags on S3
objects
enables bucket administrators to implement tag-based access control
policies, providing an alternative to
object ACLs which AWS now discourages.
The `discourse:acl` tag can be customized via the
`s3_access_control_tag_key ` site setting.
Values for `public` and `private` can also be customized via the
`s3_access_control_tag_public_value` and
`s3_access_control_tag_private_value ` site settings respectively.
### Reviewer Notes
To test it locally, run the following commands in your working discourse
directory:
1. `script/install_minio_binaries.rb`
2. Start a local minio server by running: `bundle exec rails runner
script/local_minio_s3.rb`
3. bundle exec rails runner "SiteSetting.enable_s3_uploads = true"
5. Start your development rails server with the following environment
variables: `DISCOURSE_ENABLE_S3_UPLOADS=true
DISCOURSE_S3_ENABLE_ACCESS_CONTROL_TAGS=true
DISCOURSE_BACKUP_LOCATION=s3`
This commit makes the "Color palettes" link in the admin sidebar points to
the new color palettes page at `/admin/config/colors` instead of the
legacy one `/admin/customize/colors` if the experimental feature flag
for the improved color palette system is enabled.
The filtering is currently being done in the controller. This leads to weird things where the filter loop might already be initiated and we get double filtering, as well as the issue that we can't abort like we could with a route transition.
After this change we:
- Cache the site settings fetched from the server in the route instance.
- Hook up the controller to the query params.
- Use the query params to filter the model in the route.