This is to try fix this error by sending the keys
on the element itself:
```
Failure/Error: page.send_keys([PLATFORM_KEY_MODIFIER, "v"])
Selenium::WebDriver::Error::StaleElementReferenceError:
stale element reference: stale element not found in the current frame
(Session info: chrome=135.0.7049.84); For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
```
Fixing hashtag text glued to the icon, and updated some related properties to using better units.
---------
Co-authored-by: Martin Brennan <martin@discourse.org>
The system test relies on a MessageBus message being published and
received by the client. As we currently don't have a reliable way to
ensure that we run an assertion after the client has received all
MessageBus messages, we will just double the timeout for now.
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:

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.
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>'
```
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.
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>
This change allows you to add a reaction to the most recent message, by sending a reaction message.
A reaction message can be formatted as `+{emoji}` (eg, `+❤️`), or as `+{emoji_code}` (eg, `+❤️`).
Due to a recent regression the selection management was failing in
drawer mode for threads. We were not correctly setting the active
thread.
This commit fixes the issue and adds a spec.
Previously the return to forum link would automatically take you to the
forum homepage, however this was not intended functionality. We should
attempt to take the user to their last viewed forum page.
This change fixes a bug in how we determined what the destination link
should be.
Internal ref: /t/110508
The following case was bugged:
- visit a thread in full page chat
- click channel title
- click back button in channel navbar
- 💥 you would have a channel with limited width as if there was
still the thread showing next to it, but it was empty
This commit does several changes:
- it moves the ownership of the last message info to the channel instead
of storing it on the message, it avoids the need to iterate over every
messages
- makes an optimistic update of the last read message id
- adds a spec to confirm this behavior
Users can now decide if they want to send a message on:
- <kbd>enter</kbd>
- <kbd>meta + enter</kbd>
If you choose <kbd>meta + enter</kbd>, <kbd>enter</kbd> will add a
linebreak.
<img width="192" alt="Screenshot 2025-01-21 at 12 57 48"
src="https://github.com/user-attachments/assets/abfd6f8b-83b3-4e6f-be67-8f63d536ca8a"
/>
```
Checking for expected text of nil is confusing and/or pointless
since it will always match. Please specify a string or regexp instead.
plugins/chat/spec/system/chat_message_interaction_spec.rb:51
```
When clicking the "new features" notification item for
admins that navigates to /whats-new, the user notification
menu was not closing. This was happening because inside
the UserMenu::Menu component, the `routeDidChange` event
was firing and calling `closeUserMenu()`, however this is already
called inside the `Header` component via `toggleHamburger()`.
This is _only_ happening for the "new features" notification
item, other items have already been destroyed and they do not get
the `routeDidChange` event. No idea why, but this fixes the issue
in a global way.
And change the "formula" to check for duplicate messages to
- no duplicate check in 1:1 DMs
- only duplicate check in group DMs / channels, for posts made by the
same user, in the past 10 seconds
Internal ref - t/144262
The chat emoji picker is renamed emoji-picker, and the old emoji-picker is removed.
This commit doesn't attempt to fully rework a new emoji-picker but instead tries to migrate everything to one picker (the chat one) and add small changes.
Other notable changes:
- all the favorite emojis code has been mixed into one service which is able to store one state per context, favorites emojis will be stored for all topics, and for each chat channel. Meaning that if you always use a specific emoji in a channel, it will only show as favorite emoji in this channel.
- a lot of static code has been removed which should improve initial load perf of discourse. Initially this code was around to improve the performance of the emoji picker rendering.
- the emojis are now stored, once the full list has been loaded, if you close and reopen the picker it won't have to load them again.
List of components:
- `<EmojiPicker />` will render a button which will open a dropdown
- `<EmojiPickerContent />` represents the content of the dropdown alone, it's useful when you want to render a picker from an action which is not the default picker button
- `<EmojiPickerDetached />` just a simple wrapper over `<EmojiPickerContent />` to make it easier to use it with `this.menu.show(...)`
---------
Co-authored-by: Renato Atilio <renatoat@gmail.com>
This test is flaky, sometimes it fails with the hamburger menu blocking the click on the emoji, other times it just doesn't return the list of emojis when the search term is there.
Other similar emoji tests are skipped. Stopping the bleed, but we still need to figure out a more reliable testing strategy here.
Will revisit after #28277 is merged.
ref /t/-/145212
After searching for an emoji through the autocomplete, when a user uses the emoji picker (to see the full list of emojis) and selects one, the content in the chat composer would contain both the partially written emoji and the full :selected_emoji: code.
The actual fix is just re-using the emojiSelected method from TextManipulation.
This converts the `<AdminPageHeader />` component and the
`<AdminPageSubheader />` components into new components
that can be used outside of admin, and updates the CSS classes.
Also introduces a `<DPageActionButton />` component and child
components for the header action buttons.
I have to keep the old admin-only components around for
now until plugins are updated, then we can remove it,
and remove the re-exports that are done within
admin-page-action-button.gjs
This commit introduces <NotificationsTracking /> which is a wrapper component around <DMenu /> which replaces the select-kit component <TopicNotificationsButton />.
Each tracking case has its dedicated component:
- topic -> `<TopicNotificationsTracking />`
- group -> `<GroupNotificationsTracking />`
- tag -> `<TagNotificationsTracking />`
- category -> `<CategoryNotificationsTracking />`
- chat thread -> `<ThreadNotificationsTracking />`
In this PR, we added functionality to hide the admin header for edit/new actions - https://github.com/discourse/discourse/pull/30175
To make it work properly, we have to rename `show` to `edit` which is also a more accurate name.
This change creates a shallow copy of the public message channels so we don't change the original array while sorting.
Without this change the publicMessageChannels getter cache gets invalidated and cached again with the sorted channels array instead, which causes a bug where the sidebar channel list sorting order is updated by activity when user interacts with the chat drawer.
We've seen in some communities abuse of user profile where bios and other fields are used in malicious ways, such as malware distribution. A common pattern between all the abuse cases we've seen is that the malicious actors tend to have 0 posts and have a low trust level.
To eliminate this abuse vector, or at least make it much less effective, we're making the following changes to user profiles:
1. Anonymous, TL0 and TL1 users cannot see any user profiles for users with 0 posts except for staff users
2. Anonymous and TL0 users can only see profiles of TL1 users and above
Users can always see their own profile, and they can still hide their profiles via the "Hide my public profile" preference. Staff can always see any user's profile.
Internal topic: t/142853.
This change sorts unread channels in descending order based on last message date, so channels with the latest activity will always appear at the top. It also adds some improvements for sorting channels with unread threads, now when multiple channels have unread threads, they will be sorted by last thread reply date to ensure more active channels rise to the top.
For DM channels, the order is now:
- Urgent (green badge) - unread messages, mentions and unread watched threads (most recent activity at top)
- Unread (blue badge) - unread tracked threads (most recent thread reply at top)
- Everything else (most recent message at top)
Blocks allow BOTS to augment the capacities of a chat message. At the moment only one block is available: `actions`, accepting only one type of element: `button`.
<img width="708" alt="Screenshot 2024-11-15 at 19 14 02" src="https://github.com/user-attachments/assets/63f32a29-05b1-4f32-9edd-8d8e1007d705">
# Usage
```ruby
Chat::CreateMessage.call(
params: {
message: "Welcome!",
chat_channel_id: 2,
blocks: [
{
type: "actions",
elements: [
{ value: "foo", type: "button", text: { text: "How can I install themes?", type: "plain_text" } }
]
}
]
},
guardian: Discourse.system_user.guardian
)
```
# Documentation
## Blocks
### Actions
Holds interactive elements: button.
#### Fields
| Field | Type | Description | Required? |
|--------|--------|--------|--------|
| type | string | For an actions block, type is always `actions` | Yes |
| elements | array | An array of interactive elements, maximum 10 elements | Yes |
| block_id | string | An unique identifier for the block, will be generated if not specified. It has to be unique per message | No |
#### Example
```json
{
"type": "actions",
"block_id": "actions_1",
"elements": [...]
}
```
## Elements
### Button
#### Fields
| Field | Type | Description | Required? |
|--------|--------|--------|--------|
| type | string | For a button, type is always `button` | Yes |
| text | object | A text object holding the type and text. Max 75 characters | Yes |
| value | string | The value returned after the interaction has been validated. Maximum length is 2000 characters | No |
| style | string | Can be `primary` , `success` or `danger` | No |
| action_id | string | An unique identifier for the action, will be generated if not specified. It has to be unique per message | No |
#### Example
```json
{
"type": "actions",
"block_id": "actions_1",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Ok"
},
"value": "ok",
"action_id": "button_1"
}
]
}
```
## Interactions
When a user interactions with a button the following flow will happen:
- We send an interaction request to the server
- Server checks if the user can make this interaction
- If the user can make this interaction, the server will:
* `DiscourseEvent.trigger(:chat_message_interaction, interaction)`
* return a JSON document
```json
{
"interaction": {
"user": {
"id": 1,
"username": "j.jaffeux"
},
"channel": {
"id": 1,
"title": "Staff"
},
"message": {
"id": 1,
"text": "test",
"user_id": -1
},
"action": {
"text": {
"text": "How to install themes?",
"type": "plain_text"
},
"type": "button",
"value": "click_me_123",
"action_id": "bf4f30b9-de99-4959-b3f5-632a6a1add04"
}
}
}
```
* Fire a `appEvents.trigger("chat:message_interaction", interaction)`
When using chat in drawer mode, after you've clicked on a chat bookmark in the user menu, clicking any other chat bookmark would "do nothing".
In 8b18fd155679fa9b9d6e660ac74130cb79daabdc we added an optimization to prevent the same route from being reloaded, but it ended up breaking the bookmarks.
This commit reverts the changed made the above commit and adds a system specs that ensure we can click two chat bookmarks in the user menu when using chat in drawer mode.
Internal ref - t/134362
Adds channels with unread threads (watching/tracking) to the sorting logic for both public and direct message channels.
Previously channels with unread threads could easily be missed as we didn't bump them to the top when new thread replies were created.
We are also adding a blue unread badge next to DM channels when there is an unread thread, as previously they weren't appearing as unread within the DMs tab (they only showed within the My Threads section).