This commit allows the ProseMirror rich editor to display chat
transcripts copied from chat using the "Copy" button.
The BBCode usually looks something like this:
```
[chat quote="hunter;29856;2025-03-20T07:13:04Z" channel="design gems 🎉" channelId="95"]
haha **ok** _cool_
[/chat]
```
But there are several variations that must be accounted for:
* Single message from single user
* Multiple messages from a single and multiple users
* Messages inside chat threads
The rich transcript extension has to ignore many of the chat transcript
markdown
tokens because they simply aren't necessary -- none of the ProseMirror
nodes need
to be editable. So, we basically recreate the same HTML that the chat
transcript markdown
rule does in the `toDOM()` function. Maybe in future we want to make the
markdown rule
do less and have this HTML creation in one place, but for now we need to
mirror in both files.
---------
Co-authored-by: Renato Atilio <renato@discourse.org>
We are not waiting for asynchronous operations to complete before
executing our assertions.
### Reviewer notes
Example test failure:
https://github.com/discourse/discourse/actions/runs/14100970402/job/39496976787
```
Failure/Error: measurement = Benchmark.measure { example.run }
expected `Chat::Channel.count` to have changed by 1, but was changed by 0
[Screenshot Image]: /__w/discourse/discourse/tmp/capybara/failures_r_spec_example_groups_chat_new_message_from_params_with_multiple_users_creates_a_dm_channel_when_none_exists_626.png
~~~~~~~ JS LOGS ~~~~~~~
~~~~~ END JS LOGS ~~~~~
./plugins/chat/spec/system/chat_new_message_spec.rb:57:in `block (3 levels) in <main>'
./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure'
./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>'
./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout'
./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>'
./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
```
A system test in `system/search_spec.rb` was failing with the following
error frequently on CI:
```
Failure/Error: expect(search_page).to have_heading_text("Search")
expected `#<PageObjects::Pages::Search:0x00007fb9fcd3f028>.has_heading_text?("Search")` to be truthy, got false
[Screenshot Image]: /__w/discourse/discourse/tmp/capybara/failures_r_spec_example_groups_search_when_using_full_page_search_on_mobile_works_and_clears_search_page_state_912.png
~~~~~~~ JS LOGS ~~~~~~~
(no logs)
~~~~~ END JS LOGS ~~~~~
./spec/system/search_spec.rb:42:in `block (3 levels) in <main>'
./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure'
./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>'
./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout'
./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>'
./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
```
The failure screenshot shows that the "user" is on the homepage even
though we have already clicked the search icon and ensured that the user
can see the search container. I suspect there is some sort of race
condition here since Capybara executes clicks in quick sucession where
we clicked on both the homepage logo and the search icon. It may be
possible that Ember redirected the user to the search page first
before the browser was able to finish navigating the user to the `/`
href.
### Reviewer notes
Test flaked in
https://github.com/discourse/discourse/actions/runs/14085443789/job/39448197089
with the following failure screenshot:

With header search we toggle the search icon when scrolling on topics,
which can feel visually jarring when the search icon is in the middle.
Switching the position of the chat icon in the header reduces the impact
of this.
Whenever you are about to reach the limit of users in a group DM, if you
send requests to add new users in parallel, they might all go through
ignoring the limit due to a race condition.
Internal - t/145895
We intend to replace this JQuery autocomplete implementation soon. But
before that, we'd like to drop the raw-hbs compilation/runtime system.
Therefore, this commit does a 1:1 conversion of the autocomplete
templates to vanilla JS. They're fairly small/simple, so they're still
fairly readable.
Currently, anonymous/shadow users go through the same permission checks
for chat as normal users do. This means that if a site has chat enabled
for all users, anonymous users also get access to chat. This may be
undesirable for some communities, so we're adding a new site setting
`allow_chat_in_anonymous_mode` to block access to chat for anonymous
users.
Internal topic: t/148088.
This change allows more flexibility when starting a 1-1 direct message
with another user. If there are no messages in the new DM channel then
we should still allow them to add additional users.
This PR renames a couple of settings related to anonymous mode:
1. `allow_anonymous_posting` → `allow_anonymous_mode`. This setting is
used as a switch for the entire anonymous mode feature, so it makes
sense to give it a generic name that better reflects what the setting
does.
2. `allow_anonymous_likes` → `allow_likes_in_anonymous_mode`. The new
name is clearer and will match a new setting that we'll add to allow
anonymous users to post in chat.
Internal topic: t/148088.
introduces comprehensive statistics tracking for the Discourse
Automation plugin, allowing users to monitor the performance and
execution patterns of their automations:
- Add `discourse_automation_stats` table to track execution metrics
including run counts, execution times, and performance data
- Create a new `Stat` model to handle tracking and retrieving automation
statistics
- Update the admin UI to display automation stats (runs today/this
week/month and last run time)
- Modernize the automation list interface using Glimmer components
- Replace the older enable/disable icon with a toggle switch for better
UX
- Add schema annotations to existing models for better code
documentation
- Include extensive test coverage for the new statistics functionality
This helps administrators understand how their automations are
performing and identify potential bottlenecks or optimization
opportunities.
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Ted Johansson <ted@discourse.org>
1. **Multiselect Support for Choice Fields**
- Added a `multiselect` option to the choices field component
- Updated Field model to accept arrays as values for choices fields
2. **Post Content Feature Filtering**
- Added ability to filter posts based on content features:
- Posts with images
- Posts with links
- Posts with code blocks
- Posts with uploads
3. **Improved Group Filtering**
- Renamed `restricted_user_group` to `restricted_groups` to allow
filtering by multiple groups
- Added `excluded_groups` to replace `ignore_group_members` which was
complex for end users
- Renamed `restricted_groups` to `restricted_inbox_groups` for more
specific PM filtering and clarity.
4. **Public Topics Filter**
- Added a "Public Topics" filter option that excludes all secure
categories
The test has been flaking in CI with the following message:
```
Failure/Error: measurement = Benchmark.measure { example.run }
expected: "draft"
got: ""
(compared using ==)
[Screenshot Image]: /__w/discourse/discourse/tmp/capybara/failures_r_spec_example_groups_chat_composer_draft_when_loading_a_channel_with_a_draft_loads_the_draft_458.png
~~~~~~~ JS LOGS ~~~~~~~
~~~~~ END JS LOGS ~~~~~
./plugins/chat/spec/system/chat_composer_draft_spec.rb:31:in `block (3 levels) in <main>'
```
When creating a DM to a group in chat, we show
a count of users for that group if the number of
users exceeds SiteSetting.chat_max_direct_message_users.
However, we were also counting bot users here, we should
only be counting human users, this led to confusion because
the chat group count was different from the one on
e.g. /g/:group_name
This was preventing favorites emojis to work correctly as it was using
topic context. No tests for now as it's a very specific behavior hard to
test with acceptance test and a system spec seems heavy for this.
This was causing some confusion and now that we have the chat preference
to send with enter or metakey+enter it's even more confusing.
If your setting is send on enter, you need to use shift+enter for a new
line, even in codeblocks. If your setting is meta+enter you need to use
enter in the codeblock for a new line, meta+enter will send as expected.
Continues the work done on
https://github.com/discourse/discourse/pull/30815.
Adds `spoiler` and `inline_spoiler` nodes, parsers, and serializers, and
a click handler to toggle its blurriness in the editor.
- Allow deciding if we include or exclude sub categories
- Allow filtering to only look at PMs or Topics
- Allow selection of multiple categories
- Migrations to carry all data into new structure
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Prevents channel non-members from seeing the composer in threads. Since
they do not have the correct permissions to post within the channel, we
should show the join channel CTA in place of the chat composer.
We were using a zero width space added by CSS here, but have run into
occasional encoding issues for some reason? a couple people have
reported getting this instead of an empty space:

I can't repro the issue, but we can avoid it by removing this space in
CSS — setting the container height to `1em` along with `line-height:
normal` should make it consistent with the height of the text within.
In the blame it seems this static height on the container was added
after the pseudo hack, and achieves the same goal when I test it across
a Firefox/Chrome/Safari
1. remove the margin (it's displayed inline with text, so having a left
margin didn't make sense)
2. use em unit in min-height
3. slighty tighten the button (inline padding 0.5em -> 0.4em) so it's
more proportional
This commit moves most of emoji logic into the discourse-emojis gem:
https://github.com/discourse/discourse-emojis/
Most notably:
- images are now symlinked from the gem
- the gem provides path to the json files
Search aliases have also been made asynchronous and memoized. When you
will search for an emoji we will now load the aliases and store the list
for future use.
---------
Co-authored-by: David Taylor <david@taylorhq.com>
In #31366, we added the ability for admins to remove people from group chats. However, that only works as long as the admin is already in the group chat.
For forum-side group messages, admins can join any of them at will. This PR extends that same ability to chat for parity.