Now that we have transitioned lightbox to PhotoSwipe, we can remove the
Magnific library and the `SiteSetting.experimental_lightbox` code paths.
The site setting value will be fully removed from the database in a
follow up migration.
Internal ref - /t/165699
As a follow-up of #36218.
* decrease a threshold number from `>15` to `>=10` for filter to render
* add const `FILTER_VISIBILITY_THRESHOLD` as a single source of truth
for controlling when filter should be shown
* changes hardcoded threshold with imported
`FILTER_VISIBILITY_THRESHOLD` in combo-box
* consolidate usage of `visible: true` in specs
Categories filter now appears in the dropdown for lazy loaded
categories.
<img width="293" height="404" alt="Screenshot 2025-11-26 at 10 39 22"
src="https://github.com/user-attachments/assets/055b414d-e867-4d32-a8d2-ff762eb0c752"
/>
### Context
For users in `lazy_load_categories_groups`, the filter was not rendered,
because the initial category list was below the threshold. The logic
controlling filter's visibility ignored the async scenario.
### Changes
For users in `lazy_load_categories_groups`, `autoFilterable` setting is
set to `true` regardless of a number of categories.
Also, fix quoting logic so when a draft is closed it will be reloaded
prior to adding quote.
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
To make things a bit clearer for admins, and to bury the option to
enable changes only for specific groups a bit since we don't think
most sites will do this, this commit changes the upcoming change
toggle to an "Enabled for:" dropdown with the following options:
* No one
* Everyone
* Staff only
* Specific group(s)
We automatically save the staff site setting group in the background
if staff only is selected, and we only save specific groups (and toggle
the
change on) once more than one group is selected.
---------
Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
Adds support to the grid markdown-it token in the form of a simple block
node, where regular editing and drag and drop work the same as usual.
It also extracts the auto-grid logic from ComposerUpload to live in the
textarea-specific `textarea-text-manipulation`, with a new logic for the
ProseMirror version.
The content localization feature we have (e.g. on meta) currently
assumes that everyone has set their user preferences language to
a language that we support.
If Eleni sets Greek as her Interface language, it is a non-supported
language. Posts written in Chinese or French will remain in Chinese or
French, as there is no Greek translation.
The commit shows Eleni localized posts in English (i.e. site default locale)
when a post is written in a different language. A site setting
`content_localization_use_default_locale_when_unsupported` is default
true and introduced.
When a user is viewing a localized title, and edits the title, they are
shown the original title.
They are then faced with the conundrum of updating it without being sure
if they are editing the original or localization.
This commit allows them to choose in cases where it is ambiguous.
Meta: https://meta.discourse.org/t/topic-title-translation-issues/388848
Prior to this commit, you had to exactly click on the checkbox to select
a topic row, now you can click anywhere in the row. It should especially
be better on mobile.
On top of this we have also added a background effect for selected rows
and an active state for the bulk select button.

Follow-up of #36050.
This PR contains clean-up and minor refactor work related to categories
copies and breadcrumb dropdowns:
* hide search input in filters dropdown if list is less than 10 options
* update copies to follow Discourse conventions
* remove unused `shouldDisplayIcon`
* fix spec flakiness by changing check from class name existence to
block visibility
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
As a follow-up of #35908.
Allow filtering posts by user from the user card of deactivated user.
### Context
The filter button was missing for deactivated accounts because
`topic_post_count` wasn’t exposed in `InactiveUserSerializer`.
### Result
<img width="701" height="216" alt="Screenshot 2025-11-14 at 00 11 27"
src="https://github.com/user-attachments/assets/e1b8d2a4-9465-4225-aaed-0582f43b8d90"
/>
This switches the default tab for refreshed reviewables to "timeline" to
provide more glance-able context and reverses the order... putting the
oldest events at the top and the input at the bottom.
I've also adjusted the form styling to minimize space, cleaned up some
general spacing, and made it clearer who's profile you're adding a note
to.
The textarea now auto expands as needed using `ExpandingTextArea`
Before:
<img width="1750" height="962" alt="image"
src="https://github.com/user-attachments/assets/257e041e-6f9b-4b22-b08e-a94310c21f6c"
/>
After:
<img width="2270" height="1586" alt="image"
src="https://github.com/user-attachments/assets/48c9f296-1f69-40f7-bbb6-e8df2a46878b"
/>
This includes a number of improvements based on some internal feedback,
including:
* Added date to top right of reviewable item
* Improved rendering of deleted users
* Adjusted colors for better light/dark mode support
* Adjusted flag counts to only show >1
<img width="2186" height="938" alt="image"
src="https://github.com/user-attachments/assets/cb741c28-5d9f-42f3-b0cf-5a21ac57bf78"
/>
* Improved image sizing (was too small) and allowing lightbox to
function by decorating post content
* Adjusted column sizes from 70/30 to 80/20
* Fixed HTML not rendering in timeline description and escaped user
input
* Fixed some overflow issues on narrow screens
Allow filtering posts by user from the user card even when the user’s
profile is hidden.
### Context
The filter button was missing for hidden profiles because
`topic_post_count` wasn’t exposed in `HiddenProfileSerializer`.
### Changes
* include `topic_post_count` in `HiddenProfileSerializer` - enables
filter button rendering on a user card for hidden profiles
* extract duplicate logic into private method `assign_topic_post_count`
* remove unused parameter `username` in `filterPostsLabel` computed
property
|Before|After|
|---|---|
|<img width="707" height="209" alt="Screenshot 2025-11-07 at 22 39 07"
src="https://github.com/user-attachments/assets/4de036f2-2dbe-4a59-98db-0e8aea16a2a5"
/>|<img width="706" height="258" alt="Screenshot 2025-11-07 at 22 37 51"
src="https://github.com/user-attachments/assets/7e82b7e9-d796-4ea5-89c0-47220e57af07"
/>|
This change reverses the image order when loading PhotoSwipe for RTL
languages. Meaning that the navigation arrows, left/right keys and
swiping all work in the expected way.
The default counter is disabled and a custom counter is added to support
both LTR and RTL.
## 🔍 Overview
This update adds the ability to view the raw original post when using
the translation editor. It simply adds a toggle switch so when the
preview pane is set to Original mode, you can toggle to view the raw
post.
This update also sneaks in a small fix for wide images in the preview
preventing them from overflowing.
## 📷 Screenshots
### ← Before
<img width="693" height="760" alt="Screenshot 2025-11-05 at 13 38 39"
src="https://github.com/user-attachments/assets/effa77eb-1153-4edb-9b3a-6aa560f54217"
/>
### → After
<img width="693" height="759" alt="Screenshot 2025-11-05 at 13 38 12"
src="https://github.com/user-attachments/assets/a74c87bb-c0cb-4f1d-a9bf-139af7a05601"
/>
Minor, but this has been annoying me... the welcome banner currently
says "Welcome back, username!" even if it's your first time on the site.
We can check `previous_visit_at` to see if someone has visited before,
and if they haven't we should just say "Welcome username!"
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
We are adding back the option to create **Personal message** from
composer's _"New topic"_ mode.
Now users have a simpler way to create a new personal message:
1. Start New topic (by pressing <key>C</key> hotkey or clicking **New
topic** button)
2. Click actions dropdown
3. Select **New message** option
https://github.com/user-attachments/assets/e7df15b2-834b-4c3f-83b8-39f263ebe5f2
This updates some styles for the `Reviewable UI refresh` setting
This improves some visual styles
* Makes "flagged as" less button like
* Extends content width to fill space
* Wraps the entire item in an outline rather than the summary
* Makes the controls on the right sticky on scroll so they're more
closely associated with the content
I've also added a way to add custom labels to include type information,
so these can say "AI flagged as" "Chat message flagged as" which
improves upon the previous labeling that used "flagged as" for every
type.
Before:
<img width="2168" height="1096" alt="image"
src="https://github.com/user-attachments/assets/4da9dd9b-28b3-4203-ae33-c7516e37dcb1"
/>
After:
<img width="2256" height="930" alt="image"
src="https://github.com/user-attachments/assets/52e54cc9-e1fa-4041-8747-7c61e691be7d"
/>
This PR introduces part one of the "Upcoming changes" interface for
Discourse admins.
The upcoming changes feature is an enhancement around our existing
site-setting based feature flagging and experiments system. With some
light metadata, we can give admins a much better overview of the current
work we are doing, with ways for them to opt-out in early stages and
opt-in to things that we haven’t yet turned on by default for them.
This system, along with encouraging a more liberal use of site setting
flags for features, experiments, and refactors in the app, should
minimise the problem of breakages and disruptions for all Discourse
users. It is also our intent with this system for it to be easier for
designers to add and remove these changes.
Finally, it also gives us a kind of running changelog that we can use to
communicate with site owners before releases and “What’s new?” updates.
### FOR REVIEWERS
This initial PR is gated behind a hidden `enable_upcoming_changes` site
setting, because there is still more work to do before we reveal this to
admins.
To test the UI, you can add this metadata under any boolean-based site
setting, though upcoming change settings will specifically be hidden:
```
upcoming_change:
status: "alpha" (see UpcomingChanges.statuses.keys)
impact: "feature,staff" (feature|other for the first part, staff|admins|moderators|all_members|developers for the second part)
learn_more_url: "https://some.url"
```
To test the images, add an image under `public/images/upcoming_changes`
with the file name as `SITE_SETTING_NAME.png`
### Interface
Admins can see the following in the interface for upcoming changes:
* The status of the change. Changes can progress along these statuses:
* Pre-Alpha
* Alpha
* Beta
* Stable
* Permanent
* The impact of the change. This is split into Type and Role. Type can
be "Feature" or "Other" for now. Changes may affect the following roles:
* Admins
* Moderators
* Staff
* All members
* The plugin that is making the change
* The groups that are opted-in to the change. Admins can control these
groups for a gradual rollout. If a change is enabled, it is limited to
these groups.
* In some cases, an image related to the change, behind the "Preview"
link
* A link to learn more about the change
Admins can filter the changes by name, description, plugin, status,
impact type, and whether the change is enabled.
### Promotion system
For our hosted customers, we intend to have a status-based
auto-promotion system as changes progress.
For all sites, once a change reaches the Stable status, if an admin
opts-out of that change it will generate an admin problem message that
will be shown on the dashboard.
For self-hosted Discourse admins, changes will only be forcibly enabled
when they reach the Permanent state.
### Notification system
A notification system for upcoming changes so admins can stay informed
will be added in a followup PR.
---------
Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
Allows closing of lightbox by tapping outside of the image on mobile,
while retaining the toggle UI functionality when single tapping an image
on mobile lightbox.
This change also adds a useful system helper for `tap_screen_at(x, y)`
when working with mobile specs. To use it just set `mobile: true` on the
test or surrounding context block.
Steps to reproduce:
* Start drafting a reply to a topic
* Write some text.
* Navigate to a different topic while keeping the draft open
* Click reply on the composer and choose cancel in the modal asking
which topic you want to reply to.
* Write some more text.
* Copy the text you have written or memorize your input.
* Reload the page/ close the composer.
* Navigate back to the topic you replied to
* Open your draft.
* Everything written after clicking cancel is missing.
This was happening because we weren't doing anything on Cancel for
the topic reply choice dialog, and we were doing `disableDrafts` before
opening the dialog, so drafts never resumed.
The fix is to just save the current draft on cancel then turn on
draft saving again so the user can continue typing and delay the
question about "Which topic do you want to reply to?" until later.
c.f.
https://meta.discourse.org/t/draft-is-no-longer-automatically-saved-after-you-cancel-replying/386370
Also rename TopicLabelContent to TopicReplyChoiceDialog, it
is more specific and reflects what the component actually does.
When adding a note through the timeline tab, the note wasn't being
persisted to the reviewable's reviewable_notes array. This caused the
note to disappear when switching between tabs.
Our current implementation of lightbox uses Magnific Popup, which is now
deprecated and will only receive critical/security bug fixes. Magnific
also relies on jQuery, which we would like to remove where possible
throughout our codebase.
After looking at various options, the general consensus was that
PhotoSwipe was a close match to what we need.
Regular image types that are supported with our current lightbox (jpg,
png etc) will work with their existing cooked markup (no need to rebake
posts). This PR also adds support for SVG images and markup from various
theme components (ie. Discourse Mermaid).
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
Currently, if a user is part of a group in
`experimental_new_new_view_groups`,
they will see tabs for All, Topics, and Replies underneath the New tab
in the
/new topic list.
However, if one of the tabs has no topics (i.e. no more unread replies
or no more new topics), we hide the tabs entirely. This can be confusing
and inconsistent for members.
This commit changes the behavior so that the tabs are always shown.
Also, we change the text and route of the empty topic list CTA based on
the subtab
the user is on:
* Topics subtab, when there are new Replies:
* Button text: Browse new replies
* Button points to the Replies subtab
* Replies subtab, when there are new Topics:
* Button text: Browse new topics
* Button points to Topics subtab
* All subtab, or any subtab when there are no new topics of any kind:
* Button text: Browse latest topics
* Button points to /latest
Implements exact URL matching to prioritize specific URL patterns over
Ember route matching in sidebar navigation. When a sidebar link's URL
exactly matches the current URL, it will be highlighted with the
'exact-url-match' class instead of relying on Ember's route-based active
state detection.
This change seeks to improve the reliability of our system tests by
resolving the lack of consistency in the state of the client side
application between steps in a system test. This is achieved by patching
various action methods in `Capybara::Playwright::Node` and
`Capybara::Playwright::Browser` so that the methods execute an async
JavaScript function on the client side that waits for the client side
application to reach a settled state. A settled state is currently
defined as:
1. No inflight ajax requests. (_messageBus and presence requests are
excluded_)
2. 2 event cycles of the Javascript event loop has happened for for all
"click", "input", "mousedown", "keydown", "focusin", "focusout",
"touchstart", "change", "resize", "scroll" DOM events that fired.
For debugging purposes, a `--debug-client-settled` CLI flag has been
added to `bin/rspec`. When used, detailed debugging information will be
printed to the browser's console as well as to `stdout` of the
`bin/rspec` process.
This change was inspired by
https://evilmartians.com/chronicles/flaky-tests-be-gone-long-lasting-relief-chronic-ci-retry-irritation
and the https://github.com/makandra/capybara-lockstep rubygem.
These specs are to ensure category names remain translated when
navigating around.
In a future PR there will be a fix for logged in users, where category
names get overridden in the local store due to async requests updating
the category names in store.
1 of 2.
/t/163217
This commitreworks the validation and handling of color pickers in
the category edit page so they're simpler and more predictable. In
particular:
* It no longer attempts to "autocomplete" 3-digits hex values to the
equivalent 6-digits format -- we now accept both 3 and 6 digits formats
* It no longer disables the save button if the input color value is
invalid. The save button is always clickable, and if the admin attempts
to save with an invalid color value, the form will display errors
explaining which input is invalid and what's wrong with the color value
Internal topic: t/163062.
Covers default reactions,
`discourse_reactions_experimental_allow_any_emoji`,
`discourse_reactions_enabled_reactions`, and interaction with the
`emoji_deny_list` setting from core.
---------
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
Add a `Welcome banner text color` setting that admins can use to adjust the color of the heading and subheading text on the welcome banner when they are using a background image.
For now, we want to force the markdown mode for email template
editing for admins, as this is a niche area and doesn't much
benefit from RTE for now...we may want to revert this decision
in future and do a better solution here.
This commit also fixes an issue where, for templates like
system_messages.reviewables_reminder which have a `one` and
`other` key for their text body (and thus "multiple bodies"),
we were showing `object Object` in the editor. Instead, we
want to do the same thing we do for multiple subjects, which
is provide a link to site texts with the text:
> This email template has multiple bodies.
There is only one known case of this happening so far, but at least
this minimally handles the issue for now.
* Move Discard button closer to Reply
* Make button not red
* Fix trash can size on mobile
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
This spec was not testing the right thing, the draft
is not supposed to save if the topic title is too short,
but the way the expectations were written (checking the
float message) made it pass incorrectly.
Followup 369cc65865
If we do not save a draft e.g. in the case where the post
doesn't meet minimum length requirements, then do not show
the "Draft saved!" modal as that is misleading.
This commit modifies the composer buttons along the bottom,
removing the old "Close" button in favour of a distinct "Discard"
and a persistent "X" button.
For **Discard**:
* When there is any user-input content in the composer, clicking this
button will bring up the draft confirmation modal. Users can then
confirm the discard, decide to save their draft, or return to the
editor.
* When there is no user-input content in the composer, clicking this
button will close the composer.
* In this case, we should not prompt users with the draft confirmation
modal.
For the **"X"** button in the top-right of the composer on desktop and
mobile:
* When there is any user-input content in the composer, this should save
the content as a draft and close the composer. We don’t need to bring up
the draft confirmation in this case.
* When there is no user-input content in the composer, this should close
the composer without saving.
Remove the Close button from the composer on desktop.
This commit replaces the "enabled" checkbox on the automation page with a
toggle at the top of the automation page. We've made this change to make
the automation edit page consistent with the automations list page where
a toggle is used for turning on/off automations, and also consistent
with the rest of admin pages where we've been using toggle for
controlling the enable/disable state.
Internal topic: t/153523.