* remove default prop values where they're being set in constructor
* replace some `||` operators in constructors with `??` so the fallback boolean values are actually used
* remove an unused service injection (and sort the rest)
* remove unused prop
* inline an arg check
* remove an unnecessary `?.` operator
* sort element attributes
This change replaces the chat drawer tabs with new drawer routes for channels, direct messages and threads.
The main objective is to improve navigation within drawer, now that we have separation of chat sections in drawer.
The order of chat direct message groups can sometimes place usernames in an unexpected order, this change tests multiple combinations of usernames and accepts them no matter what order they are in.
In 4e7a75a7ec, we moved to a single admin plugin page and added a few fields to the "plugin serializer" but we already had a proper route with the correct serializers to properly load channels.
This fixes it by removing the "add_to_serializer" calls and changed the calls to "/admin/plugins/chat.json" to the proper "/admin/plugins/chat/hooks.json" route.
Meta - https://meta.discourse.org/t/names-are-missing-from-list-when-creating-new-chat-channel-webhooks/308481
When chat is enabled, there's a scheduled job that runs every 5 minutes to check whether we need to send a "chat summary" email to users with unread chat messages or mentions.
On Discourse with a large number of users, the query used wasn't optimal and sometimes taking minutes. Which isn't good when the query is called every 5 minutes 😬
This PR reworks the query in `Chat::Mailer.send_unread_mentions_summary`.
Instead of starting from the `users` table, it starts from the `user_chat_channel_memberships` table which is the main piece tying everything together.
The new query is mostly similar to the previous one, with some bug fixes (like ensuring the user has `allow_private_messages` enabled for direct messages) and is also slightly simpler since it doesn't keep track of the `memberships_with_unread_messages` anymore. That part has been moved to the `user_notifications.chat_summary` email method.
The `UserEmailExtension` has been deleted since that was using to N+1 update the `user_chat_channel_memberships.last_unread_mention_when_emailed_it`(quite a mouthful 😛) but that's now done directly in the `user_notifications.chat_summary` email method.
The "plat de résistance" of that PR - the `user_notifications.chat_summary` method has been re-worked for improved performances 🚀
Instead of doing everything in one query, it does 4 tiny ones.
- One to retrieve the list of unread mentions (@something) in "category" channels
- One to retrieve the list of unread messages in "direct message" channels (aka. 1-1 and group discussions)
- One to load all the chat messages for each "category" channels from the last unread mention
- One to load all the chat messages for each "direct message" channels from the last unread message
All the specs for both `Chat::Mailer` and `UserNotification.chat_summary` have been rewriten for easier comprehension and faster execution (mostly by not using chat services which makes the specs go 10x slower...)
Internal ref - t/129848
This change prevents explicitly declaring each route that should be intercepted for chat drawer mode.
In theory all chat drawer routes should be intercepted from the main chat routes file and therefore we would only need to add new drawer routes directly within chat-drawer-router.js.
This change allows chat drawer users to edit channel settings and members without leaving drawer mode. If a channel is open within chat drawer and the user clicks the Channel name, it will load channel settings within the drawer.
This change allows the correct number of members to be added when creating a group direct message, based on the site setting chat_max_direct_message_users.
Previously we counted the current user within the max user limit and therefore the count was off by 1.
Before this fix we could only list messages of a thread if it was part of a `threading_enabled` channel or if the thread was set to `force`.
Due to our design of also using a thread id when this is just a chain of replies so we can switch from threading enabled to disabled at any time, we will allow `Chat:: ListChannelThreadMessages` to list the messages of any thread, the only important requirements are:
- having a thread id
- being able to access this thread
To allow this, this commit simply removes the check on `threading_enabled` or `force`.
Under some circumstances, the TextField component could trigger a `Assertion Failed: You attempted to update attrs on ..., but it had already been used previously in the same computation...` error, causing the Ember app to crash.
A few follup changes after changing to the chat footer split for drawer:
* Fixing a bug that stretched the unread indicator on mobile
* Minor style changes in hover/focus behaviour for chat drawer
* Repositioning of unread indicator so it has more space at the top of the footer
* Using the `c-unread-indicator` mixin
Adds a placeholder image + CTA in chat, for empty channel and DM lists.
On desktop with drawer mode, we split chat into tabs (like mobile).
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: David Battersby <info@davidbattersby.com>
Co-authored-by: Régis Hanol <regis@hanol.fr>
In this PR service objects were moved to Core https://github.com/discourse/discourse/pull/26506
However, ServiceRunner should be moved as well. Mostly for CI to run effortlessly without loading plugins.
Prior to this fix we had too logic to detect if a user is active or not:
- idle codepath on the frontend
- online user ids on the backend
The frontend solution is not very reliable, and both solution are just trying to be too smart. Making a lot of people questioning why they receive a notification sometimes and sometimes not. This commit removes all this logic and replaces it with a much more simpler logic:
- you can't receive notifications for channel you are actually watching
- we won't play a sound more than once every 3seconds
The `onNotification` signature is:
```
onNotification(data, siteSettings, user, appEvents)
```
And we were not passing `appEvents`.
Also explicitly inject `currentUser` in `chat-notification-manager` service.
We consider that you should always receive a notification sound when someone speaks directly with you in chat.
This commit also refactors the way we play audio in chat to make it simpler and throttle it to 3 seconds.
We also added a safeguard to ensure we won't play sounds for old messages, this case can happen when message bus is catching up the backlog (eg: in an inactive tab for example).
When users click a link that points to an existing group chat, we should reopen that chat instead of creating a new group chat so users can more easily continue ongoing conversations.
activeChannel is something we should use less and less as it could not exist, in this case we have the channel right here in the function so there's no reason to reach for `this.chat.activeChannel`.
Prior to this fix we wouldn't intercept it, and we also wouldn't handle it, which in result would cause us to handle as a full page interaction and open the full page chat even if you were in drawer mode.
When a user had the chat option "Show activity indicator in header" set to "all new messages", and they would get a reply to a thread they're part of, the chat icon in the header would not show the unread bubble indicator.
In order to fix this, the `ChatHeaderIconUnreadIndicator` component will now `showUnreadIndicator` whenever there is either one unread public channel or there are unread threads.
I only added a system spec for this very specific path because I don't want to slow down the whole suite to test for all the various combination of the `chat_header_indicator_preference` values.
Internal ref - t/128874
Given this is currently buggy and we have the cancel button Im not sure this is actually necessary. I feel like it's adding a lot of noise of low value.
For now at least, it's better without it.
This commit reuses the existing codepath in desktop-notifications and make it available to use to chat.
primaryTab was too hard to test if not impossible in this service test, however isIdle and disabled notifications are correctly tested.
Prior to this change, only mentions would get a notification and a sound. This change will not create a notification for this case, but will play a sound. This is still respecting notification settings, not playing the sound when you are viewing the channel or not following it.
---------
Co-authored-by: Régis Hanol <regis@hanol.fr>
Debouncing the audio was causing the audio to be lost sometimes, somewhat randomly. It's not supposed to be necessary.
The commit also refactors the code to async/await.
Prior to this fix we were using `topic.current_post_number` which is coming from the server side and represents the initial topic scroll position when initially rendered to the end user. However, this value is not dynamic and is not updated when the user scrolls, `topic.currentPost` is the dynamic equivalent.
No test as there are very low chances a test like this one based on scroll position, will be reliable over time.
... wasn't properly escaped so it would should html entities (like `'` instead of the apostrophe `'`).
I checked all the other places we show an excerpt and this was the only one that was missing the call to `htmlSafe` -> `replaceEmoji`.
Internal ref - t/128877
The translations of the labels of some of the chat preferences dropdowns were not customizable via the "site texts" feature.
This was because they were declared outside of a Controller class and were thus not taking into account the customization through "site texts".
Internal ref - t/128859
When you reply to a chat message, we [always create a thread][1]. But when the channel we're in doesn't have threading enabled, the reply is _technically_ not a thread.
This changes the `in_thread?` method to check for both the presence of a `thread_id` and to ensure that the channel has `threading_enabled`.
Internal ref - t/128103/3
[1]: e6e3eaf472/plugins/chat/app/services/chat/create_message.rb (L110-L115)
Whenever you get a bookmark notification, a mention notification, or click on a bookmark on a message in a long thread, we should ensure we always highlight and show the proper message.
Before this fix, we would correctly load the thread, but would always start at the bottom.
Internal ref. t/128103
For both `chat_allowed_groups` and `chat_message_flag_allowed_groups`,
this commit removes the `is_staff?` guardian check, and instead
adds both `moderators` and `admins` auto groups as `mandatory_values`
to those settings, as part of an ongoing effort to do this for
group-based setting values.
This change adds a wrapper link around the thread list details on mobile to make the click area larger.
We also update child div elements to span to ensure valid html, since the link is an inline element and divs are block level.
---------
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
We were missing two `getURL` calls.
The test is now written for subfolder but it's good enough. If it's working for subfolder, it's working for non subfolders, the opposite being false.
menus and tooltips are now appended to their own portals. The service are the only responsible for managing the instances, prior to this commit, services could manage one instance, but the DMenu and DTooltip components could also take over which could cause unexpected states.
This change also allows nested menus/tooltips.
Other notable changes:
- few months ago core copied the CloseOnClickOutside modifier of float-kit without removing the float-kit one, this commit now only use the core one.
- the close function is now trully async
- the close function accepts an instance or an identifier as parameter
- prevents re-rendering avatars while updating messages quickly in the thread preview indicator
- ensures the cancel button is shown when you are admin OR when the streamed message is a reply to the current user
Prior to this commit, only system users had this pass.
Another significant change of the PR, is to make membership of a channel the angular stone of the permission check to create/update/stop streaming a message. The idea being, if you are a member of a channel already we don't need to check if you can join it AGAIN.
We also have `Chat::AutoRemove::HandleCategoryUpdated` which will deal with permissions change so it's simpler and less prone to error to consider the membership as the only source of truth.
There's no point checking if a user can join a channel if they are already part of it. This case was frequent when using `enforce_membership: true` for custom bots for example.
This case had not been tested end to end as `Discourse.track_events` was not working when wrapping `send_message`. Because of this lack of end to end test, a regression has been created when renaming the expected context properties. This commit fixes the regression and write a slightly convulted, but effective end to end test.
This commit introduces several enhancements to the ChatSDK module, aiming to improve the functionality and usability of chat thread interactions. Here's what has been changed and added:
1. **New Method: `first_messages`:**
- Added a method to retrieve the first set of messages from a specified chat thread.
- This method is particularly useful for fetching initial messages when entering a chat thread.
- Parameters include `thread_id`, `guardian`, and an optional `page_size` which defaults to 10.
- Usage example added to demonstrate fetching the first 15 messages from a thread.
2. **New Method: `last_messages`:**
- Added a method to retrieve the last set of messages from a specified chat thread.
- This method supports reverse pagination, where the user may want to see the most recent messages first.
- Similar to `first_messages`, it accepts `thread_id`, `guardian`, and an optional `page_size` parameter, defaulting to 10.
- Usage example provided to illustrate fetching the last 20 messages from a thread.
The TextCleaner step has been moved from chat message’s validation to create_message/update_message services. It allows us to easily tweak part of its behavior depending on the needs.
For example we will now disable strip_whitespaces by default when streaming messages as we want to keep newlines and spaces at the end of the message.
This change encourages users to title their threads to make it easier for other users to join in on conversations that matter to them.
The creator of the chat thread will receive a toast notification prompting them to add a thread title when on mobile and the thread has at least 5 sent replies.
That could cause flakeyness in specs depending in which timing message bus would arrive and it's not necessary as it should be updated with `handleThreadOriginalMessageUpdate`.
Follow up to #26712 to account for older threads that don't have a persisted excerpt, as this was previously generated on every page load.
This change allows us to build the excerpt on the fly when none exists, fixing the issue of missing message excerpts for thread previews (within channel) and thread lists (on mobile/desktop).
This change moves the chat message excerpt into a new database column (string) on the chat_messages table.
As part of this change, we will now set the excerpt within the `Chat::CreateMessage` service, and update it within the `Chat::UpdateMessage` service.
This commit will now allow us to track read position in a thread and returns to this position when you open the thread.
Note this commit is also extracting the following components to make it possible:
- `<ChatMessagesScroller />`
- `<ChatMessagesContainer />`
The `UpdateUserThreadLastRead` has been updated to allow this.
Various refactorings have also been done to the code and specs to improve the support of last read.
It's important to close the modal or we will just remove it from screen without calling callbacks, which will cause the body to be locked on iOS.
It's hard to test this behavior, as it only happens on iOS and the modal will disappear anyways, it's only a matter of ensuring it's closed correctly.
This is reverting part of 08ff0bac29 to only have the call on exit channel. This was causing incorrect unread update. I will refactor this in another commit.
This is to enable :array type attributes for Contract
attributes in services, this is a followup to the move
of services from chat to core here:
cab178a405
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Prior to this fix we were calling the action before closing the menu which could cause various callbacks, like the enable body scroll one, to not be called as some actions will do: `chat.activeMessage = null;` causing the message actions menu to be instantly destroyed.
This method name is a bit confusing; with_secure_uploads implies
it may return a block or something with the uploads of the post,
and has_secure_uploads implies that it's checking whether the post
is linked to any secure uploads.
should_secure_uploads? communicates the true intent of this method --
which is to say whether uploads attached to this post should be
secure or not.
The complexity of the situation is that we don't want to load faker into production by default but fabricators and styleguide are available on production.
This is made possible through app/assets/javascripts/discourse/app/lib/load-faker.js which contains a function to ensure faker is loaded asynchronously (loadFaker) and another function to access the loaded faker (getLoadedFaker).
Note 1: this commit also refactors fabricators to have access to context and use faker where possible
Note 2: this commit moves automation to admin bundle
---------
Co-authored-by: David Taylor <david@taylorhq.com>
When selecting messages to move to a new channel, if any of the selected messages is the original message of a thread, the entire thread, including all its replies, will be moved to the destination channel
Adds the new quick menu for bookmarking. When you bookmark
a post (chat message behaviour will come later) we show this new quick
menu and bookmark the item straight away.
You can then choose a reminder quick option, or choose Custom... to open
the old modal. If you click on an existing bookmark, we show the same quick menu
but with Edit and Delete options.
A later PR will introduce a new bookmark modal, but for now we
are using the old modal for Edit and Custom... options.
The fix is to actually wait for the bottom arrow to show before appending a new message, otherwise sometimes it goes too fast, and we create a new message while the scroll has not ended yet, making the arrow not visible yet.
This commit also uses this opportunity to move from `50.times.map {}` to `Fabricate.times(50, ...)` in this spec file.
Some of the properties, like 'categoriesById', 'parentCategory' and
'subcategories', were updated manually when categories were loaded.
This was not ideal because it required a lot of code to keep the
objects in sync and some of the properties were not updated correctly.
Why this change?
`expect(page.title).to starts_with("...")` does not rely on capybara
waiters. This commit switches us to use `have_title` instead which will
rely on Capybara waiters.
We need to scroll lock textareas when the keyboard is visible, otherwise they might become unusable if another element is body scroll locked on the page (eg: channels messages).
Note this commit is also slightly simplifying the code.
The expected behavior when receiving a message is the following:
- if user is at the bottom of the screen, scroll and append message
- if user is not at the bottom of the screen, don't scroll, show arrow and don't append message
Why this change?
When the site setting for chat_max_direct_message_users is set to 1, it is expected that users can have a 1:1 chat with other users. However, since the current user is counting as 1 user it makes starting a new chat impossible.
This change hands this validation off to DirectMessageChannel::MaxUsersExcessPolicy which handles the count correctly by filtering out the current user.
This commit is making the following changes:
- replaces `mobile-keyboard` initializer and `chat-vh` with a new template-less component: `d-vh`
- ensures body scroll lock is released when page/tab focus changes
- correctly locks body on chat channels and chat threads when composer is focused
- removes `bodyScrollFix` as we now use body scroll lock
- `onViewportResize` has been debounced to ensure it's not a bad performance vector
- adds a reverse option do body scroll lock, this is made to support reversed scroll areas (like chat channels and threads)
---------
Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
This enables the following in Discourse AI
```
plugin.register_modifier(:chat_allowed_bot_user_ids) do |user_ids, guardian|
if guardian.user
mentionables = AiPersona.mentionables(user: guardian.user)
allowed_bot_ids = mentionables.map { |mentionable| mentionable[:user_id] }
user_ids.concat(allowed_bot_ids)
end
user_ids
end
```
some bots that are id < 0 need to be discoverable in search otherwise people can not talk to them.
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
- Converts all header buttons to use `<DButton`
- Updates `<DButton` to render `<a href=` tags when `@href` is passed (previously it was rendering a `<button`, and then using JS to route when clicked)
We were incorrectly using `return` in a block which was causing exceptions at runtime. These exceptions were not causing much issues as they are in defer block.
While working on writing a test for this specific case, I noticed that our `upsert_custom_fields` function was using rails `update_all` which is not updating the `updated_at` timestamp. This commit also fixes it and adds a test for it.
The issue:
When the current user disables chat from within user preferences, the chat button still appears when clicking another user’s profile picture to open the user card. This is also the case when the current user has chat enabled but the target user has disabled chat.
After this change:
- when a user disables chat in preferences, the chat button should not be displayed when opening a user card or visiting profiles of other users.
- when chat is enabled in preferences but another user disables chat, the chat button should not appear on their user card or profile
When adding the new "illegal" flag option, we missed adding the translation to the chat plugin, so when flagging a chat message (rather than a post) you'd see [en.chat.flags.illegal]. This PR fixes that.
This commit fixes an issue where the following happens:
1. You open /admin as a member of the admin_sidebar_enabled_groups
1. You then click the chat icon in the header when you prefer to have
drawer open, or if you just minimise chat into drawer after it opens
fullscreen
1. You lose the admin sidebar panel, and are reset instead to the main
panel
Also included is a bit of refactoring to make it so the forcing of
admin sidebar state is in one place.
Prior to this change we would pre-load all the user channels which making initial page load slower. This change will make them be loaded right after initial load. In the past this was not possible as the channels would have to be loaded on each page transition. However since about a year, we made the channels to be cached on the frontend and no other request will be needed.
I have decided for now to not show a loading state in the sidebar as I think it would be noise, but we can reconsider this later.
Note given we don't have the channels loaded at first certain things where harder to accomplish. The biggest UX change of this commit is that we removed all the complex logic of computing the best channel to display when you load /chat. We will now store the id of the last channel you visited and will use this id to decide which channel to show.
This commit makes it so the site settings filter controls and
the list of settings input editors themselves can be used elsewhere
in the admin UI outside of /admin/site_settings
This allows us to provide more targeted groups of settings in different
UI areas where it makes sense to provide them, such as on plugin pages.
You could open a single page for a plugin where you can see information
about that plugin, change settings, and configure it with custom UIs
in the one place.
In future we will do this in "config areas" for other parts of the
admin UI.
With the new admin sidebar restructure, we have a link to "Installed plugins". We would like to ensure that when the admin is searching for a plugin name like "akismet" or "automation" this link will be visible. Also when entering the plugins page, related plugins should be highlighted.
This commit adds new plugin show routes (`/admin/plugins/:plugin_id`) as we move
towards every plugin having a consistent UI/landing page.
As part of this, we are introducing a consistent way for plugins
to show an inner sidebar in their config page, via a new plugin
API `register_admin_config_nav_routes`
This accepts an array of links with a label/text, and an
ember route. Once this commit is merged we can start the process
of conforming other plugins to follow this pattern, as well
as supporting a single-page version of this for simpler plugins
that don't require an inner sidebar.
Part of /t/122841 internally
**TL;DR:** Refactor autocomplete to use async markdown parsing for code block detection.
Previously, the `inCodeBlock` function in `discourse/app/lib/utilities.js` used regular expressions to determine if a given position in the text was inside a code block. This approach had some limitations and could lead to incorrect behavior in certain edge cases.
This commit refactors `inCodeBlock` to use a more robust algorithm that leverages Discourse's markdown parsing library.
The new approach works as follows:
1. Check if the text contains any code block markers using a regular expression.
If not, return `false` since the cursor can't be in a code block.
1. If potential code blocks exist, find a unique marker character that doesn't appear in the text.
1. Insert the unique marker character into the text at the cursor position.
1. Parse the modified text using Discourse's markdown parser, which converts the markdown into a tree of tokens.
1. Traverse the token tree to find the token that contains the unique marker character.
1. Check if the token's type is one of the types representing code blocks ("code_inline", "code_block", or "fence").
If so, return `true`, indicating that the cursor is inside a code block.
Otherwise, return `false`.
This algorithm provides a more accurate way to determine the cursor's position in relation to code blocks, accounting for the various ways code blocks can be represented in markdown.
To accommodate this change, the autocomplete `triggerRule` option is now an async function.
The autocomplete logic in `composer-editor.js`, `d-editor.js`, and `hashtag-autocomplete.js` has been updated to handle the async nature of `inCodeBlock`.
Additionally, many of the tests have been refactored to handle async behavior. The test helpers now simulate typing and autocomplete selection in a more realistic, step-by-step manner. This should make the tests more robust and reflective of real-world usage.
This is a significant refactor that touches multiple parts of the codebase, but it should lead to more accurate and reliable autocomplete behavior, especially when dealing with code blocks in the editor.
> Written by an 🤖 LLM. Edited by a 🧑💻 human.
On mobile, when viewing the My Threads area, each thread will show:
- The avatar of the last responder in the thread, overlaid with the chat thread symbol to visually distinguish this area from DMs.
- Either the thread title, where applicable, or the first message of the thread, truncated to fit on one line.
- The channel where the thread originated.
- The last message sent in the thread, truncated to fit on one line.
- When the last message was sent in the thread.
---------
Co-authored-by: David Battersby <info@davidbattersby.com>
- The thread preview is now a regular link and can be right clicked
- left gutter date, and regular date of a thread message will not correctly link to the thread's message
Previously services would let you define a high level default `def default_actions_for_service; end` which would define various handlers like `on_success`, after months of usage we consider the cons are superior to the pros here.
Two mains cons:
- people would often not understand where the handling was coming from
- it's easy to miss a case when you write your specs
Forcing a thread will work even in channel which don't have `threading_enabled` or in direct message channels.
For now this feature is only available through the `ChatSDK`:
```ruby
ChatSDK::Message.create(in_reply_to_id: 1, guardian: guardian, raw: "foo bar baz", channel_id: 2, force_thread: true)
```
Prior to this fix if a user had started to reply to a message without actually sending a message, the thread would still be created and we would end up listing it in the threads list of a channel.
This commit also improves adds thread and thread_replies_count to the 4th parameter of the chat_message_created event.
* UX: chat message creator scss cleanup + design tweak to username display
* add user status with live updates to modal
* show user status description in modal
* add tests for user status
* UX: add user-status styling to chat message creator
---------
Co-authored-by: David Battersby <info@davidbattersby.com>
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Prior to this fix we were checking if user was not part of a group which allows to chat, but we were not checking if this user was part of groups who can use direct messages.
Prior to this fix clicking <kbd>x</kdb> on a channel row would effectively leave the channel on server side, but it wouldn't disappear from the screen before a page refresh.
With the adjustments of `btn-transparent` in https://github.com/discourse/discourse/pull/24666, there are more buttons that could use this class instead of `btn-flat`. This mostly relates to `x` close buttons, but also includes composer and chat toggles.
The primary difference between these styles is that `btn-transparent` never has a background, where `btn-flat` may have a hover or focus background.
When we send a bookmark reminder, there is an option to delete
the underlying bookmark. The Notification record stays around.
However, if you want to filter your notifications user menu
to only bookmark-based notifications, we were not showing unread
bookmark notifications for deleted bookmarks.
This commit fixes the issue _going forward_ by adding the
bookmarkable_id and bookmarkable_type to the Notification data,
so we can look up the underlying Post/Topic/Chat::Message
for a deleted bookmark and check user access in this way. Then,
it doesn't matter if the bookmark was deleted.
`chat_preferred_mobile_index` allows to set the preferred default tab when loading chat on mobile.
Current choices are:
- channels
- direct_messages
- my_threads
Users can hide their public profile and presence information by checking
“Hide my public profile and presence features” on the
`u/{username}/preferences/interface` page. In that case, we also don't
want to return user status from the server.
This work has been started in https://github.com/discourse/discourse/pull/23946.
The current PR fixes all the remaining places in Core.
Note that the actual fix is quite simple – a5802f484d.
But we had a fair amount of duplication in the code responsible for
the user status serialization, so I had to dry that up first. The refactoring
as well as adding some additional tests is the main part of this PR.
```ruby
ChatSDK::Message.start_stream(message_id: 1, guardian: guardian)
ChatSDK::Message.stream(raw: "foo", message_id: 1, guardian: guardian)
ChatSDK::Message.stream(raw: "bar", message_id: 1, guardian: guardian)
ChatSDK::Message.stop_stream(message_id: 1, guardian: guardian)
```
Generally speaking only admins or owners of the message can interact with a message. Also note, Streaming to an existing message with a different user won't change the initial user of the message.
Prior to this fix, if the last message of a thread had been made by a deleted user it would cause an exception as we would have no user to display, this commit uses a solution we have been using at other places: the null pattern, through the use of `Chat::NullUser.new`.
Plugins can now register this modifier:
```ruby
register_modifier(:chat_can_create_direct_message_channel) do |user, target_users|
# your logic which should return true or false
end
```
Prior to this fix the scroll was ignored when clicking the arrow bottom which would prevent the call to update last read. This fix manually calls update last read in this case and adds a test for it.
In safe mode plugins are not loaded, so the plugin admin
routes are not loaded. This was causing errors in the
admin sidebar because we are trying to show links to the plugin
admin routes.
This fixes the issue by just not adding the plugin links if
we are in safe mode.
If a user had `123456789` as username, it could be passed to the query as a number and the query would fail as it expects a string.
Also applies the same fix to groups.
Why this change?
We noticed that running `LOAD_PLUGINS=1 rspec --seed=38855 plugins/chat/spec/system/chat_new_message_spec.rb` locally
results in the system tests randomly failing. When we inspected the
request logs closely, we noticed that a `/presence/get` request from a
previous rspec example was being processed when a new rspec example is
already being run. We know it was from the previous rspec example
because inspecting the auth token showed the request using the auth
token of a user from the previous example. However, when a request using
an auth token from a previous example is used it ends up logging out the
same user on the server side because the user id in the cookie is the same
due to the use of `fab!`.
I did some research and there is apparently no way to wait until all
inflight requests by the browser has completed through capybara or
selenium. Therefore, we will add an identifier by attaching a cookie to all non-xhr requests so that
xhr requests which are triggered subsequently will contain the cookie in the request.
In the `BlockRequestsMiddleware` middleware, we will then reject any
requests when the value of the identifier in the cookie does not match the current rspec's example
location.
To see the problem locally, change `Auth::DefaultCurrentUserProvider.find_v1_auth_cookie` to the following:
```
def self.find_v1_auth_cookie(env)
return env[DECRYPTED_AUTH_COOKIE] if env.key?(DECRYPTED_AUTH_COOKIE)
env[DECRYPTED_AUTH_COOKIE] = begin
request = ActionDispatch::Request.new(env)
cookie = request.cookies[TOKEN_COOKIE]
# don't even initialize a cookie jar if we don't have a cookie at all
if cookie&.valid_encoding? && cookie.present?
puts "#{env["REQUEST_PATH"]} #{request.cookie_jar.encrypted[TOKEN_COOKIE]&.with_indifferent_access}"
request.cookie_jar.encrypted[TOKEN_COOKIE]&.with_indifferent_access
end
end
end
```
After which run the following command: `LOAD_PLUGINS=1 rspec --format documentation --seed=38855 plugins/chat/spec/system/chat_new_message_spec.rb`
It takes a few tries but the last spec should fail and you should see something like this:
```
assets/chunk.c16f6ba8b6824baa47ac.d41d8cd9.js {"token"=>"37d995a4b65395d3b343ec70fff915b4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591735}
/assets/chunk.050148142e1d2dc992dd.d41d8cd9.js {"token"=>"37d995a4b65395d3b343ec70fff915b4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591735}
/chat/api/channels/527/messages {"token"=>"37d995a4b65395d3b343ec70fff915b4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591735}
/uploads/default/test_0/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_512x512.png {"token"=>"37d995a4b65395d3b343ec70fff915b4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591735}
redirects to existing chat channel
redirects to chat channel if recipients param is missing (PENDING: Temporarily skipped with xit)
with multiple users
/favicon.ico {"token"=>"9a75c114c4d3401509a23d240f0a46d4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591736}
/chat/new-message {"token"=>"9a75c114c4d3401509a23d240f0a46d4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591736}
/presence/get {"token"=>"37d995a4b65395d3b343ec70fff915b4", "user_id"=>3382, "username"=>"bruce0", "trust_level"=>1, "issued_at"=>1708591735}
```
Note how the `/presence/get` request is using a token from the previous example.
Co-authored-by: David Taylor <david@taylorhq.com>
We have separated and combined modes for sidebar panels.
Separated means the panels show only their own sections,
combined means sections from all panels are shown.
The admin sidebar only shows its own panels, so it must set
the mode to separated; however when we navigate to chat or
home we must revert to the initial mode setttings.
When hiding/showing the sidebar, as is the case on mobile
and using the toggle in the top left on desktop, we delete
and recreate the ember component on the page. This causes
the `sections` for each sidebar panel to get re-evaluated
every time.
For the admin sidebar, this means that we were constantly
re-adding the plugin links to the sidebar, causing duplication.
This can be fixed by just adding @cached to the getter for
sections.
This feature adds the functionality to start a new chat directly from the URL using query params.
The format is: /chat/new-message?recipients=buford,jona
The initial version of this feature allows for the following:
- Open an existing direct message channel with a single user
- Create a new direct message channel with a single user (and auto redirect)
- Create or open a channel with multiple users (and auto redirect)
- Redirects to chat home if the recipients param is missing
This commit introduces the possibility to stream messages. To allow plugins to use streaming this commit also ships a `ChatSDK` library to allow to interact with few parts of discourse chat.
```ruby
ChatSDK::Message.create_with_stream(raw: "test") do |helper|
5.times do |i|
is_streaming = helper.stream(raw: "more #{i}")
next if !is_streaming
sleep 2
end
end
```
This commit also introduces all the frontend parts:
- messages can now be marked as streaming
- when streaming their content will be updated when a new content is appended
- a special UI will be showing (a blinking indicator)
- a cancel button allows the user to stop the streaming, when cancelled `helper.stream(...)` will return `false`, and the plugin can decide exit early
Affects the following settings:
delete_all_posts_and_topics_allowed_groups
experimental_new_new_view_groups
enable_experimental_admin_ui_groups
custom_summarization_allowed_groups
pm_tags_allowed_for_groups
chat_allowed_groups
direct_message_enabled_groups
chat_message_flag_allowed_groups
This turns off client: true for these group-based settings,
because there is no guarantee that the current user gets all
their group memberships serialized to the client. Better to check
server-side first.
Why this change?
The tests are consistently flaky and failing with the following error:
```
CapybaraTimeoutExtension::CapybaraTimedOut:
This spec passed, but capybara waited for the full wait duration (10s) at least once. This will slow down the test suite. Beware of negating the result of selenium's RSpec matchers.
```
In certain cases, chat channels may have empty slugs, it happens when:
1. The `slug_generation_method` setting is set to `None`
2. `slug_generation_method` is set to `ASCII` and a channel with
a Unicode name and an empty slug is created (in this case, the code
that creates channels tries to generate a slug and fallbacks to an empty slug)
At the moment, we have a unique index on the `chat_channels.slug` column
which leads to errors when creating several channels with empty slugs
(Discourse is able to create one such channel, but when trying to create
the second one fails because of the unique constraint). This PR fixes that
by adding a `where` condition to the index. Slugs still have to be unique,
but now many channels may have empty slugs.
This fix is similar to the one we made to the category slugs – 7ba914f1e1.
This commit was created with a combination of the ember-native-class-codemod and manual cleanup.
User-status-related functionality was previously encapsulated in its own `User.reopen` call, which is essentially an 'inline mixin'. This commit refactors it into a utility class, with an instance accessible on `User#statusManager`
The service `Chat::CreateMessage` will now accept `context_post_ids` and `context_topic_id` as params. These values represent the topic which might be visible when sending a message (for now, this is only possible when using the drawer).
The `DiscourseEvent` `chat_message_created` will now have the following signature:
```ruby
on(:chat_message_created) do | message, channel, user, meta|
p meta[:context][:post_ids]
end
```
Channels can include emojis in front of the channel title which causes problems when sorting.
Using the channel slug is a more reliable way to sort and avoid these kind of issues.
This change will sort channels by activity on mobile, with preference to those with urgent or unread messages.
Channels with mentions will appear first, followed by channels with unread messages, then finally everything else sorted by the channel title (alphabetically).
This commit includes several changes to make hashtags work when "lazy
load categories" is enabled. The previous hashtag implementation use the
category colors CSS variables, but these are not defined when the site
setting is enabled because categories are no longer preloaded.
This commit implements two fundamental changes:
1. load colors together with the other hashtag information
2. load cooked hashtag data asynchronously
The first change is implemented by adding "colors" to the HashtagItem
model. It is a list because two colors are returned for subcategories:
the color of the parent category and subcategory.
The second change is implemented on the server-side in a new route
/hashtags/by-ids and on the client side by loading previously unseen
hashtags, generating the CSS on the fly and injecting it into the page.
There have been minimal changes outside of these two fundamental ones,
but a refactoring will be coming soon to reuse as much of the code
and maybe favor use of `style` rather than injecting CSS into the page,
which can lead to page rerenders and indefinite grow of the styles.
When we show the links to installed plugins in the admin
sidebar (for plugins that have custom admin routes) we were
previously only doing this if you opened /admin, not if you
navigated there from the main forum. We should just always
preload this data if the user is admin.
This commit also changes `admin_sidebar_enabled_groups` to
not be sent to the client as part of ongoing efforts to
not check groups on the client, since not all a user's groups
may be serialized.
The leave channel button is cut off when accessing the channel settings page on mobile.
This change adds additional padding to the bottom of the channel settings page when accessing via iPad/PWA/Hub.
Chat mobile has separate routes for channels and direct messages. However on desktop we want to prevent these routes from being accessible as they aren't intended to be used by chat in full-page or drawer mode on desktop.
We had two issues which were present for a long time I think:
- one that impacts both core discourse and chat. We were not setting top on the header when `footer-nav-ipad` was present, meaning that you could make it scroll under if you try to scroll up by putting your finger on the discourse header
- one that impacted only chat. It's also present in core, but in core it's not a probem because we don't have a fixed height div. The body height was higher than the screen which would cause a second scrollbar to appear and would slightly break layout, if you scroll on this scrollbar (body).
* UX: increase font-size of last message + decrease emoji size
* UX: decrease size of username emoji
* UX: Mobile chat index cleanup
* UX: decrease size of chat-channel-title in thead list
Chat should follow the same convention as topics, where invalid mentions
are not styled the same as valid mentions. This PR makes chat use such styling.
I'm following the same pattern that we use for invalid mentions in posts –
9bd6523581/app/assets/stylesheets/common/base/topic-post.scss (L1285-L1288)
This way it'll be easier to dry it up if we decide to do that at some point.
We've changed access settings to be group membership based rather than based on the TL value directly. We kept both conditions here while we updated any plugins and themes. It should now be safe to remove.
This commit adds a link to the original message of a thread, this link will:
- load the channel message and highlight it while keeping thread panel open on desktop
- open the channel and highlight the message in mobile (and close thread panel, as mobile never shows channel and thread in the same view)
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
Since https://github.com/discourse/discourse/pull/25501 this behavior was broken. This PR attempts to fix it by being more fine grain.
Also note that this PR is moving `footer-nav-ipad` and `footer-nav-visible` to the `html` element and not the `body`. It makes more sense as we are already adding most of other global state class like `keyboard-visible` to the `html` element.
Tested on:
- chrome desktop
- safari ios - iphone
- PWA ios - iphone
- PWA ios - ipad
- DiscourseHub iphone
This commit sets a default of 0px for `--footer-nav-height` and set it only when `body.footer-nav-visible` allowing us to safely use `--footer-nav-height` wherever it will be needed if set.
We usually don't enforce foreign key relationships on the database level.
Because of that, occasionally it's possible to see a chat message that
references to a non-existent chat_channel or user. MessagesExporter
failed in such case before, this PR fixes that.
When reaching the top of a thread, the full thread title will be displayed if it was too long to fit.
It works in mobile, drawer mode, and fullscreen.
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
For performance reasons we don't automatically add fabricated users to trust level auto-groups. However, when explicitly passing a trust level to the fabricator, in 99% of cases it means that trust level is relevant for the test, and we need the groups.
This change makes it so that when a trust level is explicitly passed to the fabricator, the auto-groups are refreshed. There's no longer a need to also pass refresh_auto_groups: true, which means clearer tests, fewer mistakes, and less confusion.
This change adds notification badges to the new footer tabs on mobile chat, to help users easily find areas where there’s new activity to review.
When on mobile chat:
- Show a badge on the DMs footer when there is unread activity in DMs.
- Show a badge on the Channels footer tab when there is unread channel activity.
- Show a badge on the Threads footer tab when there is unread activity in a followed thread.
- Notification badges should be removed once the unread activity is viewed.
Additionally this change will:
- Show green notification badges for channel mentions or DMs
- Show blue notification badges for unread messages in channels or threads
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
The value [Yesterday] was a fixed string which couldn't be translated. Also removes nextWeek/nextDay which make no sense for dates which are always supposed to be in the past.
We're changing the implementation of trust levels to use groups. Part of this is to have site settings that reference trust levels use groups instead. It converts the min_trust_level_to_tag_topics site setting to tag_topic_allowed_groups.
We have all these calls to Group.refresh_automatic_groups! littered throughout the tests. Including tests that are seemingly unrelated to groups. This is because automatic group memberships aren't fabricated when making a vanilla user. There are two places where you'd want to use this:
You have fabricated a user that needs a certain trust level (which is now based on group membership.)
You need the system user to have a certain trust level.
In the first case, we can pass refresh_auto_groups: true to the fabricator instead. This is a more lightweight operation that only considers a single user, instead of all users in all groups.
The second case is no longer a thing after #25400.
Having the admin sidebar code in an instance initializer is not
ideal because:
* It runs during app boot which may not even be necessary based on site settings
* It makes it hard for plugins to register additional links in time without resorting
to before/after initializer gymnastics
This PR moves the admin sidebar into a lib and creates the panel
in custom-sections.js, then the sections and links are loaded when
the main sidebar component is rendered, which leaves plugins enough
time to add additional links in an initializer.
---------
Co-authored-by: David Taylor <david@taylorhq.com>
Fixes an issue with delayed rendering of the My Threads tab in chat mobile footer.
Previously we made an ajax request to determine the number of threads a user had before rendering the tab, however it is much faster (and better UX) if we can rely on a site setting for this.
The new chat_threads_enabled site setting is set to true when the site has chat channels with threading enabled.
This bug was causing broken layout when using the `header_dropdown` setting instead of `sidebar` as we were rendering `<div class="channels-list"></div>` two times.
In normal use, this `text()` getter is never called. However, when running with the Ember inspector, it is eagerly evaluated and hits throws the "not implemented" error in the base class.
* FIX: Minor bookmark issues
* We were showing "missing %{name} value" when the name for the
bookmark was undefined with title translations
* There was no way to see the bookmark details on hover in chat
for a message where the bookmark icon was in the left gutter.
We can show the title on the bookmark button in the chat message
actions instead.
* Minor fix
* DEV: Test fix
Due to an incorrect test the previous service was incorrectly implementing the map, and was most importantly not deleting the state when reaching bottom.
It was broken on iOS PWA, when you had the keyboard open and would leave the app. When you came back the body was scrolled and it was looking buggy until you close/reopen keyboard.
This commit attempt to reposition the page correctly 200ms after the tab is visible again.
This commit creates a shared implementation of the dates computation and moves all the logic (new messages since last visit and dates separator into one single component <ChatMessageSeparator />).
The frontend tests have been removed and only a single system spec has been added for threads as everything is sharing the same implementation and the existing channel specs should catch any regression.
Allows users to create DMs by selecting groups as a target. It also allows adding user groups to an existing chat
- When creating the channel, it expands the user group and adds all its members with chat enabled to the channel.
- After creation, there's no difference between adding a group or adding its members individually.
- Users can add multiple groups and users simultaneously.
- There are UI validations; the member count preview updates according to the member count of added groups, and it does not allow users to add more members than SiteSetting.chat_max_direct_message_users."
At the moment, when someone is mentioning a group, or using here or
all mention, we create a chat_mention record per user. What we want
instead is to have special kinds of mentions, so we can create only one
chat_mention record in such cases. This PR implements that.
Note, that such mentions will still have N related notifications, one
notification per a user. We don't expect we'll have performance
problems on the notifications side, but if at some point we do, we
should be able to solve them on the side of notifications
(notifications are handled in jobs, also some little delays with
the notifications are acceptable, so we can make sure notifications
are properly queued, and that processing of every notification is
fast enough to make delays small enough).
The preparation work for this PR was done in fbd24fa, where we make
it possible for one mention to have several related notifications.
A pretty tricky part of this PR is schema and data migration, I've explained
related details inline on the migration files.
This change moves the "Channels" tab to first position in the chat footer nav, and loads it as the default page when opening chat for the first time on mobile.
This update adds three tabs to the bottom of the chat overlay to make it easier for users to navigate chat on mobile.
As a result of this change:
- Direct Messages are now shown separately from public channels on mobile
- My Threads has now moved from the channel list to it's own tab on mobile
- My Threads can still be accessed on desktop via the sidebar and within the drawer channel list
- Chat back button has been updated to navigate to the correct tab (for both channels and threads)
Some special cases:
- If DMs are not used then the tab is not rendered
- If the user has no threads then the tab is not rendered
- If both the tabs for DMs and Threads aren't available then the whole footer will not be rendered
- Chat footer is only shown on the listing pages (DMs, Channels, My Threads)
---------
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
We're changing the implementation of trust levels to use groups. Part of this is to have site settings that reference trust levels use groups instead. It converts the min_trust_level_to_tag_topics site setting to tag_topic_allowed_groups.
This commit:
- increased the size of chat-header-offset from 46px to 56px on mobile
- tweaked navbar padding
- Increased the gap between back button and title; this means no more perfect alignment, but I think that's perfectly fine and a fair trade off for an easier click target for the back button without fear of hitting the title
This regressed in 2791e75072. That commit
fixed subfolder URLs in general, but the `full_url` was adding the
subfolder prefix a second time, thus breaking this URL in emails.